Aller au contenu

Micro contrôleurs AVR/Le Timer 0

Leçons de niveau 14
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du chapitre
Le Timer 0
Icône de la faculté
Chapitre no 4
Leçon : Micro contrôleurs AVR
Chap. préc. :Les PORTs
Chap. suiv. :Le Timer 1
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Micro contrôleurs AVR : Le Timer 0
Micro contrôleurs AVR/Le Timer 0
 », n'a pu être restituée correctement ci-dessus.

Présentation du timer 0

[modifier | modifier le wikicode]

Le timer 0 est un compteur sur 8 bits qui peut être commandé par l'horloge du processeur (quartz) ou par un bit externe. Nous allons présenter de manière graphique le fonctionnement du timer 0.

Documentation pour l'ATMega8

[modifier | modifier le wikicode]

Voici présenté de manière schématique le fonctionnement du timer0.

Documentation du timer 0 de l'ATMega8

Les conventions de cette figure ne sont pas universelles et ne correspondent pas à la documentation officielle. Donc pas de problème de copyright.

Le cœur du timer 0 est un compteur sur 8 bits (c'est précisé dans le dessin) en haut à gauche de la figure appelé TCNT0. Comme tout compteur il nécessite une horloge. Celle-ci peut être

  • soit une entrée externe (bit b4 du PORTD couramment appelé T0,
  • soit l'horloge du processeur (quartz externe ou horloge interne) qui est traitée par un diviseur. Ce diviseur est appelé prescaler en anglais et on le nommera ainsi dans la suite.

Un bit d'un registre TIFR est appelé TOV0 (Timer overflow 0 = débordement du timer 0). Ce bit est automatiquement positionné à 1 lors du débordement du timer 0, c'est-à-dire pour un passage de 0xFF à 0x00.

Le registre TCCR0 gère le prescaler à l'aide de trois bits CS02, CS01, CS00. La suite de ce qui est marqué dans le dessin correspond à la suite binaire sur ces trois bits. Par exemple vous en déduisez qu'une division par 1024 est réalisée par le nombre binaire 101...

Documentation pour l'ATMega328

[modifier | modifier le wikicode]

Depuis la version de l'ATMega8, le timer 0 même s'il est resté sur 8 bits s'est progressivement étoffé de fonctionnalités additionnelles.

Documentation du timer 0 de l'ATMega328

Une comparaison rapide entre les deux documentations vous montre que le registre TIFR de l'ATMega8 s'est transformé en registre TIFR0 et que le registre TCCR0 s'est transformé en TCCR0B. Il y a eu d'autres modifications qui seront présentées au fur et à mesure.

Mesure du temps d'exécution d'un algorithme

[modifier | modifier le wikicode]

L'optimisation d'un algorithme en vitesse (ou en taille) est très importante dans les systèmes embarqués réalisés par des micro-contrôleurs. Une recherche d'algorithmes sur Internet vous donnera des résultats qu’il vous faudra évaluer. Par exemple, le site : convert base vous propose un algorithme de division par 10 que voici :

unsigned int A;
unsigned int Q; /* the quotient */
        Q = ((A >> 1) + A) >> 1; /* Q = A*0.11 */
        Q = ((Q >> 4) + Q)     ; /* Q = A*0.110011 */
        Q = ((Q >> 8) + Q) >> 3; /* Q = A*0.00011001100110011 */
        /* either Q = A/10 or Q+1 = A/10 for all A < 534,890 */

1°) Sans chercher à comprendre l'algorithme de division, on vous demande de le transformer en une fonction unsigned int div10(unsigned int A);

2°) Les LEDs d'un shield maison sont couplés à l'arduino MEGA2560. Une lecture de son schéma fait apparaître la correspondance :

Numéro f5 f4 f3 f2 f1 f0 p1 p0
Couleur r o v r o v v r
Arduino Pin 13 12 11 10 9 8 7 6
Port Arduino UNO PB5 PB4 PB3 PB2 PB1 PB0 PD7 PD6
Port Arduino LEONARDO PC7 PD6 PB7 PB6 PB5 PB4 PE6 PD7
Port Arduino MEGA2560 PB7 PB6 PB5 PB4 PH6 PH5 PH4 PH3

Écrire un sous-programme capable d'afficher un nombre sur 8 bits sur les LEDs

3°) Écrire un programme complet qui mesure le temps d'exécution du sous programme de division par 10, puis modifier le programme pour qu’il puisse comparer avec une division par 10 normale.

Le mode de scrutation du flag

[modifier | modifier le wikicode]

Nous devons savoir à ce niveau, que tout débordement du timer0 (passage de 0xFF à 0x00) entraîne le positionnement du flag TOV0, bit b0 du registre TIFR0. Vous pouvez donc utiliser ce flag pour déterminer si vous avez eu débordement du timer0, ou, en d’autres termes, si le temps programmé est écoulé. Cette méthode à l’inconvénient de vous faire perdre du temps inutilement dans une boucle d'attente.

Petit exemple d'attente passive :

  while ((TIFR0 & 0x01) == 0); //attente passive

ou encore pour plus de lisibilité sur les bits manipulés :

  while ((TIFR0 & 1<<T0V0) == 0); //attente passive
Panneau d’avertissement Le drapeau TOV0 ne se remet pas tout seul à 0... et il faut écrire un 1 pour le remettre à 0 !!!

Le quartz est choisi à 4MHz dans ce problème.

On donne le programme suivant concernant le timer 0 :

 int main(void){
 // initialisation du timer   division par 8 
         TCCR0B = 0x02; // prescaler 8 , entrée sur quartz 
         TCNT0 = 0x00; //   tmr0  : début du comptage dans 2 cycles 
 // bit RB0 du PORTB en sortie
         DDRB |= 0x01; //RB0 as output
         while(1) {
                 while ((TIFR & (1<<TOV0)) == 0);
                 // ce qui est fait ici est fait tous les 256 comptages de TCNT0
                 PORTB ^= 0x01; // on bascule avec ou exclusif
                 TIFR0 |= 0x01; // clr TOV0 with 1 : obligatoire !!!
                 // TIFR0 |= (1<<TOV0); // fait le même chose
         }
         return 0;
 }

Pouvez-vous donner la fréquence d'oscillation du bit b0 du PORTB avec quelques explications ?

Écrire en langage C un programme qui fait la même chose que le programme ci-dessus : initialise le timer0, efface le flag et attend à l'aide d'une boucle le positionnement de ce dernier mais 100 incrémentations seulement. On vous demande aussi d’utiliser au mieux les déclarations du fichier d'inclusion avr/io.h :

 /* TIFR0 */ 
 #define OCF2	7 
 #define TOV2	6 
 #define ICF1	5 
 #define OCF1A	4 
 #define OCF1B	3 
 #define TOV1	2 
 /* bit 1 reserved (OCF0?) */ 
 #define TOV0	0
 //***** plein de lignes ici mais cachées *****
 /* TCCR0 */ 
 /* bits 7-3 reserved */ 
 #define CS02	2 
 #define CS01	1 
 #define CS00	0

L'utilisation des valeurs prédéfinies permet de rendre vos programmes un peu plus lisibles.

Générer un signal de fréquence 1 kHz. Pour cela :

  • calculer la valeur de la pré division
  • calculer la valeur de décomptage
  • Écrire le programme.

Générer un signal de sortie de rapport cyclique 1/4 sur le même principe.

Il y a mieux à faire avec les AVRs, utiliser le module CCP (détaillé plus loin).

Mettre en œuvre le timer0 sur le MEGA2560 avec un prescaler à 1024 et une attente active du bit T0V0 du registre TIFR0. On basculera le bit b4 du PORTB qui est relié à une LED. Quelle est la fréquence de basculement du bit b4 si la fréquence du quartz est 16 MHz ?

Solution de l'exercice 3

[modifier | modifier le wikicode]

Le nom des registres est un peu changé par rapport à un ATMega8.

//*** testé OK sur Arduino MEGA2560 + shield
#include <avr/io.h> 
int main(void){
 // initialisation du timer   division par 1024 
         TCCR0B = 0x05; // prescaler 1024 , entrée sur quartz 
         TCNT0 = 0x00; //   tmr0  : début du comptage dans 2 cycles 
 // bit RB4 du PORTB en sortie
         DDRB |= 0x10; //RB4 as output
         while(1) {
                 TIFR0 |= 0x01; // clr TOV0 with 1
                 while ((TIFR0 & (1<<TOV0)) == 0); //attente passive
                 PORTB ^= 0x10; // on bascule avec ou exclusif
         }
         return 0;
}

Fréquence 16 000 000 /(1024*256*2) = 30,51 Hz

Timer 0 et interruption

[modifier | modifier le wikicode]

La mise à zéro du bit TOV0 est complètement automatique dans les ATMega contrairement aux autres architectures connues par nous. Ceci doit être certainement lié au fait que les interruptions sont vectorisées et qu'ainsi il est possible pour le processeur de savoir à tout moment l'événement qui a déclenché l'interruption.

Documentation de l'interruption du timer 0 de l'ATMega8

Pour comprendre cette figure, il suffit de se rappeler qu'un front montant dans l'ellipse rouge réalisera cette-interruption. Ce front montant est réalisé par la mise à un, par le matériel, de TOV0. En C cette interruption est désignée par "TIMER0_OVF_vect". Si vous avez compris que ce n’est pas le logiciel qui positionnera le bit TOV0 mais le matériel, alors vous déduisez que pour réaliser une interruption il suffit de

  • mettre à 1 le bit TOIE0 du registre TIMSK pour l'ATMega8. Pour l'ATMega328 ce registre s’appelle TIMSK0.
  • mettre à 1 le bit I du registre SREG. Ceci se réalise par l'instruction "sei();" en C et "interrupts();" avec l’Arduino.

Le registre TIFR de l'ATMega8 a été rebaptisé TIFR0 pour l'ATMega328.

Mise en œuvre

[modifier | modifier le wikicode]

Si la fréquence de la platine MEGA2560 est de 16 MHz et que l’on utilise un préscalaire de 1024 et un basculement nous avons déjà trouvé une fréquence de 32 Hz dans un exercice précédent. Ainsi une division par 32 doit donner 1 Hz. On le fait par exemple par interruption, toujours avec notre MEGA2560 :

//*** testé OK sur Arduino MEGA2560 + shield
#include <avr/io.h> 
#include <avr/interrupt.h>
// compteur
volatile unsigned char cpt=0;

// Fonction de traitement Timer 0 OverFlow 
ISR(TIMER0_OVF_vect){ 
      cpt++; 
      if(cpt==31) { 
            PORTB ^=(1<<PB4); 
            cpt=0; 
      } 
} 

void main(){ 
// IT Timer0 Over Flow Active 
 TIMSK0=(1<<TOIE0);
// Prescaler 1024 (Clock/1024) 
  TCCR0B = (1<<CS02) | (1<<CS00);
//Configuration PORTB.4 en sortie 
  DDRB |= (1<<DDB4);   
  PORTB &= ~(1<<PB4); // PORTB.4 <-0 
//activation des IT (SREG.7=1) 
  sei(); 
// SREG |= 0x80; // équivalent à sei()
  while(1);
}

Le bit TOV0 est repositionné à 0 automatiquement (certainement par le retour d'interruption) ! Pour le réaliser par programme on peut mettre une valeur dans le timer0 (TCNT0) ou écrire un '1' dans le bit TOV0!

Un ATMega8 est enfoui dans un FPGA (Voir Embarquer un ATMega8 dans un FPGA). Sa seule particularité est de fonctionner à 25 MHz contre 20 MHz de fréquence maximale d'horloge pour celui du commerce. Il exécute le programme suivant écrit pour le compilateur gcc :

 /*********************************************************************
 Includes
 ***********************************************************************/
 #include <avr/io.h>
 #include <stdbool.h>
 #include <avr/interrupt.h>
 volatile unsigned char nb=0,vPORTB=1;
 /*********************************************************************
 Interrupt Routine
 **********************************************************************/
 // timer0 overflow
 ISR(TIMER0_OVF_vect) {
   nb++;
   if (!(nb % 16)) 
     vPORTB = (vPORTB << 1); 
   if (vPORTB == 0x00)   vPORTB = 0x01;
   PORTB = vPORTB; 
 }
 /**********************************************************************
 Main
 **********************************************************************/
 int main( void ) {
 // Configure PORTB as output
     DDRB = 0xFF;
     PORTB = 0x01;
 // enable timer overflow interrupt for both Timer0
     TIMSK=(1<<TOIE0);
 // set timer0 counter initial value to 0
     TCNT0=0x00;
 // start timer0 with 1024 prescaler
     TCCR0 = (1<<CS02) | (1<<CS00);
 // enable interrupts
     sei(); 
     while(true) { // grace a stdbool.h
     }
     return 0;
 }


1°) Calculer si le chenillard réalisé par ce programme est visible à l'œil humain (fréquence de changement de position des LEDs inférieure à 20 Hz).

2°) Comment peut-on écrire l'instruction "if (!(nb % 16))" pour plus d'efficacité.

3°) Quelle est la suite des états (LEDs allumées) réalisée par ce programme.

Pour les deux questions suivantes ce ne sera pas la routine d'interruption qui sera chargée de mettre le PORTB.

4°) Le programme suivant est donné et tourne dans un ATMega8 cadencé avec un quartz de 4 MHz.

#include <avr/interrupt.h>
volatile unsigned int cnt; 
ISR(TIMER0_OVF_vect) { 
    cnt++;                 // increment counter 
    TCNT0   = 96; 
  } 
} 
int main() {
  TCCR0 = (1<<CS01) | (1<<CS00);  // Assign prescaler to TCNT0 
  DDRB = 0xFF;            // PORTB is output 
  PORTB = 0xFF;            // Initialize PORTB 
  TCNT0  = 96;             // Timer0 initial value 
  TIMSK=(1<<TOIE0);        // Enable TMRO interrupt
  sei(); 
  cnt = 0;                 // Initialize cnt 
  do { 
    if (cnt >= 400) { 
      PORTB = ~PORTB;      // Toggle PORTB LEDs 
      cnt = 0;             // Reset cnt 
    } 
  } while(1); 
  return 0;
}

Quelle est la fréquence de clignotement des LEDs reliées au PORTB ?

5°) Modifier le programme principal pour réaliser un chenillard d'une LED se déplaçant vers les poids faibles en gardant le traitement en dehors de l'interruption.

Une partie matérielle est constituée de deux afficheurs sept segments multiplexés. Les sept segments sont commandés par le PORTC, tandis que les commandes d'affichages sont réalisée par les bits b0 et b1 du PORTB. Un schéma de principe est donné ci-après.

Comment utiliser deux afficheurs multiplexés

1°) A l'aide de la documentation calculer les valeurs dans un tableau "unsigned char SEGMENT[] = {0x3F,...};" pour un affichage des chiffres de 0 à 9.

2°) réaliser une fonction responsable du transcodage :

unsigned char Display(unsigned char no) { 
    unsigned char Pattern; 
    unsigned char SEGMENT[] = {0x3F,....

3°) Réaliser le programme main() responsable de l'initialisation de l'interruption qui doit avoir lieu toutes les 10ms (avec un quartz de 4MHz) et qui compte de 00 à 99 toutes les secondes environ (avec un "_delay_ms(1000);")

4°) Réaliser enfin l'interruption qui affichera tantôt les dizaines, tantôt les unités.

Exemple de réalisation à l'IUT de Troyes

[modifier | modifier le wikicode]

Un exemple de réalisation d'afficheur sept segments sur deux digits peut être trouvé ICI. Nous allons l’utiliser pour un exercice similaire à celui de la section précédente.

Les 2 afficheurs ne peuvent pas être utilisés simultanément. L'état de la sortie mux (arduino port 4 ou PD4) permet de sélectionner l'un ou l'autre. En allumant successivement l'un puis l'autre rapidement, on a l'illusion qu’ils sont tous 2 allumés.

Les segments des afficheurs sont câblés de façon analogue comme décrit ci-dessous :

Segment pt g f e d c b a
Arduino Pin 11 9 10 8 7 6 12 13
Port UNO PB3 PB1 PB2 PB0 PD7 PD6 PB4 PB5

Voici sous forme schématique la documentation correspondante :

Documentation du Shield avec carte UNO


Programme d'utilisation en langage Arduino

[modifier | modifier le wikicode]

Même si l’Arduino possède son propre chapitre dans ce livre, nous vous proposons ici du code le concernant

const char pinMux = 4;
const char pinAff[8]={13,12,6,7,8,10,9,11};
void setup() {
  char i;
  for (i=0;i<8;i++) pinMode(pinAff[i],OUTPUT); // Déclaration des 8 sorties des afficheurs
  pinMode(pinMux,OUTPUT); // + sortie de multiplexage (choix de l'afficheur)
}
void loop() {
  char i,c;
  for (i=0;i<8;i++) digitalWrite(pinAff[i],HIGH); // Les segments s'allument
  for (c=0;c<20;c++) {
    digitalWrite(pinMux,1); // sur l'afficheur 1
    delay(10);
    digitalWrite(pinMux,0); // puis sur l'afficheur 2
    delay(10);
  }
  for (i=0;i<8;i++) digitalWrite(pinAff[i],LOW); // Les segments s'éteignent
  for (c=0;c<20;c++) {
    digitalWrite(pinMux,1); // sur l'afficheur 1
    delay(10);
    digitalWrite(pinMux,0); // puis sur l'afficheur 2
    delay(10);
  }
}

Programme d'utilisation en langage C

[modifier | modifier le wikicode]

Utiliser le langage C pour utiliser le Shield ci-dessus est pas simple. L'idée est de faire un tableau qui considère que tout est sur un PORT et de gérer l'affichage qui gère correctement dans un sous-programme.

Voici un programme de test pour un sous-programme d'affichage.

#include<util/delay.h>

// Pour Arduino UNO et le shield de l'{{abréviation|IUT|institut universitaire de technologie}} de Troyes
// ch sous la forme [pt|g |f |e |d |c |b |a ] doit réaliser
// PORTB = [- |- |a |b |DP |f |g |e ]
// PORTD = [d |c |- |- |- |- |- |- ]
// Trouver les décalages à partir des trois dessins de registres ci-dessus
void affiche7segs(unsigned char ch){
  unsigned char port;
  port = 0;
  port = ((ch & 0x01)<<5) | ((ch & 0x02)<<3) | ((ch & 0x10)>>4) | ((ch & 0x20)>>3) | ((ch & 0x40)>>5);
  PORTB = port;
  port = 0;
  port = ((ch & 0x0C)<<4);
  PORTD = port;
}

int main(void){
    unsigned char transcod[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71},cmpt=0;
    DDRD = 0xC0; // 2 sorties pour D 
    DDRB = 0x3F; // 6 sorties pour B
    while(1) {
        affiche7segs(transcod[cmpt]);
         _delay_ms(1000);
        cmpt++;
        if (cmpt > 15) cmpt=0;
    } // while(1)
    return 0;
}

Il manque quelques fichiers d'inclusion, car on travaille directement avec l'environnement Arduino (sans setup() et sans loop() mais avec un main !

Pour utiliser les deux digits, le programme C suivant fonctionne correctement sans TIMER.

#include<util/delay.h>

// Pour Arduino UNO et le shield de l'{{abréviation|IUT|institut universitaire de technologie}} de Troyes
// ch sous la forme [pt|g |f |e |d |c |b |a ] doit réaliser
// PORTB = [- |- |a |b |DP |f |g |e ]
// PORTD = [d |c |- |- |- |- |- |- ]
// Trouver les décalages à partir des trois dessins de registres ci-dessus
void affiche7segs(unsigned char ch){
  unsigned char port;
  port = 0;
  port = ((ch & 0x01)<<5) | ((ch & 0x02)<<3) | ((ch & 0x10)>>4) | ((ch & 0x20)>>3) | ((ch & 0x40)>>5);
  PORTB = port;
  port = 0;
  port = ((ch & 0x0C)<<4);
  PORTD = port;
}

void afficheDiz(unsigned char ch){
  affiche7segs(ch);
  PORTD &= ~(1<<PD4);
}

void afficheUnit(unsigned char ch){
  affiche7segs(ch);
  PORTD |= (1<<PD4);
}


int main(void){
    unsigned char transcod[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71},cmpt=0,i;
    DDRD = 0xD0; // 2 sorties pour D 
    DDRB = 0x3F; // 6 sorties pour B
    
    while(1) {
        for (i=0;i<50;i++) {
            afficheDiz(transcod[cmpt>>4]);
            _delay_ms(10);
            afficheUnit(transcod[cmpt&0x0F]);
            _delay_ms(10);
        }
        cmpt++;
    } // while(1)
    return 0;
}

Reprendre l'exercice 5 avec le shield présenté dans cette section.

1°) Pour compliquer un peu on utilisera le MEGA2560 pour lequel la correspondance entre les numéros Arduino et les PORTs est :

Arduino Pin 13 12 11 10 9 8 7 6
Port MEGA2560 PB7 PB6 PB5 PB4 PH6 PH5 PH4 PH3

Trouver la correspondance entre les segments a,b, c, d, e, f et DP et les PORTs du MEGA2560.

2°) Écrire un sous-programme capable de prendre un octet et de l'afficher avec la convention "a" poids faible.

3°) Écrire le programme qui réalise une interruption à 100 Hz et affiche tantôt sur l'afficheur des poids faibles tantôt sur l'afficheur des poids forts.

Solution exercice 6
[modifier | modifier le wikicode]

La question 1°) n’est pas cachée car nous ne savons pas mettre un tableau dans le bandeau solution qui se déroule.

1°) Il vient assez facilement :

7 segments Pin pt g f e d c b a
Port MEGA2560 PB5 PH6 PB4 PH5 PH4 PH3 PB6 PB7

Timer 0 et comparaison

[modifier | modifier le wikicode]

Cette fonctionnalité n'existait pas dans l'ATMega8. Elle a été ajoutée pour gérer le PWM (Modulation de largeur d'impulsion = MLI et Pulse Width Modulation en anglais). Ce PWM sert essentiellement à commander des moteurs de Robots mais éventuellement à moduler des éclairages.

Les fonctionnalités ajoutées sont difficiles à appréhender. En effet le mode comparaison permet de gérer différents modes pour pouvoir réaliser toute une gamme de signaux du carré au PWM. Ceci complique un peu la gestion pour le programmeur. La bonne nouvelle c’est que les autres timers ont un fonctionnement un peu similaire.

Documentation de la comparaison

[modifier | modifier le wikicode]

Les différents modes de comparaison sont choisis à l'aide des bits WGM02, WGM01 et WGM00. Ces choix sont conformes au tableau suivant.

Description des bits pour la génération de forme d'onde
Mode WGM02 WGM01 WGM00 Mode de fonctionnement Bas si Mise à jour de OCRAx si Drapeau TOV0 positionné si
0 0 0 0 Normal 0XFF immédiatement MAX
1 0 0 1 PWM à phase correct OXFF TOP BOTTOM
2 0 1 0 CTC OCR0A immédiatement MAX
3 0 1 1 PWM rapide 0XFF BOTTOM MAX
4 1 0 0 Reservé - - -
5 1 0 1 PWM à phase correct OCR0A TOP BOTTOM
6 1 1 0 Reservé - - -
7 1 1 1 PWM rapide OCR0A BOTTOM TOP

Pour chacun des modes de ce tableau ci-dessus, les bits COM0A1 et COM0A0 auront un fonctionnement différent. Ces bits sont destinés à gérer les formes d'onde du signal de sortie.

Comparaison simple

[modifier | modifier le wikicode]

Nous présentons la comparaison simple ici. Elle est essentiellement utilisée avec le mode "Bascule OC0A sur la comparaison" ci-dessous quand l'objectif est de réaliser un signal de fréquence déterminé sans utiliser de logiciel (sauf pour tout initialiser bien sûr). Le mode de fonctionnement le plus adapté du timer dans ce cas est appelé CTC (Clear Timer on Compare match). Ce mode n’est pas choisi avec le tableau ci-dessous mais avec le tableau précédent.

Mode non PWM pour la comparaison
COM0A1 COM0A0 Description
0 0 Opération Normale PORT, OC0A déconnecté
0 1 Bascule OC0A sur la comparaison
1 0 Mise à 0 de OC0A sur la comparaison, mise à 1 sur overflow
1 1 Mise à 1 de OC0A sur la comparaison, mise à 0 sur overflow

Et voici donc la documentation correspondante :

La comparaison avec le timer 0


En résumé, le mode CTC s'utilise de la manière suivante :

  • mise à un de WGM01, WGM02 et WGM00 sont supposés à 0
  • choix de COM0A1 et COM0A0 pour la logique de sortie
  • choix du préscaler pour le démarrage du timer 0

Sans interruption, seul le mode "basculement du bit OC0A" a un intérêt mais il impose d’utiliser ce bit (qui est le bit b6 du PORTD).

L'interruption de comparaison peut servir à utiliser un bit de sortie quelconque. Elle n’est pas documentée mais le sera à travers un exercice (Exercice 8).

1°) Donner le squelette d'un programme qui utilise le mode CTC pour réaliser un signal de période 8 ms sur le bit OC0A. La fréquence du quartz sera de 16MHz comme sur la carte Arduino UNO.

2°) Dessiner sur un chronogramme le comptage du timer et le signal généré sur OC0A.

3°) Quelle est la fréquence la plus basse que l’on peut réaliser sur le bit OC0A ? Quel mode du générateur de signal utilise-t-on ?

4°) Quelle est la fréquence la plus haute que l’on peut réaliser sur le bit OC0A ? Quel mode du générateur de signal utilise-t-on ?

Exercice 8 : comparaison simple

[modifier | modifier le wikicode]

Compléter l'exemple ci-dessous trouvé sur Internet pour réaliser un clignotement d'un Hertz sur une LED connectée sur le bit b4 du PORTB.

// this code sets up a timer0 for 4ms @ 16Mhz clock cycle
// an interrupt is triggered each time the interval occurs.

#include <avr/io.h> 
#include <avr/interrupt.h>

int main(void){
  // Set the Timer Mode to CTC
    TCCR0A |= (1 << WGM01);
  // Set the value that you want to count to
    OCR0A = 0xF9;
    TIMSK0 |= (1 << OCIE0A);    //Set the ISR COMPA vect
    sei();         //enable interrupts
  // set prescaler to 256 and start the timer
    TCCR0B |= (1 << CS02);
    while (1)
    {
        //main loop
    }
    return 0;
}

ISR (TIMER0_COMPA_vect) // timer0 overflow interrupt
{
    //event to be exicuted every 4ms here
}

Mode PWM rapide

[modifier | modifier le wikicode]
La MLI (PWM) avec le Timer 0 (8 bits)

Pour information, le mode PWM rapide est abordé dans un autre chapitre sur le robot miniQ. Nous allons le décrire quand même ici : mieux vaut deux fois qu'une.

Mode PWM rapide et comparaison
COM0A1 COM0A0 Description
0 0 Opération Normale PORT, OC0A déconnecté
0 1 WGM02=0 Opération Normale PORT, OC0A déconnecté
0 1 WGM02=1 Basculement de OC0A sur la comparaison
1 0 Mise à 0 de OC0A sur la comparaison et à 1 à BOTTOM
1 1 Mise à 1 de OC0A sur la comparaison et à 0 à BOTTOM

Voici un exemple de programme qui réalise une PWM sur une carte Arduino UNO :

#include <avr/io.h> 
#undef F_CPU
#define F_CPU 16000000UL
#include <util/delay.h>
int main(void){  
     // Set the Timer Mode to PWM fast
    TCCR0A |= ((1 << WGM01) | (1<<WGM00));
    // clear OC0A on compare match set OC0A at TOP
    TCCR0A |= (1 << COM0A1);
    //Configuration (PORTB.6 pour UNO) 
    DDRD |= ((1<<DDD6)|(1<<DDD5)|(1<<DDD4));
    PORTD |= (1<<PD5);
    PORTD &= ~(1<<PD4);
    TCNT0 = 0x00;
    // set prescaler to 1024 and start the timer
    TCCR0B |= ((1 << CS02) | (1 << CS00));   
    while(1) {
      // Set the value that you want to count to
       OCR0A = 128;
        //_delay_ms(300);
    } // while(1)
    return 0;
}

Ce programme connecté à une carte L298N est capable d'entraîner un moteur courant continu d'un Robot. La carte de puissance en question nécessite de mettre un seul des deux bits (PD5 ou PD4) à 1 pour tourner dans un sens. Pour inverser le sens, il faut inverser les bits PD5 et PD4. La mise à 0 des deux bits PD4 et PD5 permet de freiner.

Mode PWM à phase correcte

[modifier | modifier le wikicode]

Ce mode utilise le compteur 0 dans les deux sens : en comptage et décomptage.

Mode PWM à phase correcte et comparaison
COM0A1 COM0A0 Description
0 0 Opération Normale PORT, OC0A déconnecté
0 1 WGM02=0 Opération Normale PORT, OC0A déconnecté
0 1 WGM02=1 Basculement de OC0A sur la comparaison
1 0 Mise à 0 de OC0A sur la comparaison quand comptage et mise à 1 de OC0A sur la comparaison quand décomptage
1 1 Mise à 1 de OC0A sur la comparaison quand comptage et mise à 0 de OC0A sur la comparaison quand décomptage

Exercice 9 : PWM rapide

[modifier | modifier le wikicode]

On désire changer l'intensité d'éclairage d'une LED à l'aide d'une PWM rapide. La valeur du rapport cyclique varie entre 0 et 255 et sera systématiquement envoyée par la liaison série sous forme de deux caractères 0,...,9,A,...,F.

1°) Un sous-programme sera donc chargé de lire ces deux caractères d’en vérifier la syntaxe. Écrire ce sous-programme et le tester avec les afficheurs de l'exercice 6. On affichera "--" en cas d'erreur de syntaxe.

Lisez le chapitre Les communications en tout genre pour vous aider sur la communication série.

2°) Changer votre programme de test précédent pour qu’il réalise un rapport cyclique sur le bit OC0A du PORTB.


Commande en largeur d'impulsion : la largeur des impulsions, comprise en général entre 1 et 2 millisecondes, commande la position du servomoteur.

Exercice 10 : PWM rapide et servomoteur

[modifier | modifier le wikicode]

Réaliser une commande d'un servomoteur à l'aide du timer 0.

Indications :

  • La fréquence de 50 Hz (période T=20 ms) n'est pas importante pour la commande des servomoteurs.
  • Le rapport cyclique par contre a besoin de varier entre 5% et 10%.
  • Un calcul simple montre donc que OCR0A doit varier entre 255/10 et 255/20 ce qui donnera donc 26 et 13.
  • On utilisera la fréquence minimale du Timer0.
  • OC0A est le bit PD6 du PORTD. Sur une carte Arduino, il s'agit de la broche -6