Micro contrôleurs AVR/Le Timer 1
Le timer 1 est un timer étendu sur 16 bits. Cette taille de timer, 8 bits pour le timer 0 (du précédent chapitre) et 16 bits pour le timer 1 n’est pas sans rappeler l'architecture des PIC 16F. Rappelons par contre que le timer 0 est passé à 16 bits dans les PIC 18F. Mais revenons-en à notre sujet principal : les AVRs.
Documentation du timer 1 seul
[modifier | modifier le wikicode]Le timer 1 est un compteur sur 16 bits avec une horloge calibrée réalisée à partir de l'horloge (quartz externe). Son utilisation est possible en compteur. Dans ce cas c’est un signal externe qui fera avancer le compteur.
Utiliser des registres 16 bits avec un processeur 8 bits nécessite quelques précautions. On peut lire dans la documentation :
- Pour réaliser une écriture 16 bits, l'octet de poids fort doit être écrit en premier
- pour lire un registre 16 bits, l'octet de poids faible doit être lu avant l'octet de poids fort
En principe le compilateur C sait gérer ces situations.
Timer1 de l'ATMega8
[modifier | modifier le wikicode]Voici sous forme schématique la documentation du timer 1. Il y a dans ce schéma toute l'information nécessaire pour réaliser la programmation du timer 1 dans n’importe quel langage.
Les registres utilisés par le Timer1 sont donc :
- TCNT1H et TCNT1L, (H pour High = poids fort, L pour LOW = poids faible)
- TCCR1B
- éventuellement le PORTD, et TIFR.
Au risque de se répéter, l'accès d'un registre 16 bits par un processeur 8 bits est toujours un peu complexe puisque cela se fait en deux écriture. En ce qui concerne l'architecture que l’on étudie, il y a un registre temporaire (partagé avec d'autres registres). On retiendra :
- pour une écriture : on écrit d’abord le poids fort puis le poids faible
- pour une lecture : on lit d’abord le poids faible puis le poids fort.
La bonne nouvelle est que notre compilateur C est capable de manipuler directement les 16 bits (TCNT1=0x1FF;) comme le montrent les définitions suivantes :
#define TCNT1 _SFR_IO16(0x2C)
#define TCNT1L _SFR_IO8(0x2C)
#define TCNT1H _SFR_IO8(0x2D)
La mise à zéro du bit TOV1 est un peu particulière. Elle se réalise en mettant un 1 dans le bit correspondant ou en écrivant une valeur dans le timer.
Le bit du PORTD qui sert éventuellement à piloter le compteur 1 s’appelle T1.
Timer1 de l'ATMega328
[modifier | modifier le wikicode]Le fonctionnement du Timer 1 est identique à celui de l'ATMega8 mais des registres ont changés légèrement de nom et des fonctionnalités ont été ajoutées. Mais ces nouvelles fonctionnalités ne sont pas présentées ici.
C'est maintenant le registre TIFR1 qui contient le drapeau de débordement TOV1.
Exercice 1
[modifier | modifier le wikicode]1°) L'AVR ATMega8 possède un oscillateur de 4 MHz. Quelle fréquence minimale de l'interruption TOV1 peut-il générer avec son horloge si la documentation du timer 1 de l'ATMega8 est la même que celle de l'ATMega328 ?
/*********************************************************************
Includes
***********************************************************************/
#include <avr/io.h>
#include <stdbool.h>
#include <avr/interrupt.h>
unsigned char nb=0;
volatile unsigned char vPORTB=1; // cette variable peut être évitée
/*********************************************************************
Interrupt Routine
**********************************************************************/
// timer1 overflow
ISR(TIMER1_OVF_vect) {
vPORTB ^= 0x01;
PORTB = vPORTB;
}
/**********************************************************************
Main
**********************************************************************/
int main( void ) {
// Configure PORTB as output
DDRB = 0xFF;
PORTB = 0xFF;
// enable timer overflow interrupt for both Timer0
TIMSK |= (1<<TOIE1);
// set timer0 counter initial value to 0
TCNT1=0x0000;
// start timer0 with 1024 prescaler
TCCR1B = (1<<CS12) | (1<<CS10);
// enable interrupts
sei();
while(true) { // grace a stdbool.h
}
}
Remarquez la présence d'une variable qualifiée de volatile. Il faut mieux déclarer les variables globales utilisées par une interruption comme volatile. Cela évite au compilateur de faire des optimisations qui seraient dommageable au bon fonctionnement. Cette variable est commentée comme non nécessaire car l'interruption pourrait se faire avec la simple instruction :
// timer1 overflow
ISR(TIMER1_OVF_vect) {
PORTB ^= 0x01;
}
4MHz/(1024*65536) = 0,0596 Hz.
Le bit TOV1 est automatiquement mis à 0 par l'interruption.
2°) Quelle fréquence et sur quel bit est-elle générée dans les mêmes conditions que la question précédente ?
0,0596 Hz / 2 car basculement avec un ou exclusif (soit 0,0298 Hz).
3°) Quel bit de quel registre autorise l'interruption ?
Le bit TOIE1 du registre TIMSK.
La comparaison
[modifier | modifier le wikicode]La comparaison sert à générer une fréquence calibrée sur une sortie quelconque (ou un bit interne). Elle est aussi utilisée pour générer une MLI (Modulation de Largeur d'Impulsion, PWM en anglais).
Documentation de la comparaison et de la MLI
[modifier | modifier le wikicode]Les différents modes de comparaison sont choisis à l'aide des bits WGM13, WGM12, WGM11 et WGM10 (un bit de plus que le timer 0). Ces choix sont conformes au tableau suivant.
- Description des bits pour la génération de forme d'onde
Mode | WGM13 | WGM12 | WGM11 | WGM10 | Mode de fonctionnement | Bas si | Mise à jour de OCRAx si | Drapeau TOV1 positionné si |
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | Normal | 0XFFFF | immédiatement | MAX |
1 | 0 | 0 | 0 | 1 | PWM à phase correcte (8 bits) | OXFF | TOP | BOTTOM |
2 | 0 | 0 | 1 | 0 | PWM à phase correcte (9 bits) | OX1FF | TOP | BOTTOM |
3 | 0 | 0 | 1 | 1 | PWM à phase correcte (10 bits) | OX3FF | TOP | BOTTOM |
4 | 0 | 1 | 0 | 0 | CTC (Clear Timer on Compare) | OCR1A | immédiatement | MAX |
5 | 0 | 1 | 0 | 1 | PWM rapide (8 bits) | OXFF | BOTTOM | TOP |
6 | 0 | 1 | 1 | 0 | PWM rapide (9 bits) | OX1FF | BOTTOM | TOP |
7 | 0 | 1 | 1 | 1 | PWM rapide (10 bits) | OX3FF | BOTTOM | TOP |
14 | 1 | 1 | 1 | 0 | PWM rapide | ICR1 | BOTTOM | TOP |
15 | 1 | 1 | 1 | 1 | PWM rapide | OCR1A | BOTTOM | TOP |
- MAX = 0xFFFF
- BOTTOM = 0x0000
Nous n'avons présenté qu'une partie du tableau mais cela devrait suffire pour une utilisation courante.
Comme le montre ce tableau, le timer1 est un timer 16 bits capable de fonctionner sur 8, 9 et 10 bits (modes 5, 6 et 7). Il est même aussi capable de fonctionner avec une valeur maximale quelconque (modes 14 et 15) où la valeur maximale du comptage est fixée par un registre. Cela ressemble au mode CTC mais il s'agit en fait de fixer la fréquence de la MLI. Le rapport cyclique sera fixé par OCR1A et/ou OCR1B pour le mode 14 et seulement OCR1B pour le mode 15.
Une application classique du mode 14 est la commande d'un servomoteur qui nécessite la réalisation d'un signal MLI à une fréquence de 50Hz.
Pour chacun des modes de ce tableau ci-dessus, les bits COM1A1 et COM1A0 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]- Mode non PWM pour la comparaison
COM1A1/COM1B1 | COM1A0/COM1B0 | Description |
---|---|---|
0 | 0 | Opération Normale PORT, OC1A/OC1B déconnecté |
0 | 1 | Bascule OC1A/OC1B sur la comparaison |
1 | 0 | Mise à 0 de OC1A/OC1B sur la comparaison |
1 | 1 | Mise à 1 de OC1A/OC1B sur la comparaison |
Et voici donc la documentation correspondante :
Il est possible de passer le Timer 1 en mode 8 bits, ce qui n'apparaît pas sur la figure : mais sera expliqué plus tard.
Mode PWM rapide
[modifier | modifier le wikicode]- Mode PWM rapide et comparaison
COM1A1/COM1B1 | COM1A0/COM1B0 | Description |
---|---|---|
0 | 0 | Opération Normale PORT, OC1A/OC1B déconnecté |
0 | 1 | WGM02=0 Opération Normale PORT, OC1A/OC1B déconnecté |
0 | 1 | WGM02=1 Basculement de OC1A/OC1B sur la comparaison |
1 | 0 | Mise à 0 de OC1A/OC1B sur la comparaison et à 1 à BOTTOM |
1 | 1 | Mise à 1 de OC1A/OC1B sur la comparaison et à 0 à BOTTOM |
Ce tableau nous montre clairement que seuls les modes 2 et 3 sont importants pour la MLI. Ces deux modes fonctionnent :
- mode 2 on augmente OCR1A/OCR1B pour augmenter la largeur d'impulsion
- mode 3 on augmente OCR1A/OCR1B pour diminuer la largeur d'impulsion
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
COM1A1/COM1B1 | COM1A0/COM1B0 | Description |
---|---|---|
0 | 0 | Opération Normale PORT, OC1A/OC1B déconnecté |
0 | 1 | WGM02=0 Opération Normale PORT, OC1A/OC1B déconnecté |
0 | 1 | WGM02=1 Basculement de OC1A/OC1B sur la comparaison |
1 | 0 | Mise à 0 de OC0A sur la comparaison quand comptage et mise à 1 de OC1A/OC1B sur la comparaison quand décomptage |
1 | 1 | Mise à 1 de OC0A sur la comparaison quand comptage et mise à 0 de OC1A/OC1B sur la comparaison quand décomptage |
Exemple de programme en C
[modifier | modifier le wikicode]Nous donnons, pour commencer, le programme suivant destiné à faire clignoter une LED à environ à 1 Hz sur une platine Arduino MEGA2560. Cette platine fonctionnant à 16MHz, il faut compter jusqu'à 7812 si l’on utilise un prédiviseur par 1024 et un mode de basculement de bit. Attention, 1024 * 7812 ne donne pas 16MHz mais 8MHz à cause du basculement !
#include <avr/io.h>
//*********** Pour platine MEGA2560 mais fonctionne probablement avec platine UNO
void main(){
// initialisation pour comparaison
DDRB |= (1<<DDB5); // 1 = sortie
TIFR0 |= 0x04; // clr TOV1 with 1
// Prescaler 1024 (Clock/1024)
TCCR1B = (1<<CS12) | (1<<CS10);
TCCR1B |= (1<<WGM12); //RAZ timer quand comparaison
TCCR1A |= (1<<COM1A0); //bascule sortie a chaque comparaison
TCCR1A &= ~(1<<COM1A1); //bascule sortie a chaque comparaison
OCR1A = 7812;//16 000 000 /(1024*2)
while(1);
}
Il n'y a aucune interruption dans ce programme ! La boucle while(1) peut être remplacée par n’importe quelle activité sans déranger le matériel.
Interruption de comparaison
[modifier | modifier le wikicode]La comparaison permet de faire basculer une broche particulière de manière entièrement matérielle. Pour le timer 1 les broches sont appelées OC1A et OC1B.
Et pour l'ATMega328p ces broches sont :
- PB1 broche 15 pour OC1A
- PB2 broche 16 pour OC1B
Mais il suffit que le cahier des charges impose le basculement d'une broche (qui n'est ni OC1A ni OC1B) pour que l’on soit obligé de faire ce travail de basculement à l'aide d'une interruption.
Imaginons donc que l’on désire utiliser une des interruptions de comparaison (il y en a deux). La première chose à faire est de trouver comment elles ont été définies dans "avr/interrupt.h" :
#define TIMER1_COMPA_vect _VECTOR(11) /* Timer/Counter1 Compare Match A */
#define TIMER1_COMPB_vect _VECTOR(12) /* Timer/Counter1 Compare Match B */
Si l’on désire utiliser la première, il nous faudra donc écrire quelque chose qui ressemble à :
ISR(TIMER1_COMPA_vect) {
// ... du code ici
}
Ce qui reste donc à faire est donc d'autoriser cette interruption. Cela se réalise en deux étapes :
- mise à un du bit OCIE1A du registre TIMSK1
- autorisation générale des interruptions avec un "sei()"
Exercice 2
[modifier | modifier le wikicode]Modifier le code donné en exemple dans la section précédente pour remplacer le basculement matériel de la broche OC1A par le basculement de la broche PB3 (toujours à un Herz).
#include <avr/io.h>
#include <avr/interrupt.h>
//*********** Pour platine UNO
ISR(TIMER1_COMPA_vect) {
// ... du code ici
PORTB ^= (1<<PB3); // basculement de PB3 comme demandé
}
void main(){
// setup :
// initialisation pour comparaison
DDRB |= (1<<DDB3); // 1 = sortie : Ici c’est PB3 qui est demandé
TIFR0 |= 0x04; // clr TOV1 with 1
// Prescaler 1024 (Clock/1024)
TCCR1B = (1<<CS12) | (1<<CS10);
TCCR1B |= (1<<WGM12); //RAZ timer quand comparaison : CTC
// maintenant on debranche le basculement matériel :
TCCR1A &= ~(1<<COM1A0);
TCCR1A &= ~(1<<COM1A1);
// on initialise le registre de comparaison
OCR1A = 7812;//16 000 000 /(1024*2)
// autorisation interruption
TIMSK1 |= (1<<OCIE1A);
sei();
// loop : vide
while(1);
}
Exercice 3
[modifier | modifier le wikicode]Réaliser une commande simple de servomoteur en C pur (sans la librairie Arduino). Comme le montre la figure ci-contre, il s'agit de réaliser une Modulation de Largeur d'Impulsion de fréquence 50 Hz (période de 20 ms).
Indications
Seul le timer1 permet de réaliser cela en matériel pur, c'est-à-dire sans code spécifique s'exécutant pour le réaliser et donc sans interruption. En effet 16MHz / 50 *1024 = 312,5 qui est une valeur de plus de 8 bits ! Cette valeur correspond à la valeur TOP de TCNT1 pour réaliser 50 Hz à partir de 16 MHz avec un prescaler de 1024. Il serait donc bon de prendre le mode 14 de la PWM et d'initialiser ICR1 à 313.
Le rapport cyclique doit varier entre 1 ms et 2 ms.
Une question importante est de savoir comment brancher le servomoteur ?
- Pour cela il faut chercher la broche OC1A sur le microcontrôleur. Sur l'ATMega328p c'est la broche PB1 (broche 15 d'un boîtier DIP).
- Chercher ensuite dans google en tapant "pinout arduino uno" et chercher sur une des images proposées soit PB2 soit OC1A.
- La correspondance Arduino est trouvée : c'est la broche -9 qui sera amenée au fil orange du servomoteur.
- Le fil marron foncé est amené à la masse et le rouge à Vcc (de l'Arduino).
Pour information il est possible de trouver des petits servomoteurs autour de 4 €.
#include <avr/io.h>
#include <util/delay.h>
// Frequence 16MHz
//*********** Pour platine UNO *************
int main(){
// initialisation pour comparaison
DDRB |= (1<<DDB1); // 1 = sortie=OC1A (15)
ICR1 = 313; // MLI à 50 Hz
//TIFR0 |= 0x04; // clr TOV1 with 1
// Prescaler 1024 (Clock/1024)
TCCR1B = (1<<CS12) | (1<<CS10);
// Timer 1 en mode 14
TCCR1B |= (1<<WGM13)|(1<<WGM12);
TCCR1A |= (1<<WGM11);
TCCR1A |= (1<<COM1A1); //bascule sortie a chaque comparaison
OCR1A = 31;//16 000 000 /(1024*2)
while(1){
OCR1A -= 1;
if (OCR1A == 15) OCR1A = 31;
_delay_ms(1000);
}
}
La résolution n'est pas terrible puisque l'on doit varier OCR1A de 15 à 31. Il suffit de diviser par 256 au lieu de 1024 pour avoir plus de résolution :
#include <avr/io.h>
#include <util/delay.h>
// Frequence 16MHz
//*********** Pour platine UNO *************
int main(){
// initialisation pour comparaison
DDRB |= (1<<DDB1); // 1 = sortie=OC1A (15)
ICR1 = 1250; // MLI à 50 Hz
//TIFR0 |= 0x04; // clr TOV1 with 1
// Prescaler 256 (Clock/256)
TCCR1B = (1<<CS12);
// Timer 1 en mode 14
TCCR1B |= (1<<WGM13)|(1<<WGM12);
TCCR1A |= (1<<WGM11);
TCCR1A |= (1<<COM1A1); //bascule sortie a chaque comparaison
OCR1A = 156;
while(1){
OCR1A -= 1;
if (OCR1A == 31) OCR1A = 156;
_delay_ms(1000);
}
}
On a maintenant une résolution sur 125 valeurs.
Ce code a été testé sur Tinkercad avec succès.
Il est possible d'utiliser le prescaler à 64. il faudrait alors mettre 5000 dans ICR1.
Exercice 4
[modifier | modifier le wikicode]Reprendre l'exercice 3 pour une commande en panoramique et en inclinaison (pan/tilt touret), c'est-à-dire deux servomoteurs.
On peut naturellement utiliser le timer1 pour les deux commandes si l'on se place dans le mode 14.
#include <avr/io.h>
#include <util/delay.h>
// Frequence 16MHz
//*********** Pour platine UNO *************
int main(){
// initialisation pour comparaison
DDRB |= (1<<DDB1); // 1 = sortie=OC1A (15) Arduino -9
DDRB |= (1<<DDB2); // 1 = sortie=OC1A (16) Arduino -10
ICR1 = 1251; // MLI à 50 Hz
TIFR0 |= 0x04; // clr TOV1 with 1
// Prescaler 256 (Clock/256)
TCCR1B = (1<<CS12);
// Timer 1 en mode 14
TCCR1B |= (1<<WGM13)|(1<<WGM12);
TCCR1A |= (1<<WGM11);
TCCR1A |= (1<<COM1A1); //bascule sortie a chaque comparaison
OCR1A = 125;
OCR1B = 125;
while(1){
OCR1A -= 1;
if (OCR1A == 62) OCR1A = 125;
OCR1B -= 1;
if (OCR1B == 62) OCR1B = 125;
_delay_ms(500);
}
}