Décalage horloge interne millis()

Bonsoir

Ayant réalisé un petit programme de gestion d'oscillateurs à base de servomoteurs : http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1293371084

J'effectue le changement de paramètres (vitesses, courses, ...) en fonction de l'heure.
Pour ce faire, j'ai créé une horloge interne à base de l'instruction millis().
Mais un problème de taille me pose problème. La valeur millis n'évolue pas au rythme souhaité. Normalement, un décalage de 1000 correspond à 1 seconde, et dans mon cas, cela va trop vite. J'ai donc recalé le comptage par une simple règle de trois, mais je me rends compte que le décalage est aléatoire.
parfois cela avance, parfois cela retarde.
J'ai tout d'abord pensé que cela provenait de la taille du programme et que l'horloge s'apparentait plus à un compteur de cycle où la longueur du programme faisait varier le rythme de la variable interne millis.
J'ai donc créé un tout petit programme allumant la LED avec une impulsion toutes les minutes pour vérifier ma théorie.
Effectivement la valeur est légèrement différente pour clignoter toutes les minutes, mais toujours pas dans la norme (je devrais en théorie avoir 600 pour incrémenter toutes les minutes).

Voici le bout de code :

// Déclaration des variables et constantes horloge
  long compteur_top=0;        // Initialisation des tops horloge
  long ancien_millis=0;       // Mémoire de l'horloge millis
  int minute = 5;            // Minutes de l'horloge
  int heure = 19;             // Heures de l'horloge (avec inscription "19h" comme heure initiale)
  
   
   // Sortie LED
  const int led_int = 13;
  int etat_led = LOW;
 
 

void setup() {

  
  // Adressage des sorties
  pinMode (led_int, OUTPUT);
 
  // Initialise la liaison série
  Serial.begin(19200);
   delay(50);
 
}



void loop() {
   
// Création d'une impulsion temporisée (top) pour déplacements oscillateurs sur la base de l'horloge interne millis()
  int top;
      if (millis() >= ancien_millis) { // Vérifie que le temps millis() est bien supérieur à l'ancienne valeur mémorisée
                                       // >> risque à l'init et au retour à zéro de l'horloge interne.
      if (millis() - ancien_millis >= 100){ top = 1;  // création d'un top toutes les 100ms
              ancien_millis = millis();               // réinitialisation ancien_millis
          compteur_top = compteur_top + 1; }          // Incrémente le compteur de tops
                      
                      else {delay(100-(millis()-ancien_millis));}
      }
        
// HORLOGE 
    if (compteur_top >= 498) {minute = minute + 1; //Incrémentation des minutes   
         // (498 au lieu de 600 dans ce cas pour avoir une incrémentation toutes les minutes)
    
        compteur_top = 0;  
  etat_led = HIGH;      
    }
    else etat_led = LOW;
    
    if (minute >= 60) {heure = heure + 1; //Incrémentation des heures
        minute = 0;}
    if (heure >=24) {heure = 0;} // réinitialise l'horloge toutes les 24h
  
     
    
// LIAISON SERIE POUR TESTS

 if (top = 1){
     
      Serial.print(heure);
      Serial.print("     ");  
      Serial.print(minute);
     
      Serial.println("   ");
    
      Serial.println("   ");
  
      Serial.println("     ");
      
}

 // Remise à zéro impulsion "Top"
  top = 0; 
  
 
  //    Serial.print("   ");
      Serial.println("  ");
      Serial.println("   ");
      Serial.println("   ");
      

      
           
        digitalWrite(led_int, etat_led);
       
      
}

Ma carte aurait-elle un problème, ou la valeur millis est naturellement inexploitable ?

Merci pour votre aide
Franck

Bonjour,

ça me semble un gros offset tout de même,
Je viens de refaire le montage DS1307 avec le code suivant (trèèèès fortement issu du lien ci dessus)

//ChronoDot test [ds1307]
// Based on http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1279385586

#include <WProgram.h>
#include <Wire.h>
#include <DS1307.h> // written by  mattt on the Arduino forum and modified by D. Sjunnesson

#define INT_PIN 3  //DS1307 sqw wire

extern volatile unsigned long timer0_millis;
volatile unsigned long t, now, prev;

void setup() {
  //init wire & serial
  Wire.begin();
  Serial.begin(9600);
  Wire.beginTransmission(104); // transmit to device #104, the ds 1307
  Wire.send(0x07);  //select control register
  Wire.send(0x10);  //set sqw at specified Hz of  0=1Hz
  Wire.endTransmission();

  //init interrupt
  pinMode(INT_PIN, INPUT);
  attachInterrupt(0, OneHzInterrupt, FALLING);  //0 = digital pin 3
}


void loop() {
 Serial.println(t);
  delay(1000);
}


void OneHzInterrupt(void) {
now = timer0_millis;
  t = now - prev;
  prev = now;
}

et j'ai ce genre de résultat :

1000
1000
999
1001
1000
1000
1000
1000
1000
999
1001
999
1001
1000
1000
1000
1000
1000
999
1001
1000
1000
1000
1000
1000
999
1001
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1000
1001
999
1000
1000
1000
1000
1000
1001
999
1000
1000
1000
1000
1000
1001
999
1001
999
1000
1000
1000
1001
999
1001
999
1000
1000
1000
1001
999

Donc c'est de l'ordre de moins de 1ms par seconde de dérive, ce n'est pas du même ordre que ce que tu constates, doit y avoir un souci dans le code, n'étant pas vraiment une star du code, je ne sais pas dire ou est l'erreur dans le tien

Par contre, il existe la librairie Time pour simplifier l'usage de l'heure dans les sketches, réglable par le terminal série par exemple
http://www.arduino.cc/playground/Code/Time

Ceci dit, concernant de la gestion horaire, je pense que tu devrais regarder tu coté des horloges RTC (Real Time Clock)

Les plus célèbres sont basées sur le DS1307 ou le Chronodot (DS1321 pour plus de précision)
Elles sont autonomes (batterie lithium) donc ne craignant aucunement la coupure secteur.

Leur utilisation est bien documentée, elles utilisent le bus I2c (entrées Analogiques 4 & 5) et c'est quand même bien plus simple de jouer avec le temps dans ce cas.

Bon courage,

Lionel


- Distributeur officiel Arduino - Conception de shields pour les bricoleurs

Ce que je ne comprend pas c'est la raison pour laquelle tu utilises millis() avec un delay si millis() n'est pas à la valeur que tu attends :

else {delay(100-(millis()-ancien_millis));}

Essaye simplement de supprimer ce delay() :

else {;;} // ou sans les ;;

Ton offset viens peut-être de là.

Ensuite, incrémente tes top ainsi :

compteur_top ++; }

Bonjour

Merci à tous les deux pour votre aide ! :slight_smile:

Lionel
J'avais vu cette option "librairie time", mais je n'ai pas compris comment la télécharger (si vous pouviez m'aider, je suis preneur :slight_smile: )

Pour l'utilisation d'une horloge externe RTC, je pensais pouvoir m'en passer au regard du besoin de précision relativement faible dont j'ai besoin. Mais là, c'est énorme !

Jean François
Tout le développement que j'avais réalisé était sans le else (delay). Et le décalage était présent.
Comme j'ai supposé que l'horloge se décalait à cause du temps de scrutation, je m'étais dit qu'une pause ne ferait pas de mal au processeur. Et ai donc rajouté cette instruction qui repose le proc. en attendant le temps nécessaire à l'obtention du top 100ms. Cela n'a rien changé dans le décalage, mais permet de scruter moins souvent ;).
Là n'est donc pas le problème

Oui, pour l'incrémentation, je vais modifier par "compteur_top ++; "

Si vous entrez la totalité du code donné en tête de post, je constate ce décalage. Constatez vous le même ?

Franck

J'ai effectivement un décalage d'une ou deux secondes au bout de quinze minutes.

J'avais vu cette option "librairie time", mais je n'ai pas compris comment la télécharger (si vous pouviez m'aider, je suis preneur )

La librairie se trouve en suivant ce lien

http://www.arduino.cc/playground/uploads/Code/Time.zip

En fonction de ton OS il faut l'installer a l'endroit qui convient

Lionel

J'ai une hypothèse.
la précision est de 1 milliseconde, la résolution est de 4 millisecondes.

Comme on test un chiffre plus grand ou égal à l'écart de temps que l'on veut mesurer, au meilleur des cas on tombe sur le plus petit écart possible, au pire des cas on tombe sur un chiffre plus grand, donc une dérive est obligatoire.

Lors de chaque test if t tu tombes dans la fourchettes de résolution, dans le pire des cas tu as une seconde d'offset au bout de 4 minutes.

J'ai sué un petit moment sur ton problème....

Je t'ai fait un petit bout de code avec commentaires, essaye le.
Après 30 minute de test, je n'ai pas de dérive flagrante.

// Déclaration des variables et constantes horloge

unsigned long refTemps=0; // reference prise dans le flux du temps
// on lance une ancre dans le flux du temps et on compte les noeuds qui défilent sur la corde
unsigned long diff=0; // difference entre le temps voulu et le temps reel

int jour=18; // Jour de l'horloge
int heure = 23; // Heures de l'horloge
int minute = 32; // Minutes de l'horloge
int seconde = 0; // Secondes de l'horloge

void setup() {

// Initialise la liaison série
Serial.begin(19200);

}

void loop() {

diff=millis()-refTemps-1000; // on calcule les millisecondes qui seront perdues dans la comparaison de la boucle "if"
if(millis()-refTemps>=1000){ // on compare a 1000 (1 seconde) le temps actuel moins la longueur de l'ancre jetee dans le flux du temps

refTemps=millis(); // on jette a nouveau une ancre dans le flux du temps
seconde++; // on additionne 1 seconde

refTemps -= diff; // on enleve les millisecondes de retard a la position de l'ancre, ainsi l'echeance de la seconde arrive plus tot

if(seconde>=60){ // lorsque les secondes arrivent a 60
minute++; // on additionne 1 minute
seconde=0; // et on remet les seconde a zero
if(minute>=60){ // lorsque les minutes arrivent a 60
heure++; // on additionne 1 heure
minute=0; // et on remet .... etc, etc
if(heure>=24){
jour++;
heure=0;

}
}
}
}

// on envoie dans le moniteur serie le jour, l'heure, les minutes et les secondes....
Serial.print(jour);
Serial.print(" ");
Serial.print(heure);
Serial.print(" ");
Serial.print(minute);
Serial.print(" ");
Serial.println(seconde);

delay(100);

}

Tiens moi au courant :wink:

Ouhaouuuu ! :o :o :-X

Je vois qu'il y en a qui occupent leurs soirées ;D

Merci Jean François
Bien vu la cause très probable du décalage. Je testerais l'intégration de cette astuce ce WE.

Lionel,
J'ai un portable dernière (presque) génération sous win7. Le soft Arduino installé est la version Française, sans remise à jour.
Je ferais également l'installation ce WE. Mais je n'ai pas tout compris dans la manière de procéder. Où dois-je dézipper ? Suffit-il d'écrire les instructions sans chargement préalable ? y'aurais pas un tuto qui traine des fois ?

Franck

Bonjour,

Pour installer une nouvelle librairie, il faut faire comme suggéré sur la page suivante :
http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.Librairies

(...)Pour utiliser les librairies "hors référence" fournies par la communauté, il faut télécharger un fichier zip, le décompresser et copier le répertoire obtenu dans le répertoire /arduino-00xx/libraries/. La nouvelle librairie ainsi installée sera insérée dans un programme à l'aide de l'instruction #include.

Ou alors, il y a aussi la possibilités de place la librairie dans ton répertoire sketchbook, il faut la dézipper dans un dossier /libraries/ que tu auras crée dans ton répertoire sketchbook

Ensuite, il faut quitter l'appli et la relancer pour que les nouvelles librairies soient prises en compte lors de la compilation.

Souvent, les librairies intègrent des sketches d'exemple qui seront accessibles (une fois la lib installée) par le menu File/examples du GUI arduino.

Et je ne savais même pas qu'il y avait une version fr de l'arduino, par curiosité tu l'as trouvée ou ?

Par ailleurs, je te conseille plutôt de travailler avec la version à jour du GUI (22 à ce jour) les évolutions du GUI corrigent bien des bugs.

Lionel


- Distributeur officiel Arduino - Conception de shields qui s'empilent

Merci Lionel.

Je tenterais l'opération (et celle de Jean François) ce WE

Pour le soft en Français, b'en t'étais pas loin :
http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.TelechargerArduinoFrancais
(version 018)

Voilà

Bonjour,

En ce qui concerne les libraries ajoutées, j'avais fait comme conseillé sur le site Arduino mais à la 1ere mise à jour du logiciel Arduino comme j'ai créée un nouveau répertoire pour la nouvelle version et que je n'avais pas copié le dossier "libraries", je ne pouvais plus compiler mes projets. J'ai cherché un moment avant de comprendre pourquoi ça ne marchait plus. >:(
Maintenant j'ai un dossier "librairies" dans le dossier sketchbook et j'y installe les librairies et plus de problème pour les mises à jour Arduino.

Serge

Sur dix heures d'essais, j'ai une dérive de 2 secondes.

J'ai une autre solution un peu plus simple, qui devrait fonctionner sans dérive :

// Déclaration des variables et constantes horloge

unsigned long refTemps; // echeance placee dans le futur

int jour=19; // Jour de l'horloge
int heure = 18; // Heures de l'horloge
int minute = 6; // Minutes de l'horloge
int seconde = 30; // Secondes de l'horloge

void setup() {

// Initialise la liaison série
Serial.begin(19200);

}

void loop() {

if (millis()>= refTemps){ // si millis est egal ou plus grand que l'echeance

refTemps+=1000; //la derniere echeance + 1 seconde
seconde++; // on additionne 1 seconde

if(seconde>=60){ // lorsque les secondes arrivent a 60
minute++; // on additionne 1 minute
seconde=0; // et on remet les seconde a zero
if(minute>=60){ // lorsque les minutes arrivent a 60
heure++; // on additionne 1 heure
minute=0; // et on remet .... etc, etc
if(heure>=24){
jour++;
heure=0;

}
}
}
}

// on envoie dans le moniteur serie le jour, l'heure, les minutes et les secondes....
Serial.print(jour);
Serial.print(" ");
Serial.print(heure);
Serial.print(" ");
Serial.print(minute);
Serial.print(" ");
Serial.println(seconde);
delay(100);
}

La précision de l'affichage de la seconde doit être dans le 1/100 de seconde, mais dans la globalité on est précis, car l'erreur ne se cumule pas et on réajuste environ toutes les secondes sur une référence qui est exacte.

Après quatre heures d'essai(pour le dernier code), ça marche.... dans une moyenne.

Pendant une minute on retarde et la minute suivante on avance (de l'ordre de la 1/2 seconde) et ainsi de suite, donc en moyenne c'est "précis". :wink:

Edit: Après 13 heures de test j'ai une avance de 3 secondes due à la précision du quartz et du Mcu.

Bonjour

3" sur 13 heures... Et moi qui pensais que la précision Suisse était indiscutable ;D ;D

Cela devrait me suffire. Car j'ai commandé un capteur photo-résistif. Je recalerais l'horloge au moment de la détection de l'allumage des HQI (éclairage du bac).

Cependant, je me demande comment cela se passera au débordement des variables "refTemps" et millis() puisqu'à aucun moment refTemps n'est réinitialisée.

Edit :
Si débordement de RefTemps avant millis : RefTemps < millis() = Pas de soucis
Si débordement de millis avant refTemps : RefTemps > millis = reste à traiter le cas car l'incrémentation ne se fait plus.

if millis() < 1000 {refTemps = 0} ;

En tous cas merci à tous. Je vous tiendrais au courant de l'évolution

Franck

Lorsque la variable refTemps revient à zéro au pire tu as un décalage de 1 seconde... mais ça n'arrive que tout les 50 jours.

Je rencontre aussi un problème de dérive de millis().

J'avais fait un premier montage simple avec une duelmilanove. Je commande 2 nouvelles UNO et le décalage me parait beaucoup plus grand et quasi identique sur les 2 nouvelles que sur l'ancienne.

Le problème provient du quartz, ceux pour les horloges ou pour les mesures du temps sont de 32.768khz.

Si on divise 32'768 par 2, par 2, par 2, par 2..... etc etc on arrive finalement à 1.

Même opération avec 16Mhz, on divise par 2, par 2, par 2,par 2... etc etc finalement on tombe sur un nombre à virgule, donc imprécis.

Une possibilité pour ceux qui ne veulent absolument pas utiliser un DS1307, faire ce montage :

Avec ce code :

// Déclaration des variables et constantes horloge

int jour=19; // Jour de l'horloge
int heure = 18; // Heures de l'horloge
int minute = 6; // Minutes de l'horloge
int demiSeconde = 30; // Secondes de l'horloge

void setup() {

// Initialise la liaison série
Serial.begin(19200);
attachInterrupt(1, TicTac, RISING);

}

void loop() {

if(demiSeconde>=120){ // lorsque l'on a 120 demi-secondes
minute++; // on additionne 1 minute
demiSeconde=0; // et on remet les seconde a zero
if(minute>=60){ // lorsque les minutes arrivent a 60
heure++; // on additionne 1 heure
minute=0; // et on remet .... etc, etc
if(heure>=24){
jour++;
heure=0;

}
}
}

// on envoie dans le moniteur serie le jour, l'heure, les minutes et les secondes....
Serial.print(jour);
Serial.print(" ");
Serial.print(heure);
Serial.print(" ");
Serial.print(minute);
Serial.print(" ");
Serial.println(demiSeconde/2); // on divise par deux pour avoir les secondes

}

void TicTac() // a chaque tic du diviseur de frequence + 0,5 seconde
{
demiSeconde++; // a 2Hz il y a deux tics par secondes, donc toutes les 0.5 secondes
}

Par contre je ne sais pas ce que cela donne avec un code plus conséquent que simplement les Serial.print....

Mais ça donne déjà une petite idée :), tel quel le sketche fait 2542 bytes.

Aussi il est possible de remettre un deuxième CD4060 et diviser les Hz afin d'avoir un tic toutes les secondes, 2 secondes, 4 secondes...etc.(après 4 c'est plus un sous multiple de 60, donc pour une base de temps ça ne sera pas idéal ::)).

Il faut faire rentrer le signal qui sort du premier en le mettant sur la pin 11 du deuxième. :wink: