if(millis()-t1>2000): una conferma, per favore

Ciao a tutti

Pur essendo quasi sicuro, mi è venuto un dubbio e vorrei una conferma, con eventuali approfondimenti:
se io scrivo
if(millis()-t1>2000){}
ciò che sta fra le {} verrà eseguito anche se la condizione non è più vera?
Nel caso specifico, sono impostazioni in cui tutto si ferma in un while() mentre l'encoder viene ruotato, premuto, ruotato di nuovo... Finite le impostazioni, esce dal while() e riprende il normale funzionamento.

Grazie! :slight_smile:

ciò che sta fra le {} verrà eseguito anche se la condizione non è più vera?

No! Le strutture di controllo eseguono il blocco solo se la condizione è vera, se è falsa non viene eseguito.

Se secondo te cicla quando non dovrebbe, stai semplicemente interpretando male la condizione.

Esempio if(millis()-t>2000) è sempre vera per tutti i valori superiore a 2000, per cui se diventa vera resterà "sempre vera", perché dopo che diventa vera millis()-t darà sempre valori superiori a 2000.

Questo nel linguaggio C e C like, esempio in visual basic abbiamo il ciclo DO UNTIL che cicla se la condizione è falsa...

Ciò che voglio dire, e che mi sembra sempre più ovvio riflettendoci, è: se un if è vero e inizia ciò che deve essere fatto di conseguenza, ciò non verrà interrotto se, nel frattempo, la condizione non sarà più vera!

Ovviamente, sarebbe ovvia la cosa in linguaggio macchina, in un ipotetico linguaggio macchina avremmo che se la condizione è vera salta a una sequenza di istruzioni, dove non c'è nessun modo per interromperla.

Cosi un if() esegue in sequenza il blocco, ma se nel blocco non c'è nessun controllo, non può fare altro che eseguire la sequenza dalla prima all'ultima istruzione.

Mi rispondo da solo, pur apprezzando eventuali precisazioni: se la condizione è vera, l'esecuzione salta dove previsto, senza controllare ulteriormente se la condizione è vera.
(Hai risposto mentre scrivevo! Grazie :slight_smile: )

Datman:
se la condizione è vera, l'esecuzione salta dove previsto, senza controllare ulteriormente se la condizione è vera

La domanda era un po' ambigua anche se più o meno si poteva intuire. E si, in una logica a stati/tempi discreti non parallela le cose avvengono solo in un certo momento, per cui una condizione viene valutata solo nell'esatto momento in cui viene valutata :slight_smile: ed è solo in quell'istante che diventa determinante per il corso degli eventi immediatamente successivo.

Se interessa fare qualcosa al variare della condizione questa va valutata continuamente (che è il modo normale di procedere nei programmi per PLC o nei programmi a stati non bloccanti, dove tutte le condizioni vengono testate decine, centinaia o migliaia di volte al secondo).

Ad esempio questa è la parte relativa alla regolazione ora/minuti con un encoder che sto sperimentando in questi giorni. Il case è eseguito migliaia di volte al secondo. Una pressione lunga fa ritornare alla maschera principale (fase=0). Ruota destra/sinistra (eventi onUp/onDn) modificano il valore. Con clic si avanza da regolazione ore a regolazione minuti a conferma. Se non si tocca nulla per 60 secondi si torna alla maschera principale.

case REGORA:
    if (primo)                     //  prima esecuzione del case
    {
            s = 0;                 // 0 = regola ora, 1 minuti
            modHour = now.hour;
            modMinute = now.minute;
            displayModHour(s, modHour, modMinute);
            modify = false;
            t = gmillis; 
    }

    if (onLpress) { fase = PRINCIPALE;  beepLpress();  break; }  //  abbandono

    if (onUp)
    {
            if      (0 == s) { modHour   = (modHour + 1)   % 24; }
            else if (1 == s) { modMinute = (modMinute + 1) % 60; }

            beepUp();
            modify = true;
            displayModHour(s, modHour, modMinute);
            t = gmillis;
    }
    else if (onDn)
    {
            if      (0 == s) { modHour   = (modHour > 0)   ? modHour-1   : 23; }
            else if (1 == s) { modMinute = (modMinute > 0) ? modMinute-1 : 59; }

            beepDn();
            modify = true;
            displayModHour(s, modHour, modMinute);
            t = gmillis;
    }

    if (onClic) { beepClic();  t = gmillis; }

    if (onClic  &&  0 == s)             // avanza a regolazione minuti
    {
            s = 1;
            displayModHour(s, modHour, modMinute); 
    }
    else if (onClic  &&  1 == s)        // conferma, salva ed esce
    {
            fase = 0; 
            if (modify)
            {
                    now.hour = modHour;
                    now.minute = modMinute;
                    setHour(); 
            }
    }

    if ((uint16_t)gmillis-t > 60000) { fase = PRINCIPALE; }  // timeout inattivita`
break;

Claudio_FF:
E si, in una logica a stati/tempi discreti non parallela le cose avvengono solo in un certo momento, per cui una condizione viene valutata solo nell'esatto momento in cui viene valutata :slight_smile: ed è solo in quell'istante che diventa determinante per il corso degli eventi immediatamente successivo.

Pura filosofia :slight_smile: :smiley:

Federico

Grazie a tutti :slight_smile:

Finite le impostazioni, esce dal while() e riprende il normale funzionamento.

Io in questi casi (o meglio sempre) tra {} ci faccio il passaggio dallo stato corrente a quello futuro, tipo { state = nCase; }, così in loop verrà eseguito il case selezionato. Soluzione molto simile a quella mostrata da Claudio_FF, soluzione funzionale a patto di non usare for, while ecc.

case 2:  // REGOLAZIONE ORA
    if (primo)                     //  prima esecuzione del case
    {

ERRORE {
Risulta comodissimo perché permette di eseguire una porzione di codice solo la prima volta che si entra nel case corrente, si ottiene ciò comparando l'eguaglianza tra stato precedente e stato selezionato, se c'è eguaglianza la variabile primo == true.
} (vedi il post successivo di Claudio_FF).
Usando un ambiente di sviluppo standard C dove il concetto di modulo (o compile unit) permette di avere più file .c è possibile raggruppare funzioni (almeno 3) che riguardano un solo STATO, dove la funzione ingresso() valuta quando entrare nello STATO, la funzione stato() permane in questo STATO fino a che si decide di selezionare la funzione uscita(). Tutto ciò è possibile senza usare switch case ma puntatori a funzione, modificando a cosa punta. Ovviamente nel loop ci sarà una chiamata a funzione tramite puntatore. Portato all'estremo potremmo anche pensare di modificare il puntatore a funzione ad ogni ciclo di loop. Questo tipo di implementazione non risolve la confusione che ne deriva quando il numero di stati supera un certo numero, diciamo che 20 stati sono già impegnativi.

L'esempio di Claudio_FF ipotizza una sola macchina a stati (cioè un main state), quando si pensa di dovere impiegare più macchine a stati le variabili, che riguardano la macchina generica vengono raggruppate dentro una struttura (es. struct state).

Notato come sia stato facile riavviare il timeout con t = gmillis; che poi viene valutato nella if come se 0 (zero) > 60000.

Ciao.

Maurotec:
permette di eseguire una porzione di codice solo la prima volta che si entra nel case corrente, si ottiene ciò comparando l'eguaglianza tra stato precedente e stato selezionato, se c'è eguaglianza la variabile primo == true.

Ok, il contrario :wink:

bool primo = (fase != fasePrec);
fasePrec = fase;
switch (fase)
{
    ....

Notato come sia stato facile riavviare il timeout con t = gmillis;

Infatti sempre nell'ottica che un intero giro di loop rappresenta un passo elementare dell'intero sistema (che idealmente dovrebbe avvenire in un tempo infinitamente piccolo) come prima istruzione del loop c'è la lettura di millis(), e quello è il "tempo presente globale" uguale per tutte le funzioni per l'intera esecuzione di quel giro di loop... in altri esempi viene chiamato currentMillis ma è troppo lungo da scrivere :sleeping:

void loop()
{
    gmillis = millis();
    ....
    ....
}

Nell'implementazione di Claudio_FF c'è un altro vantaggio che riduce al minimo la chiamata a funzione millis(), infatti c'è ne una soltanto. Diversamente avremmo dovuto scrivere t = millis() più volte.

In questa ottica anche la visualizzazione (aggiornamento del display) tramite la chiama a funzione displayModHour(s, modHour, modMinute); andrebbe fatta una sola volta, avremmo così una sola chiamata a funzione all'interno del loop.

Per ottenere ciò ci basta avere una variabile flag di nome updateDisplay che quando posta a true fa si che la funzione di aggiornamento del display venga chiamata. Se ciò è possibile dipende dalla attuale implementazione, resta il fatto che risparmieremmo 3 chiamate a funzione.

void updateDisplay() {
    // f_updateDisplay is flag_updateDisplay
    if (f_updateDisplay) {
       displayModHour(s, modHour, modMinute);
       f_updateDisplay = false; 
    }
}

Per aumentare la valenza didattica di questo thread, queste e altre modifiche sarebbe meglio introdurle in seguito, così da potere descrivere le motivazioni e i vantaggi che comportano.

Ciao.

Maurotec:
riduce al minimo la chiamata a funzione millis(), infatti c'è ne una soltanto. Diversamente avremmo dovuto scrivere t = millis() più volte.

Chiamare la funzione una sola volta non è il solo vantaggio, quello più importante è che il valore del tempo attuale non cambia mai per tutta l'esecuzione di quel giro di loop, il che rende il tempo discreto ed è come se tutto il loop avvenisse in un singolo istante (non possono esserci funzioni il cui presente è diverso dalle altre).

Claudio_FF:
Ad esempio questa è la parte relativa alla regolazione ora/minuti

Maurotec:
Io in questi casi (o meglio sempre) tra {} ci faccio il passaggio dallo stato corrente a quello futuro

Ho sempre seguito con molto interesse i vostri thread sulla programmazione a stati e le sue varie implementazioni. Qualche mese fa ce ne fu uno su un impianto di irrigazione, che ancor oggi ogni tanto vado a rileggere (alcuni post in particolare).

Mi piace la piega che ha preso questo thread, e vi ringrazio, perchè leggendovi, mi sono accorto che nei miei progetti (più o meno complessi), solitamente implemento questi principi in modo naturale (loop mai fermo, stato/fase, uso del tempo, etc.); vuol dire che i vostri insegnamenti sono stati utili :slight_smile:

Per esempio, questi sono gli stati, di un orologio con timer (oled+4 pulsanti)

enum STATE {
  VIEW_NONE = 0,
  VIEW_CLOCK = 1,
  VIEW_TIMER = 2,
  VIEW_ALARM = 3,
  SET_CLK_DAY = 11,
  SET_CLK_MONTH = 12,
  SET_CLK_YEAR = 13,
  SET_CLK_HOUR = 14,
  SET_CLK_MINUTE = 15,
  SET_CLK_SECOND = 16,
  SET_TMR_HOUR = 21,
  SET_TMR_MINUTE = 22,
  SET_TMR_SECOND = 23
};

e questa è una parte del loop per la gestione degli stessi

void loop() {
  //Prelevo data, ora e millis una sola volta
  currentMillis = millis();
  time_t nowTime = now();

  //Verifico lo stato dei pulsanti e definisco lo stato corrente
  checkButtonsState();

  //..omissis..

  if (
    buttons[IDX_BTNS(BTN_MODE)][2]
    || (curState < SET_CLK_DAY && (currentMillis - lastRefreshTime) >= CLK_TIME)
    || ((curState >= SET_CLK_DAY || curState == VIEW_ALARM) && (currentMillis - lastRefreshTime) >= BLINK_TIME)
  ) {
    if (curState == VIEW_NONE) {
      digitalWrite(LED_RED, blinkState);
      blinkState = !blinkState;
    } else if (curState == VIEW_CLOCK) {
      displayClock(nowTime);
    } else if (curState == VIEW_TIMER) {
      displayTimer(nowTime, curAlarm);        
    } else if (curState == VIEW_ALARM) {
      displayAlarm();
    } else if (curState >= SET_CLK_DAY && curState <= SET_CLK_SECOND) {
      displayClock(setupTime);
    } else if (curState >= SET_TMR_HOUR && curState <= SET_TMR_SECOND) {
      displayTimer(nowTime, setupAlarm);
    }
    lastRefreshTime = currentMillis;
  }

  //..omissis..
} //loop

Ok, quella prima if è alquanto complessa, forse la si potrebbe semplificare, ma al momento il progetto è fermo perchè voglio aggiungere un chip per il debouncing hardware dei quattro pulsanti, in ogni caso funziona come voglio.

Stesso discorso per la macchinina

enum MODE {
  MOD_NONE = 48,
  MOD_AUTO = 49,
  MOD_BT = 50,
  MOD_IR = 51,
  MOD_CONFIG = 52
};

enum COMMAND {
  CMD_NONE = 48,
  CMD_STOP = 49,
  CMD_FORWARD = 50,
  CMD_FORWARD_RIGHT = 51,
  //..omissis..
};


//Nel loop

 switch (readedMod) {
    case MOD_NONE: { //..omissis.. }
    case MOD_AUTO: { //..omissis.. }
    case MOD_BT: {
        switch (readedCmd) {
          case CMD_FORWARD_LEFT: { //..omissis.. }
          case CMD_ACCELERATE:   { //..omissis.. }
          case CMD_DECELERATE:   { //..omissis.. }
		//..omissis..
  }

Chiaramente non mi sto auto-celebrando :), voglio solo ringraziarvi dimostrandovi che il tempo che impiegate (volontariamente) nella divulgazione, non è tempo perso, almeno io (ma sono convinto tanti altri) ne faccio buon uso.

GRAZIE

Federico