Comandare 8 uscite digitali a tempo

Nel mio progetto devo comandare 8 uscite digitali a tempo, ho 4 fasi che vanno da 0a 3, in ogni fase posso decidere quale uscita accendere con i tempi immessi in menù

 int fase_pin[8] = {0,0,0,0,0,0,0,0};
 int inizio_pin[8] = {0,0,0,0,0,0,0,0};
 int fine_pin[8] = {0,0,0,0,0,0,0,0};

per esempio: nella fase 1 l'uscita 2 si accende a 10 secondi dall'inizio della fase e si spegne dopo 30 secondi dall'inizio della fase.
nella fase 2 l'uscita 1 si accende a 100 secondi dall'inizio della fase e si spegne dopo 150 secondi dall'inizio della fase.
Mi date un'idea di come realizzare questa cosa, non riesco a trovare un principio per buttare giù il codice.

Mi sento molto Marzullo. Fatti delle domande e trova le risposte.
Otto pin digitali si trovano inizialmente tutti a zero.
Cosa accade dopo?
Cosa è che fa iniziare una fase?
Si inizia sempre dalla fase 0?
Cosa è che fa terminare una fase?

Sono solo alcune delle domande che possono essere di aiuto o meno.
Visto che parli di fasi o stati viene in mente uno switch case simile:

switch (stato) {
    case 0:
       // qui il codice che gestisce lo stato zero.
       // if (evento) {
           stato = 1;
      } 
       break;
  case 1:
       // qui il codice che gestisce lo stato zero.
       // if (evento) {
           stato = 2;
      } 
       break;
}

Mancano gli stati (o fasi) 2 e 3 che sono uguali.

Questi li hai letti?

Semplifica sempre, invece di 8 pin,inizia con 3 pin.

L'algoritmo può essere modellato tramite oggetti (cioè usando delle struct o classi)?

Ciao.

Inizialmente si trovano tutti a zero, le 4 fasi sono sequenziali, ogni fase dura un tempo stabilito dall'utente sempre tramite menù. Le 8 uscite non devono per forza essere usate, decide l'utente, quali usare, decidendo tramite menù i vari tempi.
Quindi, in ogni fase dovrei controllare quali uscite si devono accendere per quella fase, una volta deciso quali accendere devo controllare la durata. Però la durata può essere anche maggiore del tempo della perché posso dire: accendi l'uscita 1 dopo 5 secondi dall'inizio della prima fase e soegnila dopo 2 minuti, però la prima fase può durare 20 secondi, la seconda 30 secondi e via dicendo.
Avevo pensato di inserire in ogni fase il controllo delle 8 uscite per le accensioni ma la vedo pesante come struttura perché dovrei mettere anche il controllo per lo spegnimento quindi viene ancora più pesante.
La cosa più logica sarebbe creare una routine in cui si fa il controllo sia per per l'accensione che per lo spegnimento e richiamarla in ogni fase. Ho già una variabile di nome fase che mi indica in quale fase mi trovo.
La struttura con i case la conosco, l'ho usata per realizzare i vari menù e sotto menù.
Nei tuoi esempi è stata usata per applicare le 4 fasi del parcheggio. Un esempio di sicuro interessante, io ho usato degli if ma è la stessa cosa, case fa semplicemente un controllo di una variabile: se il confronto è vero esegui il codice. La differenza sostanziale è che che if può controllare più variabili contemporaneamente mentre case solo una.

Qui non comprendo. Si resta nella fase 0 per ton0+ton1+ton ecc fino ad 8 sommando anche i tempi toff per le 8 uscite. Se toff o ton vale zero quella uscita non viene usata, ci siamo.

La fase è scomposta in massimo 8 subfasi. Ogni subfase attende con millis esegue attende con millis ed esegue. "esegue" qui sarà semplicemente un toggle del pin e cioè se è acceso lo spegne e viceversa.

Sembra che le subfasi si possano modellare come oggetti. Ogni oggetto ha un flag che indica se ha terminato o meno. Quando termina quella uscita non muta nel tempo.

Scusa ma mi devo allontanare.

Ciao.

Quando qualcosa diventa pesante da gestire... è il momento di scomporre, vuol dire che in quel punto di codice si vogliono gestire troppi dettagli di processi che potrebbero essere indipendenti, benché coordinati tra loro.

Invece di gestire ogni uscita nelle fasi... ogni uscita può essere un processo indipendente, con una propria fase interna, che legge la fase principale attiva, e si comporta di conseguenza.

E con questo è terminata la scomposizione dei compiti.


Il passo successivo se si vuole è strutturare il tutto in modo diverso.

Una prima cosa che si può dire è che quegli array di dati potrebbero essere trasformati in array di struct, dove ogni elemento (una struct) contiene tutte le variabili necessarie per processare il comportamento di un'uscita. Certo posso passare un indice a una funzione, e con quell'indice la funzione trova tutti i dati nei diversi array. Oppure posso passare una struct con tutti i dati, e la funzione lavora in modo uguale sull'insieme di dati di volta in volta ricevuti.

Se vogliamo è quello che viene chiamato "zucchero sintattico" (come switch vs if else), non cambia la logica, ma può rendere il tutto più semplice da gestire mentalmente, e soprattutto è propedeutico al successivo livello di strutturazione dei programmi, cioè gli oggetti.

Anche gli oggetti usati in modo base sono zucchero sintattico, in fondo non si fanno magie e la CPU sempre le solite quattro cose sa fare (memorizzare, calcolare, comunicare, saltare), ma cambiano il modo di pensare e progettare un programma.

Ora il caso in questione è un tipico esempio in cui una sola classe può descrivere perfettamente dati e comportamento di ciascuna di quelle uscite. Per ciascuna uscita dalla classe si "stampa" l'oggetto corrispondente, et voilà, un array di oggetti, ciascuno con le sue variabili "incapsulate" e indipendenti da quelle degli altri, e con la logica elaborativa annessa richiamabile con la notazione punto oggetto.metodo()

Aggiungere un'altra uscita diventa banale, si "stampa" dalla classe un oggetto in più passandogli i dati di configurazione.

Come esempio a cui ispirarsi, forse non stilisticamente corretto (in C++ con le classi e i namespace sono una capra):

class Punto
{
    public:
    float x;
    float y;

    // costruttore inizializza i dati
    Punto(float xx, float yy)
    {
        x = xx;
        y = yy;
    }

    // metodo per chiedere a un punto quanto
    // e` distante da un altro punto
    float distanza(Punto p)
    {
        float dx = x - p.x;
        float dy = y - p.y;
        return sqrt(dx*dx + dy*dy);
    }
};


// crea (istanzia) due oggetti tipo Punto
// con specifiche coordinate
Punto  a(30.5, 127.8);
Punto  b(-387.6, 860.1);


void setup() 
{
    Serial.begin(9600);

    // sia a che b dichiarano di trovarsi
    // alla stessa distanza dall'altro punto
    Serial.println(a.distanza(b));
    Serial.println(b.distanza(a));
}


void loop() {}

ciao a tutti
scusate se mi sono intromesso, ma leggendo sinceramente non ho capito una cosa.
Che progetto stai creando??

Leggendo meglio dal primo commento credo che manca proprio un obbiettivo finale.....prova a replicare un semaforo .

Li puoi sbizzarrirti con le idee ed è un ottimo progetto iniziale per farsi le ossa.

Che intendi: manca un obiettivo finale? L'obiettivo della domanda è semplice, accendere dopo un certo tempo dall'inizio di una fase un Pin e spegnerlo dopo un certo tempo dall'inizio della fase.

scusa allora....
usa un delay().

oppure usa millis()....
ma qui si apre un altro capitolo...non facilissimo.

@Puso: lascia stare è un vecchio progetto ed entrare così senza sapere di cosa stai parlando ... crei solo confusione.

Guglielmo

P.S.: ... e considera che "ciccioarduino" s'è beccato anche un ban di una settimana per il tipo di progetto quindi ... maggiori dettagli NON ne avrai ... rischierebbe il BAN permanente.

ok.

Del ban permanente non mi frega un tubo, ti assicuro che vivrò bene lo stesso anche senza questo forum, come ho fatto fin'ora. Il problema sei tu come persona. Banni le persone senza dare la possibilità di difendersi, in altri tempi questo comportamento avrebbe avuto un nome non proprio democratico, anche in questo commento fai vedere che persona sei, quello che hai scritto non c'entra niente con la programmazione, hai solo voluto infierire contro di me, lo si capisce dal P.S. che potevi benissimo evitare, ma lasciamo perdere. Come già detto l'altra volta sto qui per imparare (anche dopo i 60 c'è sempre da imparare) e come l'altra volta ringrazio chi propone le sue idee.

Ti ringrazio, dato che classe e oggetto non li conoscevo, sto guardando in rete per studiare questi due nuovi argomenti.

Vedo che NON hai capito il senso del PS ...
... era per informare Puso di NON insistere su dettagli che non possono essere dati e quindi ... inutle che lui continuasse a parlare ed a chiedere di problematiche che sono ormai sorpassate ... ma il tuo modo di fare ti ha "accecato" e non hai capito nulla, peccato.

Guglielmo

Un tentativo nella direzione degli oggetti si può fare. Ipotizziamo per semplicità che ci sia il solo stato 0 (case 0) e solo due pin.

Ipotizzando che la classe si chiama Dout, scriverai:

Dout out0; // out0 è una variabile oggetto di tipo classe Dout.

La dichiarazione sopra equivale a creare istanza e come conseguenza viene chiamato il costruttore.

// La classe Dout ridotta all'osso
class Dout {
  public:
    Dout(byte pin) {  // costruttore viene chiamato creando istanza
        m_pin = pin;
        pinMode(m_pin, OUTPUT);
    }
    void setTime(uint16_t tOff, uint16_t tOn) {
        m_timeOff = tOff;
        m_timeOn = tOn;
    }
      
  private:
    byte m_pin;
    uint32_t m_saveTime;
    uint16_t m_timeOff;
    uint16_t m_timeOn;
};

Il costruttore chiama la pinMode per configurare il pin come output.

Accederai ai metodi pubblici con l'operatore punto . così:

void setup() {
     out0.setTime(1000, 2000);
}

Non puoi invece accedere alle variabili che sono sotto la scritta private:
Queste variabili sono appartenenti all'oggetto, quindi:

Dout out1;

out1 ha il suo set di variabili private separate da quelle di out0.
Attenzione invece i metodi (es la funzione setTime) rimane unica.

Per vedere come funge puoi modificare setTime così:

void setTime(uint16_t tOff, uint16_t tOn) {
        m_timeOff = tOff;
        m_timeOn = tOn;
        Serial.println(m_timeOff);

    }

Ritornando al problema. Non è chiaro cosa accade dopo che una uscita passa da 0 a 1.
Se non accade nulla vuole dire che rimane accesa l'uscita.

Attendi con millis() m_timeOff tempo {
     accendi il pin.
     salva il tempo di millis()
}

Ora acceso il pin, questa porzione di codice si deve auto disabilitare.
Se il pin deve rimanere acceso tot tempo viene abilitata un altra porzione di codice sempre con millis, ma ci sono alternative. In ogni caso terminato di agire sul pin il codice si deve auto disabilitare. Per auto disabilitare il codice basta una variabile di stato impostata ad un valore non valutato da nessuna if o case. Ad esempio se ci sono 3 stati interni da 0÷3 impostando la variabile di stato a 4 si disabilitano tutti gli stati e niente viene eseguito.

Ciao.

Come ti ho scritto sopra, questa tipologia di codice mi è sconosciuta e devo prima capirne il significato altrimenti i tuoi sforzi resteranno incomprensibili per me.
Ho visto qualcosa in rete ma c'è qualcosa che non mi fa entrare in testa questo sistema di programmazione, forse perchè non ho capito a cosa dovrebbero servire gli oggetti nella pratica.
ti dico solo che a volte mi capita di rimanere bloccato anche er mezz'ora su qualcosa che non va e poi scopro che nell'IF ho messo = e non ==. Ci sono cose che per entrare in testa hanno bisogno dei loro tempi.

Gli oggetti li usi già, ad esempio l'oggetto Serial e i metodi available(), println() ecc.
L'oggetto Serial però è pre-istanziato nel core. Definire una classe equivale a definire un nuovo tipo di dato. Il nuovo tipo di dato ha un nome (es: Dout), delle variabili private (ma anche metodi) e dei metodi pubblici e mai delle variabili pubbliche (anche se è possibile). I metodi agiscono sulle variabili private.

Il vantaggio diretto degli oggetti si vede subito con le quattro seriali hardware disponibili con Arduino Mega. Poiché l'hardware della seriale0 e delle altre 3 sono identici, ogni seriale si comporta nello stesso identico modo. Ciò che li differenzia è l'indirizzo della seriale hardware. Quindi invece di scrivere codice per 4 seriali è stato creato (definito e implementato) un nuovo tipo di dato che prende come argomento l'indirizzo hardware. Sto semplificando ovviamente, ma il succo è:

HardwareSerial Serial(indirizzo hardware 0):
HardwareSerial Serial1(indirizzo hardware 1):
HardwareSerial Serial2(indirizzo hardware 2):
HardwareSerial Seria3(indirizzo hardware 3):

Ora tu hai 8 pin digitali, ognuno sembra debba comportarsi in modo simile all'altro. Per cui creo un nuovo tipo di dato e ciò che cambia è il pin.

Il codice precedente contiene un errore di svista e infatti al costruttore non passo il pin. Ho corretto il codice della classe ma adesso per creare l'oggetto devo scrivere:

Dout out0(12);

12 è il pin su cui opera l'oggetto out0.

Tornando al problema.
TEMPO0 = { 1000, 2000 }

  1. Si accende dopo 1000ms e si spegne dopo 1000ms.
  2. Oppure si accende dopo 1000ms, resta acceso per 2000ms e si spegne.

Ottengo il primo (1) comportamento se memorizzo millis() una sola volta.
Ottengo il secondo (2) comportamento se memorizzo millis() due volte.

:grinning:
Tranquillo, io dico al mio mignolo di stare fermo ma lui non ne vuole sapere, parte e si estende quando passo da lam a do maggiore. Non ci crederai ma con calma e lentamente dopo giorni e giorni riesco a muovere l'anulare senza che parta anche il mignolo.

Non ti meravigliare ma anche io non riuscivo a capire la programmazione orientata agli oggetti, mi sono dovuto impegnare tanto. Ho cominciato a capirne qualcosa quando ho sperimentato e prima di sperimentare ho dovuto imparare cosa è il costruttore (abbr. ctor) e il distruttore (abbr. dtor).

Io l'ho tirata in ballo, perché non vedo altro modo per 8 pin temporizzati. Avrei potuto fare con struct al posto di class ma non sarebbe cambiato nulla, sempre complicato è da capire, per cui complicato per complicato meglio class.

Ciao.

Io non ho capito, o meglio lo davo per scontato ma forse non è così, se i vari parametri li vuole modificare a runtime, per cui servirebbe un array di oggetti.

Ma certamente potrebbe pure essere necessario un array di oggetti Dout, ma per adesso è già in difficoltà con un solo oggetto, figuriamoci l'array di oggetti.

Ma tu hai capito se oltre ad accendersi devono anche spegnersi a tempo?
No perché quando si passa alla fase 2 e via via come me li ritrovo questi pin?
A zero oppure mantengono il loro valore e quindi nella configurazione di fase2 non posso usare gli stessi pin della fase 1. Oppure ancora nella fase2 l'utente deve tenere conto dei pin accesi e spegnerli dopo tot tempo.

Insomma mancano i dettagli.

Ciao.