La temporizzazione con Millis non è costante

Buongiorno, ho creato questo semplice codice per temporizzare lo spegnimento di alcune luci accese tramite un sensore di presenza.
Ho usato il comando Millis per identificare l'istante di accensione delle luci, il problema è che la durata non è mai costante, varia anche di qualche secondo tra una volta e l'altra, quindi più del 20% 30%
Non capisco se sia un problema di codice oppure della scheda Arduino 3, adesso dovrebbe arrivarmi un'altra scheda e proverò
Vi allego il codice:

// Costanti:
const int Sensor1Pin = 2;      // pin di ingresso sensore 1
const int Sensor2Pin = 3;      // pin di ingresso sensore 2
const int Sensor3Pin = 4;      // pin di ingresso sensore 3
const int intervallo = 12000;  //intervallo di accensione faro dopo allarme (millisecondi)

// Variabili:
int FaroPin[] {10, 11, 12};              // pin di uscita dei fari
int Statoallarme[] {LOW, LOW, LOW};      // stato dei sensori
int cella = 0;                           // indicizzazione array Statoallarme e Millisensore
unsigned long Millisensor[] {0, 0, 0};   //istante di rivelazione allarme sensori

void setup() {
 // inizializza Sensorpin come input:
 pinMode(Sensor1Pin, INPUT);
 pinMode(Sensor2Pin, INPUT);
 pinMode(Sensor3Pin, INPUT);
 // inizializza Faropin come output:
 pinMode(FaroPin[0], OUTPUT);
 pinMode(FaroPin[1], OUTPUT);
 pinMode(FaroPin[2], OUTPUT);
}

void loop() {
 // leggi stato ingresso sensori fino a quando non si verifica un allarme oppure fino a quando non è in atto ritardo accensione faro:
 do {
   Statoallarme[0] = digitalRead(Sensor1Pin);
   Statoallarme[1] = digitalRead(Sensor2Pin);
   Statoallarme[2] = digitalRead(Sensor3Pin);
 } while (Statoallarme[0] == LOW & Statoallarme[1] == LOW & Statoallarme[2] == LOW & FaroPin[0] == LOW & FaroPin[1] == LOW & FaroPin[2] == LOW);
 // verifica quali sensori sono in allarme e accendi faro
 delay(20);
 for (cella = 0 ; cella < 3 ; cella++) {
   // assegna alla variabile Millis il tempo attuale
   unsigned long Millis = millis();
   if (Statoallarme[cella] == HIGH) {
     // assegna alla variabile Millis[cella] l'istante di inizio allarme
     Millisensor[cella] = Millis;
     // accendi faro in allarme
     digitalWrite (FaroPin[cella], HIGH);
   }
   // verifica se è passato l'intervallo di accensione del faro
   if (Millis - Millisensor[cella] >= intervallo)
     // spegni faro
     digitalWrite (FaroPin[cella], LOW);
 }
}

grazie

>well66: ti ricordo che in conformità al regolamento, punto 7, devi editare il tuo post qui sopra (quindi NON scrivendo un nuovo post, ma utilizzando il bottone More -> Modify che si trova in basso a destra del tuo post) e racchiudere il codice all'interno dei tag CODE (... sono quelli che in edit inserisce il bottone con icona fatta così: </>, tutto a sinistra).

In pratica, tutto il tuo codice dovrà trovarsi racchiuso tra due tag: [code] _il _tuo_ codice_ [/code] così da non venire interpretato e non dare adito alla formazione di caratteri indesiderati o cattiva formattazione del testo. Grazie.

Guglielmo

P.S.: Ti ricordo che, purtroppo, fino a quando non avrai sistemato il codice come richiesto, nessuno ti potrà rispondere, quindi ti consiglio di farlo al più presto. :wink:

Era il mio primo invio di codice, così spero sia corretto grazie

well66:
... così spero sia corretto grazie

... perfetto, grazie :slight_smile:

Guglielmo

Il programma è confuso e prolisso

Ma non deve dare quel problema

Semplificati la vita

Togli l'inutile while e fai le letture direttamente nel for

Posta, prova e riferisci

well66:
Non capisco se sia un problema di codice oppure della scheda Arduino 3

Molto semplice, nel while() devi usare l'operatore logico "&&" e non quello a bit "&"!!

Ossia non:

while (Statoallarme[0] == LOW & Statoallarme[1] == LOW & ...

bensì:

while (Statoallarme[0] == LOW && Statoallarme[1] == LOW && ...

E comunque eviterei del tutto quel do..while perché nel frattempo non potrai mai fare nulla (e non passerebbe abbastanza spesso per il controllo millis), per cui togli il do...while() e lascia solo le tre letture e poi le if e vedi se riesci a farlo funzionare così... :wink:

Che il while sia inutile è chiaro

Non credo porti via così tanto tempo però

Anche lo and bit a bit non è un vero problema
Si tratta di variabili dichiarate int è valorizzate con un test binario
Quindi se alte hanno scritto tutte 0000001b oppure se basse zero binario
In queste condizioni lo and binario si comporta come lo and logico
Avremo sempre una variabile intera con scritto 000001b oppure 000000b
Che per il while vale comunque vero o falso

Anch'io credo che funzioni ugualmente, perché Statoallarme[0] == LOW finché è vero significa true, cioè 1: 1&1&1=1...

Anch'io eviterei il do...while e anche il for: sono inutili blocchi del flusso del programma e complicazioni. D'altra parte, i sensori di presenza credo che diano in uscita impulsi molto lunghi, tanto da non dover essere controllati in una sequenza rapidissima.

SE(allarme1 && f1==0) {t1=millis(); f1=1; accendi faro1;}
SE(allarme2 && f2==0) {t2=millis(); f2=1; accendi faro2;}
SE(allarme3 && f3==0) {t3=millis(); f3=1; accendi faro3;}

SE(f1==1 && millis()-t1>durata) {f1=0; spegni faro1;}
SE(f2==1 && millis()-t2>durata) {f2=0; spegni faro2;}
SE(f3==1 && millis()-t3>durata) {f3=0; spegni faro3;}

Salvorhardin:
Non credo porti via così tanto tempo però
Anche lo and bit a bit non è un vero problema

Con quegli "&" basta che la catena dia un valore diverso da zero far restare il codice indefinitamente dentro al while,e quindi non gestice più lo spegnimento (e neanche l'accensione). Quindi si, secondo me il codice resta proprio lì dentro per molto tempo fino a che la catena di "&" non dà un risultato zero, e tra l'altro i valori dell'array "faroPin[]", usato come condizione nella while, non viene modificato mai da nulla all'interno del do().

Quindi dovrebbe provare con questo codice:

// Costanti:
const int Sensor1Pin = 2;      // pin di ingresso sensore 1
const int Sensor2Pin = 3;      // pin di ingresso sensore 2
const int Sensor3Pin = 4;      // pin di ingresso sensore 3
const int intervallo = 12000;  //intervallo di accensione faro dopo allarme (millisecondi)

// Variabili:
int FaroPin[] {10, 11, 12};              // pin di uscita dei fari
int Statoallarme[] {LOW, LOW, LOW};      // stato dei sensori
int cella = 0;                           // indicizzazione array Statoallarme e Millisensore
unsigned long Millisensor[] {0, 0, 0};   //istante di rivelazione allarme sensori

void setup() {
 // inizializza Sensorpin come input:
 pinMode(Sensor1Pin, INPUT);
 pinMode(Sensor2Pin, INPUT);
 pinMode(Sensor3Pin, INPUT);
 // inizializza Faropin come output:
 pinMode(FaroPin[0], OUTPUT);
 pinMode(FaroPin[1], OUTPUT);
 pinMode(FaroPin[2], OUTPUT);
}

void loop() {
 // leggi stato ingresso sensori
 Statoallarme[0] = digitalRead(Sensor1Pin);
 Statoallarme[1] = digitalRead(Sensor2Pin);
 Statoallarme[2] = digitalRead(Sensor3Pin);
 // verifica quali sensori sono in allarme e accendi faro
 unsigned long Millis = millis();
 for (cella = 0 ; cella < 3 ; cella++) {
   // assegna alla variabile Millis il tempo attuale
   if (Statoallarme[cella] == HIGH) {
     // assegna alla variabile Millis[cella] l'istante di inizio allarme
     Millisensor[cella] = Millis;
     // accendi faro in allarme
     digitalWrite (FaroPin[cella], HIGH);
   }
   // verifica se è passato l'intervallo di accensione del faro
   if (Millis - Millisensor[cella] >= intervallo)
     // spegni faro
     digitalWrite (FaroPin[cella], LOW);
 }
 delay(200); // Controlla "solo" 5 volte al secondo...
}

Datman:
Anch'io credo che funzioni ugualmente, perché Statoallarme[0] == LOW finché è vero significa true, cioè 1: 1&1&1=1...

Appunto, è sempre vero quindi resta dentro al while dove non ci sono azioni per spegnere i fari dopo un certo tempo, che è quello che lampente l'OP...

Anch'io eviterei il do...while e anche il for: sono inutili blocchi del flusso del programma e complicazioni.

Non è detto, in questo modo ha parametrizzato anche il numero di sensori e di porte output senza fare inutili copia/incolla di codice (abitudine pessima).

delay(200); // Controlla "solo" 5 volte al secondo

Uhmm...

All'ora, vediamo di analizzarla questa benedetta while della discordia
che non serve a una cippa, e inoltre non fa nulla, nemmeno riesce a fare male
perchè si tratta di una catena di and (binari, non logici) su variabili che valgono 1 solo se i pulsanti NON sono premuti o le luci NON sono accese (c'è un ==0 completamente inutile, bastava un !, comunque....
il che vuol dire che appena un pulsante è premuto e/o una luce è accesa ce ne usciamo dalla while
mi spiace per docdoc, ma non è la prima volta che siamo in disaccordo, ricordo bene come finisce di solito, e sono pronto a segnalare.....
quindi una volta assodato che la while è inutile sia nel bene che nel male vediamo di decespugliare questo programma che sembra bizantino
primo, essendoci N (in particolare tre) pulsanti ognuno dei quali influisce sul suo faro e comanda il suo timer viene facile sia pensare per array (e questo lo OP lo ha fatto, anche se solo in parte) che pensare ad una struttura (il pulsante, la sua luce, il suo tempo)
quindi ecco un bell'array di strutture:

typedef struct luce_s
{
   byte pul;
   byte led;
   unsigned long int tempo;
} luce;

luce luci[] = {{2, 10}, {3, 11}, {4, 12}};
#define LUCI sizeof(luci)/sizeof(luci[0])

come si vede è completamente parametrizzato, anche non serve inizializzare il terzo elemento della struttura,
ricordatevi che le variabili globali (e l'array di strutture è dichiarato globale) sono inizializzate a zero,anche l'indice non viene dichiarato ma lascio che lo calcoli il precompilatore, anzi in realtà dovrebbe calcolaro a compile-time il compilatore ottimizzante, ma qui ci vuole la parola di Guglielmo
a questo punto la setup è semplice:

void setup(void)
{
   for (byte i = 0; i < LUCI; i++)
   {
      pinMode(luci[i].pul, INPUT);
      pinMode(luci[i].led, OUTPUT);
   }
}

un bel ciclo for (con l'indice parametrizzato) che dichiara i piedini
santo for ci ha tolto dalle castagne quel brutto indice scritto a mano, in questo ha ragione docdoc
e la main() naturalmente controllerà direttamente il piedino ed eventualmente attiverà sia timer che led
anche qui dentro un for parametrizzato

void loop()
{
   for (byte i = 0; i < LUCI; i++)
   {
      if (digitalRead(luci[i].pul))
      {
         luci[i].tempo = millis();
         digitalWrite(luci[i].led, HIGH);
      }
      else
      {
         if (millis() - luci[i].tempo >= INTERVALLO)
         {
            digitalWrite(luci[i].led, LOW);
         }
      }
   }
}

ah sì, il tempo di intervallo....
naturalmente una define, per risparmiare memoria
NOn che serva, ma programmando MCU è meglio pensarci
anzi adesso metto il programma completo:

// di Nelson-StandardOil
// IDE 1.8.10
// Progetto: un timer multiplo
// Scopo: da pubblicare sul forum
// Data inizio lavori: 06/03/20 18.35
// data termine lavori 06/03/20 18.47


typedef struct luce_s
{
   byte pul;
   byte led;
   unsigned long int tempo;
} luce;

luce luci[] = {{2, 10}, {3, 11}, {4, 12}};
#define LUCI sizeof(luci)/sizeof(luci[0])


#define INTERVALLO 12000
//intervallo di accensione faro dopo allarme (millisecondi)


void setup(void)
{
   for (byte i = 0; i < LUCI; i++)
   {
      pinMode(luci[i].pul, INPUT);
      pinMode(luci[i].led, OUTPUT);
   }
}

void loop()
{
   for (byte i = 0; i < LUCI; i++)
   {
      if (digitalRead(luci[i].pul))
      {
         luci[i].tempo = millis();
         digitalWrite(luci[i].led, HIGH);
      }
      else
      {
         if (millis() - luci[i].tempo >= INTERVALLO)
         {
            digitalWrite(luci[i].led, LOW);
         }
      }
   }
}

non mi sembra difficile da seguire, casomai lo OP può chiedere delucidazioni, ma credo di aver spiegato abbastanza bene

ah, ma in tutto questo mi sono accorto che non ho spiegato perchè il programma originale sbaglia i tempi pur non avendo errori.....

non sbaglia i tempi: serve di mettere resistenze di pull_down e un bel debounce.......

Controllare cinque volte al secondo al massimo causa un errore di 0,2s che è molto meno di quanto rilevato dall'OP.

Il do/while del post iniziale a parte discorsi di design secondo me danni non ne fa, il programma non deve fare niente altro e se si resta dentro è solo perché non ci sono ne rilevamenti ne fari accesi.

Ci scommetterei un doblone che in realtà il codice non ha problemi, ma sono i sensori a inviare più segnali che retriggerano i temporizzatori software. Ad esempio i classici PIR HC‑SR501 hanno un'impostazione che genera un impulso di due secondi ogni quattro secondi per tutto il tempo che continuano a percepire il movimento.

giusto, non sono pulsanti, ma sensori più o meno ignoti
non è per nulla difficile fare in maniera che se la luce è accesa il corrispondente input non viene letto
ecco il vantaggio di parametrizzare e scrivere cicli, basta modificare in uno solo punto
lascio allo OP l'esercizio di evitare azionamenti consecutivi
casomai chiedere

Standardoil:
il che vuol dire che appena un pulsante è premuto e/o una luce è accesa ce ne usciamo dalla while

Appunto, mica ho detto che resta in eterno, ma solo che "il codice resta proprio lì dentro per molto tempo fino a che la catena di "&" non dà un risultato zero", e che in questo tempo NON si aggiorna il tempo di spegnimento, per cui "sballa" i tempi

mi spiace per docdoc, ma non è la prima volta che siamo in disaccordo, ricordo bene come finisce di solito, e sono pronto a segnalare.....

Ok, visto che ti piace stuzzicare (ma che, hai la coda di paglia? Ti ho detto qualcosa contro, o sei TU ad aver risposto ad un mio messaggio NON indirizzato a te?) ora spiega cosa vuol dire "come finisce di solito", e cosa diamine hai da "segnalare", poi ne parliamo.
Altrimenti evita, grazie.

Standardoil:
... mi spiace per docdoc, ma non è la prima volta che siamo in disaccordo, ricordo bene come finisce di solito, e sono pronto a segnalare.....

docdoc:
... Ok, visto che ti piace stuzzicare (ma che, hai la coda di paglia? Ti ho detto qualcosa contro, o sei TU ad aver risposto ad un mio messaggio NON indirizzato a te?) ora spiega cosa vuol dire "come finisce di solito", e cosa diamine hai da "segnalare", poi ne parliamo. ...

... la vogliamo piantare di fare i "bambini"? >:(

Evitiamo inutili commenti/affermazioni e riserviamoci di "segnalare" effettivamente quando c'è qualche cosa che non va.

Guglielmo

docdoc:
Appunto, mica ho detto che resta in eterno, ma solo che "il codice resta proprio lì dentro per molto tempo fino a che la catena di "&" non dà un risultato zero", e che in questo tempo NON si aggiorna il tempo di spegnimento, per cui "sballa" i tempi

basta che ci sia accesa una luce, e non esegue quella attesa, leggile le cose che critichi
inoltre comunque il problema non è li, ma nei sensori.....

--- ho tagliato una parte ... come detto, detto basta polemiche, SOLO discussioni tecniche. - gpb01

docdoc:
Con quegli "&" basta che la catena dia un valore diverso da zero far restare il codice indefinitamente dentro al while

Sono "and" mica "or"

Basta che una sola variabile sia a 0 per uscire

E siccome vengono da un test ==0 basta che un solo ingresso sia HIGH oppure una sola uscita sia alta

Quindi vedi bene che la tua ipotesi non regge

Peraltro basta dire all'autore di mettere dei led sugli ingressi, così sappiamo cosa arriva

Grazie per gli interventi, anche se ho scatenato una diatriba fra esperti.
Sono riuscito a fare la prova con un arduino nano nuovo e con questa nuova scheda i tempi sono corretti, quindi probabilmente è l'oscillatore interno che fa le bizze.
Adesso invece vado a rileggermi tutti i vostri consigli per il codice così cerco di imparare qualcosa.

Claudio_FF:
Ci scommetterei un doblone che in realtà il codice non ha problemi, ma sono i sensori a inviare più segnali che retriggerano i temporizzatori software. Ad esempio i classici PIR HC‑SR501 hanno un'impostazione che genera un impulso di due secondi ogni quattro secondi per tutto il tempo che continuano a percepire il movimento.

Il codice è in test e quindi non uso ancora i sensori ma attivo manualmente gli input

// Costanti:
const int intervallo = 12000;  //intervallo di accensione faro dopo allarme (millisecondi)

// Variabili:
int SensorPin[] {2, 3, 4};               // pin di ingresso sensori
int FaroPin[] {10, 11, 12};              // pin di uscita dei fari
int Statoallarme[] {LOW, LOW, LOW};      // stato dei sensori
int cella = 0;                           // indicizzazione array Statoallarme e Millisensore
unsigned long Millisensor[] {0, 0, 0};   //istante di rivelazione allarme sensori

void setup() {
 // inizializza Sensorpin come input:
 pinMode(SensorPin[0], INPUT);
 pinMode(SensorPin[1], INPUT);
 pinMode(SensorPin[2], INPUT);
 // inizializza Faropin come output:
 pinMode(FaroPin[0], OUTPUT);
 pinMode(FaroPin[1], OUTPUT);
 pinMode(FaroPin[2], OUTPUT);
}

void loop() {
 
 // verifica quali sensori sono in allarme e accendi faro
 delay(20);
 for (cella = 0 ; cella < 3 ; cella++) {
   // leggi stato ingressi
   Statoallarme[cella] = digitalRead(SensorPin[cella]);
   // assegna alla variabile Millis il tempo attuale
   unsigned long Millis = millis();
   if (Statoallarme[cella] == HIGH) {
     // assegna alla variabile Millis[cella] l'istante di inizio allarme
     Millisensor[cella] = Millis;
     // accendi faro in allarme
     digitalWrite (FaroPin[cella], HIGH);
   }
   // verifica se è passato l'intervallo di accensione del faro
   if (Millis - Millisensor[cella] >= intervallo)
     // spegni faro
     digitalWrite (FaroPin[cella], LOW);
 }
}

Tolto il while come consigliato, probabilmente l'ho inserito perchè arrivavo da un altro tipo di codice che poi ho cambiato e alla fine è rimasto così