Macchina a stati con tempi (millis)

Salve a tutti
sto testando un prototipo di riempimento di un silos di materiale (segatura) mediante l' avvio e lo spegnimento di 3 motori (simulati da 3 led), comandati da due sensori capacitivi per controllare il livello MAX e il livello MIN.(simulati con 2 interruttori a scorrimento).
Ho usato il sistema della macchina a stati in quanto i led hanno comportamenti diversi a seconda dell' ordine di attivazione dei sensori.
Il mio problema è che i led devono accendersi a distanza di 5 secondi uno dall'altro in fase di accensione, e devono spegnersi a distanza di 5 secondi uno dall' altro in fase di spegnimento.
Cioè 1-pausa -2 -pausa 3 ...poi 3-pausa-2-pausa-1.
Nello sketch che posto ho provato a farlo ma il comportamento dei
LED non è quello voluto.
Chiedo aiuto.

/* 
Controllo Caricamento silos segatura con due sensori capacitivi simulati con 2 tasti a scorrimento
(esempio di macchina a stati)

D2: sensore livello minimo del silos
D3: sensore livello massimo del silos

Il carico è effettuato medianto un soffiante che aspira la segatura da un mulino raffinatore che a sua volta
è alimentato da una vasca contenente cippato di legno, quindi in totale sono 3 motori che qui vengono simulati con 3 led
che però devono partire o fermarsi (comandati dai sensori) a distanza di 5 secondi uno dall' altro.
Sequenza partenza: Soffiante-pausa 5 sec.-Mulino-pausa 5 sec.-Vasca
Sequenza Stop: Vasca-pausa 5 sec.-Mulino-pausa 5 sec.-Soffiante


 min MAX| motori| stato
---------------------------------
 1       1       |   0     |   1
 1       0       |   0     |   2
 0       0       |   1     |   3
 1       0       |   1     |   4
*/


int stato = 1;
unsigned long t1,dt;        
const long interval = 5000; 
          
void setup() {
//unsigned long t1 = millis;
  Serial.begin(9600);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
}

void loop(){
  
 dt = millis()- t1;
if (dt>=interval)
  t1 = millis();


  //leggo i sensori
  int lmin = digitalRead(2);
  int lmax = digitalRead(3);
  
  String str = "s: " + String(stato) + 
    " min: " + String(lmin) + " max:" + String(lmax);
  Serial.println(str);

   
  switch (stato){
    case 1:
      digitalWrite(9, LOW);
      if (dt>=interval){
      digitalWrite(8, LOW);
      dt = millis()-t1;
      t1 = millis();
      if (dt>=interval)
      digitalWrite(7, LOW);
      dt = millis()-t1;
      t1 = millis();
      }
     //cambio di stato se:
      if (lmin && !lmax) stato = 2;
    break;   
    case 2:
      digitalWrite(9, LOW);
      if (dt>=interval){
      digitalWrite(8, LOW);
      dt = millis()-t1;
      t1 = millis();
      if (dt>=interval)
      digitalWrite(7, LOW);
      dt = millis()-t1;
      t1 = millis();
      }
      //cambio di stato se:
      if (!lmin && !lmax) stato = 3;
    break;
    case 3:
      digitalWrite(7, HIGH);
      if (dt>=interval){
      digitalWrite(8, HIGH);
      dt = millis()-t1;
      t1 = millis();
      if (dt>=interval)
      digitalWrite(9, HIGH);
      dt = millis()-t1;
      t1 = millis();
      }
      //cambio di stato se:
      if (lmin && !lmax) stato = 1;
    break;
    case 4:
      digitalWrite(7, HIGH);
      if (dt>=interval){
      digitalWrite(8, HIGH);
      dt = millis()-t1;
      t1 = millis();
      if (dt>=interval)
      digitalWrite(9, HIGH);
      dt = millis()-t1;
      t1 = millis();
      }
      //cambio di stato se:
      if (lmin && lmax) stato = 1;
    break;
    
    }  
  
  delay(200);
  
  }
[/code]


Ogni pausa deve essere uno stato.

Questa cosa è oscena:

  String str = "s: " + String(stato) + 
    " min: " + String(lmin) + " max:" + String(lmax);
  Serial.println(str);

E' meglio così:

Serial.print ("s: ");   Serial.print (stato);
Serial.print ("min: "); Serial.print (lmin);
Serial.print ("max: "); Serial.print (lmax);
Serial.println (str);

Non ho capito bene la logica, ma i casi 3 e 4 sono uguali?...
Comunque, se funzionasse, quel blocco ripetitivo:

      digitalWrite(7, HIGH);
      if (dt>=interval)
        {
        digitalWrite(8, HIGH);
        dt = millis()-t1;
        t1 = millis();
      if (dt>=interval)
      digitalWrite(9, HIGH);
      dt = millis()-t1;
      t1 = millis();
      }

potresti trasformarlo in una funzione verfica(x, y, z):

void verifica (byte x, byte y, byte z)
{
      digitalWrite(x, HIGH);
      if (dt>=interval){
      digitalWrite(y, HIGH);
      dt = millis()-t1;
      t1 = millis();
      if (dt>=interval)
      digitalWrite(z, HIGH);
      dt = millis()-t1;
      t1 = millis();
}

ma c'è qualcosa che non va nelle parentesi e in quei doppi if (dt>=interval). Se avessi fatto gli allineamenti corretti te ne saresti accorto subito!

Hai ragione in parte, perchè in realtà fanno la stessa cosa ...ma a seguito di un comportamento diverso dei sensori.
Se quando inizia il loop ho la vasca piena, avrò Lmin & Lmax (attivi) quindi non mi serve attivare i motori (accendere il led), e questo lo faccio con lo stato 4.
Mentre lo stato 3 mi accende i motori (i Led) a seguito di una lettura di Low di entrambi i sensori sensori.
All' inizio avevo messo un if all' inizio prima del switch che mi teneva fermo il tutto finchè i sensori erano tutti e due High,
Così:
if (lmin && lmax);{
}
poi a furia di fare prove ho provato anche a tirarlo via e mettere 4 stati.

...non mi è chiaro cosa farei con la funzione che mi hai suggerito.
Flaviano

Alla fine tutto si riduce in una semplice regola:

Sensore 1 (negato) accende i motori
Sensore 2 spegne

E in due sequenze di timer per sequenziare, appunto, i tre motori

Mesi fa uno cercava la stessa cosa per due motori ( o una pompa e una valvola, qualcosa del genere)

Comunque si tratta di cosa semplice, se ben impostata

Manca piuttosto definire cosa serve fare se lo spegnimento interviene "prima" che sia completata la sequenza di accensione
E naturalmente viceversa

Il tutto si riassume, è vero, in una macchina a stati
Ma gli stati non sono esattamente quelli indicati dallo OP

stato 0: attesa.
Se il livello scende al di sotto del minimo, accende Motore 1 -> stato 1.
stato 1: attende che siano trascorsi 5 secondi, poi accende Motore 2 -> stato 2.
stato 2: attende che siano trascorsi 5 secondi, poi accende Motore 3 -> stato 0.

stato 0: attesa.
Se il livello raggiunge il massimo, spegne Motore 3 -> stato 3.
stato 3: attende che siano trascorsi 5 secondi, poi spegne Motore 2 -> stato 4.
stato 4: attende che siano trascorsi 5 secondi, poi spegne Motore 1 -> stato 0.

Io vedo un diverso tipo di problema, ma questo dipende da cosa fanno (o si suppone che facciano) nella realta' gli oggetti mulino, vasca e soffiante.

Ad esempio, presumiamo che mulino produca la segatura, vasca la raccolga dal mulino e soffiante la immetta nel silos, l'accensione sarebbe corretta anche come mulino > vasca > soffiante (anche se sarebbe meglio l'opposto), ma anche lo spegnimento dovrebbe essere uguale mulino > vasca > soffiante (se spengo prima soffiante, vasca e mulino si intasano o comunque si riempiono, e magari al secondo ciclo di accensione qualcosa si scassa o non funziona)

Quello che intendo e' che se stai facendo solo un test tuo, per divertimento o esercizio, va bene tutto, se stai facendo, ad esempio, un modello didattico, anche queste cose andrebbero prese in considerazione. :wink:

Questo dipende da quanta "logica", cioè altri flag, si vogliono far stare dentro uno stato. Ragionando solo con una sola variabile di stato direi:

0: se < minimo:     on motore 1  -> stato 1
1: se trascorsi 5s: on motore 2  -> stato 2
2: se trascorsi 5s: on motore 3  -> stato 3
3: se > massimo:    off motore 3 -> stato 4
4: se trascorsi 5s: off motore 2 -> stato 5
5: se trascorsi 5s: off motore 1 -> stato 0

Questo senza tenere conto di quanto detto da Etemenanki e senza fare altre considerazioni, tipo avvio/arresto manuale, timeout in caso di guasto sensori ecc.

5467

Massimo e minimo li avevo messi nello stesso stato 0: se raggiunge il minimo fa una cosa; se raggiunge il massimo ne fa un'altra. In queto modo, per fermare i motori non deve necessariamente trovarsi nella stato 3.
0 -> 1 -> 2 -> 0
0 -> 3 -> 4 -> 0

a dir la verità a me sembra che siano i casi due e quattro ad essere uguali, come sensori anche se non come motori

comunque, secondo me la maniera più semplice per fare il lavoro è questa

array di piedini
nel setup () tutto il cinema necessario
nella loop() pochi casi if
se non Livello basso accende (stato=1) e tempo zero
se Livello alto spegne (stato=0) e tempo zero
se stato (occhio che c'è un else)
cicla l'array e alza in progressione i piedini che hanno superato il timeout
else
cicla l'array a rovescio e abbassa in progressione i piedini che hanno superato il timeout (occhio che il timeout va calcolato a rovescio)

con 56 righe si fa tutto

e sono 56 righe delle mie che scrivo più righe vuote e righe con solo graffe e metto graffe anche a cicli for di una sola riga

se si contasssero le sole righe con statement non saprei, ma sarebbe uno "sputicchio" di programma

anzi, adesso lo faccio, conto gli statement

contati
sono 27 righe, uno statement per riga
mettendo le poche parentesi graffe obbligatorie alla fine della riga che le richiede

come sempre in C, ci vuole più "inchiostro" a dirlo che a farlo...

Grazie a tutti per le risposte.
Inizio dallo spiegare il perchè del ritardo nelle accensioni, come (quasi) giustamente ha intuito Etemenanki si tratta di ovviare a un problema di intasamento alle successive accensioni. Infatti la vasca (per mezzo di una coclea) va ad alimentare il mulino e il soffiante/aspiratore va ad aspirare il materiale dal mulino, per cui per evitare che il mulino si ingorghi bisogna spegnere prima la vasca, poi dopo 5 secondi il mulino in modo da lasciargli il tempo di svuotarsi del materiale che si trova all' interno e dopo altri 5 secondi spegnere l' aspiratore che estrae il materiale raffinato dal mulino e lo manda dentro al silos.
Alla successiva accensione si avrà l' avviamento in senso inverso cioè 1-aspiratore 2-mulino 3-vasca cosicchè quando la coclea della vasca inizierà a fuoriuscire materiale il mulino sarà già avviato al massimo dei giri perchè era vuoto dallo spegnimento precedente.

Allego schema del processo di riempimento e svuotamento del silos in cui si nota che i motori assumono stati differenti a seconda che si stia riempiendo o svuotando.
Infatti i motori in fase di riempimento rimarranno accesi finchè il materiale non avrà raggiunto il Livello max, poi si spegneranno e rimarranno spenti finchè il materiale non scenderà sotto il Livello min.

Ho trascurato il fatto che il materiale viene espulso dal silos riempito mediante un altra coclea, perchè questo non fa parte del processo che stiamo esaminando.

...in effeti è un modello didattico, non è solo per divertimento mio!

in effeti hai ragione! è oscena, ma questa parte l' ho copiata pari pari!
Meglio il serial.print

dammi qualche indicazione in più che provo a farlo...
non cerco la pappa fatta! :stuck_out_tongue_winking_eye:

Sembra di capire che si sta parlando di sequenze. Nelle sequenze A, B, C le leggiamo normalmente da sinistra verso destra. Invertendo il senso di lettura abbiamo C, B, A.

Se A, B e C sono i motori è corretto accenderli o spegnerli con questa sequenza?

Ora, l'accensione e spegnimento sono azioni composte. L'azione composta è la seguente: A(on), 5s, B(on), 5s, C(on). L'azione composta si considera eseguita dopo 10 secondi e la verifica è che A, B, C risultano accesi.

L'azione composta di spegnimento è identica, ciò che cambia è la sequenza che risulta invertita: C(off), 5s, B(off), 5s, A(off).
L'azione composta di spegnimento si considera eseguita dopo 10 secondi e la verifica è che A, B, C risultano spenti.

L'azione composta richiede quindi una variabile di stato che indica lo stato. Lo stato può essere: "In esecuzione", "ON", "OFF".

Mi concentrerei su un funzione C che esegue questa azione composta.

Ciao.

Si è una sequenza da seguire , prima A, (5s)B (5s), C poi C,(5s) B (5s), A sempre che sia abbia il consenso dai sensori come indicato nello schema qui sopra (post 13).

Ok. Supponi di avere una funzione già pronta che esegue questa azione composta. Ad esempio la funzione si chiama "azionaMotori" e prende come argomenti ON o OFF.

Lo pseudo codice potrebbe essere qualcosa di simile alla codifica finale.

IF SILOS(FULL) 
    AZIONA_MOTORI(OFF)
IF SILOS(EMPTY)
   AZIONA_MOTORI(ON)

Più che parlare di sensori tiro in ballo lo stato del SILOS che mi aiuta a descrivere l'algoritmo in modo che sia comprensibile.

PS: non ho il codice pronto e il mio teorizzare è quello che faccio normalmente quando affronto un nuovo problema.

Il fulcro del problema sembra proprio la funzione che accende (e spegne) i motori in sequenza e se l'azione non è ancora completa se lo appunta nella variabile di stato.

Si il fulcro è proprio la funzione che inverte la sequenza A, B, C.

Per modellare la sequenza usero un array.
Se comprendi il codice di seguito possiamo andare avanti.

#define MOTORE_A   7
#define MOTORE_B   8
#define MOTORE_C   9

const long interval = 5000; 
byte motori[] = { MOTORE_A, MOTORE_B, MOTORE_C };
byte indiceMotori;  

byte stateMachine;
bool stateOfAction;     

void azionaMotori( bool tf ) {
    switch (stateMachine) {
         case 0:
             if (stateOfAction != tf) {
                  stateOfActione = tf;
                  digitalWrite(motori[indiceMotori], stateOfAction);
                  // salva millis() e passa al prossimo stato.
             }
             break;
    }
}

void setup() {

  Serial.begin(9600);
  //pinMode(2, INPUT);
  //pinMode(3, INPUT);
   for (byte i=0; i<3; i++) {
        pinMode(motori[i], OUTPUT);
   }
}

void loop() {
    azionaMotori(true); 
}

Ciao.

io vorrei avere qui il programma del quale ho parlato pochi giorni fa...

ma purtroppo (o come cantava Francesco Guccini, per fortuna)
mi è capitato di salvarlo inavvertitamente con un nome sbagliato, quindi lo ho cancellato durante una pulizia

perché dico per fortuna?
perché così hai la stupenda occasione di seguire le mie indicazioni e fartelo da te, invece che copiarlo

sono sicuro che se segui le mie indicazioni sarai certamente in grado di ottenere il risultato richiesto
Sono sicuro perché avevo provato il programma...

Magari questa volta accetterai un consiglio...

non so, io te lo do lo stesso:
Questa volta, segui quello che ti si dice
perché la volta scorsa ti avevo dato indicazioni al settimo post della discussione, e tu all'ottavo mi avevi dato torto, sei arrivato al centoventiquattresimo per ottenere la soluzione

Questa volta non mi lascerò "trasportare" in giro dalle tue considerazioni "fantasiose":
fai quello che ti ho indicato oppure cerca un'altra strada senza il mio aiuto

Io accetto l' aiuto di tutti per arrivare alla soluzione, solo che io non essendo un esperto faccio più fatica a seguire te piuttosto che Maurotec, nel senso che è più probabile che ci arrivi da solo se ho un codice di base di partenza (anche stilizzato). Tutto qui.
Anche tu hai detto che avevi risolto...ma non hai messo neanche una riga di codice e quindi, ripeto, mi riesce difficile uscirne partendo solo da una decrizione.

Emmm ... però hai presente il punto 16.1 del REGOLAMENTO ? ...

Tenete sempre presente che qui sul forum nessuno scrive software per conto terzi o realizza schemi su ordinazione, ma si aiuta chi viene qui a correggere/ottimizzare il software che lui scrive o gli schemi che lui realizza .

... quindi, TU dovresti scrivere il codice e noi dovremmo aiutarti a correggerlo ... :roll_eyes:

Guglielmo