6 Arduini UNO che comunicano un float a un MEGA 2560 con SoftwareSerial

Non capisco .. la sto provando da me e ... non sbaglia un colpo ...

C'è effettivamente un problema con l'overflow ...
... quando va in overflow, bisogna infatti aggiungere un piccolo loop che pulisce il buffer in ingresso, altrimenti, quando ricomincia a leggere, legge i vecchi caratteri che erano rimasti nel buffer ! :wink:

Se hai pazienza, domani mattina sistemo il codice, faccio un altro piccolo test e te lo metto qui.

Per l'altra tua domanda relativa all'uso del parseFloat...
... suppongo tu debba fare delle println() separate, una per ogni float e delle parseFloat separate una per ogni float che devi ricevere ... ma non ne sono certo, non l'ho mai usata :smiley: :smiley: :smiley:

Continuo a dire che è meglio avere sotto il proprio controllo la situazione e saper gestire ogni possibile caso (... capendo come fare) che affidarsi a dei blocchetti già fatti che oggi qui trovi, domani, in un altro ambiente, non trovi più :wink:

Guglielmo

Concordo !
Buonanotte e grazie intanto !

Difficile andare a letto con il tarlo nella mente ... :grin: :grin: :grin:

Versione aggiornata e collaudata del mio codice :

#define MAXCHAR  10
#define TERCHAR  0x0D

char inputString[MAXCHAR + 2];
char inChar;
byte strIndex;


void setup() {
  
  strIndex = 0;
  inputString[strIndex] = 0x00;
  
  Serial.begin(9600);
  
}

void loop() {
  
  if (Serial.available()) {
    inChar = Serial.read();
    // se si vogliono vedere in HEX i caratteri che si ricevono
    // togliere il commento alla riga seguente
    // Serial.println(inChar, HEX);
    if (inChar == (char) TERCHAR) {
      // è arrivato il carattere terminatore, si puo' elaborare la stringa
      // In questo esempio semplicemente la si stampa ...
      Serial.print(F("Input string : "));
      Serial.println(inputString);
      //
      // finito il suo uso la si ripulisce per un uso successivo
      strIndex = 0;
      inputString[strIndex] = 0x00;
    }
    // NON è arrivato il carattere terminatore, si memorizza il carattere ricevuto
    // e si sposta il fine stringa ...
    inputString[strIndex] = inChar;
    strIndex++;
    inputString[strIndex] = 0x00;
    if (strIndex > MAXCHAR + 1) {
      // ... NON c'è pi` spazio nella stringa ... bisogna elaborarla e svuotarla
      // In questo esempio semplicemente la si stampa ...
      Serial.print(F("Overflow string : "));
      Serial.println(inputString);
      delay(5);
      Serial.print(F("More chars : "));
      Serial.println(Serial.available());
      // Butta via i caratteri che erano rimasti nel buffer della seriale ...
      while (Serial.available()) {
          delay(5);
          Serial.read();
      }
      //
      // e ripulisce la stringa per un uso successivo
      strIndex = 0;
      inputString[strIndex] = 0x00;
    }
  }
}

... ho ampliato di un carattere il buffer e inserito, in caso di overflow, un ciclo di svuotamento del buffer. Come vedi ci sono dei piccolissimi ritardi (dei delay(5)) che servono a dare il tempo ai vari caratteri di arrivare anche a basse velocità :wink:

Devi riadattarlo alla tua SoftwareSerial ... visto che io le prove le sto facendo sulla Serial connessa alla USB :wink:

Buona notte XD

Guglielmo

Posts: 17
View Profile

Re: 6 Arduini UNO che comunicano un float a un MEGA 2560 con SoftwareSerial
« Reply #49 on: Today at 06:21:00 pm »
Bigger Bigger Smaller Smaller Reset Reset
A sto punto sono curioso anche io perchè in realtà il parseFloat non mi risolve al 100%. Infatti lo step successivo sarebbe ricevere 2 valori float da ogni arduino UNO, uno relativo alla temperatura rilevata e uno relativo alla temperatura impostata..
Siccome il parseFloat si ferma al primo numero con virgola che trova come faccio a mandarne due ? Ho letto 30 volte le reference ma non mi pare di aver capito che si possa fare...

@Marcobz
Ho letto tutti i post, mi sembra evidente che l'approccio è sbagliato, e questo ti porta in confusione, perché in effetti se fosse così complicato spedire e ricevere dati in pochi saprebbero farlo.

Studiare il reference non è risolutivo, ciò non vuol dire che non va studiato, per cui devi integrare altre conoscenze.
Studia come è composto un float. Un float in Arduino è un tipo che occupa in memoria 4 byte.
I byte sono spediti nudi e crudi e ricevuti alla stessa maniera.

Più dati spediti in sequenza devono essere ricevuti nella stessa sequenza. Il protocollo software serve proprio a spedire e ricevere dati in modo che il ricevente sappia che i 4 byte spediti per primo sono la temperatura ambiente
gli altri 4 byte ricevuti per secondi sono la temperatura richiesta o viceversa.

C'è un modo semplice per spedire dati in modo ordinato e riceverli nello stesso ordine ed è quello di creare un pacchetto dati. Il pacchetto nel tuo caso è formato da 8 byte che se spediti 2 volte rappresentano per il ricevente due pacchetti. Stivare i dati in una struttura (keyword struct) semplifica il lavoro, la stessa struct l'avrai nel trasmittente e nel ricevente. Un struct composta da due dati di tipo float puo essere scandita byte per byte e inviata via seriale. Il ricevente riceve byte per byte e allora basta scrivere nella variabile di tipo struct 8 byte per il primo pacchetto. Questo dovrebbe farti capire anche che devi trovare il modo di capire quando inizia il pacchetto e quando finisce. La struct da inviare allora protrebbe essere composta da un byte, due float, e un byte, totale 10 byte.

Quando ricevi da seriale testi se il primo byte vale es 10 (decimale) se lo è i byte seguenti li scrivi byte per byte fino a che non ricevi un byte che es vale 20.

Es:
spedisci:
Startpack (10), 8 byte (i due float) e per ultimo il byte di EndPack(20)

Ho dei nomi al valore 10 e 20, cioè StartPack e EndPack, solo per intenderci, ma non è escluso si possano usare nel codice.
struct data {
float tAmb;
float tSet;
}

struct data dataSending; // crea una variabile di tipo struct
dataSending.tAmb = 22.5;
dataSending.tSet = 25.0;
for (byte i=0; i<sizeof(data); i++) { // clicla n volte in base a quanto è grande la struct data
// qui spedisci i dati byte per byte
// come scomporre la variabile dataSending in byte ora non mi viene (è tardi e sono cotto)
}

Vedi se riesci a capirci qualcosa, perché di tempo per scrivere tutto funzionante non nè ho.
In alternativa puoi usare il metodo parse... aggiungendo la gestione dei pacchetti, con start pack e end pack. Capisci? quando trasmetti chi riceve non è a conoscenza di quando hai iniziato a trasmettere.

PS: l'organizzazione dei dati in struct o contenitori come le classi o array ecc è solo una rappresentazione C/C++,
rappresentazioni create per aiutare il programmatore ad organizzare i dati e nel caso della classi unisce dati e metodi legati ai dati all'interno appunto di classi, ma tutto alla fine è fatto di byte o bit.

Ciao.

MauroTec:
@Marcobz
Ho letto tutti i post, mi sembra evidente che l'approccio è sbagliato, e questo ti porta in confusione, perché in effetti se fosse così complicato spedire e ricevere dati in pochi saprebbero farlo.

Ciao Mauro,
non è così complicato, ma non è nemmeno così semplice e ...
... il problema al momento NON è come trasferire uno o più float, cosa che affronterà in seguito, il problema (... se così si può chiamare :wink: ) è mettere su una comunicazione seriale che preveda la gestione dell'overflow, la futura gestione del timeout, ecc. senza ricorre a ... scorciatioie dell'IDE (parseXXXX).

Una volta che ha messo a punto un bel sistema per trasmettere/ricevere, in modo controllato, una qualsiasi sequenza di byte ... usarlo per spedire ciò che si vuole non è un problema. :slight_smile:

E, come vedi dai vari post, l'intento di questo thread è proprio questo ...

Guglielmo

Marcobz:
E funzionaaaaaaaa !!!!!!! Sarà una scorciatoia diseducativa XD ma funzionaaaaa !

Sembra, ma ... ecco un'altro esempio in cui un blocchetto chiuso ... ti nasconde quello che realmente accade ...

Leggendo carattere a carattere, stiamo evidenziando che, comunque, un qualche cosa di sporco sulla seriale c'è ... altrimenti non andrebbe mai in overflow e ti darebbe sempre la stinga pulita, mente, tutto il lavoro che stiamo facendo è per "intercettare" condizioni particolari e per gestirle (caratteri di troppo, mancanza del CR, ...)

Sai perché non vedi nulla di tutto questo con la parseXXXX ? ... E' scritto nel reference :

Serial.parseFloat() returns the first valid floating point number from the Serial buffer. Characters that are not digits (or the minus sign) are skipped. parseFloat() is terminated by the first character that is not a floating point number.

Quindi ... qualsiasi porcheria accada sulla linea, tu non la vedrai MAI ... e se dei dati arrivano corrotti, semplicemente li perderai senza nessun avvertimento.

Come vedi c'è una bella differenza, nella realtà pratica, tra usare quella e ... controllarsi da soli quello che succede :wink:

Guglielmo

La cosa più semplice per capire come funziona è guardare il listato.
Le funzioni sono in Stream.cpp

// returns the first valid (long) integer value from the current position.
// initial characters that are not digits (or the minus sign) are skipped
// function is terminated by the first character that is not a digit.
long Stream::parseInt()
{
  return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout)
}

// as above but a given skipChar is ignored
// this allows format characters (typically commas) in values to be ignored
long Stream::parseInt(char skipChar)
{
  boolean isNegative = false;
  long value = 0;
  int c;

  c = peekNextDigit();
  // ignore non numeric leading characters
  if(c < 0)
    return 0; // zero returned if timeout

  do{
    if(c == skipChar)
      ; // ignore this charactor
    else if(c == '-')
      isNegative = true;
    else if(c >= '0' && c <= '9')        // is c a digit?
      value = value * 10 + c - '0';
    read();  // consume the character we got with peek
    c = timedPeek();
  }
  while( (c >= '0' && c <= '9') || c == skipChar );

  if(isNegative)
    value = -value;
  return value;
}


// as parseInt but returns a floating point value
float Stream::parseFloat()
{
  return parseFloat(NO_SKIP_CHAR);
}

// as above but the given skipChar is ignored
// this allows format characters (typically commas) in values to be ignored
float Stream::parseFloat(char skipChar){
  boolean isNegative = false;
  boolean isFraction = false;
  long value = 0;
  char c;
  float fraction = 1.0;

  c = peekNextDigit();
    // ignore non numeric leading characters
  if(c < 0)
    return 0; // zero returned if timeout

  do{
    if(c == skipChar)
      ; // ignore
    else if(c == '-')
      isNegative = true;
    else if (c == '.')
      isFraction = true;
    else if(c >= '0' && c <= '9')  {      // is c a digit?
      value = value * 10 + c - '0';
      if(isFraction)
         fraction *= 0.1;
    }
    read();  // consume the character we got with peek
    c = timedPeek();
  }
  while( (c >= '0' && c <= '9')  || c == '.' || c == skipChar );

  if(isNegative)
    value = -value;
  if(isFraction)
    return value * fraction;
  else
    return value;
}

Le funzioni usano due metodi privati della classe per leggere il carattere successivo.

// private method to peek stream with timeout
int Stream::timedPeek()
{
  int c;
  _startMillis = millis();
  do {
    c = peek();
    if (c >= 0) return c;
  } while(millis() - _startMillis < _timeout);
  return -1;     // -1 indicates timeout
}

// returns peek of the next digit in the stream or -1 if timeout
// discards non-numeric characters
int Stream::peekNextDigit()
{
  int c;
  while (1) {
    c = timedPeek();
    if (c < 0) return c;  // timeout
    if (c == '-') return c;
    if (c >= '0' && c <= '9') return c;
    read();  // discard non-numeric
  }
}

Read, Peek e Flush sono dichiarate virtuali e si riferiscono alla classe chiamante la classe Stream, ad esempio Serial.

virtual int read() = 0;
virtual int peek() = 0;
virtual void flush() = 0;

Quindi il read() può essere interpretato come Serial.read(), cioè legge un carattere dalla Seriale.

Scusa, forse sto dicendo una ca**ata, ma non potrebbe essere la Mega ad interrogare a turno i vari slave, invece di passare tutto il tempo ad aspettare la loro trasmissione ? ... tipo:

(master>slave1) "inizia trasmissione" (e poi si mette in ricezione, se entro, esempio, 5 secondi, non riceve nulla, riprova, dopo 3 tentativi senza ricevere nulla segnala allarme mancanza comunicazione con slave 1, cosi c'e' pure una segnalazione di possibile malfunzionamento)

(slave1) (dopo un secondo di pausa, per consentire al master di passare in ricezione senza problemi) trasmette una stringa composita con tutti i valori in sequenza, ad esempio, carattere inizio stringa (puo essere *), primo valore, separatore (puo essere |), secondo valore, fine stringa (puo essere #), per due volte di seguito (come controllo) ... se la stringa fosse di lunghezza fissa, sarebbe meglio (ad esempio, spedendo sempre i valori della temperatura come quantita' fisse di bytes, potrebbero bastare 5 caratteri per dato, positivo/negativo, prima cifra, seconda cifra, punto decimale, cifra decimale ... in questo modo una stringa completa sarebbe sempre di 13 bytes, e una trasmissione sempre di 26), perche' cosi il master non dovrebbe controllarle in tempo reale, basterebbe che le infilasse in ordine in un buffer da 26 caratteri (o in due da 13, per semplificare il controllo comparandoli), e che le controllasse dopo averle ricevute tutte ...

(master) mentre riceve piazza tutto in un buffer, al termine della ricezione controlla che le due meta' della stringa siano uguali, se no, la trasmissione e' errata e ripete il tutto (max 3 volte, poi segnala errore), se si, trasforma le parti "numeriche" della stringa in valori numerici, li elabora, ci fa quello che vuoi, poi svuota il buffer e passa a "slave 2" ... e cosi via ...

In questo modo saprebbe sempre quale slave sta rispondendo, e cosa gli sta mandando, controllerebbe per errori di comunicazione, e non dovrebbe tenere sempre una linea aperta in ricezione mentre fa il resto (l'interrogazione degli slave potrebbe farla anche ogni 5 secondi, tanto la temperatura ambiente non cambia piu di tanto velocemente) ... inoltre in questo modo gli slave potrebbero trasmettere "codici di errore" se necessario quando interrogati, al posto del valore di temperatura, ed il master potrebbe discriminarli (magari perche' cominciano con un carattere diverso) ed eseguire le opportune azioni ... o sbaglio e l'idea e' stupida ?

Etemenanki:
Scusa, forse sto dicendo una ca**ata, ma non potrebbe essere la Mega ad interrogare a turno i vari slave, invece di passare tutto il tempo ad aspettare la loro trasmissione ? ...

Ahahahah ... SI, ma è ovvio che poi farà così, anzi ...
... la lettura dalla seriale dovrà diventare proprio una funzione che o gli ritorna l'array di char (se ha ricevuto qualche cosa di valido) o gli ritorna NULL in caso di errore (overflow, timeout, ...) e lui la chiamerà quando gli servirà di leggere una data porta :wink:

Ragazzi, sappiamo che siete tutti bravissimi i materia e che problemini del genere sono banalità (tu, Paolo, ecc.) ... ma, scusate ... vi manca la parte "didattica" ...

... ce li volete far arrivare per gradi e da soli o gli dovete sempre dire tutto e fare voi il lavoro ??? XD XD XD

Guglielmo

:sweat_smile: =(

gpb01:
... sappiamo che siete tutti bravissimi ...

Gli altri di sicuro, ma io proprio no :stuck_out_tongue: XD ... ricordati che sono un'hardwarista, non un softwarista :wink:

Etemenanki:
Gli altri di sicuro, ma io proprio no :stuck_out_tongue: XD ... ricordati che sono un'hardwarista, non un softwarista :wink:

XD Dai, dai, non fare il modesto ... che hai la tua gran bella esperienza :wink:

Guglielmo

Aggiornamento:

tra le mie mille ricerche mi è capitata in mano una libreria chiamata EasyTransfer. Me la sono studiata un pò e ho provato a mettere giù uno sketch per le mie esigenze.
Ecco il risultato:

PARTE TRASMITTENTE

#include <SoftEasyTransfer.h>  //includo la libreria ET
#include <SoftwareSerial.h>    //includo la libreria SS
SoftwareSerial mySerial(10, 11); //definisco le porte della SS
SoftEasyTransfer ET;              //creo l'oggetto ET

struct SEND_DATA_STRUCTURE{        //creo la struttura della serie di dati
  float valore1;                   //definisco quali variabili devono far parte della struttura
  float valore2;
};
SEND_DATA_STRUCTURE mydata;        //do un nome al gruppo di variabili

float valore1=20.97;               //dichiaro le variabili 
float valore2=19.88;

void setup(){
  mySerial.begin(9600);            //faccio partire la SS
  ET.begin(details(mydata), &mySerial);  //faccio partire la ET
  Serial.begin(9600);                    //faccio partire la seriale  
}
void loop(){
  mydata.valore1 = valore1;        //do un valore alle variabili con la struttura [nome del gruppo].[nome della variabile]
  mydata.valore2 = valore2;        //do un valore alle variabili con la struttura [nome del gruppo].[nome della variabile]
  Serial.println(valore1);
  Serial.println(valore2);
  ET.sendData();//invio i dati
  delay(1000);
}

Ho provato a commentare le righe per far capire come funziona.

PARTE RICEVENTE

#include <SoftEasyTransfer.h>  //includo la libreria ET
#include <SoftwareSerial.h>    //includo la libreria SS
SoftwareSerial mySerial(10, 11); //definisco le porte della SS
SoftEasyTransfer ET;              //creo l'oggetto ET

struct RECEIVE_DATA_STRUCTURE{    //creo la struttura
  float valore1;                  //stesse variabili del TX
  float valore2;
};
float valore1;                    //le dichiaro
float valore2;                    //le dichiaro
RECEIVE_DATA_STRUCTURE mydata;     //do lo stesso nome del TX alla struttura

void setup(){
  mySerial.begin(9600);            //faccio partire la SS
  ET.begin(details(mydata), &mySerial);  //faccio partire la ET
  Serial.begin(9600);                    //faccio partire la seriale  
}

void loop(){
  if(ET.receiveData()){        //controllo se è arrivato un pacchetto di dati
  valore1 = mydata.valore1;    //assegno i valori ricevuti alle variabili
  valore2 = mydata.valore2;
  Serial.println(valore1);     //le stampo
  Serial.println(valore2);
    }
  delay(250); //fortemente consigliato per non perdere pezzi del messaggio ricevuto
}

So che Guglielmo storcerà il naso.. Però FUNZIONA !!!
Tral l'altro da quello che ho capito ci sono diverse versioni della libreria. Questa SoftEasyTransfer nello specifico credo sia dedicata alle comunicazioni su seriale software (che poi è quello che mi serve).
Dal puro lato didattico non è il massimo.. lo so.. però per quanto mi riguarda non è che adesso smetto di cercare di imparare quello di cui si stava qui discutendo !

:wink:

Bene, contento tu ... ]:smiley:

... così non impari nulla e la prima volta che quella lib non ti funzionerà o, un giorno magari non sarà più supportata ... sarai da capo a dodici.

Ma ripeto ... contento tu ... contenti tutti !!! :grin: :grin: :grin:

Guglielmo

No non mi fraintendere !
Ho anche detto che il discorso affrontato fino a qui lo vorrei continuare ! Ovviamente se le parti sono interessate.... io lo sono !

Marcobz:
Ho anche detto che il discorso affrontato fino a qui lo vorrei continuare ! Ovviamente se le parti sono interessate.... io lo sono !

Ok, allora metti l'ultima versione a cui sei arrivato e dimmi che problemi ti da ... che riprendiamo da li ...

Guglielmo

Io non conosco nel dettaglio la SoftEasyTransfer, però da quello che vedo nel codice che hai postato mi sembra una libreria ben fatta. Più o meno è quello che dicevo nel mio post precedente riguardo alle struct usate come payload.

Preferisco questo del payload per vari motivi:

  1. La classe o funzione che spedisce i dati è generica e non legata ad un tipo utente, ma richiede semplicemente un puntatore a byte e la lunghezza del payload in byte.
  2. I dati da spedire sono messi in ordine e ricevuti nello stesso ordine in modo automatico grazie alla condivisione della struct.
  3. La classe può essere personalizzata aggiungendo dei metodi setStartCode(int code) e setEndCode(int code)
    questo permette al ricevente di scegliere il tipo di collezione di dati.
    Un tipo di collezione di dati potrebbe essere:
  • Dati di configurazione (code = 1)
  • Dati di aggiornamento (code = 2)
  • Dati di instradamento (code = 3)
  • Dati di configurazione remota (code = 4)

In ricezione basta uno switch case startCode per ricevere dati e usarli in modo specifico, modo stabilito dal trasmittente.

@gpb01
Di soluzioni a problemi conosciuti (design pattern) ce n'è più di una, basta conoscerle tutte e scegliere quella che
preferiamo, questa del payload è una, le altre suggerite con tutte le varianti e quelle a venire sono tutte da scoprire.

Ciao.

Eccomi qua !

Scusa l'assenza Guglielmo ma è un periodo "pregno" :grin:

Ho adattato lo sketch al mio caso

#include <SoftwareSerial.h>

SoftwareSerial mySerial(10, 11); 


#define MAXCHAR  10
#define TERCHAR  0x0D

char inputString[MAXCHAR + 2];
char inChar;
byte strIndex;


void setup() {
  
  strIndex = 0;
  inputString[strIndex] = 0x00;
  mySerial.begin(9600);
  Serial.begin(9600);
  
}

void loop() {
  
  if (mySerial.available()) {
    inChar = mySerial.read();
    // se si vogliono vedere in HEX i caratteri che si ricevono
    // togliere il commento alla riga seguente
    // Serial.println(inChar, HEX);
    if (inChar == (char) TERCHAR) {
      // è arrivato il carattere terminatore, si puo' elaborare la stringa
      // In questo esempio semplicemente la si stampa ...
      Serial.print(F("Input string : "));
      Serial.println(inputString);
      //
      // finito il suo uso la si ripulisce per un uso successivo
      strIndex = 0;
      inputString[strIndex] = 0x00;
    }
    // NON è arrivato il carattere terminatore, si memorizza il carattere ricevuto
    // e si sposta il fine stringa ...
    inputString[strIndex] = inChar;
    strIndex++;
    inputString[strIndex] = 0x00;
    if (strIndex > MAXCHAR + 1) {
      // ... NON c'è pi` spazio nella stringa ... bisogna elaborarla e svuotarla
      // In questo esempio semplicemente la si stampa ...
      Serial.print(F("Overflow string : "));
      Serial.println(inputString);
      delay(5);
      Serial.print(F("More chars : "));
      Serial.println(Serial.available());
      // Butta via i caratteri che erano rimasti nel buffer della seriale ...
      while (mySerial.available()) {
          delay(5);
          mySerial.read();
      }
      //
      // e ripulisce la stringa per un uso successivo
      strIndex = 0;
      inputString[strIndex] = 0x00;
    }
  }
}

e questo è il risultato

Overflow string : þ29.571329.5
More chars : 0

Che ne dici se commentiamo ogni riga a scopo esplicativo per chi vuole capire ? Tipo io ? XD

Allora,
primo ... occhio che non hai sostituito una Serial :

Serial.print(F("More chars : "));
Serial.println(Serial.available());

deve diventare :

Serial.print(F("More chars : "));
Serial.println(mySerial.available());

perché stiamo vedendo quanti caratteri ci sono ancora nel buffer della SoftwareSerial.

Poi ...
... ma ti da solo quello ??? Ovvero se lasci girare il tutto per 30 secondi ... cosa succede ... hai tutte le ricezioni errate ???

O capita ogni tanto ...

Guglielmo