Aller au contenu

PWM arduino : explications (broches de sortie, rapport cyclique, fréquences, modes), avec exemples de code pratiques !

Pwm arduino fonctionnement, explication avec exemple de code, modification de fréquence et rapport cyclique, tuto autour des arduino uno, nano, et mega

Beaucoup sont ceux qui savent qu’il est possible de générer un signal PWM avec son Arduino. Mais outre le rapport cyclique réglable de 0 à 100%, savez-vous à quelle fréquence sont émis les signaux PWM émanant de votre Arduino ? Et savez-vous s’il est possible ou non de modifier cette fréquence pwm ? C’est en tout cas ce que je vous propose de découvrir ici, dans cet article dédié au PWM Arduino !

Dans la foulée, nous verrons également quelles sont les broches d’un arduino pouvant « émettre » un signal PWM (car toutes ne le peuvent pas, de façon native), en examinant plusieurs Arduino courants, tels que l’Arduino Uno, Nano, Mega, et quelques autres ! Ensuite, nous verrons des exemples concrets de code arduino, vous montrant notamment comment générer un signal pwm arduino, afin de faire varier l’intensité lumineuse d’une LED, en fonction du rapport cyclique appliqué.

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

Comme toujours, n’oubliez pas que je ne suis pas expert sur la question, et que je vous partage seulement ici mes connaissances, complétées avec plusieurs travaux de recherches sur internet. Il se peut donc qu’il y ait quelques coquilles, surtout au niveau des dénominations de registres, qui sont fort nombreuses et vraiment semblables ! Si c’est le cas, n’hésitez pas à m’en faire part dans la zone de commentaires, afin que je puisse les corriger au plus vite. Et par avance, merci !

Intro : qu’est-ce qu’un signal PWM ?

Avant d’entrer dans le cœur du fonctionnement PWM Arduino, certains se demanderont peut-être : qu’est-ce qu’un signal PWM ?

En fait, il faut tout d’abord savoir que « PWM » est un sigle anglais, signifiant « Pulse Width Modulation ». En français, on le traduit par « Modulation de Largeur d’Impulsion » (MLI, en raccourci).

En fait, un signal PWM est un signal électrique de fréquence donnée, et dont le rapport cyclique peut varier dans le temps. Le rapport cyclique étant le rapport entre le temps où le signal sera à « l’état haut », et le temps d’une période complète de ce signal (la somme du temps passé « à l’état haut » + le temps passé à « l’état bas », en fait).

Cette définition ne vous semble pas claire ? Alors voici une illustration imagée, vous permettant de visualiser cela :

Valeur moyenne signaux pwm arduino suivant rapport cyclique, tension moyen avec fréquence fixe et ratio modulable, en largeur d'impulsion MLI électronique

Avec un arduino, on peut facilement générer un signal PWM en sortie. À noter que celui-ci sera :

  • Uniquement « diffusable » sur certaines broches de sorties spécifiques (nativement, j’entends)
  • De fréquence fixe (modifiable dans le code, dans une certaine limite, en changeant la valeur de certains registres du microcontrôleur)
  • D’amplitude 3,3 ou 5 volts (selon si votre microcontrôleur fonctionne en 3V3 ou 5V)
  • De fréquence parfois différente, d’une broche à une autre (suivant l’architecture interne de votre µC, et du mode de génération PWM sélectionné)
  • Et de rapport cyclique modifiable logiciellement, avec la fonction « analogWrite » ou via les registres du µC

Pour commencer à étudier tout cela, pas à pas, je vous propose tout d’abord de voir quelles sont les broches pouvant émettre un signal PWM ou pas, sur un Arduino. Alors en avant 😉

Broches de sortie PWM (output pins Arduino)

Vouloir générer un signal PWM c’est bien, mais encore faut-il savoir sur quelle broche celui-ci pourra être généré ! En effet, toutes les broches d’un arduino ne permettent pas d’émettre nativement de signal PWM. C’est pourquoi, ici, nous allons voir quelles sont les pins de sorties permettant de faire cela, sur les Arduino les plus courants.

Le saviez-vous ? Sur certaines cartes arduino, vous trouverez le sigle « ~ » présent devant certains numéros de broche. En fait, ce sigle (« tilde ») vous indique quelles sorties peuvent générer des signaux PWM ! Du coup, si vous voyez par exemple gravé des numéros de pins telles que « ~3 » ou « ~5 », alors vous saurez que ces sorties-là (3 et 5, en l’occurrence) vous permettront de générer des signaux PWM ! D’ailleurs, c’est une inscription simple et astucieuse, permettant d’aider à se rappeler quelles pins permettent la génération de signaux PWM, et lesquelles ne le peuvent pas (nativement, j’entends).

Sorties PWM sur Arduino Uno, Nano, et Pro Mini (microcontrôleur ATmega328P)

Si vous travaillez avec un Arduino Uno, Nano, ou Pro Mini, c’est-à-dire un modèle équipé d’un microcontrôleur ATmega328P, voici les broches qui permettent d’émettre un signal PWM :

  • La pin D3
  • La pin D5
  • La pin D6
  • La pin D9
  • La pin D10
  • La pin D11

Pour plus de clarté, les voici repérées en image, en fonction de l’arduino utilisé :

Broches PWM arduino uno, nano, et pro mini, équipés du microcontrôleur ATmega328P, pins de sorties pulse width modulation des cartes électroniques

Comme évoqué un peu plus haut, on remarque que les sorties PWM sont quelquefois repérées avec le symbole « ~ », placé juste devant le numéro de broche pouvant émettre un signal PWM. On le remarque d’ailleurs ici sur l’Arduino Uno, où les sorties D3, D5, D6, D9, D10, et D11 sont notées ~3, ~5, ~6, ~9, ~10, et ~11, directement sur le PCB. Par contre, ce n’est pas systématique, comme on peut le constater sur l’Arduino Nano et l’Arduino Pro Mini, où le « tilde » n’est gravé nulle part (par manque de place, très certainement !).

Sorties PWM de l’Arduino Mega (microcontrôleur ATmega2560)

Si vous avez besoin de beaucoup de sorties PWM, l’Arduino Mega fera très certainement votre bonheur, car il est bien « fourni » de ce côté là ! En effet, l’Arduino Mega dispose de 15 sorties PWM, pouvant être utilisées pour générer des signaux modulés en largeur d’impulsion. Ces sorties sont, plus précisément :

  • Les sorties digitales 2 à 13
  • Et les sorties digitales 44, 45, et 46

Histoire d’avoir un support visuel pour bien les repérer, voici où elles se situent :

Broches PWM arduino mega équipé d'un microcontrôleur ATmega2560, pins de sorties pulse width modulation MLI de cette carte électronique

À noter que vous trouverez également tout plein d’infos directement sur le site arduino (en anglais, par contre !).

Sorties PWM de l’Arduino Leonardo et Micro (microcontrôleur ATmega32U4)

Enfin, histoire de prendre un dernier exemple, montrant que les sorties PWM ne sont pas forcément les mêmes d’un arduino à l’autre, voici l’Arduino Micro et l’Arduino Leonardo ! Ici, vous disposez de 7 sorties PWM, soit 1 de plus que les classiques Arduino Uno, Nano, ou Pro Mini. En effet, la sortie D13 permet ici l’émission de signaux PWM, contrairement à ces derniers.

En image, voici où se trouvent ces sorties PWM Arduino, sur les modèles Micro et Leonardo :

Broches PWM arduino leonardo et micro, équipés d'un microcontrôleur ATmega32U4, pins de sorties pulse width modulation de ces cartes électroniques

Voilà en ce qui concerne les broches de sorties PWM natives sur les arduino les plus connus, ou les plus utilisés, selon moi. À présent, voyons quelles instructions utiliser dans notre code arduino, pour piloter ces sorties, et pour générer des signaux PWM, à la demande !

Comment générer un signal PWM, et régler le rapport cyclique de 0 à 100 % ?

Maintenant que nous savons quelles broches permettent d’émettre un signal PWM, et quelles broches ne le permettent pas, nous allons à passer à la partie intéressante, à savoir :

  • Comment générer un signal PWM avec son Arduino ?
  • Et comment modifier le rapport cyclique selon ses souhaits, entre 0 et 100 % ?

En fait, c’est super simple ! Car il suffit de :

  • Déclarer « en sortie » une des broches pouvant émettre un signal PWM, ce qui se fait avec un pinMode(numero_de_la_broche_souhaitée, OUTPUT);
  • Et lui envoyer la valeur de rapport cyclique souhaité, avec la commande analogWrite(numero_de_la_broche_souhaitée, valeur_de_rapport_cyclique_souhaité);

À noter que le rapport cyclique, au niveau du code arduino, est exprimé au travers d’un nombre compris entre 0 et 255. Du coup, en théorie :

  • Un rapport cyclique égal à 0 équivaut à aucun signal PWM en sortie (soit un état bas permanent, égal à 0 volt)
  • Et un rapport cyclique égal à 255 équivaut à un signal PWM à 100% (soit un état haut permanent, par exemple égal à +5V si votre microcontrôleur fonctionne sous 5 volts)

En pratique, voici un exemple permettant de générer un signal PWM sur un Arduino Uno, via la sortie D5, avec un rapport cyclique de 50% :

#define brochePwmChoisie                  5                     // On choisit d’émettre sur la broche D5
#define pourcentageRapportCycliqueChoisi  30                    // avec un rapport cyclique égal à 30%

void setup() {
  
  // Conversion du « pourcentage » de rapport cyclique en une valeur comprise entre 0 et 255
  int rapportCycliqueEntre0et255 = map(pourcentageRapportCycliqueChoisi, 0, 100, 0, 255);

  // Génération du signal PWM
  pinMode(brochePwmChoisie, OUTPUT);                            // Définition de la broche D5 en tant que « SORTIE »
  analogWrite(brochePwmChoisie, rapportCycliqueEntre0et255);    // Génération du signal PWM, avec le rapport cyclique voulu
  
}

void loop() {
  
  // Pas de code ici, car tout se passe dans la partie setup !
  
}

Remarque : savez-vous ce qui se passe AVANT et APRÈS l’appel de la fonction analogWrite, ici ? En fait :

  • Tant que la fonction analogWrite n’a pas été appelée au moins une 1ère fois, aucun signal PWM ne sera généré en sortie.
  • Par contre, une fois que cette fonction aura été appelée, le signal PWM se répètera à l’infini. Bien sûr, vous pourrez à tout moment :
    • modifier le rapport cyclique de ce signal, en réappelant la fonction analogWrite
    • ou bien arrêter la génération du signal PWM, en envoyant la valeur « 0 » via la fonction analogWrite

Fréquences par défaut des signaux PWM Arduino (Uno, Nano, Mega)

Nous venons de voir comment générer un signal PWM Arduino, et comment ajuster son rapport cyclique, à nos besoins. Mais il ne vous aura certainement pas échappé que, à aucun moment, nous avons précisé la fréquence de ce signal PWM. Pourtant, il y a bien une fréquence d’émission « par défaut », forcément. Et c’est ce que je vous propose de découvrir ici !

Mais avant d’aller plus loin, dites-vous que tout ce qui suis ne sera pas forcément facile à appréhender, si vous débutez avec Arduino. Car :

  • les fréquences d’émission des signaux PWM peuvent varier d’une broche à l’autre
  • et celles-ci dépendent de la valeur de certains registres du microcontrôleur (intégré à votre Arduino), et notamment, des « timers » de ce dernier. Nota : pour ceux qui ne sont pas familier avec les timer arduino, je vous mets ici un lien vers un article d’introduction sur les timer arduino.

De base, si vous ne touchez à aucun registre du microcontrôleur et créez un programme qui génère un signal PWM sur une broche de sortie de votre Arduino, vous aurez une fréquence bien connue à l’avance (mais parfois différente d’une broche à l’autre, attention).

C’est ainsi que l’on a, par défaut, pour un Arduino Uno / Nano / Pro Mini :

  • Un signal PWM de 976,56 Hz sur les broches de sortie D5 ou D6
  • Un signal PWM de 490,20 Hz sur les broches de sortie D3, D9, D10, ou D11

Et pour un Arduino Mega :

  • Un signal PWM de 976,56 Hz sur les broches de sortie D4 ou D13
  • Un signal PWM de 490,20 Hz sur les broches de sortie D2, D3, D5, D6, D7, D8, D9, D10, D11, ou D12
  • Un signal PWM de 490,20 Hz sur les broches de sortie D44, D45, ou D46

Et pour les Arduino Leonardo et Micro :

  • Un signal PWM de 976,56 Hz sur les broches de sortie D3 ou D11
  • Un signal PWM de 490,20 Hz sur les broches de sortie D5, D6, D9, D10, ou D13

Mais d’où viennent ces valeurs de fréquences par défaut, me direz-vous ? En fait, c’est super simple. Ces fréquences PWM proviennent de la fréquence d’horloge du microcontrôleur (16 MHz, pour les Arduino cités ci-dessus), sur laquelle on applique une division particulière, en fonction de certains modes et valeur de registre. En effet :

  • 16 MHz / 64 / 256 = 976,5625 Hz (64 étant la valeur du prédiviseur par défaut, interne au µC, et 256 le nombre de pas du signal PWM généré ici, dans un mode particulier, par défaut)
  • 16 MHz / 64 / 510 = 490,196 Hz (64 étant la valeur du prédiviseur par défaut, interne au µC, et 510 le nombre de pas du signal PWM généré ici, dans un autre mode de fonctionnement, par défaut)

Remarque : les fréquences de 976 et 490 Hz sont dans le « spectre » des fréquences audibles de l’être humain. En clair : on peut donc entendre ces fréquences, sous certaines conditions. C’est d’ailleurs souvent le cas, par exemple, lorsqu’on asservit la vitesse d’un moteur à la fréquence d’un signal PWM. C’est pourquoi il est quasi indispensable de travailler à des fréquences bien supérieures, c’est à dire en dehors du champ de la zone audible de l’oreille humaine (qui capte les sons entre 20 Hz et 20000 Hz, grosso modo). Mais rassurez-vous ! Car l’Arduino n’est pas limité aux seules fréquences 490 et 976 Hz, et peut aller bien au delà. C’est d’ailleurs ce que nous allons voir juste après, en montant jusqu’à 31372 Hz, et même 62500 Hz, selon le mode de génération de signaux PWM utilisé !

Quelles sont toutes les fréquences arduino PWM possible ?

Dans la partie précédente, je vous ai dit que la fréquence PWM d’un Arduino était fonction :

  • De la fréquence d’horloge principale du microcontrôleur (généralement 16 MHz, généré à partir d’un quartz externe)
  • De la valeur des prédiviseurs d’horloge de chaque timer (qui sont réglés pour une division par 64, par défaut)
  • Et du mode de génération de signaux PWM choisi (à 256 pas, à 510 pas, ou autre, que nous détaillerons dans les exemples)

Nota : la fréquence PWM est également fonction de la valeur du prescaler (diviseur « général » de fréquence de votre µC). Mais celui-ci étant toujours, sauf cas particulier, réglé sur un « rapport de division par 1 », le prescaler peut donc être ignoré ici. Par contre, il est important se savoir qu’il peut jouer un rôle au niveaux des signaux PWM, si modifié !

Concernant les prédiviseurs de fréquence d’horloge de chaque timer, ceux-ci peuvent prendre différentes valeurs, prédéfinies à l’avance (interne au µC). Généralement, ces prédiviseurs permettent :

  • De diviser la fréquence par 1, 8, 64, 256, et 1024 (pour la plupart)
  • Ou par 1, 8, 32, 64, 128, 256, et 1024 (pour d’autres)

En fait, suivant le modèle de microcontrôleur et la broche de sortie PWM que vous choisirez, vous aurez le choix entre l’un ou l’autre de ces rapports de division de fréquence (car chaque sortie PWM dépend d’un timer en particulier, et chaque timer a son prédiviseur associé).

Parallèlement à ça, et comme je vous le disais un peu plus haut, les fréquences des signaux PWM Arduino sont également fonctions du « mode de génération » utilisé. Ici, il en existe de 2 types :

  • Le modèle « Fast PWM Mode » : il permet la génération de signaux PWM « découpés » en 256 morceaux, ou multiples de 256. C’est un mode rapide, mais « peu » précis.
  • Le modèle « Phase Correct PWM Mode » : il permet la génération de signaux PWM « découpés » en 510 morceaux, ou multiples de 510. C’est un mode plus précis que le précédent, mais plus « lent ».

À noter qu’il existe bien d’autres modes de génération de signaux PWM encore (notamment à 8 bits, 9 bits, 10 bits), permettant au final d’avoir une grande variété de signaux PWM possibles, en sortie. Mais bien sûr, aussi riche que tout cela puisse être, tout ceci reste limité du fait qu’on ne peut travailler qu’à certaines fréquences, « imposées » par l’horloge et les différents diviseurs de fréquences.

Plus concrètement, voici des exemples de fréquences que l’on peut obtenir avec un ATmega328P, équipant les Arduino Uno, Nano, et Pro Mini, selon si on touche aux prédiviseurs de fréquence, ou au mode de génération PWM :

Fréquences PWM arduino uno sorties D3 D5 D6 D9 D10 et D11, pin pulse width modulation timer 0 1 ou 2, broches OC0A OC0B OC1A OC1B 0C2A et 0C2B

Et un autre exemple pour l’ATmega2560, équipant les classiques Arduino Mega :

Fréquences PWM arduino mega sorties D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 et D13, ainsi que pins D44 D45 et D46, en pulse width modulation ATmega2560

Si je résume : en pratique, pour changer de fréquence PWM, on peut :

  • Changer le rapport de division de fréquence d’horloge (de 1 à 1024, au niveau des prédiviseurs d’horloge)
  • Et/ou changer de mode de génération de signal PWM (Fast Mode, Phase Correct Mode, 8-bit, 9-bit, 10-bit, …)

Mais autant vous le dire de suite : pour dompter le PWM Arduino, il vous faudra tôt ou tard toucher aux registres internes du microcontrôleur. C’est d’ailleurs ce que nous verrons un peu plus loin, au travers d’exemples pratiques. Pour l’heure, commençons par quelque chose de plus simple, pour ne pas se perdre 😉

Exemple de code #1 : faire varier la luminosité d’une LED grâce à un signal PWM, piloté par potentiomètre

Premier exemple que je vous propose : un montage vous permettant de faire varier l’intensité lumineuse d’une LED à l’aide d’un signal PWM, généré par un Arduino Nano (doté d’un microcontrôleur ATmega328P, donc). Et pour pouvoir prendre le contrôle du rapport cyclique de ce signal PWM, au doigt et à l’œil, quoi de mieux qu’un bon vieux potentiomètre !

Au niveau fonctionnement, c’est hyper simple : plus vous tournerez le potentiomètre, et plus la LED s’éclairera (ou plus elle s’éteindra, suivant dans quel sens vous tournez !).

Au niveau du montage électrique, voici ce que je vous propose de réaliser, pour ce faire :

Pwm arduino led alimentée avec rapport cyclique ajustable via potentiomètre, montage avec Arduino Nano résistance et LED, pour variation luminosité

Comme vous voyez, rien de bien compliqué ! Car au final, mise à part les fils d’alimentation 0/5V, il n’y a que 2 fils à brancher au niveau de l’arduino :

  • Le point milieu du potentiomètre, à relier sur l’entrée analogique A0
  • Et la résistance alimentant la LED, à brancher sur la sortie D3

Pour rappel : si j’ai choisi la sortie D3, c’est parce qu’elle est l’une des 6 sorties possibles d’un arduino nano, pouvant générer un signal PWM (comme vu un peu plus haut, dans cet article). En fait, les sorties D5, D6, D9, D10, ou D11 auraient pu également convenir. Mais dans tous les cas, pas les autres, ici !

Au niveau du programme arduino en lui-même, vous verrez qu’il n’y a que très peu de lignes de code. C’est pourquoi j’en ai profité pour mettre un maximum de commentaires :

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       prgTestLedPiloteeEnPwmViaPotentiometre.ino
  
  Description :   Permet de faire varier la luminosité d'une LED avec un signal PWM 0-5V,
                  via un potentiomètre branché sur l'entrée analogique d'un Arduino Nano
                  
  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)
  Créé le :       10.11.2021
  
*/

#define pinOuEstBrancheLePotentiometre  0       // Le potentiomètre servant à faire varier la luminosité de la LED sera branché sur l'entrée A0 de l'Arduino Nano
#define pinOuEstBrancheLaLED            3       // La LED sera quant à elle branchée sur la sortie D3 de l'Arduino Nano (attention : toutes les sorties ne permettent pas de générer un signal PWM)

int valeurTensionEntreeAnalogique;              // Variable qui contiendra la valeur de la tension mesurée sur l'entrée analogique (valeur comprise entre 0 et 1023, car lecture sur 10 bits)
int valeurRaccordCycliqueSignalPwm;             // Variable qui contiendra la valeur du rapport cyclique du signal PWM à générer

// ========================
// Initialisation programme
// ========================
void setup()  
{ 
  
  // Définition de la broche où sera branché la LED en sortie
  pinMode(pinOuEstBrancheLePotentiometre, OUTPUT);

  // Nota : pas besoin de déclarer l'entrée analogique en entrée, car c'est sous entendu, par défaut
  
}

// =================
// Boucle principale
// =================
void loop()  
{ 

  // *****************************************************************************************************************************
  // Lecture de la tension présente sur l'entrée analogique, où est branché le potentiomètre (son "point milieu", plus exactement)
  // *****************************************************************************************************************************
  // Pour rappel : la valeur retournée sera comprise entre 0 et 1023, car il s'agit là d'une lecture sur 10 bits (0 correspondant à 0V, et 1023 à +5V)
  
  valeurTensionEntreeAnalogique = analogRead(pinOuEstBrancheLePotentiometre);

  // *****************************************************************************************************************************
  // Conversion tension -> rapport cyclique
  // *****************************************************************************************************************************
  // Comme la valeur mesurée sur l'entrée analogique sera exprimée sous la forme d'un nombre compris entre 0 et 1023,
  // et que la valeur du rapport cyclique à renseigner dans le code arduino devra être comprise entre 0 et 255,
  // alors il faut convertir les mesures 0-1023 en valeur 0-255
  
  valeurRaccordCycliqueSignalPwm = map(valeurTensionEntreeAnalogique, 0, 1023, 0, 255);

  // *****************************************************************************************************************************
  // Génération du signal PWM
  // *****************************************************************************************************************************

  analogWrite(pinOuEstBrancheLaLED, valeurRaccordCycliqueSignalPwm);  

}

Une fois ce programme uploadé dans votre Arduino, et votre montage alimenté, vous devriez être en mesure de faire varier l’intensité lumineuse de la LED, en tournant le potentiomètre vers la droite, ou vers la gauche.

Ah oui… pour ceux qui se demandent comment fonctionne ce montage, en fait, c’est tout simple :

  • Le potentiomètre permet de faire varier la tension entre 0 et +5V sur l’entrée A0.
  • Le programme arduino mesure cette tension, et génère un signal PWM en conséquence, sur la sortie D3. À noter que :
    • le rapport cyclique de ce signal sera proportionnel à cette tension lue (0% si 0 volt, jusqu’à 100% si 5 volts)
    • dans le codage arduino, le rapport cyclique ne pourra pas être renseigné en pourcentage (0 à 100%), mais plutôt au travers d’une valeur décimale, comprise entre 0 et 255
  • Enfin, la LED éclairera plus ou moins fortement, selon la valeur du rapport cyclique qui lui est appliqué. À noter qu’ici la fréquence de ce signal PWM est fixe (à sa valeur par défaut), et qu’elle est bien alimentée en 0/+5V via sa résistance. En clair : ce n’est pas la tension de sortie en D3 qui varie, mais seulement le rapport cyclique d’un signal 0/5V.

Au passage, même si j’ai réalisé ce montage avec un Arduino Nano, comme visible ci-dessous, rien ne vous empêche de le faire avec un autre arduino ou autre ! Il faudra juste, si nécessaire, adapter le code au besoin 😉

Montage led pwm arduino et potentiomètre faisant varier intensité lumineuse, exemple sur breadboard avec Arduino Nano, résistance et LED éclairage variable

Exemple de code #2 : changer la fréquence PWM de son Arduino, en jouant sur les prédiviseurs de fréquence timer

Maintenant que nous savons comment générer un signal PWM arduino de fréquence fixe, voyons comment modifier la valeur de cette fréquence, en jouant sur certains registres du microcontrôleur (prédiviseurs de fréquence d’horloge, au niveau des timers).

Mais avant tout :

  • Qu’est-ce qu’un prédiviseur de fréquence ?
  • Et où cela se situe « concrètement », dans le microcontrôleur ?

En fait, un prédiviseur de fréquence est tout simplement quelque chose qui va diviser la fréquence « principale » par un certain nombre. Ainsi, si l’on souhaite avoir une fréquence PWM plus basse ou plus élevée que celle par défaut, il faudra alors jouer sur la valeur de ce prédiviseur.

Tout ceci ne vous semble pas clair ? Ne vous inquiétez pas, je vais vous illustrer tout ça ! D’ailleurs, de manière très simplifiée, voici comment un signal PWM est « fabriqué », à partir de l’horloge du microcontrôleur :

Schéma générateur signaux pwm arduino avec timer, prescaler, et prédiviseurs d'horloge, logigramme partant du quartz 16 MHz jusqu'à sorties microcontrôleur

Comme vous le voyez, tout part de l’horloge du µC (qui « tourne » la plupart du temps à 16 Mhz, sur quartz externe, avec nos « classiques » arduino !). Ensuite, cette fréquence « initiale » passe au travers deux diviseurs de fréquences, que sont :

  • Le prescaler, un diviseur « général » qu’on ne touche que très rarement, car impactant quasiment tous les niveaux du microcontrôleur. Il est donc couramment laissé sur « un rapport de division par 1 » (comme s’il était transparent, donc)
  • Et les prédiviseurs de fréquence de chaque timer, la plupart du temps réglés sur une division de fréquence par 64, par défaut sur l’ATmega328P, par exemple

Ensuite viennent les générateurs de signaux PWM, qui « construisent » les signaux pwm « pas à pas », à partir de la fréquence ainsi divisée.

Pour la suite ici, nous allons voir comment modifier la fréquence d’un signal PWM en sortie d’un Arduino Uno (microcontrôleur ATmega328P, donc), simplement en touchant au prédiviseur de fréquence d’horloge. Arbitrairement, je vais prendre la sortie D3 en exemple, qui est reliée au « timer2 » de cet arduino. Ainsi, pour faire varier la fréquence du signal PWM en D3, il suffira de changer de rapport de division de fréquence sur le prédiviseur du timer2.

Lien : datasheet du microcontrôleur ATmega328P

Pour cela, nous allons devoir toucher à un registre interne du µC, où est stockée l’information de rapport de division de fréquence. Pour le timer2, cette info se trouve dans le registre TCCR2B, comme visible ci-dessous :

Registre TCCR2B pour PWM Arduino Uno ou Nano, équipé microcontrôleur ATmega328P, bits CS22 CS21 et CS20 pour division de fréquence d'horloge en sortie

Comme vous l’aurez compris, pour changer de fréquence, il suffira de toucher aux bits CS22, CS21, et CS20, afin d’accéder à des rapport de division de fréquence allant de 1 à 1024. Et bien évidemment, aucun nombre « intermédiaire » pourra être choisi. En clair, seuls les rapports 1, 8, 32, 64, 128, 256, et 1024 seront utilisables avec ce timer (0 étant là pour arrêter le timer, au besoin).

Pour info, le rapport de prédivision par défaut est 64. Ce qui donne une fréquence de signal PWM aux alentours de 490 Hz (si vous n’avez pas touché aux autres registres du microcontrôleur). Cette fréquence est déterminée par la formule suivante, ici :

fréquencePwm = fréquenceHorloge / rapportPrédiviseurDeFréquence / 510 (cette dernière valeur correspondant au mode « Phase Correct PWM Mode », dont nous parlerons plus loin, au travers d’un exemple).

Ainsi, étant donné que le prédiviseur de fréquence peut seulement prendre les rapports de division 1, 8, 32, 64, 128, 256, et 1024, les fréquences de signaux PWM que l’on peut obtenir sont les suivants :

  • Prédiviseur réglé sur 1 : fréquencePWM = 16.000.000 / 1 / 510 = 31372 Hz environ
  • Prédiviseur réglé sur 8 : fréquencePWM = 16.000.000 / 8 / 510 = 3921 Hz environ
  • Prédiviseur réglé sur 32 : fréquencePWM = 16.000.000 / 32 / 510 = 980 Hz environ
  • Prédiviseur réglé sur 64 : fréquencePWM = 16.000.000 / 64 / 510 = 490 Hz environ (valeur par défaut)
  • Prédiviseur réglé sur 128 : fréquencePWM = 16.000.000 / 128 / 510 = 245 Hz environ
  • Prédiviseur réglé sur 256 : fréquencePWM = 16.000.000 / 256 / 510 = 122 Hz environ
  • Prédiviseur réglé sur 1024 : fréquencePWM = 16.000.000 / 1024 / 510 = 30 Hz environ

Pour vérifier toute cette « théorie », je vous propose d’exécuter le programme suivant sur un Arduino Uno ou Nano, afin de pouvoir visualiser le résultat en sortie (moyennant le choix du rapport de division de fréquence que vous souhaitez, dans la partie setup, avant d’uploader ce code) :

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       prgTestChangementValeurPrediviseur.ino
  
  Description :   Permet de générer des signaux PWM de fréquence "modifiable" depuis un Arduino équipé d'un microcontrôleur ATmega328P (Uno, Nano, …),
                  sur la sortie D3, dépendante du timer 2 du µC
                  
  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)
  Créé le :       11.11.2021
  
*/

// **************************************************
// Trois derniers bits du registre de contrôle TCCR2B
// **************************************************
// CS22 | CS21 | CS20 | RÉSULTAT
//   0  |   0  |   0  | Timer arrêté
//   0  |   0  |   1  | Division de fréquence par 1
//   0  |   1  |   0  | Division de fréquence par 8
//   0  |   1  |   1  | Division de fréquence par 32
//   1  |   0  |   0  | Division de fréquence par 64
//   1  |   0  |   1  | Division de fréquence par 128
//   1  |   1  |   0  | Division de fréquence par 256
//   1  |   1  |   1  | Division de fréquence par 1024
// ***************************************************
#define frequencePWMde31372hz 0b00000001
#define frequencePWMde3921hz  0b00000010
#define frequencePWMde980hz   0b00000011
#define frequencePWMde490hz   0b00000100
#define frequencePWMde245hz   0b00000101
#define frequencePWMde122hz   0b00000110
#define frequencePWMde30hz    0b00000111
// Nota : ces fréquences sont celles obtenues avec un µC fonctionnant sur un quartz de 16MHz, tout en laissant le Prescaler sur "1" (pas de division de fréquence globale, donc)

void setup()  
{ 

  // Déclaration de la broche d'E/S D3 en sortie
  pinMode(3, OUTPUT);

  
  // Sélection du rapport de division de fréquence du timer 2
  
  TCCR2B &= 0b11111000;               // <===== à ne pas toucher
  TCCR2B |= frequencePWMde31372hz;    // <===== à changer, selon la fréquence que vous souhaitez en sortie

    // Nota 1 : l'opérateur "&=" constitue un "ET logique". Il applique le masque "0b11111000", afin de mettre à 0 les 3 derniers bits du registre TCCR2B, tout en laissant les autres bits intacts
    // Nota 2 : la fonction "|=" constitue un "OU logique". Il applique notre valeur à 8 bits, comme définie tout en haut, afin de modifier les 3 derniers bits du registre TCCR2B, précédemment mis à zéro


  // Génération d'un signal PWM sur la sortie D3, avec un rapport cyclique arbitraire, fixé à 33% (en prenant la valeur 85 sur 255, donc)
  analogWrite(3, 85);
  
}

void loop()  
{ 

}

Si vous branchez un oscilloscope (ou un fréquencemètre) au niveau de la sortie D3 de votre Arduino, vous devriez voir (et pouvoir mesurer) le signal PWM ainsi généré (amplitude 0-5V, avec un rapport cyclique fixé à 33% dans le programme).

À présent que nous savons comment générer un signal PWM et sélectionner une fréquence particulière, parmi « toutes » celles disponibles, nous allons explorer plus en détail les différents modes du générateur de signaux pwm (Fast, Phase Correct, 8-9-10 bits, …). D’ailleurs, commençons tout de suite par le « Fast PWM Mode » !

Le saviez-vous ? Le timer 0 sert notamment de « base » aux fonctions delay, millis, et micro. C’est pourquoi, si vous touchez au prédiviseur de fréquence du timer0, afin de le faire compter plus ou moins vite, vous altèrerez inévitablement ces fonctions ! Du coup, réfléchissez bien à l’impact de toute manipulation de registre, avant de faire quoi que ce soit 😉

Exemple de code #3 : test du mode Fast PWM arduino (signal divisé en 256 pas, de base)

Eh oui… ! Vous ne le savez peut-être pas, mais la génération de signaux PWM sur un Arduino impose de facto le choix d’un « mode » en particulier, de génération de signal. Bien sûr, il n’est pas indispensable de savoir cela lorsqu’on débute avec Arduino, car il y a des réglages par défaut qui nous permettent de faire fi de tout cela ! Par contre, dès que vous chercherez à « pousser les choses » un peu plus, savoir manier les différents mode du PWM arduino est quasi incontournable ! C’est pourquoi je vous les présente ici, au travers de cet exemple, et ceux qui suivent.

Parmi tous les différents modes de génération de signaux PWM qui existent, avec leurs « variantes », on peut résumer et simplifier les choses en disant qu’au final, il n’existe que 2 modes possibles :

  • Le mode « Fast PWM », qui permet de générer un signal PWM à 256 pas
  • Et le mode « Phase Correct PWM », qui permet de générer un signal PWM à 510 pas

Remarque : pour info, ces valeurs de 256 ou 510 pas correspondent en fait à un signal PWM généré sur 8 bits. Or, comme nous le verrons un peu plus loin, il est possible de travailler sur des signaux PWM en 8, 9, et 10 bits. Du coup, il serait plus juste de dire qu’en mode Fast PWM le signal est divisé en 256 pas, OU multiple de 256 pas. Idem pour le mode Phase Correct PWM, qui est divisé en 510 pas, OU multiple de 510 pas. Mais pour l’heure, restons simple, et continuons avec ces 256 divisions par période, pour nos signaux PWM en mode Fast !

En pratique, pour sélectionner un mode de génération de signaux PWM en particulier, il faudra toucher à certains des registres internes du microcontrôleur. Et comme il y a autant de « groupes » de registres qu’il y a de timer, je vous propose ici de rester focalisé sur un seul timer, tout en sachant que le raisonnement sera quasi identique, si vous travaillez avec les autres timer (à quelques variantes près !).

Pour cet exemple, j’ai pris un Arduino Nano (équipé d’un µC ATmega328P, donc), et utilisé le timer 0 (pilotant les sorties D5 et D6 de cet arduino).

Lien : datasheet du microcontrôleur ATmega328P

Pour commencer, il faut savoir que l’information concernant les différents modes de générations de signaux PWM possibles pour le timer 0 sont contenus dans deux registres de contrôle : le « TCCR0A » et le « TCCR0B ». Et plus particulièrement, l’information est encodée au niveau des bits WGM02, WGM01, et WGM00, qui sont disposés « à cheval » sur ces deux registres. Du coup, ce sont ces 3 bits qu’il faudra manipuler, pour sélectionner un mode, ou un autre.

Histoire d’y voir plus clair, voici d’ailleurs la « table de vérité » des bits WGM02, WGM01, et WGM00, tels qu’on peut les retrouver dans les registres TCCR0A et TCCR0B :

Mode Fast PWM arduino avec registres TCCR0A et TCCR0B du timer 0, via les bits WGM02 WGM01 et WGM00, pour sélection de mode de génération de signaux

Important : vous remarquerez sûrement que le mode Fast PWM est en double dans ce tableau. En fait, seul l’un des deux « mode » est bon dans notre cas, pour cet exemple : il s’agit de celui ayant un TOP=0xFF (soit 256 pas), et non de celui ayant un TOP=OCRA (non abordé ici).

Ainsi, si on souhaite utiliser le mode « Fast PWM » (celui qui va de 0x00 au TOP = 0xFF, pour faire nos 256 pas), il nous suffit alors de :

  • Mettre le bit WGM02 à 0, ce qui peut se faire avec la fonction « bitClear(TCCR0B, WGM02); »
  • Mettre le bit WGM01 à 1, ce qui peut se faire avec la fonction « bitSet(TCCR0A, WGM01); »
  • Et mettre le bit WGM00 à 1, ce qui peut se faire avec la fonction « bitSet(TCCR0A, WGM00); »

Au final, voici comment tout cela s’intègre dans un programme arduino, au travers de cet exemple de code permettant de générer des signaux Fast PWM sur les sorties D5 et D6 d’un Arduino Nano, avec un rapport cyclique de 33% sur D5 et 66% sur D6 :

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       prgTestFastPwmTimer0.ino
  
  Description :   Permet de générer des signaux PWM sur un Arduino équipé d'un microcontrôleur ATmega328P (Uno, Nano, Pro Mini, …),
                  sur les sorties D5 et D6 (dépendants du "timer0"), via la "méthode" Fast PWM Mode
                  
  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)
  Créé le :       12.11.2021
  
*/


// ***********************************************************************************
// ATTENTION : toucher au timer0 impactera les fonctions delay(), millis(), et micro()
// ***********************************************************************************


void setup()  
{ 
  // ================================
  // Timer 0 (sorties PWM : D5 et D6)
  //=================================

  // Déclaration des pins D5 et D6 en sorties
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);

  // *****************************************************************************
  //  BITS DE SÉLECTION DU MODE DE GÉNÉRATION PWM (timer 0)
  //                           (cf. page 86 du datasheet de l'ATmega328P)
  // *****************************************************************************
  //  Mode | WGM02 | WGM01 | WGM00 | DESCRIPTIF  
  //    0  |   0   |   0   |   0   | Normal               (TOP 0xFF)
  //    1  |   0   |   0   |   1   | Phase Correct PWM    (TOP 0xFF)
  //    2  |   0   |   1   |   0   | CTC                  (TOP OCRA)
  //    3  |   0   |   1   |   1   | Fast PWM             (TOP 0xFF) <=========== c'est le mode qui nous intéresse
  //    4  |   1   |   0   |   0   | Réservé
  //    5  |   1   |   0   |   1   | Phase Correct PWM    (TOP OCRA)
  //    6  |   1   |   1   |   0   | Réservé
  //    7  |   1   |   1   |   1   | Fast PWM             (TOP OCRA)
  // *****************************************************************************
  // Sélection du mode "FastPWM" (avec TOP à 0xFF, pour faire nos 256 pas)
  bitClear(TCCR0B, WGM02);      // Mise de WGM02 à 0
  bitSet(TCCR0A, WGM01);        // Mise de WGM01 à 1
  bitSet(TCCR0A, WGM00);        // Mise de WGM00 à 1
  // Remarque : les bits WGM01 et WGM00 sont dans le registre TCCR0A, tandis que le bit WGM02, quant à lui, est dans le registre TCCR0B


  // Nota : le prédiviseur de fréquence du timer0 est par défaut configuré pour une division de fréquence d'horloge par 64.
  //        Du coup, le timer0 sera rythmé à une fréquence égale à 16 MHz / 64, soit 250 kHz (si, bien entendu, le prescaler n'a pas été touché).
  //        Aussi, comme le mode utilisé ici (le Fast PWM) fonctionne sur 8 bits (donc 256 pas),
  //        la fréquence de sortie des signaux PWM sera égale à 250 kHz / 256, soit environ 976 Hz.


  // Génération d'un signal PWM sur la sortie D5 (connectée à la pin OC0B du µC), avec rapport cyclique de 33% (soit une valeur égale à 85 sur 255)
  analogWrite(5, 85);

  // Génération d'un signal PWM sur la sortie D6 (connectée à la pin OC0A du µC), avec rapport cyclique de 66% (soit une valeur égale à 170 sur 255)
  analogWrite(6, 170);
  
}

void loop()  
{ 

}

Si tout se passe bien, vous devriez obtenir deux signaux PWM, générés via le mode Fast PWM :

  • Sur la sortie D5 : un signal de fréquence 976 Hz, avec un rapport cyclique de 33% (environ)
  • Et sur la sortie D6 : un signal de fréquence 976 Hz, avec un rapport cyclique de 66% (environ)

Exemple de code #4 : test du mode Phase Correct PWM arduino (signal divisé en 510 pas, de base)

Second mode de génération de signaux pwm avec un arduino : le « Phase Correct PWM Mode ». Contrairement au mode Fast PWM vu précédemment, qui « fonctionnait » sur 256 pas, ici, avec le mode Phase Correct PWM, le signal sera décomposé en 510 pas. Ainsi, on obtient un signal plus « raffiné », plus précis, mais par contre, plus « lent » (comparé au Fast PWM, vu juste avant).

Là encore, petite parenthèse : quand je parle de 510 divisions pour chaque période du signal PWM, j’entends « quand on est en mode 8 bits » (ce qui est le cas par défaut, avec les Arduino « basiques »). Mais attention, car il existe des modes 9 et 10 bits, que nous verrons dans l’exemple suivant.

Pour info, les « 510 » subdivisions du signal proviennent tout simplement d’un aller-retour sur 8 bits, d’un signal allant de 0 jusqu’à 255, puis redescendant de 254 à 1 (pour redémarrer à 0 au cycle suivant). Ce qui nous donne bien 256 + 254 pas, soit 510 pas.

Afin d’illustrer ce « Phase Correct PWM Mode », je vais à nouveau prendre un Arduino Nano (donc doté d’un microcontrôleur ATmega328P). Par contre, histoire de changer, je vais prendre cette fois-ci le timer 2 (au lieu du « timer 0 », pris dans l’exemple précédent).

Lien : datasheet du microcontrôleur ATmega328P

Du coup, les registres de contrôle qui nous intéresseront pour ce timer seront les suivants (afin de nous permettre de sélectionner le mode Phase Correct PWM) :

Mode Phase Correct PWM arduino avec registres TCCR2A et TCCR2B du timer 2, via les bits WGM22 WGM21 et WGM20, pour choix mode génération signaux en sortie

Comme vous le constatez, ce sont ici les bits WGM22, WGM21, et WGM20 qu’il faudra toucher, pour sélectionner le mode « Phase Correct PWM ». Et comme « toujours » avec l’ATmega328P, ces bits sont à cheval sur 2 registres : le TCCR2A et le TCCR2B.

Au passage, pour sélectionner le Phase Correct PWM Mode « normal » (celui qui va de 0x00 à 0xFF, puis qui revient vers zéro, pour faire 510 pas), il faudra mettre les bits de configuration de la manière suivante :

  • Mettre le bit WGM22 à 0, ce qui peut se faire avec la fonction « bitClear(TCCR2B, WGM22); »
  • Mettre le bit WGM21 à 0, ce qui peut se faire avec la fonction « bitClear(TCCR2A, WGM21); »
  • Et mettre le bit WGM20 à 1, ce qui peut se faire avec la fonction « bitSet(TCCR2A, WGM20); »

Mettons à présent tout cela en application, au travers de l’exemple qui suit, permettant de générer 2 signaux PWM : un sur D3 avec un rapport cyclique de 33%, et un autre sur la broche D11, avec un rapport cyclique de 66%. Le tout, en mode « Phase Correct PWM » !

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       prgTestPhaseCorrectPwmTimer2.ino
  
  Description :   Permet de générer des signaux PWM sur un Arduino équipé d'un microcontrôleur ATmega328P (Nano, Uno, Pro Mini, …),
                  sur les sorties D3 et D11 (dépendants du "timer2"), via la "méthode" Phase Correct PWM Mode
                  
  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)
  Créé le :       13.11.2021
  
*/


// ***********************************************************************************
// ATTENTION : toucher au timer2 impactera la fonction tone(), si utilisée
// ***********************************************************************************


void setup()  
{ 
  // =================================
  // Timer 2 (sorties PWM : D3 et D11)
  //==================================

  // Déclaration des pins D3 et D11 en sorties
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);

  // *****************************************************************************
  //  BITS DE SÉLECTION DU MODE DE GÉNÉRATION PWM (timer 2)
  //                           (cf. page 130 du datasheet de l'ATmega328P)
  // *****************************************************************************
  //  Mode | WGM22 | WGM21 | WGM20 | DESCRIPTIF  
  //    0  |   0   |   0   |   0   | Normal               (TOP 0xFF)
  //    1  |   0   |   0   |   1   | Phase Correct PWM    (TOP 0xFF) <=========== c'est le mode qui nous intéresse
  //    2  |   0   |   1   |   0   | CTC                  (TOP OCRA)
  //    3  |   0   |   1   |   1   | Fast PWM             (TOP 0xFF)
  //    4  |   1   |   0   |   0   | Réservé
  //    5  |   1   |   0   |   1   | Phase Correct PWM    (TOP OCRA)
  //    6  |   1   |   1   |   0   | Réservé
  //    7  |   1   |   1   |   1   | Fast PWM             (TOP OCRA)
  // *****************************************************************************
  // Sélection du mode "PhaseCorrectPWM" (avec TOP à 0xFF, pour faire nos 510 pas)
  bitClear(TCCR2B, WGM22);      // Mise de WGM02 à 0
  bitClear(TCCR2A, WGM21);      // Mise de WGM01 à 0
  bitSet(TCCR2A, WGM20);        // Mise de WGM00 à 1
  // Remarque : les bits WGM21 et WGM20 sont dans le registre TCCR2A, tandis que le bit WGM22, quant à lui, est dans le registre TCCR2B


  // Nota : le prédiviseur de fréquence du timer2 est par défaut configuré pour une division de fréquence d'horloge par 64.
  //        Du coup, le timer2 sera rythmé à une fréquence égale à 16 MHz / 64, soit 250 kHz (si le prescaler n'a pas été touché, bien entendu).
  //        Aussi, comme le mode utilisé ici (le Phase Correct PWM) fonctionne sur 8 bits (donc 510 pas, car issu de "2x255"),
  //        la fréquence de sortie des signaux PWM sera égale à 250 kHz / 510, soit environ 490 Hz.


  // Génération d'un signal PWM sur la sortie D3 (connecté à la pin OC2B du µC), avec rapport cyclique de 33% (soit la valeur 85 sur 255)
  // **************************************************************************************
  //  Mode | COM2B1 | COM2B0 | RÉSULTAT  
  //  1er  |    0   |    0   | PWM stoppé (sortie D3 libre pour une utilisation "normale")
  //  2ème |    0   |    1   | Inutilisé
  //  3ème |    1   |    0   | PWM en marche (mode normal, dit "non inversé")
  //  4ème |    1   |    1   | PWM en marche (mode "inversé")
  // **************************************************************************************
  bitClear(TCCR2A, COM2B0);
  bitSet(TCCR2A, COM2B1);       // 3ème mode choisi ici (mode "normal", aussi appelé "non-inverting")
  OCR2B = 85;
  // Nota : ces 3 lignes sont équivalentes ici à l'instruction : "analogWrite(3, 85);"


  // Génération d'un signal PWM sur la sortie D11 (connecté à la pin OC2A du µC), avec rapport cyclique de 66% (soit la valeur 170 sur 255)
  // **************************************************************************************
  //  Mode | COM2A1 | COM2A0 | RÉSULTAT  
  //  1er  |    0   |    0   | PWM stoppé (sortie D11 libre pour une utilisation "normale")
  //  2ème |    0   |    1   | PWM stoppé si WGM22=0, ou "autobasculant" lorsque égalité entre le timer2 et la valeur stockée dans OCR2A, si WGM22=1
  //  3ème |    1   |    0   | PWM en marche (mode normal, dit "non inversé")
  //  4ème |    1   |    1   | PWM en marche (mode "inversé")
  // **************************************************************************************
  bitClear(TCCR2A, COM2A0);
  bitSet(TCCR2A, COM2A1);       // 3ème mode choisi ici (mode "normal", aussi appelé "non-inverting")
  OCR2A = 170;
  // Nota : ces 3 lignes auraient là aussi pu être remplacées par 1 seule : "analogWrite(11, 170);"

  
}

void loop()  
{ 

}

À noter que j’en ai profité pour vous montrer ici une autre façon de « lancer » la génération de signal PWM, en manipulant les bits COM2A1 et COM2A0, ainsi que COM2B1 et COM2B0. Ainsi, vous verrez qu’il est possible de prendre la main sur chacune des sorties, de manière « plus poussée » (ce qui sera nécessaire, dans l’exemple suivant). Bien sûr, pour ceux qui ne souhaitent pas avoir quelque chose de si compliqué, vous pouvez parfaitement utiliser la fonction : « analogWrite(pinDeSortieVisee, rapportCycliqueSouhaite); ».

Pour ceux que ça intéresse, voici la table de vérité des registres « COM2xx », permettant de piloter les signaux PWM arrivant sur les broches de sorties de l’arduino :

Marche arrêt signaux pwm arduino, exemple avec registre TCCR2A et bits de contrôle COM2A1 COM2A0 COM2B1 et COM2B0, pour mode stop, normal, ou inversé

Exemple de code #5 : génération de signaux PWM arduino sur 8, 9, et 10 bits

Enfin, en dernier exemple : un programme vous montrant qu’il est possible d’avoir accès à des signaux PWM échantillonnés sur 8-bit, 9-bit, ou 10-bit, avec certains timers.

Pour ce faire, je vais à nouveau prendre un Arduino Nano en exemple, équipé de son µC ATmega328P, et opter pour le « timer 1 » cette fois-ci. En effet, contrairement aux timers 0 ou 2 vus précédemment, le timer 1 permet de générer des signaux PWM sur 8, 9, ou 10 bits.

Lien : datasheet du microcontrôleur ATmega328P

Commençons donc par jeter un coup d’œil aux registres de contrôle du timer 1, pour commencer !

Sélection mode Fast PWM ou Phase Correct PWM dans registres TCCR1A et TCCR1B, au travers des bits WGM13 WGM12 WGM11 et WGM10 de l'ATmega328P

Ici, ce sont donc les bits de configuration WGM12, WGM11, et WGM10 qui définissent le mode de génération de signaux PWM. Ceux-ci nous permettent notamment de choisir l’un de ces 6 modes (les autres n’étant pas abordés ici) :

  • Fast PWM en 8 bits
  • Fast PWM en 9 bits
  • Fast PWM en 10 bits
  • Phase Correct PWM en 8 bits
  • Phase Correct PWM en 9 bits
  • Phase Correct PWM en 10 bits

Et comme toujours, afin de vous illustrer comment mettre tout cela en œuvre, voici un exemple de code arduino, vous permettant de tester les modes Fast Pwm et Phase Correct Pwm, et ce, en 8 / 9 / 10 bits. À noter qu’on utilisera ici qu’un seul mode « à la fois » ; il faudra donc le spécifier avec upload du programme, dans la partie « setup ».

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       prgTestPwm8ou9ou10bitsTimer1.ino
  
  Description :   Permet de générer des signaux PWM sur un Arduino équipé d'un microcontrôleur ATmega328P (Uno, Nano, Pro Mini, …),
                  sur les sorties D9 et D10 (dépendants du "timer1"), via les modes FastPWM ou PhaseCorrectPWM, et ce, en 8, 9, ou 10 bits

  Remarque :      Toucher au timer1 impactera la bibliothèque servo(), si utilisée
                  
  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)
  Créé le :       14.11.2021
  
*/

// Différents modes possibles, testés ici
#define ModeFastPwm8bits            1
#define ModeFastPwm9bits            2
#define ModeFastPwm10bits           3
#define ModePhaseCorrectPwm8bits    4
#define ModePhaseCorrectPwm9bits    5
#define ModePhaseCorrectPwm10bits   6

// Rapports cycliques choisis arbitrairement (modifiables selon vos souhaits)
const float rapportCycliqueVouluSurPinD9  = 0.25;   // Soit 25%
const float rapportCycliqueVouluSurPinD10 = 0.75;   // Soit 75%

// Variables internes à ce programme
int valeurMaxRegistreOCR1x = 0;
int valeurRapportCycliquePwmPinD9;
int valeurRapportCycliquePwmPinD10;

void setup()  
{ 
  // =================================
  // Timer 1 (sorties PWM : D9 et D10)
  //==================================

  // Déclaration des pins D9 et D10 en sorties
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);

  // Sélection du mode choisi 
  selectionne(ModePhaseCorrectPwm10bits);          // <=================== paramètre à changer, selon ce que vous souhaitez tester !

  // Génération de signaux PWM sur les sorties D9/D10
  genereSignauxPwm();
  
  // Nota : le prédiviseur de fréquence du timer1 est par défaut configuré pour une division de fréquence d'horloge par 64.
  //        Du coup, le timer1 sera rythmé à une fréquence égale à 16 MHz / 64, soit 250 kHz (si la valeur du prescaler n'a pas été modifiée, bien entendu).
  //        Ainsi, la fréquence de sortie des signaux en "Fast PWM Mode " sera égale à :
  //                - en FastPWM 8-bit => 250 kHz / 256 = 976 Hz environ
  //                - en FastPWM 9-bit => 250 kHz / 512 = 488 Hz environ
  //                - en FastPWM 10-bit => 250 kHz / 1024 = 244 Hz environ
  //        Ou en "Phase Correct PWM Mode ", si c'est le mode de génération de signaux choisi :
  //                - en PhaseCorrectPWM 8-bit => 250 kHz / 510 = 490 Hz environ
  //                - en PhaseCorrectPWM 9-bit => 250 kHz / 1020 = 245 Hz environ
  //                - en PhaseCorrectPWM 10-bit => 250 kHz / 2040 = 122 Hz environ
  
}

void loop()  
{ 
  // Vide, car tout se passe au sein de la partie "setup"
}

void selectionne(int mode)
{
  
  // *******************************************************************************
  //  BITS DE SÉLECTION DU MODE DE GÉNÉRATION PWM (timer 1)
  //                           (cf. page 109 du datasheet de l'ATmega328P)
  // *******************************************************************************
  //  Mode | WGM13 | WGM12 | WGM11 | WGM10 | DESCRIPTIF  
  //    0  |   0   |   0   |   0   |   0   | Normal                     (TOP 0xFFFF)
  //    1  |   0   |   0   |   0   |   1   | Phase Correct PWM 8 bits   (TOP 0x00FF)
  //    2  |   0   |   0   |   1   |   0   | Phase Correct PWM 9 bits   (TOP 0x01FF)
  //    3  |   0   |   0   |   1   |   1   | Phase Correct PWM 10 bits  (TOP 0x03FF)
  //    4  |   0   |   1   |   0   |   0   | CTC                        (TOP OCR1A)
  //    5  |   0   |   1   |   0   |   1   | Fast PWM 8 bits            (TOP 0x00FF)
  //    6  |   0   |   1   |   1   |   0   | Fast PWM 9 bits            (TOP 0x01FF)
  //    7  |   0   |   1   |   1   |   1   | Fast PWM 10 bits           (TOP 0x03FF)
  //    8  |   1   |   0   |   0   |   0   | PWM/Phase/Freq Correct     (TOP ICR1)
  //    9  |   1   |   0   |   0   |   1   | PWM/Phase/Freq Correct     (TOP OCR1A)
  //   10  |   1   |   0   |   1   |   0   | PWM/Phase Correct          (TOP ICR1)
  //   11  |   1   |   0   |   1   |   1   | PWM/Phase Correct          (TOP OCR1A)
  //   12  |   1   |   1   |   0   |   0   | CTC                        (TOP ICR1)
  //   13  |   1   |   1   |   0   |   1   | Réservé
  //   14  |   1   |   1   |   1   |   0   | Fast PWM                   (TOP ICR1)
  //   15  |   1   |   1   |   1   |   1   | Fast PWM                   (TOP OCR1A)
  // *******************************************************************************
  
  switch(mode)
  {
    case ModeFastPwm8bits:
      // Sélection du mode "Fast PWM 8 bits"
      bitClear(TCCR1B, WGM13);      // Mise à 0 de WGM13
      bitSet(TCCR1B, WGM12);        // Mise à 1 de WGM12
      bitClear(TCCR1A, WGM11);      // Mise à 0 de WGM11
      bitSet(TCCR1A, WGM10);        // Mise à 1 de WGM10
      // Valeur max registre OCR1x
      valeurMaxRegistreOCR1x = pow(2,8) - 1;                          // 2^8-1, soit 255 (0x00FF)
      break;

    case ModeFastPwm9bits:
      // Sélection du mode "Fast PWM 9 bits"
      bitClear(TCCR1B, WGM13);      // Mise à 0 de WGM13
      bitSet(TCCR1B, WGM12);        // Mise à 1 de WGM12
      bitSet(TCCR1A, WGM11);        // Mise à 1 de WGM11
      bitClear(TCCR1A, WGM10);      // Mise à 0 de WGM10
      // Valeur max registre OCR1x
      valeurMaxRegistreOCR1x = pow(2,9) - 1;                          // 2^9-1, soit 511 (0x01FF)
      break;

    case ModeFastPwm10bits:
      // Sélection du mode "Fast PWM 10 bits"
      bitClear(TCCR1B, WGM13);      // Mise à 0 de WGM13
      bitSet(TCCR1B, WGM12);        // Mise à 1 de WGM12
      bitSet(TCCR1A, WGM11);        // Mise à 1 de WGM11
      bitSet(TCCR1A, WGM10);        // Mise à 1 de WGM10
      // Valeur max registre OCR1x
      valeurMaxRegistreOCR1x = pow(2,10) - 1;                         // 2^10-1, soit 1023 (0x03FF)
      break;

    case ModePhaseCorrectPwm8bits:
      // Sélection du mode "Phase Correct PWM 8 bits"
      bitClear(TCCR1B, WGM13);      // Mise à 0 de WGM13
      bitClear(TCCR1B, WGM12);      // Mise à 0 de WGM12
      bitClear(TCCR1A, WGM11);      // Mise à 0 de WGM11
      bitSet(TCCR1A, WGM10);        // Mise à 1 de WGM10
      // Valeur max registre OCR1x
      valeurMaxRegistreOCR1x = pow(2,8) - 1;                          // 2^8-1, soit 255 (0x00FF)
      break;

    case ModePhaseCorrectPwm9bits:
      // Sélection du mode "Phase Correct PWM 9 bits"
      bitClear(TCCR1B, WGM13);      // Mise à 0 de WGM13
      bitClear(TCCR1B, WGM12);      // Mise à 0 de WGM12
      bitSet(TCCR1A, WGM11);        // Mise à 1 de WGM11
      bitClear(TCCR1A, WGM10);      // Mise à 0 de WGM10
      // Valeur max registre OCR1x
      valeurMaxRegistreOCR1x = pow(2,9) - 1;                          // 2^9-1, soit 511 (0x01FF)
      break;

    case ModePhaseCorrectPwm10bits:
      // Sélection du mode "Phase Correct PWM 10 bits"
      bitClear(TCCR1B, WGM13);      // Mise à 0 de WGM13
      bitClear(TCCR1B, WGM12);      // Mise à 0 de WGM12
      bitSet(TCCR1A, WGM11);        // Mise à 1 de WGM11
      bitSet(TCCR1A, WGM10);        // Mise à 1 de WGM10
      // Valeur max registre OCR1x
      valeurMaxRegistreOCR1x = pow(2,10) - 1;                         // 2^10-1, soit 1023 (0x03FF)
      break;

    default:
      break;
  }
  
}

void genereSignauxPwm() 
{
  
  // **************************************************
  // Vérification qu'un mode a bien été pré-sélectionné
  // **************************************************
  if(valeurMaxRegistreOCR1x == 0)
    return;
  // Sinon aucun mode sélectionné, on quitte cette fonction, sans générer le moindre signal PWM


  // ===================================
  // IMPORTANT : la fonction analogWrite n'est pas utilisée ici, car seulement valable pour la génération de signaux PWM 8-bit (avec ce µC).
  //             C'est pourquoi ce sont les registres de sortie qui sont directement manipulés ici, pour ce faire.
  // ===================================


  // ***********************************************************************************************************
  // Calcul de la valeur de OCR1A et OCR1B, fixant le rapport cyclique des signaux PWM sur les sorties D9 et D10
  // ***********************************************************************************************************
  OCR1A = rapportCycliqueVouluSurPinD9 * valeurMaxRegistreOCR1x;
  OCR1B = rapportCycliqueVouluSurPinD10 * valeurMaxRegistreOCR1x;
  // À noter qu'on écrit ici directement dans les registres du microcontrôleur (OCR1A et OCR1B), et non dans des variables quelconques

 
  // *********************************************************************
  // Mise en marche du générateur PWM sur la sortie D9 (broche OC1A du µC)
  // *********************************************************************
  //  Mode | COM1A1 | COM1A0 | RÉSULTAT  
  //  1er  |    0   |    0   | PWM stoppé
  //  2ème |    0   |    1   | PWM stoppé ou "autobasculant", suivant la valeur de WGM13
  //  3ème |    1   |    0   | PWM en marche "normal"      <====== c'est le mode qui nous intéresse
  //  4ème |    1   |    1   | PWM en marche "inversé"
  // *********************************************************************
  bitSet(TCCR1A, COM1A1);
  bitClear(TCCR1A, COM1A0);      // Mise en marche du PWM sur la sortie D9, en mode "normal"


  // **********************************************************************
  // Mise en marche du générateur PWM sur la sortie D10 (broche OC1B du µC)
  // **********************************************************************
  //  Mode | COM1B1 | COM1B0 | RÉSULTAT  
  //  1er  |    0   |    0   | PWM stoppé
  //  2ème |    0   |    1   | PWM stoppé ou "autobasculant", suivant la valeur de WGM13
  //  3ème |    1   |    0   | PWM en marche "normal"      <====== c'est le mode qui nous intéresse
  //  4ème |    1   |    1   | PWM en marche "inversé"
  // **********************************************************************
  bitSet(TCCR1A, COM1B1);
  bitClear(TCCR1A, COM1B0);      // Mise en marche du PWM sur la sortie D10, en mode "normal"

}

Du coup, avec ce programme, vous pourrez donc simuler les 6 modes PWM présentés un peu plus haut. Libre à vous de les tester dans l’ordre qui vous intéresse, en sachant que vous aurez besoin de réuploader le code à chaque modification de programme !

J’en profite au passage pour vous glisser un exemple de mode (le « Phase Correct PWM 10-bit »), pris en photo au moment de mes essais :

Signal pwm arduino sur oscilloscope numérique, exemple de code test mode génération signal PWM 8 bit 9 bit ou 10 bits, sur Arduino Nano ou Uno et timer 1

Avec, pour info :

[Ajout] Comptage partiel (TOP = OCRA, plutôt que 0xFF ou autre) pour générer encore d’autres fréquences PWM

J’en profite pour vous glisser quelques infos supplémentaires ici, au sujet des signaux PWM générés à partir de « comptages partiels », au niveau des timer.

Comme vous avez dû le remarquer, dans les tableaux de registres de contrôle des 3 exemples précédents, les modes Fast PWM et Phase Correct sont souvent présents « en double ». C’est pour cela d’ailleurs que j’avais rajouté une colonne « intermédiaire », donnant la valeur du « TOP » (valeur max avant remise à zéro du timer). Ainsi, vous remarquerez que :

  • Certains modes PWM sont donnés pour un « TOP à 0xFF »
  • Et d’autres modes PWM sont donnés pour un « TOP à OCRA »

Comme visible sur l’image ci-dessous :

Arduino PWM OCRA mode avec timer 0, comptage partiel en mode Fast ou Phase Correct pwm, registres TCCR0A et TCCR0B, avec bits WGM02 WGM01 et WGM00

Tout d’abord, il faut savoir que cette valeur de TOP « indique » au timer jusqu’à combien il va devoir compter (sa « limite haute », si vous préférez). Par défaut, ce sont les modes avec un top égal à 0xFF qui sont sélectionnés, par exemple, sur un ATmega328P. Ceci permet d’avoir les « fameux » signaux à 256 pas (lorsqu’on travaille en Fast PWM 8 bits), ou 510 pas (en Phase Correct PWM 8 bits).

Mais il faut tout de même savoir que, si nécessaire, on peut travailler avec un « TOP différent » (à une valeur inférieure que 0xFF, ou supérieure si le timer le permet). Par contre, dans ce mode particulier, il y aura des contraintes. En effet :

  • La sortie dépendante des registres « A » du timer ne pourra fonctionner qu’en mode « Toggle » (basculement). Dans ce cas, le timer comptera de 0 jusqu’à une valeur égale à OCRA, avant de redémarrer à zéro (ou redescendre vers 0, suivant le mode choisi). Et ce, en répétant ce cycle indéfiniment. Ainsi ici, à chaque fois que la valeur du timer sera égale à OCRA, la sortie « A » changera d’état (elle passera à 1 si elle était à 0, et à 0 si elle était à 1)
  • La sortie dépendante des registres « B » du timer, quant à elle, pourra adopter les 4 modes classiques (arrêt, toggle, marche normale, ou marche inversée). Ainsi ici, la sortie « B » basculera à chaque fois que le timer sera revenu à 0, et lorsque le timer aura atteint une valeur égale à OCRB (tout en gardant bien à l’esprit que le timer continuera à compter après avoir atteint OCRB, et ce, jusqu’à atteindre OCRA, avant de repartir à zéro ou dans l’autre sens, suivant le mode employé)

Au passage, il est donc indispensable que la valeur d’OCRB soit inférieure à la valeur OCRA.

Pour donner plus de précisions ici :

  • Pour la sortie « A » : il faudra mettre les bits COMxA1 à 0, et COMxA0 à 1 pour « forcer » le mode toggle, qui est obligatoire ici (« x » étant à remplacer par le numéro du timer)
  • Pour la sortie « B » : il faudra ajuster les bits COMxB1 et COMxB0 selon ses besoins (mode libre ici)

Voici un exemple de sélection de bits, pour le timer 0 de l’ATmega328P, lorsque le mode TOP=OCRA est choisi :

Toggle signal PWM arduino TCCR0A du microcontrôleur ATmega328P, TOP égal à OCRA, pour mode Fast PWM et Phase Correct avec comptage partiel

À noter également que :

  • Pour la sortie « A » : le rapport cyclique sera forcément fixe et égal à 50% ; car le comptage se fait toujours de la même manière, avec un basculement à chaque fois que le timer aura atteint la valeur TOP, définie dans OCRA. À noter, par ailleurs, que la fréquence sera 2 fois moindre ici, par rapport aux autres modes de fonctionnement, car il faudra parcourir « 2 timer complets » pour revenir à l’état initial en sortie (une période complète = 2 range du timer, donc).
  • Pour la sortie « B » : le rapport cyclique sera égal au rapport entre OCRxB et OCRxA, car OCRxB défini le temps à l’état haut, et OCRxA, le temps TOTAL de l’onde PWM.

Tout ceci ne vous semble pas clair ? Alors voici un exemple pratique :

  • Prenons un Arduino Uno (µC ATmega328P / horloge sur quartz à 16 MHz)
  • Servons-nous du timer 0, afin de pouvoir générer des signaux PWM sur les sorties D5 (pin OC0B) et D6 (pin OC0A)
  • Laissons le prescaler général en l’état, par défaut, avec un rapport de division de fréquence par 1
  • Laissons le prédiviseur du timer 0 en l’état, par défaut, avec un rapport de division de fréquence par 64
  • Fixons arbitrairement OCR0A à 190 et OCR0B à 60 (nota : ce sont des valeurs sur 8 bits, donc comprises entre 0 et 255)
  • Paramétrons COM0A1 et COM0A0 afin que la sortie OC0A fonctionne en « Toggle » (obligatoire pour avoir un signal en sortie, lorsqu’on choisit un mode avec TOP=OCRA)
  • Paramétrons COM0B1 et COM0B0 afin que la sortie OC0B fonctionne en générateur PWM « normal » (non inversé, j’entends)
  • Et testons tout ceci en mode Fast PWM et Phase Correct PWM, pour voir ce qu’on obtient en sortie

En transposant tout ceci en code arduino, voici ce que cela donne :

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       prgTestOcraPwmTimer0.ino
  
  Description :   Permet de générer d'autres signaux PWM avec un Arduino équipé d'un microcontrôleur ATmega328P (Pro Mini, Nano, Uno, …),
                  via les sorties D5 et D6 (dépendants du "timer 0"), aussi bien en mode "Fast PWM" qu'en mode "Phase Correct PWM",
                  mais avec un comptage partiel au niveau du timer (ici, TOP sera égal à OCRA, au lieu de 0xFF)

  ATTENTION :     Toucher au timer0 impactera les fonctions delay(), millis(), et micro(). Gardez toujours cela à l'esprit !
                  
  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)
  Créé le :       04.12.2021
  
*/

// Constantes
#define MODE_FAST_PWM             1
#define MODE_PHASE_CORRECT_PWM    2

// Variables
byte VALEUR_DE_OCRA = 190;      // Limite "haute" de comptage, pour le timer 0 ; cette valeur doit être comprise entre 0 et 255
byte VALEUR_DE_OCRB = 60;       // Valeur du temps à l'état haut, du signal PWM à générer sur la sortie OC0B
  // Nota : dans ce cas précis, le rapport cyclique sera égal à OCRB/OCRA

void setup()  
{ 
  // ================================
  // Timer 0 (sorties PWM : D5 et D6)
  //=================================

  // Déclaration des pins D5 et D6 en sorties
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);

  // Sélection du mode qui nous intéresse (Fast PWM, ou bien, Phase Correct PWM)
  selectionner(MODE_FAST_PWM);

  // Génère les signaux PWM
  genererSignauxPwm();
 
}

void loop()  
{ 
  // Vide, puisque "tout" se passe dans la fonction "setup"
}

void selectionner(byte mode)
{
  
  // *****************************************************************************
  //  BITS DE SÉLECTION DU MODE DE GÉNÉRATION PWM (timer 0)
  //                           (cf. page 86 du datasheet de l'ATmega328P)
  // *****************************************************************************
  //  Mode | WGM02 | WGM01 | WGM00 | DESCRIPTIF  
  //    0  |   0   |   0   |   0   | Normal               (TOP 0xFF)
  //    1  |   0   |   0   |   1   | Phase Correct PWM    (TOP 0xFF)
  //    2  |   0   |   1   |   0   | CTC                  (TOP OCRA)
  //    3  |   0   |   1   |   1   | Fast PWM             (TOP 0xFF)
  //    4  |   1   |   0   |   0   | Réservé
  //    5  |   1   |   0   |   1   | Phase Correct PWM    (TOP OCRA) <===================== c'est un des modes qui nous intéresse (TOP = OCRA)
  //    6  |   1   |   1   |   0   | Réservé
  //    7  |   1   |   1   |   1   | Fast PWM             (TOP OCRA) <===================== c'est l'autre mode qui nous intéresse (TOP = OCRA)
  // *****************************************************************************

  if(mode == MODE_FAST_PWM)
  {
    // Sélection du mode "FastPWM" (TOP OCRA)
    bitSet(TCCR0B, WGM02);        // Mise de WGM02 à 1
    bitSet(TCCR0A, WGM01);        // Mise de WGM01 à 1
    bitSet(TCCR0A, WGM00);        // Mise de WGM00 à 1    
  }
  else if (mode == MODE_PHASE_CORRECT_PWM)
  {
    // Sélection du mode "PhaseCorrectPWM" (TOP OCRA)
    bitSet(TCCR0B, WGM02);        // Mise de WGM02 à 1
    bitClear(TCCR0A, WGM01);      // Mise de WGM01 à 0
    bitSet(TCCR0A, WGM00);        // Mise de WGM00 à 1
  }
  
}

void genererSignauxPwm()
{
  
  // Nota : 
  //    - le prescaler général est par défaut réglé pour une division de fréquence d'horloge par 1
  //    - le prédiviseur de fréquence du timer 0 est par défaut configuré pour une division de fréquence d'horloge par 64
  //
  //        Du coup, le timer0 sera rythmé à une fréquence égale à 16 MHz / 64, soit 250 kHz


  // Génération d'un signal PWM sur la sortie D6 (connecté à la pin OC0A du µC)
        //  Mode | COM0A1 | COM0A0 | RÉSULTAT  
        //  1er  |    0   |    0   | PWM stoppé (OC0A déconnecté, correspondant à la sortie D6)
        //  2ème |    0   |    1   | PWM stoppé si WGM02=0, ou autobasculant (mode "Toggle") si WGM02=1
        //  3ème |    1   |    0   | PWM en mode "non inversé" : sortie D6 mise à 0 lorsque la valeur de timer0 atteint la valeur de OCR0A ; sortie D6 mise à 1 à chaque remise à zéro du timer0
        //  4ème |    1   |    1   | PWM en mode "inversé" : sortie D6 mise à 1 lorsque la valeur de timer0 atteint la valeur de OCR0A ; sortie D6 mise à 0 à chaque remise à zéro du timer0
  // Ici, on utilisera le 2ème mode ("toggle"), qui est "obligatoire" pour utiliser le mode PWM "TOP = OCRA"
  bitClear(TCCR0A, COM0A1);
  bitSet(TCCR0A, COM0A0);
  OCR0A = VALEUR_DE_OCRA;


  // Génération d'un signal PWM sur la sortie D5 (connecté à la pin OC0B du µC)
        //  Mode | COM0B1 | COM0B0 | RÉSULTAT  
        //  1er  |    0   |    0   | PWM stoppé (OC0B déconnecté, correspondant à la sortie D5)
        //  2ème |    0   |    1   | Inutilisé
        //  3ème |    1   |    0   | PWM en mode "non inversé" : sortie D5 mise à 0 lorsque la valeur de timer0 atteint la valeur de OCR0B ; sortie D5 mise à 1 à chaque remise à zéro du timer0
        //  4ème |    1   |    1   | PWM en mode "inversé" : sortie D5 mise à 1 lorsque la valeur de timer0 atteint la valeur de OCR0B ; sortie D5 mise à 0 à chaque remise à zéro du timer0
  // Ici, on utilisera le 3ème mode ("normal", aussi appelé mode "non inversé") 
  bitSet(TCCR0A, COM0B1);
  bitClear(TCCR0A, COM0B0);
  OCR0B = VALEUR_DE_OCRB;

}

Si vous faites des essais en mode Fast PWM :

  • La sortie OC0A (pin D6) devrait avoir :
    • Une fréquence de 16 MHz / 64 / (190 + 1) / 2 = 654,45 Hz
    • Un rapport cyclique de 50% (Toggle Mode « obligatoire » ici)
  • Et concernant la sortie OC0B (pin D5) :
    • Une fréquence de 16 MHz / 64 / (190 + 1) = 1308,90 Hz
    • Un rapport cyclique de (60 + 1) / (190 + 1) = 31,93 %

Et en mode Phase Correct PWM :

  • La sortie OC0A (pin D6) devrait avoir :
    • Une fréquence de 16 MHz / 64 / (190/2) / 2 = 328,94 Hz
    • Un rapport cyclique de 50% (Toggle Mode « obligatoire » ici)
  • Et concernant la sortie OC0B (pin D5) :
    • Une fréquence de 16 MHz / 64 / (190/2) = 657,89 Hz
    • Un rapport cyclique de 60 / 190 = 31,57 %

Au final, on voit que le mode « TOP = OCRA » permet d’atteindre bien d’autres fréquences PWM encore. À noter que la valeur du registre OCRA peut être sur 8 bits (timer 0 et timer 2), tout comme être sur 16 bits (timer 1), dans le cas d’un microcontrôleur ATmega328P. Vous pouvez donc vraiment générer une pléiade de signaux PWM, avec un « simple » arduino !

Arduino PWM : conclusion !

Voilà ! Je pense que nous avons fait le tour de tout ce qu’il y a à savoir sur les signaux PWM des « classiques » Arduino, lorsqu’on débute en électronique. Bien sûr, il y aurait encore beaucoup à dire, si l’on rentrait encore plus dans les détails. Mais sincèrement, je pense avoir suffisamment approfondi pour la plupart d’entre nous, amateurs d’électronique !

À noter que j’ai surtout pris en exemple le microcontrôleur ATmega328P, équipant notamment les Arduino Uno et Nano. Bien évidemment, la démarche d’apprentissage reste la même, quelque soit l’arduino utilisé (c’est-à-dire : récupérer le datasheet du µC, regarder son architecture interne, cibler les registres dont on a besoin, et noter les valeurs à mettre dedans pour arriver au résultat souhaité !).

Du reste, j’espère que tout ce contenu vous aidera à mieux comprendre le PWM arduino, et puisse vous servir de support à tous vos projets courants, ou à venir !

Voilà ! À bientôt 😉

Jérôme.

À découvrir aussi : introduction aux timers de l’ATmega328P

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

(*) Mis à jour le 08/03/2022

23 commentaires sur “PWM arduino : explications (broches de sortie, rapport cyclique, fréquences, modes), avec exemples de code pratiques !”

  1. Site passionelectronique.fr

    Bonjour,

    Un document très pédagogique et très bien écrit qui permet de comprendre le fonctionnement interne d’un µc et le rôle des registres d’état internes. En particulier, le fait que tout code Arduino ne fait qu’interagir avec des registres et non réellement avec des signaux. Le fonctionnement des timers est aussi très bien explicité.

    Jean-Luc Curilla, Groupe ESEO – Angers

  2. Site passionelectronique.fr

    Bonjour, merci pour ce tuto.

    Je me demandais si vous aviez un exemple pour générer un signal PWM afin de contrôler un servomoteur.
    J’ai écrit un code en utilisant le timer/compteur1 en mode 14, mais je ne suis pas certain d’avoir choisi le bon mode.
    Le code effectue un simple balayage de 0 à 180° mais le servomoteur se bloque aléatoirement.

    Cdt.
    Serge.

    1. Site passionelectronique.fr

      Salut Serge !

      Désolé, je n’ai pas d’exemple sous la main, qui soit « prêt à l’emploi ».
      Je me penche dessus, si j’arrive à me dégager du temps (ce qui n’est pas gagné, en ce moment !).

      @+
      Jérôme.

    2. Site passionelectronique.fr

      Re,

      Voici ce que j’ai pu faire de mon côté (avec le mode OCR1A du timer1), pour arriver à piloter un servomoteur via le timer Arduino, directement :

      // *******************************************************************
      // PROGRAMME : permet de sortir un signal de 50 Hz sur la sortie D10,
      //             avec rapport cyclique ajustable à la demande,
      //             afin de pouvoir PILOTER UN SERVO, depuis un Arduino Uno ou Nano
      //
      // REMARQUES : - un servo nécessite un signal PWM de fréquence 50 Hz pour fonctionner (soit une période de 20 ms)
      //             - en modélisme, ce signal a typiquement une "durée à l'état haut" comprise entre 1 et 2 ms ; ça donne normalement du 0~90° d'angle (ou du -45°~45°, si tu préfères)
      //             - en pratique, c'est plutôt une durée à l'état haut variant entre 0,5 et 2,5 ms qui serait nécessaire d'envoyer, pour atteindre les 0~180° (ou -90/90, si tu préfères)
      //             - nota : 0.5/20 ms (le minimum, donc) correspond à un rapport cyclique de  2,5%
      //                      2.5/20 ms (le maximum, donc) correspond à un rapport cyclique de 12,5%
      // *******************************************************************
      
      // Rapport cyclique du signal PWM (valeur qui doit donc être comprise entre 0.025 et 0.125, pour atteindre les 0 à 180° d'angle)
      float valeurDuRapportCyclique = 0.075;          // --> pour un rapport cyclique à  2.5%, mettre 0.025 (minimum -> 0°)
                                                      // --> pour un rapport cyclique à  7.5%, mettre 0.075 (valeur médiane -> 90°)
                                                      // --> pour un rapport cyclique à 12.5%, mettre 0.125 (maximum -> 180°)
      
      void setup()  
      { 
        // Déclaration de la broche D10 en sortie (Arduino Uno ou Nano, utilisé ici)
        pinMode(10, OUTPUT);
        
        // Sélection du mode "Fast PWM avec TOP=0CR1A", du Timer 1 de l'ATmega328P (µC de l'Arduino Nano/Uno)
        bitSet(TCCR1B, WGM13);        // Mise à 1 du bit WGM13
        bitSet(TCCR1B, WGM12);        // Mise à 1 du bit WGM12
        bitSet(TCCR1A, WGM11);        // Mise à 1 du bit WGM11
        bitSet(TCCR1A, WGM10);        // Mise à 1 du bit WGM10
        
        // Valeur de OCR1A = durée totale d'une période de signal PWM
        OCR1A = 39999;      // Nota : ces 39999 permettent d'obtenir une fréquence de 400 Hz en sortie, de base,
                            //        s'il n'y a pas de prédivision de fréquence au niveau du timer
        
        // Valeur OCR1B = durée du signal à l'état haut (donc, une fraction de OCR1A)
        OCR1B = OCR1A * valeurDuRapportCyclique;
      
        // Réglage du prévidiseur du Timer1 pour une "division de fréquence par 8" (afin d'obtenir du 50 Hz, "à partir" des 400 Hz vus ci-dessus)
        TCCR1B &= 0b11111000;
        TCCR1B |= 0b00000010;   // Nota : les 3 derniers bits correspondent à CS12/CS11/CS10 (la valeur "010" spécifie une division de fréquence par 8, ici)
        
        // Génération de ce signal PWM sur la sortie D10 de l'Arduino
        bitSet(TCCR1A, COM1B1);
        bitClear(TCCR1A, COM1B0);
      }
      
      void loop()  
      { 
        // Nada ! Tout se passe dans la partie "setup"
      }

      J’ai vérifié à l’oscillo, puis en branchant un servo, ça marche nickel ! Maintenant, il ne te reste plus qu’à t’en inspirer, si cette façon de faire te convient !

      @+
      Jérôme.

  3. Site passionelectronique.fr

    Bonjour Jérôme.

    Merci pour cette présentation très clair des fonctions PMW.

    Pour un projet, j’utilise des petits servos modifiés (c.-à-d. l’axe du potentiomètre est coupé). L’avantage, c’est que par une seule sortie de l’arduino l’on peut commander : marche, arrêt et deux sens de rotation. Cependant, la librairie Servo n’est pas très souple, et j’ai besoin d’indexer une position relativement précise. Je vais m’inspirer de votre publication précédente pour envoyer un court train d’impulsion.

    J’ai 2 questions :

    • La modification des registres est-elle réversible ?
    • Pour ce projet j’utilise la librairie « millis » je crains une éventuelle interaction ?

    Bonne journée.

    Cordialement
    Didier

    1. Site passionelectronique.fr

      Salut Didier !

      Alors, concernant tes deux questions :

      • Oui, la modification d’un registre peut être vue comme réversible (en ce sens où on peut dynamiquement modifier sa valeur, et ce, autant de fois qu’on le veut)
      • La fonction « millis », quant à elle, utilise le timer0 de l’ATmega328P ; donc libre à toi d’utiliser le timer 1 ou 2, selon tes besoins (en sachant que le timer1 compte sur 16 bits, ce qui peut être plus précis selon les cas, tandis que le timer 2 compte « seulement » sur 8 bits)

      Voilà ! En espérant avoir pu t’aider un peu dans ton projet !
      Bon courage à toi 😉

      Jérôme.

  4. Site passionelectronique.fr

    Merci infiniment d’avoir pris du temps pour me répondre aussi rapidement !
    Le projet en question est une éphéméride analogique (phase de lune, lever/couché du soleil, heure zénithale, durée du jour).

    Bonne journée Jérôme et encore bravo pour vos publications.
    Didier

  5. Site passionelectronique.fr

    Bonjour Mr,

    Vous avez mis le #code2 pour changer la fréquence, en jouant sur les prédiviseurs de fréquence timer. À la fin du code, on a un analogWrite pour changer le rapport cyclique.

    De notre côté, la fréquence reste à 490hz, car nous n’avons pas appliqué le TCCR2B. Je me demande comment nous pouvons appliquer le TCCR2B pour avoir une fréquence en sortie de 31kHz ? Ou avez-vous changé la fonction analogWrite pour qu’elle change la fréquence aussi. Si c’est le cas est que vous pouvez nous l’envoyer ?

    Cordialement.

    1. Site passionelectronique.fr

      Salut !

      En fait, il n’y a rien à toucher au programme #2, pour obtenir une fréquence de 31 kHz en sortie.

      Histoire d’être sûr, je viens de refaire rapido le montage et les mesures, et j’obtiens bien un signal de 31kHz, et non de 490 Hz. La preuve en photo !

      Montage arduino 31 kHz signal carré, fréquence générée par programme, avec réglage possible du rapport cyclique dans code

      Donc, il suffit de garder le programme originel 😉

      Jérôme.

  6. Site passionelectronique.fr

    Bonjour Jérôme et merci pour ce magnifique Tuto, très précis et pédagogique.
    Il correspond exactement à ce que je cherchais en vain depuis longtemps.

    Juste une question :

    • je veux coder des 1 et des 0 par un rapport cyclique 60% et 40 % d’une même fréquence (pour transmission hertzienne)
    • d’après ce tuto je pense donc utiliser :
      • Phase Correct PWM
      • OCRxA, le temps TOTAL de l’onde PWM (pour définir la fréquence)
      • puis, lors de chaque descente du signal (détecté dans la routine d’interruption correspondante), OCRxB (pour définir le rapport cyclique de la période suivante)

    Est-ce que j’ai bien tout compris ?

    Merci d’avance si vous trouvez un petit moment pour me répondre.
    Cordialement.

    1. Site passionelectronique.fr

      Bonsoir Jean-Loup,

      Oui, c’est bien ça (comme décrit dans le point 11, un peu plus haut).
      Enfin… sauf erreur de ma part, car là je suis à plat !

      Bonne soirée à toi 😉

  7. Site passionelectronique.fr

    Merci pour ce tuto, j’avais besoin de connaitre les pins compatibles PWM sur les différents modèles d’Arduino (j’en utilise au moins 3) et j’ai eu toutes les réponses, et même plus !

    Je vais noter ces informations dans les commentaires de mon code avec le lien vers cette page.

    Cordialement

  8. Site passionelectronique.fr

    Bonjour,
    Merci pour cet article, je cherche depuis un moment des infos claires sur les PWM en arduino pour m’attaquer à un projet Arduino + élec : créer un générateur alternatif triphasé (en 3×12V eff), piloté par un arduino, donc je pourrais même faire varier la fréquence.
    Chez les fournisseurs pour l’éduc, ce type de générateur est généralement à plus de 600€.
    Ce que j’ai en tête :
    – un arduino qui génère 3 signaux en PWM, peut-être avec une table de sinus préprogrammée,
    – une conversion de chacun de ces signaux en tension filtrée, allant de -17V à +17V (si je veux rester sur du 12Veff) ou +/- 8,5 pour du 6V eff
    – une fréquence finale soit à 50Hz, soit de 5 à 100 Hz par ex (une molette pour faire varier)
    Je crains que cela représente un paquet « d’étages » à mettre en place pour arriver à quelque chose de fonctionnel (je parle même pas de l’aspect pratique !).
    Je continue mes recherches 😉

  9. Site passionelectronique.fr

    N’ayant pas peur de faire une lapalissade, je dirais que vos explications sont d’une clarté … limpide.

    Votre présentation est très pédagogique.

    Merci beaucoup.
    Pierre.

  10. Site passionelectronique.fr

    Bonjour Jérôme,

    Un grand merci pour ton partage d’une grande valeur pédagogique. Cerise sur le gâteau, les exemples de prog fonctionnent ! Je remplace un récepteur de radiocommande 7 canaux par un Nano et ton site m’a bien aidé.

    Cordialement.
    Gérard

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Symbole danger point d'exclamation site PSE, triangle jaune avec contour noir, texte noir alerte au milieuAfin de filtrer au maximum les messages de type "spam" ou "inappropriés", chaque commentaire est soumis à modération, et validé manuellement. Du coup, il se peut que certains commentaires ne soient pas publiés, ou sinon, avec un peu de retard. Par ailleurs, j'ai malheureusement plus de messages à traiter que de temps pour y répondre ; c'est pourquoi je ne pourrais pas répondre à tout le monde. Désolé …

Étiquettes: