Preciosione al centesimo di secondo millis()

Salve ragazzi,
ho creato questo programma che fa un countdown dei secondi.
Siccome ho bisogno di una precisione nell'ordine del centesimo di secondo, volevo capire se il mio listato puo essere affetto da imprecisioni (che purtroppo so che ci sono per confronto con altri cronometri di precisione) oppure se tale imprecisione è dovuta ad un limite hardware.
Per darvi una idea: in due minuti perdo anche fino a mezzo secondo!
Grazie in anticipo per delle risposte.

while (settore[j]>=0 && j<=npc)
                 {
                  if (millis()>=tempo+1000)
                    {
                      tempo=millis(); settore[j]--; //decremento i secondi
 
                      
                      if(settore[j]<=10)  tone(t, NOTE_B5, 50);  
                    }
                  lcd.setCursor(0, 0); lcd.print(settore[j]);           
                  
                      if (j>1)
                         {lcd.setCursor(0, 3); lcd.print(scarto[j-1]); lcd.print("    "); lcd.setCursor(12, 3); lcd.print("Scarto "); lcd.print(j-1); }
                  
                  if ((digitalReadFast(split)==1) && (f1 == 0))   
                    {   lcd.setCursor(0, 2); lcd.print("        ");      
                       push[p]=millis(); 
            if ((push[p]-push[p-1])>1500) 
              {          
                  
                       a=EEPROM.read((30+((i-1)*20))+j);  
                       scarto[j]=push[p]-push[p-1]-(a*1000);                     
                       p++;                                                     
                       f1 = 1; f2++;
                                              
                       if (f2==1) { settore[j]++; f2++; }    
                       else       { j++; settore[j]++;  }
                       tone(t, NOTE_C5, 50); tempo=0; 
                       lcd.setCursor(12, 0); lcd.print("PC "); lcd.print(j); lcd.print("/"); lcd.print(npc);
              } else if (p==1) p=0;                   //non funziona solo il controllo del primo PC
                    }      
                  if ((digitalReadFast(split)==0) && (f1 == 1))     f1 = 0; 
                  
                  while (settore[j]==0)
                       {
                         if ((digitalReadFast(split)==1) && (f1 == 0))   
                           {   
                            push[p]=millis(); 
                            a=EEPROM.read((30+((i-1)*20))+j);
                            scarto[j]=push[p]-push[p-1]-(a*1000); 
                            p++;
                            
                            f1 = 1; tempo=0; 
                            j++; settore[j]++;  
                            tone(t, NOTE_C5, 50);
                            lcd.setCursor(12, 0); lcd.print("PC "); lcd.print(j); 
                           }
                         if ((digitalReadFast(split)==0) && (f1 == 1))     f1 = 0;
                       }
                 if(settore[j]<10)  {lcd.setCursor(1, 0); lcd.print (" ");} //pulizia display seconda cifra
                 if(settore[j]<100) {lcd.setCursor(2, 0); lcd.print (" ");} //pulizia display terza cifra 
               }

E' noto che il conteggio con millis() non è preciso

E poi se usi Arduino UNO il risuonatore ha una precisione del 0,5%. Percui mezzo secondo su 60 secondi ci siamo quasi.

Ciao Uwe

Quello che non fa l'imprecisione dell'oscillatore lo fa questo:

                if (millis()>=tempo+1000)
                    {
                      tempo=millis(); settore[j]--; //decremento i secondi

Anche tralasciando il problema dell'overflow dei 50 giorni che imperversa come un demone sul forum (costa così tanto scrivere [color=red]millis()-tempo>=1000[/color] ?:drooling_face:) quel tempo=millis() successivo accumula ogni volta l'errore esistente tra il momento ideale dello scadere del periodo e quello in cui la condizione viene effettivamente valutata.

Se si vogliono tempi periodici il più possibile precisi la variabile 'tempo' deve sempre essere incrementata esattamente del periodo voluto, e non portata all'attuale valore di millis() che può essere anche diversi millisecondi più avanti.

In questo modo si ha comunque (ma è inevitabile) un piccolo jitter del momento di riconoscimento rispetto all'ideale, ma non si accumula niente e l'imprecisione rimane solo quella dell'oscillatore.

Ha perfettamente ragione Claudio_FF ...

... non so da dove ora sia sbucata questa, totalmente sbagliata moda, di fare il confronto su millis() come vedo sopra (nel primo post), ma la forma esatta è quella riportata da Claudio_FF ed anche la logica di incremento del intervallo.

Prova a cambiare quelle righe in:

if (millis() - tempo >= 1000)
{
   tempo += 1000; settore[j]--;
   if(settore[j]<=10)  tone(t, NOTE_B5, 50);  
}

... e vedi come va.

Guglielmo

Credo che l'uso "fantasioso" del confronto tra millis e il valore precedente memorizzato in una variabile deriva dall'esempio "blink without delay", se non serve ottimizzare la precisione va bene anche in quel modo, altrimenti il modo corretto è indubbiamente fare il confronto tra l'attuale valore della millis() e la quantità di tempo totale che deve passare previo incremento fisso della variabile di controllo.

Volendo si può sintetizzare il tutto anche in questo modo:

if(millis() >= controllo) 
{
 controllo += intervallo;
// codice da eseguire
}

Però se serve realmente una precisione al centesimo di secondo questa si ottiene solo usando direttamente un timer, con relativi interrupt, preferibilmente il timer1 a 16 bit con prescaler a 1/8, in questo modo ogni count vale 0.5 uS, 20 count = 1/100 di secondo.
Basta tenere conto degli overflow, dal momento in cui si partire il cronometro, ovvero il timer, avvengono ogni 32,768 ms, così da avere il moltiplicatore per ottenere il tempo finale cronometrato.
In pratica occorre inizializzare il timer1 come contatore con il clock settato tramite prescaler 1/8, attivare l'interrupt su overflow (TIMSK1 bit TOIE1), quando il cronometro parte si avvia il timer e nella ISR per l'overflow si incrementa un contatore ogni volta che viene attivata, ovviamente sia il timer che il contatore vengono azzerati al momento dell'avvio.
Quando il conteggio del tempo viene stoppato basta prendere il valore del contatore overflow, moltiplicarlo per 32768, sommare il valore del timer al momento dello stop, dividere il valore ottenuto per 20 ed ecco il tempo in 1/100 di secondi preciso.
Ovviamente se si usa un Arduino con clock non quarzato è necessario inserire un valore di correzione, da determinare tramite misura diretta del clock (serve un frequenzimetro o un DSO dotato di frequenzimetro hardware) oppure empiricamente, per correggere il tempo ottenuto.
In alternativa si può usare un clock esterno, ideale da 1 MHz, per il timer1, per un paio di Euro è possibile acquistare dei precisi oscillatori quarzati come questo.

astrobeed:
... Volendo si può sintetizzare il tutto anche in questo modo:

... modo che però "soffre" del problema dell'overflow di millis() al raggiungimento di 0xFFFFFFFF, cosa che invece, con l'altro metodo è ininfluente.

Ovviamente dipende se parliamo di una cosa accesa 10 minuti o .. accesa per mesi e mesi :wink:

Guglielmo

anche controllo va in overflow

comunque detto come eliminare l' errore dato dal confronto sbagliato di millis() resta l' errore dato dal clock inpreciso visto che l'arduino UNO non ha un quarzo ma un risuonatore che é molto piú impreciso.
È da considerare di usare un leonardo, un vecchio 2009 oppure usare un oscillatore a quarzo esterno come propone astrobeed.
Ciao Uwe

anche controllo va in overflow

Certo, ma ci sarà un lasso di tempo in cui millis() non lo è ancora, e in quel lasso di tempo la condizione risulta sempre vera come se il tempo fosse già trascorso.

È da considerare di usare un leonardo, un vecchio 2009 oppure usare un oscillatore a quarzo esterno come propone astrobeed.

Oppure usare una sorgente 100Hz esterna precisa a cui "agganciare" l'avanzamento del software.

Ragazzi, intanto grazie a tutti per le risposte.
da quello che mi dite, mi sto imbattendo nei meandri di arduino e purtroppo al momento credo di non avere la preparazione necessaria per affrontare la cosa, specie leggendo la risposta di astrobeed.
Spero di avere il tempo di studiare le vostre risposte in modo da capire quale mi conviene implementare.
A tal proposito magari mi potete dare una ulteriore dritta specificando che:

  • tale apparecchio misurerà tempi al max dell'ordine dei minuti.
  • generalmente misuro tempi entro i 30 secondi.
  • uso arduino nano

Grazie sempre a tutti.

devi solo adoperare il blink degli esempi ..............

if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;

// if the LED is off turn it on and vice-versa:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}..........

ma dopo devi "tararlo" va bene anche l orologio del pc come verifica
ti segni l ora del pc esatta e quella del contatore ricontrolla dopo 2-3 ore
se arduino è avanti abbassi di qualche unita il valore interval
se arduino è indietro alzi di qualche unita il valore interval

puoi ottenere un orologio molto preciso con un po di pazienza

Il blink degli esempi non tiene conto dell'accumulo dell'errore.... insignificante per l'esempio stesso, errato se si vuole un periodo preciso.

Si è già detto tutto.

Di millis() bisogna sapere solo DUE cose

  1. il tempo trascorso si calcola con millis()-tempoiniziale

  2. se si vuole creare un processo periodico, a tempoiniziale va sommato il periodo e NON va impostato al valore attuale di millis()

Basta, solo queste, e non c'è più da parlare né di overflow, né di errori accumulati ogni ciclo, né di reset di millis o calcoli vari.

Fatto ciò rimane solo l'imprecisione dell'oscillatore, e per questa, oltre ad un intervento hardware, allora può stare bene anche una piccola calibrazione a tentativi (ma cambiando scheda va rifatta).

Pero', senza un frequenzimetro, la calibrazione per tentativi potrebbe richiedere un certo tempo ... poi, siamo sicuri che l'RC interno del 328P non soffra piu di tanto della deriva termica o non cambi eccessivamente frequenza se il micro si scalda o e' freddo o se la temperatura ambiente cambia ? ...

Non di per se, ma perche' lui chiede precisione al centesimo di secondo, serve vedere se anche un paio di centesimi gli creano problemi ... in questo caso mi associo pure io al suggerimento di Astro di usare un clock esterno, ma pure 1MHz, magari esagerando lo si puo persino trasformare in un sistema termostatato mantenuto a 40 gradi o poco piu da un transistor di potenza che fa da riscaldatore, controreazionato da una NTC, per rimanere sull'economico (o magari esageriamo e ci mettiamo pure un'operazionale per aumentare la precisione del termostato, volendo :D)

ho provato ad eseguire la parte di programma suggerita da Guglielmo e devo dire che la situazione è migliorata tantissimo :slight_smile: credo di avere annullato buona parte dell'errore, grazie molte per la dritta.
Sto capendo meglio adesso come funziona millis(), mi sorge solo un dubbio:

Come posso modificare il programma in modo da far partire il conteggio alla chiusura di un interruttore?
Ho provato a farlo ma non riesco a "congelare il valore" appena chiudo l'interruttore al valore da cui parte il conteggio, viene sottratto il tempo in cui arduino è stato acceso... :confused:

Grazie a tutti

domix1684:
ho provato ad eseguire la parte di programma suggerita da Guglielmo e devo dire che la situazione è migliorata tantissimo :slight_smile: credo di avere annullato buona parte dell'errore, grazie molte per la dritta.

:smiley: Ringrazia Claudio_FF, io sono intervenuto dopo, per sviluppare il pezzetto di codice che lui ti ha suggerito (... e che altro non è che il modo corretto di fare le temporizzazioni con millis()) :wink:

Guglielmo

domix1684:
... non riesco a "congelare il valore" appena chiudo l'interruttore al valore da cui parte il conteggio, viene sottratto il tempo in cui arduino è stato acceso... :confused:

Grazie a tutti

Se intendi millis, non puoi "congelarla", e' una funzione che si limita a restituire il numero di millisecondi dall'accensione di Arduino ... pero' se ti serve "il valore di millis nel momento in cui chiudi un'interruttore", puoi sempre usare un ciclo if ed una flag ... leggi l'ingresso dell'interruttore e controlli con if se e' chiuso "E" se la flag e' a zero, nel momento in cui la condizione e' vera, metti il valore di millis in una unsigned long e setti la flag ad uno (in modo che non continui ad aggiornarla per tutto il tempo che rimane chiuso) ... in quel momento la unsigned long conterra' il valore che aveva millis nel momento in cui hai chiuso l'interruttore, e potrai usarlo per altri confronti ... poi con un secondo if "inverso", quando apri l'interruttore resetterai la flag per poterla riutilizzare ancora ... almeno, se e' questo che vuoi fare ... :wink:

Ragazzi vi chiedo solo un altro piccolo aiuto, purtroppo una volta chiuso l'interruttore non riesco a fare partire il conteggio dal valore desiderato.
Come posso fare a "non tenere conto" del tempo trascorso dall'accensione fino alla chiusura del tasto, senza modificare la condizione:

if (millis() - tempo >= 1000)
{
   tempo += 1000; 
   secondi--;
  
}

Grazie a tutti per la pazienza

ciao

direi che "alla chiusura del tasto" la variabile tempo assume il valore di millis()

ciao
pippo72

Grazie Pippo72 per la risposta,
pero' la tua affermazione va in contrasto con quanto detto prima da Claudio_FF e cioè:

Di millis() bisogna sapere solo DUE cose

  1. il tempo trascorso si calcola con millis()-tempoiniziale

  2. se si vuole creare un processo periodico, a tempoiniziale va sommato il periodo e NON va impostato al valore attuale di millis()

Basta, solo queste, e non c'è più da parlare né di overflow, né di errori accumulati ogni ciclo, né di reset di millis o calcoli vari.

:confused: