Multitâche Arduino : Interruptions et Minuteries
Multitâche Arduino : Interruptions et Minuteries
Aperçu
Souvent, dans un projet, le processeur doit effectuer une seule
tâche supplémentaire. Comment peut-il le faire alors qu'il est
occupé par d'autres tâches ?
Photo de One Man Band vers 1865 par Knox via Wikimedia est
dans le domaine public
Dans ce guide, nous nous appuierons sur les techniques
apprises dans la partie 1 de la série tout en explorant plusieurs
types d'interruptions Arduino et en montrant comment elles
peuvent être utilisées pour aider votre Arduino à jongler avec
encore plus de tâches tout en gardant votre code simple et
réactif.
Nous apprendrons à exploiter les interruptions du minuteur pour
que tout fonctionne parfaitement. Nous explorerons également
l'utilisation d'interruptions externes pour nous notifier
d'événements externes.
Autres guides de cette série :
Multitâche sur l'Arduino - Partie 1
Multitâche sur l'Arduino - Partie 3
Installation
Pour tous les exemples de ce guide, le câblage suivant
sera utilisé :
Qu'est-ce qu'une interruption ?
Une interruption est un signal qui indique au processeur
d'arrêter immédiatement son activité et de traiter un traitement
prioritaire. Ce traitement prioritaire est appelé gestionnaire
d'interruptions.
Un gestionnaire d'interruptions est comme toute autre fonction
void. Si vous en écrivez un et l'associez à une interruption, il
sera appelé dès que ce signal d'interruption sera déclenché.
Lorsque vous quittez le gestionnaire d'interruptions, le
processeur reprend son activité initiale.
D'où viennent-ils ?
Les interruptions peuvent être générées à partir de plusieurs
sources :
Interruptions de minuterie provenant de l'un des minuteurs
Arduino.
Interruptions externes dues à un changement d'état de
l'une des broches d'interruption externes.
Les interruptions de changement de broche résultent d'un
changement d'état de l'une des broches d'un groupe.
À quoi servent-ils ?
Grâce aux interruptions, vous n'avez pas besoin d'écrire de
code en boucle pour vérifier en permanence la condition
d'interruption prioritaire. Vous n'avez pas à vous soucier des
lenteurs de réponse ou des appuis manqués sur les boutons dus
à des sous-routines à exécution prolongée.
Le processeur arrête automatiquement son activité lorsque
l'interruption se produit et appelle votre gestionnaire
d'interruption. Il vous suffit d'écrire du code pour répondre à
l'interruption lorsqu'elle se produit.
Copier le code
Copier le code
//si(digitalRead(2) == HAUT)
{
sweeper2. Mise à jour (currentMillis);
led1. Mise à jour (currentMillis);
}
Copier le code
boucle vide ()
{
}
Vous pouvez désormais faire tout ce que vous voulez dans votre
boucle. Vous pouvez même être décadent et utiliser un delay() !
Les flashers et les sweepers s'en ficheront. Ils seront quand
même appelés une fois par milliseconde !
Pour en savoir plus :
Ceci n'est qu'un exemple simple de ce que permettent les
minuteurs. Pour plus d'informations sur les différents types de
minuteurs et leur configuration, consultez la page
« Bibliothèques et liens ».
Le code source :
Voici le code complet, y compris les clignotants et les
balayeurs :
Télécharger le fichier
Copier le code
# inclure <Servo.h>
classe Flasher
{
// Variables des membres de classe
// Celles-ci sont initialisées au démarrage
int ledPin; // le numéro de la broche LED
long OnTime; // millisecondes de temps d'activation
long OffTime; // millisecondes de temps d'arrêt
OnTime = activé ;
OffTime = désactivé ;
ledState = BAS;
précédentMillis = 0 ;
}
classe Sweeper
{
Servo servo; // le servo
int pos; // position actuelle du servo
int increment; // incrément à déplacer pour chaque
intervalle
int updateInterval; // intervalle entre les mises à jour
unsigned long lastUpdate; // dernière mise à jour de la position
public :
Sweeper ( intervalle int )
{
updateInterval = intervalle;
incrément = 1 ;
}
void Détacher ()
{
servo.détacher () ;
}
Balayeuse balayeuse1 ( 25 ) ;
Balayeuse balayeuse2 ( 35 ) ;
configuration vide ()
{
balayeuse1. Attacher ( 9 );
balayeuse2. Attacher ( 10 );
si ( digitalRead ( 2 ) == HAUT)
{
sweeper2. Mise à jour (currentMillis);
led1. Mise à jour (currentMillis);
}
boucle vide ()
{
}
Interruptions externes
Quand il est préférable de rester en
dehors du coup
Contrairement aux interruptions de temporisation, les
interruptions externes sont déclenchées par des événements
externes. Par exemple, lorsqu'un bouton est enfoncé ou que
vous recevez une impulsion d'un encodeur rotatif. Cependant,
tout comme les interruptions de temporisation, il n'est pas
nécessaire d'interroger constamment les broches GPIO pour
changer de mode.
L'Arduino UNO possède deux broches d'interruption externes.
Dans cet exemple, nous allons connecter notre bouton-poussoir
à l'une d'elles et l'utiliser pour réinitialiser nos balais. Tout
d'abord, nous allons ajouter une fonction « reset() » à notre
classe de balai. La fonction reset() définit la position à 0 et
positionne immédiatement le servomoteur à cet endroit :
Télécharger le fichier
Copier le code
réinitialisation du vide ()
{
pos = 0 ;
[Link]( pos );
incrément = abs (incrément);
}
Copier le code
pinMode( 2 , INPUT_PULLUP);
attachInterrupt( 0 , Réinitialiser, CHUTE);
Copier le code
annuler Réinitialiser()
{
balayeuse1 .reset ();
sweeper2 .reset ();
}
Désormais, chaque fois que vous appuyez sur le bouton, les
servos arrêtent ce qu'ils font et recherchent immédiatement la
position zéro.
Le code source :
Voici le croquis complet avec les minuteries et les interruptions
externes :
Télécharger le fichier
Copier le code
# inclure <Servo.h>
classe Flasher
{
// Variables des membres de classe
// Celles-ci sont initialisées au démarrage
int ledPin; // le numéro de la broche LED
long OnTime; // millisecondes de temps d'activation
long OffTime; // millisecondes de temps d'arrêt
OnTime = activé ;
OffTime = désactivé ;
ledState = BAS;
précédentMillis = 0 ;
}
classe Sweeper
{
Servo servo; // le servo
int updateInterval; // intervalle entre les mises à jour
public :
Sweeper ( intervalle int )
{
updateInterval = intervalle;
incrément = 1 ;
}
void Détacher ()
{
servo.détacher () ;
}
void reset ()
{
pos = 0 ;
servo. écriture (pos);
incrément = abs (incrément);
}
Balayeuse balayeuse1 ( 25 ) ;
Balayeuse balayeuse2 ( 35 ) ;
configuration vide ()
{
balayeuse1. Attacher ( 9 );
balayeuse2. Attacher ( 10 );
pinMode ( 2 , INPUT_PULLUP);
attachInterrupt ( 0 , Réinitialiser, CHUTE);
}
void Réinitialiser ()
{
balayeuse1. réinitialiser ();
sweeper2. réinitialiser ();
}
//si(digitalRead(2) == HAUT)
{
sweeper2. Mise à jour (currentMillis);
led1. Mise à jour (currentMillis);
}
boucle vide ()
{
}
Bibliothèques et liens
En savoir plus sur les minuteries
Les temporisateurs peuvent être configurés pour
fonctionner à différentes fréquences et selon différents
modes. Outre la génération d'interruptions, ils servent
également à contrôler les broches PWM. Les liens ci-
dessous constituent d'excellentes ressources pour
comprendre comment configurer et utiliser les
temporisateurs :
Les secrets de l'Arduino PWM
Aide-mémoire sur les minuteries/PWM
Bibliothèques de minuteries
Il existe plusieurs bibliothèques de « minuteries » Arduino
disponibles sur le web. Beaucoup se contentent de
surveiller millis() et nécessitent une interrogation
constante, comme nous l'avons fait dans la première
partie de cette série. Mais quelques-unes permettent de
configurer des minuteries pour générer des interruptions.
Les excellentes bibliothèques TimerOne et TimerThree de
Paul Stoffregan gèrent de nombreux détails de bas niveau
de la configuration des interruptions de temporisation.
(Notez que TimerThree n'est pas compatible avec l'UNO. Il
est compatible avec les cartes Leonardo, Mega et
certaines cartes Teensy.)
Bibliothèques TimerOne et Timer Three
Interruptions de changement de
code PIN
Pour quand 2 ne suffisent pas
L'Arduino UNO ne possède que deux broches d'interruption
externes. Mais que faire si vous avez besoin de plus de
deux interruptions ? Heureusement, l'Arduino UNO prend
en charge les interruptions par changement de broche sur
toutes les broches.
Les interruptions de changement de broche sont similaires
aux interruptions externes. La différence réside dans le fait
qu'une interruption est générée pour tout changement
d'état sur l'une des huit broches associées. Leur gestion
est un peu plus complexe, car il faut suivre le dernier état
connu des huit broches pour déterminer laquelle a
provoqué l'interruption.
La bibliothèque PinChangeInt de l'Arduino Playground
implémente une interface pratique pour les interruptions
de changement de broche :
[Link]
Dernière révision27/05/2024
Introduction
La modulation de largeur d'impulsion (MLI) peut être implémentée sur l'Arduino de
plusieurs manières. Ce tutoriel explique des techniques PWM simples et comment
utiliser directement les registres PWM pour mieux contrôler le rapport cyclique et la
fréquence. Ce tutoriel se concentre sur les modèles Arduino Diecimila et
Duemilanove, qui utilisent l'ATmega168 ou l'ATmega328.
Objectifs
À propos des techniques de modulation de largeur d'impulsion.
analogWrite(pin, dutyCycle)
, où
dutyCycle
est une valeur comprise entre 0 et 255, et la broche est l'une des broches PWM (3, 5, 6, 9,
10 ou 11).
analogWrite()
Cette fonction offre une interface simple avec le PWM matériel, mais ne permet aucun
contrôle de la fréquence. (Notez que malgré le nom de la fonction, la sortie est un signal
numérique, souvent appelé onde carrée.)
analogWrite
, mais il existe d’autres options qui offrent plus de flexibilité.
Modulation de largeur d'impulsion Bit-Banging
Vous pouvez implémenter « manuellement » le PWM sur n'importe quelle broche
en activant et désactivant la broche à plusieurs reprises pendant les durées
souhaitées.
1void setup()
2{
3 pinMode(13, OUTPUT);
4}
6void loop()
7{
8 digitalWrite(13, HIGH);
10 digitalWrite(13, LOW);
11 delayMicroseconds(1000 - 100);
12}
Un exemple plus élaboré de PWMing manuel de toutes les broches peut être
trouvé ici .
Utilisation directe des registres PWM ATmega
La puce ATmega168P/328P possède trois temporisateurs PWM contrôlant six
sorties PWM. En manipulant directement les registres de temporisation de la puce,
vous obtenez un contrôle plus étendu que celui offert par la fonction analogWrite.
Un mot sur la relation entre le langage Arduino et la fiche technique peut être de
mise ici.
Registres de minuterie
Bits de mode de génération de forme d'onde (WGM) : ils contrôlent le mode général
du temporisateur. (Ces bits sont répartis entre TCCRnA et TCCRnB.)
Bits de sélection d'horloge (CS) : ils contrôlent le préscaleur d'horloge
Comparer les bits du mode de sortie A (COMnA) : ils activent/désactivent/inversent la
sortie A
Comparer les bits du mode de sortie B (COMnB) : ils activent/désactivent/inversent la
sortie B
Les bits étant légèrement différents pour chaque temporisateur, consultez la fiche
technique pour plus de détails. Le temporisateur 1 est un temporisateur 16 bits doté
de modes supplémentaires. Le temporisateur 2 possède des valeurs de prédiviseur
différentes.
PWM rapide
L'extrait de code suivant configure une modulation de largeur d'impulsion rapide sur
les broches 3 et 11 (Timer 2). Pour résumer les réglages des registres, la définition
des bits WGM du mode de génération de forme d'onde à 011 sélectionne une
modulation de largeur d'impulsion rapide. La définition des bits COM2A et COM2B
à 10 fournit une modulation de largeur d'impulsion non inversée pour les sorties A
et B. La définition des bits CS à 100 configure le prédiviseur pour diviser l'horloge
par 64. (Les bits étant différents selon les timers, consultez la fiche technique pour
connaître les valeurs correctes.) Les registres de comparaison de sortie sont définis
arbitrairement à 180 et 50 pour contrôler le rapport cyclique de la modulation de
largeur d'impulsion des sorties A et B. (Vous pouvez bien sûr modifier les registres
directement au lieu d'utiliser
pinMode()
, mais vous devez définir les broches sur la sortie.)
1pinMode(3, OUTPUT);
2 pinMode(11, OUTPUT);
4 TCCR2B = _BV(CS22);
5 OCR2A = 180;
6 OCR2B = 50;
Le deuxième mode PWM est appelé PWM à correction de phase. Dans ce mode, le
temporisateur compte de 0 à 255, puis redescend à 0. La sortie s'éteint lorsque le
temporisateur atteint la valeur du registre de comparaison de sortie à
l'augmentation, et se réactive lorsqu'il atteint la valeur du registre de comparaison
de sortie à la diminution. Le résultat est une sortie plus symétrique. La fréquence
de sortie sera environ la moitié de celle du mode PWM rapide, car le temporisateur
fonctionne à la fois en amont et en aval.
1pinMode(3, OUTPUT);
2 pinMode(11, OUTPUT);
4 TCCR2B = _BV(CS22);
5 OCR2A = 180;
6 OCR2B = 50;
Les modes PWM rapide et PWM à correction de phase disposent tous deux d'un
mode supplémentaire permettant de contrôler la fréquence de sortie. Dans ce
mode, le temporisateur compte de 0 à OCRA (valeur du registre de comparaison de
sortie A), plutôt que de 0 à 255. Cela offre un contrôle bien plus précis de la
fréquence de sortie que les modes précédents. (Pour un contrôle encore plus
précis de la fréquence, utilisez le temporisateur 16 bits.)
Notez que dans ce mode, seule la sortie B peut être utilisée pour la modulation de
largeur d'impulsion (PWM) ; l'OCRA ne peut pas être utilisée à la fois comme valeur
maximale et comme valeur de comparaison PWM. Cependant, il existe un mode
spécial « Activer l'OCnA lors de la comparaison » qui active la sortie A à la fin de
chaque cycle, générant un rapport cyclique fixe de 50 % et une fréquence de 50 %.
Les exemples utiliseront ce mode.
L'extrait de code suivant configure une modulation de largeur d'impulsion rapide sur
les broches 3 et 11 (Timer 2), en utilisant OCR2A comme valeur maximale. Les bits
du mode de génération de forme d'onde WGM sont définis à 111 pour une
modulation de largeur d'impulsion rapide, OCRA contrôlant la limite maximale. La
limite maximale d'OCR2A est fixée arbitrairement à 180, et le registre de
comparaison d'OCR2B est fixé arbitrairement à 50. Le mode d'OCR2A est défini
sur « Activer la comparaison » en définissant les bits COM2A à 01.
1pinMode(3, OUTPUT);
2 pinMode(11, OUTPUT);
5 OCR2A = 180;
6 OCR2B = 50;
De même, la minuterie peut être configurée en mode PWM à phase correcte pour
se réinitialiser lorsqu'elle atteint OCRnA.
PWM à phase correcte avec OCRA top. Le fragment de code suivant configure un
PWM à phase correcte sur les broches 3 et 11 (Timer 2), en utilisant OCR2A
comme valeur maximale pour le timer. Les bits du mode de génération de forme
d'onde WGM sont définis sur 101 pour un PWM à phase correcte avec OCRA
contrôlant la limite supérieure. La limite supérieure de l'OCR2A est fixée
arbitrairement à 180, et le registre de comparaison de l'OCR2B est fixé
arbitrairement à 50. Le mode de l'OCR2A est défini sur « Basculer sur la
correspondance de comparaison » en définissant les bits COM2A sur 01.
1pinMode(3, OUTPUT);
2 pinMode(11, OUTPUT);
5 OCR2A = 180;
6 OCR2B = 50;
Notez que dans cet exemple, le temporisateur passe de 0 à 180, puis revient à 0,
ce qui prend 360 cycles d'horloge. Ainsi, tout est divisé par 180 ou 360,
contrairement au cas PWM rapide, qui divise tout par 181 ; voir ci-dessous pour
plus de détails.
Un par un
Vous avez peut-être remarqué que les PWM rapides et les PWM à correction de
phase semblent décalés d'une unité l'un par rapport à l'autre, divisant par 256 au
lieu de 255 et ajoutant 1 à divers endroits. La documentation étant un peu opaque,
je vais vous expliquer cela plus en détail.
Supposons que le temporisateur soit réglé en mode PWM rapide et qu'il soit
configuré pour compter jusqu'à une valeur OCRnA de 3. Le temporisateur prendra
les valeurs 012301230123... Notez que chaque cycle de temporisation comporte 4
cycles d'horloge. Ainsi, la fréquence sera divisée par 4, et non par 3. Le rapport
cyclique sera un multiple de 25 %, car la sortie peut être à l'état haut pendant 0, 1,
2, 3 ou 4 cycles sur les quatre. De même, si le temporisateur compte jusqu'à 255,
chaque cycle de temporisation comportera 256 cycles d'horloge, et le rapport
cyclique sera un multiple de 1/256. En résumé, le PWM rapide divise par N+1, où N
est la valeur maximale du temporisateur (OCRnA ou 255).
OC0A 6 12 PD6
OC0B 5 11 PD5
OC1A 9 15 PB1
Sortie de minuterie Sortie Arduino Code PIN à puce Nom de la broche
OC1B 10 16 PB2
OC2A 11 17 PB3
OC2B 3 5 PD3
L'Arduino utilise le Timer 0 en interne pour les fonctions millis() et delay(). Attention,
modifier la fréquence de ce timer peut entraîner des erreurs dans ces fonctions.
L'utilisation des sorties PWM est toutefois sans danger si vous ne modifiez pas la
fréquence.
Le
analogWrite(pin, duty_cycle)
La fonction définit la broche appropriée sur PWM et définit le registre de comparaison de
sortie approprié sur duty_cycle (avec le cas particulier pour un cycle de service de 0 sur le
temporisateur 0).
digitalWrite()
La fonction désactive la sortie PWM si elle est appelée sur une broche de temporisation. Les
codes correspondants sont wiring_analog.c et wiring_digital.c.
Si vous utilisez
analogWrite(5, 0)
Vous obtenez un rapport cyclique de 0 %, même si le temporisateur de la broche 5
(Temporisateur 0) utilise une modulation de largeur d'impulsion rapide (PWM). Comment est-
ce possible, alors qu'une valeur de PWM rapide de 0 produit un rapport cyclique de 1/256,
comme expliqué ci-dessus ? La réponse est la suivante :
analogWrite
« triche » ; il a un code de cas spécial pour désactiver explicitement la broche lorsqu'il est
appelé sur le Timer 0 avec un cycle de service de 0. En conséquence, le cycle de service de
1/256 n'est pas disponible lorsque vous utilisez `analogWrite`` sur Timer0, et il y a un saut
dans le cycle de service réel entre les valeurs de 0 et 1.
D'autres modèles d'Arduino utilisent des processeurs AVR différents avec des
timers similaires. L'Arduino Mega utilise l'ATmega1280 (fiche technique), qui
possède quatre timers 16 bits avec 3 sorties chacun et deux timers 8 bits avec 2
sorties chacun. Cependant, seules 14 sorties PWM sont prises en charge par la
bibliothèque de câblage Arduino. Certains modèles Arduino plus anciens utilisent
l'ATmega8 (fiche technique), qui possède trois timers mais seulement 3 sorties
PWM : le timer 0 n'a pas de PWM, le timer 1 est 16 bits avec deux sorties PWM, et
le timer 2 est 8 bits avec une sortie PWM.
Dépannage
Faire fonctionner les sorties PWM peut s'avérer complexe. Quelques conseils :
Pour obtenir une sortie, vous devez activer la broche de sortie et activer le mode
PWM. Autrement dit, vous devez exécuter pinMode() et définir les bits COM.
Les différents temporisateurs utilisent les bits de contrôle et le préscaler
différemment ; consultez la documentation pour le temporisateur approprié.
Certaines combinaisons de bits que vous pourriez espérer fonctionner sont
réservées, ce qui signifie que si vous essayez de les utiliser, elles ne fonctionneront
pas.
Par exemple, le mode bascule ne fonctionne pas avec un PWM rapide à 255 ou avec
la sortie B.
Assurez-vous que les bits sont configurés comme prévu. Les opérations sur les bits
peuvent être délicates ; imprimez donc les valeurs des registres avec le formateur
binaire (BIN) et assurez-vous qu'elles correspondent à vos attentes.
Assurez-vous d'utiliser les bonnes broches de sortie. Voir le tableau ci-dessus.
Vous aurez probablement besoin d’un condensateur de découplage pour éviter les
pics sur la sortie.
Un oscilloscope est très pratique pour déboguer le PWM si vous en avez un. Si
vous n'en avez pas, je vous recommande d'utiliser votre carte son et un programme
comme xoscope.
Conclusion
Cet article explique les modes PWM de l'Arduino. J'ai trouvé la documentation des
différents modes assez opaque et les problèmes de décalage d'un élément
inexpliqués.
Lire le document original