Bonjour, Habr! Je vous présente la traduction de l'article "Timer interrupts" de E.
Préface
La carte Arduino vous permet de résoudre rapidement et de manière minimale une variété de problèmes. Mais là où des intervalles de temps arbitraires sont nécessaires (interrogation périodique des capteurs, signaux PWM de haute précision, impulsions de longue durée), les fonctions de délai de bibliothèque standard ne sont pas pratiques. Pendant la durée de leur action, l'esquisse est suspendue et il devient impossible de la gérer.
Dans une situation similaire, il est préférable d'utiliser les minuteries AVR intégrées. Un article réussi explique comment faire cela et ne pas se perdre dans la jungle technique des fiches techniques, dont une traduction est portée à votre attention.

Cet article décrit les temporisateurs AVR et Arduino et comment les utiliser dans des projets Arduino et des circuits utilisateur.
Qu'est-ce qu'une minuterie?
Comme dans la vie quotidienne des microcontrôleurs, une minuterie est une chose qui peut signaler à l'avenir, au moment où vous la définissez. Lorsque ce moment arrive, le microcontrôleur est interrompu, lui rappelant de faire quelque chose, par exemple, pour exécuter un certain morceau de code.
Les minuteries, comme les interruptions externes, fonctionnent indépendamment du programme principal. Au lieu de boucler ou de répéter l'appel de retard millis () , vous pouvez assigner une minuterie pour faire son travail pendant que votre code fait d'autres choses.
Supposons donc qu'un appareil doive faire quelque chose, par exemple, faire clignoter une LED toutes les 5 secondes. Si vous n'utilisez pas de minuteries, mais écrivez du code normal, vous devez définir une variable au moment où la LED est allumée et vérifier constamment si le moment de sa commutation est arrivé. Avec une interruption de minuterie, il vous suffit de configurer l'interruption, puis de démarrer la minuterie. La LED clignotera exactement à l'heure, quelles que soient les actions du programme principal.
Comment fonctionne la minuterie?
Il agit en incrémentant une variable appelée registre de comptage . Le registre de comptage peut compter jusqu'à une certaine valeur, en fonction de sa taille. La minuterie incrémente son compteur encore et encore jusqu'à ce qu'il atteigne sa valeur maximale, à ce stade, le compteur débordera et se remettra à zéro. Une minuterie définit généralement un bit indicateur pour vous informer qu'un dépassement s'est produit.
Vous pouvez vérifier cet indicateur manuellement ou effectuer un changement de minuterie - provoquer une interruption automatiquement lorsque l'indicateur est défini. Comme toute autre interruption, vous pouvez affecter une routine de service d'interruption ( ISR ) pour exécuter le code spécifié lorsque le temporisateur déborde. L'ISR lui-même effacera l'indicateur de débordement, donc l'utilisation d'interruptions est généralement le meilleur choix en raison de sa simplicité et de sa vitesse.
Pour augmenter les valeurs du compteur à des intervalles de temps exacts, la minuterie doit être connectée à la source d'horloge. La source d'horloge génère un signal à répétition constante. Chaque fois que le temporisateur détecte ce signal, il augmente la valeur du compteur d'une unité. Étant donné que la minuterie fonctionne sur une source d'horloge, la plus petite unité de temps mesurable est la période de cycle. Si vous connectez un signal d'horloge de 1 MHz, la résolution du minuteur (ou la période du minuteur) sera:
T = 1 / f (f est la fréquence d'horloge)
T = 1/1 MHz = 1/10 ^ 6 Hz
T = (1 ∗ 10 ^ -6) s
Ainsi, la résolution de la minuterie est d'un millionième de seconde. Bien que vous puissiez utiliser une source d'horloge externe pour les minuteries, dans la plupart des cas, la source interne de la puce elle-même est utilisée.
Types de minuterie
Dans les cartes Arduino standard sur une puce AVR 8 bits, il y a plusieurs temporisateurs à la fois. Les puces Atmega168 et Atmega328 ont trois temporisateurs Timer0, Timer1 et Timer2. Ils ont également une horloge de surveillance qui peut être utilisée pour se protéger contre les pannes ou comme mécanisme de réinitialisation logicielle. Voici quelques fonctionnalités de chaque minuterie.
Timer0:
Timer0 est un temporisateur 8 bits, ce qui signifie que son registre de comptage peut stocker des nombres jusqu'à 255 (c'est-à-dire un octet non signé). Timer0 est utilisé par les fonctions temporaires Arduino standard telles que delay () et millis () , il est donc préférable de ne pas le confondre si vous vous souciez des conséquences.
Timer1:
Timer1 est un temporisateur de 16 bits avec une valeur de comptage maximale de 65535 (entier non signé). Ce minuteur utilise la bibliothèque Arduino Servo, gardez cela à l'esprit si vous l'utilisez dans vos projets.
Timer2:
Timer2 est 8 bits et est très similaire à Timer0. Il est utilisé dans la fonction Arduino tone () .
Timer3, Timer4, Timer5:
Les puces ATmega1280 et ATmega2560 (installées dans les variantes Arduino Mega) ont trois temporisateurs supplémentaires. Tous sont en 16 bits et fonctionnent de manière similaire à Timer1.
Enregistrer la configuration
Afin d'utiliser ces minuteries, l'AVR dispose de registres de paramètres. Les temporisateurs contiennent de nombreux registres de ce type. Deux d'entre eux - les registres de contrôle du temporisateur / compteur contiennent des variables de réglage et sont appelés TCCRxA et TCCRxB, où x est le numéro du temporisateur (TCCR1A et TCCR1B, etc.). Chaque registre contient 8 bits et chaque bit stocke une variable de configuration. Voici les détails de la fiche technique Atmega328:
Les plus importants sont les trois derniers bits de TCCR1B: CS12, CS11 et CS10. Ils déterminent la fréquence d'horloge du temporisateur. En les choisissant dans différentes combinaisons, vous pouvez commander la minuterie pour agir à différentes vitesses. Voici un tableau de données techniques décrivant l'effet des bits sélectionnés:
Par défaut, tous ces bits sont mis à zéro.
Supposons que vous vouliez que Timer1 s'exécute à une fréquence d'horloge avec un échantillon par période. Quand il déborde, vous voulez appeler la routine d'interruption, qui fait passer la LED connectée à la jambe 13 à l'état allumé ou éteint. Pour cet exemple, nous écrirons le code Arduino, mais nous utiliserons les procédures et les fonctions de la bibliothèque avr-libc chaque fois que cela ne rend pas les choses trop compliquées. Les partisans de l'AVR pur peuvent adapter le code à leur guise.
Tout d'abord, initialisez le minuteur:
Le registre TIMSK1 est un registre de masque d'interruption temporisateur / compteur1. Il contrôle l'interruption que la minuterie peut provoquer. La définition du bit TOIE1 indique au temporisateur d'interrompre lorsque le temporisateur déborde. Plus d'informations à ce sujet plus tard.
Lorsque vous définissez le bit CS10, la minuterie commence à compter et dès qu'une interruption de dépassement se produit, l'ISR (TIMER1_OVF_vect) est appelé. Cela se produit toujours lorsque la minuterie déborde.
Nous définissons ensuite la fonction d'interruption ISR:
ISR(TIMER1_OVF_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Nous pouvons maintenant définir le cycle loop () et changer la LED indépendamment de ce qui se passe dans le programme principal. Pour désactiver la minuterie, réglez TCCR1B = 0 à tout moment.
À quelle fréquence la LED clignotera-t-elle?
Timer1 est réglé pour déborder l'interruption et supposons que vous utilisez un Atmega328 avec une fréquence d'horloge de 16 MHz. Étant donné que la minuterie est de 16 bits, elle peut compter jusqu'à la valeur maximale (2 ^ 16 - 1), ou 65535. À 16 MHz, le cycle s'exécute 1 / (16 ∗ 10 ^ 6) secondes ou 6,25e-8 s. Cela signifie que 65535 échantillons se produiront en (65535 ∗ 6,25e-8 s) et que l'ISR sera appelé après environ 0,0041 s. Et donc à chaque fois, tous les quatre millièmes de seconde. Il est trop rapide de voir le scintillement.
Si nous appliquons un signal PWM très rapide avec une couverture de 50% à la LED, alors la lueur apparaîtra continue, mais moins lumineuse que d'habitude. Une telle expérience montre la puissance incroyable des microcontrôleurs - même une puce 8 bits peu coûteuse peut traiter les informations beaucoup plus rapidement que nous ne pouvons le détecter.
Diviseur de minuterie et mode CTC
Pour contrôler la période, vous pouvez utiliser un diviseur qui vous permet de diviser le signal d'horloge en différents degrés de deux et d'augmenter la période de la minuterie. Par exemple, vous souhaitez que la LED clignote à des intervalles d'une seconde. Il y a trois bits CS dans le registre TCCR1B définissant la résolution la plus appropriée. Si vous définissez les bits CS10 et CS12 en utilisant:
TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12);
alors la fréquence de la source d'horloge sera divisée par 1024. Cela donne une résolution de temporisation de 1 / (16 ∗ 10 ^ 6/1024) ou 6,4e-5 s. Maintenant, la minuterie débordera tous les (65535 ∗ 6,4e-5s) ou pour 4,194 s. C'est trop long.
Mais il existe un autre mode de minuterie AVR. C'est ce qu'on appelle une réinitialisation du temporisateur coïncident ou CTC. Au lieu de compter pour déborder, le temporisateur compare son compteur avec la variable qui était précédemment stockée dans le registre. Lorsque le nombre correspond à cette variable, le temporisateur peut soit définir un indicateur, soit provoquer une interruption, tout comme dans le cas d'un débordement.
Pour utiliser le mode CTC, vous devez comprendre le nombre de cycles dont vous avez besoin pour obtenir un intervalle d'une seconde. Supposons que le rapport de division soit toujours de 1024.
Le calcul sera le suivant:
(target time) = (timer resolution) * (# timer counts + 1) (# timer counts + 1) = (target time) / (timer resolution) (# timer counts + 1) = (1 s) / (6.4e-5 s) (# timer counts + 1) = 15625 (# timer counts) = 15625 - 1 = 15624
Vous devez ajouter une unité supplémentaire au nombre d'échantillons car en mode CTC, lorsque le compteur correspond à la valeur définie, il se remet à zéro. La réinitialisation prend une période d'horloge, qui doit être prise en compte dans les calculs. Dans de nombreux cas, une erreur sur une période n'est pas très importante, mais dans les tâches de haute précision, elle peut être critique.
La fonction setup () sera comme ceci:
void setup() { pinMode(LEDPIN, OUTPUT);
Vous devez également remplacer l'interruption de débordement par une interruption coïncidente:
ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Maintenant, la LED s'allume et s'éteint pendant exactement une seconde. Et vous pouvez tout faire dans une boucle loop (). Tant que vous ne modifiez pas les paramètres de la minuterie, le programme n'a rien à voir avec les interruptions. Vous n'avez aucune restriction sur l'utilisation d'une minuterie avec différents modes et paramètres du diviseur.
Voici un exemple de départ complet que vous pouvez utiliser comme base pour vos propres projets:
N'oubliez pas que vous pouvez utiliser les fonctions ISR intégrées pour étendre les fonctions du minuteur. Par exemple, vous devez interroger le capteur toutes les 10 secondes. Mais il n'y a pas de paramètres de minuterie fournissant un compte aussi long sans débordement. Cependant, vous pouvez utiliser ISR pour incrémenter la variable de comptage une fois par seconde, puis interroger le capteur lorsque la variable atteint 10. En utilisant le mode STS de l'exemple précédent, l'interruption pourrait ressembler à ceci:
ISR(TIMER1_COMPA_vect) { seconds++; if(seconds == 10) { seconds = 0; readSensor(); } }
Comme la variable sera modifiée à l'intérieur de l'ISR, elle doit être déclarée volatile . Par conséquent, lors de la description des variables au début du programme, vous devez écrire:
volatile byte seconds;
Postface du traducteur
À un moment donné, cet article m'a fait gagner beaucoup de temps lors du développement d'un prototype de générateur de mesure. J'espère que cela sera utile à d'autres lecteurs.