Interface pour micro PDM

Interface pour micro PDM

OBJECTIF :

micro_pdm.png

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.

sigma_delta.svg

L’application d’un filtre passe-bas ou moyenneur à un signal PDM permet de retrouver les points du signal analogique initial :

filtrage.svg

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.

presentation_systeme.svg


Filtre Moyenneur

Afin de réaliser un filtre simple, nous allons considérer le filtre moyenneur.

filtre_moyenneur.svg

Exemple d’un filtre moyenneur d’ordre 4

J’applique sur l’entrée du filtre un signal PWM à valeur moyenne constante :

signal_val_moy_cte.svg

Exemple d’un filtre moyenneur d’ordre 8

signal_val_moy_cte_2.svg

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.

filter_order.svg

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 :

Tsig=11000=1ms T_{sig} =\frac{1}{1000} = 1ms , TPDM=11.106=1usT_{PDM}= \frac{1}{1.10^6} = 1us 1.1031.106=1000 \rightarrow \frac{1.10^{-3}}{1.10^{-6}}=1000

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 2561.106256*1.10^{-6}

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

systeme_complet

systeme_complet.svg

fsm_sys.svg

Considérations Temporelles

considerations_temporelles.svg

  • 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 )