Interface pour micro PDM
OBJECTIF :
- Concevoir sur le FPGA une interface permettant de filtrer un signal de type PDM ( Pulse-density modulation ) issu d’un micro.
- Ce signal filtré sera ensuite envoyé sur le PC via une liaison UART, afin de le visualiser.
Datasheet MEMS Microphone MP34DT01-M
Signal PDM ( Pulse Density Modulation )
Un signal PDM résulte d’une conversion sigma-delta 1 bit d’un signal analogique.
L’application d’un filtre passe-bas ou moyenneur à un signal PDM permet de retrouver les points du signal analogique initial :
Architecture du Système
Nous allons utiliser le FPGA afin de réaliser une interface pour le micro PDM.
Cette interface doit filtrer le signal PDM issu du micro, et envoyer les échantillons filtrés sur une liaison RS232.
Les échantillons seront ensuite affichés sur le PC.
C’est également au FPGA de fournir une horloge ( qu’on fixera à 1MHz ) au micro PDM.
Filtre Moyenneur
Afin de réaliser un filtre simple, nous allons considérer le filtre moyenneur.
Exemple d’un filtre moyenneur d’ordre 4
J’applique sur l’entrée du filtre un signal PWM à valeur moyenne constante :
Exemple d’un filtre moyenneur d’ordre 8
Codage VHDL
Une proposition de codage pour le filtre moyenneur est la suivante :
- Le signal d’entrée vaut 0 ou 1, on associe à ces valeurs respectivement 0 et 255 pour faire le calcul de la moyenne
- On utilise une pile pour stocker les valeurs
- La mise à jour ou l’initialisation des piles est réalisées dans des boucles ; la boucle est réalisée intégralement lors d’un process.
- l’accumulation d’une valeur suppose la mise à jour de cette valeur plusieurs fois dans un même process ; d’où l’utilisation d’une variable acc_v.
- La division par 2^N correspond à un décalage ( shift right ) ; en VHDL, il suffit de récupérer une partie du vecteur initial ( affectation de d_out ).
Filtre moyenneur
entity average_filter is
generic( SIZE : integer := 3 ); -- Moyenne sur 2^SIZE échantillons
port(
clk : in std_logic;
reset : in std_logic;
d_in : in std_logic;
d_out : out std_logic_vector(7 downto 0)
);
end average_filter;
architecture arch_average_filter of average_filter is
type t_pile is array(integer range 0 to 2**SIZE-1) of unsigned(7 downto 0);
signal pile : t_pile;
signal new_ech : unsigned(7 downto 0);
begin
-- Détection de la nouvelle entrée d_in
new_ech <= "11111111" when d_in = '1' else "00000000";
process(clk, reset)
variable acc_v : unsigned(SIZE+7 downto 0);
begin
if reset = '1' then
-- Réinitialisation de la pile à zéro
for i in 0 to (2**SIZE)-1 loop
pile(i) <= "00000000";
end loop;
elsif rising_edge(clk) then
-- Décalage de tous les éléments de la pile
for i in 0 to 2**SIZE-2 loop
pile(i+1) <= pile(i);
end loop;
-- Ajout du nouvel échantillon à la position 0
pile(0) <= new_ech;
-- Calcul de la moyenne
acc_v := (others => '0');
for i in 0 to 2**SIZE-1 loop
acc_v := acc_v + pile(i);
end loop;
-- Division par N (2^SIZE) en décalant à droite
d_out <= std_logic_vector(acc_v(7+SIZE downto SIZE));
end if;
end process;
end arch_average_filter;Le filtre a un paramètre de généricité ; la taille définitive est indiquée lors de l’instanciation :
average_filter0 : entity average_filter generic map(8) port map (
clk => clk_micro,
reset => reset,
d_in => P1B1,
d_out => data_to_send
);Ordre du filtre et fréquence du signal PDM
Nous avons pu constater sur les figures précédentes que plus l’ordre du filtre est élevé, meilleure est la précision.
Cependant un ordre trop élevé ( correspondant à plus d’un quart de la fréquence moyenne du signal d’entrée), aurait pour effet
de faire disparaitre ce signal.
Si l’on considère une horloge du filtre de fréquence 1 MHz, et des fréquences moyennes des signaux d’entrée comprises entre 0 et 1000Hz :
,
On peut supposer un bon filtrage pour un ordre de filtre de 256.
Test du Filtre Moyenneur en Simulation
Pour un filtre d’ordre 256 avec une fréquence 1 MHz, on peut tester le filtre avec un signal PWM de fréquence
On peut envisager l’utilisation d’une boucle dans le fichier icebreaker_tb.vhd ( ici signal pwm de rapport cyclique 0.5 ) :
simulation filtre
stimuli: process
begin
...
for i in 0 to 500 loop
for j in 0 to 127 loop
P1B1 <= '1';
wait for 1 us;
end loop;
for k in 0 to 127 loop
P1B1 <= '0';
wait for 1 us;
end loop;
end loop;
wait;
end process;Système Complet
Considérations Temporelles
La sérialisation des données sur l’UART implique de respecter un temps minimum pour l’envoi de chaque point.
Par rapport au signal PDM filtré disponible dans le FPGA, on enverra un point tous les T_send.
En tenant compte du débit sur la ligne, l’objectif est de pouvoir observer un nombre suffisant de points sur le PC,
le pire des cas étant quand la fréquence du signal filtré est maximale.
- La fréquence maximale du signal à afficher est 1kHz, soit une période T=1ms.
- Il faut envoyer 10 bits pour chaque valeur
- Pour un baudrate de 230400 bits/s, on enverra au plus vite 23040 valeurs/s
- Ce qui fait, pendant T=1ms,
- On a alors 23 points pour afficher une période du signal d’entrée.
Si l’on veut choisir la fréquence T_send, on peut utiliser le compteur associé à la machine à états.
Ici T_send = 50us ( , ce qui correspond alors à 20 points pour afficher une période du signal d’entrée.
Affichage PC
Les valeurs envoyées par la FPGA au PC sont des valeurs ( et non des chaines de caractères ).
On ne peut pas utiliser directement un terminal liaison série ( qui nécessite la réception de caractères ).
Nous pouvons donc utiliser les programmes python suivant :
Affichage dans un Terminal
import serial
# Configuration du port série
port = '/dev/ttyUSB1' # à adapter
baudrate = 230400 # à adapter
try:
with serial.Serial(port, baudrate, timeout=1) as ser:
print(f"Lecture des données depuis {port} à {baudrate} bauds...")
while True:
data = ser.read(1) # Lecture d’un octet
if data:
value = int.from_bytes(data, byteorder='big') # Convertir en entier
print(f"{value}")
except serial.SerialException as e:
print(f"Erreur d’ouverture du port série : {e}")
except KeyboardInterrupt:
print("Arrêt par l’utilisateur.")
Affichage Graphique
import serial
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from collections import deque
# Configuration port série
port = '/dev/ttyUSB1' # à adapter
baudrate = 230400 # à adapter
# Initialisation fenêtre graphique
plt.style.use('ggplot')
fig, ax = plt.subplots()
ax.set_title("Données reçues en temps réel")
ax.set_xlabel("Nombre d'échantillons")
ax.set_ylabel("Valeur reçue (0-255)")
ax.set_ylim(0, 255)
# Buffer circulaire pour afficher les dernières valeurs
max_len = 200
data_buffer = deque([0]*max_len, maxlen=max_len)
line, = ax.plot(range(max_len), data_buffer)
def update(frame):
n = ser.in_waiting
if n:
data = ser.read(n)
data_buffer.extend(data) # ajoute tous les octets reçus
line.set_ydata(data_buffer)
return line,
try:
ser = serial.Serial(port, baudrate, timeout=0.01)
ani = animation.FuncAnimation(fig, update, interval=20) # appel toutes les 20 ms
plt.show()
except serial.SerialException as e:
print(f"Erreur port série : {e}")
except KeyboardInterrupt:
print("Arrêt demandé par utilisateur.")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
Test avec le générateur de signaux Digilent Discovery
Dans l’immédiat et pour le confort de tous, le signal du micro peut être forcé directement par l’analyseur et générateur de signaux Digilent.
Il faut débrancher le micro et réaliser la connexion suivante :
|
|
|
Ouvrir le fichier gene_pdm.dwf3work avec le logiciel WaveForms.
Cette configuration permet de générer un signal PDM à valeur moyenne sinusoidale 1kHZ sur DIO0 ( fréquence PDM 1 MHz )
L’affichage avec print_serial.py devrait donner si tout est fonctionnel :
Test avec le micro PDM
Tester le système en appliquant un son au micro ( son à fréquence constante, fréquence entre 0 et 1kHz )