Problema splittare una stringa letta dalla seriale.

Buongiorno ragazzi.
Sto cercando di realizzare una comunicazione Visual Basic 2010 con un arduino master (arduino mega) e 3 arduino slave (arduino uno). Dunque girando in internet e nel forum ho trovato un pò di discussioni sul mio problema, ma le soluzioni trovate o non funzionano o non soddisfano il mio problema.
Io invio una stringa da Visual Basic ad esempio: codice= {from, to, azione, comando}. La stinga di nome codice, non avendo ancora un protocollo realizzato posso dichiararla di qualsiasi tipo, string, byte, int ecc.... (poi sarò un problema di Visual Basic come inviare). Anche la lunghezza e come deve essere composta posso modificarle.
Una volta inviato via seriale la stringa codice nella forma dell'esempio, dovrei farla leggere da arduino sulla seriale e associare a delle variabili i comandi della stringa. Esempio:
variabile A= from;
variabile B= to ;
variabile C= azione;
variabile D= comando.
Anche qua non avendo ancora un protocollo realizzato le 4 variabili possono essere di qualsiasi tipo, la terza variabile preferibilmente byte.
So che parecchi hanno provato e risolto questo problema ma io ancora niente. Tra i codici trovati in rete questo è quello che un pò fa quello che chiedo, ma non fà quello e come dico io.

int c = 0;
int a = 0;
int var = 0;
int variabile[] ={0,0,0,0};

void setup(){
Serial.begin(9600);
}

void loop(){
   if (Serial.available()>0){
     c= Serial.read();
     
     
     if (c == ':'){
       var=1;
     }

        
     if (c == ','){//con questo blocchetto mi leggo il caso 2 se trovo una virgola
       var =2;
       }

     if (c == '#'){//con questo blocchetto mi leggo il caso 3 se trovo il #
       var =3;
       }
     switch (var) {
       
       case 1:
       
       delay (10);
       //di questa forse ne potrei fare a meno, ma fa tanta compagnia
       a = 0;
       break;
       
       case 2:
       
       delay (10);
       
       //incomincio a "infilare" le mie variabili famose nell'array
       variabile[a] = Serial.read();
       a++;
       break;
       
       case 3://se la stringa viene chiusa con # mi comincio a leggere le mie variabili
              
       //giusto per vedere se le mie variabili sono corrette
       Serial.println(variabile[0]);
       Serial.println(variabile[1]);
       Serial.println(variabile[2]);
       Serial.println(variabile[3]);
       
       //riporto le mie variabili al valore di partenza
       a = 0;
       c = 0;
       var = 0;
       break;
       
       //default:
       // if nothing else matches, do the default
       // default is optional
       }
     }
   }

Qualcuno può aiutarmi?
Ho anche provato a usare il comando split, ma poi non so come associare alle 4 variabili il proprio valore.
Il codice sopra postato è solo un esempio, posso tranquillamente asfaltarlo tutto.
Grazie

Il mio suggerimento è di ricevere tutta la stringa di char in un unico buffer grande il massimo + 1 (per 0x00 terminatore di stringa) e poi usare le apposite funzioni che mette a disposizione la AVR libc per il trattamento di stringhe (in particolare la string.h).

Ad esempio, tu ricevi la stringa (intesa sempre com char array) : "FFFFFF,TTT,AAA,CCCC" (FFFFFF = from, TTT = to, AAA = azione, CCCC = comando), semplicemente usando la strtok_r dicendogli che le varie parti sono separata da " , " e chiamandola più volte ... hai le tue varie parti separate :wink:

Guglielmo

Ciao Guglielmo, grazie per l'interessamento.
Scusa ma sono un pò acerbo sulla programmazione e non so molto su come manipolare le stringhe. Quindi inanzitutto dovrei usare la libreria string (#include <string.h> ), poi utilizzare un ciclo for per assegnare alle 4 variabili il valore proveniente dalla lettura della seriale?
Il comando strtok_r andrebbe utilizzato per splittare la stringa letta?
Grazie.

Ciao Ale, scusa la mia ignoranza. Dovrei fare una cosa del genere?

char c = 0;
String s = 0;
String from;
String to;
String comando;
String azione;

void setup(){
  Serial.begin(9600);
}

void loop(){
 String buffer = String();
 while(Serial.available()){
   c= Serial.read();
    if(buffer !="#"){
            buffer += ",";
    }
 }
String subString = String(); 
// con limite inferiore e superiore
from = s.substring(0,3);
to = s.substring(4,8);
comando = s.substring(5,7);
azione = s.substring(8,13);
}

Sono alle prime armi, scusa se ho scritto un codice ineseguibile.
Grazie

Come detto da @Guglielmo, usa le stringhe (vettori di char terminati da null ovvero '\0') ed evita se puoi la classe String. Arrivando da VB la classe String sembra più semplice ma NON è come in VB.
La cosa migliore è organizzare un protocollo da VB con campi a lunghezza fissa. Da VB è semplice e in Arduino ti semplifica il dove cercare le informazioni. Ad esempio quando @Guglielmo suggerisce "FFFFFF = from" io specifico che è meglio se FFFFF è a lunghezza fissa ovvero se devi spedire "123" lo spedisci sempre lungo 6 ad esempio "000123". In Arduino (C) sarà più semplice gestire i pezzi. FFFFFF,TTT,AAA,CCCC# => 000123,002,567,0078# sempre a lunghezza fissa, sa sempre che la seconda cifra parte dal 7° carattere. Magari aggiungi alla fine un bel carattere di fine trasmissione tipo #

@ Ale_Carsa : Il peggior consiglio che potessi dare ... XD

NON siete su un PC dove c'è un sistema operativo ed un "garbage collector", siete su una piccola MCU con solo 2KBytes di SRAM, dove dovete fare tutto voi e dove usare la classe "String", a causa dell'allocazione e riallocazione dinamica della memoria, porta quasi sempre ... a sicuri problemi ! ]:smiley:

Imparate ad usare le stringhe classiche del C ... ovvero semplici array di char terminati dal carattere null (0x00) e, come detto, le funzioni che trovate nella libreria standard (... che, oltretutto, è automaticamente inclusa dal IDE) AVR libc.

Guglielmo

@ adamo : se guardi la funzione strtok_r vedi che ogni volta che la chiami, cerca il separatore che gli hai indicato e ti ritorna o il "token" (ovvero il pezzo cercato) che ha trovato o puntatore nullo ad indicare che non c'è più nulla da ritornare.

Se ti sembra troppo complessa (... occorre almeno sapere cosa è un puntatore :grin:), allora definisci un tracciato fisso (quindi con le varie parti d lunghezza fissa e predeterminata) e fai come ti ha spiegato Nid o segui la strata di Pablos :wink:

Guglielmo

Ragazzi grazie per i suggerimenti. Ho cercato di capire un pò i vari comandi suggeriti e pistolando un pò e con qualcosa trovata sul forum sono riuscito a scrivere questo

#include <string.h>

  char miaStringa[] = ",AAAAA,BBBBB,CCCCC";
  char separatore[] = ",";
  char* valoreVariabile;
  
  void setup(){
  valoreVariabile = strtok(miaStringa, separatore);

  Serial.begin(9600);
 
  while(valoreVariabile != NULL){
    Serial.println(valoreVariabile);
    valoreVariabile = strtok(NULL, separatore);
  } 
}

void loop(){

 if (Serial.available()>0){
     
 }
}

Dunque non mi è ben chiara come utilizzare in pratica la funzione strtok_r, ma credo che la funzione strtok può andare bene.
dalla stringa ",AAAAA,BBBBB,CCCCC" riesco ad ottenere
AAAAA
BBBBB
CCCCC

a questo punto ho ancora due problemi, il primo come fare ad associare alle mie variabili il valore "valoreVariabile". Ho provato a usare un ciclo for, ma senza risultati.
Il secondo problema è che adesso utilizzo char miaStringa[] = ",AAAAA,BBBBB,CCCCC" quindi ho già una stringa pronta, quando invece vado a inserire

void loop(){


 String buffer = String();
 while(Serial.available()){
   miaStringa= Serial.read();
 }
}

mi dà errore perchè incompatible types in assignment of 'int' to 'char. Come faccio a convertire la lettura della seriare in char?

Grazie a tutti

Per rispondere alla tua prima domanda, guarda, sempre in AVR libc, la funzione strcpy() che serve a copiare una stringa di caratteri in una altra (quindi il risultato della strtok_r in una tua variabile stringa di char).

Per il secondo problema ...
... come t'ho detto NON devi usare la classe String ma le stringhe come array di char e invece ... tu definisci :

String buffer = String();

... ovvero mi vai a usare le String ]:smiley: ]:smiley: ]:smiley:

Purtroppo ... le cose tocca studiarle prima di usarle ... XD XD XD

Guglielmo

Ciao ragazzi, rieccomi.
Tengo a precisare che uso arduino e sto cercando di imparare C per hobby. Copiare un codice da internet non ha senso. Se non capisco com'è fatto e se non capisco perchè è stato fatto in quel modo non mi serve a nulla, primo perchè non posso adattarlo alle mie esigenze e poi perchè sarà impossibile per me modificarlo. Ovvio che sto cercando di studiarle le cose, capirle però non mi è semplice.

Ringrazio Guglielmo per le dritte che mi hai dato e per avermi introdotto alla libreria AVR libc, non ne ero a conoscevo.
Per mancanza di tempo mi ero bloccato, ma alla fine sono riuscito ad arrivare dove volevo arrivare. Sicuramente non è il miglior codice esistente, ma fà quello che deve fare, almeno credo.

Ho un problema: quando invio la prima stringa di caratteri il codice viene eseguito e mi stampa "OK" se la stringa inviata è corretta. Se poi invio un'altra stringa, il codice non viene più eseguito.
Posto il codice:

#include <string.h>
char miaStringa_1[10];
char* dest[]={"xxxxx","yyyyyy","zzzzzz","wwwwww","eeeeee"};
int a=0;
int s=0;

void setup(){
  Serial.begin(9600);
}

void loop(){
  if(Serial.available()>0){
    miaStringa_1[s]=Serial.read();
    if(miaStringa_1[s]!=NULL){
      s++;
    }
    if (s==10){
      s=0;
      spezzaStringa();
      a++;
      if((String)dest[0]=="mast"){
        if((String)dest[1]=="slav1"){
          Serial.print("OK");
        }
      }
    }
  }
}

void spezzaStringa(){
  char separatore[] = ",";
  char* valoreVariabile;
  int len=0;
  for(int y=0;y<1;y++){
    valoreVariabile = strtok(miaStringa_1, separatore);
    strcpy(dest[len], valoreVariabile);
    while(valoreVariabile != NULL){
      //Serial.println(dest[len]);
      len++;
      valoreVariabile = strtok(NULL, separatore);
      strcpy(dest[len], valoreVariabile);
    }
  }
}

Ho provato ad azzerare la variabile s (con un s=0) prima di richiamare la funzione e anche in altre parti del codice, ma non ho avuto buoni risultati.

Qualcuno mi può dare una dritta su dove sto sbaglio?
Inoltre il codice è troppo incasinato o si può semplificare con l'aggiunta di qualchee comando che non conosco?

Grazie

Allora ... ti manca ancora un po' d'allenamento con le funzioni di <string.h> :grin: :grin: :grin:

Quello che ti riporto è un esempio di come correttamente si usa la funzione :

char * 	strtok_r (char *, const char *, char **)

strtok_r parses string into tokens. The first call to strtok_r should have string as its first argument. Subsequent calls should have the first argument set to NULL. If a token ends with a delimiter, this delimiting character is overwritten with a '\0' and a pointer to the next character is saved for the next call to strtok_r. The delimiter string delim may be different for each call. last is a user allocated char* pointer. It must be the same while parsing the same string. strtok_r is a reentrant version of strtok().

... esempio che potrai usare tale e quale, dato che suddivido una stringa composta di quattro parti, separate dal carattere "," in 4 stringhe che metto in un array di stringhe :wink:

#include <string.h>

char stringaDaDividere[] = "Primo elemento,Secondo elemeto,Terzo elemento,Quarto elemento";
char elemento[4][20];
byte i;

char* elementoSeparatore  = ",";
char* pElemento           = NULL;
char* pProssimoElemento   = NULL;

void setup() {
   // solo per precauzione per eventuali problemi con il bootloader inserisco un delay iniziale
   delay(1000);
   //
   Serial.begin(9600);
   Serial.print(F("Stringa da suddividere: "));
   Serial.println(stringaDaDividere);
   Serial.print(F("Carattere separatore: "));
   Serial.println(elementoSeparatore);
   Serial.println();
   //
   // recupera il primo elemento di stringaDaDividere ...
   pElemento = strtok_r(stringaDaDividere, elementoSeparatore, &pProssimoElemento);
   strcpy(elemento[0], pElemento);
   //
   // ... e poi recupera i successivi (nota il NULL al posto di stringaDaDividere).
   for (i=1; i<4; i++) {
     pElemento = strtok_r(NULL, elementoSeparatore, &pProssimoElemento);
     strcpy(elemento[i], pElemento);
   }
   //
   // Stampa gli elementi
   Serial.println(F("Elementi separati ..."));
   for (i=0; i<4; i++) {
      Serial.print(F("   elemento "));
      Serial.print((i+1), DEC);
      Serial.print(F(": "));
      Serial.println(elemento[i]);
   }
   //
   // Finito
}

void loop() {

}

Eseguilo e vedrai che in elemento[0], elemento[1], elemento[2], elemento[3] troverai le quattro parti di cui è composta l'unica stringa: stringaDaDividere.

Nella dichiarazione di elemento :

char elemento[4][20];

... ho previsto solo 4 stringhe lunghe al massimo 19 caratteri (+ lo 0x00 terminatore). Chiaramente questo array può essere modificato secondo le proprie esigenze (... così come il carattere separatore).

Buono studio ...

Guglielmo

P.S. : Sembra lungo perché ci sono i commenti e le Serial.print() ... ma in realtà sono veramente poche righe :wink:

Tutto OK,
Vorrei solo spezzare una lancia in favore della String arduinica. È maturata e specialmente per progetti semplici dove non si sta a guardare la pesantezza o la velocità di esecuzione può essere tranquillamente usata. Magari per sicurezza usare l IDE 1.5.x invece del 1.0.x in modo da essere sicuri di usare l'ultimo aggiornamento di strong.
Nel reference ufficiale trovi tutti gli strumenti per ricevere stringhe, tagliare, contare caratteri ecc.
Questi sono i comandi a disposizione String() - Arduino Reference

Testato:
Vorrei solo spezzare una lancia in favore della String arduinica. È maturata e specialmente per progetti semplici dove non si sta a guardare la pesantezza o la velocità di esecuzione può essere tranquillamente usata.

... "tranquillamente usata" mi sembra veramente una affermazione azzardata e, se Astro (... sempre super impegnato) avesse ancora tempo di passare da questi lidi, probabilmente ti metterebbe al rogo per una cosa del genere !

NON sei su un PC dove c'è un sistema operativo ed un "garbage collector", sei su una piccola MCU con solo 2KBytes di SRAM, dove devi fare tutto tu e ... la classe "String", comunque usa allocazione e riallocazione dinamica della memoria, cosa che ... sappiamo tutti, quali effetti può avere. ]:smiley: ]:smiley: ]:smiley:

Testato:
...
Nel reference ufficiale trovi tutti gli strumenti per ricevere stringhe, tagliare, contare caratteri ecc.
...

... appunto ... tutti gli strumenti atti a ... massacrare la memoria ]:smiley: ]:smiley: ]:smiley:

Poi, naturalmente, uno è liberissimo di farsi del male come vuole ... per carità XD XD XD

Guglielmo

Andando su questa strada c'è da sconsigliare l'intero wiring non solo string :stuck_out_tongue_closed_eyes:
Arduino è principalmente c++ librerie incluse non si usa per l'efficienza ma per imparare facilmente.
In tale ottica anche string è bene imparare ad usare. Io ogniqualvolta che l'ho usata ha funzionato.

Testato:
Andando su questa strada c'è da sconsigliare l'intero wiring non solo string :stuck_out_tongue_closed_eyes:
Arduino è principalmente c++ librerie incluse non si usa per l'efficienza ma per imparare facilmente.

Scusa ma che c'entra ??? :astonished: :astonished: :astonished:

Io sto parlando di malloc(), realloc(), free(), ... ovvero di tutta la gestione della memoria che è costretta a fare una classe come String ogni volta che tu riassegni una stringa aumentandone le dimensioni ... non di efficienza o quant'altro ! ]:smiley:

Guglielmo

Le quali funzioni sono state gestite e testate dal team e funzionano. Quindi di efficienza stai parlando.
Guarda che io sono pienamente d'accordo con te, se leggi il mio primo post inizio con Tutto OK, mica dico che stai sostenendo cose sbagliate. Dopodiché dico che per una stringa minima in un progetto non esodo di richieste ci sta che si possa usare/imparare anche la strong ufficiale arduinica.
Io la uso in un progetto che come core ha la gestione di una stringa e gestendo bene lgs cancellazione dello spazio allocato non ho problemi.
Sto solo ammorbidendo le posizioni verso una classe che spesso viene proprio vietata, come se non funzionasse leggendo i consigli.

Testato:
...
Sto solo ammorbidendo le posizioni verso una classe che spesso viene proprio vietata, come se non funzionasse leggendo i consigli.

Mai detto che non funziona ... sempre detto di NON usarla perché è facile che causi problemi ... e continuerò a ripeterlo all'infinito.

Tu poi resta ovviamente della tua opinione e naturalmente usa ciò che più gradisci ... ]:smiley:

Guglielmo

Pace :slight_smile:

Ciao Guglielmo,
grazie per il codice di esempio. Non l'ho ancora provato.

Volevo intanto chiederti se puoi spiegarmi questa cosa:
Serial.println(F("Elementi separati ..."));

So benissimo che serve a stampare "Elementi separati ...", ma non ho capito a cosa serve la "F".
Ho provato a cercare e mi sembra di aver capito che sia una funzione forse per risparmiare RAM, corretto?
Non sono riuscito a trovare una spiegazione chiara.

Grazie

... emmm ... adamo ... bastava fare COSÌ ... per trovare subito tutte le spiegazioni :grin:

Dal primo risultato riporto :

Version 1.0 of the Arduino IDE introduced the F() syntax for storing strings in flash memory rather than RAM.

... in pratica, invece di copiare le stringhe in SRAM prima di usarle, le usa direttamente dalla memoria Flash risparmiando preziosa SRAM.

Ovviamente, puoi farlo solo con stringhe costanti, non con le variabili.

Guglielmo