Comptage avec le TIMER1.

Comptage évènementiel avec l’ATmega328 :

Avant de « sortir l’artillerie lourde », c’est à dire l’utilisation des ressources internes spécifiques au microcontrôleur, et en particulier les INTERRUPTIONS, examinons un minimum le programme de base P05_Compteur_SIMPLIFIE.ino. Comme montré sur laFig.1 l’organigramme de la boucle assurant le comptage est réellement élémentaire.

Fig 1

Nous pourrions utiliser n’importe quelle broche configurable en entrée, mais nous avons choisi l’entrée binaire D5 car c’est elle qui sera utilisée dans le projet final pour mesurer des fréquences. Ce choix n’est pas libre. En effet, comme nous le verrons plus avant, c’est D5 qui sur l’ATmega328 est reliée au TIMER1. En A on prépare l’attente du front montant qui sera détecté en B dès que l’entrée surveillée passera de « 0 » à « 1« . Tester en permanence une entrée binaire est assez rapide, et incrémenter un entier ne prend pas beaucoup de temps. Par contre, l’affichage de la valeur sur la ligne série va ralentir considérablement la boucle de mesure, surtout si le débit est faible. De toute façon, même si l’affichage était plus rapide, on n’atteindra jamais avec cette technique la cadence d’un compteur électronique. Le « silicium » sera toujours plus performant que le logiciel.
C’est la raison pour laquelle pratiquement tous les microprocesseurs disposent de compteurs internes purement électroniques pouvant être « lus » sur les bus de l’unité centrale. L’ATmega328 dispose d’au moins quatre compteurs rapides, dont celui déjà mentionné en page 16. (Fonction millis() ; du C++ d’Arduino)

LE COMPTEUR TIMER1 de l’ATmega328.

Disposant de trois COMPTEURS/TIMEURS principaux dont les modes d’utilisation sont assez complexes, il n’est pas question dans cet exposé d’en passer en revue toutes les possibilités.
Nous allons limiter dans ces pages les explications au plus élémentaire, uniquement pour que vous puissiez comprendre les programmes proposés.
Désolé, mais un minimum de théorie s’impose !
Globalement, TIMER0 et TIMER1 présentent des morphologies identiques pour la sélection de la source provoquant le comptage. La Fig.2 exagérément simplifiée met en évidence les diverses sources d’incrémentations possibles. Ce sont les trois bits de poids faible du registre de contrôle nommé TCCRnB qui imposent l’une des huit options disponibles. C’est en aiguillant vers le compteur des signaux d’horloge périodiques que ce dernier devient un chronomètre capable de gérer des événements liés au temps. La Fig.2 représente le TIMER1 qui fonctionnant sur 16 bits est en réalité composé de deux registres de huit bits TCNT1H et TCNT1L. Lorsque le CPU fait un accès à l’emplacement TCNT1H, l’unité centrale accède au registre Tampon pour l’octet de poids fort. Ce registre temporaire est mis à jour avec la valeur de TCNT1H lorsque TCNT1L est lue. TCNT1H est mis à jour en écriture avec la valeur du registre Tampon lorsque TCNT1L est écrit. Le registre de poids forts TCNT1H ne peut donc être accessible directement par l’unité centrale du microcontrôleur.
La LOGIQUE permet de remettre à zéro leFig2Page19s compteurs, de valider les interruptions sur débordement, de précharger des valeurs initiales etc. La fréquence du signal d’horloge de l’OSCillateur interne piloté par le quartz extérieur Xtal (Généralement de 16Mhz) peut être envoyée au compteur via un Prédiviseur qui augmente la période des signaux comptés. On dispose ainsi d’un chronomètre qui peut de ce fait fonctionner à des cadences variées.

La fonction compteur d’événements.

Fig 3

A l’instar de tous les compteurs 16 bits, TIMER1 recyclera naturellement à zéro quand il aura atteint 65 535 et qu’une impulsion de plus lui parviendra. Bien que cette valeur soit inférieure aux 9.999.999 que nous nous sommes fixés pour objectif final, dans un premier temps nous allons nous en contenter. On va juste apprendre à compter, sans se préoccuper du dépassement de capacité de ce double registre. C’est le programme démonstrateur P06_Compteur_TIMER1_brut.ino qui va illustrer cette approche initiale. Avant de décortiquer quelques lignes de ce programme, examinons la Fig.3 qui

montre le programme. L’organigramme présente un lien de parenté évident avec celui de la Fig.1 mais présente toutefois une différence fondamentale. Le processeur peut passer autant de temps qu’il le désire dans la routine d’affichage, car en (1) le TIMER1 indépendant compte inexorablement chaque fois qu’un front montant arrive sur D5. De plus, taillé dans le silicium ce compteur peut incrémenter jusqu’à 8MHz d’où l’avantage incontestable d’une logique câblée assistant l’unité centrale. Tout au plus, entre deux affichages, la valeur aura changé d’autant plus que l’affichage est long et que la fréquence du signal compté est élevée.
TCCR1A (Identificateur reconnu de l’I.D.E.) est l’un des deux registres de conditionnement du TIMER1. L’instruction TCCR1A = 0 ; configure TIMER1 en simple compteur.

La ligne TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10) ; aboutit à placer 111 dans les trois bits de poids faible du registre TCCR1B (Identificateur reconnu de l’I.D.E.) qui est le deuxième registre de configuration du TIMER1. Cette forme d’écriture est particulière à l’environnement d’Arduino, en détailler ici les raisons sortirait du cadre de cet exposé. Cette combinaison binaire 111 impose comme source l’entrée D5 et sélectionne le front montant pour provoquer l’incrémentation.
           //========================= Traiter le COMPTEUR =====================
          // Ci-dessous une première lecture fait passer TCNT1H dans le TAMPON.
          COMPTEUR = TCNT1L ; (2)
          // Ci-dessous lecture des 16 bits dans la variable COMPTEUR.
          COMPTEUR = TCNT1 ; (3)
          // ================================================================
Conformément à ce qui était précisé dans ce qui précède, on ne peut pas lire directement les deux registres du TIMER1 car l’ATmega328 est un microcontrôleur huit bits avec un bus de données à ce format. La première lecture de TCNT1L en (2) fait passer la valeur de TCNT1H dans le registre dédié Tampon. Notez qu’il serait possible de coder COMPTEUR = TCNT1 ; qui codifie la lecture des deux registres de TIMER1. La valeur de TCNT1H passerait exactement de la même façon dans le Tampon. Maintenant que les poids forts sont disponibles dans le Tampon, la lecture du TIMER1 complet en (3) transfère la valeur attendue dans la variable 16 bits COMPTEUR.
COMPTEUR est l’identificateur que nous avons donné à notre variable. C’est un nom « personnalisé » qui contrairement à TCCR1A, TCCR1B, TCNT1L ou TCNT1H n’est pas standard dans l’IDE.
Dans ce programme nous aurions pu tout aussi bien placer directement TCNT1 dans l’instruction Serial.println(COMPTEUR) ; mais par la suite, augmenter la capacité à 9.999.999 va obliger à du traitement intermédiaire, et cette donnée devra être mémorisée. (On prépare le terrain !)
Dans la procédure de service void RAZ_COMPTEUR() on a écrit TCNT1H = 0 ; TCNT1L = 0;. C’est une façon de faire qui respecte l’ordre qui en écriture doit commencer par TCNT1H et finir par TCNT1L, l’usage du Tampon étant inversé en lecture et en écriture. Rien n’interdit d’écrire directement

TCNT1 = 0 ; car le compilateur procède dans l’ordre correct en écriture et en lecture.

Rompre régulièrement la routine.

Contrairement aux humains qui détestent être interrompus sans arrêt quand ils cherchent à dénombrer de quelconques événements, les microcontrôleurs deviennent particulièrement réactifs quand on vient perturber le confort routinier de la boucle de base ou de ses procédures de servitude. Le petit programme expérimental P07_Compteur_TIMER1_avec_IRQ.ino va nous permettre d’illustrer ce propos. Vous avez déjà compris que nous allons nous aventurer dans les sentiers redoutés, à tort, des INTERRUPTIONS. Rassurez-vous, c’est infiniment plus simple que l’on croit trop souvent. Du reste, vous pratiquez des interruptions régulièrement sans y avoir prêté attention. Envisageons par exemple le fonctionnement très routinier de la boucle de base. Dans void LOOP() on « tourne » régulièrement d’une instruction à la suivante avec une régularité lancinante.
Mais si votre programme est bien écrit, au lieu de comporter 500 instructions élémentaires, notre ronde n’est composée que d’appels à des fonctions de service assurant chacune une tâche bien spécifique. Lors de l’appel, il y a interruption de la boucle de base pour sauter à un traitement « ponctuel ». Quand ce dernier est achevé, on revient au train train familier de void LOOP().
Dans les propos qui précèdent, on quitte le traitement en cours dans un ordre déterminé à l’avance.
Les INTERRUPTIONS, c’est exactement analogue, sauf que l’appel de la routine particulière peut survenir à tout moment. Le traitement en cours est suspendu, la routine d’interruption est traitée, puis on reprend le travail de fond là où il avait été suspendu. C’est aussi simple que ça.

Fonctionnement des INTERRUPTIONS.

Choisir en exemple d’école dans la vie de tous les jours va nous faire comprendre facilement le mécanisme des INTERRUPTIONS. Envisageons une infirmière dévouée, (Toutes le sont !) qui travaille de nuit dans la surveillance des patients d’un Hôpital. Dans le calme nocturne, sa boucle de base routinière consiste à passer de chambre en chambre pour vérifier que tout va bien.
Discrètement, elle pénètre dans la chambre 38, consulte le moniteur, et si tout va bien elle revient dans le couloir et continue courageusement vers la chambre 39. Tout d’un coup, à l’improviste, une alerte sonore se déclenche. Que fait-elle alors ?
• Elle sort immédiatement dans le couloir, cherche quelle lampe rouge est allumée au dessus des portes.
• Elle se rend sans perdre de temps à la chambre 12 dont le moniteur a déclenché l’alerte.
• Avec maitrise et calme elle traite l’incident. Quand c’est fait, elle désactive et réarme l’alerte.
• Puis elle retourne à la chambre 39 pour y terminer ses vérifications avant de poursuivre en 40.
Le fonctionnement en INTERRUPTIONS des microcontrôleurs est strictement analogue. Plusieurs électroniques périphériques à l’U.C. peuvent au souhait du programmeur assister cette dernière.
Chacune est l’équivalent de nos chambres d’hôpital. Quand l’une d’elles a besoin d’être prise en compte, elle le signale au processeur qui mémorise l’endroit où il « se trouve » dans le programme. Il y a alors saut à une procédure spécifique qui commence par interdire toute nouvelle interruption si elle est prioritaire. Quand elle s’achève, le « drapeau » qui la concerne est remis à « 0 », éventuellement les interruptions réactivées, puis le processeur retourne à l’adresse du code qui avait été mémorisée.
TOUT CE PROCESSUS EST AUTOMATIQUE… OUFFFFFFFFFFFFFFFFF !

Concrètement, chaque module électronique interne au processeur pouvant déclencher une interruption sera organisé pour que l’on puisse facilement travailler (Programmer !) comme le fait l’infirmière mentionnée ci-avant. L’ATmega328 possède plusieurs sources internes pouvant déclencher des interruptions particulières. Pour chacune de ces sources on dispose :
• D’un « vecteur » dans lequel on indiquera quelle est la procédure de traitement associée,
• D’un « drapeau » qui indique à l’U.C. qui a déclenché l’interruption,
• D’un masque d’interruption qui permet d’activer les déclenchements ou de bloquer le demandeur.
Pour compter, mesurer des fréquences, des périodes etc., on va utiliser les interruptions.
EN QUOI UNE INTERRUPTION EST-ELLE PLUS DYNAMIQUE qu’un programme routinier ? Il suffit de reprendre l’exemple d’école déjà envisagé. Dans la chambre 12, le patient est victime d’un arrêt cardiaque. S’il n’y avait pas les alertes, l’infirmière continuerait professionnellement à passer les 28 autres chambres en revue avant de revenir à la 12 … trop tard une fois de plus !
L’immense avantage des interruptions, c’est que des phénomènes transitoires parfois de très courte durée sont immédiatement pris en compte et ne passent pas inaperçus. Interrompre la routine de base qui ne s’occupe que d’éléments non urgents n’est absolument pas pénalisant. Nous avons tous les éléments en main pour réaliser un vrai compteur rapide. En sois, il est assez rare de désirer compter des phénomènes à cadence rapide. Mais plus avant, on va concevoir un fréquencemètre. Ce n’est pas autre chose qu’un compteur que l’on valide durant une seconde, mais on désire alors pouvoir mesurer à des fréquences de plusieurs MHz… et il n’y aura pas de temps à perdre !

Compteur rapide avec TIMER1.

Avec le programme simplifié P06_Compteur_TIMER1_brut.ino nous nous somme contentés d’utiliser TIMER1 sans prendre en compte son débordement, c’est à dire le recyclage de la valeur maximale (Sur 16 bits.) de 65 535 à 0. S’il s’agissait de compter les automobiles qui passent sur une route ou les vaches qui rentrent dans une étable, cette capacité couvrirait largement nos besoins et l’on pourrait en rester là. Mais lorsque nous allons chercher à concevoir un fréquencemètre, le limiter à 65KHz serait bien tristounet. Limitation d’autant plus « déraisonnable que théoriquement TIMER1 peut mesurer jusqu’à 8 MHz. Nous allons donc nous imposer un compteur capable de couvrir cette plage. Pour des raisons diverses, la limite sera fixée à 9.999.999 comme déjà annoncé dans les pages précédentes. C’est P07_Compteur_TIMER1_avec_IRQ.ino qui va nous servir de banc d’essai, mais avant, un précepte fondamental doit contraindre en permanence notre enthousiasme :

Pour cerner le bienfondé de ce principe qu’il ne faudra jamais oublier, envisageons un comptage à cadence maximale, soit à 8 MHz. À cette vitesse, les 65 536 comptages sont effectués en moins de 9 mS. Si à chaque interruption on lambine et l’on prend le temps d’effectuer un long traitement, on risque de laisser passer un recyclage et la valeur mesurée sera erronée. Hors, pour avoir un résultat correct, il suffit de savoir avec certitude combien de dépassements ont été recensés. Le programme respectant ces préambules est résumé sur la Fig.4 qui met en évidence le traitement « dérisoire » (Mais impératif.) réalisé dans la procédure d’interruption, et le travail assuré ensuite dans la boucle de base. Vous avez déjà compris que :

Fig 4

Formule dans laquelle l’identificateur Nb_IRQ représente concrètement le « Nombre d’Interrupt ReQuest ».

Détaillons l’architecture et le fonctionnement du programme :
En (1) le TIMER1 étant conditionné en simple compteur fonctionne en autonome et s’incrémentera chaque fois que sur D5 une transition montante sera détectée.
Comme en (2) on a validé des déclenchements d’interruptions, chaque fois qu’il passe de
65 535 à zéro il y aura des branchements à la Routine IRQ à sa demande. Dans notre logiciel, la routine d’interruption spécifique à TIMER1 ne fait qu’incrémenter en (3) la variable Nb_IRQ ce qui ne monopolise l’U.C. que durant quelques μS.
En (4) la boucle de base procède « sereinement » et sans se presser aux divers calculs. Elle commence par récupérer en (5) la valeur actuelle de TIMER1 qu’elle place dans COMPTEUR. Puis en (6) elle lit la valeur de la variable globale Nb_IRQ qu’elle multiplie par 65 536, résultat qu’elle ajoute à COMPTEUR. En (7), si COMPTEUR est arrivé à la valeur maximale autorisée, TIMER1 est bloqué et s’arrête définitivement de compter. Notez qu’en (8) la valeur de COMPTEUR n’est affichée que si elle a changé pour éviter un défilement vertical permanent des informations sur le moniteur vidéo. Dans tout ce mécanisme, il importe de remarquer que le fonctionnement de TIMER1 est totalement

indépendant du déroulement de la boucle de base void LOOP(). Par ailleurs, les interruptions qu’il déclenche en (3) ne font que dérouter le programme dans Routine IRQ durant à peine quelques μS. Ces détournements arrivent à n’importe quel « endroit » de void LOOP(). C’est le principe de base des mécanismes d’INTERRUPTION. Le reste n’est plus qu’une question de codage avec des écritures et des conventions propres à chaque compilateur. En langage C adapté à l’environnement d’Arduino nous disposons de divers protocoles et identificateurs réservés à cette fin. Passons en revue les lignes de programmes relatives à cette facette de la programmation de l’ATmega328 dans P07_Compteur_TIMER1_avec_IRQ.ino servant à défricher ce chapitre de la « programmatique » :

• Vous avez reconnu l’instruction :
TCCR1A = 0 ; qui configure TIMER1 en simple compteur et la ligne :
TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10) ; qui impose comme source l’entrée D5 et sélectionne le front montant provoquant l’incrémentation.
• Ce qui est nouveau, c’est la déclaration ISR(TIMER1_OVF_vect) {Nb_IRQ++ ;}
placée en tête de programme, avant void SETUP() qui définit la routine d’interruption du TIMER1 sur débordement. Dans cette forme d’écriture,
ISR informe le compilateur que le vecteur (Adresse d’interruption.) relatif à TIMER1_OVF devra pointer sur la routine dédiée placée entre
accolades
  (Vector for Interrupt Service Routine on OVerFlow TIMER1)

Cette écriture n’est finalement pas compliquée du tout. On se contente de prévenir que la ligne de code concerne les interruptions, puis entre parenthèses on précise quel module fonctionnel du microcontrôleur est concerné, puis entre accolades on place toutes les instructions qui seront déroulées lors de cette interruption. Avant de poursuivre, ouvrons une petite parenthèse : (Vocabulaire)

• Autre particularité : La ligne TIMSK1 |= (1<<TOIE1) ; qui sous une forme d’écriture propre à l’I.D.E. place à « 1 » le drapeau TOIE1 dans le registre TIMSK1.
Les explications développées dans l’encadré doivent vous permettre d’en déduire que TIMER1 pourra interrompre sur débordement.
Au final, quand on analyse le programme expérimental, mis à part des écritures un peu particulières comme TIMSK1 |= (1<<TOIE1) ; qui utilisent du décalage logique avec du OU dont il suffit de comprendre la structure, utiliser un TIMER en interruption sur débordement est en conclusion très simple. La structure de la séquence globale se résume à :

     > Préciser au compilateur le code de la routine d’interruption et la fonction électronique concernée,
     > Configurer le TIMER/COMPTEUR en fonction de nos choix, (Compteur, chronomètre etc.)
     > Mettre à zéro le TIMER et les variables associées dans notre programme,
     > Valider le drapeau du TIMER dans le REGISTRE des INTERRUPTIONS,
     > Utiliser les valeurs des variables associées dans la boucle de base ou ses servitudes,
     > Éventuellement arrêter le comptage ou suspendre les interruptions si le logiciel l’exige.

Notez que dans P07_Compteur_TIMER1_avec_IRQ.ino c’est la procédure RAZ_COMPTEUR(); qui se charge de mettre à zéro les variables du programme et de configurer TIMER1. Dans cet exemple expérimental ce n’est pas très rentable, car l’appel à ce code est unique, mais on anticipe ici le développement d’un programme plus global qui devra effectuer cette opération plusieurs fois.
Remarquez également que pour agrémenter l’affichage, des « zéros en tête » sont ajoutés pour rendre la visualisation plus agréable à l’écran, stabilisant sur les lignes affichées la position latérale des nombres qui défilent à la rapidité du comptage ou du rafraichissement de la boucle de base.
Certains vont avoir l’impression que les trois pages qui précèdent présentent par moment quelques aspects « indigestes », car encombré de beaucoup de texte. C’est probablement assez justifié, vu que l’on a foncé « bille en tête » dans le domaine des Interruptions qui généralement se trouve plutôt vers la fin des livres de vulgarisation. Ne vous prenez pas la tête, si tous ce charabias vous a un peu gavé, oubliez ce sentier, on peut s’en passer. Contentez-vous de tous les autres chapitres où nous n’abordons que des « fondamentaux de base ». Si certaines lignes de code ne sont pas des évidences, aucune raison d’en faire un complexe. Appropriez-vous ce qui vous plait et… oubliez le reste. Il vous sera toujours possible d’y revenir quand l’envie s’en fera sentir.
Maintenant que la notion de compteur est globalement assimilée, tout au moins dans les grandes lignes, on va pouvoir explorer des domaines très intéressants dans le monde du mesurage.
Pour achever ce chapitre sur le comptage rapide, vous pouvez si vous en avez l’envie, charger le petit croquis démonstrateur en C++ P08_Compteur_USB_par_TIMER1.ino qui propose une version « à deux B.P. » en vue de son intégration dans le logiciel définitif. Le bouton poussoir
FC + en utilisation courte inverse le sens de la transition qui provoque le comptage, l’affichage précisant en début de chaque ligne l’option en cours. Le bouton FC + en utilisation longue fait sortir de la fonction compteur et retourne(ra) au menu de base. Enfin FC – provoque une remise à zéro du compteur sans interrompre ses incrémentations. Pour tester la fonction COMPTEUR de notre mini laboratoire, il suffit d’injecter sur D5 un signal périodique quelconque. S’il fait 1 MHz, le septième chiffre affiché changera environ toutes les secondes. À 1000 Hz c’est le quatrième chiffre qui s’incrémentera à chaque seconde. À
4 MHz le compteur sera saturé en moins de trois secondes et à 8 MHz… devinez !

L’artillerie lourde !

Entre survoler de très loin en « cachant tout » et détailler la moindre molécule informatique constituant nos programmes, il a fallu accepter des compromis pas forcément facile à choisir. Dans un document tel que celui-ci, il n’est pas possible d’expliciter chaque ligne de programme. Parfois un encadré tel que celui ci-avant ou d’autres disséminés ici et là peuvent nous aider à clarifier certains points incontournables. Mais multiplier ce genre de parenthèses alourdirait inexorablement notre cheminement réputé « amusement de loisir ».

D’un autre coté, quand on affirme que des instructions telles que TCCR1B = (1<<CS12) | (1<<CS11) | (1<<CS10) ; ne sont que des écritures particulières pour imposer une source l’entrée D5 et sélectionner le front montant provoquant l’incrémentation, certains aimeraient bien disposer d’informations supplémentaires sans avoir à chercher des développements ou des résumés sur Internet. C’est la raison pour laquelle, en prime,  le petit fichier Fiches Arduino.pdf est mis à votre disposition. Vous y trouverez en vrac un certain nombre de petites fiches gavées à saturation d’informations « disparates » mais plus ou moins directement liées au contenu des « bavardages » qui encombrent Fiches Arduino.pdf. À votre guise vous pouvez ignorer sans vergogne ces fiches, mais aussi en faire une consommation sans modération.

>>> Page suivante.

 

7 réflexions sur « Comptage avec le TIMER1. »

    1. l’intégralité des documents téléchargeable de cet ouvrage est donné dans les premières lignes de l’ouvrage.
      Page d’introduction de l’ouvrage
      Par contre en effet on pourrait le mettre un peu plus en évidence. ( Il faut cliquer sur le  » ici  » de la phrase :  » Cher lecteur, (>>>> L’intégralité des fichiers téléchargeables se trouvent ici, vous avez l’explication en bas de cette page.)  »

      à très bientôt sur le forum j’espère ! =)

  1. Bonjour, merci pour ce cours que je viens de découvrir. Enfin on rentre dans le vif du sujet et on s’attaque au micro contrôleur. Peut on aussi décompter sur ces registres ? et comment faire. Je ne comprend pas la série de IF qui permettent d’afficher la valeur du compteur dans le progarmme P07 Compteur TIMER1 avec IRQ.ino? Je démarre tjrs avec une valeur de 65537 que je n’ai pas réussi a remettre a 0 ?
    Pourriez vous me conseiller des ouvrages ou des sites qui parlent de l’architecture de l’ATmega328 et des instructions en C ou autre qui permettent de le programmer ( en français si possible mais pas obligatoirement). Merci a l’avance

  2. Je cherche à programmer un axe CN avec un moteur pas à pas. le comptage direct dans un registre n’est peut être pas la meilleur solution ? Une simple interrupteur et comptage programmé serait peut être plus simple.

  3. Désolé pour la demande sur les IF, la réponse est dans le cours que j’ai parcouru trop vite. J’ai l’impression qu’il en est de même pour les registre de l’ATmega …

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *