Controllo per superamento tempo massimo

Buone feste a tutti, vado alla mia richiesta.
il compito di una parte del codice che sto elaborando e che riporto di seguito, deve gestire due motori elettrici il primo azionato da un pressostato semplicemente on/off ed il secondo da un livellostato al riaprirsi del quale una temporizzazione prolunga il lavoro.
Adesso vorrei aggiungere ad ognuno dei cicli un controllo che limiti il tempo massimo di marcia in modo che una situazione imprevista (richiesta eccessiva o guasto contatti di input) non porti ad un funzionamento continuo i due motori.
Il controllo deve poter essere attivato o disattivato dipendentemente dal livello di un input.
Ho tentato con millis ma non riesco ad applicarlo in modo corretto.

 void trattamentoAcqua(){

    bool h2oState = digitalRead(presPozzo);
  if(h2oState == HIGH) {
    Serial.println ("BASSA PRESSIONE H2O");
    digitalWrite(poH2o, LOW);
  }
  else {
    digitalWrite(poH2o, HIGH);
  } 

    bool neraState = digitalRead(livNera);
  if(neraState == HIGH) {
    Serial.println ("LIVELLO MX POZZO NERO ");
    digitalWrite(poNera, LOW);
    start1=millis();
  }
  
  if ((millis() - start1) >= time1) {
    Serial.println ("STOP POMPA");
    digitalWrite(poNera, HIGH);
  } 
 }

Grazie

Dovresti impostare start1 solo la prima volta e non ad ogni giro di loop().

Credo che quello che ha scritto sia il timer per "prolungare il lavoro", un classico timer Toff retriggerabile.

Se non ho capito male sono da implementare altre due temporizzazioni, di tipo Ton, che facciano un ovverride sulle uscite, disabilitandole comunque anche se le condizioni di attivazione risultano ancora vere.

Non ho capito invece se queste temporizzazioni aggiuntive devono essere a loro volta disabilitabili e in quale modo.

Se la logica fosse scritta sotto forma di macchine a stati situazione/evento/azione la modifica sarebbe semplicissima, come la frase in italiano che la descrive: se situazione X, se avviene Y, esegui Z.

Invece con la logica scritta in senza stati espliciti è molto complesso, anche perché la scrittura delle uscite deve comunque essere accentrata, non può restare sparsa in diversi if potenzialmente interferenti tra loro.

C'è da dire che comunque il tutto è già pensato/scritto in modo non bloccante, e questa è un ottima partenza.

Un'alternativa "esotica" per compiti di automazione così semplici è di pensare in ladder, praticamente una riga per ogni rung:

void trattamentoAcqua() 
{
  static byte f1 = 0;
  static byte f2 = 0;
  static byte f3 = 0;
  static unsigned long t1 = 0;
  static byte T1 = 0;
  static unsigned long t2 = 0;
  static byte T2 = 0;
  static unsigned long t3 = 0;
  static byte T3 = 0;

  unsigned long now = millis();

  byte h2oState  = digitalRead(presPozzo) == HIGH;

  byte neraState = digitalRead(livNera) == HIGH;

  if (h2ostate && !f1) { Serial.println ("BASSA PRESSIONE H2O"); }

  if (!h2ostate && f1) { Serial.println ("PRESSIONE H2O NORMALE"); }
  f1 = h2ostate;

  if (!h2ostate) { t1=now; T1=0; } else if (now-t1 > TIMEOUTH) { T1=1; }

  if (neraState && !f2) { Serial.println ("LIVELLO MAX POZZO NERO"); }
  f2 = neraState;

  if (neraState) { t2=now; T2=1; } else if (now-t2 > time1) { T2=0; }

  if (!T2 && f3) { Serial.println ("STOP POMPA"); }
  f3 = T2;

  if (!T2) { t3=now; T3=0; } else if (now-t3 > TIMEOUTN) { T3=1; }

  digitalWrite(poH2o, (h2ostate && !T1) ? LOW : HIGH);

  digitalWrite(poNera, (T2 && !T3)      ? LOW : HIGH);
}

Dimmi fratt in che punto andrebbe inserito l'inizio conteggio per fare un solo giro?

Per Claudio_FF.
In effetti è così, il primo tempo serve ad allungare il lavoro per la poNera (di 5 minuti) per completare il lavoro di svuotamento dopo la riapertura del livello,
L2 2 temporizzazioni per il controllo dell' override è di norma abilitato, deve poter essere disabilitato perché controproducente in talune circostanze tramite un selettore e a fine conteggio devono poter interrompere il funzionamento del corrispondente motore in ogni circostanza anche se il relativo contatto di comando e perennemente chiuso
Da un po provo a capire questa tua logica che incontro per la prima volta e da buon neofita non ho esperienza.
Forse mi aiuterebbe avere la descrizione delle static byte.
Ciao

Le static sono variabili locali definite nella funzione, che però mantengono il valore come se fossero globali definite all'esterno. Ho definito static tutte le variabili che devono contenere/mantenere lo stato interno della funzione tra una chiamata e l'altra.

Ho proposto logica ladder perché dici di aver lavorato con i PLC. In questo caso la disabilitazione delle temporizzazioni aggiuntive si ottiene semplicemente chiudendo due contatti in parallelo ai contatti negati T1 e T3 negli ultimi due rung dello schema (quelli che comandano le uscite).

Comunque in generale affrontare il discorso macchine a stati è propedeutico per realizzare qualsiasi altro tipo di automazione, anche molto più complessa, in modo molto più semplice.

EDIT: mi ero dimenticato la definizione della variabile 'now', che è il valore attuale di millis. Adesso l'ho aggiunta nella funzione, ma nei miei programmi è una variabile globale che viene valorizzata ad ogni ciclo di loop (proprio come prima istruzione della funzione loop) e rappresenta il tempo attuale istantaneo valido in tutto il programma.

  1. Quindi le static non vengono azzerate ad ogni ciclo come suggerirebbe la assegnazione.
  2. Ti ringrazio della similitudine con il ladder che mi fa sentire a casa mia ma anche se è semplice aggiungere due contatti in parallelo a T1 eT3 cosa diversa per me ( spero solo per ora ) tradurlo nei concetti matematici tipici del wiring.
  3. Mi sono arreso nell'applicazione del Millis perchè anche se concettualmente semplice non mi riesce di utilizzarlo correttamente. Non sono riuscito a farlo nemmeno nella porzione di codice che ho postato che per quanto funzionante non si azzera mai in quanto probabilmente sempre richiamato dal ciclo ed infatti continua a stampare "STOP POMPA"
    Ho letto vari codici di esempio trovati online in questo forum che apprezzo, ed in altre pagine web trovate on line, ma in ogni esempio sembra che non venga utilizzato per altro che far lampeggiare uno o più led in un ciclo ripetitivo e non per un uso fine a se stesso che comporti un unico ciclo.
    4)Avevo letto una breve introduzione alla programmazione a stati finiti (forse Aliverti) ed in effetti sembrava interessante se applicato ad automazioni ma non ho trovato di più.

Ma poi quanti modi di programmazione esistono?

Ciao

Mi verrebbe da dire infiniti

Per quanto riguarda l'uso millis() non si tratta di "schemi di funzionamento" da applicare, ma di capirne il principio ed applicarlo secondo le proprie esigenze. millis() o micros() non fanno altro che restituire il "tempo di sistema", sta al programmatore ed alla sua "fantasia" dopo.

Visto che sei pratico di PLC, se conosci lo STEP7, c'è il blocco funzione SFC64 "TIME_TCK" che fa più o meno la stessa cosa.
Ad esempio ricordo di averlo usato in un progetto dove dovevo azionare una cinquantina di attuatori pneumatici controllando per ciascuno fine corsa e tempo di azionamento con una vecchia CPU S7-300 con pochissime risorse libere. Con un blocco FB multi-istanza e l'uso di SFC64 non ho "sprecato" nemmeno un timer.
Lo stesso approccio lo puoi usare nel mondo embedded con Arduino.

pinocchio1:

  1. Quindi le static non vengono azzerate ad ogni ciclo come suggerirebbe la assegnazione

Esatto, è un'inizializzazione che avviene solo alla prima esecuzione della funzione.

aggiungere due contatti in parallelo a T1 eT3 cosa diversa per me ( spero solo per ora ) tradurlo nei concetti matematici tipici del wiring

Contatti in serie sono un and &&
Contatti in parallelo sono un or ||
Contatto negato è un not !
Occhio alle parentesi per le precedenze:
a && b || c sono due contatti 'a' 'b' in serie, messi in parallelo con 'c'
a && (b || c) è un contatto 'a' in serie con il parallelo di 'b' e 'c'

non si azzera mai in quanto probabilmente sempre richiamato dal ciclo ed infatti continua a stampare "STOP POMPA"

No, quel codice è giusto e millis è usato correttamente. Il problema delle print multiple è un altro ed è dovuto al fatto che la condizione da quel momento in poi è sempre vera e il contenuto sotto condizione viene eseguito ad ogni giro. Per evitarlo vanno usate delle variabili flag per disabilitare volontariamente la condizione in modo da eseguire il contenuto solo una volta. Nel codice simil ladder sono le 'f1' 'f2' 'f3'. Se non ci fossero anche in quel codice le print non si fermerebbero mai.

Ma poi quanti modi di programmazione esistono?

Il bello di un linguaggio completamente general purpose è che si possono interpretare/progettare le cose in molti modi, compreso scriverci interpreti e compilatori per linguaggi di livello superiore con funzionalità nuove (vedi programmazione logica -prolog-, o programmazione funzionale e lambda calcolo inesistenti in C, ma presenti in linguaggi come Python che sono scritti in C/C++), quindi si, probabilmente la risposta di cotestatnt è corretta :slight_smile:

In ambito micro e automatismi ridimensioniamo un po', e ci limitiamo a espressioni logiche/booleane per simulare contatti e porte logiche, programmazione a stati espliciti (perché implicitamente qualsiasi elaborazione che tenga conto anche di un solo bit di "storia passata" è già un'elaborazione a stati, ma solo l'implementazione a stati espliciti è "discorsiva" come il pensiero umano), e la "normale" programmazione procedurale/imperativa degli esempi/tutorial dove le cose avvengono istruzione per istruzione nell'ordine in cui sono scritte, ma poco adatta per gestire più di UNA cosa assieme. Tutto questo si può usare mescolato per trarre il massimo vantaggio/semplificazione.

Conosco i plc Siemens e il loro tipo di programmazione ma ho più frequentemente usato Ormon e similari per motivi di costo e di immediatezza d'uso dei software, spesso il denaro comanda.
Innegabile che i Siemens siano apprezzati per la affidabilità e l' ottimo uso della velocità e del risparmio di memoria.
Cambiando argomento, devo dire che le vostre disquisizioni sulla programmazione mi trovano entusiasta, ma se è vero che per fare la guerra servono i soldati per programmare servono i mezzi ovvero conoscenza ed esperienza dello specifico, da qui in avanti l' estro e la fantasia possono liberare i freni.
Io mi sono avvicinato da poco a questa specialità e le mie conoscenze bastano per ora a riempire un bicchiere.
Da persona concreta voglio quindi aggiungere una goccia al mio bicchiere ed a riguardo di millis che ritengo indispensabile vorrei chiedervi: con quale criterio va posizionato l' inizio conteggio? Come si usano i flag per il reset? Lo stesso punto di inizio può essere usato per più conteggi?
Ringrazio Claudio_FF per la precisazione sulle operazioni booleane che mi eserciterò a scrivere in seguito, non avrei mai pensato che ci fosse una relazione diretta con la stesura ladder o comunque simbolica.
Cercherò anche maggiori informazioni sulla programmazione a stati finiti che da quanto ho capito dovrebbe stabilire un filo logico in una sequenza di operazioni.
Per intanto grazie :slight_smile:

pinocchio1:
per programmare servono i mezzi ovvero conoscenza ed esperienza dello specifico

Beh, alla fine i concetti fondamentali e generali non sono poi tantissimi, è che per capirlo servono sempre anni :grinning:

riguardo di millis che ritengo indispensabile vorrei chiedervi: con quale criterio va posizionato l' inizio conteggio?

millis ritorna il tempo di sistema, è come leggere l'orologio (solo che è in millisecondi e arriva a 232), si può usare per misurare la durata di qualcosa (ti segni l'ora di inizio e vedi quanto è passato al momento della fine), per produrre qualcosa di una certa durata (ti segni l'ora di inizio e quando è trascorso tot si fa, o si smette di fare, quello che serve), o per produrre operazioni periodiche. L'errore dovuto agli oscillatori non quarzati è di 5..10 secondi all'ora.

L'inizio del conteggio è il momento da cui ti interessa iniziare a misurare/fare qualcosa, che sarà identificato dal verificarsi di una certa condizione.

Come si usano i flag per il reset?

Non sono sicuro di capire la domanda. I flag, bandierine, indicatori, piattini con fagioli sono solo variabili, merker, informazioni che servono per tenere conto di cosa fare o non fare, non un qualcosa di specifico e particolare legato alla macchina. Hai solo variabili in cui memorizzare dati, operazioni (aritmetiche o logiche), e condizioni da testare per fare questo o quest'altro.

Lo stesso punto di inizio può essere usato per più conteggi?

Anche qui non capisco. Per stesso punto di inizio intendi la variabile o il momento memorizzato nella variabile? In genere è molto più comodo avere una diversa variabile tempo per ogni conteggio/temporizzazione. Una stessa variabile intesa come contenitore può essere usata in contesti/punti diversi purché non contemporaneamente... se ci sto cucinando la pasta non posso metterci i würstel (o meglio, niente lo impedisce ma i risultati finali saranno diversi da quanto ci si aspetta).

Cercherò anche maggiori informazioni sulla programmazione a stati finiti che da quanto ho capito dovrebbe stabilire un filo logico in una sequenza di operazioni

In ambito PLC si parlerebbe di SFC, solo che l'SFC è "vincolato" e ISOnormato a ben precise scelte/possibilità/limiti.
Qui vedilo come metodo per pensare / descrivere / progettare un processo partendo dall'elenco degli stati di funzionamento possibili (generalmente corrispondenti alle "attese di qualcosa"), dagli eventi a cui rispondere in ogni stato, ed infine dalle azioni in risposta agli eventi. Se ne sta parlando anche in questo post per evidenziare come questo metodo permette di descrivere/simulare il tutto in italiano, e tradurlo poi praticamente uno a uno. Permette modifiche e ampliamenti in modo semplice (si possono aggiungere eventi e stati senza stravolgere le altre parti). E soprattutto permette il completo multitasking... nella la mia sveglietta per la camera (con ArduinoMega) girano almeno una ventina di macchine a stati che si occupano in parallelo di tutte le sotto operazioni.

Vedo di spiegarmi meglio:
in questa parte di codice

bool neraState = digitalRead(livNera);
if(neraState == HIGH) {
Serial.println ("LIVELLO MX POZZO NERO ");
digitalWrite(poNera, LOW);
start1=millis();
}

if ((millis() - start1) >= time1) {
Serial.println ("STOP POMPA");
digitalWrite(poNera, HIGH);
}

start1=millis(); l' avrei potuto scrivere anche appena prima della riga if ((millis() - start1) >= time1)
oppure in un altro punto del listato precedente o seguente oppure no?

La domanda relativa al flag non era relativa alla variabile stessa ma a come viene impiegata
per eseguire un reset di un conteggio millis.

start1=millis(); l' avrei potuto scrivere anche appena prima della riga if ((millis() - start1) >= time1)
oppure in un altro punto del listato precedente o seguente oppure no?

L'hai scritto nel punto giusto per far si che il suo valore continui ad inseguire quello di millis solo finché la prima condizione resta vera.

Solo quando la prima condizione non è più vera il valore di 'start1' rimane fisso, e il tempo trascorso calcolato dalla seconda condizione inizia ad aumentare, fino al punto da superare il limite previsto e far avverare anche la seconda condizione, che poi risulta vera ad ogni ciclo (e quindi stampa stop pompa ad ogni ciclo).

Quando la prima condizione torna vera la variabile 'start1' ricomincia ad essere aggiornata al valore di millis e la seconda condizione ritorna falsa.

Se scrivi start1=millis(); in un altro punto si ottiene una logica diversa.

In particolare scritta appena prima di if ((millis() - start1) >= time1) seguirebbe incondizionatamente il valore di millis aggiornandosi ad ogni ciclo, e la seconda condizione non potrebbe mai diventare vera, in sostanza la pompa non si fermerebbe mai.

Chiaro.
Adesso come dovrei usare un flag ed in che modo per annullare la condizione di sempre vero della seconda condizione?

bool neraState = digitalRead(livNera);
if(neraState == HIGH) {
    Serial.println ("LIVELLO MX POZZO NERO ");
    digitalWrite(poNera, LOW);
    start1=millis();
    flag = 1;
}

if (flag  &&  ((millis() - start1) >= time1)) {
    Serial.println ("STOP POMPA");
    digitalWrite(poNera, HIGH);
    flag = 0;
}

Questa potrebbe invece essere una traduzione discorsiva a stati. La variabile primo giro è un flag per abilitare la partenza pompa solo alla prima esecuzione dello stato accesa. Certo è qualche riga in più, però ogni cosa avviene in modo esattamente predeterminato quando si verifica la rispettiva condizione, non ci sono dubbi o ambiguità, non si verificano mai print in esubero ecc.

se fase attesa
    se nerastate high
        fase = accesa
        primo giro = 1

altrimenti se fase accesa
    se primo giro
        print livello nero max
        accendi pompa nera
        primo giro = 0
    se nerastate low
        carica tempo
        fase = mantieni

altrimenti se fase mantieni
    se nerastate high
        fase = accesa    
        primo giro = 1
    altrimenti se timeout time1
        print stop pompa
        spegni pompa nera
        fase = attesa

Quindi è sufficente dichiarare on e off una variabile.
Il mio dubbio riguardava il fatto che il valore del flag dovesse essere subordinato ad altre condizioni, alle volte un esempio vale più delle parole.
Grazie della pazienza

pinocchio1:
Dimmi fratt in che punto andrebbe inserito l'inizio conteggio per fare un solo giro?

Scusa, non ti ho più risposto. Comunque vedo che con Claudio sei andatao avanti bene.

Nessun problema, fin qui bene ma dubito che andando avanti nella stesura del mio codice o di un successivo non ci si risenta.
Grazie

Anche perché il quesito del primo post non è ancora stato affrontato, si è fatta una lunga parentesi parlando di altre cose, ma ora alla luce di queste sono possibili diverse vie a seconda del metodo di programmazione scelto, a contatti puro (simil ladder), a stati espliciti discorsivi (macchina a stati), o algoritmico con flag vari da settare/resettare (praticamente il frammento di codice postato).

Il primo è il più compatto ma anche il più criptico e incomprensibile.
Il secondo è il più lungo ma chiaro e progettabile a priori.
Il terzo una via di mezzo che si presta tanto a virtuosismi informatici, quanto altrettanto facilmente a effetti collaterali e bug incomprensibili :slight_smile:

Posso chiederti tu quale usi o comunque quale consigli?
Tra i tanti a quale quesito ti riferisci?
Approfitto anche per togliermi una curiosità, le lettere P e N all'interno dei contatti cosa indicano? Non ricordo di averle viste nei ladder che ho avuto occasione di usare.