Go Down

Topic: Problemi con millis() e sintassi di un if (Read 280 times) previous topic - next topic

oxyjo

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:


Code: [Select]
#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 Freq;                               // 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

int letturaPrec = HIGH;     //     Variabile lettura encoder

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 frequenza del quarzo. Esempio: (125000017)
}

void loop() {

  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;

  {

    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

    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
      }
    }

    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("FREQ USO");
    lcd.print("  ");
    lcd.print(sendFreq);           // visualizza la frequenza inviata al DDS
    lcd.print(" Hz");             

  }
}


E fin qui tutto bene... almeno funziona!
Il problema è nato quando ho deciso di aggiungere un timer che dopo un'ora di funzionamento dalla pressione del pulsante di
start, fermasse il programma, fermasse il DDS, scrivesse sul display "fine lavoro" o qualcosa del genere e mi rimandasse al
setup(), oppure rimanesse in attesa di di un nuovo settaggio e start.
Ho provato a definire delle variabili unsigned long per Time, StartTime e WorkTime, per usarle con "millis()".


Il Ragionamento ( magari sbagliato) che ho fatto è stato:
Nel ciclo while() dove avvio il lavoro con la pressione del pulsante, leggo millis() e lo metto in una variabile StartTime
(quindi lo faccio una volta sola) e memorizzo così il tempo che è passato da quando ho avviato arduino prima di farlo
lavorare.
Dopo che leggo di nuovo millis(), lo metto in una variabile Time e gli sottraggo StartTime (il tempo che non ha lavorato),
e lo scrivo nella variabile WorkTime.

Code: [Select]
  ....tutto quello che c'è prima.....

    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);        //   invia la frequenza selezionata al modulo DDS
               StartTime = millis();      //    scrive il tempo che è passato fino questo momento nella variabile StartTime                      // aggiorna il tempo globale nella variabile Time
   
     
      }
    }
      ...
      ...
      "qui il ciclo di stampa sull'LCD"
      ...
      ...
      ...

        Time = millis();                                      // legge il tempo globale dall'avvio di arduino           
   
        WorkTime = (Time - StartTime);            //   sottrae il tempo di inattività prima dell'inizio del ciclo
   
     
     a questo punto if (WorkTime arriva al tempo fisso impostato (in millisecondi) che dovrebbe essere per
     esempio un'ora)
       
      }
             
        lcd.clear();                            //      pulisce il display
        lcd.setCursor(2, 0);               //       centra la scritta
        lcd.print ("Fine  Lavoro");     //        avviso di fine lavoro
            setup();                         //          riparte da zero in attesa di un nuovo start             
      } 
    }

   
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   

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 :)

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.

pgiagno

Aggiungo altre considerazioni a quelle di torn24.

Al posto di

Code: [Select]
   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

Code: [Select]
   Freq = encoder * 100;

Con queste istruzioni

Code: [Select]
   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ì

Code: [Select]
   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

Code: [Select]
   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.




savoriano

#3
Mar 26, 2020, 09:27 am Last Edit: Mar 26, 2020, 09:32 am by savoriano
Quote
più 30 anni fa con il commodore 64.
Che bei tempi! Con il suo "peek" "poke"...
Quote
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!!

Code: [Select]
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
Code: [Select]
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;
Pardonnez moi pour mon français, ce n'ai pas ma langue maternelle.

Claudio_FF

#4
Mar 26, 2020, 10:36 am Last Edit: Mar 26, 2020, 05:42 pm by Claudio_FF
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ì:

Code: [Select]
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).

Quote
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:

Code: [Select]
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:

Code: [Select]
if (1 == fase) {
    ...
} else if (2 == fase) {
    ...
} else if (3 == fase) {
    ...
} else if (4 == fase) {
    ...
}

...o con uno switch equivalente:

Code: [Select]
switch (fase) {

    case 1:
        ...
    break;

    case 2:
        ...
    break;

    case 3:
        ...
    break;

    case 4:
        ...
    break;
}
* * * * Una domanda ben posta è già mezza risposta * * * *

oxyjo

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.


oxyjo

#6
Mar 27, 2020, 06:36 am Last Edit: Mar 27, 2020, 06:54 am by oxyjo
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
Code: [Select]
/* 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

Quote
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 :)
Hai perfettamete ragione, :)  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.

Quote
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

Quote
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à.

Quote
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().

Quote
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

Quote
Che bei tempi! Con il suo "peek" "poke"...
che spettacolo che era nèh?  :)  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.

Quote
Manca solo una piccola descrizione all'inizio di quello che fa!
Giusto, mi ero dimenticato le buone maniere  :)

Quote
Hai 2 variabili; encoder, Freq che sono di troppo!!
ci ho guardato ma non ci arrivo... mi spiegheresti perchè e come faresti per favore?

Quote
Avendo come valore HIGH o LOW, dovresti dichiarare la variabile letturaPrec come una bool. risparmi un byte.
...Ho risparmiato il byte  :)

Quote
Non riesco a capire questa parte
perchè era commentato da cani...

Grazie.


oxyjo

@Claudio_FF

Quote
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  :)

Quote
Come "trucchetto" nell'incremento ho usato l'operatore %
Bello!....compreso e... catturato  :)

Quote
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.

Quote
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: http://gammon.com.au/interrupts
e questo: https://www.maffucci.it/2012/06/11/appunti-su-arduino-interrupts/
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.

Code: [Select]
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:
Code: [Select]
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.

savoriano

Quote
dove andrebbe messa la funzione attachinterrup?
Secondo me stai facendo un passo un po' più lungo della tua gamba, dato che
Quote
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:
Code: [Select]
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.
Pardonnez moi pour mon français, ce n'ai pas ma langue maternelle.

Claudio_FF

#9
Mar 27, 2020, 11:17 am Last Edit: Mar 28, 2020, 12:48 pm by Claudio_FF
Bello!....compreso e... catturato  :)
Ci sarebbe anche la versione per il decremento ma è più complessa (significa aggiungi a 'encoder' -1 se encoder>1, altrimenti 9):
Code: [Select]
encoder += (encoder > 1) ? -1 : 9;  // decrementa circolarmente da 10 a 1
Quote
mentre Switch, pur avendolo letto e riletto non sono ancora riuscito a digerirlo.


Quote
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).

Quote
1) dove andrebbe messa la funzione attachinterrup?
Nel setup.

Quote
2) e dove invece void EncRotate (), se fosse giusta?
È una funzione a parte, nei programmi Arduino è indifferente scriverla prima o dopo le altre funzioni.

Quote
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).

Quote
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.

Quote
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.

Quote
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:

Code: [Select]
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

Quote
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)

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


Freq = freqs[encoder-1];
* * * * Una domanda ben posta è già mezza risposta * * * *

Go Up