Jusqu’à présent, dans les exemples qui ont illustré les articles sur LOCODUINO, nous avons vu l’émission de signaux numériques par le biais des broches numériques et de digitalWrite(...) et l’allumage et l’extinction de DEL. Ces signaux numériques ont soit une valeur égale à LOW, c’est à dire 0V, soit une valeur égale à HIGH, ce qui correspond à la tension d’alimentation de l’Arduino, VDD, soit 5V pour les Arduino à base de micro-contrôleurs AVR ou bien 3,3V pour les Arduino à base de micro-contrôleurs ARM, voir à ce sujet « Qu’est ce qu’une carte Arduino ? ».
Mais les sorties numériques ne nous permettent pas, par exemple, de régler la luminosité de la DEL, ou de faire varier la vitesse de rotation d’un moteur [1]. Pour pouvoir faire cela, il serait nécessaire de pouvoir disposer de sorties permettant des tensions intermédiaires entre LOW et HIGH.
Mais ceci n’existe pas sur les Arduino à base d’AVR [2]. L’alternative est d’utiliser une PWM, pour Pulse Width Modulation, ou MLI en français pour Modulation de largeur d’impulsion [3].
Qu’est ce que la PWM ?
On reste en numérique, les signaux ont toujours une valeur LOW ou HIGH et le principe est de construire un signal qui est alternativement LOW et HIGH et de répéter très vite cette alternance. La DEL est donc alternativement allumée et éteinte mais le cycle est tellement rapide que la persistance rétinienne nous donne l’illusion d’une DEL allumée en permanence. Nous avons déjà rencontré ce phénomène dans « La programmation, qu’est ce que c’est » où, en tentant de faire clignoter une DEL, on obtenait un allumage permanent mais d’une luminosité moindre.
Prenons par exemple une période de 10ms, soit une fréquence de 100Hz. Si la DEL est allumée pendant 5ms et éteinte pendant 5ms, comme sur la figure ci-dessous, l’impression sera une luminosité de 50% de la luminosité maximum.
PMW à 50%
La fréquence est de 100Hz, le rapport cyclique de 50%
Si la DEL est allumée pendant 1ms et éteinte pendant 9ms, l’impression sera une luminosité de 10% comme sur la figure ci-dessous.
PWM à 10%
La fréquence est de 100Hz et le rapport cyclique de 10%.
Le pourcentage de temps passé à l’état HIGH sur la période du signal est appelé le rapport cyclique. Il varie donc de 0%, le signal est tout le temps LOW, à 100%, le signal est tout le temps HIGH.
Les capacités PWM des Arduino
Les micro-contrôleurs proposent donc des dispositifs permettant de disposer de PWM autonomes où le signal voulu est engendré indépendamment de l’exécution du programme. Toutes les broches ne sont pas utilisables. Sur l’Arduino Uno, les broches concernée portent le symbole ’~’. Sur le Uno et les Arduino à base d’ATMega 328, 6 broches sont disponibles, sur le Mega, 15 broches sont disponibles. Le tableau ci-dessous résume la disponibilité des PWM sur les cartes Arduino.
Modèle d’Arduino
Broches PWM
Uno, Pro Mini, Nano
3, 5, 6, 9, 10 et 11
Mega
2 à 13, 44 à 46
Leonardo
3, 5, 6, 9, 10, 11 et 13
Due
2 à 13
Zero
2 à 13
La fréquence de la PWM est prédéterminée sur l’Arduino. Il est possible de la changer comme nous le verrons dans un futur article mais ce n’est pas une possibilité accessible très simplement. La fréquence n’est pas la même selon les broches. Sur le Uno, la fréquence est de 490Hz sur les broches 3, 9, 10 et 11 et de 980Hz sur les broches 5 et 6.
Mise en œuvre
Nous allons mettre en œuvre la PWM avec une simple DEL. Le câblage reste identique à ce qui a été présenté dans « Fonctionnement et pilotage d’une DEL » dans la figure de gauche excepté que nous allons employer la broche 3 au lieu de la 2 car cette dernière n’a pas la fonction PWM.
En ce qui concerne la programmation, la fonction permettant de fixer le rapport cyclique de la PWM est la fonction analogWrite(...). Nom bien mal choisi puisque, comme on l’a vu, tout est numérique. Le premier argument de analogWrite(...) est la broche concernée et le second argument le rapport cyclique. Le rapport cyclique n’est pas donné de 0 à 100 mais de 0 à 255. Il faut donc faire une règle de 3 pour calculer la valeur à appliquer pour le rapport cyclique voulu. Par exemple, la valeur à appliquer pour un rapport cyclique de 75% sera égal à 0,75 x 255 = 191.
La figure ci-dessous montre différents réglages de PWM.
Figure 1
Rapport cyclique et PWM
Voici pour commencer un programme très simple qui augmente progressivement, de 1 en 1, la luminosité de la DEL de 0, extinction totale, à 255, luminosité maximum, puis recommence à 0. On va fixer le temps de ce cycle à 1000ms. Le délai entre chaque augmentation est donc de 1000 / 255 = 3,922 ms. Arrondissons à 4ms. En utilisant un byte pour stocker la valeur, nous pouvons profiter du débordement qui se produit lorsque, étant à 255, l’ajout de 1 fait repasser la valeur 0. Voir à ce propos « Types, constantes et variables ». Vous noterez également qu’il n’est pas nécessaire de programmer la broche en sortie pour son utilisation en tant que sortie PWM. Cette programmation s’effectue automatiquement lors du premier analogWrite(). Malgré tout il faut que la fonction setup() soit présente même si elle est vide.
Nous allons maintenant prendre comme objectif de reproduire un feu clignotant comme montré dans cette vidéo.
Comme on peut le voir, le feu ne passe pas instantanément de 0 à 100% ni de 100 à 0% Il y a une phase d’augmentation et une phase de diminution. Après examen, on constate que le temps pendant que le feu est à 0% et à 100% est d’environ 200ms. Le temps de passage de 0 à 100% et de 100 à 0% est d’environ 250ms. La période de clignotement est de 900ms. Le programme doit donc :
mettre la PWM à 0
attendre 200ms
augmenter la PWM de 1 toutes les millisecondes jusqu’à atteindre 250
attendre 200ms
diminuer la PWM de 1 toutes les millisecondes jusqu’à atteindre 0
recommencer à l’étape 2
Fort de ce que nous savons déjà faire en programmation, nous allons déclarer une structure pour gérer notre feu et gérer le temps via la fonction millis(). Vous pouvez lire à ce propos « Les structures » et les articles précédents sur le programmation ainsi que « Comment gérer le temps dans un programme ? ».
Notre feu clignotant a 4 états : ETEINT, EN_AUGMENTATION, ALLUME et EN_DIMINUTION qui sont déclarés via un enum, voir « Trois façons de déclarer des constantes », et mémorisés dans un membre de notre structure. Nous avons également besoin de la date à laquelle le feu clignotant a changé d’état.
La structure contient donc 3 membres : la broche sur laquelle la DEL du feu est connectée, l’état du feu et la date de dernier changement d’état :
struct FeuClignotant {
constbyte pin;
byte etat;
unsignedlong dateDernierChangement;
};
Nous créons maintenant une variable de type struct FeuClignotant et l’initialisons :
struct FeuClignotant feuPN ={3, ETEINT,0};
Au lieu d’augmenter puis de diminuer la PWM de 1 toutes les millisecondes, nous allons calculer sa valeur : si le feu est dans l’état EN_AUGMENTATION, c’est le temps écoulé, en millisecondes depuis le passage du feu à l’état EN_AUGMENTATION. De même si le feu est EN_DIMINUTION, c’est 250 moins le temps écoulé, en millisecondes depuis le passage du feu à l’état EN_DIMINUTION. La fonction gereFeuClignotant(...) est donc programmée en utilisant un switch ... case pour faire évoluer l’état du feu en fonction de l’écoulement du temps, voir à ce propos l’article « Instructions conditionnelles : le switch … case ».
// Les 250 ms sont passées, on change l'état du feu
feu.dateDernierChangement= date;
feu.etat= ETEINT;
}
break;
}
}
Enfin, dans loop, la fonction gereFeuClignotant(...) est appelée de manière répétitive.
void loop()
{
gereFeuClignotant(feuPN);
}
Vous remarquerez que cette fonction gère le temps de manière non bloquante. Il est donc possible de gérer simultanément plusieurs feux de passage à niveau voire de gérer simultanément d’autres fonctions.
[2] En revanche, cette fonction est disponible sur l’Arduino Due sur les broches DAC0 et DAC1. Le micro-contrôleur du Due possède des convertisseurs numérique analogique.
[3] Peut-être n’avez vous jamais essayé d’alimenter une locomotive avec une tension continue, une vraie avec une alimentation de laboratoire car nos chers transformateurs ne délivre pas une tension continue. Le résultat est médiocre. La machine hoquette, sa vitesse est instable.
Dans le cadre des alimentations PWM je serais intéressé par une alimentation traction "moderne" à asservissement de vitesse par exemple gérée par un Arduino.
Je n’ai rien trouvé sur la toile, mais j’ai peut-être mal cherché.
Ce pourrait être l’objet d’un nouveau sujet qui intéresserait d’autres modélistes
Merci pour vos réponses
À vrai dire un tel projet est dans les cartons. Un 2e article traitant de la PWM appliquée aux moteurs est en cours de rédaction. Concernant l’asservissement de vitesse, une carte à été conçue et je suis en train de développer le logiciel. En fait cette carte va au delà d’une alimentation à asservissement de vitesse, elle est destinée à piloter un canton et à collaborer avec ses semblables via un réseau CAN mais rien n’empêche de l’utiliser seule. Deux sujets sur le forum existent : http://forum.locoduino.org/index.php?topic=36.0 et http://forum.locoduino.org/index.php?topic=74.0 . Le prototype de programme de cette alimentation avec asservissement de vitesse est ici : https://git.framasoft.org/Koryphon/AlimentationTraction/tree/master
J’ai besoin de commander 30 servos.
Normalement ils doivent être branchés sur des pins PWM, sur l’Arduino Mega ils y en 15, mais n’est-il pas possible d’utiliser les pins A0 à A15 en sortie PWM ? ce qui au total me permettrait d’utiliser mes 30 servos avec un seul Arduino.
D’avance merci pour la réponse
Non, il n’est pas nécessaire de connecter les servos aux broches PWM. La bibliothèque utilise les broches numériques normales et le Mega peut piloter 48 servos. La limite vient du multiplexage de la génération des impulsions par les interruptions des timers.
Mais, j’imagine que vous voulez commander 30 servos pour des aiguillages. Votre réseau est donc plutôt grand. Par conséquent, utiliser un unique Mega nécessitera des fils assez longs entre le Mega et les servos, certains atteignant 2m et peut être plus. Je vous garantie que vos servos vont frétiller car, sur le signal de commande, très peu de courant circule et la tension ne sera pas stable. Les parasites seront interprétés comme des consignes de position et entraîneront dès mouvements erratiques. Un camarade a expérimenté le problème : le fait de mettre une nourrice sous tension engendrait des mouvements des aiguilles.
Il faut donc employer plusieurs Arduino afin de garder des câbles courts, moins de 1m entre un Arduino et les servos qu’il commande. Les Arduino sont reliés par un bus conçu pour être robuste aux parasites.
Bonjour,
Je voudrais juste rebondir sur le fait qu’en éloignant les servo des commandes,des parasites font marcher les servos de façon ératique.
Simplement les courants parasites sont de trés faible puissance. Il faut juste que la ligne soit en basse impédance pour que les parasites se perdent , juste mettre des résistances de faible valeur en parallèle sur le fil de commande, et verifier de ne pas trop descendre pour que le courant fourni par la commande ne mette pas le controleur à genoux.La solution idéale étant de bufferiser la ligne.
Pour augmenter le nombre de sorties PWM commandées par un même Mega, il faut recourir à une ou plusieurs cartes d’extension dont on va vous trouver des exemples,
Merci à Jean-Luc et Dominique pour ces réponses rapides et précises.
Je vais donc plutôt m’orienter vers plusieurs UNO à la place d’un MEGA car effectivement par endroit j’avoisine les 2m de distance en plaçant le MEGA au centre.
Je vais chercher les infos sur le bus de liaison entre plusieurs UNO.
C’est justement l’exemple simple du début de l’article, mais ce programme ne fait QUE ça !
Pour commander plusieurs objets (feux, servos, etc..) en même temps et en toute indépendance, il faut abandonner "delay()" au profit de "millis()", ce que font les bibliothèques pour nous simplifier la vie.
Désolé, je n’avais pas compris la différence… Maintenant, j’ai compris… Mais comme je débute sur Arduino, je m’étais étonné de la complexité de votre programme… pas si compliqué en fait !!
Merci en tout cas, pour toutes les richesses de ce site !
Je pense utiliser Arduino pour gérer mes cantons et ma gare cachée, donc j’ai du pain sur la planche…
bonjour j’ai besoin vos aides si vous voulez je cherche d’ecrire un programme arduino uno on utilise le PWM ce programme est comme suit alumer une lampe pas a pas contre l alumage on et off sa vous dire qu’il faut jouer sur le rapport alpha et merci
bonsoir svp j’ai une question jai un arduino uno et j’utilise les 6 ports analogwrite pour allumé 6 leds blanche j’ai mis comme rapporot cyclique la valeur 80
est ce que l’intensité n’est pas la même sur les 6 leds vu que les fréquences ne sont pas les même sur les broches 3, 9, 10 et 11 et sur les broches 5 et 6 ? personalement a mes yeux je remarque aucune différence
merci de votre réponse
Bonjour,
L’intensité dépend en première approximation du rapport cyclique et pas de la fréquence, surtout pour des fréquences aussi proches (facteur 2).
bonjour svp quel est l’unité de l’intensité de la luminosité de la DEL genre pour un rapport cyclique de 75% l’intensité égal à 0,75 x 255 = 191
mais 191 quoi ??
merci d’avance
La « valeur » de la PWM est sur un octet. Le maximum est donc de 255, ce qui correspond à 100% de rapport cyclique. Pour fixer le rapport cyclique à n%, il suffit de faire une règle de 3 : n/100 = p/255. Donc p = 255 x n / 100. En ce qui concerne l’unité physique, 255 est un temps et correspond à la durée de la période de la PWM. 191 est également un temps.
un moteur brushless se commande avec un contrôleur brushless. Si vous disposez de contrôleurs brushless pour vos moteur et si la question est de savoir comment commander ces contrôleurs avec une PWM alors la réponse est que ça se commande comme un servomoteur et donc vous pouvez utiliser la bibliothèque Servo.
Ce fameux article sur la PWM m’a permis d’y voir plus clair, merci, cependant une question m’obsède.
Car, ayant un projet dans la robotique je souhaiterai synchroniser mon microcontroleur Arduino a mon logiciel de musique. Grace a la carte ARM une fonction "MIDI READ" est disponible dans la bibliothèque ARDUINO. Du coup, par le biais de cette fonction j’envisage de relier la PWM au metronome de mon logiciel.
Ma question est la suivante, est il possible de transmettre les données de BPM (battements par minute) du metronome a la PWM du microcontroleur ? je pense que oui, mais je souhaiterai avoir une vision plus clair de la chose, merci.
Bonjour je viens de faire un copier coller de votre code mais je n’arrive pas
à le faire fonctionner.
Auriez vous un lien ou il serait coder en une seule fois .
MERCI.
Le code présenté dans l’article est incomplet, il n’est là que pour être expliqué et certaines déclarations manquent. Le code complet sous forme d’archive zip, comme souligné ci-dessous par msport, est à la fin de l’article.
je suis en pleine création d’un pédalier d’effets pour guitare. Je vais utiliser plusieurs Arduinos en I2C à fin de piloter 48 relais, 10 écrans OLED, 10 switchs au pied, une 10ène de type micro boutons, une 20ène de LED et de 34 potards analogiques linéaires et logarithmiques.
J’aimerais comprendre un truc sur les sorties PWM. Imaginons que je rentre mon potard 1 linéaire dans mon entrée A0, alimenté en 5V, que je récupère l’info reçue (0 à 1023), que je la transforme à 0-255, puis-je utiliser ces infos pour piloter le gain sur ma pédale via une sortie PWM ? En gros, au lieu d’utiliser mon potard directement, j’aimerais passer par l’Arduino, et du coup, pouvoir sauvegarder et rappeler les valeurs via ma carte SD ?
j’ai surement loupé des tutos mais j’ai 2 Pb de compréhension
1) avec (struct FeuClignotant& feu) plus particulièrement avec & feu. pourquoi l’ajouter ? et pourquoi &
2) avec switch (feu.etat) plus particulièrement avec feu.etat c’est une variable ? où la trouve-t-on ? et explications sur ce point . dans arduino
désolé peut-être déjà expliqué mais où ? je suis débutant jusqu’à présent pas de Pb mais là avec les structures, nœud au cerveau. mais c’est super, très bon tuto.
Cordialement
Phil
1) En C++, le & dans la déclaration d’un argument de fonction signifie que l’argument est passé par référence et non par valeur. Si l’argument était passé par valeur, il faudrait copier 6 octets (les deux byte et l’unsigned long). Avec le &, c’est l’adresse de l’argument qui est passé et donc, sur Arduino AVR 8 bits, 2 octets seulement. Ça permet d’économiser de la mémoire.
2) feu est l’argument, comme c’est une struct qui possède 3 champs : pin, etat et dateDernierChangement, on accède aux champs via le . et leur nom. feu.etat permet donc d’accéder au champ etat de la struct. Voir Les structures.
Bonjour Jean-Luc
désolé pour ce retard de réponse mais déjà MERCI du retour.
je pense déjà descendre d’un échelon (voir plus) car je pensais être à l’aise avec mon simulateur Tinkercad (pas tjrs juste calage avec servomoteur), les boucles, les bouton les feux, millis() etc… mais là avec les structures et les pointeurs je pense avoir sauté plusieurs classes et ne pas être de niveau (pour l’instant).
bien que ton explication soit parfaitement explicite c’est mon cerveau qui a du mal. je vais donc relire et relire ta réponse pour bien la comprendre. tu sais je débute après un passage sur une matière qui me tenait à cœur : l’électronique. je me souviens les boites que m’avait acheté mes parents pour faire des montages. A la retraite, j’ai voulu mis mettre sérieusement, j’ai donc édité les 3 volumes de "Apprendre l’électrique en partant de zéro niveau 1, 2 et 3" (300 pages à chaque fois.
je n’ai tjrs pas fini le premier volume car ce n’est pas évident et en plus quand j’en ai parlé autour de moi, on m’a dit c’est du passé, il faut évoluer. il faut passer à Arduino avec sa programmation. toi qui aime l’informatique, pas de pb. au début je n’étais pas pour mais avec Tinkercad et quel bouquins, super mais tout à une limite donc les "structures" avec modération pour bien les digérer.
j’ai même trouvé des erreur dans l’édition de 2018 de "l’Arduino à l’école". et j’ai cherché car pour moi un livre, un prof ne peuvent se trompé. et bien si. dans leur schéma de montage le bouton est faux, il utilise la mauvaise pas.
Bref, je parle, je parle des fois pour ne rien dire mais sincèrement encore MERCI er heureusement qu’il y a des gens comme toi, Christian et doit échanger avec Thierry (pas encore fait).
si une dernière petite chose, après échange avec Christian je me suis lancé dans le langage C qui étudier les structures (un peu digéré mais pas les pointeurs), tu me parle du C++ donc je dois étudier le c++ ou rester avec mon C ???
Merci du retour
bon courage pour la suite
Cordialement
Phil
Ps : désolé pour la longueur
Bonjour Jean-Luc
encore moi, super le programme mais j’ai encore un Pb.
je voudrai imprimer sur le moniteur série (Tinkercad) l’état de "feu.etat".
j’ai donc testé 3 possibilités et rien ou message d’erreur ? comment puis-je faire ?
j’ai écrit :
switch(feu.etat)
//Serial.print("feu.etat : "); {{à ce niveau message d'erreur}}
//Serial.println(feu.etat);
{"{{symbole d'ouverture de switch qui ne s'imprime pas}} {"
Serial.print("feu.etat : ");{{à ce niveau pas de visualisation}}
le "Serial.print" après le switch me provoque un message d’erreur et les autres "Serial.print" ne me bloque pas la simulation mais ne m’affiche rien dans le moniteur série de Tinkercad.
je cherche donc une explication car j’itilise assez souvent "Serial.print" pour vérifier l’état de variable ou le bon fonctionnement de mes boucles.
Merci du retour
Cordialement
Phil
Bonjour Jean-Luc
je viens trouver la source de mon erreur avec un autre programme.
si on ne déclare pas :
Serial.begin(9600) ; dans le set up, les Serial.print et Serial.println sont inactif dans le moniteur de tinkercad, donc aucun affichage de tes variables, valeurs.