LOCODUINO

Forum de discussion
Dépôt GIT Locoduino
Flux RSS

mardi 28 mars 2017

4 visiteurs en ce moment

Les Timers (I)

Les bases

. Par : Christian Bézanger

Un timer est un registre à l’intérieur du microcontrôleur qui s’incrémente (ou se décrémente) chaque fois qu’il reçoit une impulsion d’un signal d’horloge. Ce signal d’horloge peut être propre au microcontrôleur ou bien extérieur à celui-ci. Un timer est donc un compteur capable de compter le temps qui s’écoule, d’où son nom anglais de timer counter. Dans ce qui suit, le timer est toujours employé dans un mode où il s’incrémente, mais dans d’autres modes, il peut se décrémenter. On peut télécharger la documentation du constructeur sur le lien suivant :
http://www.atmel.com/images/doc8161.pdf

Si le registre du timer comporte 8 bits, il est alors capable de compter de 0 à 255 (en hexadécimal, de 00 à FF). Lorsqu’il arrive à 255 (FF), un coup d’horloge supplémentaire devrait le faire passer à 256 (soit 100 en hexadécimal), ce qui n’est pas possible puisque le registre n’a que 8 bits. Le registre passe donc à 0 ; on dit qu’il subit un débordement (Overflow en anglais) [1], mais ce débordement entraîne la mise à 1 d’un bit bien particulier dans un registre de contrôle associé au timer. Ce bit est appelé un flag (drapeau en anglais) et indique que le timer vient de compter jusqu’à 256, ce qui permet d’attirer l’attention du programmeur, un peu comme ces boîtes aux lettres américaines qui possèdent un petit drapeau qui se dresse chaque fois que le facteur a déposé du courrier à l’intérieur.

L’intérêt d’un timer est qu’il compte sans cesse et que pendant ce temps, le programme peut réaliser autre chose, ce qui n’est pas possible si on utilise la fonction delay() qui est bloquante et qui ne permet pas de faire autre chose pendant ce temps d’attente. Le temps que le timer met pour compter 256 coups dépend bien sûr de la fréquence de l’horloge ; à 16 MHz (fréquence du microcontrôleur utilisé dans les modules Arduino), c’est très rapide, mais il est possible de diviser cette fréquence d’horloge grâce à des circuits internes au microcontrôleur appelés prédiviseur (prescaler en anglais). On peut alors diviser la fréquence de base (16 MHz) par 8, 32, 64, 128, 256 ou 1024 ; pour cela, il faut utiliser intelligemment d’autres registres de contrôle associés au timer. Par exemple, si on règle de prédiviseur pour diviser la fréquence par 1024, le timer comptera donc à une fréquence de 15625 Hz.

Comme pour tout registre, on peut lire la valeur d’un timer ou bien écrire une valeur particulière dans le timer. Mais ce qui est surtout important, ce sont les registres de contrôle associés au timer car ce sont eux qui permettent de modifier le comportement du timer et de contrôler ce qu’il fait. Il faut donc bien les connaître pour bien savoir les utiliser et la lecture de la documentation liée au microcontrôleur est souvent indispensable.

Timers du microcontrôleur de l’Arduino

Le module Arduino Uno est construit autour du microcontrôleur AVR ATmega328P d’Atmel qui possède 3 timers :

  • Le timer0, sur 8 bits, utilisé par les fonctions delay(), millis() et micros(). Il commande également des PWM   (Pulse Width Modulation ou Modulation par Largeur d’Impulsion) sur les broches 5 et 6.
  • Le timer1, sur 16 bits, qui compte de 0 à 65535 (0 à FFFF en hexadécimal) et qui est utilisé par la bibliothèque Servo ou bien pour de la PWM   sur les broches 9 et 10.
  • Le timer2, sur 8 bits, qui est utilisé par la fonction Tone() ou bien pour de la PWM sur les broches 3 et 11.

Le langage d’Arduino fait donc appel aux timers du microcontrôleur mais ceci reste transparent pour le programmeur. D’ailleurs, dans la majorité des applications, le langage d’Arduino est souvent très suffisant et permet de développer des applications sans avoir besoin de faire appel à l’architecture du microcontrôleur. Néanmoins, dans quelques cas particuliers, il peut être intéressant de savoir programmer les timers ; dans cet article, nous allons évoquer l’utilisation des timers comme compteur de temps, même si ceux-ci sont capables de faire bien d’autres choses.

Les registres de contrôle

Le tableau suivant donne les différents registres de contrôle associés à chaque timer ; nous verrons le rôle de chaque registre tout en nous limitant à ce qui est vraiment à connaître pour réaliser un premier exemple.

Timer 0Timer 1Timer 2Rôle
TCNT0 TCNT1L TCNT2 Timer (bit 0 à 7)
- TCNT1H - Timer (bit 8 à 15)
TCCR0A TCCR1A TCCR2A Registre de contrôle
TCCR0B TCCR1B TCCR2B Registre de contrôle
- TCCR1C - Registre de contrôle
OCR0A OCR1AL OCR2A Output Compare (bit 0 à 7)
- OCR1AH - Output Compare (bit 8 à 15)
OCR0B OCR1BL OCR2B Output Compare (bit 0 à 7)
- OCR1BH - Output Compare (bit 8 à 15)
- ICR1L - Input Capture (bit 0 à 7)
- ICR1H - Input Capture (bit 8 à 15)
TIMSK0 TIMSK1 TIMSK2 Interrupt Mask
TIFR0 TIFR1 TIFR2 Interrupt Flag

ASSR Asynchronous Status Register
GTCCR General Timer/Counter Control Register

Le timer1 est un timer de 16 bits et est donc constitué de deux registres de 8 bits, l’un donnant les bits 0 à 7, l’autre donnant les bits 8 à 15.

Pour se repérer dans ce tableau, il faut savoir que TCNT signifie Timer/Counter (Register), TCCR Timer/Counter Control Register, OCR Output Compare Register, ICR Input Capture Register, TIMSK Timer/Counter Interrupt Mask Register et TIFR Timer/Counter Interrupt Flag Register. OCR et ICR ont des rôles particuliers dont nous ne parlerons pas dans ce premier article. TIMSK et TIFR servent pour que le timer puisse générer des interruptions [2] comme nous le verrons dans un autre article.

Exemple d’utilisation simple

Nous allons maintenant nous intéresser au timer 2 dans son rôle le plus simple, compter le temps, et aux registres de contrôle qui y sont associés. Ce que nous allons dire reste bien entendu valable pour les timers 0 ou 1. Pour mieux comprendre, nous prendrons comme exemple le clignotement d’une DEL   à 1 Hz, ce qui signifie que toutes les 500 ms (demi-période), il faut inverser la DEL  , c’est-à-dire l’allumer si elle est éteinte et l’éteindre si elle est allumée.

Ralentir ou accélérer le timer

Nous avons vu qu’il était facile de ralentir un timer : il suffit d’utiliser le prédiviseur pour diviser la fréquence du signal d’horloge. Pour faire cela, il suffit de positionner certains bits du registre TCCR2B. À partir de là, notre compteur comptera de 0 à 255 (cela lui prendra un certain temps) puis passera en débordement, c’est-à-dire qu’il repartira de 0 après avoir positionné le flag TOV2 (Timer/Counter 2 Overflow Flag) à 1. Il s’agit là du bit 0 du registre TIFR2. Chaque fois que le bit 0 du registre TIFR2 passe à 1, cela signifie que notre timer a compté un certain laps de temps (connu) ; il suffit alors de repositionner le flag TOV2 à 0 et d’incrémenter un compteur (une variable) pour continuer ce processus. Lorsque ce compteur arrive à une certaine valeur, c’est que le temps à attendre s’est écoulé.

Pour accélérer un timer, il faut le faire déborder avant qu’il ait compté 256 coups : pour cela, il suffit de partir d’une valeur différente de 0 pour effectuer le comptage puisqu’il est possible d’écrire dans le registre du timer.

On voit maintenant comment il faut opérer pour faire clignoter notre DEL. Le timer part de 0 et s’incrémente jusqu’à 255, puis recommence de 0 vers 255 en ayant positionné le flag TOV2 à 1. Il suffit de surveiller ce flag ; chaque fois qu’il est à 1, on repositionne le flag à 0 et on incrémente un compteur. Lorsque ce compteur arrive à une certaine valeur, on a atteint 500 ms et il faut agir sur la DEL. Pour que le timer ait ce comportement, il doit être utilisé en mode normal ; dans ce mode, le timer compte en incrémentant, et le flag est positionné à 1 chaque fois que le timer repasse par 0.

Pour que le timer soit en mode normal, il faut que les trois bits appelés WGM20 , WGM21 et WGM22 soient à 0, ce qui nécessite d’aller écrire dans 2 registres (WGM2 pour Waveform Generation Mode du timer 2). En effet, WGM20 et WGM21 sont les bits 0 et 1 du registre TCCR2A, et WGM22 est le bit 3 du registre TCCR2B. Les bits 0 à 2 de ce même registre sont appelés CS20 , CS21 et CS22 et servent à régler le prédiviseur suivant le facteur de division souhaité (CS2 pour Clock Select du timer 2). Tout cela sera plus clair sur un exemple.

Calcul théorique du comptage

Le timer 2 sera utilisé en le faisant compter à la fréquence de 62500 Hz (fréquence d’horloge divisée par 256). Un cycle d’horloge dure donc 16 µs (l’inverse de la fréquence). Pour avoir 500 ms (500000 µS), il faut compter 500 000 µs / 16 µs = 31250 fois. Cette valeur est décomposable en 125 * 250. Le timer doit compter 250 fois pour déborder ; il suffit de le faire partir de la valeur 6. Chaque fois qu’il déborde, une variable compteur est incrémentée ; quand cette variable atteint 125, on a bien 500 ms qui se sont écoulées et on agit sur la DEL.

Initialisation des registres de contrôle

Pour que le timer soit en mode normal, il faut que WGM20 , WGM21 et WGM22 soient à 0. On positionne ces bits à 0 dans deux registres TCCR2A et TCCR2B par une instruction adéquate (bitClear dans le langage Arduino). Pour que le prédiviseur divise par 256, il faut que le bit CS22 soit égal à 1, le bit CS21 soit égal à 1 et le bit CS20 soit égal à 0 ; il suffit d’écrire la valeur binaire 0b00000110 dans le registre TCCR2B. Heureusement pour nous, le compilateur d’Arduino connaît les noms des registres et les noms des bits.

La dernière chose à savoir, c’est que pour réinitialiser le flag TOV2 par logiciel, il faut écrire à 1 le bit 0 du registre TIFR2 ; l’instruction est bitSet (et non bitClear comme on aurait pu le croire).

Programme

Voici le programme, écrit avec le langage Arduino :

  1. /*
  2.  * Clignotement d'une DEL (LED en anglais) à 1 Hz par timer 2.
  3.  */
  4. const byte Led = 13; // Pour utiliser la LED du module
  5. #define LedToggle digitalWrite (Led, !digitalRead(Led))
  6.  
  7. void setup ()
  8. {
  9. pinMode (Led, OUTPUT);
  10. bitClear (TCCR2A, WGM20); // WGM20 = 0
  11. bitClear (TCCR2A, WGM21); // WGM21 = 0
  12. TCCR2B = 0b00000110; // Clock / 256 soit 16 micro-s et WGM22 = 0
  13. TIFR2 = 0b00000001; // TOV2
  14. TCNT2 = 256 - 250; // Chargement du timer à 6
  15. }
  16.  
  17. byte varCompteur = 0; // La variable compteur
  18.  
  19. void loop () {
  20. if (bitRead (TIFR2, 0) == 1) { // Flag TOV2 mis à 1 ?
  21. TCNT2 = 256 - 250; // Rechargement du timer à 6
  22. bitSet (TIFR2, TOV2); // Remise à zéro du flag TOV2 (voir texte)
  23. if (varCompteur++ > 125) { // Incrémentation et a atteint 125 ?
  24. varCompteur = 0; // On recommence un nouveau cycle
  25. LedToggle; // Inversion de la LED
  26. }
  27. }
  28. }

Télécharger

Conclusion

Nous avons vu qu’un timer est un registre compteur qui s’incrémente (ou se décrémente) à chaque impulsion d’une horloge qui peut être celle du microcontrôleur divisée par un certain nombre pour la ralentir. Lorsque le timer déborde, un flag est positionné à 1 dans un registre de contrôle. En surveillant ce flag, on peut compter le temps qui s’écoule jusqu’à obtenir une durée déterminée. On peut lire la valeur du timer et on peut écrire une valeur dans le timer. Plusieurs registres de contrôle sont associés au timer et permettent de modifier son comportement et de suivre son débordement. La connaissance de ces registres de contrôle et du rôle joué par chacun des bits les composant est indispensable au programmeur qui veut utiliser les timers. Pour cela, une lecture assidue de la documentation du constructeur est indispensable.

L’intérêt d’un timer pour compter le temps qui s’écoule est que le programme peut réaliser autre chose pendant ce temps. Dans l’exemple donné, il suffit de surveiller le flag TOV2 pour connaître l’écoulement du temps ; ceci est possible parce que le programme ne fait rien d’autre que de surveiller. Nous verrons dans un prochain article que le débordement du timer peut engendrer une interruption ; le programme peut alors faire autre chose et être interrompu pour traiter l’événement. Nous verrons aussi que les timers peuvent faire d’autres choses grâce aux autres registres de contrôle comme OCR ou ICR. Mais tout ceci est une autre histoire.

[1Voir à ce propos l’article « Types, constantes et variables ».

[2Voir l’article « Les interruptions (1) »

15 Messages

Réagissez à « Les Timers (I) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Programmation »

Comment gérer le temps dans un programme ?

La programmation, qu’est ce que c’est

Types, constantes et variables

Installation de l’IDE arduino

Répéter des instructions : les boucles

Les interruptions (1)

Instructions conditionnelles : le if ... else

Instructions conditionnelles : le switch ... case

Comment gérer l’aléatoire ?

Calculer avec l’Arduino (1)

Calculer avec l’Arduino (2)

Les structures

Systèmes de numération

Les fonctions

Trois façons de déclarer des constantes

Transcription d’un programme simple en programmation objet

Les chaînes de caractères

Trucs, astuces et choses à ne pas faire !

Le monde des objets (1)

Le monde des objets (2)

Le monde des objets (3)

Le monde des objets (4)

Les pointeurs (1)

Les pointeurs (2)

Les derniers articles

Les Timers (V)


Christian Bézanger

Trucs, astuces et choses à ne pas faire !


Dominique

Le monde des objets (4)


Thierry

Les pointeurs (2)


Thierry

Les chaînes de caractères


Thierry

Transcription d’un programme simple en programmation objet


Dominique, Guillaume, Jean-Luc

Les pointeurs (1)


Thierry

Comment gérer l’aléatoire ?


Dominique, Guillaume, Jean-Luc

Les Timers (IV)


Christian Bézanger

Les fonctions


Jean-Luc

Les articles les plus lus

Les interruptions (1)

Les Timers (I)

Calculer avec l’Arduino (1)

Répéter des instructions : les boucles

Les Timers (II)

Comment gérer le temps dans un programme ?

Les Timers (III)

Installation de l’IDE arduino

Les structures

Les pointeurs (1)