Temporizzare un relè con Millis

Devo tenere attratto un relè per un certo tempo quando si presenta un soggetto ( la mia gatta) davanti a un sensore IR classico.
Ho pensato di utilizzare la funzione Millis().
Uso un Wemos Lolin D1mini.
Il sensore IR vede la presenza del corpo davanti a sé, infatti mi accende il led ma poi anche se il programma esegue la funzione Eroga() non riesco a far attrarre il relè per il tempo che ho impostato nella "#define Rele_TIME 10000".
Lo sketch è il seguente:

/****************          *************
  Test_sensore_IR
  22 gennaio 2022
  Ruotando il trimmer in senso orario 
  aumenta la distanza fino a circa 15 cm
*****************************************/
#define Rele_TIME 10000  // tempo di attrazione del relè

unsigned long lastMillis;
int LED = 13;   // Utilizzo del led LOLIN D1
int Rele = 14;  // Pin D5 = GPIO14 del LOLIN 
int isObstaclePin = 2;  // Pin D4  = GPIO2 del LOLIN 
int isObstacle = HIGH;  // non trovato ostacolo

void setup(){
  pinMode(LED, OUTPUT);
  pinMode(Rele, OUTPUT);
  pinMode(isObstaclePin, INPUT);
  Serial.begin(9600);
  lastMillis = 0;
}

void Ostacolo(){
  isObstacle = digitalRead(isObstaclePin);
  
  if (isObstacle == LOW){
    Serial.println("Presenza ");
    Eroga();
  }
  else{
    Serial.println("Libero ");
    digitalWrite(LED, LOW);
  }
}

void Eroga(){
  unsigned long now = millis();
  
  if(now - lastMillis < Rele_TIME){ 
    digitalWrite(LED, HIGH);
    digitalWrite(Rele, HIGH); // rele attivo
    lastMillis = now;
  }  
  digitalWrite(Rele, LOW);  // rele disattivo
}

void loop(){
  Ostacolo();
  delay(1000);
}

Dove sbaglio?

Lello

Stai cercando di usare la funzione millis() come fosse un delay(), non è cosi che funziona.

millis() restituisce un unsigned long che equivale al tempo trascorso da quando il micro è stato avviato

Se presumi che questo blocco di codice mantenga attivo il relè per 10 secondi sei del tutto fuori strada.

if(now - lastMillis < Rele_TIME){ 
    digitalWrite(LED, HIGH);
    digitalWrite(Rele, HIGH); // rele attivo
    lastMillis = now;
  }  
  digitalWrite(Rele, LOW);  // rele disattivo

Facciamo conto che al tempo t0 millis() è uguale a 1000;
nel momento in cui entri nella funzione Eroga() quello che accade è che now diventa uguale a 1000, lastMillis non è stata ancora assegnata quindi è uguale a 0

  • 1000 - 0 < 10000 -> vero
  • Accende il led ed attiva il relé
  • lastMillis == 1000
  • esce dall'if e rimette subito il pin del relé su LOW.

Al giro successivo l'if non sarà più vero ed il relè rimane su LOW, ammesso che il sensore IR rimanga impegnato perché in caso contrario la funzione non viene nemmeno eseguita.

Quando lo attivi, prendi il tempo t_on, facendolo una sola volta;
poi, se millis()-t_on>=tempo, spegni.
Puoi aggiungere qualcosa per non ripetere lo spegnimento a ogni giro se è già stato effettuato, ma ripeterlo è solo un minimo rallentamento.

@anon67178573 un altro possibile approccio è quello di assegnare ad una variabile il valore di millis() + ritardo 10 secondi quando il sensore viene attivato.
Poi nel loop controlli se millis() == variabile (con la dovuta tolleranza per non mancare il momento esatto nel caso la MCU fosse impegnata in atro quando esegui l'if).

In questo modo eviti di dover gestire il fronte del segnale con un ulteriore variabile.

Un esempio semplice semplice sul solito simulatore online

Se assegni il valore di now a ogni loop questo non potrà mai soddisfare la condizione now - lastMillis < Rele_TIME . Il momento dell'assegnazione dei valori alle variabili deve essere quindi controllato dalle opportune condizioni:

void Ostacolo() {
  if ((lastMillis == 0) && (!Flag)) isObstacle = digitalRead(isObstaclePin);
  if (!isObstacle) {
    Serial.println("Presenza ");
    Flag = true;
    Eroga();
  }
  else {
    Serial.println("Libero ");
    digitalWrite(LED, LOW);
  }
}

Per prima cosa leggi lo stato del pin una sola vola per periodo di attivazione, alla lettura di una presenza attivi il Flag che permetterà la scrittura della variabile lastMillis nell'altra funzione

void Eroga() {
  //unsigned long now = millis();
  if (Flag) {
    lastMillis = millis();
    Flag = false;
    isObstacle = true;
  }
  if (millis() > (lastMillis + Rele_TIME)) {
    digitalWrite(Rele, LOW);  // rele disattivo
    lastMillis = 0;
  }  else {
    digitalWrite(LED, HIGH);
    digitalWrite(Rele, HIGH); // rele attivo
    //lastMillis = now;
  }

}

Il flag impostato true attiva l'aggiornamento della variabile lasMillis e riporta a faslo il flag, anche isObstacole viene riportato allo stato iniziale per inibire la riattivazione del flag prima che sia trascorso il tempo di attivazione.

Trascorso il tempo di attivazione anche lastMillis viene riportato a zero e la condizione ((lastMillis == 0) && (!Flag)) è nuovamente verificata dando il via a un nuovo ciclo.

"Bandiera" come nome di variabile non è molto esplicativo... È un po' come una spia di allarme su un pannello con scritto "spia"... :frowning:

:joy:

Inoltre, la disuguaglianza

millis() > lastMillis+Rele_TIME

è in una forma soggetta al problema dell'overflow.

Grazie per l'aiuto sopratutto pratico.
Appena rientro a casa riprendo a fare esperienza con i vostri consigli.
Per quanto riguarda "l'overflow" non c'è problema, visto che il marchingegno che ho pensato di fare, rimane attivo per un massimo di 4 o cinque giorni.
L'overflow mi pare che arrivi dopo circa 49 giorni o giù di li.

Lello

Io farei così:

if (digitalRead (PIN_SENSORE)==HIGH && gatto_prec==LOW)
  {
  gatto_prec=HIGH;
  t_on=millis();
  digitalWrite (PIN_RELE, HIGH);
  }
if (gatto_prec==HIGH && millis()-t_on>T_RELE)
  {
  digitalWrite (PIN_RELE, LOW);
  }
if (digitalRead (PIN_SENSORE)==LOW) gatto_prec=LOW

Nota: le costanti sono scritte in maiuscolo.

Quel flag chiamato flag... effettivamente...

Non avevo mai preso in considerazione questa cosa, i controlli che uso con millis gestiscono intervalli inferiori al secondo che si attivano solo su richiesta dell'utente.

ho modificato il mio codice seguendo questo tuo suggerimento.

Se non ho capito male, quando millis() < t_on, millis() - t_on dovrebbe dare un negativo ma non accade perché millis() restituisce un unsigned long.

Cerca nel forum millis overflow.

Si, ma se quegli intervalli ricadono proprio quando il contatore torna a zero, la logica del programma può sbarellare da una frazione di secondo a centinaia di ore (a seconda delle condizioni testate).

Invece calcolando la differenza tra due interi long senza segno ottieni sempre il valore corretto, con l'unico limite che il risultato non può comunque eccedere le 1192 e qualcosa ore, ma considerando l'imprecisione di millis (circa cinque secondi all'ora) se serve misurare tempi così lunghi meglio usare altro.

Con il vostro aiuto adesso funziona.
Grazie!
Adesso posso continuare a svilupparlo con il tool Blynk.
Lello