Atmega328 timers
Introduction
Lorsque l'on veut qu'un événement se produise à un intervalle régulier, il est tentant d'utiliser la fonction delay() mais cette dernière va juste mettre en pause le programme. Cela revient à gâcher la puissance de calcul de l'ATmega328 qui passe la plupart de son temps à ne rien faire !
Fonctionnement
Voila un diagramme de fonctionnement des timers:
Les différents timers
Il existe 3 timers/compteur dans l'ATmega328 qui s'incrémente à chaque fois que l'on à une impulsion du quartz (tick). Sur un ATmega328, le quartz est cadencé à 16mHz, ce qui nous fait un incrément toutes les 62,5ns (1/16.000.000MHz). On va pouvoir rendre un programme conscient du temps qui passe en programmant des actions qui se déclenche périodiquement (interruption interne) ou en fonction d'événement externe (interruption externe). Faites attention, en modifiant un timer, à ne pas utiliser des librairies ou sortie PWM dépendantes du même timer. |
|
Les registres
- TCCRx → Timer/Counter Control Register, c'est ici que l'on règle le prescaler;
- TCNTx → Timer/Counter Register, permet de stocker la valeur actuelle du timer;
- OCRx → Output Compare Register, permet de stocker la valeur de comparaison du timer;
- ICRx → Input Capture Register, juste pour le timer1;
- TIMSKx → Timer/Counter Interrupt Mask Register, permet d'activer les interruptions du timer;
- TIFRx → Timer/Counter Interrupt Flag Register, indique si une interruption due au timer est survenue.
Les interruptions
Voici à quoi correspondent les différentes interruptions:
- Compare Match → quand le timer atteint une certaine valeur, définie dans le registre de comparaison OCRXA/B, une interruption est générée. Cette interruption peut servir à générer des signaux PWM spécifiques ou déclencher des événements à intervalles précis.
- Timer OVerflow → quand le timer arrive à sa valeur maximale, il recommence à compter à partir de zéro et une interruption est générée 0. Cette interruption peut servir à générer des signaux PWM spécifiques ou déclencher des événements à intervalles précis.
- Input Capture → quand une broche spécifique (ICP1) change d'état, la valeur courante du timer (ICR1) est copiée dans le registre ICR1 pour une consultation ultérieur. Cela permet de savoir quand à eu lieu le dernier changement pour ainsi mesurer le temps entre chaque impulsion.
Compare Match |
Timer OVerflow |
Input Capture | |
Timer0 |
|
TIMER0_OVF |
n/a |
Timer1 |
|
TIMER1_OVF |
TIMER1_CAPT |
Timer2 |
|
TIMER2_OVF |
n/a |
Pour exécuter du code sur une ISR, il suffit d'appeler la fonction d'utiliser le vecteur associé. Par exemple, pour l'interruption TIMER1_COMPA:
ISR(TIMER1_COMPA_vect){
// code...
}
Le prescaler
Si on fait le calcul en l'état:
- les timers 0 et 2, qui sont sur 8 bits, ne pourrons compter qu'un temps de 16µs (62,5ns * 256);
- le timer 1, qui est sur 16 bits, ne pourra compter qu'un temps de 4,096ms (62,5ns * 65536);
Cela n'est pas très pratique surtout si l'on veut compter des valeurs plus grande que 4ms ! Pour pouvoir attendre plus longtemps il suffit de diviser la base de temps, c'est à dire, de ne pas incrémenter la valeur du timer à tous les ticks d'horloge. La modification de la base de temps de l'incrément est rendu possible grâce au prescaler qui permet un incrément tous les 8, 64, 128, 256 ou 1024 ticks.
La modification du prescaler sur le Timer0 se fait grâce aux registres suivants:
CS02 |
CS01 |
CS00 |
Description |
0 |
0 |
0 |
Pas de signal d'horloge (Timer et compteur stoppés) |
0 |
0 |
1 |
Signal d'horloge brute (pas de pré-échelonnage) |
0 |
1 |
0 |
Signal / 8 |
0 |
1 |
1 |
Signal / 64 |
1 |
0 |
0 |
Signal / 256 |
1 |
0 |
1 |
Signal / 1024 |
1 |
1 |
0 |
Horloge externe sur la broche T0 avec signal descendant (falling edge) |
1 |
1 |
1 |
Horloge externe sur la broche T0 avec signal montant (rising edge) |
La modification du prescaler sur le Timer1 se fait grâce aux registres suivants:
CS12 |
CS11 |
CS10 |
Description |
0 |
0 |
0 |
Pas de signal d'horloge (Timer et compteur stoppés) |
0 |
0 |
1 |
Signal d'horloge brute (pas de pré-échelonnage) |
0 |
1 |
0 |
Signal / 8 |
0 |
1 |
1 |
Signal / 64 |
1 |
0 |
0 |
Signal / 256 |
1 |
0 |
1 |
Signal / 1024 |
1 |
1 |
0 |
Horloge externe sur la broche T1 avec signal descendant (falling edge) |
1 |
1 |
1 |
Horloge externe sur la broche T1 avec signal montant (rising edge) |
La modification du prescaler sur le Timer2 se fait grâce aux registres suivants:
CS22 |
CS21 |
CS20 |
Description |
0 |
0 |
0 |
Pas de signal d'horloge (Timer et compteur stoppés) |
0 |
0 |
1 |
Signal d'horloge brute (pas de pré-échelonnage) |
0 |
1 |
0 |
Signal / 8 |
0 |
1 |
1 |
Signal / 32 |
1 |
0 |
0 |
Signal / 64 |
1 |
0 |
1 |
Signal / 128 |
1 |
1 |
0 |
Signal / 256 |
1 |
1 |
1 |
Signal / 1024 |
Exemple
Ci-dessous un code qui permet de faire clignoter la led présente sur la broche 13 toutes les secondes:
void setup(){
Serial.begin(9600);
// Broche 13 en sortie
DDRB |= (1<<PB5);
// Désactivation des interruptions (clear interrupts)
cli();
// Remise à zéro du prescaler du timer 1
TCCR1A = 0;
TCCR1B = 0;
// Remise à zéro de la valeur du compteur
TCNT1 = 0;
// Positionnement de la valeur du registre de comparaison
// 16MHz / 256 = 2Hz
OCR1A = 31250;
// Activation du comparateur pour le timer 1
TCCR1B |= (1 << WGM12);
// Prescaler de 256 sur le timer 1
TCCR1B |= (1 << CS12);
// Activation des interruptions du timer 1
TIMSK1 |= (1 << OCIE1A);
// Activation des interruptions globales (Set External Interrupt)
sei();
}
// Routine exécutée par le vecteur d'interruption du comparateur A du timer 1
ISR(TIMER1_COMPA_vect){
// Bascule l'état de la broche 13
PORTB ^= (1<<PB5);
}
void loop(){
}