Input che aziona 5 relay in sequenza e si interrompe al rilascio improvviso

AlPinkish:
Ottimo, ho capito molte migliorie, i cicli for semplificano davvero tanto.

Bene! Ma non solo, usa i #define per cose come i pin (se fissi), ed array quando hai gruppi di oggetti dello stesso tipo, che quindi puoi gestire ed identificare con un solo indice numerico.

Ho testato e funziona bene, la cosa che ho notato è che se lascio il pulsante e poi lo ripremo subito, il ciclo non si interrompe e prosegue, secondo me è proprio a causa del delay.

Esattamente.

una domanda mi sorge spontanea, avendo fatto tutto con il ciclo for, se in futuro volessi aggiungere un effetto accendi-spegni per 3 volte, solo al relay 4, questo ciclo for non va più bene, giusto?

Dovresti mettere una if() dentro al for, per fare qualcosa di diverso se l'indice vale 4, ad esempio:

      if ( releON < NUMRELE ) { // Se non ho ancora finito
        Serial.print("Relè ");
        Serial.print(releON);
        Serial.println(" acceso");
        if ( releON == 4 ) {
          for (int j=0; j<3; j++) {
            digitalWrite(pin[releON], HIGH); //accendo
            delay(1000); // aspetto 1 secondo 
            digitalWrite(pin[releON], LOW); //spengo
          }
          digitalWrite(pin[releON], HIGH); //alla fine riaccendo
        }
        else
        {
          digitalWrite(pin[releON], HIGH); //accendo
          delay(1000); // aspetto 1 secondo 
        }
      }

Ma, ripeto, io lo gestirei con dei timer (o comunque con millis) ed una macchina a stati finiti. Ma è prematuro, per ora direi prosegui a sperimentare con queste logiche.

docdoc:
Ma, ripeto, io lo gestirei con dei timer (o comunque con millis) ed una macchina a stati finiti. Ma è prematuro, per ora direi prosegui a sperimentare con queste logiche.

Grazie per tutti i consigli, adesso devo solo inserire i millis e poi per il momento va bene così.

Fatto questo il il thread è risolto. :smiley:

AlPinkish:
... ho capito il senso, ma per integrarlo in questo codice ho bisogno di capire bene come funziona....

Sono solo un paio di cicli if nidificati, ed un for per fare la sequenza, sia di accensione (temporizata) che di spegnimento (rapida) ... unica cosa, ho messo un solo ciclo di millis esterno al ciclo perche' tanto i ritardi erano tutti uguali ... pero' ripeto, buttato giu al volo e tutto da controllare, cosa che non posso fare io qui ...

AlPinkish:
... devo solo inserire i millis ...

Attenzione che non puoi semplicemente "inserire" i millis al posto dei delay ... delay e' una "funzione" che blocca tutto per il numero di millisecondi che gli passi, mentre millis si limita a restituirti un valore, sta poi a te usarlo in modo opportuno per fare quello che ti serve ... :wink:

AlPinkish:
Grazie per tutti i consigli, adesso devo solo inserire i millis e poi per il momento va bene così.

Fatto questo il il thread è risolto. :smiley:

Devi quindi mettere una variabile dove memorizzare il valore di millis() quando inizia l'evento di accensione di un relè (o direttamente millis()+1000 quindi di quando devi passare al successivo) e fare in modo che quel codice passi al successivo relè quando il pulsante è stato premuto E quando il tempo è passato.

Io l'ho già fatto in locale (Tinkercad..;-)) ti dò solo una "imbeccata" vedi se riesci a capire e completare lo sketch:

 ...
      // Passo al successivo rele?
      if ( millis() >= endTime || releON == -1) {
        // (all'inizio poiché parto da -1, il primo sarà 0)
        releON++;
        if ( releON < NUMRELE ) { // Se non ho ancora finito
          digitalWrite(pin[releON], HIGH); //accendo
          Serial.print("Relè ");
          Serial.print(releON);
          Serial.println(" acceso");
          //delay(1000); // aspetto 1 secondo 
          endTime = millis() + 1000;
        }
  ...

docdoc:
Devi quindi mettere una variabile dove memorizzare il valore di millis() quando inizia l'evento di accensione di un relè (o direttamente millis()+1000 quindi di quando devi passare al successivo) e fare in modo che quel codice passi al successivo relè quando il pulsante è stato premuto E quando il tempo è passato.

Dunque ci sto provando e vedo che, sicuramente devo aggiungere long endTime;.

La condizione permette di proseguire o se i millis sono >= di endTime oppure se nessun relay è attivo, quindi,
la prima volta la condizione è soddisfatta da releON=-1, poi dopo che è stato acceso il realy1, endTime diventa uguale a millis + 1000ms.

fin qua ci sono ed è chiaro. Ora dovrebbe rifare il ciclo considerando l'altra condizione millis() >= endTime, dove adesso i millis sono minori di 1000ms rispetto a endTime, pertanto proseguirà solo quando si verifica la condizione maggiore o uguale, e così si simula il ritardo. Ma logicamente non accade perchè manca qualcosa...

indizio?

Il modo corretto di usare millis() NON è vedere se ha raggiunto tinizio+x (che funziona solo per 50 giorni poi il ritorno a zero del contatore causa problemi), ma vedere se da tinizio sono trascorsi almeno x calcolando millis()-tinizio (questo non causa mai problemi di overflow).

In pratica basta una funzione che calcoli la differenza ritornando true se è passato l'intervallo di tempo specificato:

bool timeOut(unsigned long initTime, unsigned long interval)
{
    return (millis() - initTime >= interval);
}

Ad esempio per far lampeggiare due LED in modo indipendente senza bloccare il mainloop con delay:

#define  LED1_PIN    ...
#define  LED2_PIN    ...
#define  LED1_DELAY  ...
#define  LED2_DELAY  ...
//--------------------------------------------------------------------
uint32_t led1Time = 0;
uint32_t led2Time = 0;
//--------------------------------------------------------------------
bool timeOut(uint32_t initTime, uint32_t interval) {
    return (millis() - initTime >= interval);
}
//--------------------------------------------------------------------
void setup() {
    pinMode(LED1_PIN, OUTPUT);
    pinMode(LED2_PIN, OUTPUT);
}
//--------------------------------------------------------------------
void loop() 
{
    if (timeOut(led1Time, LED1_DELAY)) {
        led1Time += LED1_DELAY;
        digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));
    }

    if (timeOut(led2Time, LED2_DELAY)) {
        led2Time += LED2_DELAY;
        digitalWrite(LED2_PIN, !digitalRead(LED2_PIN));
    }
}

Tra parentesi: sbaglio o il programma deve fare questo?

Le soluzioni possibili sono molte, tra cui quella di emulare direttamente il circuito a porte e timer tramite espressioni logiche. Il circuito qui sopra sono banalmente quattro timer in serie che si comportano come nel diagrammino temporale disegnato. Il codice qui sotto simula i quattro timer (l'uscita del precedente diventa l'ingresso del seguente) ed è totalmente parametrizzato, basta compilare le define in base ai collegamenti effettuati (nel resto del codice non si specificano mai esplicitamente stati legati all'hardware come LOW HIGH, quindi il suo funzionamento è indipendente dai collegamenti hardware). Si da per scontato che l'ingresso abbia un adeguato debounce hardware, altrimenti bisognerebbe aggiungere anche una funzione per il debounce sofware.

//--------------------------------------------------------------------
#define  RELEASE_STATE   ...  // Stato pulsante rilasciato
#define  RELEOFF_STATE   ...  // Stato rele' spento
#define  IN_PIN          ... 
#define  RELE1_PIN       ...
#define  RELE2_PIN       ...
#define  RELE3_PIN       ...
#define  RELE4_PIN       ...
#define  RELE5_PIN       ...
#define  RELEON_STATE    !RELEOFF_STATE
#define  SEQUENCE_DELAY  1000
//--------------------------------------------------------------------
byte      T1out = 0;
uint32_t  T1time;

byte      T2out = 0;
uint32_t  T2time;

byte      T3out = 0;
uint32_t  T3time;

byte      T4out = 0;
uint32_t  T4time;
//--------------------------------------------------------------------
void timerUpdate(byte in, uint32_t *t, byte *out, uint32_t dly) {
    if (!in)  { *t = millis();  *out = 0; }
    else if (millis() - *t >= dly)  *out = 1;
}
//--------------------------------------------------------------------
void setup() {
    pinMode(IN_PIN, INPUT);
    pinMode(RELE1_PIN, OUTPUT);
    pinMode(RELE2_PIN, OUTPUT);
    pinMode(RELE3_PIN, OUTPUT);
    pinMode(RELE4_PIN, OUTPUT);
    pinMode(RELE5_PIN, OUTPUT);
}
//--------------------------------------------------------------------
void loop() {
    byte in = (digitalRead(IN_PIN) != RELEASE_STATE);

    timerUpdate(in,    &T1time, &T1out, SEQUENCE_DELAY);
    timerUpdate(T1out, &T2time, &T2out, SEQUENCE_DELAY);
    timerUpdate(T2out, &T3time, &T3out, SEQUENCE_DELAY);
    timerUpdate(T3out, &T4time, &T4out, SEQUENCE_DELAY);

    digitalWrite(RELE1_PIN, in    ? RELEON_STATE : RELEOFF_STATE);
    digitalWrite(RELE2_PIN, T1out ? RELEON_STATE : RELEOFF_STATE);
    digitalWrite(RELE3_PIN, T2out ? RELEON_STATE : RELEOFF_STATE);
    digitalWrite(RELE4_PIN, T3out ? RELEON_STATE : RELEOFF_STATE);
    digitalWrite(RELE5_PIN, T4out ? RELEON_STATE : RELEOFF_STATE);
}

Claudio_FF:
Il modo corretto di usare millis() NON è vedere se ha raggiunto tinizio+x (che funziona solo per 50 giorni poi il ritorno a zero del contatore causa problemi), ma vedere se da tinizio sono trascorsi almeno x calcolando millis()-tinizio (questo non causa mai problemi di overflow).

In pratica basta una funzione che calcoli la differenza ritornando true se è passato l'intervallo di tempo specificato:

bool timeOut(unsigned long initTime, unsigned long interval)

{
   return (millis() - initTime >= interval);
}



Ad esempio per far lampeggiare due LED in modo indipendente senza bloccare il mainloop con delay:



#define  LED1_PIN    ...
#define  LED2_PIN    ...
#define  LED1_DELAY  ...
#define  LED2_DELAY  ...

//--------------------------------------------------------------------

uint32_t led1Time = 0;
uint32_t led2Time = 0;

//--------------------------------------------------------------------

bool timeOut(uint32_t initTime, uint32_t interval)
{
   return (millis() - initTime >= interval);
}

//--------------------------------------------------------------------

void setup()
{
   pinMode(LED1_PIN, OUTPUT);
   pinMode(LED2_PIN, OUTPUT);
}

//--------------------------------------------------------------------

void loop()
{
   if (timeOut(led1Time, LED1_DELAY))
   {
       led1Time += LED1_DELAY;
       digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));
   }

if (timeOut(led2Time, LED2_DELAY))
   {
       led2Time += LED2_DELAY;
       digitalWrite(LED2_PIN, !digitalRead(LED2_PIN));
   }
}





---


Tra parentesi: sbaglio o il programma deve fare questo? (ok, magari c'è anche qualche porta in esubero)

![|500x440](http://stor.altervista.org/p.php?p=alpinkish001.png)

Le soluzioni possibili sono molte, tra cui quella di emulare direttamente il circuito a porte e timer tramite espressioni logiche.

Diciamo che ho capito come funziona il millis(), però il problema è inserirlo qua:

Quote from: docdoc on Sep 15, 2017, 03:41 pm
Devi quindi mettere una variabile dove memorizzare il valore di millis() quando inizia l'evento di accensione di un relè (o direttamente millis()+1000 quindi di quando devi passare al successivo) e fare in modo che quel codice passi al successivo relè quando il pulsante è stato premuto E quando il tempo è passato.

Dunque ci sto provando e vedo che, sicuramente devo aggiungere long endTime;.

La condizione permette di proseguire o se i millis sono >= di endTime oppure se nessun relay è attivo, quindi,
la prima volta la condizione è soddisfatta da releON=-1, poi dopo che è stato acceso il realy1, endTime diventa uguale a millis + 1000ms.

fin qua ci sono ed è chiaro. Ora dovrebbe rifare il ciclo considerando l'altra condizione millis() >= endTime, dove adesso i millis sono minori di 1000ms rispetto a endTime, pertanto proseguirà solo quando si verifica la condizione maggiore o uguale, e così si simula il ritardo. Ma logicamente non accade perchè manca qualcosa...

indizio?

...
      // Passo al successivo rele?
      if ( millis() >= endTime || releON == -1) {
        // (all'inizio poiché parto da -1, il primo sarà 0)
        releON++;
        if ( releON < NUMRELE ) { // Se non ho ancora finito
          digitalWrite(pin[releON], HIGH); //accendo
          Serial.print("Relè ");
          Serial.print(releON);
          Serial.println(" acceso");
          //delay(1000); // aspetto 1 secondo 
          endTime = millis() + 1000;
        }
  ...

Lo sketch era quasi finito e mi basta capire come concluderlo

AlPinkish:
Diciamo che ho capito come funziona il millis()

 millis() >= endTime

Come detto nel post precedente, questa condizione va in errore dopo 50 giorni.
Se va bene sballa i tempi per un secondo, se va male blocca per altri 50 giorni (o per sempre, se ne sta parlando anche qui).

condizione maggiore o uguale, e così si simula il ritardo. Ma logicamente non accade perchè manca qualcosa...
indizio?

L'errore si trova al di fuori di quel pezzo di codice.

Provo a spiegarlo io il millis() siccome sono neofita e spiegazioni ne ho sentite tante e sono ancora lontano dall'averle stampate nella zucca....in pratica è come accendere il forno e metterci dentro una torta, ora se la torta ci mette 45minuti a cuocere, punto la campanella del forno da 0 a 45 minuti, cosi suona e levo la torta.

Ora però se nello stesso forno ci voglio mettere anche i biscotti che ci mettono solo 5 minuti a cuocere e li metto dentro mettiamo dopo mezzora della torta, mi serve un altra campanella che metterò a 5minuti,ma non da 0,bensi da quando o messo la torta nel forno,quindi da mezz'ora che è gia trascora punterò i 5minuti.

Attento a non cadere nelle sbaglio che ho fatto io ultimamente di azzerare il forno"MILLIS()" perche magari la torta esce buona,ma i biscotti o escono crudi oppure bruciati.

Quindi da come l'ho capita io per i tuoi rele è sufficente che accendi il forno e punti il timer a suonare NON una volta sola ma OGNI tot tempo che decidi ed ogni volta che suona cambi semplicemente pin.

visto che sembra che siamo in dirittura di arrivo, mi permetto di scrivere le mie idee, come al solito risultano alternative a quello che ho letto finora

ho pensato, non me ne vogliate se sono monomaniaco, ad un array di piedini per le uscite ed a un ciclo for per ciclarlo

la condizione per accendere o spegnare le uscite è un semplice test del tipo tempo maggiore a gradino per numero dell'uscita

contando la millis() e il suo overflow diventa una cosa del genere

al premere del tasto segno i millisescondi

poi, se tasto è premuto, accendo le varie uscite per una condizione del tipo:

millis meno istante memorizzato > gradino per numero dell'uscita

è quindi venuta fuori una cosa del genere:

#define PIEDINI 5
int out[PIEDINI] = {2, 3, 4, 5, 6};
// definisco le uscite in uso

#define IN 7
// definisco il piedino di ingresso
// che DEVE avere il debounce hardware
// essendo importanti i tempi di esecuzione NON posso usare il debounce SW

// definisco il passo di tempo da attendere
int passo = 1000; //1000 millisecondi un secondo

// la variabile che registra il momento di pressione del tasto
unsigned long h;

// la variabile che registra lo stato del pulsante
byte st;

e con questo abbiamo definito il campo giochi
con i piedini di uscita scritti in un array
che mi permette sia di espanderli, sia di usare piedini non contigui

void setup() {
  // attivo le uscite e gli ingressi
  for (int i = 0; i < PIEDINI; i++) {
    pinMode(out[i], OUTPUT);
  }
  pinMode(IN, INPUT);
}

l'inizializzazione dei piedini, praticamente una cosa obbligata
con l'unico trucco di parametrizzarla e chiuderla in un ciclo
facilmente espandibile

la loop, per una volta lunga, non ci sono funzioni complesse che ho dovuto sviluppare separatamente

void loop() {
  // se il tasto è cambiato
  if (digitalRead(IN) != st) {
    h = millis();
    // memorizzo il tempo
  }
  st = digitalRead(IN);

tutte le volte che il tasto cambia. memorizzo il tempo

  if (st) {
    // se l'ingresso è attivo
    // accendo le uscite in funzione di un tempo
    for (int i = 0; i < PIEDINI; i++) {
      digitalWrite(i, ((millis() - h) > (i * passo)));
      // accendo o spengo l'uscita a seconda se millis supera h+N gradini
    }
  }
  else {
    // ingresso non attivo, spengo tutte le uscite
    for (int i = 0; i < PIEDINI; i++) {
      digitalWrite(out[i], LOW);
    }
  }
}

qui c'è tutto il lavoro

prima il ramo else, il più semplice da capire

se NON c'è st, (siccome st=digitalread....) è scritto prima è come dire
se il tasto NON è premuto
un ciclo for spegne le uscite

adesso il ramo if

se il tasto è premuto
un ciclo for per ogni uscita confronta il tempo passato dalla pressione del tasto con il NUMERO dell'uscita moltiplicato la costante passo

per la prima uscita il NUMERO è 0, la condizione è vera da subito
per la seconda uscita, numero 1, condizione vera dopo un "passo" di millisecondi
e via così..........

per risparmiare qualche variabile uso la condizione come argomento della digitalwrite corrispondente

esito di tutto questo?

non lo so, nel fine settimana sono in una casa differente e non ho la mia UNO

ma compilare compila e mi dice:

Lo sketch usa 1142 byte (3%) dello spazio disponibile per i programmi. Il massimo è 32256 byte.
Le variabili globali usano 24 byte (1%) di memoria dinamica, lasciando altri 2024 byte liberi per le variabili locali. Il massimo è 2048 byte.

qualche anima buona vuole provare per me a vedere se funziona?

oh bella, a pensarci lo ho semplificato ancora.......

basta invece della if (st) e della sua else
mettere solo questo

  // accendo le uscite in funzione di un tempo
  for (int i = 0; i < PIEDINI; i++) {
    digitalWrite(i, (millis() - h) > (i * passo)*st);
    // accendo o spengo l'uscita a seconda se millis supera h+N gradini

  }

la condizione if, e la sua else sono state "implicitate" passatemi il neologismo
dentro nella condizione di test del tempo

credo che se funziona la prima funziona anche questa

qualche anima buona che me la prova?

Claudio_FF:
Il modo corretto di usare millis() NON è vedere se ha raggiunto tinizio+x (che funziona solo per 50 giorni poi il ritorno a zero del contatore causa problemi)

Vero, ma intanto bisogna vedere se Arduino lo si lascia acceso per più di 50 giorni, e in questo caso probabilmente non è così.

Ma se anche fosse, intanto deve capitare che l'evento di accensione inizi MENO di un secondo dal limite di 2^32 del millis() e quindi finisca oltre, e la cosa è abbastanza poco probabile direi non vi pare?...

E poi è comunque una condizione semplice da gestire, sempre SE il programma deve poter girare per più di 50 giorni ed evitare che non vada in overflow.

Di metodi per gestire questa situazione ce ne sono, e ben noti, in siti come QUESTO ci sono tutte le info necessarie. Ma, ripeto, magari farlo solo se si ritiene probabile il caso e quindi gestire l'overflow.

docsavage:
... come al solito risultano alternative a quello che ho letto finora

ho pensato, non me ne vogliate se sono monomaniaco, ad un array di piedini per le uscite ed a un ciclo for per ciclarlo ...

Ehm ... a me sembrava di aver fatto piu o meno la stessa cosa, anche se in modo diverso ... :smiley:

(che poi, rileggendolo, mi sa che il primo for si poteva pure evitarlo ... :D)

per alternativo non mi riferivo all'uso di un array e di un for

mi riferivo alla condizione, scusa se non mi sono spiegato bene

Etemenanki:
(che poi, rileggendolo, mi sa che il primo for si poteva pure evitarlo ... :D)

scusa, a quale ciclo for ti riferisci?

forse mi sono perso......

No problem doc ... del resto non essendo un programmatore, faccio confusione pure io (soprattutto io, in effetti :D)

Ad esempio, mi sono reso conto che si potrebbe gestire tutto quanto usando solo un paio di if ... una cosa di questo tipo (sempre ammesso di non aver scritto cavolate :D)

...
definisci i pin dei rele' come 1, 2, 3, 4, 5, per comodita'
byte tempo = 1; // per sapere se il secondo e' passato
byte rele = 0; //per accendere le uscite
unsigned long prevmil = millis(); //per controllo tempo
...

setup()
...

void loop()
{
   if (digitalRead(PULSANTE) == HIGH) //pulsante premuto
   {
      if ((rele != 5) && (tempo = 1)) //se rele non tutti accesi e secondo trascorso
      {
         digitalWrite(rele + 1, HIGH); //uso il valore di rele+1 per accendere i pin
         tempo = 0; //azzero tempo per controllare quando passato un'altro secondo
         prevmil = millis(); //e resetto prevmil per lo stesso motivo
         rele++ //incremento variabile rele
      }
   }
   else //pulsante non premuto
   {
      digitalWrite(1, LOW);
      digitalWrite(2, LOW);
      digitalWrite(3, LOW);
      digitalWrite(4, LOW);
      digitalWrite(5, LOW);
      rele = 0;
      tempo = 1;
   }
   if (prevmil - millis() >= 1000) tempo = 1; //tempo=1 dopo un secondo da ultimo azzeramento
   ...
   ... resto del programma
   ...
}

... che dici, fa abbastanza pena ? ... :smiley:

docsavage:
scusa, a quale ciclo for ti riferisci?

Al mio primo esempio, non al tuo :wink:

Non credo faccia pena,
anchesì tu sei una delle mie figure di riferimento......

nemmeno io sono un programmatore, almeno non di lavoro

ho programmato:
TI59
ZX Spectrum
123
dBase
Clipper
Basic, Basica, GWBasic
anche il compilatore basic di IBM: BASCOM, quasi intercambiabile con BASICA
oltre a quick basic e qbasic (compilato e interpretato, diciamo così)
e questo negli anni 80
negli anni 90
VisualBasic for application, in particolare con Excel e Access
ma MAI riconosciuto per lavoro

e adesso Arduino
insomma per campare adesso mi tocca fare pompe

per tornare a bomba

senza entrare troppo nella organizzazione del tuo programma,
io farei solo queste modifiche, che mi balzano all'occhio a prima vista

if (digitalRead(PULSANTE)) //pulsante premuto, non serve ==HIGH
   {
      if ((tempo) && (rele<5)) //se rele non tutti accesi e secondo trascorso
//inverto la condizione solo in previsione di un ulteriore test
// uso un esplicito <5 per evitare che se per caso "scappa" oltre il 5 ricomincia a contare
// potrebbe scappare solo per errori di programmazione da altre parti, che non ci sono, ma non si sa mai 

      {
         digitalWrite(++rele, HIGH); //uso il valore di rele+1 per accendere i pin ma lo esplicito con un ++
// davanti, e non dietro come di solito, per provocare il calcolo PRIMA che venga valutata l'espressione
         tempo = 0; //azzero tempo per controllare quando passato un'altro se condo
         prevmil = millis(); //e resetto prevmil per lo stesso motivo
         //rele++ //incremento variabile rele, non serve già fatto
      }

scusami, non ho resistito....
quando ti sei chiesto se faceva pena, non ho resistito a guardarla con occhio critico
ma noterai che ho scritto solo banalità

ti basta come risposta alla tua domanda?

o per meglio essere espliciti:

mi sembrava buona anche prima

docsavage:
oh bella, a pensarci lo ho semplificato ancora.......

basta invece della if (st) e della sua else
mettere solo questo

  // accendo le uscite in funzione di un tempo

for (int i = 0; i < PIEDINI; i++) {
   digitalWrite(i, (millis() - h) > (i * passo)*st);
   // accendo o spengo l'uscita a seconda se millis supera h+N gradini

}





la condizione if, e la sua else sono state "implicitate" passatemi il neologismo
dentro nella condizione di test del tempo

credo che se funziona la prima funziona anche questa

qualche anima buona che me la prova?

Nel provarlo spero di non aver fatto errori, ma solo i primi 3 relay si accendo, tutti insieme...
non so se posso linkare tikercard, ma qui c'è un test online di quanto hai scritto.

Per il discorso dei 50 giorni, è chiaro il motivo per cui si tende a scrivere un codice affidabile nel tempo, ma considerando l'osservazione di docdoc:

Vero, ma intanto bisogna vedere se Arduino lo si lascia acceso per più di 50 giorni, e in questo caso probabilmente non è così.

vi confermo che il sistema sarà riavviato ogni giorno per necessità dell'impianto stesso. Quindi il problema, in questi termini non c'è. In ogni caso è interessante conoscere la soluzione anche per il caso dei 50+ giorni.

Ma tornando al codice che ha funzionato, almeno per le prove che ho fatto io, ho solo bisgogno di capire come docdoc lo aveva concepito con i millis:

// numero di relè
#define NUMRELE 5
// Pin di ogni relè
int pin[5] = { 3,4,5,6,7 };
// Relè attualmente attivo (-1 indica "nessun relè")
int releON = -1; 
// Flag fine ciclo relè
bool fineCiclo = false;
// Pin del pulsante
#define PULSANTE 12

void setup() {
  Serial.begin(115200); //attivo comunicazione seriale
  // Inizializzazione pin dei relè e li spengo
  for (int i=0; i<NUMRELE; i++) {
    pinMode(pin[i], OUTPUT);
 digitalWrite(pin[i], LOW);
  }
  // Inizializzazione pin pulsante
  pinMode(PULSANTE, INPUT);

  }

void loop() {
  // Vedo se il tasto è stato premuto
  if ( digitalRead(PULSANTE) == HIGH ) {
    if ( !fineCiclo ) {
      // Passo al successivo rele?
      // (all'inizio poiché parto da -1, il primo sarà 0)
      releON++;
      if ( releON < NUMRELE ) { // Se non ho ancora finito
        digitalWrite(pin[releON], HIGH); //accendo
        Serial.print("Relè ");
        Serial.print(releON);
        Serial.println(" acceso");
        delay(1000); // aspetto 1 secondo 
      }
      else
      { // Ho finito!
        fineCiclo = true; // Segnalo che ho finito
      }
    }
  }
  else
  { // Tasto non premuto o rilasciato, resetto le variabili
    spengo();
    resetto();
  }

  funzione_esterna();

} // fine loop

void spengo() {
  for (int i=0; i<NUMRELE; i++) {
    digitalWrite(pin[i], LOW);
  }
  releON = -1;
}

void resetto() {
  fineCiclo = false;
  releON = -1;
}

void funzione_esterna(){
  if ( fineCiclo ) {
    Serial.println(" tutti i relay sono stati accesi e la nuova funzione è stata richiamata correttamente");
    //nuova funzione da preparare
  }
}