Tout est parti d'un post de Sylvain sur les forums de Planete-sciences, à propos d'un générateur PWM Spread Spectrum pour moteur. Il proposait d'en coder un en VHDL. Seulement voilà, le VHDL, c'est comme croiser les rayons, c'est mal... Alors, en voici un, mais en Verilog !
Plus sérieusement, à quoi sert un modulateur SSPWM ? Les moteurs des robots sont généralement commandés en PWM (modulation de largeur d'impulsion). Pour faire varier la puissance moyenne envoyée au moteur, on les alimente par un créneau de fréquence constante dont on fait varier le rapport cyclique.
La fréquence de commande est choisie :
. On n'oubliera pas de prendre en compte dans R la résistance du moteur et celle des transistors du pont en H.Le problème est que, si la fréquence choisie est plus petite que 20kHz, elle tombe dans le spectre audible : les moteurs sifflent. Ça peut être attractif et amusant pendant quelques minutes, ça devient vite énervant... La solution ? Utiliser une fréquence variable.
On fait varier rapidement la fréquence du PWM, tout en gardant le même rapport cyclique. Le bruit résultant est alors réparti sur plusieurs fréquences, de puissances plus faibles. Selon la répartition de ces fréquences, le bruit ressemble à un chuintement plus ou moins aigu. En d'autres termes, au lieu d'avoir un spectre sonore avec un grand pic, on a un spectre plus large, plus plat, et plus bas : il est étalé, d'où le nom du système (spread spectrum).

Comment fait-on un modulateur PWM spread spectrum (appelé aussi Random PWM) ? Il existe des circuits intégrés ou des cartes toutes faites, généralement utilisés pour le contrôle de moteurs alternatifs puissants. Pour des robots, une solution existe : les FPGA.
En deux mots, les FPGA sont des circuits programmables contenant des portes logiques en vrac. Lors de la programmation, on décrit la façon dont on va relier ces portes logiques entre elles. On peut ainsi implémenter la fonction logique que l'on souhaite.
La fonction à obtenir peut être décrite sous forme schématique, ou textuelle, à l'aide d'un langage spécialisé (Verilog, VHDL, SystemC, AHDL, Abel, ...). Les FPGA ont un intérêt tout particulier en robotique : ils permettent d'ajouter des fonctionnalités simples et pratiques à moindre frais à un micro-contrôleur :
Ainsi le processeur peut être réservé à des tâches plus complexes (vision, intelligence, ...).
Cet article montre comment implémenter un modulateur PWM spread spectrum dans un FPGA. Le code est suffisamment générique pour être utilisé tel quel dans tout type de FPGA. Le langage choisi est Verilog. D'abord parce que c'est un langage simple et aussi puissant que VHDL, et surtout parce qu'il existe des simulateurs libres (GPL) de Verilog : Icarus Verilog et Cver. Si on utilise Cver, on fera attention à prendre au moins la version 2.11a. Si vous préférez VHDL, la traduction du code en VHDL est assez simple et directe.
Pour plus de détails sur Verilog et VHDL, on pourra se rapporter au cours en ligne de Verilog, VHDL et SystemC de l'ENST
.
Un modulateur PWM est en fait un compteur. On compare ce compteur à une valeur correspondant au rapport cyclique désiré. Si le compteur est en dessous de cette valeur, on met la sortie du circuit à 1, sinon on la met à 0.
Le temps que met le compteur à reboucler donne la fréquence du PWM. Généralement, on pré-divise l'horloge système par n pour aboutir à la fréquence PWM désirée. Pour un compteur 8 bits, la fréquence PWM sera donc : 
Il existe plusieurs façons de réaliser un modulateur SSPWM (variation de phase, random XOR, NCO, ...). Nous avons pris une approche simple et facile à implémenter : on fera varier de façon aléatoire le coefficient de pré-division de l'horloge (n). Pour être suffisamment souple, on donnera en paramètres à notre modulateur les fréquences minimum et maximum admissibles...
Il nous faut tout d'abord choisir le nombre de bits de n. Pour cela, examinons des cas extrêmes. On posera : 
On utilisera donc un coefficient de division n (et Δn) sur 12 bits. Dans le pire des cas, on aura 389 fréquences possibles, dans le meilleur 3897... On peut donc espérer un élargissement de spectre sympathique. La fréquence obtenue varie environ inversement proportionnellement à Δn. L'écart entre deux fréquences pour deux valeurs successives de Δn vaut :
Il est maximal pour Δn=0, c'est-à-dire en fmax, et vaut :
Récapitulatif :
| f (fmax =40kHz / fmin= 100Hz) | n | Δn (nombre de fréquences) | Δfmax | Δf10kHz | Δfmin |
| 10MHz | 1 | 389 | 20 kHz | 2.56 kHz | 0.25 Hz |
| 40 MHz | 4 | 1558 | 8 kHz | 640 kHz | 0.05 Hz |
| 80MHz | 8 | 3117 | 4.4 kH | 320 Hz | 0.03 Hz |
| 100MHz | 9 | 3897 | 4 kHz | 256 Hz | 0.03 Hz |
| 170MHz | 16 | 6624 | 2.3 kHz | 150 Hz | ... |
On a donc intérêt à choisir une fréquence système au moins égale à 40Mhz. Dans les FPGA Stratix d'Altera, on dispose de plusieurs PLL. Le générateur présenté ici peut fonctionner à une fréquence de 170MHz dans ces FPGA. A condition de prendre des coefficients sur 13 bits, on peut obtenir un spectre assez dense (2.3kHz d'espacement max, à 40kHz, et 150Hz d'espacement entre raies à 10kHz). On aurait pu implémenter un système permettant un étalement de spectre plus régulier. Ce sera probablement l'objet d'un autre article...
Il nous faut maintenant générer un facteur aléatoire compris en n et (n+Δn). Pour l'instant on le prendra uniformément distribué, mais il serait souhaitable dans la suite de modifier le générateur aléatoire pour compenser la dépendance des fréquences en 1/n...
Pour générer un nombre pseudo-aléatoire entre n et (n+Δn), on va le générer entre 0 et 1, on le multipliera par Δn, et on ajoutera n. C'est pour cela qu'on parle depuis le début de n et (n+Δn) et non pas de nmin et nmax...
La génération d'un nombre pseudo aléatoire compris en 0 et 1 sera fait au moyen d'un LFSR. Ce ne sont pas les meilleurs générateurs aléatoires (ce sont même presque les pires...), mais ils ont le mérite d'être faciles à implémenter, et extrêmement rapides. Il en existe deux formes :
Pour plus de précision sur les LFSR, on pourra se rapporter cette page de New Wave Instruments.
Pour nous, nous avons choisi un LFSR de Galois sur 32 bits à 16 taps, correspondant au polynôme générateur suivant : P=x^{32}+x^{31}+x^{28}+x^{27}+x^{24}+x^{23}+x^{20}+x^{19}+x^{18}+x^{15}+x^{12}+x^{11}+x^{8}+x^{7}+x^{3}+x^{2}+1
Le code du LFSR est disponible ici.
Le LFSR nous sort un nombre pseudo-aléatoire sur 32 bits. Il faut maintenant en extraire un nombre sur 11 bits (qui sera considéré comme la partie décimale de 0,xxx) et le multiplier par Δn (appelé period_range dans le code). On pourrait utiliser les multiplieurs intégrés aux Stratix, mais pour rester générique et rapide quel que soit le FPGA utilisé, nous implémentons le multiplieur à la main, à l'aide d'additions et de décalage successifs. La multiplication se fait alors en 12 cycles.
Pour cela, on utilise une machine à états basique :
period_range, c'est-à-dire si le bit correspondant de period_range vaut '1'. On obtient ainsi un nombre compris entre 0 et Δn (vu la façon dont l'algorithme est implémenté, ce nombre est en fait compris entre 0 et Δn-11).period_min). On obtient ainsi un nombre aléatoire compris entre n et n+Δn (period_min et period_min+period_range).Le module réalisant cette fonction est appelé gene_aleatoire, et instancie le LFSR. Son code est disponible ici..
| Fichier attaché | Taille |
|---|---|
| gene_aleatoire.v | 2.92 Ko |
| lfsr.v | 1.06 Ko |
Le fonctionnement du générateur PWM est simple. A chaque début de cycle, on récupère une nouvelle période à partir du générateur aléatoire. Puis on compte 256 périodes, en générant la sortie PWM en comparant le compteur à la consigne val, et on recommence. La sortie PWM est une fonction combinatoire du compteur. Mais pour éviter les glitchs, il vaut mieux la générer de façon séquentielle (autrement dit, qu'elle sorte d'un registre). Le générateur SSPWM est donc un module, appelé sspwm, qui instancie :
val), et une période de base periode (durée du 256ème de cycle)gene_aleatoire.| Fichier attaché | Taille |
|---|---|
| pwm.v | 1.88 Ko |
| sspwm.v | 1.05 Ko |
Pour vérifier le bon fonctionnement du générateur, nous avons écrit un environnement de simulation (appelé testbench dans le jargon des HDL). Ce n'est pas un testbench à proprement parler, car il ne vérifie pas automatiquement que la sortie du générateur SSPWM est correcte. Il se contente juste de fournir quelques valeurs en entrée, et de générer des chronogrammes (trace.vcd). A charge de l'utilisateur de regarder les chronogrammes (Modelsim, gtkWave, ...) et de vérifier qu'ils sont corrects.
Dans ce fichier, la génération des chronogrammes est commentée, car ce qui nous intéresse vraiment est décrit plus bas... Voici un bout des chronogrammes de la simulation du testbench par défaut (rapport cyclique val=128, soit 50%) :

On vérifie bien que le rapport cyclique est constant (50%), et que la période varie de très-peu à beaucoup !
Cependant, il est intéressant de vérifier l'impact des paramètres period_min et period_range sur le spectre du signal obtenu. Pour cela, le testbench effectue trois tests, avec trois valeurs différentes du paramètres period_range, les autres paramètres restant fixés. Il sort la valeur de out à chaque cycle d'horloge dans un fichier séparé pour chaque test (test1.dat, test2.dat et test3.dat). Ces fichiers sont ensuite passés dans un programme (transvec) qui se charge de calculer le spectre du signal PWM par FFT. Le spectre peut ensuite être affiché grâce à GnuPlot.
Pour simplifier la vie, un Makefile se charge de tout. Il suffit d'appeller "make" et les opérations suivantes sont effectuées :
Pour que cela fonctionne, il faut bien entendu que GnuPlot et Cver soient installés, et disposer d'un compilateur C et la bibliothèque fftw3. Voici quelques résultats, pour différentes valeurs d'horloge système. On a pris un rapport cyclique de 50%, une fréquence PWM maximale d'environ 40kHz, et une minimum d'environ 7kHz. Cliquez sur les images pour les agrandir.
| | |||
| Horloge 10MHz | Horloge 25MHz | Horloge 40MHz | Horloge 100MHz |
On peut constater qu'il ne sert à rien d'avoir un range trop grand. Cela vient du fait que les nombres aléatoire choisis sont uniformément distribués, et déterminent non pas une fréquence mais une période. On a donc une plus grande concentration de fréquences possibles dans la partie basse du spectre due à l'hyperbole (T=1/f). On peut constater aussi qu'il vaut mieux utiliser une horloge de base d'au moins 25MHz. Et qu'avec un range d'environ 6, on obtient bien un spectre bien plus plat et étalé...
| Fichier attaché | Taille |
|---|---|
| Makefile. | 456 octets |
| test_sspwm.v | 2.51 Ko |
| transvec.c | 2.93 Ko |
Ce code a été synthétisé avec Quartus II, pour un Stratix EP1S25.
Résultats :
Ce mini-article clôt la description d'un générateur spread-spectrum PWM simple. Son architecture, même si ce n'est pas la meilleure du monde, est simple, et illustre bien comment piloter à moindre frais des moteurs en PWM spread spectrum. Ce code est implémentable dans tout FPGA contenant au moins 119 registres, autrement dit quasiment tous...
Si cela ne vous convainc toujours pas de l'utilité, la puissance et la souplesse des FPGA, allez donc jeter un coup d'oeil à nos balises vidéo, implémentées entièrement dans un FPGA (dans un petit 10ème d'un Stratix). Vous verrez comment traiter de la vidéo en temps réel, sans consommer le moindre cycle CPU du processeur :)
Pour les questions / les commentaires : par mail à publications à telecom-robotics.org !
Pour conclure, l'archive des codes (sources + test bench) est disponible en bas de page.
Remarque : Pour les adeptes du code-packing et haut-niveau : oui, on peut faire plus court. J'en ai une version cradoc à 16 lignes (déclarations non comprises). Mais c'est vraiment pas le but ici...