Countdown Timer e misura del tempo imprecisa su Arduino Uno

Ciao,

ho creato un piccolo countdown timer usando dei led ws2812 e un bottone per avviare il countdown.

Ho usato questa libreria: GitHub - inflop/Countimer: This is simple timer and counter Arduino library. che mi sembra buona e mi fornisce anche la possibilità di richiamare ogni tot una funzione che uso per aggiornare le luci che mostrano il tempo trascorso.

Lo sketch che sto usando è il seguente ed è la base di un progetto più ampio per la creazione di un tabellone segnapunti. Implementa anche una matrice di bottoni per la gestione di più eventi. Al momento l'unico utilizzato è il bottone P che serve a fare Start/Pause del timer.

#include <Keypad.h>
#include <Adafruit_NeoPixel.h>
#include "Countimer.h"

#ifdef __AVR__
  #include <avr/power.h>
#endif

#define PIN            10
#define NUMPIXELS      50
#define BUZZER         11

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_RGB + NEO_KHZ800);

const byte ROWS = 3; //four rows
const byte COLS = 3; //four columns
//define the cymbols on the buttons of the keypads
char hexaKeys[ROWS][COLS] = {
  {'L','l','P'},
  {'O','o','S'},
  {'T','t','M'}
};

byte rowPins[ROWS] = {3, 4, 5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6, 7, 8}; //connect to the column pinouts of the keypad

int charLayout[10][15] = {
{1,1,1,1,0,1,1,0,1,1,0,1,1,1,1}, // zero
{1,1,1,0,1,0,0,1,0,1,1,0,0,1,0}, // one
{1,1,1,1,0,0,1,1,1,0,0,1,1,1,1}, // two
{1,1,1,0,0,1,1,1,1,0,0,1,1,1,1}, // three
{1,0,0,0,0,1,1,1,1,1,0,1,1,0,1}, // four
{1,1,1,0,0,1,1,1,1,1,0,0,1,1,1}, // five
{1,1,1,1,0,1,1,1,1,1,0,0,1,1,1}, // six
{0,1,0,0,1,0,0,1,0,0,0,1,1,1,1}, // seven
{1,1,1,1,0,1,1,1,1,1,0,1,1,1,1}, // eight
{1,1,1,0,0,1,1,1,1,1,0,1,1,1,1}  // nine
};

//initialize an instance of class NewKeypad
Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS); 

// Initialize timer
Countimer tDown;

void toggleLocation(int loc, int ledState){
  if(ledState==0){ 
    pixels.setPixelColor(loc, pixels.Color(0,0,0)); //off
  } else{
    pixels.setPixelColor(loc, pixels.Color(255,0,0)); // red
  }
}

void showNumber(int number){
    for(int k=0;k<15;k++){
      toggleLocation(k, charLayout[number][k]);
    }
    pixels.show();
}

void number_display(){
    showNumber(tDown.getCurrentSeconds() % 10);
}

void buzz(){
  showNumber(0);
  Serial.println(tDown.getCurrentSeconds());
  digitalWrite(BUZZER, HIGH);
  delay(3000);
  digitalWrite(BUZZER, LOW);
}

void setup(){
  pixels.begin(); 
  Serial.begin(9600);
  tDown.setCounter(0, 0, 60, tDown.COUNT_DOWN, buzz);
  tDown.setInterval(number_display, 1000);
  pinMode(BUZZER, OUTPUT);
}
  
void loop(){
  tDown.run();
  char customKey = customKeypad.getKey();
  if (customKey){
    Serial.println(customKey);
    switch (customKey){
      case 'P':
        tens = 0;
        if (!tDown.isStopped() && !tDown.isCounterCompleted()) {
          tDown.pause();
        } else {
            tDown.start();
        }
        break;
    }
  }
}

Ho notato una cosa piuttosto sgradevole sul conteggio del tempo: impostando il timer per il countdown di 60 secondi e avviando contemporaneamente il cronometro sul cellulare, mi trovo che il buzzer comincia a suonare pochi decimi di secondo prima dello scattare dei 59 secondi.
Il valore che vedo rappresentato sul mio pannello è 0, quindi il tempo, per arduino, risulta essere correttamente scaduto con un anticipo di ben 1 secondo.

Immagino che non si possa pretendere una gestione precisa del tempo da parte di arduino, ma 1 secondo ogni minuto diventa un problema per me che devo impostare il countdown a 7 o 10 minuti.

C'è qualche tipo di problema nel codice o nella libreria che ho scelto? Esistono librerie "migliori" o più "solide" di quella che ho utilizzato?

In alternativa che si può fare per migliorare la precisione in modo che arduino non si disallinei così rapidamente? (per esempio 1 secondo ogni 10 minuti è accettabile).

Grazie

Luca

D'accordo che il timer interno di Arduino non è precisissimo, ma 1 secondo ogni 60 mi pare effettivamente troppo... Se non ricordo male, la precisione del clock di Arduino (senza quarzo) è <=0,5% quindi in un minuto dovrebbe sbagliare AL MASSIMO di 3 decimi di secondo.

In ogni caso la soluzione sarebbe usare un RTC, che non ha risoluzione dei decimi ma almeno ha un oscillatore al quarzo.

Poi io userei meglio la memoria variabili. Un array di 150 interi che devono contenere solo 0 o 1 non mi sembra una scelta interessante, specie er un progetto in ampliamento. Si potrebbero usare byte o direttamente un array monodimensionale di 10 interi, lavorando sui bit.
...e questo era solo il primo che mi é venuto sott'occhio, credo ce ne siano molti altri
......non risolverà il priblema dell'anticipo, ma potrebbe evitare futuri problemi di memoria

docdoc:
In ogni caso la soluzione sarebbe usare un RTC, che non ha risoluzione dei decimi ma almeno ha un oscillatore al quarzo.

Forse, per quello che gli occorre (se ho ben capito, tempi precisi al decimo), potrebbe, in prima battuta buttare via la libreria ed usare LUI, nel modo migliore, la funzione millis() ... e secondo me già risolve ... se poi occorre ancora più precisione, un oscillatore termostabilizzato esterno, di quelli ad alta precisione ed un interrupt con cui conta gli impulsi e calcola il tempo.

Guglielmo

Silente:
Poi io userei meglio la memoria variabili. Un array di 150 interi che devono contenere solo 0 o 1 non mi sembra una scelta interessante, specie er un progetto in ampliamento. Si potrebbero usare byte o direttamente un array monodimensionale di 10 interi, lavorando sui bit.
...e questo era solo il primo che mi é venuto sott'occhio, credo ce ne siano molti altri
......non risolverà il priblema dell'anticipo, ma potrebbe evitare futuri problemi di memoria

Hai ragione, ci lavorerò. Diciamo che avevo scopiazzato da un altro sketch. Temevo però di ricadere in questo caso: xkcd: Optimization :slight_smile:

gpb01:
Forse, per quello che gli occorre (se ho ben capito, tempi precisi al decimo), potrebbe, in prima battuta buttare via la libreria ed usare LUI, nel modo migliore, la funzione millis() ... e secondo me già risolve ... se poi occorre ancora più precisione, un oscillatore termostabilizzato esterno, di quelli ad alta precisione ed un interrupt con cui conta gli impulsi e calcola il tempo.

Guglielmo

Ciao,

ci avevo provato con millis ma non ricordo i risultati di affidabilità del tempo.
Avevo cercato una libreria per praticità ed evitare di scrivere codice aggiuntivo per:

  • aggiornare il valore visualizzato dai led ogni secondo
  • gestire la situazione in cui viene messo in pausa il tempo e quando riprende
  • effettuare il callback (far suonare il buzzer) quando scade il tempo.

Posso prendere sempre ispirazione dal codice altrui, ma alla fine credo l'implementazione sarà più o meno la stessa.

Provo con millis questa sera a vedere cosa succede se il conteggio è più preciso.

Luca

gpb01:
Forse, per quello che gli occorre (se ho ben capito, tempi precisi al decimo)

Beh intanto non so cosa faccia quella libreria, ma se si basa su millis() questo, come detto, normalmente ha una precisione relativamente bassa, se in un minuto sbaglia di 3 decimi di secondo siamo più o meno nelle misurazioni effettuate dall'OP e che ritiene non accettabili.

Per l'RTC, beh, non è che gli serva conoscere i decimi di secondo, ma che la misurazione del tempo sia precisa, ed un RTC lo è: anche se non risolve meno di un secondo, quando sono passati 10 minuti, sono 10 minuti, senza imprecisioni (sicuramente ben inferiori al millis() visto che L'RTC ha un quarzo).

Per cui al posto suo userei un RTC, al limite facendo correzioni sui decimi ossia ad es. se faccio "start" alle ore 10:00:00 più 4 decimi, il minuto successivo deve scattare a 10:00:01.4: quindi quando parte mi basta contare quanti millisecondi passano prima del cambiamento del primo secondo rilevato dal RTC, e questi li tengo in memoria e devo sommarli al tempo finale.
In alternativa se premo "start" a 10:00:00.4 la sirena di inizio (e quindi il countdown) suonerà alle 10:00:01 e terminerà alle 10:01:01, tanto l'importante è il tempo dalla sirena di start e quella di fine, immagino...

docdoc:
Beh intanto non so cosa faccia quella libreria, ma se si basa su millis() questo, come detto, normalmente ha una precisione relativamente bassa, se in un minuto sbaglia di 3 decimi di secondo siamo più o meno nelle misurazioni effettuate dall'OP e che ritiene non accettabili.

Si, usa millis, come praticamente tutte le librerie che fanno questo tipo di lavoro. Sbagliare 3 decimi di secondo ogni minuto non
è così male, significa che in 10 minuti andrebbe a sbagliare di 3 secondi. Io sono in una situazione molto più grave, invece, perchè sbaglia di oltre 1 secondo ogni minuto! Il che significa circa 12 secondi ogni 10 minuti, che è sufficiente per segnare un goal!

docdoc:
Per l'RTC, beh, non è che gli serva conoscere i decimi di secondo, ma che la misurazione del tempo sia precisa, ed un RTC lo è: anche se non risolve meno di un secondo, quando sono passati 10 minuti, sono 10 minuti, senza imprecisioni (sicuramente ben inferiori al millis() visto che L'RTC ha un quarzo).
Per cui al posto suo userei un RTC, al limite facendo correzioni sui decimi ossia ad es. se faccio "start" alle ore 10:00:00 più 4 decimi, il minuto successivo deve scattare a 10:00:01.4: quindi quando parte mi basta contare quanti millisecondi passano prima del cambiamento del primo secondo rilevato dal RTC, e questi li tengo in memoria e devo sommarli al tempo finale.
In alternativa se premo "start" a 10:00:00.4 la sirena di inizio (e quindi il countdown) suonerà alle 10:00:01 e terminerà alle 10:01:01, tanto l'importante è il tempo dalla sirena di start e quella di fine, immagino...

A me interessa che il tempo di inizio e fine sia corretto e anche il tempo visualizzato dal tabellone. Ci ho perso una partita perché pensavo mancasse un minuto e invece erano solo 20 secondi (e non avevo un tabellone con il tempo da guardare).

Esistono altre soluzioni, anche software, oltre all'RTC per migliorare la precisione?

Luca

Comincia a usare millis() dirrttamente, senza passare per librerie.
Poi risolvere una limitazione hw in modo sw non é facile. Hai una proma strada che é ridurre i tempi il più possibile (più il loop() gira veloce minore sarà l'errore). A questa poi puoi unire una scelta brutale e brutta: dopo aver testato varie volte che 60 secondi ne durano 59 dici al programma che un minuto sono 61 secondi... Io lo ho detto che é brutta.
I problemi hw si risolvono hw

Silente:
... A questa poi puoi unire una scelta brutale e brutta: dopo aver testato varie volte che 60 secondi ne durano 59 dici al programma che un minuto sono 61 secondi... Io lo ho detto che é brutta.
I problemi hw si risolvono hw

Il problema che l'errore NON è sistematico, ma variabile e dovuto alla scarsità di precisione del risuonatore ceramico (...sull'ATmega328P NON c'è un quarzo) ed alla sua sensibilià alle variazioni di temperatura.

Se si vuole precisione su tempi di svariati minuti, NON c'è altra scelta, occorre un HW esterno adatto.

Guglielmo

Ciao,

purtroppo ieri non sono riuscito a fare delle prove. Ho trovato però questo:

Qualcuno di voi l'ha mai provato? Vi sembra una soluzione fattibile per ridurre la deriva del clock?

Luca

mi sembra che sia un falso rimedio, se il clock di partenza non è preciso -> il segnale pwm non sarà preciso --> e quindi il risultato finale non sarà preciso
se la lancetta dei secondi va piano non è che prendendo come riferimento quella dei minuti sia più precisa

Patrick_M:
mi sembra che sia un falso rimedio, se il clock di partenza non è preciso -> il segnale pwm non sarà preciso --> e quindi il risultato finale non sarà preciso

Vero, ma ... seguono strade, divisori diversi, quindi errori diversi ... e, da prova fatta, già solo dopo 10 minuti i secondi contati con la classica millis() erano indiatro di 1 secondo rispetto a quelli contati con quel sistema ... ::slight_smile:

Quale dei due indicasse i secondi corretti? ... al momento non lo so; appena ho un attimo di tempo, metterò un RTC e farò anche questa verifica su tempi lunghi. :wink:

Guglielmo

gpb01:
Quale dei due indicasse i secondi corretti? ... al momento non lo so; appena ho un attimo di tempo, metterò un RTC e farò anche questa verifica su tempi lunghi.

Nessuno dei due :smiley: , anzi, il metodo proposto è quello che produce l'errore più grande ...
... risultati dopo 4580 secondi:

RTC: 4580 sec.  -  Millis: 4579 sec.  -  PWM + Int: 4590 sec.

Guglielmo

P.S.: ... lo lascio ancora girare ... poi stasera vi aggiorno :grin:

Ah ... per inciso ... ma lo avete guardato lo schema che quello propone ? ? ? :o :o :o

Secondo lui, l'uscita PWM è sui ... pin analogici !!! :smiley: :smiley: :smiley: Quello NON lo ha mai né provato né, tanto meno, realizzato, altrimenti se ne sarebbe accorto.

Io l'ho voluto provare usando il PWM prelevato dal pin 3 di Arduino UNO e mandato sul pin 2 (INT0) ...
... come avete visto dai precedenti risultati ... è una bojata pazzesca :grin: :grin: :grin:

In una parola ... "Bullshit!" !!!

Guglielmo

P.S.: Quando arrivo alle 2 ore (7200 sec) vi riporto i nuovi risultati :wink:

... ecco qui:

RTC: 7200  -  Millis: 7199  -  PWM + Int: 7216

... fine della storia :smiley:

UTILIZZA UN RTC !!! :grin: :grin: :grin:

Guglielmo

Beh, 1 secondo di difetto in 2 ore mi sembra più accettabile, provo a ripercorrere la strada millis().

Eventualmente, un RTC da consigliare?

Non ho bisogno di cose miracolose, il massimo del tempo che andrò a misurare sono 15 minuti.

Luca

remix_tj:
Eventualmente, un RTC da consigliare?

DS3231 ... è il più preciso e, ormai, te li tirano dietro ... guarda QUI ;D

Guglielmo

cioè pwm +int è peggiore rispetto a millis()? :smiley: :smiley:

me pareva strano...

gpb01:
DS3231 ... è il più preciso e, ormai, te li tirano dietro ... guarda QUI ;D

Guglielmo

Mi hai convinto. Credevo costassero di più! ne ho ordinato 2 al negozio qui vicino casa, domani li vado a prendere.

Comunque prima faccio anche un test della deriva di millis per capire se si tratta di una questione legata al mio hardware o alla libreria.

Grazie, siete proprio preziosi per gli impediti come me :slight_smile: