Problemi con millis() e sintassi di un if

Premetto che questo è il primo programma che scrivo per un microcontrollore, la mia unica esperienza di programmazione risale a più 30 anni fa con il commodore 64.
Fatta la premessa, volevo imparare ad usare il modulo DDS AD9850, ma per particolari motivi non volevo realizzare il solito generatore di funzioni con l'encoder che setta la frequenza per incrementi, ma bensì generare un set di frequenze predefinito.
Leggendo molto per cercare di capire e scopiazzando qualche riga qua e là, il programma l'ho scritto... e gira anche. Lo posto sotto, perchè mi interesserebbe ricevere consigli a livello didattico su come potrebbe essere stato scritto, in maniera più "elegante", in particolare so che per evitare la catena dei comandi per scrivere sull'LCD, si possono usare altre funzioni e quindi gradirei consigli sulla struttura.
L'ho commentato anche eccessivamente, ma mi è servito più che altro per cercare di capire quello che stavo facendo.

Il programma è questo:

/* Questo programma invia a un modulo DSS un set predefinito di frequenze
   che vengono cambiate ciclicamente con la rotazione di un encoder, alla
   pressione del pulsante dell'encoder viene avviato il modulo per un tempo
   predefinito.
*/

#include <LiquidCrystal.h>
#include <DDS.h>           /* descrizione: http://m0xpd.blogspot.com/2014/03/dds-and-duedds-libraries.html
                              download:    https://github.com/m0xpd/DDS */

const int rs = 7, e = 8, d4 = 9, d5 = 10, d6 = A3, d7 = 12;     // costanti pin per display LCD

const int W_CLK = 13, FQ_UD = A2, DATA = 11, RESET = A0;        // costanti pin per modulo DDS

LiquidCrystal lcd(rs, e, d4, d5, d6, d7);                       // assegno i pin al display LCD

DDS dds(W_CLK, FQ_UD, DATA, RESET);                             // assegno i pin al modulo DDS

int ciclo = 0;                          // setta a 0 la variabile del ciclo
int Freq = 174;                         // variabile di settaggio frequenza
int encoder = 1;                        // posizione di start dell'encoder
int sendFreq = 174;                     /* la prima frequenza di default da inviare al DDS
                                             che corrisponde alla posizione 1 dell'encoder */
bool letturaPrec = HIGH;               // Variabile lettura encoder
unsigned long StartTime = 0;           // definisce l'inizio del nuovo ciclo
unsigned long WorkTime = 10000;        // preimposta il tempo di lavoro del ciclo
unsigned long contTempo;               // variabile conteggio tempo


void setup() {


  pinMode (4, INPUT_PULLUP);     // pin CLK encoder
  pinMode (5, INPUT_PULLUP);     // pin DT encoder
  pinMode (6, INPUT_PULLUP);     // pin pulsante encoder

  lcd.begin(16, 2);              // inizializza LCD
  dds.init();                    // inizializza modulo DDS
  dds.trim(125000000);           /* opzionale per la taratura fine della frequenza dell'oscillatore
                                     rispetto alla reale frequenza del quarzo. Esempio: (125000017) */
 }

void loop() {

  if (ciclo == 0 && (digitalRead(6) == HIGH)) {
    byte n = digitalRead(4);                  // assegna alla variabile "n" il livello letto dal pin CLK dell'encoder
    if ((letturaPrec == HIGH) && (n == LOW))  // rileva rotazione dell'encoder (fronte discesa CLK)
    {
      if (digitalRead(5) == HIGH) {           // se il pin DT è alto direzione decremento
        encoder--;                            // decrementa il valore di encoder
        if (encoder == 0) encoder = 10;       // decrementa circolarmente da 10 a 1

      } else {

        encoder = (encoder % 10) + 1;        // incrementa circolarmente da 1 a 10
      }

    }
    letturaPrec = n;


    unsigned int freqs[] = {  174,  220,  315,  483, 532,    // set di frequenze predefinito
                              596, 651, 798, 842, 974
                           };

    Freq = freqs[encoder - 1];                           /* assegna il valore di frequenza predefinito
                                                               a seconda della posizione dell'encoder */

    lcd.setCursor(0, 0);             // posiziona il cursore a 0 sulla prima riga
    lcd.print("FREQ SET");
    lcd.print("  ");
    lcd.print (Freq);                // visualizza la frequenza di set
    lcd.print(" Hz");
    lcd.setCursor (0, 1);            // posiziona il cursore a 0 sulla seconda riga
    lcd.print("   IN  ATTESA   ");
  }

  if (ciclo == 0 && (digitalRead(6) == LOW)) {  // se il pulsante encoder viene premuto
    sendFreq = Freq;                            // assegna il valore di Freq a sendFreq
    dds.setFrequency(sendFreq);                 // invia la frequenza selezionata al modulo DDS e lo avvia

    lcd.setCursor (0, 1);                       // posiziona il cursore a 0 sulla seconda riga
    lcd.print("FREQ USO");
    lcd.print("  ");
    lcd.print(sendFreq);                        // visualizza la frequenza inviata al DDS
    lcd.print(" Hz");

    StartTime = millis();                       // scrive il tempo passato fino questo momento nella variabile StartTime
    ciclo = 1;                                  // mette a 1 la variabile ciclo per eseguire le istruzioni successive
    while (digitalRead(6) == LOW);              // finchè il pulsante è premuto non fa altro

  }
  if (ciclo == 1 && digitalRead(6) == HIGH)  {  // se il pulsante è rilasciato ed ha eseguito il ciclo precedente
    do {
      contTempo = millis() - StartTime;         /* sottrae il tempo di inattività dal tempo globale
                                                   e lo assegna alla variabile di conteggio tempo*/
    }
    while (WorkTime > contTempo && digitalRead(4) == HIGH && digitalRead(5) == HIGH);      /* esce dal ciclo se viene raggionto il tempo di
                                                                                          lavoro preimpostato o se l'encoder viene ruotato */

  }
   ciclo = 2;

  if (ciclo == 2) { 
    digitalWrite(RESET, HIGH);          // invia impulso di reset al DDS di un millisecondo
    delay(1);                           
    digitalWrite(RESET, LOW);      
    ciclo = 0;                                 // torna all'inizio
  }
   
}

Ho parecchi problemi con la sintassi e devo andarmela spesso a rivedere anche per comandi che ho già usato.
Quando sono riuscito a farlo compilare, o il display flickerava, oppure ne vedevo solo la metà, o il programma
smetteva di girare.
L'ho scritto e riscritto, ma non ne vengo a capo, e non riesco a immaginare quale sia l'impostazione corretta e
avrei bisogno di qualche aiuto.
Grazie

Ciao! Faccio una considerazione, per programmare arduino si dovrebbe partire da cose molto semplici in modo da imparare le basi, e cimentarsi successivamente in cose più complesse, questo per imparare in modo graduale. Il tuo programma è di solito considerato impegnativo come prima esperienza di programmazione, di solito di parte da cose semplicissime come blink di un led :slight_smile:

Tornando al tuo codice, l'uso di millis() mi sembra corretto, anche se hai usato diverse variabili di cui se ne poteva fare a meno, comunque è il modo giusto per controllare il trascorrere di un intervallo di tempo.
Adesso mi sembra che ci diano qualche parentesi graffa di troppo, che non è associata a niente, se metto solo due parentesi graffe con codice, il codice verrà sempre eseguito.

Quello che devi tenere presente Che il setup() e il loop(), vengono eseguiti in un tempo brevissimo, tu allo scadere di un ora stampi su lcd, ma in millesimi di secondo viene eseguito il setup() e il loop(), se nel loop() è prevista la scrittura su lcd, tu non avrai il tempo di vedere nessuna scritta su lcd, ossia, allo scadere di un ora stampo su lcd ma dopo millesimi di secondo il loop() stampa il suo messaggio.
Quindi guarda bene la stampa su lcd del loop(), perché forse viene fatta di continuo.

Aggiungo altre considerazioni a quelle di torn24.

Al posto di

   if (encoder == 1) Freq = 100;   // Set 100Hz
   if (encoder == 2) Freq = 200;   // Set 200Hz
   if (encoder == 3) Freq = 300;   // Set 300Hz
   if (encoder == 4) Freq = 400;   // Set 400Hz
   if (encoder == 5) Freq = 500;   // Set 500Hz
   if (encoder == 6) Freq = 600;   // Set 600Hz
   if (encoder == 7) Freq = 700;   // Set 700Hz
   if (encoder == 8) Freq = 800;   // Set 800Hz
   if (encoder == 9) Freq = 900;   // Set 900Hz
   if (encoder == 10) Freq = 1000;  // Set 1000Hz

puoi scrivere semplicemente

   Freq = encoder * 100;

Con queste istruzioni

   if (digitalRead(6) == LOW) {            // solo se il pulsante viene premuto
     while (digitalRead(6) == LOW) {   
       sendFreq = Freq;                      //  assegna il valore di Freq a sendFreq
       dds.setFrequency(sendFreq);    //   e invia la frequenza selezionata al modulo DDS
     }
   }

tu invii continuamente Freq al dds finché il pulsante resta premuto. Per inviarlo una volta sola puoi fare così

   if (digitalRead(6) == LOW) {            // solo se il pulsante viene premuto
     sendFreq = Freq;                      //  assegna il valore di Freq a sendFreq
     dds.setFrequency(sendFreq);    //   e invia la frequenza selezionata al modulo DDS
     while (digitalRead(6) == LOW) ;  //FINCHÈ IL PULSANTE È LOW NON FARE NIENTE
   }

Per quanto riguarda l'uso di millis(), dopo aver settato StartTime come da te suggerito, dovresti inserire il controllo del tempo che passa nel loop() con queste istruzioni

   if (millis() - StatTime >= WorkTime) {
     lcd.clear();                            //      pulisce il display
     lcd.setCursor(2, 0);               //       centra la scritta
     lcd.print ("Fine  Lavoro");     //        avviso di fine lavoro
//==A QUESTO PUNTO PUOI BLOCCARE IL PROGRAMMA CON UN'ISTRUZIONE DEL TIPO=====
     while (true) ;
   }

Ciao,
P.

più 30 anni fa con il commodore 64.

Che bei tempi! Con il suo "peek" "poke"...

L'ho commentato anche eccessivamente

E' perfetto cosi'. Manca solo una piccola descrizione all'inizio di quello che fa!

Concordo perfettamente con quello che ha scritto da @torn24

Aggiungerei:

Freq = encoder * 100; non sarebbe meglio?
Hai 2 variabili; encoder, Freq che sono di troppo!!

int letturaPrec = HIGH;

Avendo come valore HIGH o LOW, dovresti dichiarare la variabile letturaPrec come una bool. risparmi un byte.

Non riesco a capire questa parte

int n = digitalRead(4);                                // assegna la variabile "n" al pin CLK dell'encoder
  if ((letturaPrec == HIGH) && (n == LOW))  //  stabilisce la direzione dell'encoder
  {
    if (digitalRead(5) == HIGH) {                  //   se il pin DT è alto
      encoder--;                                           //   decrementa il valore di encoder
      if (encoder == 0) encoder = 10;           //   se il valore arriva a zero ricomincia da 10 a scalare

    } else {
      encoder++;                                      //     incrementa il valore di encoder
      if (encoder == 11) encoder = 1;        //      se il valore supera 10 ricomincia a contare da 1

    }

  }
 
 letturaPrec = n;

oxyjo:
L'ho commentato anche eccessivamente, ma mi è servito più che altro per cercare di capire quello che stavo facendo.

I commenti iniziali sulla lettura encoder sono un po' imprecisi, meglio così:

byte n = digitalRead(4);   // assegna alla variabile "n" il livello letto dal pin CLK
if ((letturaPrec == HIGH) && (n == LOW))  // rileva rotazione dell'encoder (fronte discesa CLK)
{
    if (digitalRead(5) == HIGH) {            // se il pin DT è alto direzione decremento
        encoder--;                           // decrementa circolarmente da 10 a 1
        if (encoder == 0) { encoder = 10; }
    } else {
        encoder = (encoder % 10) + 1;        // incrementa circolarmente da 1 a 10
    }
}
letturaPrec = n;

Come "trucchetto" nell'incremento ho usato l'operatore % (resto della divisione che quando 'encoder' vale 10 risulta 0).

rimandasse al setup(), oppure rimanesse in attesa di di un nuovo settaggio e start.

Al setup non si torna, a meno di un reset fisico. Ma nel loop basta condizionare la logica con una variabile che indica se siamo nello stato "regolazione" o "marcia", qualcosa tipo:

SE run == 0:
    ...
    SE premuto:
       scrivi DDS
       scrivi partenza LCD
       memorizza tempo
       run = 1

SE run == 1:
  ...
  SE trascorso T:
     ferma DDS
     scrivi fine LCD
     run = 0

Più in generale, se parliamo di struttura, la programmazione si dovrebbe dividere in due fasi, nella prima c'è l'analisi degli stati possibili del sistema, degli eventi a cui rispondere in ogni stato e di come rispondere (tutto questo si fa su carta), nella seconda c'è la traduzione/codifica vera e propria usando uno specifico linguaggio (quindi con i tipi dati e operazioni disponibili) e uno specifico hardware.

Ad esempio sicuramente voglio che all'accensione il sistema si predisponga in una certa situazione di "riposo". Poi? Nel caso in questione voglio regolare una frequenza con un encoder e premendolo dare il via al generatore, questa è una nuova situazione/stato "avviato". Poi? Voglio che dopo un certo tempo il tutto ritorni a "riposo". Ma potrei anche voler tornare a "riposo" ripremendo il pulsante una seconda volta. Oppure cambiare la frequenza anche nella fase "avviato", o ancora far si che il tempo scada non dalla pressione del pulsante, ma dall'ultima operazione effettuata, e vuoi mai che mi venga anche in mente di far lampeggiare un LED...

Pensare a tutte queste cose nella fase di codifica/sintassi, aggiungendo "a martellate" una modifica dopo l'altra man mano che vengono le idee è difficile anche per un esperto. Progettarle invece nella prima fase di analisi è molto più semplice, perché banalmente basta disegnare cerchi e frecce (stati e transizioni) e cosa c'è da fare in ogni stato.

Alla luce di questo, la base di un programma che abbia molti stati di funzionamento si può scrivere con una grande struttura 'if/else if' gestita da una variabile che rappresenta lo stato attuale:

if (1 == fase) {
    ...
} else if (2 == fase) {
    ...
} else if (3 == fase) {
    ...
} else if (4 == fase) {
    ...
}

...o con uno switch equivalente:

switch (fase) {

    case 1:
        ...
    break;

    case 2:
        ...
    break;

    case 3:
        ...
    break;

    case 4:
        ...
    break;
}

Grazie a tutti per i commenti ed i preziosi consigli, siete veramente un bel gruppo di persone!
Ora passerò attraverso i vostri suggerimenti, per cercare di comprenderli a fondo e metterli in
pratica.
Appena ottengo i risultati (mi ci vuole un po'... ma arrivano! :-), posterò di nuovo il codice
rispondendo ad ognuno.

Rieccomi,
Vi ringrazio nuovamente per i preziosi consigli.
Non mi sono limitato a fare dei copia e incolla, per rispetto del tempo che mi avete dedicato e per la reale voglia che ho di imparare, ma sono andato attraverso cercando di comprenderli meglio che ho potuto.

ecco il codice

/* Questo programma invia a un modulo DSS un set predefinito di frequenze
   che vengono cambiate ciclicamente con la rotazione di un encoder, alla
   pressione del pulsante dell'encoder viene avviato il modulo per un tempo
   predefinito.  
*/
#include <LiquidCrystal.h>
#include <DDS.h>           // descrizione: http://m0xpd.blogspot.com/2014/03/dds-and-duedds-libraries.html
                           // download:    https://github.com/m0xpd/DDS

const int rs = 7, e = 8, d4 = 9, d5 = 10, d6 = 11, d7 = 12;     // costanti pin per display LCD

const int W_CLK = A3, FQ_UD = A2, DATA = A1, RESET = A0;        // costanti pin per modulo DDS

LiquidCrystal lcd(rs, e, d4, d5, d6, d7);                       // assegno i pin al display LCD

DDS dds(W_CLK, FQ_UD, DATA, RESET);                             // assegno i pin al modulo DDS

int ciclo = 0;                    // setta a 0 la variabile del ciclo
int Freq;                         // variabile di settaggio frequenza
int encoder = 1;                  // posizione di start dell'encoder

int sendFreq = 100;               // la prima frequenza di default da inviare al DDS
                                  //   che corrisponde alla posizione 1 dell'encoder
bool letturaPrec = HIGH;          // Variabile lettura encoder
unsigned long StartTime = 0;      // definisce l'inizio del nuovo ciclo
unsigned long WorkTime = 10000;   // preimposta il tempo di lavoro del ciclo
byte CLKen = 2;

void setup() {

  pinMode (CLKen, INPUT_PULLUP); // pin CLK encoder
  pinMode (5, INPUT_PULLUP);     // pin DT encoder
  pinMode (6, INPUT_PULLUP);     // pin pulsante encoder

  lcd.begin(16, 2);              // inizializza LCD
  dds.init();                    // inizializza modulo DDS
  dds.trim(125000000);           // opzionale per la taratura fine della frequenza dell'oscillatore
                                 // rispetto alla frequenza del quarzo. Esempio: (125000017)
}

void loop() {

  byte n = digitalRead(CLKen);              // assegna alla variabile "n" il livello letto dal pin CLK dell'encoder
  if ((letturaPrec == HIGH) && (n == LOW))  // rileva rotazione dell'encoder (fronte discesa CLK)
  {
    if (digitalRead(5) == HIGH) {           // se il pin DT è alto direzione decremento
      encoder--;                            // decrementa il valore di encoder
      if (encoder == 0) encoder = 10;       // decrementa circolarmente da 10 a 1

    } else {

      encoder = (encoder % 10) + 1;        // incrementa circolarmente da 1 a 10
    }

  }
  letturaPrec = n;

  
  if (encoder == 1) Freq = 100;   // Set 100Hz

  if (encoder == 2) Freq = 185;   // Set 185Hz

  if (encoder == 3) Freq = 296;   // Set 296Hz

  if (encoder == 4) Freq = 357;   // Set 357Hz

  if (encoder == 5) Freq = 437;   // Set 437Hz

  if (encoder == 6) Freq = 592;   // Set 592Hz

  if (encoder == 7) Freq = 688;   // Set 688Hz

  if (encoder == 8) Freq = 721;   // Set 721Hz

  if (encoder == 9) Freq = 884;   // Set 884Hz

  if (encoder == 10) Freq = 935;  // Set 935Hz
 
  if (ciclo == 0) {

    lcd.setCursor(0, 0);             // posiziona il cursore a 0 sulla prima riga
    lcd.print("FREQ SET");
    lcd.print("  ");
    lcd.print (Freq);                // visualizza la frequenza di set
    lcd.print(" Hz");
    lcd.setCursor (0, 1);            // posiziona il cursore a 0 sulla seconda riga
    lcd.print("   IN  ATTESA   ");   // Stampa su LCD e aspetta la pressione del pulsante
  }

  if (digitalRead(6) == LOW) {            // resta in attesa finchè il pulsante viene premuto

    sendFreq = Freq;                      // assegna il valore di Freq a sendFreq
    dds.setFrequency(sendFreq);           // e invia la frequenza selezionata al modulo DDS
    StartTime = millis();                 // scrive il tempo passato fino questo momento nella variabile StartTime
    ciclo = 1;                            // mette a 1 la variabile ciclo per eseguire la condizione successiva
    while (digitalRead(6) == LOW);        // finchè il pulsante è premuto non fa altro
  }

  if (ciclo == 1) {                // se il pulsante è stato premuto e rilasciato
    lcd.setCursor (0, 1);          // posiziona il cursore a 0 sulla seconda riga
    lcd.setCursor (0, 1);          // posiziona il cursore a 0 sulla seconda riga
    lcd.print("FREQ USO");
    lcd.print("  ");
    lcd.print(sendFreq);           // visualizza la frequenza inviata al DDS
    lcd.print(" Hz");

  if (millis() - StartTime >= WorkTime){        // sottrae dal tempo globale il tempo di inutilizzo
                                                       //   e conta il tempo di lavoro
    ciclo = 0;                                  // mette la variabile ciclo a 0 per rimettersi in attesa
    }
  }
}

@Torn24

Ciao! Faccio una considerazione, per programmare arduino si dovrebbe partire da cose molto semplici in modo da imparare le basi, e cimentarsi successivamente in cose più complesse, questo per imparare in modo graduale. Il tuo programma è di solito considerato impegnativo come prima esperienza di programmazione, di solito di parte da cose semplicissime come blink di un led :slight_smile:

Hai perfettamete ragione, :slight_smile: ma nei lunghi anni che ho lavorato con l'elettronica, ho avuto a che fare con diversi programmatori; non ho imparato propriamente a programmare, ma a forza di discutere codici, in PHP, Basic, C e Lisp, qualcosa mi è rimasto nella zucca tanto avere perlomeno una vaga idea di quello che ho davanti. Far blinkare un led è alquanto noioso, e poi come si dice... di necessità si fa virtù,
prima di decidermi a usare un micro, ho montato 4 oscillatori tradizionali uno con un XR2206, uno con un ICL8038, uno con i tradizionali operazionali ed infine uno tirato fuori dalle application notes della linear, l'unico veramente stabile e degno di essere considerato era quest'ultimo, ma aveva un notevole costo e un cablaggio da incubo, gli altri se ne andavano tutti a spasso con le frequenze.

Quello che devi tenere presente Che il setup() e il loop(), vengono eseguiti in un tempo brevissimo....Quindi guarda bene la stampa su lcd del loop(), perché forse viene fatta di continuo.

Giusto... poi neanche in millesimi ma in microsecondi... pur sapendolo(remotamente), non mi era venuto in mente che quello fosse il problema. Grazie.

@Pgiagno

puoi scrivere semplicemente... Freq = encoder * 100;

molto interessante, in questo caso evita 10 if e me lo terrò a mente, purtroppo io nello scrivere il codice, non avendo ancora stabilito le frequenze esatte, avevo messo a caso dei multipli di 100 non pensando che avrebbe causato una misinterpretazione, ma nella realtà le frequenze saranno del tutto diverse, in quello nuovo le ho rimesse a caso, ma più consone alla realtà.

tu invii continuamente Freq al dds finché il pulsante resta premuto.

era una cosa che immaginavo,ma non sapevo come fare... mi sono detto: ...fà niente... perchè non sentivo le bestemmie che tirava il DDS :D. Sono andato a vederlo, ed ora capisco meglio cosa fa while().

Per quanto riguarda l'uso di millis(), dopo aver settato StartTime come da te suggerito, dovresti inserire il controllo del tempo che passa nel loop()

L'ho fatto, e intanto mi sono risparmiato una variabile, perchè non sapevo che millis() si potesse usare direttamente. Il while(true) invece l'ho evitato per il motivo che alla fine rimaneva lì, e non mi permetteva di fare altro, e dovevo resettare la Nano per ricominciare.
Comunque l'insieme dei consigli e stato molto istruttivo :).
Grazie
@Savoriano

Che bei tempi! Con il suo "peek" "poke"...

che spettacolo che era nèh? :slight_smile: e stato è stato il primo computer "vero" sul quale ho messo le mani, a parte qualche lezione a all'ITIS nel 1975 con un olivetti che era grosso come una cassettiera a 5 cassetti, e aveva 365 Byte (si, proprio Byte) di memoria e andava con le schede perforate, niente ram e solo assembler.

Manca solo una piccola descrizione all'inizio di quello che fa!

Giusto, mi ero dimenticato le buone maniere :slight_smile:

Hai 2 variabili; encoder, Freq che sono di troppo!!

ci ho guardato ma non ci arrivo... mi spiegheresti perchè e come faresti per favore?

Avendo come valore HIGH o LOW, dovresti dichiarare la variabile letturaPrec come una bool. risparmi un byte.

...Ho risparmiato il byte :slight_smile:

Non riesco a capire questa parte

perchè era commentato da cani...

Grazie.

@Claudio_FF

I commenti iniziali sulla lettura encoder sono un po' imprecisi

Sei stato gentile a definirli imprecisi... se leggi 4 righe sopra, la mia descrizione si addice meglio :slight_smile:

Come "trucchetto" nell'incremento ho usato l'operatore %

Bello!....compreso e... catturato :slight_smile:

Al setup non si torna, a meno di un reset fisico. Ma nel loop basta condizionare la logica con una variabile che indica se siamo nello stato "regolazione" o "marcia", qualcosa tipo:

... Ci ho messo un po' a comprenderlo bene, ma tenendo presente la "oxyjostate" che mi hai allegato l'ho ristruttutato come hai suggerito.

Alla luce di questo, la base di un programma che abbia molti stati di funzionamento si può scrivere con una grande struttura 'if/else if' gestita da una variabile che rappresenta lo stato attuale: ........ o con uno switch equivalente:

ah! else if lo comprendo meglio anche se mi è ancora difficile usarlo.... mentre Switch, pur avendolo letto e riletto non sono ancora riuscito a digerirlo.

Grazie anche a Te.

Prima di compilare il programma, guardandolo mi è venuto il sospetto (poi confermato) che una volta partito non avrei più potuto cambiare niente, il che se fosse per i dieci secondi che ho impostato in WorkTime per provarlo non sarebbe un problema, ma siccome dovrà girare un'ora o due se mi fossi sbagliato nell'impostare le frequenza, mi toccherebbe resettarlo.

Mi sono letto questo tutorial: Gammon Forum : Electronics : Microprocessors : Interrupts
e questo: Appunti su Arduino: interrupts | Michele Maffucci
che sostanzialmente è la traduzione del primo.

Quindi ho spostato il pin del CLK dell'encoder sul pin 2 e ho provato ad usare l'interrupt.

const byte CLKenc = 2;  //ho nominato il pin 2

void EncRotate (){ 
     ciclo == 0;             //poi ho creato un Void EncRotate

attachInterrupt (digitalPinToInterrupt (CLKenc), EncRotate, CHANGE); // ho usato la funzione relativa

                                                          
interrupts() e noInterrupts();            //poi ho provato ad abilitarla e disabilitarla quando serviva

..... ed ho cercato in tutti i modi di uscire da "if (millis() - StartTime >= WorkTime){...}, ma per quanto ci abbia girato intorno, spostandole qua è là, non ci ho ricavato niente. Quindi tenendo bene a mente la citazione di Claudio_FF: * * * * Una domanda ben posta è già mezza risposta * * * *... per favore:

  1. dove andrebbe messa la funzione attachinterrup?
  2. e dove invece void EncRotate (), se fosse giusta?
    3 è giusto utilizzare interrupts() e noInterrupts () o sarebbe meglio sei (); e cli ();?
    se ho capito bene le ultime due lavorano su tutti gli interrupts, ma in questo caso
    usandone solo uno cambia qualcosa?
    4)Ho letto che prima di leggere millis() se c'è un interrupt attivo, potrebbe dare dei risultati inconsistenti nel caso che quest'ultimo venga chiamato nel mezzo della lettura di millis(),e che quindi bisognerebbe memorizzare lo stato del contatore e poi ripristinarlo dopo la lettura, e fa questo esempio che non mi è per niente chiaro:
unsigned long millis()
{
  unsigned long m;
  uint8_t oldSREG = SREG;

  // disable interrupts while we read timer0_millis or we might get an
  // inconsistent value (e.g. in the middle of a write to timer0_millis)
  cli();
  m = timer0_millis;
  SREG = oldSREG;

  return m;
}

5)come si usa correttamente una volatile?
6)quanto sopra rientra nel mio caso?

Grazie nuovamente a Tutti per il Vostro aiuto e per la Pazienza.

dove andrebbe messa la funzione attachinterrup?

Secondo me stai facendo un passo un po' più lungo della tua gamba, dato che

mentre Switch, pur avendolo letto e riletto non sono ancora riuscito a digerirlo.

Cominciamo a risolvere i problemi del tuo codice senza aggiungerne degli altri.
La sintassi dello switch:

switch (var) {
  case label1:
    // statements
    break;
  case label2:
    // statements
    break;

Dove var è la tua variabile encoder e labelx sono i suoi valori possibili. //statements è il codice da eseguire.
Switch funziona come un if else if ed è adatto al tuo tipo di codice.
Lo switch (o if else if) lo puoi usare anche per la variabile di stato "ciclo".
L'uso di switch o if else if è puramente soggettivo.

Poi c'è il problema che scrivi sul LCD ad ogni ciclo. Dovresti scriverci sopra solamente se cambia qualche cosa.
Una soluzione sarebbe usando una variabile bool che passerà vera se è cambiato qualche cosa, scrivere sul LDC e riportarla falsa.

oxyjo:
Bello!....compreso e... catturato :slight_smile:

Ci sarebbe anche la versione per il decremento ma è più complessa (significa aggiungi a 'encoder' -1 se encoder>1, altrimenti 9):

encoder += (encoder > 1) ? -1 : 9;  // decrementa circolarmente da 10 a 1

mentre Switch, pur avendolo letto e riletto non sono ancora riuscito a digerirlo.

oxyjoswitch.png

provarlo non sarebbe un problema, ma siccome dovrà girare un'ora o due se mi fossi sbagliato nell'impostare le frequenza, mi toccherebbe resettarlo.

Appunto, questa è una cosa da progettare a priori come logica, non cercando un'istruzione (o altro sistema) che lo faccia nel codice già presente (che segue un'altra logica).

  1. dove andrebbe messa la funzione attachinterrup?

Nel setup.

  1. e dove invece void EncRotate (), se fosse giusta?

È una funzione a parte, nei programmi Arduino è indifferente scriverla prima o dopo le altre funzioni.

3 è giusto utilizzare interrupts() e noInterrupts () o sarebbe meglio sei (); e cli ();?

Non andrebbero usati se non sapendo perché e cosa si sta facendo. Normalmente non c'è alcun bisogno di disattivare gli interrupt (che dietro le quinte portano avanti altri compiti come aggiornare il contatore di millis, gestire la comunicazione seriale ecc).

4)Ho letto che prima di leggere millis() se c'è un interrupt attivo...

È una questione già risolta dalla funzione millis, normalmente non c'è bisogno di occuparsene.

potrebbe dare dei risultati inconsistenti nel caso che quest'ultimo venga chiamato nel mezzo della lettura di millis(),e che quindi bisognerebbe memorizzare lo stato del contatore e poi ripristinarlo dopo la lettura

Si chiama concorrenza, quando due diversi processi (in questo caso il programma principale e la routine di gestione dell'interrupt) vogliono leggere/modificare la stessa variabile interferendo l'uno con l'altro. Dichiarare una variabile 'volatile' risolve la concorrenza a livello di programma, ma non quella hardware a livello di registri. L'esempio si riferisce a come evitare una corruzione (o errata lettura) da registri hardware, anche queste cose che con Arduino non si ha praticamente bisogno di occuparsene.

6)quanto sopra rientra nel mio caso?

Ed infine no, per me non serve affatto andarsi a impegolare con gli interrupt solo per bypassare a basso livello una logica impostata in un certo modo.

Con il codice del post #7 hai giustamente creato due fasi di funzionamento distinte (grazie alla variabile 'ciclo' che assume i valori 0 e 1). Basta mettere anche la lettura dell'encoder dentro ciascuna di esse per regolare e passare senza colpo ferire da una all'altra ad ogni pressione:

Se ciclo 0:
    Se encoder up:
        incrementa circolarmente
        aggiorna LCD
    Altrimenti Se encoder dn:
        decrementa circolarmente
        aggiorna LCD
    Altrimenti Se pressione:
        scrivi LCD fase run
        avvia DDS
        memorizza millis di inizio
        attendi rilascio
        ciclo = 1
Altrimenti se ciclo 1:
    Se encoder up:
        incrementa circolarmente
        aggiorna LCD
        aggiorna DDS
    Altrimenti Se encoder dn:
        decrementa circolarmente
        aggiorna LCD
        aggiorna DDS
    Altrimenti Se pressione OR timeout:
        scrivi LCD fase stop
        stop DDS
        attendi rilascio
        ciclo = 0

evita 10 if e me lo terrò a mente, purtroppo io nello scrivere il codice, non avendo ancora stabilito le frequenze esatte,

A questo servono gli array (il primo elemento ha indice 0, per questo ho scritto -1)

unsigned int freqs[] = {  137,  200,  500,  1000, 1100,
                          2000, 5600, 5750, 8000, 12300  };


Freq = freqs[encoder-1];

oxyjoswitch.png

Grazie a tutti per l'aiuto che mi avete dato fin'ora.
Grazie CLAUDIO_FF e savoriano per la spiegazione di if else e switch... spiegata in modo "umano", ho fatto un po' di esercizi ed ora mi sono molto più chiare. In particolare grazie Claudio per il tempo speso a spiegarmi la strutturazione del programma... ho imparato moltissimo, e Te ne sono grato.
Ho ri-editato il programma al primo post con quello attuale. Seguendo i vostri consigli il programma ora funziona, non so se sia stata la miglior maniera di scriverlo... ma gira! Tranne che per una cosa che più sotto spiego.
Prima ho una considerazione: ho notato che l'uscita dal ciclo di conteggio del tempo non si ferma esattamente alla fine preimpostata, ma sempre qualche millisecondo dopo...random, il che è ovvio dato che dipende dalla iterazione delle istruzioni e per quanto tempo tengo premuto il pulsante prima di rilasciarlo. In questo caso la differenza su due ore di ciclo (anche se nel programma postato sono 10 secondi per comodità di test) è insignificante, ma se avessi a che fare con tempi cortissimi diventerebbe un problema, e mi chiedevo a livello didattico se ci fosse una maniera di evitare questo ritardo.

Il problema al quale mi riferivo è invece che, alla fine del ciclo il DDS non si ferma e continua a generare la frequenza, sul datasheet dell'AD9850 c'è scritto che per resettare i registri gli va dato un impulso positivo di reset (della durata minima di 5 cicli di clock), quindi alla fine del programma gli ho dato questo impulso. Il risultato di quello che ho scritto è che non ho più nessuna forma d'onda in uscita :o, che presumo sia dovuto al fatto che l'istruzione viene eseguita continuamente e mi tenga il modulo costantemente resettato... ma non sono riuscito ad immaginare una condizione univoca per la quale il reset venga inviato una e una sola volta solo alla fine del programma.... qualche suggerimento??

StartTime = millis(); // scrive il tempo passato fino questo momento nella variabile StartTime

Arghhhh! Il commento: memorizza in StartTime il tempo attuale

Ok che è il tempo passato (più o meno) dall'accensione, ma per noi è il momento attuale da cui iniziamo a misurare il trascorrere dei 'WorkTime' millisecondi.

contTempo = millis() - StartTime; /* sottrae il tempo di inattività dal tempo globale

Anche questo: calcola tempo trascorso da StartTime

l'uscita dal ciclo di conteggio del tempo non si ferma esattamente alla fine preimpostata, ma sempre qualche millisecondo dopo...random

Tra il momento in cui parte la frequenza e quello in cui carichi 'StartTime' ci sono un po' di operazioni intermedie, gli accessi all'LCD portano via tempo. Prova a mettere il caricamento di 'StartTime' appena prima di 'dds.setFrequency'

presumo sia dovuto al fatto che l'istruzione viene eseguita continuamente e mi tenga il modulo costantemente resettato... ma non sono riuscito ad immaginare una condizione univoca per la quale il reset venga inviato una e una sola volta solo alla fine del programma..

Bastava dare l'impulso appena dopo il do/while, o, in alternativa, impostare ciclo=2 appena dopo il do/while e non fuori da ogni condizione dove viene eseguito sempre.