Serial.ReadLine()

Salve ragazzi, ultimamente mi sto dando allo sviluppo di applicazioni in C# che mi permettano di scambiare dati fra arduino e pc.
Per l'invio di dati dall'arduino al pc è mooolto facile poichè utilizzo per ogni dato inviato la funzione Serial.println(); quindi sfrutto il fatto che l'arduino mette alla fine della stringa un carattere di andata a capo e così da C# mi basta: serialPort1.ReadLine();

Ma per inviare dati dal pc all'arduino la cosa è un pochino diversa! A parte il fatto che il C# non ha (o almeno credo) una funzione che inserisca un carattere di andata a capo (ma a questo si ovvia inserendolo manualmente), l'arduino non ha una funzione per leggere una riga quindi stavo pensando a una cosa del genere:

String readLine(){
  
  String datoricevuto = "";
  char c = '';
  
  while(Serial.available() > 0){
    c = Serial.read();

    if (c == '\n')
      break;

    else
      datoricevuto += c;
  }
  return datoricevuto;
}

'\n' E' il carattere di nuova riga? Correggetemi se sbaglio!

Ciao Uwe

uwefed:
Comandare arduino da tastiera - #6 by uwefed - Generale - Arduino Forum
Ciao Uwe

Ma io devo ricevere tre dati di fila inviati da C# sotto forma di stringa, stringa che dovrei poi convertire in int (ma come?);
Dovrei avere una cosa tipo:

void loop(){
  if (Serial.available() > 0){
    dato1 = (int)readLine();
    dato2 = (int)readLine();
    dato3 = (int)readLine();
  }
}

Solo che il compilatore mi da errore: invalid cast from type 'String' to type 'int'

per trasformare una stringa in int usa atoi()

il codice nel primo post è erroneo. Non hai considerato il fatto che non ci sono più dati nella seriale ( Serial.available()==0 ), ma non perchè la stringha è finita, ma perchè devono ancora finire di arrivare.

io metterei il Serial.available()==0 in un if, che se vero inizia il loop finché c != '\n'

il vero carattere di fine riga è il \0, ma non viene inviato via seriale quindi è comodo usare lo '\n', ovvero il "a capo"

lesto:
per trasformare una stringa in int usa atoi()

il codice nel primo post è erroneo. Non hai considerato il fatto che non ci sono più dati nella seriale ( Serial.available()==0 ), ma non perchè la stringha è finita, ma perchè devono ancora finire di arrivare.

io metterei il Serial.available()==0 in un if, che se vero inizia il loop finché c != '\n'

il vero carattere di fine riga è il \0, ma non viene inviato via seriale quindi è comodo usare lo '\n', ovvero il "a capo"

Ho appena scoperto che c'è una funzione in C# che scrive una linea: serialPort1.WriteLine(Strigna);
Quindi a me basterebbe leggere linea per linea.

Comunque non ho capito cosa intendi per errore.
Ecco tutto il codice:

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

String dato1 = "a";
String dato2 = "b";
String dato3 = "c";

void setup(){
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.print("Serial.ReadLine");
  delay(2000);
}

long wait = 0;
void loop(){
  if (Serial.available() > 0){
    dato1 = readLine();
    dato2 = readLine();
    dato3 = readLine();
  }
  
  if (wait < millis()){
    wait = millis() + 100;
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("D1: ");
    lcd.print(dato1);
    lcd.setCursor(9,0);
    lcd.print(" D2: ");
    lcd.print(dato2);
    lcd.setCursor(0,1);
    lcd.print("D3: ");
    lcd.print(dato3);
    }
}

String readLine(){

  String datoricevuto = "";
  char c = 0;

  while(Serial.available() > 0){
    c = Serial.read();

    if (c == '\n')
  return datoricevuto;

    else
      datoricevuto += c;
  }
}

mettiamo che dal tuo pc scrivi il numero 590. Sono 4 caratteri, il 5, il 9, lo 0, più il carattere di a capo (abbiamo deciso lo '\n')

quando entri nel while, però, ci entri proprio quando il PC sta inviando i dati. per ora sono arruvati solo i primi 2 caratteri, il 5 e il 9, fai 2 volte il while, dopodiché la serial.available ritorna 0(anche se devono in realtà arrivare ancora 2 dati).. la return ritorna la stringa "59", che viene interpretata appunto come 59, ben lontano dal reale numero 590 che abbiamo inviato!!!

Come puoi notare non è un errore sintattico (ovvero un'errore di cui il compilatore si accorge), ma un errore logico.

lesto:
mettiamo che dal tuo pc scrivi il numero 590. Sono 4 caratteri, il 5, il 9, lo 0, più il carattere di a capo (abbiamo deciso lo '\n')

quando entri nel while, però, ci entri proprio quando il PC sta inviando i dati. per ora sono arruvati solo i primi 2 caratteri, il 5 e il 9, fai 2 volte il while, dopodiché la serial.available ritorna 0(anche se devono in realtà arrivare ancora 2 dati).. la return ritorna la stringa "59", che viene interpretata appunto come 59, ben lontano dal reale numero 590 che abbiamo inviato!!!

Come puoi notare non è un errore sintattico (ovvero un'errore di cui il compilatore si accorge), ma un errore logico.

Non si può ovviare inserendo un delay di qualche millisecondo ad ogni ciclo di while?
E poi una domanda, quanti dati possono entrare nel buffer seriale?

il buffer seriale mi pare sia 250 byte, lo puoi verificare nella libreria di sistema Serial.
sì, puoi rimediare con una delay, ma la sua durata dipende dal baud rate scelto se il baud è 9600, vuol dire che in 1/9600 di secondo ti arriva un dato, sembra veloce, ma l'arduino esegue un clock ogni 1/20000000 di secondo...

lesto:
il buffer seriale mi pare sia 250 byte, lo puoi verificare nella libreria di sistema Serial.
sì, puoi rimediare con una delay, ma la sua durata dipende dal baud rate scelto se il baud è 9600, vuol dire che in 1/9600 di secondo ti arriva un dato, sembra veloce, ma l'arduino esegue un clock ogni 1/20000000 di secondo...

Quindi cosa faccio?
Aumento il baudrate o inserisco un delay di circa 100 ms ogni ciclo?
Oppure entrambi?

E poi non dovrebbe essere un clock di 1/16000000 per l'arduino?

si per il clock hai ragione, per il resto io farei così:
ogni volta che finisce il while metti una delaymicros di (1000000/baudrate) MICRO secondi.
così puoi mettere anche il baudrate a 1 e non vere problemi :slight_smile:

lesto:
si per il clock hai ragione, per il resto io farei così:
ogni volta che finisce il while metti una delaymicros di (1000000/baudrate) MICRO secondi.
così puoi mettere anche il baudrate a 1 e non vere problemi :slight_smile:

Scusami, ma non ne capisco il motivo!
E poi dovrei inserirlo ad ogni ciclo o appena terminato il ciclo?

lo devi mettere ad ogni ciclo, ed in pratica ti fa "delaiare" per esattamente i microsecondi necessari per ricevere un nuovo dato.
Anzi ancora meglio, la delay la metti ogni loop ma solo ce non ci sono più dati (available==0): quindi in pratica aspetti il tempo necessario per un altro dato solo se effettivamente non ci sono più dati in coda. A questo punto il while ricomincia, e se è arrivato un dato prosegue nel suo loop, altrimenti vuol dire che effettivamente non ci sono più dati ed esce...

Però comunque, visto che sai che l'ultimo carattere è lo '\n' secondo me la cosa migliore non è questa, ma ogni loop tu leggi i caratteri disponibili e li metti in una stringa. La stringa non viene usata dal resto del programma finché non leggi il carattere '\n'. Quindi per mettere una stringa sarà possibile metterci più loop.

Questro approccio è migliore, perché se il baud rate è lento o se la fonte trasmissiva tra un baud e l'altro fa altre operazioni (possibile) il programma non si impalla nè perde dati.

In pratica dici che anzichè bloccarlo in un ciclo aspettando di ricevere il carattere '\n' lui continua a fare quello che deve fare e poi a ogni giro di loop va a controllare se ci sono altri caratteri disponibili?

esatto, quando trova il carattere '\n' considera la stringa completa e la passa al resto del programma per essere utilizzata come meglio credi.

edit: sarebbe da modificare la Serial per aggiunge una readLine(), è una funzione che si usa spesso e incasina un poco i nuovi arrivati.

lesto:
esatto, quando trova il carattere '\n' considera la stringa completa e la passa al resto del programma per essere utilizzata come meglio credi.

edit: sarebbe da modificare la Serial per aggiunge una readLine(), è una funzione che si usa spesso e incasina un poco i nuovi arrivati.

Hai ragione... Appena finirò di sistemare e vedere se funziona magari ci penso a farlo :slight_smile:
Comunque come potrei fare? Perchè nel loop io leggo una riga dopo l'altra:

void loop(){
  if (Serial.available() > 0){
    dato1 = readLine();
    dato2 = readLine();
    dato3 = readLine();
  }
}

Come faccio ad andare avanti nel loop? Cioè, se va avanti prova a leggere l'altra linea!
Insomma a me serve una funzione che appena chiamata mi ritorna quella linea!

Potrei decidere di seguire un'altra strada! Potrei inviare i dati tutti insieme separati da un carattere come per esempio '|' (come fatto qui: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1273637601) e poi leggerli spezzando la stringa con "String.split" (ammesso che esista questo comando nel codice dell'arduino).

il problema rimane. Allora, prendi esempio dalla read(): se non c'è alcun dato disponibile ritorna "-1". ovviamente tu il -1 non puoi ritornarlo perchè è una stringa valida, ma puoi ritornare il puntatore alla stringa con valore NULL (ovvero non sta puntando a nulla, è un valore che devi assegnare a mano)

Tu metti data1, data2 e data3 a NULL, a questo punto tu leggi data1, data2 e data3(saltando quelli NON NULL, anche se per ora non ci sono). Se anche uno solo dei data rimane NULL vai avanti col programma come se non avessi letto nessun valore, ovviamente tenendo memorizzati i data che ti sono arrivati.

come puoi notare, se al prico loop hai letto data1 e data2, essi non saranno più null, e quindi passerai direttamente a leggere data3 (che è NULL). Se la lettura è andata a buon fine usi i data all'interno del tuo programma, e poi li rimetti tutti a NULL.

et voilà il gioco è fatto

lesto:
il problema rimane. Allora, prendi esempio dalla read(): se non c'è alcun dato disponibile ritorna "-1". ovviamente tu il -1 non puoi ritornarlo perchè è una stringa valida, ma puoi ritornare il puntatore alla stringa con valore NULL (ovvero non sta puntando a nulla, è un valore che devi assegnare a mano)

Tu metti data1, data2 e data3 a NULL, a questo punto tu leggi data1, data2 e data3(saltando quelli NON NULL, anche se per ora non ci sono). Se anche uno solo dei data rimane NULL vai avanti col programma come se non avessi letto nessun valore, ovviamente tenendo memorizzati i data che ti sono arrivati.

come puoi notare, se al prico loop hai letto data1 e data2, essi non saranno più null, e quindi passerai direttamente a leggere data3 (che è NULL). Se la lettura è andata a buon fine usi i data all'interno del tuo programma, e poi li rimetti tutti a NULL.

et voilà il gioco è fatto

Non sono molto abile con i puntatori!
E non riesco a trovare il modo di farlo!
Ma comunque a me non importa che il loop venga ripetuto mentre aspetto i dati, posso anche non fare niente!
Mi sembra quindi più semplice ricevere tutta una stringa e splittarla! Ma come faccio a splittare una strigna?

per slittarla ci sono varie funzioni di split già precotte nell'oggetto string, oppure lo fai "a mano" scorrendo l'array di caratteri e tirando fuori quello che ti serve.

se non ti importa di aspettare, allora la soluzione migliore è di mettere
carattereLetto='';
while (carattereLetto!='\n'){
//legge un carattere
carattereLetto=Serial.read();
if (carattereLetto!=-1){
stringa+=carattereLetto;
}
}

lesto:
per slittarla ci sono varie funzioni di split già precotte nell'oggetto string

Per esempio?

il reference ragazzi, il reference!