Consiglio utilizzo millis

Buongiorno, nel mio progetto vorrei creare un contaore utilizzando la funzione millis.

Il programma è questo:

unsigned long previousMillis = 0;
unsigned long interval = 60000;
unsigned long currentMillis;

byte minuti = 0;
unsigned int ore = 0;
void setup() {
Serial.begin(9600);
Serial.println("inizio");
}

void loop() {
   calcora();
   delay(20);
}
void calcora(){
   currentMillis = millis();
  if(currentMillis - previousMillis > interval) {
     previousMillis = currentMillis; //save the last time you blinked the LED
    minuti++;  
    if(minuti == 60){
       ore++;
       minuti = 0; 
     }
     Serial.println("Ore trascorse = " + String(ore) + ":" + String(minuti));
  }
}

Secondo voi può funzionare in modo corretto oppure potrei incorrere in qualche problema di troppo ?

Di sicuro, minuti++; va PER FORZA prima di quell'if che verifica se ha raggiunto 60

Millis non e' molto preciso, perche' dipende dalla precisione del clock della MCU, che non e' il massimo, specie se usa un risuonatore ceramico o il clock interno ... se ci devi contare intervalli brevi in modo saltuario, o anche un'intervallo relativamente lungo ma non ripetitivo su periodi di giorni, puo andare, ma se ci dovessi fare, ad esempio, un contatore o timer programmabile su tempi di parecchi giorni o un'orologio, l'errore sarebbe eccessivo ...

nid69ita ti ringrazio per la segnalazione.

Etemenanki io utilizzo un oscillatore esterno di 16 mhz. Prendendo come esempio quello che hai detto tu, "un'intervallo relativamente lungo", il codice sopra descritto può andare bene ? Devi far conto che ogni 15 minuti mi vado a memorizzare nella EEprom i minuti trascorsi e per ogni ora vado a memorizzare le ore totali.

fabio22:
… Devi far conto che ogni 15 minuti mi vado a memorizzare nella EEprom i minuti trascorsi e per ogni ora vado a memorizzare le ore totali.

… e tu devi fare conto che i ‘minutiNON sono ‘minuti’ ma sono 'minuti +/- secondi’ e la stessa cosa è per le ore.

Se non ti serve una esatta ripetitività, ma è una cosa una tantum, va bene, ma se è una cosa che deve ripetersi ad intervalli regolari, tutti i giorni … ti occorre necessariamente un RTC.

Guglielmo

Guglielmo mi ha preceduto, ma comunque volevo chiarire solo una cosa ... se per contaore intendi un timer ciclico, cioe', ogni giorno devi contare tot ore/minuti o ripetere ciclicamente alcune operazioni per parecchi giorni, allora si, ti serve un'RTC come ha detto lui ...

Se invece per contaore lo intendi nel senso "classico" del termine, cioe' un qualcosa che conta il tempo cumulativo in cui un'applicazione, un macchinario o un'operazione e' in funzione, anche se fra piu giorni, allora se non ti serve la precisione ed alcuni secondi di scarto sono accettabili, potresti anche usare millis ... ricordando pero' che ogni 49 giorni e rotti (dove "e rotti" e' un'unita' di misura della scala spannometrica, internazionalmente riconosciuta :D) millis si resetta ... quindi se l'intervallo in cui effettuare la misura e' piu lungo, devi tenerne conto ...

Etemenanki:
… ricordando pero’ che ogni 49 giorni e rotti (dove “e rotti” e’ un’unita’ di misura della scala spannometrica, internazionalmente riconosciuta :D) millis si resetta … quindi se l’intervallo in cui effettuare la misura e’ piu lungo, devi tenerne conto …

Questo SOLO se ha un periodo di ciclo > 49gg … per qualsiasi altro ciclo la cosa è inifluente.

Ovvero se deve fare una cosa di X ore ogni Y giorni con Y > 49 allora ne deve tenere conto, ma se deve fare, anche all’infinito, una cosa di X ore/minuti ogni Y ore/giorni con Y < 49 gg. allora il problema NON esiste.

Guglielmo

Allora ragazzi, intanto vi ringrazio per l'interesse che state avendo per questa discussione.

Sotto troverete il codice aggiornato per evitare l'overflow:

unsigned long intervallo;
byte minuti;
unsigned int ore;
void setup() {
  intervallo = millis() + 60000;
  Serial.begin(9600);
  Serial.println("Ok");
}
void loop() {
  if ((long)(millis() - intervallo) >= 0) {
    minuti++;
    if (minuti == 60) {
      ore++;
      minuti = 0;
    }
    Serial.println("Ore trascorse = " + String(ore) + ":" + String(minuti));
    intervallo += 60000;
  }
}

Lo scopo principale e capire per quante ore o minuti il segnale in uscita rimane alto.
Ogni volta che il segnale da 0 passa a 1 ecco che comincio a contare i secondi e successivamente i minuti e le ore. Una volta che il segnale da 1 passa a 0 ecco che non entrerà più nella routine del millis

Questo è sbagliato:

if ((long)(millis() - intervallo) >= 0) { .... }

millis() DEVE essere unsigned long (come è, quindi leva quel cast) ... altrimentitutto il giochino NON funziona.

Sarà il caso che ti ristudi bene QUESTO articolo di Leo per capire il perché :wink:

Guglielmo

E comunque il modo corretto e più semplice per contare i minuti è questo:

unsigned long lastMillis = 0;
...
...
if (millis() - lastMillis >= 60000) {
   lastMillis += 60000;
   minuti++;
   if (minuti == 60) {
      minuti = 0;
      ore++;
      if (ore == 24) {
         ore = 0;
      }
   }
}

Guglielmo

... però ripeto che questo sistema da risulati errati già dopo poche ore e sicuramente dopo un giorno ore/minuti non è più esatto.

Ovvero, se serve un orologio che indichi ore e minuti, serve un RTC, se devo fare delle cose ogni CIRCA N ore allora no.

Guglielmo

Gugliemo l'artico da te riportato già l'avevo letto se pur con molta leggerezza:
Leo riporta questo esempio:
Ecco il trucco. Basta cambiare nel primo esempio il confronto in modo da verificare se la differenza fra il valore attuale di millis() e l’intervallo, trasformata in un tipo signed long, è inferiore a 0: se ciò è vero, significa che millis() è ripartito da 0 ed il test restituirà un numero negativo fino a quando millis() non avrà un valore maggiore di zero e di intervallo. Ecco un esempio di codice che non soffre del problema dell’overflow di millis():

void setup() {
    intervallo = millis() + 1000;
}
void loop() {
    if ((long)(millis() - intervallo) >= 0) {
        //codice da eseguire
        intervallo += 1000;
    }
}

… leggi la SOLUZIONE 2 che è la più corretta e più usata e la più efficiente, NON la 1 !

Guglielmo

ok grazie, ora cerco di studiare la seconda soluzione.

Per cercare di finire la discussione che penso possa interessare anche ad altri, vi faccio quest'ultima domanda:

facciamo finta che su 1200 ore (50 giorni*24ore) io leggo in modo continuo senza mai fermarmi, di quanto può essere l'errore? Di 10, 50, 100 ecc ore ?

Fortunatamente l’errore è sia positivo che negativo (… es. in funzione della temperatura che varia la frequenza dell’oscillatore) … su un periodo di un paio di mesi aspettati un errore +/- di qualche ora.

Guglielmo

Ciao ragazzi, scusatemi se riprendo di nuovo il discorso:

Ho letto che di norma non bisogna resettare il "millis", ma stavo pensando che lo potrei resattare nel momento in cui la condizione sia vera:

if (digitaRead(7) ==0){
 
    cli(); //blocco gli interrupt
    timer0_millis = 4294950000UL; //cambio il valore del registro
    sei(); //riattivo gli interrupt
 
}

Lo posso fare oppure no ?

Tecnicamente lo puoi fare, ma non ha senso, e non serve, farlo.

Lavora sulle differenze, come ti è già stato detto, è più che sufficiente.

fabio22:
stavo pensando che lo potrei resattare nel momento in cui la condizione sia vera:

Così ricominci a perderti cicli di clock in giro...
LA SOLUZIONE corretta è quella del post #9 :wink: