Aller au contenu

Timer Arduino : introduction, explications, et exemples de code concrets (microcontrôleur ATmega328P)

Introduction timer arduino avec code exemple sur Arduino Uno, Nano, ou Pro mini, avec ou sans interruption, gestion des registres avec explications

S’il y a bien une chose parfois difficile à appréhender au début, c’est bien la manipulation des Timer Arduino. Car pour « bien » les paramétrer, il faut savoir quels bits toucher, et dans quels registres ces bits se situent. Et franchement, ce n’est si simple que ça, lorsqu’on fait face à eux pour la première fois 😉

C’est pourquoi j’ai eu l’idée de vous faire ce petit tuto, mêlant à la fois un peu de théorie (l’explication des timers, et de leurs registres respectifs), et plusieurs exemples pratiques, vous montrant comment concrétiser tout ça, dans le code arduino ! Ainsi, vous verrez différentes façons de faire les choses, afin d’arriver à manipuler les timer comme bon vous semble, dans vos futurs projets !

Bien entendu, cet article n’a pas d’autre but qu’être éducatif, et didactique. Car il est clair que les exemples de codes qui seront présentés ici ne serviront pas à grande chose, et qu’on aurait également pu les écrire de manière bien plus simple. D’ailleurs, pour ma part, je suis convaincu du fait qu’avoir un aperçu concret de ce que peuvent permettre les timer arduino sera un bon préambule pour vous, à leur apprentissage. Qui plus est : quand c’est concret, c’est bien plus motivant ! Dans tous les cas, n’hésitez pas à analyser et décortiquer tout ce que je vais vous présenter ici, afin de bien comprendre comment fonctionnent « précisément » les timer Arduino !

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

Ici, je ne vais m’intéresser qu’aux timer présents dans le microcontrôleur ATmega328P (ici lien vers le datasheet de l’ATmega328P), équipant entre autre les Arduino Uno, Nano, et Pro Mini. C’est pourquoi tout ce qui suit ne sera valable que pour ce microcontrôleur, et aucun autre. Du coup, si vous travaillez sur Arduino Mega, Micro, ou je ne sais quoi d’autre, il faudra reprendre les datasheets du fabricant, pour adapter les programmes qui suivent.

Remarque : gardez bien à l’esprit ici que ce ne sera qu’une introduction aux timer arduino, et non un cours ou tuto complet et exhaustif. Car il y aurait tant à dire… qu’un simple article ne suffirait pas à tout résumer. Aussi, je vais me limiter à ce qui selon moi sont les bases à connaître, dès lors qu’on parle de « timer arduino », tout en vous rajoutant des exemples pratiques, pour mieux comprendre les choses !

Introduction aux timer de l’ATmega328

Le microcontrôleur ATmega328P, présent sur les Arduino Nano, Uno et d’autres, comporte 3 compteurs (timer) :

  • Le 1er compteur (nommé « timer0 »)
  • Le 2ème compteur (nommé « timer1 »)
  • Le 3ème compteur (nommé « timer2 »)

Ces trois compteurs peuvent compter :

  • Pour le timer0 : de 0 à 255 (car il est codé sur 8 bits)
  • Pour le timer1 : de 0 à 65535 (car il est codé sur 16 bits)
  • Pour le timer2 : de 0 à 255 (car il est codé sur 8 bits)

Ces compteurs avancent d’un en un, et, une fois qu’ils « arrivent au bout » (à leur valeur maximale), ils repassent immédiatement à zéro, puis recommencent à compter (sans interruption). En clair : ils comptent de manière perpétuelle, sans jamais s’arrêter (sauf s’ils sont désactivés, bien sûr !). À noter que chaque fois qu’ils reviennent à zéro, un signal est « émis » (aussi appelé drapeau, ou flag en anglais), et nous verrons que ce signal est très utile à capter !

La valeur de chaque timer est stockée dans un registre qui lui est propre (l’équivalent d’une variable d’un programme arduino, si vous voulez). Ainsi, respectivement :

  • La valeur du timer0 est stockée dans le registre TCNT0
  • La valeur du timer1 est stockée dans le registre TCNT1 (et plus précisément, en arrière plan, dans TCNT1H et TCNT1L ; car comme la valeur TCNT1 fait 16 bits, il faut la stocker dans 2 registres de 8 bits)
  • La valeur du timer2 est stockée dans le registre TCNT2

Par ailleurs, il faut savoir que même si ces timer démarrent à zéro, au début du programme, on peut « forcer » leur valeur à tout moment, pour qu’ils comptent à partir d’un certain nombre, si souhaité. D’ailleurs c’est exactement ce que l’on fera dans l’un des deux exemples que je vous montrerai à la fin de ce tuto.

L’avancement de ces compteurs se fait au rythme des coups d’horloge du microcontrôleur, ou un sous-multiple de la fréquence d’horloge, plus précisément, je devrais dire. Car si votre ATmega328P est cadencé à 16 MHz, le comptage pourra aussi bien se faire à 16 MHz, tout comme à 8 MHz, 4 MHz, ou bien d’autres fréquences encore, en fonction du « prescaler ». Ce prescaler, ou « diviseur de fréquence » si vous préférez, permet en fait de compter plus ou moins vite, selon ses besoins, au niveau des timer arduino. Par contre, on ne pourra pas choisir des vitesses précises, mais seulement choisir parmi les rapports de fréquences proposés par le microcontrôleur.

À noter que ces compteurs sont totalement indépendants (sauf instruction les visant particulièrement) du programme exécuté sur l’Arduino. C’est-à-dire que quel que soit le programme tournant sur votre Arduino, les comptages se feront toujours de manière régulière, indépendamment de ce que fait votre programme. Car l’avancée des timer ne se fait pas de manière logicielle, mais de manière physique, si je puis dire. C’est d’ailleurs tout l’intérêt des timer, qui agissent en parallèle de l’exécution des programmes arduino.

Au niveau des diviseurs de fréquences possibles sur l’ATmega328P, on retrouve :

  • Un prescaler (diviseur de fréquence « général »), permettant de diviser la fréquence du microcontrôleur par 1, 2, 4, 8, 16, 32, 64, 128, ou 256
  • Et 3 prédiviseurs « secondaires » (un pour chaque timer), permettant de rediviser la fréquence issue du prescaler par :
    • 1, 8, 64, 256, ou 1024 s’il s’agit du timer0
    • 1, 8, 64, 256, ou 1024 s’il s’agit du timer1
    • 1, 8, 32, 64, 128, 256, ou 1024 s’il s’agit du timer2

Nota : la fréquence rythmant l’avancée de chaque timer correspondra donc au final à la fréquence du microcontrôleur, divisée par le rapport de division du prescaler, puis encore divisée par le rapport de division du prédiviseur propre au timer en question. Ainsi, par exemple :
– si la fréquence du microcontrôleur est de 16 MHz
– si le prescaler est réglé sur 4
– et si le prédiviseur du timer est réglé sur 32
– Alors la fréquence arrivant au timer sera de 16MHz / 4 / 32, soit 125 kHz.

Le saviez-vous ? vous utilisez certainement déjà les timer arduino, sans sans même le savoir ! Car la plupart du temps, ceux-ci sont utilisés en « arrière plan », dans des librairies arduino. Du coup, vous n’avez peut-être même pas conscience de vous en servir, indirectement ! En effet :
Le timer0 sert par exemple aux fonction delay(), micro(), millis(), ainsi qu’aux signaux PWM sortant sur les broches D5 et D6
Le timer1 sert en outre à la bibliothèque « Servo », permettant de contrôler des servomoteurs, ainsi qu’aux signaux PWM sortant sur les broches D9 et D10
Le timer2 sert entre autre à la fonction tone(), ainsi qu’aux signaux PWM sortant sur les broches D3 et D11

Dernière chose : gardez toujours à l’esprit que TOUCHER AU PRESCALER OU PRÉDIVISEUR de fréquence des timer arduino peut ALTÉRER LE FONCTIONNEMENT DU RESTE DE VOTRE PROGRAMME ! Car ces timer peuvent servir à bien des fonctions, en arrière plan ! Du coup, réfléchissez bien avant de faire quoi que ce soit 😉

Le Prescaler (diviseur de fréquence général)

Comme vu en intro, le cadençage (l’avancée) des timers arduino se fait à partir de la fréquence d’horloge du microcontrôleur. Mais celle-ci peut, au passage, être divisée par 2 diviseurs de fréquence d’horloge. Ces diviseurs sont :

  • Le prescaler (celui que nous allons voir ici)
  • Et un prédiviseur, propre au timer en question

Graphiquement, voici comment on pourrait représenter les choses :

Prescaler arduino de l'ATmega328P, permettant de diviser la fréquence d'horloge du microcontrôleur par 1 jusqu'à 256, division de fréquence quartz uno

Comme vous le voyez, il est possible de faire fonctionner les timer à « pleine vitesse », tout comme à vitesse réduite. Pour ce faire, on peut jouer sur le prescaler, et le prédiseur propre au timer visé. Au niveau du prescaler, on peut diviser la fréquence du microcontrôleur par :

  • Division par 1 (comme si le prescaler était désactivé)
  • Division par 2
  • Division par 4
  • Division par 8
  • Division par 16
  • Division par 32
  • Division par 64
  • Division par 128
  • Division par 256 (réduction maximale de la fréquence d’horloge, à ce niveau)

Ainsi, par exemple : si le quartz de votre ATmega328P a une fréquence de 16 MHz, alors les fréquences possibles en sortie du prescaler seront : 16 MHz, 8 MHz, 4 MHz, 2 MHz, 1 MHz, 500 kHz, 250 kHz, 125 kHz, ou 62500 Hz.

À noter que la valeur du prescaler peut être changée à tout moment, de manière logicielle (dans le code arduino, donc). Et pour ce faire, il suffit simplement de changer un ou plusieurs bits du registre « CLKPR », interne à l’ATmega328P. Voici d’ailleurs le détail de chacun des bits de ce registre :

Registre CLKPR atmega328p installé sur un Arduino Uno ou Nano, fonction prescaler avec rapport de division fréquence d'horloge microcontrôleur code

Comme vous avez pu le voir ci-dessus, c’est le bit « CLKPCE » (tout à gauche) qui permet d’indiquer au microcontrôleur que l’on souhaite modifier la valeur du prescaler. Et une fois cette indication faite, il suffira de renseigner les bits CLKPS3, CLKPS2, CLKPS1, et CLKPS0 au microcontroleur, pour spécifier le rapport de division souhaité. Ainsi, selon la valeur de ces bits, la fréquence en sortie du prescaler sera égale à la fréquence d’horloge en entrée, divisée par le rapport correspondant.

Très important : pour éviter tout changement involontaire de rapport de division de fréquence d’horloge, le fabricant impose une procédure en 2 étapes pour changer ce rapport de fréquence, au niveau du prescaler. Il faudra donc :
tout d’abord mettre à 1 le bit CLKPCE et tous les autres bits à 0 (en envoyant par exemple le nombre 0b10000000 dans le registre CLKPR)
et ensuite, dans un délai maximal de 4 cycles d’horloge, de mettre à 0 le bit CLKPE, tout en spécifiant dans les 4 derniers bits le rapport de division de fréquence souhaitée (pour cela, il suffira donc d’envoyer le nombre 0b0000xxxx dans le registre CLKPR, en remplaçant xxxx par des valeurs 1 ou 0, selon le rapport de division que vous voulez, comme visible dans le tableau ci-dessus)

Par ailleurs, il faudra vous assurer que les interruptions soient bien désactivées au moment de ce changement de rapport de fréquence, pour être certain que cette procédure se déroule bien, dans « le temps imparti » (qui, pour rappel, doit se faire sous 4 cycles d’horloge maximum, comme le précise le fabricant). Bien évidemment, si vous n’utilisez pas la moindre interruption, il n’est pas nécessaire de les désactiver 😉

Exemple : si vous souhaitez activer le prescaler, pour qu’il divise la fréquence d’horloge par 32, alors il faudra écrire les 2 lignes de code suivantes, dans votre programme Arduino :

CLKPR = 0b1000 0000;  // On indique au microcontrôleur qu’on souhaite changer de rapport de fréquence
CLKPR = 0b0000 0101;  // Puis on indique le rapport voulu (ici « 0101 » sur les 4 derniers bits,  donc division par 32)

Remarque : il faut « presque impérativement » que ces deux lignes de code se suivent. Car une fois que vous aurez spécifié au microcontrôleur votre souhait de changement de rapport de fréquence (1ère ligne), il faudra lui indiquer le nouveau rapport dans un délai inférieur ou égal à 4 cycles d’horloge (2ème ligne). C’est court ! Donc mieux vaut ne pas traîner 😉

Autre remarque importante : modifier le rapport de division du prescaler (par défaut à 1) impactera directement tout un tas de systèmes, internes au microcontrôleur. Pour en citer une : la liaison série (UART). Celle-ci sera d’autant plus lente que le rapport de division sera élevé. Ainsi, si vous écrivez un programme avec Serial.begin(9600) au début, pour établir une liaison série à 9600 bauds avec votre ordi, en fait, vous ne pourrez QUE communiquer à une vitesse égale à 9600 divisée par le rapport choisi. Du coup, si par exemple vous paramétrez votre prescaler pour une division par 4, alors la communication ne pourra se faire qu’à 2400 bauds (9600/4). Et donc, pour que les données puissent s’afficher sur un terminal, comme le moniteur série de l’IDE Arduino, il faudra alors régler la communication sur 2400 bauds au niveau de votre ordi, et non 9600 ! Par contre, dans votre programme arduino, il faudra bien rester sur 9600 bauds. En synthèse : toucher au prescaler peut influer bien des systèmes. Et si vous n’y faites pas attention, vous pourriez fort bien vous retrouver avec des comportement totalement « bizarres » !!!

Les Prédiviseurs (diviseurs de fréquences secondaires, propres à chaque timer)

Maintenant que nous avons vu le prescaler, nous allons voir les prédiviseurs. À peu de choses près, il s’agit de la même chose. Mais alors que le prescaler ralentit « tout » le microcontrôleur, les prédiviseurs, quant à eux, ne ralentissent que les timers auxquels ils sont associés. Par contre, les prédiviseurs sont « alimentés » avec la fréquence du prescaler. C’est à dire que la fréquence d’un timer donné, sera en fait égale à la fréquence d’horloge du microcontrôleur, divisée par le rapport de division du prescaler, puis encore divisée par le rapport du prédiviseur associé à ce timer.

Au niveau de l’ATmega328P, retrouve donc 3 prédiviseurs, puisqu’il y a 3 timer. Mais attention, car tous ces prédiviseurs ne sont pas identiques. En effet, leur rapport de division n’est pas forcément le même, suivant s’il s’agit du timer0, du timer1, ou timer2. Plus précisément :

  • Timer0 et Timer1 permettent de diviser la fréquence par 1, 8, 64, 256, ou 1024
  • Timer2 permet quant à lui de diviser la fréquence par 1, 8, 32, 64, 128, 256, ou 1024

Le rapport de division souhaité est quant à lui simplement stocké dans un registre particulier, et propre à chaque timer. C’est ainsi que :

  • On indiquera dans le registre TCCR0B le rapport souhaité pour le timer0
  • On indiquera dans le registre TCCR1B le rapport souhaité pour le timer1
  • On indiquera dans le registre TCCR2B le rapport souhaité pour le timer2

Nota : faites très attention à partir d’ici, car bien souvent, une seule lettre ou un seul chiffre peuvent tout changer, et spécifier tout à fait autre chose. Car il existe des registres TCCRxB, tout comme il existe des registres TCCRxA, par exemple. Donc attention à ne pas vous mélanger les pinceaux, en écrivant les noms de tous ces registres !

Au niveau des registres de contrôle TCCR0B, TCCR1B, et TCCR2B, voici les « tables de vérités » permettant de choisir un rapport de division de fréquence souhaité, en fonction des bits renseignés (CS00 à CS22) :

Registre TCCR0B du timer0, permettant de changer le diviseur de fréquence de l'ATmega328P, de 1 à 1024, pour que l'arduino compte moins vite sur ce timer
Registre TCCR1B de l'Arduino, permettant de choisir le rapport de division fréquence, prédiviseur intégré aux timer arduino de l'ATmega328P, avec code exemple
Registre TCCR2B arduino, prédiviseur de fréquence du timer 2 de l'ATmega328P, rapport de division de 1 à 1024, accessible via code de programmation IDE

Comme vous avez certainement dû le remarquer, les bits de sélection de pré-division ne sont pas les seuls bits présents dans ces registres. Du coup, au niveau du code arduino, il faudra bien faire attention de ne pas écraser les autres valeurs au passage, par erreur ! Mais rassurez-vous, car il existe plusieurs moyens de faire les choses, sans modifier la totalité des registres :

  • Soit vous changez tous les bits du registre TCCRxB à la fois, en mettant à jour tous les paramètres d’un seul coup (avec par exemple un : TCCR1B = 0b00000011, pour mettre ICNC1, ICES1, WGM13, WGM12, et CS12 à 0, ainsi que CS11 et CS10 à 1)
  • Soit vous changez vos valeurs « bit à bit », en utilisant des masques ou fonctions particulières, pour ne pas modifier les autres valeurs au passage

Pour cette 2ème façon de faire, voici quelques exemples de lignes de code arduino, permettant de mettre un bit à 0 ou à 1, dans un registre particulier :

  • Exemple #1 : pour mettre CS10 à 1 dans le registre TCCR1B :
    • Solution 1 : bitSet(TCCR1B, CS10);
    • Solution 2 : TCCR1B |= (1<<CS10)
  • Exemple #2 : pour mettre CS12 à 0 dans le registre TCCR1B :
    • Solution 1 : bitClear(TCCR1B, CS12);
    • Solution 2 : TCCR1B &= ~(1<<CS12);

Si tout ça ne vous semble pas très clair, ne vous inquiétez pas ! Car avec les exemples que vous trouverez un peu plus bas, tout prendra sens ! Pour l’heure, passons à présent aux différents registres qu’on trouve sur l’ATmega328P, et liés aux timer arduino.

Les différents registres utiles et liés aux Timer Arduino

Dans cette partie, je vais essayer de parcourir tous les registres « principaux », en lien avec les timers de l’ATmega328P, équipant notamment les Arduino Uno, Nano, et Pro mini. Bien sûr, je ne pourrais pas tout lister ici, tellement il y aurait à dire ! Je reste donc focus sur ce qui est l’essentiel, selon moi.

Pour rappel : cet article n’est qu’une introduction aux timer arduino, et par conséquent, tout ce qui est abordé ici est simplifié ou épuré au maximum. Il s’adresse donc avant tout à tous ceux qui débutent avec les timer, et qui veulent découvrir comment se servir de ceux de l’ATmega328P (au travers d’un peu de théorie, et d’exemples d’utilisation pratique). Ne vous attendez donc pas à trouver ici quelque chose d’exhaustif, ni de très détaillé, sinon cet article serait bien trop long, et je perdrais certainement beaucoup de monde en cours de route ! Aussi, afin de ne pas vous dégouter des timer arduino, je vais éviter de vous présenter trop d’informations à la fois, et essayer de synthétiser les choses au maximum 😉

Les timer en eux-mêmes (dont la valeur est stockée dans des registres nommés « TCNTx »)

Comme vu un peu plus tôt, l’ATmega328P possède 3 timer. Ceux-ci sont notés « timer0 », « timer1 », et « timer2 ». Ces timer sont en fait tout simplement des compteurs, pouvant compter de 0 à 255 pour les timer n°0 et n°2 (8 bits), et de 0 à 65535 pour le timer n°1 (2 x 8 bits). On peut accéder à la valeur de ces compteurs à tout moment, tout comme forcer leur valeur. Ces valeurs sont stockées dans des registres, très facilement accessibles depuis le code Arduino.

En effet, dans votre programme Arduino :

  • La variable « TCNT0 » désignera la valeur du timer0
  • La variable « TCNT1 » désignera la valeur du timer1
  • La variable « TCNT2 » désignera la valeur du timer2

Remarque : contrairement aux autres, la variable TCNT1 est exprimée sur 16 bits (du fait que le timer1 peut compter jusqu’à 65535). Même si, dans le code arduino, on peut manipuler des variables 16 bits sans problème, en réalité ce sont bien 2 registres de 8 bits qui sont renseignés : TCNT1H (contenant les 8 bits de poids les plus forts), et TCNT1L (contenant les 8 bits de poids les plus faibles). Bien sûr, tout ceci est transparent pour nous, qui codons sous l’IDE Arduino. Car le compilateur se charge de tout, et donc, nous n’avons pas à nous occuper de tout ça ! Par contre, sachez que vous pourrez parfaitement renseigner les registres 8 bits TCNT1H et TCNT1L « à la main », si le coeur vous en dit ! Cela reviendra au même, bien qu’il soit plus facile pour nous de manipuler une seule variable, que deux « demi variables » 😉

Les registres de contrôle (TCCRxx)

L’ATmega328P possède 7 registres de contrôle principaux, qui sont :

  • Pour le timer 0 : TCCR0A, et TCCR0B
  • Pour le timer 1 : TCCR1A, TCCR1B, et TCCR1C
  • Pour le timer 2 : TCCR2A, et TCCR2B

Avant d’aller plus loin, faites très attention à ne pas vous mélanger les pinceaux dans l’écriture de ces chiffres ou lettres, car tous ces noms de registres arduino se ressemblent beaucoup ! Du coup, il est facile de faire des erreurs à ce niveau, si on ne fait pas assez attention. Qui plus est, chacun de ces registres n’est pas forcément un « copier-coller » des autres. Car il y a certaines « variations » au niveau des bits que les composent, et d’un timer à l’autre, ceux-ci peuvent vouloir dire des choses bien différentes !

Cela étant dit, voyons à présent le détail de tous ces registres, exposé bit à bit :

Registres de contrôle de l'ATmega328P avec TCCR0A, TCCR0B, TCCR1A, TCCR1B, TCCR1C, TCCR2A, et TCCR2B, pour prescaler des timer 0, 1, ou 2, par code

Ça fait peur, hein ? Rassurez-vous ! On ne se sert généralement que de quelques bits à la fois, sans se préoccuper du reste 😉

Alors… histoire de défricher un peu le terrain, commençons par des choses simples ! Et voyons ensemble les bits de contrôle des prédiviseurs (CSxx), permettant de choisir le rapport de fréquence sur chaque timer. Ce sont les bits :

  • CS02, CS01, et CS00, pour le timer0
  • CS12, CS11, et CS10, pour le timer1
  • CS22, CS21, et CS20, pour le timer2

Pour savoir quelle valeur vous devez mettre à l’intérieur, il suffit de vous reporter dans la partie « Prédiviseurs », présentée un peu plus haut. Mais attention : comme tous les timer ne sont pas identiques, vous n’aurez pas forcément les mêmes valeurs permises dans l’un, comparé à d’autres.

Ensuite viennent les registres WGMxx. Si je simplifie les choses au maximum, je vous dirais que les registres WGMxx permettent de définir « comment compter » au niveau des timer, et dans quel but ces derniers sont utilisés. Par exemple, dans les 2 exemples que vous trouverez en fin d’article, je vous montrerai 2 usages typiques de ces registres :

  • Le comptage « Normal » : le timer visé compte jusqu’à atteindre sa valeur maximale possible, puis envoie un signal de débordement (overflow), avant de revenir à zéro (et continuer à compter)
  • Le comptage « CTC » : là, le timer va compter tranquillement, jusqu’à déclencher une alerte « en cours de route » (levée de drapeau, ou « flag » en anglais), lorsque sa valeur courant sera égale à une valeur de référence qu’on aura préalablement définie (spécifiée dans un registre dit « de comparaison »). À ce moment-là, une interruption pourrait être générée, si souhaité. Et dans tous les cas, le timer, quant à lui, aura repris son comptage depuis zéro !

Voilà ! Je ne vais pas aller plus loin dans toutes les explications relatives à ces registres, tant il y aurait à dire ici. Et pour l’instant, je préfère que vous reteniez déjà cela, plutôt que de vous perdre sous des tonnes d’informations techniques !

Les registres drapeaux (TIFR0, TIFR1, TIFR2)

Le microcontrôleur ATmega328P contient en outre 3 « registres drapeaux » (ou flag, en anglais), permettant d’alerter toute personne intéressée sur le fait qu’il s’est produit un évènement particulier sur un timer. Ces registres portent les noms de :

  • TIFR0, pour le timer0
  • TIFR1, pour le timer1
  • TIFR2, pour le timer2

À l’intérieur de ces registres, on retrouve plusieurs « bits drapeaux » (flag), permettant notamment d’alerter sur le fait que :

  • La valeur d’un timer a atteint sa valeur maximale (overflow) : ce sont les bits TOV0, TOV1, et TOV2, correspondant respectivement aux timer 0, 1, et 2, qui passent à « 1 » dès lors qu’il y a débordement (overflow)
  • Ou la valeur d’un timer est égale à une valeur stockée dans un registre de comparaison (compare match) : ce sont les bits OCF0A et OCF0B pour le timer0, OCF1A et OCF1B pour le timer1, et OCF2A et OCF2B pour le timer2, qui permettent d’indiquer s’il y a eu égalité entre la valeur courant des timers, et leurs registres de comparaison respectifs

Voici d’ailleurs comment figurent ces bits drapeaux dans leurs registres 8 bits :

Registres drapeaux atmega328p de l'arduino uno ou nano TIFR0, TIFR1, et TIFR2, pour flag sur débordement overflow ou comparaison sur valeur dans code

Ces registres sont donc particulièrement intéressant lorsqu’il s’agit d’exécuter des actions particulières, dès lors qu’il se produit un évènement particulier sur un timer, et qu’on attendait.

Le saviez-vous ? lorsqu’on dit qu’un drapeau est levé, cela signifie simplement que son bit correspondant est passé à « 1 ». Mais attention : car dans un ATmega328P, il ne faudra pas essayer d’inscrire la valeur 0 par dessus, pour effacer ce bit à 1. En fait, et aussi surprenant que cela puisse paraître, il faudra envoyer la valeur 1 à ce bit, pour qu’il repasse à 0 ! Ne me demandez pas pourquoi… c’est le constructeur qui a lui-même fixé les règles, pour son microcontrôleur 😉

Les registres d’interruption (TIMSK0, TIMSK1, TIMSK2)

Les registres d’interruption (ou plutôt « masque d’interruption » ici) permettent de définir à l’avance, si l’on souhaite ou non déclencher une interruption particulière, au niveau d’un notre programme arduino, suivant la valeur précise d’un compteur. Au passage, un drapeau est levé, comme nous l’avons vu dans le point précédent.

Au niveau de l’ATmega328P, voici comment se présentent ces registres masques d’interruption :

Registres masques d'interruptions TIMSK0, TIMSK1, et TIMSK2 des timers de l'ATmega328P, bits de déclenchement après overflow ou compare valeur

Chacun de ces registres contient des bits, qui, s’ils sont mis à 1, définissent dans quelle condition une interruption est déclenchée. Par exemple (en remplacement « X » par le numéro du timer que vous visez) :

  • Si le bit TOVx est mis à 1, alors une interruption sera levée quand le timerX atteindra sa valeur maximale (en fait, le drapeau est levé en même temps que le timer repasse à zéro, après avoir essayé de dépasser son maximum)
  • Si le bit OCFxA est mis à 1, alors une interruption sera levée quand le timerX aura atteint une valeur égale à celle qu’on aura préalablement renseigné, et stocké dans le registre OCRxA
  • Si le bit OCFxB est mis à 1, alors une interruption sera levée quand le timerX aura atteint une valeur égale à celle qu’on aura préalablement renseigné, et stocké dans le registre OCRxB

Bien entendu, si on laisse tous ces bits à 0, il n’y aura pas la moindre interruption déclenchée. On pourra toutefois tester l’état des drapeaux vus précédemment, en testant manuellement et à intervalle régulier leur état. Bien évidemment, ceci est très énergivore au niveau d’un programme, c’est pourquoi il est souvent plus intéressant de passer par des interruptions, car ainsi, tout se fait en « automatique » !

Les registres de comparaison (OCRxx)

Pour compléter les deux points précédents, sur la partie drapeaux et interruptions sur « comparaison » (CTC, en anglais), sachez que l’ATmega328P dispose de plusieurs registres de comparaison. Ceux-ci permettent de stocker des valeurs qui serviront à effectuer des tests d’égalité avec la valeur courant des timer. Ces valeurs sont stockées dans les registres suivants :

  • OCR0A et OCR0B, pour le masque d’interruption TIMSK0 (timer0 donc)
  • OCR1A et OCR1B, pour le masque d’interruption TIMSK1 (timer1 donc)
  • OCR2A et OCR2B, pour le masque d’interruption TIMSK2 (timer2 donc)

À noter que le timer1 est différent des deux autres, puisqu’il fonctionne sur 16 bits au lieu de 8. Du coup, les registres huit bits de comparaison sont en fait OCR1AL, OCR1AH, OCR1BL, et OCR1BH (avec, pour rappel, les 8 bits de poids les plus forts dans les registres notés « H », et les 8 bits de poids les plus faibles dans les registres notés « L »). Par contre, au niveau de votre programme arduino, vous n’aurez pas du tout à vous soucier de tout cela, car la décomposition en 2 mots de 8 bits n’est pas nécessaire à ce niveau. En clair : vous pourrez directement entrer un nombre de 16 bits dans la variable OCR1x de votre code arduino, et le compilateur se chargera de répartir ces valeurs dans les deux registres 8 bits correspondants !

Bon… je pense qu’on va arrêter là pour cette introduction « théorique » sur les timer arduino, pour ne pas trop vous embrouiller l’esprit ! Maintenant, place à la pratique ! Car l’heure est venue de manipuler concrètement ces timer arduino, avec des exemples parlants, pour bien comprendre comment ils fonctionnent, et à quoi ils peuvent bien nous servir 😉

Exemple de code #1 : faire clignoter une LED à 1 Hz (en détectant les overflow)

Le but de l’exercice sera de faire clignoter une led à une fréquence de 1 Hz. C’est-à-dire qu’elle devra être allumée 500 ms, puis éteinte 500 ms, et répéter cela à l’infini. Et comme 500ms + 500ms fait bien 1 seconde, nous aurons bien nos 1 hertz de fréquence de clignotement, en procédant ainsi 😉

Ici, je vais utiliser un Arduino Uno. Celui-ci est équipé d’un microcontrôleur ATmega328P, cadencé à 16 MHz. C’est donc ces 16 MHz qu’il faudra « transformer » en 1 Hz, pour faire clignoter une LED à ce rythme. À ce propos, nous allons nous servir de la led embarquée sur l’arduino uno, typiquement branchée sur la sortie D13 de ces Arduino. Celle-ci est d’ailleurs accessible dans le code Arduino, sous l’appellation « LED_BUILTIN » (en sachant que le compilateur remplacera cette valeur par D13, si vous êtes sur un Arduino Uno, par exemple).

Mais comme vous vous en doutez, il faut avant tout diviser la fréquence principale de 16 MHz, pour générer une fréquence de 1 Hz au niveau de la LED. Pour ce faire, nous allons mettre en application tout ce que nous avons vu plus haut, à savoir :

  • L’utilisation du prescaler, pour diviser ces 16 MHz une première fois
  • L’utilisation d’un prédiviseur, pour diviser une deuxième fois la fréquence (issue du prescaler, cette fois-ci)
  • Et l’utilisation d’un timer pour compter un nombre précis de fois, pour arriver à nos 500 ms, vus plus haut (qui répétés deux fois, feront bien nos 1 Hz de fréquence de sortie)

Pour arriver à faire cela, il y a bien des façons de faire. Ici, je vais vous en présenter une, tout en sachant qu’il en existe bien d’autres ! Voici la méthode que je vous propose de faire dans ce premier exemple :

  • Nous allons régler le PRESCALER pour une division par 4. Ainsi, les 16 MHz du quartz qui cadence l’ATmega328P seront divisés par 4, ce qui nous donnera une fréquence de 4 MHz en sortie du prescaler (interne au µC ATmega328P, pour rappel)
  • Nous allons utiliser le TIMER1, car il a une grande résolution (16 bits). En effet, celui-ci peut compter de 0 à 65535 du fait qu’il compte sur 16 bits. Ceci nous donnera bien plus de « souplesse » que d’utiliser des timer 8 bits, comme le timer0 ou le timer2, qui eux ne peuvent compter que de 0 à 255.
  • Nous allons régler le PRÉDIVISEUR du timer1 pour une division par 64. Ainsi, la fréquence qui cadencera le timer1 sera 64 fois plus lente que celle sortant du prescaler, qui pour rappel était de 4 MHz. Ainsi, la fréquence d’avancement du timer1 sera de 4 MHz divisé par 64, soit 62500 Hz. Nota : à cette fréquence, le compteur mettra 16 µs pour avancer « d’un pas » (car 1 / 62500 Hz = 0,000016 seconde)

Or, comme nous avons besoin de faire changer l’état de la LED toutes les 0,5 secondes (500ms) et que le timer1 compte au rythme de 16 µs chaque pas, il faudra que le timer compte : 0,5 / 0,000016 = 31250 fois. Ainsi, on sait que si le timer1 a compté 31250 fois, il aura « parcouru » 500 ms.

Par contre, dans cet exemple, nous n’allons pas faire compter le timer1 de 0 à 31250, car j’aimerais vous montrer comment utiliser simplement « l’overflow ». Pour rappel, le basculement d’un timer s’opère quand son compteur essaye de dépasser sa valeur maximale (65536, dans le cas d’un timer 16 bits). Comme c’est impossible, le compteur revient immédiatement à 0, en levant au passage un drapeau, signalant le débordement (dépassement, ou overflow en anglais). Du coup, il nous suffit de poser l’opération suivante pour savoir qu’elle devra être la valeur de démarrage du timer1, à chaque « remise à zéro » : 65536 – 31250 = 34286.

Pour résumer : au lieu de faire compter le timer1 de 0 à 31250, nous allons compter de 34286 à 65536. Et comme 65536 n’est pas atteignable, c’est bien à ce moment là que le timer lèvera un drapeau, pour signaler le débordement du compteur. Et bien évidemment, dès que le compteur aura redémarré à 0, on lui remettra au plus vite la valeur 34286 dedans, pour qu’il recommence bien à compter à partir de ce nombre-là.

Au passage, il faudra également penser à rabaisser le drapeau signalant le débordement (il s’agit du bit TOV1, situé dans le registre TIFR1, pour ce timer1).

Bon, j’espère que j’ai perdu personne en cours de route… sinon, prenez le temps de simplement analyser le code qui suit, que j’ai essayé de commenter au maximum ! Ainsi, tout vous semblera plus clair 😉

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       1HzTimerSansInterruption.ino
  
  Description :   Permet de faire clignoter la LED embarquée sur un Arduino Uno, à une fréquence de 1 Hz, via le "timer1"
                  (c'est à dire la voir s'allumer 500ms, puis s'éteindre 500ms, en répétant cela à l'infini !)
                  Pour ce faire, on fera démarrer le timer à partir d'une certaine valeur, et on testera s'il y a débordement du timer à chaque bouclage du programme
                  
  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)

  Créé le :       22.05.2021
*/

void setup()  
{ 
  pinMode (LED_BUILTIN, OUTPUT);      // On définit comme étant une "sortie", la pin où est branché la LED sur l'Arduino Uno

  // Paramétrage du prescaler
  CLKPR = 0b10000000;                 // Tout d'abord, on active le prescaler (en mettant son "1er" bit à 1, et tous les autres à 0)
  CLKPR = 0b00000010;                 // Puis on inscrit le code "0000 0010", signifiant qu'on désire une division par 4 de la fréquence principale

  // Paramétrage du "mode de fonctionnement" du timer1 en "Normal"
  //    -> c'est à dire que le compteur timer1 va compter jusqu'à son maximum, puis revenir à 0, en levant un drapeau au passage (bit TOV1)
  //    -> pour activer ce mode, il faut mettre les bits WGM10, WGM11, WGM12, et WGM13 à 0 (nota : les 2 premiers se trouvent dans le registre TCCR1A, et les 2 autres, dans TCCR1B)
  //    -> pour ce faire, on va se servir de la fonction arduino "bitClear", qui permet de mettre à "0" un bit spécifique, dans un registre donné
  bitClear(TCCR1A, WGM10);
  bitClear(TCCR1A, WGM11);            // Nota : WGM10 et WGM11 se trouvent dans le registre TCCR1A
  bitClear(TCCR1B, WGM12);
  bitClear(TCCR1B, WGM13);            // tandis que WGM12 et WGM13 se trouvent dans le registre TCCR1B

  // Puis on paramètre le prédiviseur du timer1, pour qu'il divise la fréquence issue du prescaler par 64
  //    -> pour faire une division par 64, il faut mettre le bit CS12 à 0, CS11 à 1, et CS10 à 1 également (voir la partie sur les prédiviseurs, pour plus d'infos)
  //    -> et comme juste avant, on va se servir de la fonction "bitClear" pour mettre un bit à 0, et compléter cela avec la fonction "bitSet", pour mettre un autre bit à 1
  bitClear(TCCR1B, CS12);
  bitSet(TCCR1B, CS11);
  bitSet(TCCR1B, CS10);

  // Nota : ici, j'ai tout décomposé pour les "débutants". Mais sachez qu'on aurait pu directement renseigner les registres TCCR1A et TCCR1B, pour aller plus vite
  // Pour cela, il aurait suffit d'écrire les 2 lignes suivantes, pour renseigner ces deux registres :
  //              TCCR1A = 0b00000000;    // pour mettre WGM10 et WGM11 à 0 
  //              TCCR1B = 0b00000011;    // pour mettre WGM12 et WGM13 à 0, et activer la division par 64 


  // Enfin, on fait démarrer le timer1 non pas à 0, mais à la valeur 34286 (pour rappel, ce timer 16 bits compte jusqu'à 65535, puis revient à 0, en levant au passage le flag TOV1)
  TCNT1 = 34286;

} 
 
void loop()  
{ 
  // À chaque tour de boucle, on test si le bit drapeau "TOV1", contenu dans le registre TIFR1, est levé (c'est à dire à "1")
  // Ainsi, on saura que le timer1 a compté un nombre précis de fois (depuis nos "34286" initiaux), correspondant à nos 500 ms (qui alternés, font bien nos 1 Hz voulus)
  if (bitRead(TIFR1, TOV1) == 1)
  { 
    // Pour commencer, on force la valeur du compteur timer1 à "34286" (correspondant à nos 500 ms de délais), parce qu'il a déjà recommencé à compter depuis 0
    TCNT1 = 34286;  
    
    // On en profite pour rabaisser le drapeau ayant signalé l'overflow (bit TOV1), sinon il restera toujours à 1, et on se serait plus jamais averti
    bitSet(TIFR1,TOV1);       // Nota : chose très particulière ici, il faut renvoyer la valeur 1 sur le bit TOV1 pour qu'il passe à 0
                              //        (c'est le constructeur qui impose cette méthode d'acquittement)

    // Puis on inverse l'état de la LED (si elle était allumée, alors on l'éteint, et si elle était éteinte, alors on l'allume)
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  } 

  // Et... on boucle à l'infini ! 
}

Nota : vous le remarquerez certainement au bout de quelques minutes, les 1 Hz générés ne sont pas si précis que ça (comparé à un chronomètre, par exemple). Et pour cause : car on vérifie seulement de temps en temps si le timer a « débordé » (à chaque bouclage de fonction loop, en fait). Ainsi, il peut très bien s’écouler plusieurs cycles d’horloge, avant qu’on s’aperçoive que le drapeau était bien levé (provoquant ainsi un décalage). De même lorsqu’on remet la valeur « 34286 » dans le timer1, pour le faire recompter depuis ce nombre. En fait, celui-ci avait déjà repris son comptage depuis 0, suite au débordement, et du temps supplémentaire s’est donc déjà écoulé également ici. Et même si ces décalages paraissent infimes au début, ils finissent inéluctablement par devenir prépondérants (et donc perceptibles), au bout de quelques minutes. Du coup, si vous cherchez à faire une horloge précise, mieux vaut ne pas compter sur cette méthode là, pour y arriver !

Exemple de code #2 : faire clignoter une LED à 1 Hz (en utilisant les interruptions)

Pour ce deuxième exemple, on va changer quelque peu les choses. En synthèse, nous allons :

  • Utiliser un Arduino Uno (fonctionnant avec un microcontrôleur ATmega328P, cadencé à une fréquence de 16 MHz)
  • Utiliser le timer1 de l’ATmega328P (car étant sur 16 bits, il offre une plus grande « maniabilité » comparé aux autres timers arduino, qui sont sur 8 bits)
  • Régler le prédiviseur du timer1 sur 256, afin que le timer1 avance au rythme de 16 MHz divisé par 256, soit 62500 Hz. Ainsi, le compteur avancera d’une unité toutes les 16 µs (issu du calcul 1/62500=0,000016).
  • Programmer une interruption, qui se déclenchera à chaque fois que le timer atteint la valeur 31250 (car 31250 x 16µs = 0,5 seconde)
  • Et basculer l’état de la LED à chaque interruption, à savoir toutes ces 0,5 seconde. Ainsi, la LED sera allumée 500ms, puis éteinte 500ms, de telle sorte qu’on aura bien notre fréquence de clignotement à 1 Hz au final.

Dans cet exemple, nous allons utiliser le mode « CTC » de l’ATmega328P, c’est-à-dire le « Clear Timer on Compare Match ». Ainsi, dès que la valeur du compteur « timer1 » aura atteint une valeur égale à celle qu’on aura préalablement renseignée dans un registre de comparaison, alors ce compteur (timer1) sera remis à 0, et reprendra le comptage. En parallèle, une interruption sera générée, nous indiquant qu’il y a eu égalité, et remise à zéro.

En fait, comme vous allez voir juste après, tout cela est assez simple à coder, au final ! Car mise à part les réglages à faire au début (dans la fonction SETUP de l’arduino), tout est automatique, et ne comporte que très peu de ligne de code. En bref, vous verrez que c’est bien plus « simple » que l’exemple précédent 😉

En parlant du code, le voici :

/*
   ______               _                  _///_ _           _                   _
  /   _  \             (_)                |  ___| |         | |                 (_)
  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
                                                                                      | |
                                                                                      \_|
  Fichier :       1HzTimerAvecInterruption.ino
  
  Description :   Permet de faire clignoter la LED embarquée sur un Arduino Uno, à une fréquence de 1 Hz, via le "timer1"
                  (c'est à dire la voir s'allumer 500ms, puis s'éteindre 500ms, en répétant cela à l'infini !)
                  Pour ce faire, on utilisera une "interruption", se déclenchant à chaque fois que le timer aura une valeur égale à un registre particulier

  Auteur :        Jérôme TOMSKI (https://passionelectronique.fr/)

  Créé le :       23.05.2021
*/

// ========================
// Initialisation programme
// ========================
void setup () {
  // Déclaration de la pin D13 en sortie (c'est là où est branché la LED "onboard" de l'Arduino Uno)
  pinMode (LED_BUILTIN, OUTPUT);

  // ======================================================================================================================================================
  // Paramétrage du timer1, pour qu'il déclenche une interruption, à chaque fois que sa valeur sera égale à celle qu'on aura indiqué dans le registre OCR1A  
  // ======================================================================================================================================================
  noInterrupts();                 // On désactive les interruptions, pour commencer

  // On règle les bits WGM10, WGM11, WGM12, WGM13 pour fonctionner en mode "CTC" (c'est à dire, en mode "comparaison timer <-> valeur de référence")
  bitClear(TCCR1A, WGM10);        // On met le bit WGM10 à 0 (contenu dans le registre TCCR1A)
  bitClear(TCCR1A, WGM11);        // On met le bit WGM11 à 0 (contenu dans le registre TCCR1A)
  bitSet(TCCR1B, WGM12);          // On met le bit WGM12 à 1 (contenu dans le registre TCCR1B)
  bitClear(TCCR1B, WGM13);        // On met le bit WGM13 à 0 (contenu dans le registre TCCR1B)

  // Ensuite, on règle les bits CS10, CS11, et CS12 pour que le prédiviseur du timer1 fasse une division par 256
  bitClear(TCCR1B, CS10);         // On met le bit CS10 à 0 (contenu dans le registre TCCR1B)
  bitClear(TCCR1B, CS11);         // On met le bit CS11 à 0 (contenu dans le registre TCCR1B)
  bitSet(TCCR1B, CS12);           // On met le bit CS12 à 1 (contenu dans le registre TCCR1B)

  // Puis on active l'interruption du timer1, qui test en permanence s'il y a égalité entre la valeur courant du timer, et la valeur
  // stockée dans un registre de comparaison. En pratique, pour ce faire, on va mettre à 1 le bit OCIE1A dans le registre TIMSK1,
  // afin qu'une interruption soit générée, à chaque fois que la valeur du timer1 sera égale à la valeur qu'on aura renseigné dans le registre OCR1A
  bitSet(TIMSK1, OCIE1A);         // On met le bit OCIE1A à 1 (contenu dans le registre TIMSK1)

        /* Pour info, on aura pu simplifier l'écriture des lignes ci-dessus, en entrant directement la valeur de tous les bits à la fois, 
         * dans leurs registres correspondants. Pour cela, il aurait fallut écrire les lignes suivantes (au lieu de bitClear/bitSet) : 
         * 
         *      TCCR1A = 0b00000000;      // pour WGM11=0 et WGM10=0
         *      TCCR1B = 0b00001100;      // pour WGM12=1 et WGM13=0, puis CS12=1/CS11=0/CS10=0 pour prédiviseur réglé sur division par 256
         *      TIMSK1 = 0b00000010;      // pour OCIE1A=1, afin d'activer l'interruption par comparaison "A" (test d'égalité entre timer et valeur de registre OCIE1A)
         */


  // Enfin, on met le compteur à zéro, on entre la valeur déclenchant l'interruption (nos "31250" coups d'horloge), et on réactive les interruptions
  TCNT1 = 0;            // Mise du timer1 à zéro
  OCR1A = 31250;        // Valeur correspondant à 500ms (car 31250 fois 16 µS donne bien 500ms ; pour rappel, ces 16 µS proviennent du calcul 1/(16MHz/256),
                        // avec les 16 MHz correspondant à la fréquence du quartz de l'ATmega328P, et le 256 au réglage du prédiviseur du timer1 fait précédemment)
  interrupts();         // Et, on ré-active les interruptions
}


// ======================
// Routine d'interruption
// ======================
ISR(TIMER1_COMPA_vect) {
  // À chaque appel d'interruption, on inverse l'état de la LED
  // En bref : si elle était allumée, alors on l'éteint, et si elle était éteinte, alors on l'allume !
  digitalWrite (LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
 
// =================
// Boucle principale
// =================
void loop () {
  // Boucle infinie...
  // À noter qu'à chaque interruption, on quitte cette boucle pour aller exécuter le code ci-dessus (routine d'interruption), avant de revenir ici
}

Remarque : ce programme est bien plus précis que le précédent, d’un point de vu temporel. Car les interruptions prennent « immédiatement » le contrôle sur le programme en cours d’exécution (en interrompant la boucle loop, à n’importe quel moment). Mais pourtant, en faisant vos essais, vous constaterez qu’il se créé toutefois un petit décalage au bout de quelques minutes de fonctionnement (si vous comparez avec un chronomètre, par exemple). Pour compenser cela, il faudrait ajuster quelque peu la valeur de OCR1A, pour que le timing soit plus proche des 1 Hz souhaités. Dans tous les cas, ne vous attendez pas non plus à quelque chose d’ultra précis 😉

Timer Arduino : conclusion

Voilà ! J’espère que cette introduction aux timer arduino vous a plu, et que je ne vous ai pas perdu en cours de route ! J’espère aussi que ces exemples, même s’ils ne sont pas vraiment d’une utilité particulière, vous auront aidé à mieux comprendre la « mécanique interne » de l’ATmega328P, et comment manipuler les timers.

Au passage : si vous relevez la moindre coquille dans cet article, n’hésitez pas à m’en faire part dans la zone commentaire présente ci-dessous ! Car nombreux étaient les registres et les bits à détailler, et il se peut qu’il se soit glissé des erreurs, au moment où j’ai rédigé cet article.

Enfin, si vous avez la moindre question au sujet des timer arduino, n’hésitez pas à les poser juste en dessous ! J’y répondrai dès que je pourrais, et bien entendu, dans la limite du temps dont je dispose, et dans la limite de mes connaissances (car je ne suis pas un expert, attention !). Dans tous les cas, je vous dis à très vite 😉

Jérôme.

À découvrir aussi : Tous les autres articles autour de l’Arduino, publiés sur ce site !

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

(*) Mis à jour le 24/05/2021

21 commentaires sur “Timer Arduino : introduction, explications, et exemples de code concrets (microcontrôleur ATmega328P)”

  1. Site passionelectronique.fr

    Merci pour cette aide. Vos tableaux des registres sont SUBLIMES et le texte rend limpide les sujets abordés. Mais après une gestion d’énergie secteur et solaire par PI4, RF24Network et arduino(s), je rêve d’un onduleur autonome ou pouvant injecter dans le réseau. Pour cela il me faut deux sorties PWM de 50*128 Hz chaque impulsion étant différente de la précédente (tableau sinus ou ADC de la tension réseau). Je compte sur trois interruptions, deux A et B pour PWM (commute les sorties) et une dépassement timer (restaure les sorties, recharge le timer pour choisir la fréquence PWM avec précision, recharge les comparateurs A et B en évitant analogWrite j’espère être plus rapide (ADC dans loop). Où peut on trouver la liste des vecteurs d’interruption j’ai essayé ISR(TIMER12_OVF_vect), çà compile sans erreurs (12 c’est beaucoup pour un nano). Cette approche vous parait elle réaliste ?

    1. Site passionelectronique.fr

      Salut Jacques !

      Tout d’abord merci à toi, pour ce commentaire élogieux 😉

      Alors, avant de te répondre sur la partie timers/interruptions, une petite parenthèse concernant les onduleurs : ceux qui sont prévus pour injecter dans le réseau doivent répondre à une norme VDE, assurant leur déconnexion automatique en cas de perte du signal EDF. Le non respect de cette norme entraînera un risque d’électrocution bien réel. Soit donc extrêmement prudent si tu prévois de réaliser un tel dispositif, afin que personne ne soit mis en danger. Car les conséquences pourraient être très graves, en cas d’erreur.

      Du reste, concernant la liste des vecteurs d’interruption, il y en a 26 à ma connaissance. Tu peux d’ailleurs les retrouver à la page 49 du datasheet de l’ATmega328P équipant l’Arduino Nano, disponible ici : https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf. Comme tu le verras, il n’y a aucun vecteur « 12 » ici, sauf erreur de ma part.

      Dans ton cas, tu pourrais par contre utiliser :

      • TIMER0_OVF_vect, TIMER1_OVF_vect, et TIMER2_OVF_vect (pour les interruptions en overflow)
      • TIMER0_COMPA_vect, TIMER0_COMPB_vect, TIMER1_COMPA_vect, TIMER1_COMPB_vect, TIMER2_COMPA_vect, et TIMER2_COMPB_vect (pour les interruptions sur registres de comparaison A et B)

      Voilà !
      A+, Jérôme.

  2. Site passionelectronique.fr

    J’ai tardé à vous remercier car j’ai osé attendre que ça marche : et ça marche. Donc un très grand merci. La page 49 donne toute la syntaxe qui me manquait. J’ai longtemps galéré avec les bits WGM21…2…3. Quand on mélange différent exemples, il est difficile de savoir ce qui reste utile dans son cas. Pour la partie sécurité, je confirme qu’il est interdit d’électrocuter un pompier ou un électricien qui aurait honnêtement coupé le disjoncteur et qu’au delà de 24V le danger est réel. Il me reste à attaquer les mos et les tores ferrite pour paramétrer mes interruptions timer. Arduino est une belle petite machine ! Dommage que sur https://www.arduino.cc/reference/en/ on ne trouve pas des mots comme timer ou watchdog. A+, c’est sûr, car j’ai 5 nano dans mon réseau RF24Network qui refusent de marcher tous ensembles. Encore merci.

    Jacques

    1. Site passionelectronique.fr

      Salut Serge !

      Pas dans l’immédiat, désolé. Car je n’ai déjà pas suffisamment de temps pour continuer à faire avancer tous mes projets en cours …
      Encore désolé.

      Jérôme.

  3. Site passionelectronique.fr

    Ayant lu différents articles sur le sujet des Timers, je me suis perdu dans sa compréhension car tout n’était pas clair dans les explications chaotique du sujet.
    J’en félicite l’auteur de celui ci qui m’a bien fait comprendre la méthode et l’usage des Timers, avec une approche simple et parfaitement compréhensible pour un novice comme moi
    Si je devrais attribué une note ce serait 10/10

  4. Site passionelectronique.fr

    Bonjour,

    Waw, que dire c’est super intéressant pour moi qui travaille sur Arduino Uno et qui n’y connaissais rien !

    Merci !

    Dommage que tu n’évoques pas les modes « Fast » et « Phase correct PWM » dont tu parles dans ton article « PWM arduino : explications (broches de sortie, rapport cyclique, fréquences, modes), avec exemples de code pratiques ! ». On retrouve ces modes dans la datasheet de ATmega328P.

    Cela mis à part, c’est parfait pour s’initier au timer.

    Simon

  5. Site passionelectronique.fr

    Bonjour Jérôme,

    Magnifique Tuto, clair, précis et bien détaillé.

    J’ai utilisé le Timer 1 (paramétré selon mes besoins) pour la gestion PWM de deux voies de modélisme ferroviaires.
    Tout fonctionne très bien mais l’on me demande d’ajouter des servomoteurs pour la gestion d’équipements auxiliaires.

    Mes questions sont :
    – Si j’utilise la librairie SERVO qui elle-même utilise le Timer 1 quel sera l’interaction entre mon code et la librairie ?
    – Et en général quel est l’interaction entre un code personnel utilisant un Timer et une librairie utilisant ce même Timer.

    Cordialement

    1. Site passionelectronique.fr

      Bonsoir Jean-Pierre !

      Difficile de te donner une réponse précise ici, car tout cela dépend des parties de ton code, qui touchent aux registres du Timer1. Et de quand tu modifies ceux-ci, vis à vis de la même action faite par les librairies en elles-mêmes.

      Dans ton cas, pour parler plus concrètement : si tu modifies les registres du Timer1, alors le signal produit par la librairie Servo risque déjà de ne plus faire 20 millisecondes de période (ce qui est pourtant indispensable, pour piloter tout servomoteur usuel). En résulterait alors un blocage du servomoteur, et potentiellement des mouvements « anormaux ».

      Plus généralement, si tu souhaites éviter toute interaction entre ton code et une librairie donnée, tu n’auras pas d’autre choix que d’aller examiner le code détaillé de cette librairie (sur GitHub, par exemple), et voir les registres qu’elle impacte. Ainsi, du sauras ce à quoi elle touche, et pourra déterminer les impacts que ceux-ci pourrait avoir sur ton code. Bien évidemment, autant te dire que tout cela serait fastidieux à faire. C’est pourquoi on évite tout simplement de modifier les registres d’un timer donné dans son code, dès lors qu’une librairie importée repose déjà dessus (ou d’ajouter à son code une librairie qui touchera aux registres dont on se sert, comme c’est dans ton cas).

      En espérant que ma réponse puisse t’aider !

      Bonne soirée à toi.
      Jérôme.

  6. Site passionelectronique.fr

    Bonjour, superbe explication, beaucoup plus digeste que la datasheet atmegaXXX. Cela va m’aider a essayer de peaufiner un compteur qui me permet de générer un signal carré a fréquence variable, comprise entre 0 et 3400 Hz.
    Merci.

Laisser un commentaire

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

Symbole danger point d'exclamation site PSE, triangle jaune avec contour noir, texte noir alerte au milieuAfin de filtrer au maximum les messages de type "spam" ou "inappropriés", chaque commentaire est soumis à modération, et validé manuellement. Du coup, il se peut que certains commentaires ne soient pas publiés, ou sinon, avec un peu de retard. Par ailleurs, j'ai malheureusement plus de messages à traiter que de temps pour y répondre ; c'est pourquoi je ne pourrais pas répondre à tout le monde. Désolé …

Étiquettes: