[RISOLTO]Utilizzare 4 registri a scorrimento (con 32 LED) senza i delay

Salve, sto cercando di utilizzare 4 registri a scorrimento per usare 32 led e creare dei giochi di luce.
Dato che questo programma verrà usato con altro codice che arduino svolgerà contemporaneamente, mi serve creare dei giochi di luce senza utilizzare delay e quindi usando la funzione millis().
Ho fatto un bel pò di tentativi ma il problema è che non so dove mettere la pausa.
Ecco il codice:

 unsigned long previousMillis1 = 0;        // will store last time LED1 was updated
 unsigned long previousMillis2;
 const long interval1 = 300;               // interval at which to blink1 (milliseconds)
 unsigned long currentMillis1 = millis();
 unsigned long currentMillis2;
 unsigned long intervalshift = 1000;
#define data_ 2
#define latch_ 3
#define clock_ 4 
void setup() {
  #define led 8
  pinMode(led, OUTPUT);
  pinMode(data_, OUTPUT);
 pinMode(latch_, OUTPUT);
 pinMode(clock_, OUTPUT);
 Serial.begin(9600);
}

void loop() {
   blink1();
   animazione1();
       
   }
  
  void scritturaregistro(unsigned long valore){          //funzione che si occupa del passaggio dei bit al registro
  digitalWrite(latch_, LOW);
  shiftOut(data_ ,clock_ ,MSBFIRST , (valore >> 24) );   //sposto le cifre più significative "in fondo" fino al 32 esimo LED
  shiftOut(data_ ,clock_ ,MSBFIRST , (valore >> 16) );
  shiftOut(data_ ,clock_ ,MSBFIRST , (valore >> 8) );
  shiftOut(data_ ,clock_ ,MSBFIRST , valore);
  digitalWrite(latch_, HIGH);      }

void animazione1(){     //funzione che si occupa dell'unica (fin ora) animazione che ho creato
unsigned long currentMillis2 = millis();
Serial.println(previousMillis2);
 if(( currentMillis2 - previousMillis2) >= intervalshift){
    previousMillis2 = currentMillis2;
 for(unsigned long i = 0 ; i < 32 ; i++){             //animazione da sinistra verso destra
    scritturaregistro((unsigned long)0x1 << i);  
 } }    
   if(( currentMillis2 - previousMillis2) >= intervalshift){             //questo if sarebbe la pausa che non so dove mettere
    previousMillis2 = currentMillis2;
    for(long i = 0 ; i < 32 ; i++){              //animazione da destra verso sinistra
  scritturaregistro(0x80000000 >> i);
  } }
}
  void blink1() {
   unsigned long currentMillis1 = millis();
    if((currentMillis1 - previousMillis1) >= interval1){
   previousMillis1 = currentMillis1;
   digitalWrite(led, !digitalRead(led)); 
  } 
  }

La pausa che ho creato sembra funzionare correttamente, ma non trovando il posto giusto dove inserirla, i led si accendono con delle pause nel momento in cui non dovrebbero (per esempio in questo caso si accendono tutte insieme o molto velocemente (non si distingue) e poi si spengono subito dopo).

Forse mi sto solo complicando troppo la vita e conoscete modi più semplici e veloci per fare quello che voglio fare io?
Fatemelo sapere, grazie in anticipo.

Io posso darti questo suggerimento, riparti dal vecchio programma con il delay, in quello io eliminerei il for dall'animazione e lo sostituirei con una cosa del tipo

i++;
if(i>32)
{
  i=0;
}

poi userei il test di millis() nel loop principale per richiamare la funzione

if(( millis() - previousMillis2) >= intervalshift)
{
  animazione1();
  previousMillis2 = millis();
}

in questo modo lasci che la funzione faccia solo quello per cui è pensata (animare i led) e demandi all'esterno la logica quando e come richiamarla.
Ovvio che la modifica che ti ho suggerito per la rimozione del for fa funzionare l'animazione solo in un verso, dovrai trovare e implementare la logica per far fare all'animazione le varie cose che vuoi tu senza usare cicli all'interno della funzione (a meno che tu non voglia che una volta richiamata una determinata animazione inizi e termini senza far far altro a Arduino)

Rieccoci di nuovo a discutere di un mio problema :slight_smile:
L' ultimo codice che mi hai dato (dove metti nel loop l' IF con dentro la funzione dell'animazione), non riesco a capire come dovrebbe dare delle pause ai led. Se la richiamo al tempo che voglio io (intervalshift) blocco la funzione in modo da creare dei ritardi?
Comunque lo sketch è giusto sintatticamente e lo carico. Ma vedo ancora che il risultato finale è sbagliato: vedo i led che si accendono tutti insieme ad un intervallo di tempo dato dall'intervalshift (e mi fa capire che la condizione if che genera il ritardo funziona), ma credo che sia comunque messo in un posto sbagliato. Mentre per quanto riguarda la sostituzione del ciclo for con quelle stringhe che hai suggerito tu, non lo capisco: è davvero necessario sostituire quel ciclo for? Credo che lui non mi dia problemi. Correggimi se sbaglio, perchè sicuramente nella programmazione non sono molto bravo, e penso che tu sia mooolto più ferrato di me.

Allora occorre che tu ponga l'attenzione su fatto che, contrariamente al PC che sei abituato ad usare, Arduino non è multithread quindi può eseguire solo una cosa alla volta quindi partendo dal fatto che il loop viene eseguito di continuo se vuoi fare molte cose di continuo devi far si che l'esecuzione si del tipo:
inizio loop
fai cosa 1
fai cosa 2
...
fai cosa N
fine loop
Invece se tu richiami una funzione che al suo interno ha un ciclo succede che
inizio loop
fai cosa 1
fai cosa 2
ciclo in cosa 2
ciclo in cosa 2
ciclo in cosa 2
fai cosa N
fine loop
Vedi che dopo cosa 2 non parte nulla finché il suo ciclo all'interno non termina.
Detto questo per come hai usato millis ad ogni ciclo di loop entri nella funzione se non è trascorso il tempo esci subito, se è trascorso il tempo fai tutta l'animazione (che corrisponderebbe a ciclo in cosa 2 )
Idealmente le funzioni dovrebbero invece fare ciò che sono pensate per fare, il resto della logica dovrebbe essere fatta da altri, non è imperativo ovviamente ma trovarsi a leggete un codice che richiama la funzione stampa_a_video_il_risultato() e poi dentro legge gli ingressi, apre una comunicazione ethernet verso un server, aggiorna il display, salva su SD lo stato degli ingressi, ecc. è fuorviante e difficile da manuntenere.
Quindi il suggerimento è quello di chiedersi nel loop se serva richiamara una determinata funzione se si, richiamarla altrimenti passare oltre.
Se la necessità è quella di svolgere molte funzioni assieme ogni funzione deve durare il meno possibile quindi, ogni volta che trascorre il tempo tra uno step e il successivo dell'animazione, e la funzione viene richiamata dovrà fare un solo step e non tutto il ciclo.
Sarebbe sbagliato fare tutto il ciclo? No, ma dipende da quel che vuoi ottenere.
Caso d'uso:
Ho 32 led che devono animarsi da dx a sx ogni 500mS. Voglio poter interrompere l'animazione in qualsiasi momento tramite un pulsante.
Se la funzione fa uno step alla volta ogni mezzo secondo (ad esempio) e quindi ogni mezzo secondo la richiamo dopo aver aggiornato lo stato dei registri esce e ritorna il controllo al loop il quale può interrogare l'ingresso del pulsante e se premuto terminare l'esecuzione dell'animazione.
Se invece dopo mezzo secondo dall'avvio la funzione esegue tutto il ciclo per i 32 led Arduino non potrà interrogare il pin del pulsante e quindi "sentire" che l'utente vuol interrompere l'animazione se non dopo 32*0,5s = 16s
Tra 16s e 0,5s la risposta che il dispositivo da all'utente passa da "E' bloccato, non funziona" a "Ok grande s'è fermato subito"
Detto anche questo il codice che hai scritto tu invece aspetta il tempo prefissato, quando questo è trascorso richiama 32 volte la funzione scritturaregistro senza aspettare tra una chiamata e la successiva portando a due possibili risultati:
Il primo è che la funzione viene eseguita solo una volta e poi spegne i led, si ha l'impressione che nessun led sia mai stato acceso
Il secondo è che la funzione viene richiamata di continuo aggiornando i led ad una velocità pazzesca e portando l'osservatore a vedere tutti i led sempre accesi per via della persistenza retinica.

parto con lo scusarmi per il ritardo nella risposta, ma con l'arrivo delle scuole sono arrivati anche gli impegni e ho un attimo trascurato il progetto...
Ho letto quello che volevi dirmi e mi hai fatto riflettere su molte cose. Però a me rimane ancora un dubbio: come dovrei agire quindi? Se d'ora in poi voglio iniziare a fare dei programmi un attimino più complessi devo riuscire a capire come bloccare delle esecuzioni senza usare i delay... ma è davvero così difficile? Spero di non avere questo tipo di problema in ogni cosa che provo a fare... Comunque qualche consiglio su come agire nel programma?

Difficile o non difficile dipende da chi si approccia al problema, se riesci a comprenderlo poi ti sarà facile usarlo. Chiaro che una funzione tipo il delay che la piazzi in un punto e fa fermare tutto per N tempo è di una comprensione immediata per il 99.9% degli utenti, millis è anch'essa una funzione, contrariamente a delay che nella sua implementazione fa "qualcosa" di specifico, millis ti restituisce un valore. Questo valore andrà usato con una logica e qui occorre un minimo di studio su come applicare una logica allo sviluppo, ma di fatto non è nulla di complicato, normalmente è una sottrazione ed un confronto, è il cambio di logica da applicare nella realizzazione del software che disorienta chi ha poca esperienza.
E' per questo che quando posso dico subito di abituarsi dal primo momento a non usare delay, classe String, Fritzing ecc. perché poi dover reimparare porta sconforto nell'utente che vorrebbe risolvere velocemente e facilmente il problema che ha davanti. Purtroppo se ho un programma finito al 95% e per arrivare al 100% mi dicono che devo ripensarlo da zero perché per colmare quel 5% non posso fare altrimenti di solito la reazione è "col cavolo! ci sarà pure una via facile", molto spesso non c'è :slight_smile:
Comunque finito il pippone per imparare ad usare millis per programmare i compiti e per capire le basi con cui si usa millis() consiglio prima la lettura di QUESTO post esplicativo, dopo di che lo studio di come si usa la funzione millis(), prima QUI, poi QUI e QUI e QUI e tutti gli articoli che sono in QUESTA pagina ... alla fine il tutto dovrebbe essere più chiaro :slight_smile:

Va bene
allora vedo di approfondire la parte di "teoria" per fare tutto ciò... Ma quindi mi stai dicendo che devo ripensare da capo il programma?? :o forse dovrei provare questa funzione millis su qualche progetto un pò più semplice (per es. la sincronizzazione di due semafori) e mettere un attimo in pausa questo... perchè già i registri a scorrimento sono una bella difficoltà in più (dato che li trovo difficili da pilotare anche solo col delay) e con questo millis sto diventando pazzo...
comunque concordo perfettamente con quello che hai detto tu. I delay sono veramente una droga per chi è alle prime armi... Quindi secondo te, d'ora in poi, anche se potessi, farei meglio ad utilizzare millis e non più i delay per fare una qualsiasi cosa? In modo che possa farci pratica giusto?? Grazie sempre dei tuoi consigli comunque...
gentilissimo. :slight_smile:

brun0filipp0:
Va bene
allora vedo di approfondire la parte di "teoria" per fare tutto ciò... Ma quindi mi stai dicendo che devo ripensare da capo il programma??

Ripensarlo sicuramente, ma visto il tuo programma in questione non è così arduo adattarlo, se hai tempo e voglia possiamo vedere di adattarlo per passare dai delay a millis in cinque o sei passi.
Intanto ti indico il primo passaggio da fare per arrivare all'obiettivo, se poi vorrai continuare, andremo avanti :slight_smile:
Prima cosa, torna al programma originale funzionante con i delay.
Poi per il momento mi concentrerei su una sola animazione in modo che sia per te più semplice seguire la logica.
Diciamo che animazione1 in realtà ne fa due, quindi la prima cosa che ti propongo è la seguente, dividi animazione1 in due funzioni distinte (che chiamerò per chiarezza anima_SX_DX e anima_DX_SX ma tu chiamale come ti pare), una che anima da sx a dx e l'altra che anima da dx a sx, fatto questo nel loop usando una variabile dovrai ad ogni ciclo di loop richiamare prima anima_SX_DX e al successivo ciclo anima_DX_SX.
Si tratta di due compiti credo facili che non dovrebbero porti particolari difficoltà, quando hai fatto posta il codice che proseguiamo :slight_smile:

non ho capito cosa vuoi dire nel momento in cui mi parli di far eseguire prima il ciclo anima sx-dx e poi di far animare al successivo ciclo anima ds-sx. A quale ciclo ti riferisci? Se parli del loop non capisco a cosa ti riferisci: dato che il loop si ripete continuamente e ripete sempre le stesse istruzioni... scusami ma purtroppo posso perdermi in qualche discorso... Ti ricordo che non sono molto ferrato :wink:

Ok, ci riprovo in modo più chiaro (spero)
La tua funzione animazione1 fa due cicli per animare i led da una parte all'altra e poi vice-versa. La prima cosa che devi fare e suddividere questa funzione in due funzioni distinte (che chiamerai come più ti pare), ognuna di queste dovrà vare solo una delle due animazioni, la prima farà la prima animazione, l'altra la seconda animazione.
A questo punto avrai due funzioni da dover richiamare nel loop e non solo una come adesso.
Se riesci ad ottenere questo risultato, posta il codice che proseguiamo

Aaah, si scusami non avevo capito io.
Comunque sia, rieccomi qua, dopo aver sistemato il programma con i delay:

#define data_ 2
#define latch_ 3
#define clock_ 4
void setup() {
 pinMode(data_, OUTPUT);
 pinMode(latch_, OUTPUT);
 pinMode(clock_, OUTPUT);

}

void loop() {
DXtoSX(30);   //tempo di accensione tra un LED e l'altro
SXtoDX(30);   //tra parentesi il delay casualmente uguale tra le animazioni
}

void scritturaregistro(unsigned long valore){          //funzione che si occupa del passaggio dei bit al registro
  digitalWrite(latch_, LOW);
  shiftOut(data_ ,clock_ ,MSBFIRST , valore >> 24 );   //sposto le cifre più significative "in fondo" fino al 32 esimo LED
  shiftOut(data_ ,clock_ ,MSBFIRST , valore >> 16 );
  shiftOut(data_ ,clock_ ,MSBFIRST , valore >> 8 );
  shiftOut(data_ ,clock_ ,MSBFIRST , valore);
  digitalWrite(latch_, HIGH);      }

void DXtoSX(unsigned int delaytoSX){     //DXtoSx (da destra verso sinistra)
 for(long i = 0 ; i < 32 ; i++){
  scritturaregistro((unsigned long)0x1 << i);
  delay(delaytoSX);             //ritardo definito nel loop
  }}
  void SXtoDX(unsigned int delaytoDX){   //SXtoDX (da sinistra verso destra)
  for(long i = 0 ; i < 32 ; i++){
  scritturaregistro(0x80000000 >> i);
  delay(delaytoDX);             //ritardo definito nel loop (delay diversi per le due animazioni)
  } 
                             }

(sappi che metto i commenti per chiarezza, in modo che possano capire tutti, credo che uno come te non ne abbia proprio bisogno :slight_smile: ).
Dai almeno per ora nulla di troppo difficile :smiley:
Beh, che dire...
Fammi strada!!

Ok, a questo punto devi riuscire a rimuovere il for da dentro la funzione, quindi ti suggerirei di aggiungere un parametro che passerai alla funzione che farà la vece di i. In questo modo sei costretto ad usare una variabile definita nel loop che dovrai incrementare nel loop dopo ogni chiamata finché il valore non superi il massimo possibile, nel qual caso dovrai azzerarla di nuovo.
Mi sono reso conto di aver detto una ca...ta mostruosa.
Ti suggerisco di usare una variabile globale che ad ogni chiamata della funzione andrai ad incrementare finché il suo valore non è superiore al massimo consentito, nel qual caso dovrai azzerarla

Per praticità ti consiglio di mettere temporaneamente questa riga del loop commentata

SXtoDX(30);

così:

//SXtoDX(30);

in modo da concentrarti sulla sola animazione da dx a sx che dovrai risucire a vedere eseguita in modo ciclico (primo led acceso, poi secondo, ..., ultimo led acceso epoi di nuovo primo acceso, secondo e così via)

In un punto del loop puoi mettere un
if(millis()-t>durata) {t+=durata; effetti();}

In effetti() metterai un contatore che si incrementa a ogni passaggio, tenendo il punto degli effetti. In base a tale contatore, a ogni passaggio, con uno switch/case farai il passo necessario e imposterai la durata del passo, ad esempio (pseudo codice, ormai più codice che pseudo):

effetti()
{
passo+=1;
if(passo>3) passo=1;
switch (passo)
  {
  case 1:
    spegnitutto();
    accendi_1();
    durata=200;
      break;

  case 2:
    spegni_1();
    accendi_2();
    durata=200;
      break;

  case 3:
    spegni_2();
    accendi_3();
    durata=200;
      break;
  }
} // fine effetti

Naturalmente, puoi anche scrivere qualcosa del tipo: se passo è compreso fra 1 e 10,
digitalWrite(passo, HIGH);
per accendere un sequenza da 1 a 10. Facendo qualche calcolo, nello stesso modo puoi spegnere l'uscita precedente.
Puoi anche considerare separatamente passo a seconda che sia tra 1 e 10 o tra 11 e 20 o tra 21 e 30.

brun0filipp0:
Quindi secondo te, d'ora in poi, anche se potessi, farei meglio ad utilizzare millis e non più i delay per fare una qualsiasi cosa? In modo che possa farci pratica giusto??

Il problema (come al solito) non è delay contro millis, ma la struttura dell'ordine di esecuzione delle istruzioni (post: Consiglio su come sostituire delay con millis - #11 by Claudio_FF - Software - Arduino Forum).

Se devi eseguire un solo lavoro/processo allora la logica procedurale passo passo e delay vanno benissimo. Appena vuoi fare due cose assieme bisogna assolutamente cambiare logica di esecuzione (ed eventualmente utilizzare la funzione millis perché diventa il modo più comodo per tenere conto del trascorrere del tempo).

Ora lo scoglio principale è comprendere la differenza di impostazione tra logica procedurale passo passo e logica non bloccante più simile al funzionamento di un PLC.

Questo è un loop contenente un for bloccante. Finché il for non termina azioni2 non vengono mai eseguite e azioni1 non vengono mai ripetute.

void loop()
{
    ...azioni1...

    for(long i = 0 ; i < 32 ; i++)
    {
        scritturaregistro(0x80000000 >> i);
        delay(delaytoDX);
    }

    ...azioni2...
}

Questo è un loop non bloccante, azioni1 e azioni2 vengono ripetute in continuazione, assieme all'if centrale che prende completamente il posto del for:

void loop()
{
    ...azioni1...

    if (millis()-inizio > delaytoDX)
    {
        inizio += delaytoDX; 
        scritturaregistro(0x80000000 >> i);
        i++;
    }

    ...azioni2...
}

Ok, è solo un esempio per partire con l'idea dell'elaborazione "affettata" in tantissimi passaggi invece che portata avanti un'istruzione per volta. Infatti manca ancora la gestione equivalente al termine del for e anche l'inizializzazione. In sostanza in questo codice di esempio non vi è ancora alcuna discriminazione di stati / fasi di funzionamento, che invece nel codice bloccante erano implicitamente "cablate" nella sequenza di avanzamento delle istruzioni (ma comunque ben presenti). Quello che c'è da fare con questa "nuova" impostazione è "esplicitarle", ad esempio con una variabile chiamata 'fase' che identifica se ci troviamo nella fase verso destra o in quella verso sinistra (è un banale if). Una volta capito questo modo di procedere, si ha la base generale per realizzare qualsiasi programma che porta avanti anche decine di compiti contemporaneamente (ovviamente ogni parte del codice deve essere strutturata allo stesso modo senza parti bloccanti).

Ringrazio tutti per il supporto...
Allora, rispondo a Claudio FF: Si in effetti coordinare tante azioni senza creare nessun blocco è una cosa difficile... diciamo uno degli ostacoli più grandi da superare per chi è alle prime armi (tipo io :slight_smile: ). Solo che non ho ancora ben capito per quale motivo il ciclo for che ho scritto io è "bloccante". Perdonami ma sono proprio io che non ho capito la tua spiegazione riguardo questa cosa.
Ora rispondo a Datman: ti ringrazio del consiglio: ma mi chiedo come mai suggerisci di fare questo. Quale vantaggi può darmi nello sketch? TI rinrazio in anticipo per la risposta.
E ora a fabpolli: Come mai ti sei reso conto della tua "ca...ta" addirittura mostruosa? :slight_smile: Comunque ok... mi concentrerò sulla sola animazione da destra a sinistra... Più tardi proverò a sistemare il programma come dici tu, e posterò il tutto. Comunque come mai mi consigli di rimuovere il for?

brun0filipp0:
non ho ancora ben capito per quale motivo il ciclo for che ho scritto io è "bloccante"

Perché finché il for non è finito il programma non può fare nient'altro. Il for è lungo 32 iterazioni e ciascuna prevede un delay di 30ms, quindi per 960ms Arduino è impegnato ad eseguire quel for, e solo dopo può proseguire con le altre operazioni.

brun0filipp0:
E ora a fabpolli: Come mai ti sei reso conto della tua "ca...ta" addirittura mostruosa? :slight_smile: Comunque ok... mi concentrerò sulla sola animazione da destra a sinistra... Più tardi proverò a sistemare il programma come dici tu, e posterò il tutto. Comunque come mai mi consigli di rimuovere il for?

Perché con la parte che poi ho barrato avrei spostato la logica all'esterno della funzione direttamente nel loop, cosa che non mi piace, una funzione deve fare quando possibile una cosa ben specifica e gestirla dalla A alla Z. Seguendo il consiglio che ho messo in quel post (uso della globale, incremento e verifica del limite massimo) elimini il for (il motivo lè già stato spiegato nei post precedenti) e poi potrai in seguito modificare il programma per eliminare il delay.

fabpolli:
Ok, a questo punto devi riuscire a rimuovere il for da dentro la funzione, quindi ti suggerirei di aggiungere un parametro che passerai alla funzione che farà la vece di i. In questo modo sei costretto ad usare una variabile definita nel loop che dovrai incrementare nel loop dopo ogni chiamata finché il valore non superi il massimo possibile, nel qual caso dovrai azzerarla di nuovo.
Mi sono reso conto di aver detto una ca...ta mostruosa.
Ti suggerisco di usare una variabile globale che ad ogni chiamata della funzione andrai ad incrementare finché il suo valore non è superiore al massimo consentito, nel qual caso dovrai azzerarla

Per praticità ti consiglio di mettere temporaneamente questa riga del loop commentata

SXtoDX(30);

così:

//SXtoDX(30);

in modo da concentrarti sulla sola animazione da dx a sx che dovrai risucire a vedere eseguita in modo ciclico (primo led acceso, poi secondo, ..., ultimo led acceso epoi di nuovo primo acceso, secondo e così via)

Mentre rileggevo il tuo post per poi iniziare a scrivere il codice mi sono chiesto: Come mai dovrei usare la variabile globalmente e misurare quante volta richiamo la funzione? A cosa mi serve? E poi percheè dopo dovrei azzerarla? Perdonami se non ho capito.

Claudio_FF:
Perché finché il for non è finito il programma non può fare nient'altro. Il for è lungo 32 iterazioni e ciascuna prevede un delay di 30ms, quindi per 960ms Arduino è impegnato ad eseguire quel for, e solo dopo può proseguire con le altre operazioni.

Ah si ho capito cosa intendevi, ma infatti ho provato a non mettere dei delay nel for (ed usare quindi la funzione millis), ma il problema è che non riesco ad ottenere lo stesso risultato del delay che usavo prima...

brun0filipp0:
Ah si ho capito cosa intendevi, ma infatti ho provato a non mettere dei delay nel for (ed usare quindi la funzione millis), ma il problema è che non riesco ad ottenere lo stesso risultato del delay che usavo prima...

Perché come più volte indicato per sostituire il delay con millis occorre rivedere la logica del programma e non il flusso e basat. Se hai fatto una prova postala che la controlliamo e possiamo segnalare eventuali errori e/o correzioni da apportare