Différences entre versions de « Atmega328 timers »

De The Linux Craftsman
Aller à la navigation Aller à la recherche
(Page créée avec « = 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 just... »)
 
 
(14 versions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
 
= Introduction =
 
= 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 !
 
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 =
 
= Fonctionnement =
== Les différents timers ==
+
Voila un diagramme de fonctionnement des timers:
Il existe 3 timers dans l'ATmega328:
+
[[Fichier:Atmega328 timer diagram.png|centré]]
 +
= Les différents timers =
 +
{|
 +
|-
 +
|valign="top" width="40%"|
 +
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.
 +
||
 
{|align="center" border="1"
 
{|align="center" border="1"
 
|-
 
|-
Ligne 49 : Ligne 58 :
 
* Overflow
 
* Overflow
 
|}
 
|}
Chaque timer s'incrémente à chaque fois que l'on à une impulsion du quartz. Sur un ATmega328, le quartz est cadencé à 16mHz, ce qui nous fait un incrément toutes les '''62,5ns''' (1/16.000.000).
+
|}
 +
= 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.
 +
 
 +
{|align="center" border="1"
 +
|-
 +
|width="20%"|
 +
|width="20%" align="center"|
 +
Compare Match
 +
|width="20%" align="center"|
 +
Timer OVerflow
 +
|width="20%" align="center"|
 +
Input Capture
 +
|-
 +
|align="center"|
 +
Timer0
 +
|align="center"|
 +
*TIMER0_COMPA
 +
*TIMER0_COMPB
 +
|align="center"|
 +
TIMER0_OVF
 +
|align="center"|
 +
n/a
 +
|-
 +
|align="center"|
 +
Timer1
 +
|align="center"|
 +
*TIMER1_COMPA
 +
*TIMER1_COMPB
 +
|align="center"|
 +
TIMER1_OVF
 +
|align="center"|
 +
TIMER1_CAPT
 +
|-
 +
|align="center"|
 +
Timer2
 +
|align="center"|
 +
*TIMER2_COMPA
 +
*TIMER2_COMPB
 +
|align="center"|
 +
TIMER2_OVF
 +
|align="center"|
 +
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:
 +
<source lang="c">
 +
ISR(TIMER1_COMPA_vect){
 +
  // code...
 +
}
 +
</source>
  
Faites attention, en modifiant un timer, à ne pas utiliser des librairies ou sortie PWM dépendantes du même timer.
+
= Le prescaler =
== Le prescaler ==
 
 
Si on fait le calcul en l'état:
 
Si on fait le calcul en l'état:
* les timers 0 et 2 ne pourrons compter qu'un temps de 16µs (62,5ns * 256);
+
* 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 ne pourra compter qu'un temps de 4,096ms (62,5ns * 65536);
+
* 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:
 +
[[Fichier:TCCR0B atmega328.png|centré|750px]]
 +
{|align="center" border="1"
 +
|-
 +
|valign="top" width="10%" align="center"|
 +
'''CS02'''
 +
|valign="top" width="10%" align="center"|
 +
'''CS01'''
 +
|valign="top" width="10%" align="center"|
 +
'''CS00'''
 +
|valign="top" width="60%" align="center"|
 +
'''Description'''
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Pas de signal d'horloge (Timer et compteur stoppés)
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal d'horloge brute (pas de pré-échelonnage)
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top""|
 +
Signal / 8
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal / 64
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Signal / 256
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal / 1024
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Horloge externe sur la broche T0 avec signal descendant (''falling edge'')
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" |
 +
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:
 +
[[Fichier:TCCR1B atmega328.png|centré|750px]]
 +
{|align="center" border="1"
 +
|-
 +
|valign="top" width="10%" align="center"|
 +
'''CS12'''
 +
|valign="top" width="10%" align="center"|
 +
'''CS11'''
 +
|valign="top" width="10%" align="center"|
 +
'''CS10'''
 +
|valign="top" width="60%" align="center"|
 +
'''Description'''
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Pas de signal d'horloge (Timer et compteur stoppés)
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal d'horloge brute (pas de pré-échelonnage)
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top""|
 +
Signal / 8
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal / 64
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Signal / 256
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal / 1024
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Horloge externe sur la broche T1 avec signal descendant (''falling edge'')
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" |
 +
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:
 +
[[Fichier:TCCR2B atmega328.png|centré|750px]]
 +
{|align="center" border="1"
 +
|-
 +
|valign="top" width="10%" align="center"|
 +
'''CS22'''
 +
|valign="top" width="10%" align="center"|
 +
'''CS21'''
 +
|valign="top" width="10%" align="center"|
 +
'''CS20'''
 +
|valign="top" width="60%" align="center"|
 +
'''Description'''
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Pas de signal d'horloge (Timer et compteur stoppés)
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal d'horloge brute (pas de pré-échelonnage)
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top""|
 +
Signal / 8
 +
|-
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal / 32
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Signal / 64
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal / 128
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
0
 +
|valign="top"|
 +
Signal / 256
 +
|-
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top" align="center"|
 +
1
 +
|valign="top"|
 +
Signal / 1024
 +
|}
  
Cela n'est pas très pratique surtout si l'on veut compter des valeurs plus grande que 4ms ! Pour pouvoir attendre un temps plus long il suffit de diviser la base de temps, c'est à dire, de ne pas incrémenter le timer à tous les ticks d'horloge mais tous les 8 ou 16 ticks par exemple. La modification de la base de temps de l'incrément est rendu possible grâce au prescaler.
+
= Exemple=
 +
Ci-dessous un code qui permet de faire clignoter la led présente sur la broche 13 toutes les secondes:
 +
<source lang="c">
 +
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);
 +
}
  
== Les interruptions ==
+
void loop(){
 +
}
 +
</source>

Version actuelle datée du 15 novembre 2018 à 11:32

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:

Atmega328 timer diagram.png

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.

Nom

Taille

Utilisation

Interruptions

Timer0

8 bits

  • delay(), millis(), micros()
  • analogWrite() sur les broches 5 et 6 (PWM)
  • Compare Match
  • Overflow

Timer1

16 bits

  • Servo
  • analogWrite() sur les broches 9 et 10 (PWM)
  • Compare Match
  • Overflow
  • Input Capture

Timer2

8 bits

  • Tone()
  • analogWrite() sur les broches 3 et 11 (PWM)
  • Compare Match
  • Overflow

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_COMPA
  • TIMER0_COMPB

TIMER0_OVF

n/a

Timer1

  • TIMER1_COMPA
  • TIMER1_COMPB

TIMER1_OVF

TIMER1_CAPT

Timer2

  • TIMER2_COMPA
  • TIMER2_COMPB

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:

TCCR0B atmega328.png

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:

TCCR1B atmega328.png

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:

TCCR2B atmega328.png

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(){
}