0% ont trouvé ce document utile (0 vote)
10 vues30 pages

Multitâche Arduino : Interruptions et Minuteries

Ce document traite de l'utilisation des interruptions sur Arduino pour gérer plusieurs tâches simultanément, en s'appuyant sur les techniques de la première partie de la série. Il explique comment configurer des interruptions de minuteur et externes pour améliorer la réactivité du code tout en simplifiant la gestion des tâches. Des exemples de code illustrent l'utilisation de ces interruptions pour contrôler des LED et des servomoteurs.

Transféré par

Zoubid Zekri
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
10 vues30 pages

Multitâche Arduino : Interruptions et Minuteries

Ce document traite de l'utilisation des interruptions sur Arduino pour gérer plusieurs tâches simultanément, en s'appuyant sur les techniques de la première partie de la série. Il explique comment configurer des interruptions de minuteur et externes pour améliorer la réactivité du code tout en simplifiant la gestion des tâches. Des exemples de code illustrent l'utilisation de ces interruptions pour contrôler des LED et des servomoteurs.

Transféré par

Zoubid Zekri
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

Multitâche sur l'Arduino - Partie 2

par Bill Earl

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.

Ne nous appelez pas, nous vous


appellerons
Dans la première partie de cette série, nous avons appris à
utiliser millis() pour chronométrer. Mais pour que cela
fonctionne, nous devions appeler millis() à chaque boucle pour
savoir s'il était temps d'agir. C'est un peu inutile d'appeler
millis() plus d'une fois par milliseconde pour finalement
constater que l'heure n'a pas changé. Ne serait-il pas préférable
de n'avoir à vérifier qu'une fois par milliseconde ?
Les minuteurs et les interruptions de minuteur nous permettent
de faire exactement cela. Nous pouvons configurer un minuteur
pour qu'il nous interrompe une fois par milliseconde. Le
minuteur nous appellera pour nous signaler qu'il est temps de
vérifier l'heure !
Minuteries Arduino
L'Arduino Uno possède trois timers : Timer0, Timer1 et Timer2.
Le Timer0 est déjà configuré pour générer une interruption de
millisecondes afin de mettre à jour le compteur de
millisecondes signalé par millis(). Puisque c'est ce que nous
recherchons, nous allons demander au Timer0 de générer une
interruption pour nous aussi !
Fréquence et comptages
Les temporisateurs sont des compteurs simples qui comptent à
une fréquence déterminée, dérivée de l'horloge système à 16
MHz. Vous pouvez configurer le diviseur d'horloge pour modifier
la fréquence et les différents modes de comptage. Vous pouvez
également les configurer pour générer des interruptions lorsque
le temporisateur atteint un certain nombre.
Le Timer0 est un 8 bits qui compte de 0 à 255 et génère une
interruption en cas de dépassement. Il utilise un diviseur
d'horloge de 64 par défaut, ce qui donne une fréquence
d'interruption de 976,5625 Hz (suffisamment proche de 1 kHz
pour notre cas). Nous ne modifierons pas la fréquence du
Timer0, car cela perturberait millis() !
Registres de comparaison
Les timers Arduino disposent de plusieurs registres de
configuration. Ceux-ci peuvent être lus ou écrits à l'aide de
symboles spéciaux définis dans l'IDE Arduino. Pour une
description complète de tous ces registres et de leurs fonctions,
consultez les liens dans la section « Pour en savoir plus » ci-
dessous.
Nous allons configurer un registre de comparaison pour le
temporisateur 0 (appelé OCR0A) afin de générer une autre
interruption au milieu de ce décompte. À chaque tic, le
compteur du temporisateur est comparé au registre de
comparaison et, lorsqu'ils sont égaux, une interruption est
générée.
Le code ci-dessous générera une interruption « TIMER0_COMPA
» chaque fois que la valeur du compteur dépasse 0xAF.
Télécharger le fichier

Copier le code

// Timer0 est déjà utilisé pour millis() - nous allons simplement


interrompre quelque part
// au milieu et appeler la fonction "Comparer A" ci-dessous
OCR0A = 0xAF ;
TIMSK0 |= _BV(OCIE0A);

Nous définirons ensuite un gestionnaire d'interruption pour le


vecteur d'interruption du temporisateur, appelé
« TIMER0_COMPA_vect ». Ce gestionnaire d'interruption
exécutera toutes les opérations effectuées dans la boucle.
Télécharger le fichier

Copier le code

// L'interruption est appelée une fois par milliseconde,


SIGNAL (TIMER0_COMPA_vect)
{
unsigned long currentMillis = millis ();
sweeper1. Mise à jour (currentMillis);

//si(digitalRead(2) == HAUT)
{
sweeper2. Mise à jour (currentMillis);
led1. Mise à jour (currentMillis);
}

led2. Mise à jour (currentMillis);


led3. Mise à jour (currentMillis);
}

Ce qui nous laisse avec une boucle complètement vide.


Télécharger le fichier

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

// Ceux-ci maintiennent l'état actuel


int ledState; // ledState utilisé pour
définir la LED
unsigned long previousMillis; // stockera la dernière
fois que la LED a été mise à jour

// Constructeur - crée un Flasher


// et initialise les variables membres et l'état
public :
Flasher ( int pin, long on, long off)
{
ledPin = broche ;
pinMode (ledPin, SORTIE);

OnTime = activé ;
OffTime = désactivé ;

ledState = BAS;
précédentMillis = 0 ;
}

void Update ( unsigned long currentMillis)


{
si ((ledState == HIGH) && (currentMillis - previousMillis >=
OnTime))
{
ledState = LOW; // Éteignez-
le previousMillis = currentMillis; // Mémoriser l'heure
digitalWrite (ledPin, ledState); // Mettre à jour la LED
réelle
}
sinon si ((ledState == LOW) && (currentMillis - previousMillis
>= OffTime))
{
ledState = HIGH; // l'allumer
previousMillis = currentMillis; // Mémoriser l'heure
digitalWrite (ledPin, ledState); // Mettre à jour la LED
réelle
}
}
};

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 Attach ( int pin)


{
servo. attacher (broche);
}

void Détacher ()
{
servo.détacher () ;
}

void Update ( unsigned long currentMillis)


{
if ((currentMillis - lastUpdate) > updateInterval) // heure de
mise à jour
{
dernière mise à jour = millis ();
pos += incrément;
servo. write (pos);
if ((pos >= 180 ) || (pos <= 0 )) // fin du balayage
{
// sens inverse
incrément = -incrément ;
}
}
}
};

Clignotant led1 ( 11 , 123 , 400 ) ;


Clignotant led2 ( 12 , 350 , 350 ) ;
Clignotant led3 ( 13 , 200 , 222 ) ;

Balayeuse balayeuse1 ( 25 ) ;
Balayeuse balayeuse2 ( 35 ) ;

configuration vide ()
{
balayeuse1. Attacher ( 9 );
balayeuse2. Attacher ( 10 );

// Timer0 est déjà utilisé pour millis() - nous allons simplement


interrompre quelque part
// au milieu et appeler la fonction "Comparer A" ci-dessous
OCR0A = 0xAF ;
TIMSK0 |= _BV(OCIE0A);
}

// L'interruption est appelée une fois par milliseconde, pour mettre


à jour les LED
// Sweeper2 n'est pas mis à jour si le bouton du numérique 2 est
enfoncé.
SIGNAL (TIMER0_COMPA_vect)
{
unsigned long currentMillis = millis ();
sweeper1. Mise à jour (currentMillis);

si ( digitalRead ( 2 ) == HAUT)
{
sweeper2. Mise à jour (currentMillis);
led1. Mise à jour (currentMillis);
}

led2. Mise à jour (currentMillis);


led3. 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);
}

Ensuite, nous ajouterons un appel à AttachInterrupt() pour


connecter l'interruption externe à notre code de gestionnaire.
Sur l'UNO, l'interruption 0 est associée à la broche numérique 2.
Nous lui demandons de rechercher le front descendant du
signal sur cette broche. Lorsque le bouton est enfoncé, le signal
passe de HAUT à BAS et le gestionnaire d'interruption
« Réinitialisation » est appelé.
Télécharger le fichier

Copier le code

pinMode( 2 , INPUT_PULLUP);
attachInterrupt( 0 , Réinitialiser, CHUTE);

Voici le gestionnaire d'interruption « Reset ». Il appelle


simplement les fonctions de réinitialisation du balayeur :
Télécharger le fichier

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

// Ceux-ci maintiennent l'état actuel


volatile int ledState; // ledState
utilisé pour définir la LED
volatile unsigned long previousMillis; // stockera
la dernière fois que la LED a été mise à jour

// Constructeur - crée un Flasher


// et initialise les variables membres et l'état
public :
Flasher ( int pin, long on, long off)
{
ledPin = broche ;
pinMode (ledPin, SORTIE);

OnTime = activé ;
OffTime = désactivé ;

ledState = BAS;
précédentMillis = 0 ;
}

void Update ( unsigned long currentMillis)


{
si ((ledState == HIGH) && (currentMillis - previousMillis >=
OnTime))
{
ledState = LOW; // Éteignez-
le previousMillis = currentMillis; // Mémoriser l'heure
digitalWrite (ledPin, ledState); // Mettre à jour la LED
réelle
}
sinon si ((ledState == LOW) && (currentMillis - previousMillis
>= OffTime))
{
ledState = HIGH; // l'allumer
previousMillis = currentMillis; // Mémoriser l'heure
digitalWrite (ledPin, ledState); // Mettre à jour la LED
réelle
}
}
};

classe Sweeper
{
Servo servo; // le servo
int updateInterval; // intervalle entre les mises à jour

volatile int pos; // position actuelle du servo


volatile unsigned long lastUpdate; // dernière mise à jour de la
position
volatile int increment; // incrément à déplacer pour
chaque intervalle

public :
Sweeper ( intervalle int )
{
updateInterval = intervalle;
incrément = 1 ;
}

void Attach ( int pin)


{
servo. attacher (broche);
}

void Détacher ()
{
servo.détacher () ;
}

void reset ()
{
pos = 0 ;
servo. écriture (pos);
incrément = abs (incrément);
}

void Update ( unsigned long currentMillis)


{
if ((currentMillis - lastUpdate) > updateInterval) // heure de
mise à jour
{
dernière mise à jour = millisecondes actuelles ;
pos += incrément;
servo. write (pos);
if ((pos >= 180 ) || (pos <= 0 )) // fin du balayage
{
// sens inverse
incrément = -incrément ;
}
}
}
};

Clignotant led1 ( 11 , 123 , 400 ) ;


Clignotant led2 ( 12 , 350 , 350 ) ;
Clignotant led3 ( 13 , 200 , 222 ) ;

Balayeuse balayeuse1 ( 25 ) ;
Balayeuse balayeuse2 ( 35 ) ;

configuration vide ()
{
balayeuse1. Attacher ( 9 );
balayeuse2. Attacher ( 10 );

// Timer0 est déjà utilisé pour millis() - nous allons simplement


interrompre quelque part
// au milieu et appeler la fonction "Comparer A" ci-dessous
OCR0A = 0xAF ;
TIMSK0 |= _BV(OCIE0A);

pinMode ( 2 , INPUT_PULLUP);
attachInterrupt ( 0 , Réinitialiser, CHUTE);
}

void Réinitialiser ()
{
balayeuse1. réinitialiser ();
sweeper2. réinitialiser ();
}

// L'interruption est appelée une fois par milliseconde,


SIGNAL (TIMER0_COMPA_vect)
{
unsigned long currentMillis = millis ();
sweeper1. Mise à jour (currentMillis);

//si(digitalRead(2) == HAUT)
{
sweeper2. Mise à jour (currentMillis);
led1. Mise à jour (currentMillis);
}

led2. Mise à jour (currentMillis);


led3. 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]

Les secrets de l'Arduino PWM


En savoir plus sur les techniques de modulation de largeur d'impulsion

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.

Matériel et logiciels nécessaires


 Carte Arduino avec une puce ATmega168 ou ATmega328.
 Arduino IDE ( en ligne ou hors ligne ).

Modulation de largeur d'impulsion


La modulation de largeur d'impulsion (MLI) est une technique permettant d'obtenir
des résultats analogiques à l'aide de moyens numériques. La commande
numérique permet de créer une onde carrée, un signal commuté entre marche et
arrêt. Ce modèle marche-arrêt permet de simuler des tensions comprises entre la
tension Vcc maximale de la carte (par exemple, 5 V sur UNO, 3,3 V sur une carte
MKR) et la tension d'arrêt (0 V) en modifiant la durée d'activation et la durée
d'inactivité du signal. La durée de cette période est appelée largeur d'impulsion.
Pour obtenir des valeurs analogiques variables, il faut modifier, ou moduler, cette
largeur d'impulsion. Si vous répétez ce modèle marche-arrêt suffisamment
rapidement avec une LED, par exemple, vous obtenez un signal stable entre 0 et
Vcc, contrôlant la luminosité de la LED.
Exemples PWM
Le PWM a plusieurs utilisations :

 Variation de l'intensité d'une LED


 Fournit une sortie analogique ; si la sortie numérique est filtrée, elle fournira une
tension analogique comprise entre 0% et 100%.
 Génération de signaux audio.
 Fournit un contrôle de vitesse variable pour les moteurs.
 Générer un signal modulé, par exemple pour piloter une LED infrarouge pour une
télécommande.

Modulation de largeur d'impulsion simple avec


analogWrite

Le langage de programmation d'Arduino rend le PWM facile à utiliser ; il suffit


d'appeler

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.)

Probablement 99 % des lecteurs peuvent s'arrêter ici et simplement utiliser

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);

9 delayMicroseconds(100); // Approximately 10% duty cycle @ 1KHz

10 digitalWrite(13, LOW);

11 delayMicroseconds(1000 - 100);

12}

Cette technique présente l'avantage de pouvoir utiliser n'importe quelle broche de


sortie numérique. De plus, vous maîtrisez parfaitement le rapport cyclique et la
fréquence. Un inconvénient majeur est que toute interruption affecte la
synchronisation, ce qui peut entraîner une gigue importante si vous ne désactivez
pas les interruptions. Un deuxième inconvénient est que vous ne pouvez pas
laisser la sortie active pendant que le processeur effectue une autre opération.
Enfin, il est difficile de déterminer les constantes appropriées pour un rapport
cyclique et une fréquence spécifiques, à moins de compter soigneusement les
cycles ou d'ajuster les valeurs à l'aide d'un oscilloscope.

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.

La fiche technique de l'AVR ATmega328P fournit une description détaillée des


minuteries PWM, mais la fiche technique peut être difficile à comprendre, en raison
des nombreux modes de contrôle et de sortie différents des minuteries.

Un mot sur la relation entre le langage Arduino et la fiche technique peut être de
mise ici.

Les minuteries Atmega 168/328.

L'ATmega328P possède trois temporisateurs : temporisateur 0, temporisateur 1 et


temporisateur 2. Chaque temporisateur possède deux registres de comparaison de
sortie qui contrôlent la largeur PWM de ses deux sorties : lorsque le temporisateur
atteint la valeur du registre de comparaison, la sortie correspondante bascule. Les
deux sorties de chaque temporisateur ont généralement la même fréquence, mais
peuvent avoir des rapports cycliques différents (selon le registre de comparaison de
sortie concerné).

Chaque temporisateur possède un prédiviseur qui génère l'horloge en divisant


l'horloge système par un facteur de prédiviseur tel que 1, 8, 64, 256 ou 1024.
L'Arduino a une horloge système de 16 MHz et la fréquence d'horloge du
temporisateur correspond à cette fréquence divisée par le facteur de prédiviseur.
Notez que le temporisateur 2 possède des valeurs de prédiviseur différentes de
celles des autres temporisateurs.

Les temporisateurs sont complexes grâce à plusieurs modes différents. Les


principaux modes PWM sont « PWM rapide » et « PWM à phase correcte », décrits
ci-dessous. Le temporisateur peut fonctionner de 0 à 255, ou de 0 à une valeur fixe.
(Le temporisateur 16 bits dispose de modes supplémentaires pour prendre en
charge des valeurs de temporisation jusqu'à 16 bits.) Chaque sortie peut également
être inversée.
Les temporisateurs peuvent également générer des interruptions en cas de
dépassement et/ou correspondre à l'un ou l'autre des registres de comparaison de
sortie, mais cela dépasse le cadre de cet article.

Registres de minuterie

Plusieurs registres permettent de contrôler chaque temporisateur. Les registres de


contrôle du temporisateur/compteur TCCRnA et TCCRnB contiennent les
principaux bits de contrôle du temporisateur. (Notez que TCCRnA et TCCRnB ne
correspondent pas aux sorties A et B.) Ces registres contiennent plusieurs groupes
de bits :

 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 registres de comparaison de sortie OCRnA et OCRnB définissent les niveaux


d'impact sur les sorties A et B. Lorsque la valeur du temporisateur correspond à
celle du registre, la sortie correspondante est modifiée selon le mode spécifié.

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

Dans le mode PWM le plus simple, le temporisateur compte de 0 à 255 de manière


répétée. La sortie s'active lorsque le temporisateur est à 0 et se désactive lorsqu'il
correspond au registre de comparaison de sortie. Plus la valeur du registre de
comparaison de sortie est élevée, plus le rapport cyclique est élevé. Ce mode est
appelé mode PWM rapide. Le schéma suivant illustre les sorties pour deux valeurs
particulières d'OCRnA et d'OCRnB. Notez que les deux sorties ont la même
fréquence, correspondant à la fréquence d'un cycle de temporisation complet.
Mode 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);

3 TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);

4 TCCR2B = _BV(CS22);

5 OCR2A = 180;

6 OCR2B = 50;

Sur l'Arduino Duemilanove, ces valeurs donnent :


 Fréquence de sortie A : 16 MHz / 64 / 256 = 976,5625 Hz
 Rapport cyclique de sortie A : (180+1) / 256 = 70,7 %
 Fréquence de sortie B : 16 MHz / 64 / 256 = 976,5625 Hz
 Cycle de service de sortie B : (50+1) / 256 = 19,9 %

La fréquence de sortie correspond à la fréquence d'horloge système de 16 MHz,


divisée par la valeur du prédiviseur (64), puis par les 256 cycles nécessaires au
retour à la normale du temporisateur. Notez que la modulation de largeur
d'impulsion rapide maintient la sortie à un niveau élevé un cycle de plus que la
valeur du registre de comparaison.

PWM à phase correcte

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.

Exemple de PWM à correction de phase

L'extrait de code suivant configure la modulation de largeur d'onde (PWM) à phase


correcte sur les broches 3 et 11 (Timer 2). Les bits du mode de génération de
forme d'onde WGM sont définis sur 001 pour la PWM à phase correcte. Les autres
bits sont identiques à ceux de la PWM rapide.

1pinMode(3, OUTPUT);
2 pinMode(11, OUTPUT);

3 TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);

4 TCCR2B = _BV(CS22);

5 OCR2A = 180;

6 OCR2B = 50;

Sur l'Arduino Duemilanove, ces valeurs donnent :

 Fréquence de sortie A : 16 MHz / 64 / 255 / 2 = 490,196 Hz


 Rapport cyclique de sortie A : 180 / 255 = 70,6 %
 Fréquence de sortie B : 16 MHz / 64 / 255 / 2 = 490,196 Hz
 Cycle de service de sortie B : 50 / 255 = 19,6 %

La modulation de largeur d'impulsion (PWM) à correction de phase divise la


fréquence par deux par rapport à la PWM rapide, car le temporisateur augmente et
diminue simultanément. Étonnamment, la fréquence est divisée par 255 au lieu de
256, et les calculs du rapport cyclique n'ajoutent pas 1 comme pour la PWM rapide.
Voir l'explication ci-dessous sous « Décalage de 1 ».

Variation de la limite supérieure du temporisateur : PWM rapide

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.

Dans le schéma suivant, le temporisateur se réinitialise lorsqu'il correspond à


OCRnA, ce qui produit une fréquence de sortie plus élevée pour OCnB que dans
les schémas précédents. Notez que OCnA bascule une fois à chaque
réinitialisation du temporisateur.

Mode PWM rapide avec OCRA top

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);

3 TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);

4 TCCR2B = _BV(WGM22) | _BV(CS22);

5 OCR2A = 180;

6 OCR2B = 50;

Sur l'Arduino Duemilanove, ces valeurs donnent :

 Fréquence de sortie A : 16 MHz / 64 / (180+1) / 2 = 690,6 Hz


 Cycle de service de sortie A : 50 %
 Fréquence de sortie B : 16 MHz / 64 / (180+1) = 1 381,2 Hz
 Cycle de service de sortie B : (50+1) / (180+1) = 28,2 %
Notez que dans cet exemple, le minuteur passe de 0 à 180, ce qui prend 181
cycles d'horloge, donc la fréquence de sortie est divisée par 181. La sortie A a la
moitié de la fréquence de la sortie B car le mode Basculer sur Comparer la
correspondance bascule la sortie A une fois par cycle de minuterie complet.

Variation de la limite supérieure du temporisateur : PWM à phase correcte

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);

3 TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);

4 TCCR2B = _BV(WGM22) | _BV(CS22);

5 OCR2A = 180;
6 OCR2B = 50;

Sur l'Arduino Duemilanove, ces valeurs donnent :

 Fréquence de sortie A : 16 MHz / 64 / 180 / 2 / 2 = 347,2 Hz


 Cycle de service de sortie A : 50 %
 Fréquence de sortie B : 16 MHz / 64 / 180 / 2 = 694,4 Hz
 Cycle de service de sortie B : 50 / 180 = 27,8 %

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).

Considérons maintenant le mode PWM à phase correcte avec un temporisateur


comptant jusqu'à une valeur OCRnA de 3. Les valeurs du temporisateur seront
012321012321... Chaque cycle de temporisation (012321) comporte 6 cycles
d'horloge. La fréquence sera donc divisée par 6. Le rapport cyclique sera un
multiple de 33 %, car la sortie peut être à l'état haut pendant 0, 2, 4 ou 6 de ces 6
cycles. De même, si le temporisateur compte jusqu'à 255 puis redescend, chaque
cycle de temporisation comptera 510 cycles d'horloge, et le rapport cyclique sera
un multiple de 1/255. En résumé, le PWM à phase correcte divise par 2N, où N est
la valeur maximale du temporisateur.

La deuxième différence temporelle importante réside dans le fait que le PWM


rapide maintient la sortie à un niveau élevé pendant un cycle de plus que la valeur
du registre de comparaison de sortie. En effet, pour un comptage PWM rapide
jusqu'à 255, le rapport cyclique peut être compris entre 0 et 256 cycles, tandis que
le registre de comparaison de sortie ne peut contenir qu'une valeur comprise entre
0 et 255. Qu'advient-il de la valeur manquante ? Le mode PWM rapide maintient la
sortie à un niveau élevé pendant N+1 cycles lorsque le registre de comparaison de
sortie est défini sur N. Ainsi, une valeur de 255 dans le registre de comparaison de
sortie correspond à un rapport cyclique de 100 %, tandis qu'une valeur de 0 dans le
registre de comparaison de sortie correspond non pas à un rapport cyclique de
0 %, mais à un rapport cyclique de 1/256. Ceci est différent du PWM à correction
de phase, où une valeur de registre de 255 correspond à un rapport cyclique de
100 % et une valeur de 0 à un rapport cyclique de 0 %.

Les minuteries et l'Arduino

L'Arduino prend en charge la modulation de largeur d'impulsion (PWM) sur un


sous-ensemble de ses broches de sortie. Il n'est pas toujours évident de savoir quel
timer contrôle quelle sortie, mais le tableau suivant clarifiera la situation. Il indique,
pour chaque sortie de timer, la broche de sortie de l'Arduino (c'est-à-dire l'étiquette
sérigraphiée sur la carte), la broche de la puce ATmega, ainsi que le nom et le bit
du port de sortie. Par exemple, la sortie OC0A du timer 0 est connectée à la broche
de sortie 6 de l'Arduino ; elle utilise la broche 12 de la puce, également appelée
PD6.

Sortie de minuterie Sortie Arduino Code PIN à puce Nom de la broche

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 initialise les temporisateurs. Il initialise le prédiviseur des trois


temporisateurs pour diviser l'horloge par 64. Le temporisateur 0 est initialisé en
PWM rapide, tandis que les temporisateurs 1 et 2 sont initialisés en PWM à
correction de phase. Consultez le fichier source Arduino wiring.c pour plus de
détails.

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

Étiquette de minuterie et d'interruption


Les interruptions sont comme la file d'attente au supermarché. Soyez prévenant et limitez-
vous à 10 articles maximum, et tout se passera bien.
Si tout est prioritaire, alors rien n’est prioritaire.
Les gestionnaires d'interruptions doivent être utilisés uniquement pour traiter les événements
prioritaires et urgents. N'oubliez pas que les interruptions sont désactivées lorsque vous êtes
dans le gestionnaire d'interruptions. Si vous tentez d'en faire trop au niveau de l'interruption,
vous dégraderez la réponse aux autres interruptions.
Une interruption à la fois.
En mode ISR, les interruptions sont désactivées. Cela a deux conséquences importantes :
1. Le travail effectué dans l'ISR doit être bref afin de ne manquer aucune interruption.
2. Le code de l'ISR ne doit pas appeler d'instructions nécessitant l'activation d'interruptions (par
exemple, delay() ou tout autre appel utilisant le bus I2C). Cela entraînerait le blocage du
programme.

Différez le traitement long à la boucle.


Si vous devez effectuer un traitement important en réponse à une interruption, utilisez le
gestionnaire d'interruptions pour effectuer uniquement l'essentiel, puis définissez une variable
d'état volatile (voir ci-dessous) pour indiquer qu'un traitement supplémentaire est requis.
Lorsque vous appelez votre fonction de mise à jour depuis la boucle, vérifiez la variable d'état
pour voir si un traitement supplémentaire est requis.
Vérifiez avant de reconfigurer une minuterie
Les minuteurs sont une ressource limitée. Il n'y en a que trois sur un UNO, et ils servent à de
nombreuses choses. Si vous modifiez la configuration d'un minuteur, d'autres fonctions
risquent de ne plus fonctionner. Par exemple, sur un Arduino UNO :
 Timer0 - utilisé pour millis(), micros(), delay() et PWM sur les broches 5 et 6
 Timer1 - utilisé pour les servos, la bibliothèque WaveHC et PWM sur les broches 9 et 10
 Timer2 - utilisé par Tone et PWM sur les broches 3 et 11

Partagez vos données en toute sécurité


Étant donné qu’une interruption suspend tout ce que le processeur fait pour traiter
l’interruption, nous devons être prudents quant au partage de données entre les gestionnaires
d’interruption et le code de notre boucle.
Variables volatiles
Parfois, le compilateur tente d'optimiser votre code pour plus de rapidité. Parfois, ces
optimisations conservent une copie des variables fréquemment utilisées dans un registre pour
un accès rapide.
Le problème est que si l'une de ces variables est partagée entre le gestionnaire d'interruption et
le code de la boucle, l'un d'eux risque de consulter une copie obsolète au lieu de la version
réelle. Marquer la variable comme telle volatileindique au compilateur de ne pas effectuer
ces optimisations potentiellement dangereuses.
Protection des variables plus grandes
Même le marquage d'une variable volatilene suffit pas si elle est supérieure à un entier (par
exemple, une chaîne, un tableau, une structure, etc.). La mise à jour de variables plus grandes
nécessite plusieurs cycles d'instructions, et une interruption au milieu de cette mise à jour peut
corrompre les données. Si vous avez des variables ou des structures plus grandes partagées
avec des gestionnaires d'interruptions, il est conseillé de désactiver les interruptions lors de
leur mise à jour depuis la boucle. (Les interruptions sont déjà désactivées par défaut dans le
gestionnaire d'interruptions.)
Dernière modification de la page : 27 mars 2024

Vous aimerez peut-être aussi