PWM Généralités Un signal PWM (→ Pulse Width Modulation) consiste en un signal carré de fréquence fixe (Fpwm = 1/Tpwm) dont on fait varier le rapport cyclique (→ duty cycle noté α) — c’est-à-dire la durée de l’état haut (Thigh) au cours de la période du signal (⇒ α = Thigh / Tpwm) — de manière à obtenir un signal dont la valeur moyenne évolue. Ce signal peut-être utilisé pour : faire varier l’intensité lumineuse d’une DEL. En effet, l’application de ce signal à une DEL va la faire clignoter “plus ou moins longtemps” à une fréquence telle que la persistance rétinienne de notre œil interdira de distinguer ce clignotement et notre œil interprètera celui-ci comme une intensité lumineuse plus ou moins importante. faire varier la vitesse d’un moteur à courant continu. L’inertie du moteur empêche effectivement celui-ci de suivre les évolutions du signal PWM. Il aura donc tendance à prendre en compte que sa valeur moyenne et adapter sa vitesse en conséquence. Le PWM avec les Raspberry Pi Le microprocesseur qui équipe les Raspberry Pi prend en charge le PWM matériel sur certaines de ses broches GPIO. Le microprocesseur BCM2835 qui équipe la Raspberry Pi jusqu’à sa version 3 expose 2 canaux PWM sur 2 broches GPIO accessibles via le connecteur 40 broches de la carte : canal 0 (→ PWM0) sur la broche GPIO12 ou GPIO18 canal 1 (→ PWM1) sur la broche GPIO13 ou GPIO19 La broche réellement utilisée est déterminée par la fonction alternative (→ ALT<n>) qui lui a été attribuée au démarrage de la Raspberry Pi conformément au tableau ci-dessous : De ce tableau, on détermine, par exemple, que la broche GPIO12 sera affectée au canal 0 du PWM si on lui a attribué la fonction alternative ALT0. La fonctionnalité PWM matériel du BCM2835 s’appuie également sur l’utilisation d’une horloge interne dont la source est configurable (→ oscillateur, PLL, APER). La librairie bcm2835 s’appuie sur l’horloge fournie par l’oscillateur du BCM2835 dont la fréquence est de 19.2MHz pour synthétiser les signaux PWM. De la fréquence de cette horloge — que l’on peut diviser par un facteur allant de 20 à 211 — découle une période élémentaire (→ tclk) qui servira d’unité temporelle pour la génération du signal PWM. Sur le BCM2835, l’horloge est commune aux 2 canaux PWM. Ils utiliseront donc tous les 2 la même unité de temps. La façon dont vont être synthétisés les signaux PWM varie selon le mode choisi pour le canal considéré. Lors de la configuration de chaque canal PWM, on doit effectivement spécifier un mode. Ce mode détermine la façon dont seront interprétés les 2 paramètres — DATA et RANGE — que l’on doit fournir au générateur de signaux PWM pour lui indiquer respectivement la valeur du rapport cyclique et la fréquence de chaque signal PWM. 2 modes existent : le mode mark-space le mode balanced Le mode mark-space indique au générateur PWM de synthétiser cycliquement un signal qui sera à l’état haut pendant DATA * tclk puis à l’état bas pendant (RANGE - DATA) * tclk. On obtient ainsi un signal PWM dont : le rapport cyclique est DATA / (RANGE-DATA) la fréquence est 1/(RANGE * tclk). Le mode balanced va, quant à lui, exiger du générateur qu’il synthétise un signal PWM de même fréquence qu’avec le mode mark-space — c’est-à-dire 1/(RANGE * tclk) — mais dont le rapport cyclique sera obtenu en répartissant DATA impulsions positives de durée tclck sur l’ensemble de la période du signal PWM. Consulter la section suivante pour une illustration des signaux obtenus pour les 2 modes avec un paramétrage identique (→ tclk = 106µs, RANGE = 16, DATA = 8). Programme de démonstration Le programme C, dont le code source est fourni ci-dessous, illustre comment mettre en œuvre le PWM sur la Raspberry Pi avec la librairie bcm2835 en permettant toutes les variations possibles de génération d’un signal PWM (→ choix de la broche, du mode et du rapport de division de l’horloge pour chaque canal). Code source de demo-pwm-bcm2835.c #include <stdio.h> #include <limits.h> #include <math.h> #include <bcm2835.h> #define DISABLED 00 #define ENABLED 1 #define PWM_CHANNEL_0 0 #define PWM_CHANNEL_1 1 #define PWM_BALANCED_MODE 0 #define PWM_MARKSPACE_MODE 1 void configurePinForPwmChannel(int channel) { int quit = 0; int pwmPins[][2] = { {12, 18} , {13, 19} }; int channelPin; while (!quit) { printf("\nSaisir le n° de GPIO désiré pour le canal %d du PWM (%d ou %d) : " , channel , pwmPins[ channel ][ 0 ] , pwmPins[ channel ][ 1 ] ); scanf("%d", &channelPin); if(channelPin == pwmPins[ channel ][ 0 ]) { bcm2835_gpio_fsel(channelPin, BCM2835_GPIO_FSEL_ALT0); quit = 1; } else if(channelPin == pwmPins[ channel ][ 1 ]) { bcm2835_gpio_fsel(channelPin, BCM2835_GPIO_FSEL_ALT5); quit = 1; } else { printf("\n!! N° de GPIO non valide pour le canal 0 du PWM !!\n"); } } } void setPwmMode(int channel) { int quit = 0; int pwmMode; while( !quit ) { printf("\nSaisir le mode de fonctionnement désiré pour le canal %d du PWM (0=BALANCED, 1=MARK-SPACE) : " , channel ); scanf("%d", &pwmMode); if((pwmMode == PWM_BALANCED_MODE) || (pwmMode == PWM_MARKSPACE_MODE)) { bcm2835_pwm_set_mode(channel, pwmMode, ENABLED); quit = 1; } else { printf("\n!! Mode non valide pour le PWM !!\n"); } } } void setPwmClockDivider() { int quit = 0; unsigned long pwmClockDivider; while( !quit ) { printf("\nSaisir la puissance de 2 pour le diviseur de fréquence de l'oscillateur (19.2MHz) commun aux 2 canaux du PWM ([0, 11]) : "); scanf("%d", &pwmClockDivider); if((pwmClockDivider >= 0) && (pwmClockDivider <= 11)) { bcm2835_pwm_set_clock(pow(2, pwmClockDivider)); quit = 1; } else { printf("\n!! Valeur non valide pour l'étendue !!\n"); } } } void setPwmRange(int channel) { int quit = 0; unsigned long pwmRange; while( !quit ) { printf("\nSaisir l'étendue pour le canal %d du PWM ([0, 2^32-1]) : " , channel ); scanf("%d", &pwmRange); if((pwmRange >= 0) && (pwmRange <= UINT_MAX)) { bcm2835_pwm_set_range(channel, pwmRange); quit = 1; } else { printf("\n!! Valeur non valide pour l'étendue !!\n"); } } } void setPwmData(int channel) { int quit = 0; unsigned long pwmData; while( !quit ) { printf("\nSaisir la donnée pour le canal %d du PWM ([0, 2^32-1]) : " , channel ); scanf("%d", &pwmData); if((pwmData >= 0) && (pwmData <= UINT_MAX)) { bcm2835_pwm_set_data(channel, pwmData); quit = 1; } else { printf("\n!! Valeur non valide pour la donnée !!\n"); } } } int main() { if( bcm2835_init() != 1) { printf("!! Erreur d'initialisation de la librairie bcm2835 !!"); exit(-1); } configurePinForPwmChannel(0); configurePinForPwmChannel(1); setPwmMode(0); setPwmMode(1); setPwmClockDivider(); setPwmRange(0); setPwmRange(1); setPwmData(0); setPwmData(1); bcm2835_close(); } La séquence d’actions qu’il exécute est la suivante : Initialiser la librairie bcm2835 Sélectionner la broche utilisée par chaque canal PWM → bcm2835_gpio_fsel (uint8_t pin, uint8_t mode) Sélectionner sur chaque canal le mode utilisé pour générer le signal PWM (→ balanced ou mark-space) → bcm2835_pwm_set_mode (uint8_t channel, uint8_t markspace, uint8_t enabled) Fixer le facteur de division de l’horloge commune aux 2 canaux PWM → bcm2835_pwm_set_clock (uint32_t divisor) Spécifier la valeur du paramètre RANGE pour chaque canal PWM → bcm2835_pwm_set_range (uint8_t channel, uint32_t range) Spécifier la valeur du paramètre DATA pour chaque canal PWM → bcm2835_pwm_set_data (uint8_t channel, uint32_t data) Pour compiler ce programme : gcc -o demo-pwm-bcm2835 demo-pwm-bcm2835.c -lm -lbcm2835 Pour exécuter le programme : sudo ./demo-pwm-bcm2835 (1) 1 l’exécution du programme sans sudo ne provoque pas d’erreur mais ne génère pas les signaux PWM. L’activation des 2 canaux PWM dans le device-tree est requise pour que le programme fonctionne. Ceci est réalisé en ajoutant la ligne suivante au fichier /boot/config.txt : dtoverlay=pwm-2chan,pin=12,pin2=13,func=4,func2=4 (1) 1 on peut obtenir de l’aide sur cet overlay avec la commande dtoverlay --help pwm-2chan On peut écouter les signaux générés à travers la prises casque de la Raspberry Pi. Le canal PWM0 correspond à la voie droite du signal audio stéréo et le canal PWM1 à la voie gauche. Ci-dessous le résultat observé à l’oscilloscope quand on exécute le programme avec le paramétrage suivant : $ sudo ./demo-pwm-bcm2835 Saisir le n° de GPIO désiré pour le canal 0 du PWM (12 ou 18) ? 18 Saisir le n° de GPIO désiré pour le canal 1 du PWM (13 ou 19) ? 19 Saisir le mode de fonctionnement désiré pour le canal 0 du PWM (0=BALANCED, 1=MARK-SPACE) : 0 Saisir le mode de fonctionnement désiré pour le canal 1 du PWM (0=BALANCED, 1=MARK-SPACE) : 1 Saisir la puissance de 2 pour le diviseur de fréquence commun aux 2 canaux du PWM ([0, 11]) : 11 Saisir l'étendue pour le canal 0 du PWM ([0, 2^32-1]) : 16 Saisir l'étendue pour le canal 1 du PWM ([0, 2^32-1]) : 16 Saisir la donnée pour le canal 0 du PWM ([0, 2^32-1]) : 8 Saisir la donnée pour le canal 1 du PWM ([0, 2^32-1]) : 8 Le canal PWM0 — exploité en mode balanced — correspond à la voie 1 de l’oscilloscope (→ en jaune). Le canal PWM1 — configuré en mode mark-space correspond à la voie 2 (→ en bleu). La durée élémentaire est tclk = 211 / 19,2.106 = 106µs. La période du signal PWM de type mark-space est donc Tpwm = RANGE * 106µs = 16*106µs ⇒ Fpwm = 586Hz Le signal PWM de type mark-space est à l’état haut pendant Thigh = DATA * 106µs = 8 * 106µs = 853µs Enfin, on a bien 8 impulsions de durée tclk = 106µs réparties sur la période Tpwm = 16*106µs pour le signal de type balanced 🕮 Références : Raspberry Pi And The IoT In C - - Pulse Width Modulation BCM2835 ARM Peripherals > Pulse Width Modulator Notes on the general-purpose clock on BCM2835 🞄 🞄 🞄 Raspberry Pi