Cambio stato switch case break e il delay non bloccante sembra non funzionare

Salve a tutti. Premesso che ho cercato in ogni fiume mare e lago ma non ho trovato una soluzione pertanto mi trovo a scrivere questo problema.
Ho un sensore ad ultrasuoni che si dovrebbe spostare a destra e a sinistra per leggere la distanza dall'ostacolo.
Dopo aver letto la distanza con un ritardo (NON BLOCCANTE) mi dovrebbe cambiare di stato con un cambio di case: .
Quello che non capisco è perchè appena entra nel case 0 tutto funziona ed aspetta (nell'esempio 5 secondi) prima di spostarsi sul case 1.
Quando arriva al case 1 nonostante abbia messo ancora un controllo del tempo con i millis mi passa direttamente al case iniziale.
Allego codice di esempio con anche la figura del serial monitor

//test lettura ultrasuoni non bloccante
int stato = 0;
float distanzaAnteriore;
float distanzaSinistra;
float distanzaDestra;
unsigned long tempoUltrasuoniAnteriore;
unsigned long millisPrecedentiDX = 0; 
unsigned long millisPrecedentiSX = 0;
const long intervallo = 5000;

#define PIN_TRIG_ANT 5 //pin ultrasuoni anteriore
#define PIN_ECHO_ANT 4 //pin ultrasuoni anteriore

void setup()
{
  Serial.begin(9600);
  //setPinUltrasuoni 
  Serial.println("Inizio");
  pinMode(PIN_TRIG_ANT, OUTPUT);
  pinMode(PIN_ECHO_ANT, INPUT); 
  digitalWrite(PIN_TRIG_ANT, LOW);
}

void loop()
{
  switch(stato)
  {
    case 0:
    {
      //inizia qui e quando passano 5 secondi va a stato 1
      giraSensDx();
      unsigned long millisCorrenti = millis();
      if(millisCorrenti - millisPrecedentiDX >= intervallo)
      {
        millisPrecedentiDX = millisCorrenti;
        distanzaDestra = distanzaAnteriore;
        Serial.println("stato 0");
        Serial.println(distanzaDestra);
        
        stato = 1; 
      }
      
    }
    break;

    case 1:
    {
      giraSensSx();
      unsigned long millisCorrenti = millis();
      if(millisCorrenti - millisPrecedentiSX >= intervallo)
      {
        millisPrecedentiSX = millisCorrenti;
        distanzaSinistra = distanzaAnteriore;
        Serial.println("stato 1");
        Serial.println(distanzaSinistra);
        
        stato = 0;
      }
    }
    break;
  }
}

void giraSensDx()
{
  controlloDistanzaOstacoloAnteriore();
}

void giraSensSx()
{
  controlloDistanzaOstacoloAnteriore();
}
void controlloDistanzaOstacoloAnteriore()
{
  digitalWrite(PIN_TRIG_ANT, HIGH);
  delayMicroseconds(10);
  digitalWrite(PIN_TRIG_ANT, LOW);
  tempoUltrasuoniAnteriore = pulseIn(PIN_ECHO_ANT, HIGH);
  distanzaAnteriore = 0.03438 * tempoUltrasuoniAnteriore/2;
}

In poche parole lo stato 1 me lo legge senza attendere i 5 secondi
Dove sto sbagliando?
Grazie in anticipo a tutti.

Sembra tutto a posto

Ma ho due dubbi

Anni fa ho letto di problemi con nomi di variabili lunghi pur se sotto il limite nominale del compilatore
Penso in particolare a quella tempoultrasuoniantariore
Che potrebbe mandare in confusione il compilatore

Ma io per primo ci credo poco

Provare non costa nulla comunque

Un secondo dubbio viene dalle due variabili con lo stesso nome nella stessa funzione
Le due milliscorrenti
Che dichiari separatamente nei due case

Non mi piace per nulla
Io le eliminerei direttamente e userei direttamente milli()
Oppure una sola variabile globale o dichiarata locale una sola volta all'inizio della funzione

1 Like

Quando vai a valutare il case 1 per la prima volta millisPrecedentiSX è uguale a 0 perché non lo hai ancora assegnato da nessun'altra parte.
Il valore di millis() però è già maggiore di 5000 (e di conseguenza millisCorrenti) perché hai già valutato il case 0 e fatto tutto il resto.

1 Like

Grazie della info a cui non avevo pensato. Quindi per risolvere il problema dovrei impostare la variabile intervallo a intervallo + millis()? O ancora meglio prevederne una diversa per ogni stato?

Assolutamente no, sei fuori strada.

millisCorrenti - millisPrecedentiSX = ?

Questa è una semplice sottrazione, vogliamo che il risultato sia uguale o maggiore di 5000, giusto?

Sembra assurdo ricordare come si fa la sottrazione, ma ho constatato che basta avere delle variabili al posto dei numeri per mandare in confusione il principiante. Quindi sostituisco alle variabili i numeri:

millisCorrenti  -  millisPrecedenteSX
1000            -          0          = 1000
5000            -          0          = 5000
15000          -         10000        = 5000 

Nello specifico il case 0 viene eseguito in ciclo per 5000ms, fino al momento in cui la sottrazione vale 5000 (o maggiore) per cui il corpo della if viene eseguito e al suo interno prenoti lo stato 1 per essere eseguito nel successivo ciclo di loop. All'interno del corpo della if allora carichi il timer con il valore attuale di millis(), es:

    if(millisCorrenti - millisPrecedentiDX >= intervallo)
      {
        millisPrecedentiDX = millisCorrenti;
        distanzaDestra = distanzaAnteriore;
        Serial.println("stato 0");
        Serial.println(distanzaDestra);
        millisPrecedentiSX = millisCorrenti;  // salva il valore corrente di millis()
        stato = 1; 
      }

stessa cosa dovrai fare nel case 1, ma con la variabile millisPrecedentiDX = millisCorrenti.

Che ne pensi?
C'è qualcosa che non comprendi?

Ciao.

1 Like

Nell'ambito della progettazione c'è un acronimo che, per quanto un po' rude, è estremamente significativo:
K I S S, ovvero Keep It Simple, Stupid.

Tralasciando l'ultima parola che è decisamente fuori luogo, per quello che ti serve basta una sola variabile condivisa tra gli stati usata per tenere traccia di quando avviene il passaggio di stato. Inoltre, come ti hanno già suggerito, la variabile millisCorrenti è decisamente ridondante.

Qualcosa del genere ad esempio.

1 Like

Sei stato perfettamente chiaro! Ho capito l'errore che facevo grazie alla tua chiara spiegazione.

Prego, purtroppo mi sono accorto solo ora che c'è una assegnazione da eliminare marcata (1). Questa assegnazione si usa quando si vuole ripetere il codice ad intervallo specificato per tutta la durata dell'applicazione. Ma all'interno del case 0 quando scade il tempo passi ad eseguire il case 1, la conseguenza è che il case 0 non viene eseguito. Lo switch case seleziona un solo case per ciclo e i vari case presenti sono attivi in modo mutuamente esclusivo. Detto semplicemente, il case (o stato) attivo disattiva tutti gli altri.

Ad intuito credo che la necessità sia di eseguire 1 case alla volta ogni 5 secondi, allora il suggerimento di @cotestatnt è da esaminare ed approfondire poiché è una soluzione ottimale senza stravolgere il programma attuale.

Hai già il codice per muovere a destra e sinistra il sensore ad ultrasuoni?

Ciao.

1 Like

Si. Però avevo impostato i delay bloccanti perché iil sensore aveva bisogno di stabilizzarsi un attimo. Finito il blocco leggeva bene la misura e si spostava a sinistra. Con i delay funziona bene ma volevo assolutamente evitarli. Stasera metto in pratica le info ricevute e le provo ad inserire nel programma completo.

... allora, evitiamo di demonizzare la funzione delay() !

Ci sono casi in cui si può tranquillamente usare (dove bloccare completamente il programma non ha effetti collaterali) e dove invece occorre completamente evitarli perché, durante l'intervallo di tempo, si hanno altre cose da fare.

Valuta sempre in che condizioni ti trovi e decidi di conseguenza ... :wink:

Guglielmo

1 Like

non si tratta di demonizzare delay(), ma di incoraggiare i principianti ad utilizzare fin da subito le buone prassi di programmazione.

L’uso di delay() su Arduino è un compromesso accettabile solo in sketch estremamente semplici, dove la temporizzazione bloccante non causa problemi o magari in sketch che hanno solo uno scopo dimostrativo.
Tuttavia, negli sketch più complessi, questa funzione introduce ritardi che impediscono la gestione efficiente di eventi concorrenti, rendendo il codice meno reattivo e meno scalabile.

Purtroppo, molti tutorial ed esempi ufficiali di Arduino e librerie abusano di delay(), trasmettendo ai principianti un paradigma di programmazione bloccante che, in progetti più avanzati, diventa immediatamente un ostacolo. Approcci basati su macchine a stati o su millis() sono decisamente più adatti per sviluppare codice robusto e reattivo.

Se dovessi insegnare le basi di Arduino a qualcuno, inizierei con l'uso di millis(); solo dopo aver imparato bene a usarlo fino a farlo diventare spontaneo, aggiungerei che esiste anche delay(), che può essere usato nei casi che lo consentono.

Prima imparare bene il sistema più difficile e di uso generale; poi, dopo averci faticato tanto, scoprire che esiste un sistema più semplice che in alcuni casi è vantaggioso. Mi sembra di ricordare che un insegnante fece così a scuola... Non so se era il guadagno del transistor a doppio carico, che dopo tanti calcoli veniva molto semplicemente approssimato a Rc/Re.

Bah, ci sono un'infinità di casi in cui devi attendere e non fare altro, e, in tutti questi casi, il delay() evita inutili costrutti ...

Quello di sicuro e sarebbe la corretta prassi ... analizzare il problema e vedere quale delle due scelte usare :slight_smile:

Guglielmo

Dipende da come sei abituato a sviluppare, a me capita raramente di avere l'esigenza di aspettare "localmente" e non fare altro.

Nascondere un costrutto dietro un'istruzione dal nome semplice e accattivante non la rende per forza di cose preferibile.

Questa è l'implementazione del delay() nel core AVR di Arduino, nulla di trascendentale (anche se mi sembra fin troppo complessa per quello che deve fare): perché un principiante non dovrebbe imparare a usare fin da subito lo stesso approccio con millis() in tutte le sue sfaccettature e con tutta la flessibilità che questo comporta?

void delay(unsigned long ms)
{
	uint32_t start = micros();
	while (ms > 0) {
		yield();
		while ( ms > 0 && (micros() - start) >= 1000) {
			ms--;
			start += 1000;
		}
	}
}

Vedo che però non leggi completamente le risposte ... :roll_eyes:

Guglielmo

NO, dipende dalle applicazioni, sviluppare devi essere in grado di sviluppare utilizzando entrambe le possibilità :wink:

Stiamo andando comunque OT ... il problema dell'OP è altro ... :roll_eyes:

Guglielmo

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.