Implémenter un contrôleur PWM en VHDL : Conception, simulation et démo FPGA
La modulation de largeur d'impulsion (PWM) transforme une broche FPGA numérique en un contrôleur analogique puissant. En commutant l'alimentation à haute vitesse, le PWM délivre le courant moyen exact dont un dispositif a besoin, sans jamais générer une vraie tension analogique.
Les applications typiques du PWM incluent la sortie audio pour haut-parleurs, la gradation de LED, et le contrôle du couple de moteurs à induction tels que les servomoteurs, ventilateurs d'ordinateur, pompes, et moteurs brushless DC pour véhicules électriques.
Voir aussi : Contrôleur de servo RC utilisant PWM depuis une broche FPGA
Comment fonctionne le PWM
Quand une alimentation est basculée entre 100 % et 0 % à une fréquence élevée, le courant moyenné dans le temps vu par la charge est directement proportionnel à la fraction de temps où l'alimentation est active. Le diagramme suivant illustre le principe :

Cycle de travail (Duty Cycle)
Le cycle de travail définit combien de temps la sortie PWM reste haute pendant chaque période. Exprimé en pourcentage, il est calculé à partir de la valeur du compteur binaire :

Dans une implémentation VHDL, nous traitons le cycle de travail comme un entier non signé, nous donnant la résolution complète du compteur PWM. Un cycle de travail de 0 % éteint la sortie en permanence, tandis qu'un cycle de travail de 100 % la maintient continuellement active. La résolution atteignable est déterminée par la largeur du compteur.
Pour convertir la valeur binaire du cycle de travail en pourcentage, utilisez :
\mathit{duty\_cycle\_percentage} = \frac{\mathit{commanded\_duty\_cycle} * 100}{2^\mathit{pwm\_bits} - 1}
Fréquence PWM
La fréquence PWM est l'inverse de la période :
\mathit{pwm\_freq} = \frac{1}{\mathit{pwm\_period}}
Le choix de la bonne fréquence dépend du dispositif cible. Pour les LED, quelques centaines de hertz suffisent pour éviter le scintillement visible. Les moteurs brushless DC, en revanche, fonctionnent généralement dans la gamme des dizaines de kilohertz ; une fréquence trop basse peut produire des vibrations mécaniques, tandis qu'une fréquence trop élevée gaspille de l'énergie.

Comme les commutateurs MOSFET sont plus lents que la logique FPGA, faire fonctionner le PWM trop vite peut faire passer le transistor dans un état partiellement conducteur, ce qui dissipe de la chaleur supplémentaire.
Module générateur PWM
Voici une implémentation VHDL générique d'un contrôleur PWM qui peut être instancié avec n'importe quelle largeur de compteur et facteur de division d'horloge.
Déclaration d'entité
entity pwm is generic ( pwm_bits : integer; clk_cnt_len : positive := 1 ); port ( clk : in std_logic; rst : in std_logic; duty_cycle : in unsigned(pwm_bits - 1 downto 0); pwm_out : out std_logic ); end pwm;
Les ports clk et rst fournissent un contrôle synchrone. L'entrée duty_cycle a une largeur définie par pwm_bits, donc la résolution du compteur est directement configurable. Le signal pwm_out est routé vers la broche FPGA qui pilote la grille du MOSFET.
Signaux internes
signal pwm_cnt : unsigned(pwm_bits - 1 downto 0); signal clk_cnt : integer range 0 to clk_cnt_len - 1;
Le compteur pwm_cnt miroite la largeur du cycle de travail. Le compteur clk_cnt implémente un diviseur d'horloge optionnel ; régler clk_cnt_len à 1 désactive le diviseur et supprime la logique associée.
Processus diviseur d'horloge
CLK_CNT_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then clk_cnt <= 0; else if clk_cnt < clk_cnt_len - 1 then clk_cnt <= clk_cnt + 1; else clk_cnt <= 0; end if; end if; end if; end process;
Avec le clk_cnt_len = 1 par défaut, les outils de synthèse élimineront ce processus automatiquement.
Processus de sortie PWM
PWM_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pwm_cnt <= (others => '0'); pwm_out <= '0'; else if clk_cnt_len = 1 or clk_cnt = 0 then pwm_cnt <= pwm_cnt + 1; pwm_out <= '0'; if pwm_cnt = unsigned(to_signed(-2, pwm_cnt'length)) then pwm_cnt <= (others => '0'); end if; if pwm_cnt < duty_cycle then pwm_out <= '1'; end if; end if; end if; end if; end process;
Le compteur s'incrémente seulement quand clk_cnt est zéro, limitant effectivement la fréquence PWM. La condition qui réinitialise pwm_cnt à sa valeur la plus haute moins deux garantit qu'un cycle de travail de 100 % est atteignable.
\mathit{pwm\_hz} = \frac{\mathit{clk\_hz}}{(2^\mathit{pwm\_bits} - 1) * \mathit{clk\_cnt\_len}}
Conception de niveau supérieur
Pour démontrer le contrôleur PWM, la conception pilote la LED de mise sous tension sur une carte FPGA Lattice iCEstick. L'oscillateur 12 MHz de la carte alimente un compteur qui pilote l'intensité de la LED en motif de dent de scie.

Déclaration d'entité
entity pwm_led is generic ( pwm_bits : integer := 8; cnt_bits : integer := 25; clk_cnt_len : positive := 47 ); port ( clk : in std_logic; rst_n : in std_logic; led_1 : out std_logic; led_2 : out std_logic; led_3 : out std_logic; led_4 : out std_logic; led_5 : out std_logic ); end pwm_led;
L'horloge 12 MHz et les génériques choisis produisent une fréquence PWM d'environ 1 kHz :
\frac{12e6}{(2^8 - 1) * 47} \approx 1 \text{ kHz}
Le générique cnt_bits pilote un compteur en dent de scie de 25 bits qui contrôle le cycle de travail de la LED. Sa période est d'environ 2,8 Hz :
\frac{2^{\mathit{cnt\_bits}}}{\mathit{clk\_hz}} = \frac{2^{25}}{12e6} \approx 2.8 \text{ Hz}
Seule la LED 5 est utilisée ; les autres LED sont tirées à bas pour éviter l'éclairage parasite.
Signaux internes et câblage
architecture str of pwm_led is signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; alias duty_cycle is cnt(cnt'high downto cnt'length - pwm_bits); begin led_1 <= '0'; led_2 <= '0'; led_3 <= '0'; led_4 <= '0'; led_5 <= pwm_out;
Le signal rst est une version synchronisée, non inversée du reset externe. L'alias duty_cycle mappe les bits de poids fort de cnt vers le contrôleur PWM.
Instanciations de modules
RESET : entity work.reset(rtl) port map ( clk => clk, rst_n => rst_n, rst => rst ); PWM : entity work.pwm(rtl) generic map ( pwm_bits => pwm_bits, clk_cnt_len => clk_cnt_len ) port map ( clk => clk, rst => rst, duty_cycle => duty_cycle, pwm_out => pwm_out ); COUNTER : entity work.counter(rtl) generic map ( counter_bits => cnt'length ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );
Simulation
Les génériques facilitent l'accélération de la simulation. En réduisant cnt_bits et clk_cnt_len, nous pouvons observer des cycles PWM complets en quelques microsecondes au lieu de plusieurs secondes.
Instanciation du banc de test :
DUT : entity work.pwm_led(str) generic map ( pwm_bits => 8, cnt_bits => 16, clk_cnt_len => 1 );
La forme d'onde dans ModelSim montre un compteur en dent de scie pilotant la luminosité de la LED. Une brève impulsion « on » apparaît au sommet de chaque cycle quand le cycle de travail atteint sa valeur maximale.

Implémentation FPGA
Après synthèse avec iCEcube2, la conception ne consomme que 5 % des 64 LUTs disponibles sur la partie ice40hx1ktq144 :
Resource Usage Report for pwm_led Mapping to part: ice40hx1ktq144 Cell usage: GND 3 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use VCC 3 uses SB_LUT4 64 uses I/O ports: 7 I/O primitives: 7 SB_GB_IO 1 use SB_IO 6 uses I/O Register bits: 0 Register bits not including I/Os: 44 (3%) Total load per clock: pwm_led|clk: 1 @S |Mapping Summary: Total LUTs: 64 (5%)
La programmation du FPGA via USB avec le programmeur Diamond standalone était simple. L'animation LED résultante montre une rampe de luminosité douce suivie d'un bref assombrissement avant que le cycle ne se répète.

LED respirante avec cycle de travail en onde sinusoïdale
Remplacer le compteur en dent de scie par une table de recherche d'onde sinusoïdale produit un effet « respirant » naturel, couramment vu sur les LED de notification.

Dans un article à venir, nous montrerons comment construire un générateur d'onde sinusoïdale utilisant la RAM bloc et l'intégrer dans la conception pwm_led.
Lisez le prochain article : Comment créer un effet LED respirant en utilisant une onde sinusoïdale stockée dans la RAM bloc
Voir aussi : Contrôleur de servo RC utilisant PWM depuis une broche FPGA
VHDL
- Contrôleur de puissance PWM : construction d'un driver de lampe à modulation de largeur d'impulsion
- Créer des listes de chaînes en VHDL : Bonnes pratiques et exemple
- Créer un banc de test piloté par Tcl pour un module verrou code VHDL
- Terminer proprement une simulation de banc de test VHDL
- Générer des nombres aléatoires en VHDL : De l'uniforme à OSVVM
- Conception d'un tampon circulaire VHDL robuste en mémoire bloc
- Comment construire un testbench auto-vérifiant en VHDL - Guide pratique
- Implémentation d'une liste chainée dynamique en VHDL avec types protégés et pointeurs d'accès
- Guide étape par étape pour charger des condensateurs en toute sécurité et efficacement