Misurare il tempo da un evento

Buonasera,

trovo qualche difficoltà nel misurare (e visualizzare) il tempo a partire da un determinato evento.
Mi spiego meglio allegando un codice di massima di un progetto che sto realizzando:

void setup() {

  Serial.begin(9600);
  ...

  lcd.init();
  sensors.begin();
  ...
  ...
  ...

 

void loop() {

  // IN QUESTA PRIMA PARTE INSERISCO MOLTI DATI (TRA CUI DEI TEMPI) DA UN TASTIERINO E LI SALVO IN DIVERSI VETTORI
  // ESEMPIO: tempo1=135; tempo2=15;

  if (flag == 0) {
    ...
    flag++;
  }

  if (flag == 1) {
    ...
    flag++;
  }

  ...
  ...
  ...


 
  // QUI, DOPO AVER INSERITO I DATI, DEVO COMPIERE DELLE AZIONI PER LA DURATA INSERITA! 
  // ESEMPIO:      OPERAZIONE 1: inizia a contare il tempo e chiudi un relè fino a che tempo <= tempo1 
  //               OPERAZIONE 2: ricomincia a contare il tempo ed accendi un led fino a che tempo <= tempo2


}

Per la visualizzazione del tempo utilizzo questo codice, che mi permette di visualizzare minuti e secondi a partire da una variabile che utilizza la funzione millis():

        contatempo = millis() / 1000;
        ContaTempo();
        m = int(m) + int(h) * 60;

        if (m < 10) {
          lcd.setCursor(12, 3);
          lcd.print(int(m));
          lcd.print(":");
        }
        if (m < 100 && m >= 10) {
          lcd.setCursor(11, 3);
          lcd.print(int(m));
          lcd.print(":");
        }
        if (m >= 100) {
          lcd.setCursor(10, 3);
          lcd.print(int(m));
          lcd.print(":");
        }
        if (s < 10) {
          lcd.setCursor(14, 3);
          lcd.print("0");
          lcd.print(int(s));
        }
        if (s >= 10) {
          lcd.setCursor(14, 3);
          lcd.print(int(s));
        }

        lcd.setCursor(17, 3);
        lcd.print("min");
   
    


void ContaTempo() {
  h = float(contatempo) / 3600;
  dec = (h - int(h));
  m = dec * 60;
  dec = (m - int(m));
  s =  dec * 60;
}

Il problema è che millis(), come noto, misura il tempo trascorso dall'accensione del microcontrollore.

In questo modo può risultare facile calcolare la durata di un evento per differenza tra due variabili poste pari a millis(), una precedente ed una successiva allo stesso.

Non ho idea però di come realizzare un conteggio, da visualizzare e da confrontare con un valore salvato, che parta da un preciso istante, come riportato nel codice di esempio.

Ringrazio per l'attenzione.

Scusa ma credo di non aver capito... parte un evento nell'istante N (che corrisponderà ad esempio a 12300ms dall'avvio) e to nell istante N+1000, N+2000, ecc. vuoi visualizzare che è rascorso un secondo, due secondi, e così via?
Se l'istante attuale lo chiamiamo M ti basta fare M-N e ottieni i millisecondi trascorsi non dall'avvio ma dall'inizio del tuo evento

A livello concettuale sono d'accordo con te, ma il problema è inserire questo nel codice.
Infatti sono costretto a contare il tempo all'interno di diversi cicli concatenati.

Provo ad essere più chiaro. Il programma che vorrei scrivere lo utilizzerei per la produzione domestica della Birra. A livello pratico è sufficiente sapere che la produzione si articola in una prima parte in cui ci sono diversi step di infusione dei malti a diverse temperature (ad esempio STEP 1: 65°C per 60min, STEP2: 72°C per 45min, STEP3: 78°C per 15min), ed una seconda parte di bollitura con i luppoli (esempio: 120min con un luppolo + 25min con un secondo).

Concentrandoci per ora solo sulla prima parte, e sapendo che ogni ricetta prevede un numero di step diverso, per prima cosa inserisco da tastiera il numero di step e lo salvo in una variabile, ed in base a questa con un ciclo while(indice<numero step) inserisco tempi e temperature di ognuno in un vettore.

(chiaramente ad ogni passaggio incremento una variabile flag per eseguire il codice "a stadi")

A questo punto in un altro ciclo while(altro indice<numero step) devo far partire il timer (e visualizzarlo a schermo con il codice postato) e finchè il tempo trascorso DA QUEL MOMENTO è inferiore al tempo previsto per lo step, controllo con un relè collegato ad un'elettrovalvola sul fornellone a gas le temperature previste per quello step. Raggiunto il tempo aumento l'indice e passo allo step successivo.

In pratica, ad un certo punto del codice mi trovo qualcosa del genere:

if(flag==...){

while(indice < numero step){

INIZIA A MISURARE IL TEMPO

CONTROLLA LA TEMPERATURA DELLO step[indice]

if(tempo >= tempo dello step[indice]){
indice++;
}
}
}

In questa situazione come posso comportarmi per misurare il tempo?

Posto parte del codice reale, che provo a rendere comprensibile aggiungendo i commenti. (da immaginare inserito dopo un gran numero di cicli if(flag==...) per l'inserimento dei dati iniziali)

Ci tengo a sottolineare che il codice funziona: visualizzo a schermo le informazioni volute: numero di step, temperatura attuale, temperatura target, tempo target e tempo attuale, che però parte dall'accensione e non da questo if. Inoltre settando le temperature poco oltre quelle ambiente e toccando i sensori ho verificato che anche i relè si aprono e chiudono correttamente.
Quindi resta solo da far partire da zero il tempo.

Grazie dell'aiuto.

  if (flag == 1001) {                                             // utilizzo 1001 perchè da un certo punto del codice per passare allo step successivo dichiaro flag=1001;

    while (n <= step_num) {                                       // ripeto il codice per ogni step della ricetta

      for (int i = 0; i < numberOfDevices; i++) {                 // sono costretto a "lavorare" all'interno di un ciclo for perchè i sensori di temperatura sono 2 e devo controllare contemporaneamente entrambi, l'infusione con le temperatire inserite a un altra pentola a temperatura costante

        sensors.requestTemperatures();
        temperature_sensori[i] = sensors.getTempCByIndex(i);      // nelle righe successive in base alle temperature lette comando la fiamma con l'apertura e chiusura di relè

        if (temperature_sensori[0] < variabili_mesh[ii - 1] * 0.98) {
          digitalWrite(rele_mesh, HIGH);
        }
        if (temperature_sensori[0] > variabili_mesh[ii - 1]) {
          digitalWrite(rele_mesh, LOW);
        }
        if (temperature_sensori[1] < sparge * 0.98) {
          digitalWrite(rele_sparge, HIGH);
        }
        if (temperature_sensori[1] > sparge) {
          digitalWrite(rele_sparge, LOW);
        }

        lcd.setCursor(0, 0);                                      // nelle molte righe successive visualizzo sull'lcd lo step attuale, la temperatura ed il tempo target, la temperatura ed il tempo attuale
        lcd.print(" - MESH -- STEP ");
        lcd.print(n);
        lcd.print(" -");

        float temperatura_step = variabili_mesh[ii - 1];
        int tempo_step = variabili_mesh[ii];

        lcd.setCursor(0, 2);
        lcd.print(temperatura_step);
        lcd.print(" ");
        lcd.print((char)223);
        lcd.print("C");

        if (tempo_step < 10) {
          lcd.setCursor(12, 2);
          lcd.print(tempo_step);
          lcd.print(":00");
          lcd.print(" min");
        }
        if (tempo_step < 100 && tempo_step >= 10) {
          lcd.setCursor(11, 2);
          lcd.print(tempo_step);
          lcd.print(":00");
          lcd.print(" min");
        }
        if (tempo_step >= 100) {
          lcd.setCursor(10, 2);
          lcd.print(tempo_step);
          lcd.print(":00");
          lcd.print(" min");
        }

        contatempo = (millis()) / 1000;                           // QUI C'E' IL PROBLEMA! NON SO COME CONTARE IL TEMPO! FACENDO COSI' PARTE DALL'ACCENSIONE
        ContaTempo();
        m = int(m) + int(h) * 60;

        if (m < 10) {                                             // le righe successive servono a visualizzare il tempo sul display
          lcd.setCursor(12, 3);
          lcd.print(int(m));
          lcd.print(":");
        }
        if (m < 100 && m >= 10) {
          lcd.setCursor(11, 3);
          lcd.print(int(m));
          lcd.print(":");
        }
        if (m >= 100) {
          lcd.setCursor(10, 3);
          lcd.print(int(m));
          lcd.print(":");
        }
        if (s < 10) {
          lcd.setCursor(14, 3);
          lcd.print("0");
          lcd.print(int(s));
        }
        if (s >= 10) {
          lcd.setCursor(14, 3);
          lcd.print(int(s));
        }

        lcd.setCursor(17, 3);
        lcd.print("min");


        lcd.setCursor(0, 3);
        lcd.print(temperature_sensori[0]);                        // qui visualizzo la temperatura letta dal sensore
        lcd.print(" ");
        lcd.print((char)223);
        lcd.print("C");

        tastobattuto = keypad.getKey();

        if (tastobattuto == '*') {                                // questa parte si può ignorare, serve solo per visualizzare la temperatura della seconda pentola
          lcd.clear();
          lcd.setCursor(4, 0);
          lcd.print("-  SPARGE  -");

          float temperatura_sparge = sparge;

          lcd.setCursor(1, 2);
          lcd.print(temperatura_sparge);
          lcd.print(" ");
          lcd.print((char)223);
          lcd.print("C");

          lcd.setCursor(11, 2);
          lcd.print(temperature_sensori[1]);
          lcd.print(" ");
          lcd.print((char)223);
          lcd.print("C");

          delay(5000);
          lcd.clear();
        }
      }

      // if( ho raggiunto il tempo del primo step){               // questa parte di codice deve essere ancora scritta, ma non è difficile               
      //   ii=ii+2;                                               // in pratica, raggiuto il tempo previsto per il primo step passo al successivo nel while
      //   n++;
      // }
      
      // if (ho raggiunto l'ultimo step){                         // raggiunto l'ultimo step flag++ così esco dall'if principale
      //   flag++;
      // }

    }
  }

quando incrementi la variabile di step aggiorni la variabile di riferimento con il millis corrente e parti a misurare la differenza da li.
inizia lo step 1, X = millis()
quando passa Y tempo, quindi millis() - X >= Y riparto con step = 2 e X = millis(), il nuovo millis() e aspetto il tempo Z prima di passare al passo 3
...

Essendo che sono tutti step che vengono eseguito non in contemporanea ma uno di seguito all'altro allora la sostanza non cambia, devi solo inserire una macchina a stati finiti, che banalmente puoi simulare/vedere come una serie di if o meglio ancora uno switch, ovvero avvio il tutto mi segno millis() e imposto la temperatura, continuo a verificiare se è trascorso il tempo, se è trascorso passo allo step successivo e così via fino alla fine, in pseudo codice:

#define STEP_BOLLITURA 1
#define STEP_FERMENTAZIONE 2
#define STEP_NONSOCOSAALTROSCRIVERE 3

byte nStepPrecedente = 0;
byte nStepAttuale = 1;

loop()
{
  if(nStepPrecedente != nStepAttuale )
  {
    nStepPrecedente = nStepAttuale;
    switch(nStepAtt)
    {
      case STEP_BOLLITURA:
         inizio = millis();
         durata = 20000;
         temperatura = 12.5;
     case STEP_FERMENTAZIONE:
         inizio = millis();
         durata = 50000;
         temperatura = 52;
    case STEP_NONSOCOSAALTROSCRIVERE:
         inizio = millis();
         durata = 2000000;
         temperatura = 99.8;
    }
  }
  if( millis() - inizio > durata)
  {
     nStepAttuale++;
     //Verifico fine del ciclo di produzione, visualizzo info, ecc. ecc..
  }
  else
  {
     //Imposto temperatura, visualizzo informazioi ecc.
  }
}

maubarzi:
quando incrementi la variabile di step aggiorni la variabile di riferimento con il millis corrente e parti a misurare la differenza da li.
inizia lo step 1, X = millis()
quando passa Y tempo, quindi millis() - X >= Y riparto con step = 2 e X = millis(), il nuovo millis() e aspetto il tempo Z prima di passare al passo 3
...

Ok, seguendo le tue istruzioni ho provato a scrivere il codice che riporto di seguito:

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);

unsigned long contatempo;       
float h;                      
float m;                        
float s;                        
float dec;                      

int flag = 0;
int t;
int tempo []= {5, 10, 15};
int stp = 0;

void setup() {
  lcd.init();
  lcd.backlight();
}

void loop() {

  if (flag == 0) {
    lcd.setCursor(0, 0);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();

    flag++;
  }

  if (flag == 1) {

    t = millis();

    while (stp < 3) {

      lcd.setCursor(0, 0);
      lcd.print("tempo ");
      lcd.print(stp);
      lcd.print(" = ");

      contatempo = (millis() - t) / 1000;
      ContaTempo();
      m = int(m) + int(h) * 60;

      if (m < 10) {
        lcd.setCursor(10, 0);
        lcd.print(int(m));
        lcd.print(":");
      }
      if (m < 100 && m >= 10) {
        lcd.setCursor(9, 0);
        lcd.print(int(m));
        lcd.print(":");
      }
      if (m >= 100) {
        lcd.setCursor(8, 0);
        lcd.print(int(m));
        lcd.print(":");
      }
      if (s < 10) {
        lcd.setCursor(12, 0);
        lcd.print("0");
        lcd.print(int(s));
      }
      if (s >= 10) {
        lcd.setCursor(12, 0);
        lcd.print(int(s));
      }

      if (contatempo >= tempo[stp]) {
        lcd.setCursor(0, 0);
        lcd.print("step completato!");
        delay(2000);
        lcd.clear();
        stp++;
        t = millis();
      }
    }
  }



}




void ContaTempo() {

  //CALCOLA LE ORE TRASCORSE
  h = float(contatempo) / 3600;    //DIVIDO I SECONDI PER 3600
  //  Serial.print(int(h));        //L' INTERO DELLA VARIABILE "h" SONO LE ORE
  //  Serial.print(":");

  //CALCOLA I MINUTI
  dec = (h - int(h));              //I DECIMALI DELLA VARIABILE "h" SONO I MINUTI
  m = dec * 60;                    // TRASFORMO I MINUTI DA DECIMALE A SESSADECIAMALE
  //  Serial.print(int(m));        //L'INTERO DELLA VARIABILE "m" CONTIENE I MINUTI
  //  Serial.print(":");

  //CALCOLA I SECONDI
  dec = (m - int(m));              //I DECIMALI DELLA VARIABILE "m" SONO I SECONDI
  s =  dec * 60;                   // TRASFORMO I SECONDI DA DECIMALE A SESSADECIMALE
  //  Serial.println(int(s));      //L'INTERO DELLA VARIABILE "S" CONTIENE I SECONDI
  //  Serial.println();
}

Effettivamente funziona, infatti ad ogni step inizia a contare il tempo da zero. Ottimo.

Non riesco solo a capire una cosa: se i secondi nel vettore tempo non sono in ordine crescente, ma sono ad esempio tempo[]={25, 15, 20};, dopo aver contato i primi 25 secondi, il programma rientra nell'ultimo if e ritiene nuovamente soddisfatta la condizione. Come se il confronto non lo facesse con un tempo che riparte da zero, ma con 25, che è > di 15 e di 20 e quindi passa allo step successivo, terminando di fatto l'esecuzione del programma.

A cosa è dovuto questo problema? Con i valori decrescenti funziona ed ogni volta il tempo si azzera, quindi il confronto dell'if dovrebbe essere corretto con qualsiasi valori di tempo!
Spero di essere stato chiaro e che il problema sia comprensibile.
Grazie ancora.

Metti la variabile "t" del tipo giusto, poi si vede

Senza poterlo provare prova a risponderti :slight_smile:
Credo che l'anomalia dipenda da come hai definito le variabili, tutte le variabili che hanno a che fare con millis() devono essere di tipo

unsigned long

, tu t l'hai definita int che può ospitare valori fino a 32767 consideranto che all'avvio attendi 2000*5 millisecondi quando vai a valutare la seconda unità di tempo sei andato in overflow e si incarta tutto.
Seconda cosa, non è un errore ma è un ottimizzazione che su MCU è sempre bene fare, hai definto come int anche la variabile stp che può valere al massimo 3, definisci le variabili che contengono valori piccoli come byte.
Più in generale cerca sempre di utilizzare tipi più "piccoli" possibile, es byte occupa appunto un byte, l'int due se non serve stai nello stretto che è meglio.
Altra otimizzazione possibile, per stampare su lcd (ma anche per altri scopi) puoi usare la funzione snprintf puoi formattare la stringa di output nei modi più disparati, ti semplifica il codice e lo rende sicuramente più leggibile.
Ho parlato di striga di output, nel dubbio chiarisco anche che mi riferisco alle stringhe classiche del C (Array di char) e non alla famigerata classe String da NON usare sulle MCU

Edit = sovrapposto ma avevo scritto troppo pen non postare :wink:

Grazie ancora, problema risolto!
Terrò anche in considerazione l'opportunità di utilizzare byte al posto di int per tutte quelle variabili sicuramente minori di 255.

Allego il codice funzionante, nel caso a qualcuno possa servire.

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);

unsigned long contatempo;       
float h;                        
float m;                        
float s;                        
float dec;                      

int flag = 0;
unsigned long t;
int tempo [] = {20, 5, 15};
int stp = 0;

void setup() {
  Serial.begin(9600);

  lcd.init();
  lcd.backlight();
}

void loop() {

  if (flag == 0) {
    lcd.setCursor(0, 0);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" perdiamo tempo");
    delay(2000);
    lcd.clear();

    flag++;
  }

  if (flag == 1) {

    t = millis();

    while (stp <= 2) {

      lcd.setCursor(0, 0);
      lcd.print("tempo ");
      lcd.print(stp);
      lcd.print(" = ");

      contatempo = (millis() - t) / 1000;
      ContaTempo();
      m = int(m) + int(h) * 60;

      Serial.println(contatempo);

      if (m < 10) {
        lcd.setCursor(10, 0);
        lcd.print(int(m));
        lcd.print(":");
      }
      if (m < 100 && m >= 10) {
        lcd.setCursor(9, 0);
        lcd.print(int(m));
        lcd.print(":");
      }
      if (m >= 100) {
        lcd.setCursor(8, 0);
        lcd.print(int(m));
        lcd.print(":");
      }
      if (s < 10) {
        lcd.setCursor(12, 0);
        lcd.print("0");
        lcd.print(int(s));
      }
      if (s >= 10) {
        lcd.setCursor(12, 0);
        lcd.print(int(s));
      }

      if (contatempo >= tempo[stp]) {
        lcd.setCursor(0, 0);
        lcd.print("step completato!");
        Serial.println("step completato!");
        delay(2000);
        lcd.clear();
        stp++;
        t = millis();
      }
    }

    lcd.setCursor(5, 0);
    lcd.print("FINE!");
    Serial.println("FINE");
    delay(2000);
    lcd.clear();
    flag++;
  }

}




void ContaTempo() {

  //CALCOLA LE ORE TRASCORSE
  h = float(contatempo) / 3600;    //DIVIDO I SECONDI PER 3600
  //  Serial.print(int(h));        //L' INTERO DELLA VARIABILE "h" SONO LE ORE
  //  Serial.print(":");

  //CALCOLA I MINUTI
  dec = (h - int(h));              //I DECIMALI DELLA VARIABILE "h" SONO I MINUTI
  m = dec * 60;                    // TRASFORMO I MINUTI DA DECIMALE A SESSADECIAMALE
  //  Serial.print(int(m));        //L'INTERO DELLA VARIABILE "m" CONTIENE I MINUTI
  //  Serial.print(":");

  //CALCOLA I SECONDI
  dec = (m - int(m));              //I DECIMALI DELLA VARIABILE "m" SONO I SECONDI
  s =  dec * 60;                   // TRASFORMO I SECONDI DA DECIMALE A SESSADECIMALE
  //  Serial.println(int(s));      //L'INTERO DELLA VARIABILE "S" CONTIENE I SECONDI
  //  Serial.println();
}

Ora non resta che implementare quanto appreso nel codice iniziale...