[RISOLTO] Problema stringhe via seriale "troncate"

Salve, ho un problema molto strano e particolare, che non sono riuscito a capire per quale motivo si presenta.

Dopo molti test sono riuscito ad isolare il problema, e quindi tramite poche righe di codice posso sottoporvelo e dirvi come poterlo ricreare.

Praticamente ho un'applicazione in python che deve inviare ad un arduino nano (atmega 328@16Mhz) delle stringhe di numeri separati tra di loro da un accapo. Lo sketch sull'arduino si occupa di separare i numeri dall'accapo ed usarli per fare delle operazioni che non sto qui a spiegare perchè non riguardano il problema. Se la stringa di numeri da inviare all'arduino è "abbastanza lunga" (si parla di appena 70-90 caratteri circa, non di cifre eccessivamente alte), e le operazioni che faccio sui dati nella funzione loop() richiedono più di 100-200ms, la stringa viene misteriosamente troncata dopo un tot, con conseguente perdita dei dati sucessivi.

Ecco il codice python sul pc:

import serial

ser = serial.Serial("/dev/ttyUSB0", 9600)  # open first serial port

ser.write("545\n546\n547\n548\n801\n802\n803\n804\n1057\n1058\n1059\n1060\n1313\n1314\n1315\n1316\n1569\n1570\n1571\n1572\n1825\n1826\n1827\n1828\n")      # write a string

output=""

while (1):
    x = ser.read()          # read one byte
    if (x == "\n"):
        print output
        output = ""
    else:
        output += x

In ser.write() c'è un esempio di stringa che provo ad inviare all'arduino. Ora il codice sull'arduino:

String inputString = "";         // a string to hold incoming data

unsigned long inputnumber;

unsigned long time;

int ardNumber;


void setup() {

  Serial.begin(9600);
  inputString.reserve(600);

  Serial.print("Pronto\n");
}




void loop() {
  time = millis();

  char convertString[inputString.length()+1];
  inputString.toCharArray(convertString, sizeof(convertString));

  char *ultimoAccapo = NULL;
  char *commands;
  char *result = NULL;
  char delims[] = "\n";

  ultimoAccapo = strrchr (convertString, (int)'\n');

  if (ultimoAccapo != NULL){
    if (convertString+strlen(convertString) > ultimoAccapo+1) {
      int command_size = ultimoAccapo-convertString+1;
      commands=(char*)malloc(command_size);
      strncpy(commands,convertString,command_size);
      commands[command_size-1]='\0';
      

      inputString = inputString.substring(command_size-1);
    } 
    else {
      inputString = "";
      commands=convertString;
    }

    result = strtok( commands, delims );

    while( result != NULL ) {

      inputnumber = atoi(result);

[b]      Serial.println(inputnumber);
          delay(300);[/b]


      result = strtok( NULL, delims );
    }

  }

}


void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read(); 
    inputString += inChar;
  }
}

Praticamente in loop il codice ha il compito di prendere la stringa che arriva dalla seriale e splittare i singoli numeri secondo i "\n". In grassetto, all'interno del while, c'è la parte della mia applicazione dove prendo uno ad uno i numeri arrivati dalla seriale e li elaboro. e l'elaborazione richiede per ogni numero, un tempo variabile, da 0 a 700ms. Ricordo che questo è un demo per ricreare l'errore, perchè altrimenti il codice sarebbe stato troppo complesso da leggere e capire, non è la mia vera applicazione. Il delay ha lo scopo di simulare la perdita di tempo dell'ellaborazione dei dati. E qui c'è la stranezza: se quel delay è di 100ms o inferiore, l'arduino riesce a stamparmi correttamente tutti i numeri che gli arrivano dalla seriale. Se invece il delay supera i 200 ms, si blocca attorno a metà della stringa.

La mia sensazione è come se si saturasse il buffer, ma se non erro il buffer dell'arduino nano dovrebbe aggirarsi attorno ai 4kb, possibile che sia già pieno con solo così pochi dati? Sbaglio qualcosa nella gestione del flusso dalla seriale? C'è qualche timeout/flow controll o simile da togliere/impostare?

Grazie in anticipo.

Non ho guardato il codice (a proposito, il grassetto non funziona all’interno dei tag code) ma se mi parli di grosse sequenze spedite e di lunghi tempi di elaborazione il problema potrebbe risiedere nei buffer seriali dell’Arduino.
L’Arduino ha 2 buffer, 1 in ricezione ed 1 in trasmissione, di 64 byte l’uno. Se spedisci 70/90 byte per volta e quelli che arrivano non li prelevi prima che riempiano il buffer RX, poi si sovrappongono.

Hai 2 modi per tentare di risolvere: aumentare il buffer editando il file CDC.cpp del core oppure svuotando il buffer in un array tampone e poi da lì elaborare i dati.

Ah! Pensavo ce il buffer fosse più grande… Mi sa che hai beccato il problema al volo…

Per risovere io aumenterei il buffer nel file CDC.cpp, mi pare la via più rapida, così su due piedi. Ma quali problematiche può portare un aumento del buffer?
Invece per quanto riguarda la soluzione con un array tampone, in pratica dovrei cercare di dare più priorità possibile allo “svuotamento” della seriale durante l’esecuzione del codice, evitando di stare troppo tempo senza controllare se sono arrivati nuovi dati, giusto? Però questa strada l’avevo già provata, e per quello che ho da fare non riesco a fare dei controlli molto spesso.

Grazie mille per la risposta! :slight_smile:

Allora, ho provato a modificare il file /usr/share/arduino/hardware/arduino/cores/arduino/CDC.cpp (sono su linux) in questa maniera:

#if (RAMEND < 1000)
#define SERIAL_BUFFER_SIZE 16
#else
#define SERIAL_BUFFER_SIZE 200
#endif

Ho anche riavviato l’IDE dopo aver salvato la modifica, ma il comportamento dello sketch è il medesimo. Sbaglio qualcosa?

michelef87: Ah! Pensavo ce il buffer fosse più grande... Mi sa che hai beccato il problema al volo...

Per risovere io aumenterei il buffer nel file CDC.cpp, mi pare la via più rapida, così su due piedi. Ma quali problematiche può portare un aumento del buffer?

Fino a poco tempo fa i buffer erano di 128 byte l'uno, poi recentemente sono stati ridimensionati a 64 cad. Questo per impattare meno sull'impiego di memoria RAM del micro, che sugli Atmega328 sono solo 2048 byte. Quindi il problema potrebbe nascere se il tuo sketch impegna molte risorse.

michelef87: Ho anche riavviato l'IDE dopo aver salvato la modifica, ma il comportamento dello sketch è il medesimo. Sbaglio qualcosa?

Hai provato a togliere quel delay(300) per vedere se il problema sparisce?

aumentare il buffer di contro ti diminuisce la RAM disponibile.

Notare che ripempire il buffer TX non crea casini, la la Serial.print rimane bloccata finchè non c'è abbastanza spazio disponibile.

fai attenzione: serialEvent() in realtà è chiatata ad ogni fine loop(), quindi la durata del loop influenza il suo funzionamento. potresti chimarla ogni tanto dal loop(), prima e dopo e magari anche durante operazioni onerose in termini di tempo...

leo72:

michelef87: Ho anche riavviato l'IDE dopo aver salvato la modifica, ma il comportamento dello sketch è il medesimo. Sbaglio qualcosa?

Hai provato a togliere quel delay(300) per vedere se il problema sparisce?

SI, se tolgo il delay(300) sparisce, o anche se lo calo sotto a 100ms, riesce a catturare tutti i dati in arrivo ed a stamparli correttamente. Però quel delay vorrebbe "simulare" la perdita di tempo del mio codice.

@lesto: il buffer che si riempie è quello in entrata, non in uscita. SI, sapevo che serialEvent() viene chiamata in modo alternato dopo loop(), in effetti potrei fare anche come dici te, cioè chiamarla ogni tanto anche da loop.

altrimenti potresti usare un timer interrupt.. se conosci il tuo baud-rate, conosci il tempo impiegato per riempirsi. Setti un timer che ogni metà o poco più di quetso tempo si sveglia e lancia la funzione serialEvent.

Dato che la serialEvent copia i dati da un buffer alla tua stringa, e quindi a tutti gli effetti è una copia di array, la cosa è abbastanza veloce e non dovrebbe essere assolutamente problematica lato tempistica (gli interrupt devono essere il più snelli possibile).

Se poi modifichi la libreria HardwareSerial per ritornare direttamente tutti i dati available in un colpo sono invece che fare X chiamate a read, allora ottimizzi il codice al massimo.

Poichè lavorare con i timer è un bel casino se sei nuovo, il buon leo72 ha nella firma dei link ai suoi lavori tra cui una libreria che fa giusto quello che ci interessa. (LeOS 2). Leggiti per bene tutto l'articolo però prima di fare altre domande! http://www.leonardomiliani.com/2012/leos-un-semplice-so-per-arduino/

michelef87: SI, se tolgo il delay(300) sparisce, o anche se lo calo sotto a 100ms, riesce a catturare tutti i dati in arrivo ed a stamparli correttamente. Però quel delay vorrebbe "simulare" la perdita di tempo del mio codice.

Se il tuo codice perdesse 100 ms ad ogni ciclo per analizzare delle stringhe vorrebbe dire che c'è qualcosa che non va. Secondo me è un tempo esageratamente lungo.

Allora, googlando un po’ ho trovato che anche in HardwareSerial.cpp ci sono dei parametri riguardanti il buffer seriale, e modificando quelli tutto funziona! :slight_smile:

#if (RAMEND < 1000)
  #define SERIAL_BUFFER_SIZE 16
#else
  #define SERIAL_BUFFER_SIZE 200
#endif

Praticamente devo solo star attento a non sforare i 2kb di memoria, sommando sia la dimensione del buffer, sia tutte le variabili che tengo in memoria, se non ho capito male. Per sicurezza magari controllo la memoria libera con freeMemory().

leo72:

michelef87: SI, se tolgo il delay(300) sparisce, o anche se lo calo sotto a 100ms, riesce a catturare tutti i dati in arrivo ed a stamparli correttamente. Però quel delay vorrebbe "simulare" la perdita di tempo del mio codice.

Se il tuo codice perdesse 100 ms ad ogni ciclo per analizzare delle stringhe vorrebbe dire che c'è qualcosa che non va. Secondo me è un tempo esageratamente lungo.

E' molto rapido ad analizzare le stringhe, il problema è che devo anche spedire i dati, e se tutto va bene (cioè il segnale è buono, ecc ecc) è molto rapido, e non ci sono problemi. Ma se il ricevitore è spento o lento a rispondere, entrano in gioco delle latenze derivate dal protocollo di trasmissione, che portano a dei ritardi che si accumulano, ed è per quello che arrivo ad un "delay" così alto. Tuttavia è un caso limite che non è detto che si verifichi, ma preferisco prevedere le situazioni peggiori, in modo che non capitino problemi.

@lesto: grazie mille per le dritte, anche se ho risolto aumentando il buffer, sono comunque soluzioni interessanti per altre applicazioni, ci darò comunque un'occhiata!

Grazie mille a tutti per le risposte! :)

michelef87:
Allora, googlando un po’ ho trovato che anche in HardwareSerial.cpp ci sono dei parametri riguardanti il buffer seriale, e modificando quelli tutto funziona! :slight_smile:

Ups… ho sbagliato a passarti il file, scusa. E’ vero, era in HardwareSerial.cpp ma stamani non riuscivo a ricordarmi il file ed ho fatto una ricerca veloce in tutto il core ed ho trovato Cdc.cpp come prima occorrenza. :sweat_smile: :sweat_smile: