Cadencement par overflow timer ==> dérive?

Bonsoir,

Dans le but de gérer correctement le temps, je suis en train d'appréhender les interruptions par Overflow de timer.

J'ai fait un bout de code dont le principe est de programmer le timer 1 et son overflow pour avoir une période de 0.5s (2Hz) par interruption. L'ISR associé me permet de changer l'état de la led (celle de la carte arduino sur pin 13) toutes les 0.5s soit une période 1s normalement (1Hz).

void setup()
{
  pinMode(13, OUTPUT);

  // initialize timer1 
  TCCR1A = 0;
  TCCR1B = 0;

  TCNT1 = 34286;            // preload timer 65536-16MHz/256/2Hz
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
 
}

ISR(TIMER1_OVF_vect)        // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  TCNT1 = 34286;            // preload timer
  digitalWrite(13, digitalRead(13) ^ 1);
}

void loop()
{

}

Mon problème est que le clignotement de la led commandé par le timer dérive. J'ai bien mes 1Hz mais pas tout à fait. J'ai quelque chose comme une erreur de 0.5s au bout de 10min

Est ce que la dérive vient du code? ou de l'horloge interne du arduino? ou d'autre chose. Je me demandais si les timers s'incrémentent durant l'ISR.

Merci de votre aide

Bonsoir,

L'arrondi de 1024 à 1000 (ou inversement) peut-être ?

Merci de ta réponse,

Mais je pense que cette façon de procéder s'affranchit de cet aspect là.

Si je détaille,

J'ai mon horloge interne cadencée à 16Mhz, avec le prescaler de 256, ceci me donne une deuxième horloge à 62500Hz.

Soit une période de 1/62500 = 1.6*10^(-5)s (pile poil)

Pour atteindre 0.5s il faut compter de 0 à 0.5/(1.6*10^(-5)) = 31250 (pile poil)

Donc si le timer s'incrémente toute les 1.6*10^(-5)s, il faut initialiser le timer à TCNT1 = 65536-31250 = 34286.

Donc théoriquement par d'erreur côté initialisation, des retards s'introduisent sur l'interruption?

Il faudrait faire une mesure précise. Il n'est pas impossble que tu sois dans la précision du "quartz" de la carte. Je mets sciemment quartz entre guillemets car ce n'est pas tout le temps un quartz qui est monté sur les cartes mais plus souvent pour des raisons d'économie un résonateur céramique qui est moins précis.

Déjà ce n'est pas 65536 qu'il faut prendre, mais 65536 - 1 (et oui "0" est compté)

+1

C'est vrai qu'à faire le calcul d'erreur on se rend compte que c'est pas si scandaleux que ça : 0.5/(60/10) /100 = 0.083 % soit toute les secondes une erreur de 830 µs

Mais bon pas top non plus. Je n'ai jamais codé en dur sous Atmel mais sous MSP430 il y a un registre pour configurer la capacitance du quartz. Sur les Atmega je ne crois que ça y soit mais du coup la capacitance du quartz + celle des condos à ajouter est très importante (quelques pF d'écart et le décalage décolle en flèche).

Merci pour les éclaircissements,

Effectivement, le pourcentage d'erreur est très faible, mais quand même visible. Et c'est un peu gênant pour un futur projet que j'ai :

Mais en plus simple : enregistrement du régime uniquement dans un premier temps par arduino sur carte SD, et pas dans une Porsche malheureusement...
Un décalage de 0.5s entre la data et la vidéo serait donc désagréable à l'oeil (un peu comme les décalages de son à la télé, il en faut peu pour que ça se voye).

Si on revient à ma problématique : l'imprécision de l'horloge interne semble poser problème, je vais tenter le coup avec la Shield SD que j'ai qui dispose d'une horloge RTC DS1307. Je vais prendre comme ref le signal SQW à 32768Hz qui semble avoir une précision plus dans mon besoin.

Je vous tiens au courant.

Tu peux brancher un quartz 32768 Hz sur l'oscilalteur de l'Atmega et utiliser le résonateur interne 8 mhz pour le core

Confirmation, avec un quartz précis, le résultat est parfait : Pas de retard visible après 15min.

Voici le code qui a permis de faire la manip, avec une SD shield de chez Snootlab et le signal SQW raccordé à la pin 5 de l'arduino board :

#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // each I2C object has a unique bus address, the DS1307 is 0x68
int ledPin=9;

void setup()
{
  pinMode(ledPin, OUTPUT);

  // set SQW output of DS1307 to 32768Hz
  Wire.begin();
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0x07); // move pointer to SQW address
  Wire.write(0x13); // sends 0x13 (hex) 00010011 (binary)
  Wire.endTransmission();

  // initialize timer1 
  TCCR1A = 0b00000000;
  TCCR1B = 0b00000000;
   
  TCNT1 = 65536-32768;      // preload timer 65536-32768/1Hz
  TCCR1B = 0b00000110;      // no prescaler, clock source on T1 (Pin 5 of the arduino uno board, pin 11 of the Atmega)
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt (1)
 
}

ISR(TIMER1_OVF_vect)        // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  TCNT1 = 65536-32768;            // preload timer
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}

void loop()
{}

Merci de votre attention.

:wink:

Sinon même remarque que précédemment :

TCNT1 = 65536-32768-1

..:
Le zéro ne peut pas être pris en compte puisqu'il ne dure que le temps de recharger le timer : le premier cycle se fait donc avec la valeur de chargement ... à moins que le timer ne tourne plus vite que l'ISR, auquel cas il faut "essayer et ajuster", mais ce n'est certainement pas le cas avec un prescaler à 256 !

Ah mais sous Atmega on doit recharger "manuellement" la limite d'overflow ??

Vu que le problème est résolu on peut toujours continuer :wink:

Ok c'est bien ce que je pensais. Donc on est d'accord qu'en CTC, à supposer pas de prescaler ou autre, oscillateur 32768hz sur le timer, on configurera "l'arrivée" à 32768-1 ?

Ok c'est bien ce que j'avais compris alors ^^

Merci pour ces précisions

Merci bien pour toutes ces infos très intéressantes, ne soyez pas gêner d'en dire plus que nécessaire, ça m'aide à appronfondir mes connaissances en Aduino/328p.

Donc le mode CTC est donc une autre façon de cadencer et sans interruption apparement, ce qui est intéressant si j'ai beson de l'interrupion pour une autre fonction (ma mesure de régime par exemple).

Je file de ce pas essayer cette technique :slight_smile:

et voilà! ça fonctionne

#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // each I2C object has a unique bus address, the DS1307 is 0x68

void setup()
{

// congifure pin15/Atmega PINB1/Register pin9/arduino as an output
DDRB |= (1 << DDB1);

// set SQW output of DS1307 to 32768Hz
Wire.begin();
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0x07); // move pointer to SQW address
Wire.write(0x13); // sends 0x13 (hex) 00010011 (binary)
Wire.endTransmission();

// initialize timer1
TCCR1A = 0b00000000;
TCCR1B = 0b00000000;

// no prescaler, clock source on T1 (Pin 5 of the arduino uno board, pin 11 of the Atmega)
TCCR1B |= (1 << CS11);
TCCR1B |= (1 << CS12);

// set CTC mode
TCCR1B |= (1 << WGM12);

// set compare match
OCR1A = 32768-1; // pour une période de 1s à 32768Hz

// toggle OC1A on compare match
TCCR1A |= (1 << COM1A0);

}

void loop()
{}

Non même en CTC tu as des interruptions, la différence fondamentale c'est que le timer "fait sa vie" : tu le programme une fois, et il te lève une interruption une fois le compteur terminé. La grosse différence c'est que tu n'as rien à "recharger" dans l'interruption, et que dans l'idée tu peux rester autant que tu veux dans l'interruption car le timer continu à fonctionner indépendamment. Néanmoins il vaut mieux ressortir de l'ISR avant qu'une nouvelle interruption soit levée par le timer :wink: