Bonne nouvelle, chacune des 30 broches de la carte Raspberry Pi Pico peut être dirigée vers le bloc PWM. La datasheet nous apprend que ce bloc PWM de la puce RP2040 comprend 8 slices identiques pouvant chacune générer deux sorties (ou channels) modulées en largeur d'impulsion.
Schéma d'une slice - Extrait datasheet RP2040 (page 525)
Le bloc PWM comporte 8 slices comme sur ce schéma, générant chacune deux sorties modulées (ou channels) A et B.
Le bloc PWM étant très flexible et hautement configurable, je ne décrirai pas son fonctionnement en entier, et je me contenterai du minimum essentiel pour la prise en main avec le SDK C/C++. Pour plus de détails, voir les spécifications.
Pour un numéro de broche GP donné, vous trouverez une table de correspondance dans la datasheet qui permet de remonter à la slice associée et sa sortie (ou channel) A ou B. Mais plutôt que de se référer à cette table, le SDK propose des fonctions qui font ce travail de correspondance.
Par exemple, si vous souhaitez générer un signal PWM sur la broche GP8 :
Code C : | Sélectionner tout |
1 2 3 4 | const uint pwm_pin = 8; gpio_set_function(pwm_pin, GPIO_FUNC_PWM); // fonction PWM sur GP8 uint slice = pwm_gpio_to_slice_num(pwm_pin); // slice associée à GP8 uint channel = pwm_gpio_to_channel(pwm_pin); // channel associée à GP8 |
On notera que la broche GP9 juste à côté est connectée à la même slice 4, mais à l'autre channel B. Vous pouvez ainsi configurer la fréquence du PWM pour la slice entière, et configurer indépendamment les rapports cycliques des deux channels A et B. Choisir deux broches GP sur la même slice est intéressant si, par exemple, vous devez piloter en vitesse les deux moteurs CC d'un robot roulant à deux roues motrices indépendantes (une roue devant tourner plus vite que l'autre dans un virage), ou encore piloter indépendamment en position deux servomoteurs identiques.
On donne ci-dessous le schéma-bloc simplifié d'une slice :
Le bloc PWM est synchronisé avec l'horloge principale de la carte Raspberry Pi Pico à 125 MHz. Un diviseur peut être activé pour réduire cette fréquence. S'ensuit un compteur 16 bits qui, si le mode "phase-correct" (cas par défaut) n'est pas activé, s'incrémente sur chaque front montant du signal d'horloge jusqu'à atteindre une valeur (wrap) choisie avant de retomber à zéro :
D'après datasheet RP2040
La valeur wrap détermine ainsi la fréquence du signal PWM. Grâce à un comparateur, le signal PWM bascule lorsque le compteur franchit un seuil choisi (level). La valeur level détermine alors le rapport cyclique.
Prenons le cas d'un servomoteur de modélisme classique asservi à une position donnée et fixée par le rapport cyclique du signal de commande PWM. Typiquement, la fréquence est de 50 Hz, et la position 180° de l'arbre de sortie est atteinte pour un rapport cyclique de 10% (soit un état haut pendant 2ms sur une période de 20ms).
Avec la fréquence de l'horloge principale à 125 MHz, le compteur 16 bits va déborder et repartir de zéro au bout de (1 / 125.106) x 65536 = 0,524 ms, et on est loin des 20 ms. Le diviseur de fréquence est nécessaire, et on doit le choisir tel que :
divclk ≥ 20 / 0,524, soit 38,17 au minimum.
Le registre qui stocke le diviseur de fréquence comprend 8 bits pour la partie entière, et 4 bits pour la partie fractionnaire, ce qui autorise une division jusqu'à un facteur proche de 256 (255 + 15/16 ≃ 256).
Pour le PWM de notre servomoteur, on prendra donc un diviseur : 38 + 3/16 ≃ 38,1875.
Et dans ce cas, le compteur devra repartir de zéro une fois la valeur 65465 atteinte, car (1 / 125.106) x 38,1875 x 65466 ≃ 20 ms.
Note : faire évoluer le compteur jusqu'à une valeur proche de sa valeur maximum (65535) permet d'offrir une meilleure résolution à votre système. Ici, la résolution théorique en termes de position angulaire est inférieure à 0,03°... Résolution largement suffisante en pratique.
On considère ainsi le programme essai_pwm.c suivant :
Code C : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /** * Démonstration : servomoteur commandé par signal PWM * Rapport cyclique entre 5% et 10% (position entre 0° et 180°) */ #include "pico/stdlib.h" #include "hardware/pwm.h" int main() { const uint pwm_pin = 8; gpio_set_function(pwm_pin, GPIO_FUNC_PWM); // fonction PWM sur GP8 uint slice = pwm_gpio_to_slice_num(pwm_pin); // slice associée à GP8 uint channel = pwm_gpio_to_channel(pwm_pin); // channel associée à GP8 pwm_set_phase_correct (slice, false); // mode phase-correct non activé pwm_set_clkdiv_int_frac (slice, 38, 3); // diviseur de fréquence = 38 + 3/16 pwm_set_wrap(slice, 65465); // valeur wrap pour fixer la fréquence pwm_set_chan_level(slice, channel, 65465 / 10); // rapport cyclique = 10% pour servo à 180° pwm_set_enabled(slice, true); // activer le signal } |
Pour le build du projet, on donne le fichier CMakeLists.txt (voir le premier billet de la série pour la génération du projet) :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | cmake_minimum_required(VERSION 3.13) # initialize the SDK based on PICO_SDK_PATH # note: this must happen before project() include(pico_sdk_import.cmake) project(essai-pwm) # initialize the Raspberry Pi Pico SDK pico_sdk_init() # rest of your project add_executable(essai-pwm essai-pwm.c ) # Add pico_stdlib library which aggregates commonly used features # Add hardware_pwm for PWM features target_link_libraries(essai-pwm pico_stdlib hardware_pwm) # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(essai-pwm) |
Ci-dessous, le signal visualisé à l'analyseur logique (échantillonnage 24 MHz) sur la sortie G8 :
Le signal est conforme à celui attendu (période ≃ 20 ms, et durée à l'état haut ≃ 2 ms, soit un rapport cyclique = 10%).
Pour aller plus loin :
- spécifications techniques RP2040 (voir le document RP2040 datasheet)
- Documentation Raspberry Pi (Pico C SDK Hardware APIs, hardware_pwm)