Interface pour micro PDM
OBJECTIF :
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, 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
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 for i in 0 to (2**SIZE)-1 loop
pile(i)<="00000000";
end loop;
elsif rising_edge(clk) then
for i in 0 to 2**SIZE-2 loop
pile(i+1) <= pile(i) ;
end loop;
pile(0) <= new_ech;
-- Moyenne
acc_v := ( others => '0') ;
for i in 0 to 2**SIZE-1 loop
acc_v := acc_v + pile(i);
end loop;
d_out <= std_logic_vector(acc_v( 7+SIZE downto SIZE )); -- Division par N (2^SIZE) => décalage à droite de SIZE bits
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 fréquence maximale du signal à afficher est 1kHz
- Il faut envoyer 10 bits pour chaque valeur
- Pour un baudrate de 230400 bits/s, on enverra au plus vite 23040 valeurs/s
- cela correspond 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
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 )
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 )