Codificatore/decodificatore dati seriali.

Spesso salta fuori il problema di come fare per estrapolare i vari dati contenuti in una comunicazione seriale, idem per l'opposto, ecco una soluzione semplice, elegante e che richiede poche risorse. Premetto che quanto segue non riguarda la ricezione/trasmissione dei dati e relativo protocollo, indipendentemente dal tipo di bus, siamo nella condizione di avere un buffer pieno con i dati ricevuti oppure di doverlo riempire con quelli da inviare. La soluzione è usare una unione composta da un struttura e il buffer dati, in pratica prima si crea una struttura che contiene tutti i dati che dobbiamo inviare/ricevere, possono essere di tutti i tipi meno che stringa perché non è un tipo dati standard del C, si devono usare gli array di char al loro posto, dopo di che la struttura viene inserita in una unione assieme ad un buffer, di tipo byte, con una dimensione pari alla somma di tutti i dati inseriti nella struttura. Vediamo un esempio pratico, ipotizziamo di voler inviare/ricevere quattro dati di tipi char, una stringa char di quattro caratteri e due numeri unsigned long int, la struttura da realizzare è questa:

// struttura dati
typedef struct
{
  char dato1;
  char dato2;
  char dato3;
  char dato4;
  char dato5[4];
  unsigned long int val1;
  unsigned long int val2;
} Dati_S;

Dopo di che creiamo l'unione tra la struttura e un buffer di tipo byte grande 16 byte, è la somma dei vari dati, 1+1+1+1+4+4+4 = 16.

// union che gestisce il buffer
union Gest_Dati
{
  Dati_S myData;
  char buffer_s[16]; 
} 
Dati_U;

Ora abbiamo gli strumenti per poter codificar e decodificare il buffer dati inviato/ricevuto tramite seriale/I2C/SPI, etc. Per decodificare i dati prima si copia il buffer ricevuto nel buffer dell'unione, o più semplicemente si usa direttamente questo per ricevere i dati, dopo di che è sufficiente leggere i singoli campi della struttura per avere i nostri dati estratti dal buffer. Per trasmettere si fa il contrario, prima si caricano i singoli campi della struttura con i dati da inviare dopo di che si trasmette il buffer dell'unione, che automaticamente contiene tutti i byte da inviare già formattati.

Esempio di codice completo funzionante, i dati caricati nei singoli campi della struttura vengono poi stampati sul terminale seriale usando il buffer dell'unione.

// struttura dati
typedef struct
{
  char dato1;
  char dato2;
  char dato3;
  char dato4;
  char dato5[4];
  unsigned long int val1;
  unsigned long int val2;
} 
Dati_S;

// union che gestisce il buffer
union Gest_Dati
{
  Dati_S myData;
  char buffer_s[16]; 
} 
Dati_U;

// CodeDecode.buffer_s[0]

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

  // init dati struttura
  Dati_U.myData.dato1 = 'a';
  Dati_U.myData.dato2 = 'b';
  Dati_U.myData.dato3 = 'c';
  Dati_U.myData.dato4 = 'd';
  Dati_U.myData.dato5[0] = 'T';
  Dati_U.myData.dato5[1] = 'e';
  Dati_U.myData.dato5[2] = 's';
  Dati_U.myData.dato5[3] = 't';
  
  Dati_U.myData.val1 = 123456;
  Dati_U.myData.val2 = 654321;

  // stampa buffer
  Serial.println(Dati_U.buffer_s);
}

void loop() {

}

La cosa bella che tutto sto codice non consuma risorse CPU in più che accedere direttamente al buffer senza la struttura.

C'è un modo più sporco e portatore di errori che comunque ho visto usare tanto. Mi riferisco al cast sporco da puntatore buffer a puntatore a struct, o viceversa da struct a puntatore uint8_t.

Ciao.

ciao ragazzi, ma quindi io questo lo posso utilizzare anche con una comunicazione RS485?

Erik86: ciao ragazzi, ma quindi io questo lo posso utilizzare anche con una comunicazione RS485?

Come ho specificato questo metodo è indipendente dal tipo di trasmissione seriale, sia come bus che come protocollo, in quanto agisce direttamente sul buffer dati ricevuto o da trasmettere.

Questo codice funziona:

(*(Dati_S*)(buffer_s)).dato1 = 1;        // accesso tramite operatore . 
((Dati_S*)(buffer_s))->dato1 = 1;       // accesso tramite operatore ->

Tuttavia il compilatore ora mi avvisa: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

Con la union è tutto meno misterioso.

Ciao.

x iscrizione

Ciao,
ho provato a utilizzare il codice postato ma in compilazione (IDE 1.06 windows 7 64 bit ) mi restituisce quest’errore e non riesco a capirne il motivo:

Arduino: 1.0.6 (Windows 7), Board: "Arduino Uno"
F:\Fabrizio\Software\arduino_editor\hardware\tools\avr\bin\avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -MMD -DUSB_VID=null -DUSB_PID=null -DARDUINO=106 -IF:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino -IF:\Fabrizio\Software\arduino_editor\hardware\arduino\variants\standard C:\Users\Fabrizio\AppData\Local\Temp\build7981574632788313861.tmp\TestStruttura.cpp -o C:\Users\Fabrizio\AppData\Local\Temp\build7981574632788313861.tmp\TestStruttura.cpp.o 

TestStruttura.ino: In function 'void setup()':
TestStruttura:43: error: call of overloaded 'println(byte [16])' is ambiguous
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:71: note: candidates are: size_t Print::println(const String&) <near match>
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:72: note:                 size_t Print::println(const char*) <near match>
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:73: note:                 size_t Print::println(char) <near match>
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:74: note:                 size_t Print::println(unsigned char, int) <near match>
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:75: note:                 size_t Print::println(int, int) <near match>
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:76: note:                 size_t Print::println(unsigned int, int) <near match>
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:77: note:                 size_t Print::println(long int, int) <near match>
F:\Fabrizio\Software\arduino_editor\hardware\arduino\cores\arduino/Print.h:78: note:                 size_t Print::println(long unsigned int, int) <near match>

fabpolli: Ciao, ho provato a utilizzare il codice postato ma in compilazione (IDE 1.06 windows 7 64 bit ) mi restituisce quest'errore e non riesco a capirne il motivo:

Cambia "byte buffer_s[16];" con "char buffer_s[16];" e l'errore sparisce, è stata una distrazione mia quando ho ricopiato il codice e ho modificato il buffer da char in byte senza riprovare il tutto.

Grazie mille adesso funziona alla perfezione!

Buongiorno a tutti, sono molto interessato al topic, posso farti qualche domanda Astrobeed? credi che possa usare questa soluzione per inviare contemporaneamente 6 variabili di tipo int (ingressi analogici) ad un secondo arduino ricevendone da esso altrettanti indietro utilizzando comunicazione seriale? con tutti i tipi di software che avevo utilizzato per test (atoi, parseint) mi si è sempre accumulato un ritardo nel loop (credo causato da un errato codice) fino anche a 5 secondi e talvolta uno slittamento delle variabili. grazie mille

signorbarro: credi che possa usare questa soluzione per inviare contemporaneamente 6 variabili di tipo int (ingressi analogici) ad un secondo arduino ricevendone da esso altrettanti indietro utilizzando comunicazione seriale?

Certo che si, però ti rammento che quanto spiegato si applica solo ad un set dati già pronto all'uso, non ha nulla a che vedere con il protocollo di ricezione/invio dei dati che deve comunque essere interpretato. Se l'invio/ricezione dei dati avviene non frequentemente, p.e. una volta al secondo, puoi semplicemente preparare l'array da inviare tramite l'unione e poi usare una serial.print(buffer) per inviare tutto quanto in una volta sola, dal lato ricezione non devi fare altro che attendere la ricezione del corretto numero di dati, p.e. 12 byte nel caso di sei interi, per poi recuperare i vari dati dai singoli elementi della struttura.

grazie per la risposta, infatti l'avevo notato tentando di utilizzarlo. giusto per curiosità, ho provato il tuo schetch, che in uscita mi manda questa seriale:

abcdTest@â

forse ho frainteso, ma non avrei dovuto ricevere abcdTest123456654321

credo di aver capito la tua spiegazione infatti pensavo al discorso della struttura a byte definiti in ricezione (anche se in realtà vorrei una frequenza di trasmissione un po' maggiore) per poter in pratica ottenere i valori degli analogici del primo arduino sul secondo. giusto una curiosità, quando recupero per esempio un valore dalla mia struttura in ricezione il mio arduino capisce che è un numero oppure lo interpreta come una stringa?

buonanotte e grazie di nuovo

signorbarro: giusto per curiosità, ho provato il tuo schetch, che in uscita mi manda questa seriale:

abcdTest@â

forse ho frainteso, ma non avrei dovuto ricevere abcdTest123456654321

Il limite è il serial monitor di Arduino che ragiona solo in ASCII. Val1 e val2 sono dati di tipo unsigned int e vengono inviati sotto forma di 4 byte, ciascuno, rappresentanti il contenuto reale della variabile, 123456 corrisponde a 0x0001E240 internamente alla variabile e questo viene trasmesso sulla seriale, ovvero quattro byte contenenti rispettivamente 0x00 x001 0xE2 0x40. Il monitor seriale di Arduino non può visualizzare correttamente dei valori numerici in formato binario e cerca di stampare i corrispondenti caratteri ASCII con risultati imprevedibili.

credo di aver capito la tua spiegazione infatti pensavo al discorso della struttura a byte definiti in ricezione (anche se in realtà vorrei una frequenza di trasmissione un po' maggiore) per poter in pratica ottenere i valori degli analogici del primo arduino sul secondo.

Senza usare un protocollo, per quanto semplice, ipotizzando di trasmettere a 115200 bps, al massimo riesci ad inviare pochi pacchetti al secondo, solo in questo modo eviti sovrapposizioni di dati. Se usi un protocollo, quello che preferisci, che ti consente di distinguere i pacchetti tra di loro puoi trasmettere anche centinaia di pacchetti al secondo, quanto dipende dalle dimensioni del pacchetto e dalla velocità della seriale.

giusto una curiosità, quando recupero per esempio un valore dalla mia struttura in ricezione il mio arduino capisce che è un numero oppure lo interpreta come una stringa?

Il vantaggio di usare l'unione più struttura è proprio che non serve un parser, sempre complicato da scrivere e pesante da eseguire, che analizza i dati ricevuti e li ricompone come serve, tutti i dati inviati vengono ricomposti automaticamente, senza impegnare nemmeno un ciclo di clock, nel loro formato originale.

quindi riassumo per vedere se ho capito:

  • lo schetch mi da quei valori sul serial monitor, ma se usassi comunicazione tra due arduino o tra arduino e computer per esempio via Hyperterminal oppure con seriale (tradotta ad esempio con max232) otterrei 123456654321.

  • quando mi spieghi che per l’invio senza sovrapposizione di dati, dovrei usare un protocollo intendi un protocollo software tipo easy transfer (per arduino<=>arduino) o firmata (arduino<=>pc) oppure intendi un protocollo di trasmissione come seriale, I2C, o wire.
    Nel caso tu ti riferisca ad un protocollo software mi sapresti suggerire il più usabile per inviare un pacchetto a byte definiti con i valori analogici dei potenziometri, magari un inizio e un fine riga che poi possa essere pescato in ricezione tramite convertito in una struttura come quella del topic e poi scorporato come se in pratica fossero i valori dei potenziometri dell’arduino ricevente?

  • volendo usare in ricezione la struttura io pensavo di recuperare dal buffer di ricezione a partire dal carattere inizio riga e creare una stringa a byte finiti es 14 (inizio riga 6analogici e un fine riga), successivamente decriptare la struttura come hai fatto nello schetch di esempio. ho fatto diversi tentativi, ma mi sono legato. mi puoi dire solo se il mio è un approccio corretto o è un modo sbagliato?

ti ringrazio nuovamente, se sto andando OT non ho problemi ad aprirne uno nuovo

Una variabile di tipo struct o un array occupano un porzione di memoria RAM contigua, cioè tutti i dati sono uno di seguito all'altro, quindi 0x00 x001 0xE2 0x40 possono essere:

  • un array di 4 elementi di tipo byte
  • un array di 2 elementi di tipo int
  • una struct di 4 byte o 2 int o 1 float

La union fa la magia, e infatti si tratta di una illusione in quanto su i 4 byte dell'esempio il compilatore non genera codice eseguibile (una minima parte si, ma non per realizzare la magia).

Sostanzialmente è come se i dati noi li mettessimo in una lista excell o in una tabella, che è una operazione che facciamo per meglio interpretare i dati.

@signorbarro Non sono sicuro di aver capito, qual'è il tuo dubbio. Dai una lettura alla tabella ASCII, il valore del byte da 0 a 255 (0x0-0xFF) e ciò che viene spedito/ricevuto tramite seriale, quindi è come copiare una porzione di memoria RAM da Arduino A in una porzione di RAM presente in Arduino B.

Non dimentichiamo che nel caso di stringhe C, queste devono avere il carattere terminatore '/0' per cui la dimensione imposta deve essere sempre +1 la dimensione in caratteri della stringa. la stringa "Pippo/0" deve stare in un array di 6 elementi.

Ciao.

signorbarro:

  • lo schetch mi da quei valori sul serial monitor, ma se usassi comunicazione tra due arduino o tra arduino e computer per esempio via Hyperterminal oppure con seriale (tradotta ad esempio con max232) otterrei 123456654321.

Se visualizzi su un terminale seriale in modalità ASCII ottieni sempre lo stesso risultato del monitor di Arduino, se visualizzi in modalità hex vedi i valori dei singoli byte, per le lettere la loro codifica ASCII, per i numeri i byte che compongono la variabile di partenza, da uno a quatto a seconda del tipo.

  • quando mi spieghi che per l’invio senza sovrapposizione di dati, dovrei usare un protocollo intendi un protocollo software tipo easy transfer (per arduino<=>arduino) o firmata (arduino<=>pc)

Mai usati e non so dirti se sono utilizzabili, tocca vedere che formato dati accettano e come li gestiscono.
Il protocollo dati più semplice possibile, nel caso di pacchetti con lunghezza fissa, è usare un header e un footer che identificano in modo univoco l’inizio e la fine del pacchetto più un timeout, sul primo byte ricevuto, per annullare la ricezione e azzerare il tutto in caso di mancata conferma pacchetto ricevuto nei tempi previsti.
Non sarebbe male aggiungere un semplice cheskusm (somma binaria di tutti i byte del pacchetto) a 16 bit.

astrobeed: Il protocollo dati più semplice possibile, nel caso di pacchetti con lunghezza fissa, è usare un header e un footer che identificano in modo univoco l'inizio e la fine del pacchetto più un timeout, sul primo byte ricevuto, per annullare la ricezione e azzerare il tutto in caso di mancata conferma pacchetto ricevuto nei tempi previsti. Non sarebbe male aggiungere un semplice cheskusm (somma binaria di tutti i byte del pacchetto) a 16 bit.

grazie astrobeed per la dritta, anch'io sto sperimentando la union che non conoscevo, anzi mi è stata utile anche per maneggiare finalmente i byte ;) :blush:

ho provato una cosa del genere:

typedef struct
{
  char startByte;
  char idArduino;
  char idDevice;
  char comando; 
  unsigned int valore;
  char endByte;
  char crc;
  
  // lunghezza: 1+1+1+1+2+1+1=8
} Dati_S;


union Gest_Dati
{
  Dati_S myData;
  char buffer_s[8]; 
} 
Dati_U;

dove la struct identifica l'Arduino (qualora ne avessi più di uno, nell'ipotesi di una comunicazione tipo 485), il device (in pratica il pin), il comando e un valore. All'inizio metto uno carattere di start e alla fine uno di end, oltre ad un crc.

Facendo così, ovviamente, un minimo di parsing dovrò farlo, perchè dovrò capire, con una Serial.read, cosa mi sta arrivando e procedere a riempire il buffer man mano che arrivano byte sulla seriale, e di processare il buffer ricevuto quando sarò sicuro di averlo ricevuto tutto (carattere end) & corretto (carattere crc).

C'è un modo più veloce? Non è che questo parsing, alla fine, vanifica l'utilità della union? Qualche suggerimento?

Grazie

quindi intanto grazie delle risposte, e credo di potermi comportare in questa maniera:

trasmissione: mi creo una struttura, ad esempio con un inizio riga, 6 variabili int ed un fine riga. dichiaro i valori della struttura all'interno del loop invece che nel setup per ottenere i valori dell'analogico, la spedisco come hai fatto tu con serial.print sempre nel loop.

ricezione: inizio la lettura con serial.available()>0, ci metto un Read.bytesUntil inizio riga con un timeout (penso tu intendessi una cosa simile) poi faccio un serial.read () e mi leggo i byte e li inserisco nella struttura fino al byte di fineriga (questo però non ho capito come si fa). poi faccio un calcolo dei byte della struttura e solo se la lunghezza mi corrisponde a quella prefissata la "smonto" nelle variabili int di partenza.

vi sembra un modo ragionevole? vi ringrazio, non avete idea del tempo buttato con parse int e atoi e dei rallentamenti del loop a causa di una ricezione raffazzonata.

paolo

MauroTec: Non dimentichiamo che nel caso di stringhe C, queste devono avere il carattere terminatore '/0' per cui la dimensione imposta deve essere sempre +1 la dimensione in caratteri della stringa. la stringa "Pippo/0" deve stare in un array di 6 elementi.

In questi giorni ho effettuato varie prove con questo portentoso sistema e mi sono accorto che se trasferisco stringhe con terminatore \0 non funziona correttamente in quanto quando la union "mette" i dati nel buffer di char e si usa la serial.print per inviari questa trova lo \0 e li si ferma scartando di fatto tutto ciò che segue. Quindi io metto sempre i caratteri senza terminatore e in base a cosa mi torna meglio confronto dato[0]=='A' oppure copio i dati in un altro array con un for ad esempio (tanto conosco la sua dimensione massima) e aggiungo il terminatore \0 a mano

Ciao Fabrizio

@fabpolli Appunto print, stampa, mentre write scrive. Quindi al posto di print (visto che non devi stampare ma riempire il buffer) usa write.

Altrimenti ti perdi la semplicità di spedire e ricostruire il pacchetto. Quando vuoi stampare su display o serial monitor una stringa membro di struttura, userai il metodo print.

@signorbarro

vi sembra un modo ragionevole? vi ringrazio, non avete idea del tempo buttato con parse int e atoi e dei rallentamenti del loop a causa di una ricezione raffazzonata.

Sarebbe meglio postare un esempio di codice, perché nella descrizione ci sono troppi punti fraintendibili.

poi faccio un calcolo dei byte della struttura e solo se la lunghezza mi corrisponde a quella prefissata la "smonto" nelle variabili int di partenza.

Si, ma astro diceva che non sarebbe male aggiungere anche un checksum, cioè la somma di tutti i byte (valore) inviati deve essere la stessa in trasmissione e in ricezione, questo ci da una certa garanzia che ciò che è stato ricevuto è effettivamente ciò che è stato spedito.

Ciao.