Seriale comanda colore

Strega comanda colore è un gioco classico dell'infanzia
esiste anche in una versione alternativa (meno diffusa)
Strega comanda dimensione #numero
e i bambini devono trovare oggetti di quella dimensione

ecco quindi che per far "passare" numeri dalla seriale ho inventato
"Seriale comanda colore"

premessa: seriale non vuol dire nulla, potrebbe essere seriale, II5, file, Seriale Software
non importa, basta che sia uno stream
io scriverò sempre seriale, ma intendo lo stream che si sta usando

premessa II la vendetta
non intendo ne risolvere problemi specifici ne compilare i miei programmi qui presentati
do solo spunti, poi che ha orecchie per intendere intenderà...

Cominciamo con una grande condizione propedeutica:
Sappiamo quali sono le caratteristiche del "flusso" di dati
ovvero come viene scritto
cosa viene scritto
ogni quanto viene scritto
e via così

la mia non è una domanda: è una condizione
se non sapete cosa e quanto e come e...
è inutile che si prosegua la lettura, andate ad informarvi
Ricordate sempre la:
Seconda legge di Nelson (che sono io): Remember, researching how to solve a problem is often necessary before a programmer can teach the computer how to solve it
ovvero:
Ricorda, la ricerca su come risolvere un problema è spesso necessaria prima che un programmatore possa insegnare al computer come risolverlo (Bjarne Stroustrup)

facciamo quindi adesso degli esempi di "ricezione" da seriale
esempio: numeri codificati in ASCII, interi, con separatore/identificatore di fine/terminatore
rappresentanti una sola variabile, sempre la stessa ripetuta ogni... oppure semplicemente aggiornata
prima domanda:
conosciamo il terminatore?
NO? come nel gioco dell'oca: tornate alla casella "andate ad informarvi"
SI? bene
è un carattere o una stringa?
Come dite? cosa è una stringa? tornate alla casella "andate ad informarvi"
poniamo caso che sia un carattere, potrebbe essere un semplice spazio (' ' oppure 32 oppure 0x20) o magari un fine riga, carattere speciale che si indica '\r' o ....)
bene, si prosegue

seconda considerazione:
se la trasmissione avviene rapidamente ci troviamo a dover facilmente gestire stringhe "lunghe", che potrebbe rallentare il programma
oppure anche se il programma fosse bloccante ci troveremmo facilmente nella condizione di perdere parte della trasmissione, a causa del riempimento del buffer di seriale durante la parte bloccante del programma
sono sicuro che non vi stupirete se alla domanda "e come si evitano programmi bloccanti?" la risposta sarà: come nel gioco dell'oca - tornate alla casella "andate ad informarvi"

da adesso domande e risposte del genere saranno "evitate" in questa discussione

siccome noi non vogliamo trovarci a dover "gestire" perdite di dati dobbiamo evitare di usare metodi bloccanti, anche per la lettura da seriale
chi avesse un minimo (ma proprio minimo) di dimestichezza con il linguaggio di Arduino potrebbe pensare che una cosa del tipo

String parola;
parola=Serial.readString;
int valore;
valore=atoi(parola);

Non provateci nemmeno
questo programma è il "male"
usa l'allocazione dinamica, è bloccante e non fa un vero controllo di start e stop
rischiate di trovarvi col programma principale bloccato ad aspettare inutilmente un valore che non viene trasmesso in questo momento
e questo chiude ogni considerazione del genere
pertanto gli "arrivi" dalla seriale vanno gestiti "carattere per carattere" e usando funzioni "semplici" del linguaggio

altra questione è certamente "dove" salvare il valore "in arrivo"
dato che se "aggiorniamo" il valore mano a mano che arrivano successivi caratteri appare evidente che "durante" una ricezione il valore non è corretto, invece "appena" dopo una ricezione corretto lo è, ma lo rimane per poco tempo

quindi serve di avere una "variabile di appoggio" che deve essere usata come "blocco d'appunti" e solo al termine della ricezione copiata nella variabile "giusta"

fatto questo "cominciamo il circo"

// per prima cosa la variabile da tenere aggiornata
int variabile; // che fantasia, vero?
// comunque deve essere globale, per poter essere aggiornata da una funzione separata
// il terminatore
#define TERMINATORE ' ' // al posto dello spazio scrivete il giusto terminatore
// e che non venga in mente di usare const int const char o similia, siamo su un Arduio, la memoria è poca e si usano macro!

invece dentro nella loop() andiamo a vedere se la seriale ha ricevuto un carattere e ed eventualmente lo trattiamo

.
. qui tutta la loop..
.
if (Serial.available())
{
   // leggo il carattere
   char c=Serial.read();
   tratta(c);
   //ovviamente che fa il lavoro sporco è la tratta()
}
.
. anche qui la loop prosegue...
.

come vedete se la loop è scritta bene (ovvero non bloccante) basta aggiungere una manciatina di righe per avere l'aggiornamento della variabile da seriale

il lavoro duro lo fa la tratta

void tratta(char c)
{
   // una variabile locale per conservare il valore in ricezione
   // naturalmente static per non perderne il valore
   static int attuale=0; // sempre inizializzare variabili locali
   // qui bisogna ragionare un poco
   // i caratteri si dividono in tre categorie:
   // terminatore, che chiude la ricezione e "manda avanti" il risultato
   if (c==TERMINATORE)   
      {
      // scrivo sulla variabile globale il valore trovato finora
      variabile=attuale;
      // azzero la locale per ricominciare i conti
      attuale=0;
      // basta, lavoro finito, torno alla loop
      return;
   }
   // oppure cifre, che devono venire conteggiate
   if (isdigit(c))
      {
      locale = locale * 10 + c -'0';
      // per sapere come fnziona leggere il K&R
      // cifra elaborata, lavoro finito
      return;
      }
   // oppure caratteri di riempimento, inutili
   // che appunto non tratto
   // e quindi lavoro finito
   return;
}

di questo modo, dopo la prima trasmissione, in "variabile" si trova sempre l'ultimo valore trasmesso
invece la funzione tratta() sta aggiornando il "prossimo" valore, attualmente in trasmissione
il tutto è velocissimo e non rischia di rallentare mai la loop() e nemmeno di intasare i buffer di seriale

2 Likes

ci sono da considerare un paio di varianti:

variabili float (o double, stessa minestra è)
Più variabili trasmesse in sequenza (fissa), ma tutte dello stesso tipo

poi verrà la parte "bella", più variabili NON dello stesso tipo NON trasmesse in sequenza fissa
ma un passo per volta
per adesso variabili float

naturalmente serve di aggiornare i tipi delle variabili dove serve,
nel resto di questa discussione non tratterò ovvietà del genere

serve anche di identificare il separatore decimale '.' oppure ','
naturalmente una @define DECIMALE ...

convertire una sequenza di char in un float non è banale come nel caso di un int
serve di tenere traccia se siamo nella parte intera o nella parte decimale del numero
nella parte intera il lavoro è uguale a prima, nella parte decimale serve un po' di fantasia

diciamo che nel riconoscimento della categoria del carattere serve aggiungere

if (c==DECIMALE){
   //trovato separatore decimale
   divisore++;
   //  il divisore è il "peso" da dare a ciascuna cifra successiva alla virgola
  return;
}

naturalmente "divisore" va riazzerato ogni volta si riazzera attuale, e anche lui deve essere locale static, come "attuale"
se invece troviamo una cifra, dobbiamo selezionare due casi

 if (isdigit(c))
   if(!divisore){
      {
      locale = locale * 10 + c -'0';
      // per sapere come funziona leggere il K&R
      // cifra elaborata, lavoro finito
      return;
      }
   }
   else
   {
      float cifra=c-'0';
      // il valore grezzo della cifra trovata
      int diviso=divisore;
      while (diviso)
         {
         diviso--;
         cifra=cifra/10;
         }
    // adesso che la abbiamo "pesata" la aggiungiamo
   attuale=attuale+cifra;
  // la prossima vale un decimo di questa
   divisore++;
   }
}

Contate le parentesi che lo ho scritto qui al volo, non sull'IDE

chiaro come funziona?
quando trova la virgola (o il punto) alza un divisore, che da zero passa ad uno
per ogni cifra che trova col divisore alzato la riduce a un decimo e la somma, poi alza il divisore
la prossima la ridurrà ad un centesimo, e via così

questo non lo ho provato, qui
ma l'idea viene dritta dritta da uno dei miei tanti salvataggi e recuperi di variabili su file
quindi funziona, basta solo mettere a posto le cose che ho lasciato come "compito al lettore"

vediamo invece adesso la possibilità di avere più variabili, dello stesso tipo, trasmesse in sequenza, sequenza conosciuta e separatore tra variabili noto e sempre uguale (potrebbe anche non essere così, ma i problemi si affrontano caso per caso

la questione si risolve facilmente mettendo tutte le variabili da ricevere in un unico array


int variabile[8];

che deve essere globale come nel caso di una sola variabile

il ciclo di riconoscimento dello stream si complica un poco
si tratta di contare il separatore, che va comunque definito con una #define SEPARATORE

e usarlo come indice dello array
va da se che il conteggio dei separatori va azzerato ad ogni terminatore
e che la parte che calcola la variabile deve aggiornare l'elemento di array e riazzerarsi ad ogni successivo separatore

void tratta(char c)
{
   // una variabile locale per conservare il valore in ricezione
   // naturalmente static per non perderne il valore
   static int attuale=0; // sempre inizializzare variabili locali
   static indice=0;
   // qui bisogna ragionare un poco
   // i caratteri si dividono in varie categorie:
   // Separatore, che serve per contare l'indice dell'array
   if (c==SEPARATORE){
      // aggiorno lo array
      variabile[indice]=attuale;
      attuale=0;
      indice++;
      return;
   }
   // terminatore, che chiude la ricezione 
   if (c==TERMINATORE)   
      {
      // scrivo sulla variabile globale il valore trovato finora
      variabile[indice]=attuale;
      // azzero la locale per ricominciare i conti
      attuale=0;
      indice=0;
      // basta, lavoro finito, torno alla loop
      return;
   }
   // oppure cifre, che devono venire conteggiate
   if (isdigit(c))
      {
      locale = locale * 10 + c -'0';
      // per sapere come fnziona leggere il K&R
      // cifra elaborata, lavoro finito
      return;
      }
   // oppure caratteri di riempimento, inutili
   // che appunto non tratto
   // e quindi lavoro finito
   return;
}

come vedete ho cominciato a ridurre i commenti

rimane la questione dei nomi

certo che una variabile di nome Temperatura_regolata
è molto più descrittiva di variabile[4]

però basta ricordarsi di scrivere
#define Temperatura_regolata variabile[4]

rimane il caso di più variabili tutte float
e sono sicuro che confrontando la versione monovariabile intera con quella float siete in grado di aggiornare la versione multivariabile

e credo per oggi di chiudere qui

Prossimo passo, che non so fare, sarebbe evitare di usare variabili globali

Che variabili globali siano il "male" è vero per i pc, che di memoria ne hanno in abbondanza e hanno situazioni SW grandi e complicate

Su un sistema minimo come Arduino sono generalmente accettate

Però evitarle potrebbe essere utile

Si accettano consigli

Se sono tante e riconducibile ad un algoritmo che opera su di esse potresti benissimo usare una struct che li raggruppa. Oppure come dici tu, pensare laterale, ma di preciso non so al momento cosa comporta.

Ciao.

Ne verremo a capo...

Sicuramente, sai cosa non mi piace?
La tendenza a reinventare la ruota. Ogni tanto capita però che chi ha questa tendenza reinterpreti totalmente ciò che pare consolidato. Io non ho questa tendenza e fatico se mi togli tutti i riferimenti che considero consolidati, ad esempio gli algoritmi. Però come vedi, mi incuriosisce e mi affascina questo modo di affrontare le problematiche.

Ciao.

Io mi limito ad offrire spunti per chi si vuole scrivere il suo programma

A dimostrare che le cose accadono a "ondate" oggi salta fuori la richiesta di leggere e caricare variabili double da trasmissioni espresse in forma esponenziale (detta anche notazione scientifica)

Non ci avevo pensato, qui e adesso

Però lo avevo risolto anni fa, nella mia "salva che ti passa"

Quindi sono sicuro che farci un giro aiuterà chi avesse tale esigenza

Buongiorno Standardoil,
ti ringrazio molto per l'ausilio gli spunti che ci stai dando!

AL momento, ho i seguenti dubbi in merito alla tratta(char c):

"variabile" è una int che prende il valore di "c" quando c è una cifra e che invece azzeriamo quando c corrisponde al terminatore che abbiamo individuato.
E' corretto?
Visto che il valore che ci viene trasmesso dalla seriale può essere un numero formato da una sequenza di cifre, non sarebbe il caso che variabile vada a riempire un array[ ] in grado di collezionare tutte cifre di cui abbiamo bisogno?

Non ho capito la funzione della variabile static int "attuale" e non ho capito quali valori assume.
Forse nella if(isdigit(c)) (valori cifra) dovremmo andare a lavorare sulla variabile "attuale" e non su quella indicata come "locale".
Mi sbaglio?

Nella if(isdigit(c)) diciamo che, se c corrisponde ad una cifra allora locale = locale * 10 + c -'0'.
Ti prego di spiegarci brevemente questa sintassi per favore. Ho cercato di recuperare informazioni (come ci hai consigliato tu a proposito del K&R) ed ho capito che in questo modo, attraverso c-'0', convertiamo il nostro valore tipo char in un int. E' giusto?.
Ma ancora non mi torna il precedente *10.
Puoi almeno darci un link nel quale poter comprendere il significato di questa azione?
A questo punto inoltre, tornando a quanto detto al precedente punto 1., avremmo bisogno di un int array[ ] per collezionare tutte le cifre del nostro numero.

Ti ringrazio infinitamente.

1a: giusto
1b: tu hai bisogno di un numero, non di un array di cifre

2a: c'è nel commento
2b: sì, sbagli

3a: se non conosci un minimo di aritmetica...
3b: ancora con un array? A te serve un numero

Ci ho pensato molto prima di dare queste risposte, il programma è breve, lineare e ben commentato
Se anche così non è comprensibile io non posso farci più nulla...

Adesso, per considerare il caso di più variabili non tutte dello stesso tipo avevo intenzione di prendere un GPS e decodificarne "al volo" (ovvero in maniera non bloccante e byte per byte) la sentenza NMEA

Purtroppo non è arrivato
Io per principio acquisto sempre le cose più scrause e al minor prezzo,
se funziona cosi "a maggior ragione" funzione anche con cose migliori
Purtroppo questa volta la spedizione ha fallito in qualcosa...

Mi sa che serve di aspettare ancora un po'

Mi spiace

... purtroppo:

  1. la "sentenza" NON è una, ma sono svariate "sentenze" ciascuna con il suo significato e vanno riconosciute e decodificate tutte (inclusa la verifica del CRC).

  2. le suddette arrivano in continuazione a 9600 bps e ... tempo per fare altre cose ne resta abbastanza poco (comunque si arriva farci un "tracker" senza troppe difficoltà). I guai cominciano se vuoi fare cose che impiegano del "tempo" ...

Guglielmo

Si e no

Stando ai varii datasheet sembra che le varie sentenze siano disabilitabili da software (sul ricevitore)

Comunque la seriale adottata è TTL
Attiva bassa quindi
Un semplicer OR a diodi dovrebbe disabilitarla in HW quando non serve

Vedremo...

Si, certo, peccato che per fare una cosa che abbia una qualche utilità e non sono "giocare", ce ne sono una serie che sono praticamnete obbligatorie :wink:

Guglielmo

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.