Arduino : gestion des temporisations

La question que l’on se pose dans cet article est de pouvoir générer une interruption qui se déclenche de manière précise après un laps de temps bien défini. Pour ce faire, il va falloir entrer plus en détail dans la gestion des registres timer du microcontrôleur équipant les cartes Arduino.

Prenons comme exemple, qu’il nous faut faire une action bien précise toutes les millisecondes. Pour ce faire, utilisons le Timer 2 qui équipe notre ATMEGA328 et apprenons à configurer ses registres.

Le principe consiste à incrémenter la valeur du registre du timer à un intervalle régulier géré par une horloge et à générer une interruption chaque fois que ce registre déborde. (Chaque fois qu’il atteint la valeur hexadécimale de FF). A ce stade, nous n’avons plus qu’à recharger le registre du timer avec une valeur bien précise afin que l’interruption se déclenche toutes les millisecondes. Il faut encore ajouter un lien vers la routine de gestion de l’interruption dans la table des vecteurs d’interruptions.

Un peu de code...

Avant d’entrer dans le vif du sujet, il peut être intéressant de passer un peu de temps à lire le chapitre 17 de la datasheet du microcontrôleur ATMEGA328.

unsigned int tcnt2; // valeur de recharge du Timer2
int etat = 0; //
//Configuration et enclenchement de l’interruption de débordement du Timer 2
void setup() {
// on interdit le declenchement de l’interruption pendant la configuration
TIMSK2 &= ~(1<<TOIE2);
//Configuration de timer2 en mode compteur pur
TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
TCCR2B &= ~(1<<WGM22);
// choix de la clock : interne
ASSR &= ~(1<<AS2);
// On inhibe l’interruption Compare Match A : on ne veut que le débordement
TIMSK2 &= ~(1<<OCIE2A);
// Configuration du prescaler pour diviser l’horloge CPU par 128
TCCR2B |= (1<<CS22) | (1<<CS20);
TCCR2B &= ~(1<<CS21);
// Calcul de la valeur à précharger dans le compteur du Timer
//(Fréquence CPU ) / (valeur prescaler) = 125000 Hz = 8us.
//(Période désirée) / 8us = 125.
//256 - 125 = 131;
// On sauve la valeur pour usage ultérieur
tcnt2 = 131;
//on précharge la valeur et on autorise l’interruption
TCNT2 = tcnt2;
TIMSK2 |= (1<<TOIE2);
}
// configuration de l’ISR(Interrupt service Routine)
ISR(TIMER2_OVF_vect) {
// on recharge le timer
TCNT2 = tcnt2;
// on fait commuter la pin2 pour pouvoir mesurer la milliseconde
digitalWrite(2, etat == 0 ? HIGH : LOW);
etat = ~etat;
}
// Boucle principale … vide mais nécessaire
void loop() {
}

Les explications dans le code doivent être suffisamment claires pour tout comprendre mais nous allons quand même un peu expliciter les calculs réalisés.

Tout d’abord, le lecteur attentif aura remarqué que le code n’est valable que pour unCPU fonctionnant à 16 MHz vu que les calculs sont basés sur cette fréquence. De plus, en utilisant la fonctionnalité du prescaler (prédiviseur), on peut légèrement augmenter le temps écoulé entre deux incrémentations du compteur. Il permet dans notre code d’incrémenter le compteur qu’une fois tous les 128 coups d’horloge (ou encore, tous les 8 µsec pour un CPU tournant à 16 MHz). Maintenant que nous connaissons le temps consommé par chaque incrémentation du compteur, nous pouvons calculer le nombre d’incrémentations nécessaires pour obtenir 1 milliseconde. Il faut donc diviser 1 milliseconde par 8 µsec, ce qui nous donne 125 incrémentations du compteur. Enfin sachant que le compteur est un compteur 8 bit, nous savons qu’il débordera chaque fois qu’il comptera jusqu’à 256. Il faut donc le charger avec une valeur de 131 (256-125) pour obtenir un débordement toutes les millisecondes.

Réalisation : LaboElectronique.be