Ritardo con millis()

ero arrivato a una conclusione simile, ma dove mi sono bloccato è il punto, riferito al gestore pir, di aspettare 30 sec. senza delay bloccante.

Io uso spesso incapsulare questo genere di cose all'interno di classi.
Trovo che il codice risultante sia molto più pulito ed organizzato...

L'esempio su citato da @Maurotec potrebbe diventare cosi:

Il terzo tipo comune di temporizzatore è quello con ritardo all'inserzione:

// timer tipo TON

bool t1(bool in, uint32_t pt) {
    static bool s = 0;  //stato interno: 0 attesa, 1 in funzione
    static bool q = 0;
    static uint32_t t;

    if (!s  &&  in) {  //avvio
        s = 1;
        t = millis();
    }
    else if (s  &&  !in) {  //stop
        q = s = 0;
    }
    else if (s  &&  !q  &&  millis()-t >= pt) {  //timeout
        q = 1;
    }
    return q;
}

45352342

Un'altra possibilità era usare il temporizzatore Tp, e al suo scadere (fronte di discesa dell'uscita) impostare a 1 una variabile.

456757

I miei interventi non servono a risolvere il caso specifico, ma a mostrare degli esempi (che non sono l'unica soluzione possibile, anzi), che si possono eventualmente combinare/comporre per realizzare quello che pare.

Ho rappresentato i timer con delle funzioni da chiamare, e per ogni timer aggiuntivo bisognerebbe scrivere una nuova funzione. Meglio sarebbe fare come indicato da cotestatnt, creare delle classi da cui "generare" tutti gli oggetti che servono, timer o altro che siano.

In automazione industriale (PLC), di base oltre ai tre timer presentati, solitamente si usano i riconoscitori di fronte e i contatori up/down. Sono tutte cose che si possono "simulare" in C/C++ tramite funzioni e oggetti, e che per compiti semplici permettono di ragionare per blocchetti funzionali da collegare tra loro, più che per algoritmi o diagrammi di flusso.

Avendo appena inizializzato s a zero, come potrebbe esserne diverso? :slight_smile:

Come te la cavi con i "meccanismo" di auto esclusione?
No perché è richiesta una certa confidenza con questi "meccanismi".

Qui il codice di claudio in cui s attua il meccanismo di auto esclusione.

    if (!s  &&  in) {  //avvio
        s = 1;
        t = millis();
    }

Scritto diversamente:

   if (s == 0  ) {  //avvio
        if (in == 1) {
            s = 1;   // abilita auto esclusione di questo porzione di codice
            t = millis();  // salva il tempo una volta soltanto
        }
    }

Ricorda che questo codice si trova in un ciclo infinito e pertanto sono necessari
dei meccanismi di auto esclusione. Se aggiungo la clausola else il codice tra le sue graffe verrà eseguito solo se s is(1).
Nota che uso la clausola is(valore) per non usare il simolo = o == che potrebbe essere frainteso.

Ciao.

Mah... Sbaglierò, ma vedo che ogni volta che viene chiamata la funzione viene eseguito
static bool s = 0;
Dopo di ciò, pur essendo static, s non vale sempre zero?...

Ok, si sbagli. La parolina static è determinante.
Considera static di funzione una variabile membro privata di funzione (o legata alla funzione).
Essa viene creata ancora prima di chiamare la funzione, o meglio torna utile pensarla in questo modo.
Alla prima chiamata come alla seconda la funzione si ritrova s come variabile privata già inizializzata.

Può essere confusa con una variabile locale ed in effetti è locale ma non vive nello stack. La cosa interessante è che vive in uno spazio dei nomi riservato alla funzione tanto che in diverse funzioni può comparire lo stesso nome di variabile senza creare errori di compilazione.

Ovviamente conserva il proprio valore e può accedervi solo la funzione che contiene la dichiarazione/inizializzazione.

Ciao.

Cioè, dichiarandola uguale a zero all'interno della funzione, in realtà viene posta a zero all'alba dei tempi e poi "=0" viene ignorato ogni volta che la funzione viene chiamata?
E' assolutamente controintuitivo, ma essendo 'C' non mi stupisce neanche un po'. :grin:

E sì, lo dice qui:
http://www.science.unitn.it/~fiorella/guidac/guidac024.html

Come l'inizializzazione di una globale, solo che non inquini il namespace globale con tante variabili che servono solo dentro una funzione. Così se servono dieci funzioni timer, ciascuna ha le sue locali permanenti con lo stesso nome. Se poi, ancora meglio, invece di tante funzioni si scrive una sola classe da cui si "generano" N oggetti uguali (ma indipendenti), quelle variabili sono le variabili di istanza:

class TON {  // timer tipo TON
    bool s = 0;  //stato interno: 0 attesa, 1 in funzione
    uint32_t t;
    
    public:
    
    bool q = 0;

    bool run(bool in, uint32_t pt) {
        if (!s  &&  in) {  //avvio
            s = 1;
            t = millis();
        }
        else if (s  &&  !in) {  //stop
            q = s = 0;
        }
        else if (s  &&  !q  &&  millis()-t >= pt) {  //timeout
            q = 1;
        }
        return q;
    }
}

Così posso istanziare tutti i timer che mi servono

TON  t1;
TON  t2;
TON  t3;
TON  t4;

Usarli chiamando la funzione interna (metodo) run:

t1.run(ingresso, tempo);

E anche leggerne l'uscita in un secondo momento:

t1.q
1 Like

Beh... Le globali stanno fuori da qualunque funzione, per cui è normale che una dichiarazione bool pippo=0; non venga eseguita ogni volta che la funzione viene chiamata o il loop venga eseguito. Se non è static, d'altra parte, è normale che venga azzerata ogni volta che si chiama la funzione o venga eseguito il loop.

GRAZIE. Ma se hai un pò di tempo non potresti fare un post generico in cui spieghi i vari tipi di timer, facendo gli esempi come mi hai postato che vanno benissimo, perché appunto sono generici e semplici da capire, per principianti come me. Ti chiedo questo, perché così vengono raccolti in solo punto e penso che siano utili anche ad altri.
nello sketch ho modificato:

else if (s  &&  !q  &&  millis()-t >= pt) {  //timeout

con

else if (!s  &&  !q  &&  millis()-t >= pt) {  //timeout

se ho interpretato bene il codice; comunque ho raggiunto lo scopo.

Per crearmi delle classi, dovrà passare un pò di tempo, perché devo comprendere come comporle come struttura. Leggendo quello che fatto Te, mi sembrano abbastanza semplici, forse avendo un tracciato ci dovrei riuscire. quando trovo il tempo ci proverò, anche se onestamente mi trovo in difficoltà a suddividere in vari files, in modo efficiente un progetto di arduino.

Pensa, per me è esattamente il contrario.
Quando vedo quei file .ino chilometrici da millemila righe mi prende lo sconforto :joy:

Ad ogni modo, quella di suddividere le classi in file distinti (.h e .cpp) è solo una buona prassi, non un obbligo.

Infatti, come puoi vedere nell'esempio ho scritto tutto nello stesso file.

Potresti anche fare l'overload dell'operatore di confronto, oppure aggiungere un metodo getter invece di esporre direttamente un membro della classe.

L'obiezione sul getter me la aspettavo. L'overload dell'operatore in C++ invece non lo conoscevo :+1:

In entrambi i casi però mi sembra ci sia un appesantimento della sintassi lato uso degli oggetti (immaginiamo di avere un oggetto con più uscite q1 q2 q3 ecc).

Ma non era mica un'obiezione la mia... solo un'osservazione :sweat_smile:

Per quanto riguarda l'overload degli operatori, in C++ si può fare quasi di ogni cosa compreso quello di assegnazione che in questo caso potrebbe tornare utile per "abilitare manualmente" l'uscita.

void operator= (bool value) {
  q = value;
}
2 Likes

concordo pienamente con Te. in effetti sto cercando di capire come suddividere le variabili nei vari files.
Non avresti qualche link che mi fornisce dei chiarimenti ed esempi ?

Mi sono sbagliato, la mia soluzione ha l'effetto che se in è sempre 0 q va a1 dopo pt.
Sto cercando il modo per cui in non abbia importanza la durata, ma solo il fronte di salita per iniziare il ritardo.
La Tua soluzione NON porta mai q a 1 indipendentemente quanto in sia a 1

Credo che sia come dici, quel ! fa la differenza.

!1 is(false)
!0 is(true)
!true is(false)
!false is(true)

In breve la negazione di false è true e la negazione di true è false.

else if (!s      &&  !q         &&      millis()-t >= pt) {  
else if (!false  &&  !false     &&             true     ) { 
else if (1       &&  1          &&             1        ) { 

Nella seconda riga ho sostituito i valori booleani in modo che il codice nella graffe venga eseguito.
Nella 3° riga invece direttamente i valori numerici 0 e 1.

Quando vedo che vado in confusione uso un modo per rendere espliciti i valori di variabile e di espressioni. Per tenere conto di ciò che accade di ciclo in ciclo incremento una variabile ciclo.

LOOP() {
    ciclo++;
    if (!s  &&  !q  &&  millis()-t >= pt) {  
          // azione
    }
}

Allora scrivo:

ciclo is(0) {
     millis() is(1000), pt is(100), s is(0), q is(1) ecc

}

Ripeto il tutto incrementando il ciclo ed esplicitando i valori che mutano.

Se può esserti utili, ma dubito che ti possa bastare questa breve spiegazione. Si tratta di un linguaggio che ho inventato io e nel tempo ho affinato che mi aiuta tantissimo a vedere le cose in modo esplicito.

Ciao.

Riconosci il momento in cui l'ingresso va alto:

if (!s && in) {
  s = 1;
  t = millis();
}

E da quel momento trascorso il tempo alzi l'uscita:

if (s && !q && (millis()-t >= 30*SECOND)) {
  q = 1;
}

Non essendoci altre specifiche, 's' e 'q', inizialmente 0, rimarranno poi sempre a 1. Nota come il valore di 's' abilita o disabilita le condizioni. Alla fine entrambe le condizioni saranno sempre false.

3454457868678

O, ancora, si poteva comandare il timer TON del post #23 non direttamente con l'ingresso del PIR ma con una variabile che rimane sempre a 1 (facciamo sempre 's' inizialmente 0):

if (digitalRead(pinPIR1)) {
  s = 1;
}
q = t1(s, 30*SECOND);  //t1 e' un timer TON

Il che equivarrebbe ad uno schema tipo il seguente, con un latch di tipo RS che non viene mai resettato.:

3656787