Elaborare in ricezione 2 valori float in seriale

Secondo me scegliete la strada più complicata perché è l'unica intuitiva.

Scambiare dati (collezione di dati) byte per byte viene da sempre fatto tramite una struttura dati che è la collezione di dati. A questa collezione si accede byte per byte e la si spedisce via seriale, la stessa operazione viene effettuata in ricezione nel verso opposto; cioè leggo byte per byte dalla seriale e scrivo questi byte all'interno della collezione.

struct MyStruct {
    float x;
    float y;
};

struct MyStruct myCollection;

Alla variabile myCollection è possibile accedere byte per byte.

Questa soluzione è efficiente ed è anche flessibile ma purtroppo richiede l'uso dei puntatori e/o union, i quali sono argomenti sconosciuti al principiante. In alternativa si potrebbe cercare una libreria che offre questa funzionalità; Il concetto è la serializzazione della collezione dati.

Qui c'è un thread dedicato all'argomento, vedi se trovi qualcosa di utile.

Ciao.

sì e no

se leggi i miei primi post, di anni fa, vedrai che ti do pienamente ragione

però c'é un però

ovvero che così facendo perdi la possibilità di "leggere" il flusso dei "caratteri" (non byte o dati, prorpio scritte in ASCII)
cosa che ti aiuta un fracco per il debug, se sei inesperto

ecco perché non conisglio più la strada che indichi tu

a parte poi che in questo caso particolare la scelta la ha fatta lo OP, prima...

Vi ringrazio per le risposte, l’idea delle strutture la trovo molto interessante, non ne ero a conoscenza.

è anche più compatta e ordinata.

Ho valutato il problema per il debug e per il mio progetto non è un problema, è abbastanza semplice e ho già testato i valori sul serial monitor prima di aggiungere la parte di codice che invia tutto al nodemcu.
In ogni caso lo terrò a mente in futuro nel caso in cui dovessi scrivere un codice più complesso :slight_smile:

A questo punto allego quello a cui sto lavorando (TX Arduino)

const int sensorPin = A0; //TMP 36
const int AuxPin = A1; //Sensore Qualsiasi


void setup() {
  Serial.begin(9600);
  analogReference(EXTERNAL); //Collegare il pin AREF al pin 3,3v
}

void loop() {
  
 delay(4990);
 int val_Adc = 0;
 //eseguo un ciclo
 for(byte Ciclo = 0; Ciclo<100; Ciclo++)
 {
  //acquisisco il valore e lo sommo alla
  //variabile
  val_Adc += analogRead(sensorPin);
  delay(10); //Ritardo per acquisizione
 }

 //eseguo la media dei 100 valori letti
 val_Adc /= 100;
 
 float temp = ((val_Adc * 0.0032) - 0.5) * 100; //Conversione 

 int AuxVal = analogRead(AuxPin);
 int mapped = map(AuxVal, 0, 200, 0, 100); 
 float AuxFinal = constrain(mapped, 0, 100); 
 
 
struct MyStruct {
    float temp;
    float AuxFinal;
};

struct MyStruct myCollection;



}

Ho integrato la struttura, essendo però principiante non so bene come utilizzarla… da qui come posso inviare tutto tramite seriale?

Poi ho anche il problema della ricezione. ho dato un’occhiata al topic sulle struct ma non ho ben capito le procedure. :o

Io vedo, prevedo, stravedo, come dice un noto indovino

In particolare prevedo forti problemi in debug, che è tutt'altro che una cosa risolta

Mancano alcuni elementi base del C per poter dire di non avere bisogno di debug

Anche l'affermazione di Maurotec, che usare strutture e puntatori, oppure strutture e unioni sia più semplice e più efficiente è vera sotto specifiche condizioni, e non siamo sicuri che qui siano verificate

Io comunque ho parlato

Anche l’affermazione di Maurotec, che usare strutture e puntatori, oppure strutture e unioni sia più semplice e più efficiente è vera sotto specifiche condizioni, e non siamo sicuri che qui siano verificate

Concordo, in particolare la condizione potrebbe essere che le due architetture hardware sono differenti, una è AVR 8-bit e l’altra è ARM 32-bit.

Tuttavia un float è grande 32-bit (4 byte) in entrambe le architetture, per cui la tua struct di @axel39 è grande 4+4 = 8 byte.

Un float è codificati in binario secondo lo standard IEEE-754, qui un interessante convertitore online.

const int sensorPin = A0; //TMP 36
const int AuxPin = A1; //Sensore Qualsiasi

// MyStruct è un tipo di dato "composto" - grande 4+4 = 8 byte
struct MyStruct {
    float temp;
    float AuxFinal;
};

// myCollection è variabile (gloabale) istanza di tipo struct MyStruct.
// questa dichiarazione potrebbe essere all'interno del loop, in tal caso
// myCollection sarebbe "locale" e non globale. 
struct MyStruct myCollection;


void setup() {
  Serial.begin(9600);
  analogReference(EXTERNAL); //Collegare il pin AREF al pin 3,3v
}

void loop() {
 
 delay(4990);
 int val_Adc = 0;
 //eseguo un ciclo
 for(byte Ciclo = 0; Ciclo<100; Ciclo++)
 {
  //acquisisco il valore e lo sommo alla
  //variabile
  val_Adc += analogRead(sensorPin);
  delay(10); //Ritardo per acquisizione
 }

 //eseguo la media dei 100 valori letti
 val_Adc /= 100;
 
 float temp = ((val_Adc * 0.0032) - 0.5) * 100; //Conversione

 int AuxVal = analogRead(AuxPin);
 int mapped = map(AuxVal, 0, 200, 0, 100);
 float AuxFinal = constrain(mapped, 0, 100);
 
 // carico i dati dentro la mia collezione
 myCollection.temp = temp; 
 myCollection.AuxFinal = AuxFinal;

 // da qui in poi l'obbiettivo è di accedere a myCollection byte per byte.
 uint8_t *bytePtr = (uint8_t*)&myCollection;
// la riga sopra va letta da destra verso sinistra, sotto iniziate a leggere da <-
// uint8_t *bytePtr dichiarazione di variabile puntatore a tipo uint8_t
// = operatore assegnamento
// (uint8_t*) forzatura casting a puntatore di tipo uint8_t (equivalente al tipo byte)
// & (estrattore di indirizzo) myCollection (la mia variabile istanza) <-
// da adesso possiamo stampare byte per byte via seriale
for (uint8_t idx = 0; idx < 8; idx++ )
        Serial.println(*(bytePtr++));


}

Se entrambe le variabili membro temp e AuxFinal è stato assegnato il valore 50.050
la stampa dovrebbe essere questa sequenza di numeri decimali.

51
51
72
66
51
51
72
66

Questo non è il modo più elegante e inoltre è C crudo, mentre per una implementazione in C++
aprire la libreria EEprom di arduino che però usa i template C++.

Mi fermo qui, per dare il tempo di sperimentare e digerire il codice.

Secondo me scegliete la strada più complicata perché è l’unica intuitiva.

C’è il plurale e gli utenti sono axel39 e Standardoil quindi è naturale pensare che il plurale
si riferisce ad questi due utenti, tuttavia il plurale era di uso generale, nel senso che
molti utenti prendono strade alternative alla serializzazione.

Ciao.

Sapevo benissimo che non ti riferivi direttamente a noi due

Comunque la condizione per la quale potrebbe non essere ne comodo ne conveniente il passare per una struttura non è la presenza di architetture differenti

Ma la necessità di trasferire tutte le volte completamente l'intera struttura dati

E questo potrebbe essere un bel peso, ad esempio su canale lento e/o disturbato quando magari serve solo di aggiornare l'unica variabile modificata

Quindi come vedi la 'maniera' per trasmettere andrebbe scelta sapendo cosa si deve trasmettere, quando e sotto che condizioni

Arrivati al punto di scrivere il programma queste sono analisi e scelte che dovrebbero essere già state fatte...

Un valore con due cifre intere e due decimali tra 0 e 99,99 non è un "float"! Basta moltiplicarlo per 100 e diventa un valore tra 0 e 9999 per cui puoi usare un semplice INT :slight_smile:

E di nuovo siamo a confermare che serve prima l'analisi del problema e poi studiarne la soluzione

E questo potrebbe essere un bel peso, ad esempio su canale lento e/o disturbato quando magari serve solo di aggiornare l’unica variabile modificata

Condivido circa il peso, ma non è questo il caso. Il problema del canale disturbato andrebbe affrontato sia via hardware che software, via software si è obbligati ad aumentare il traffico di dati e aggiungere codice per calcolare qualcosa tipo CRC, ma lo stesso problema lo si avrebbe in codifica ASCII dove in aggiunta c’è codice per convertire numeri in stringhe e viceversa.

Il costo di spedire ASCII per due float composti da 000.00 non contando i caratteri delimitatori e di 6 byte per float. Non dimentichiamo il costo in cicli cpu per la conversione dei dati.

Comunque nel caso la struct sia grande tanti byte e si ha necessità di estremizzare le prestazioni ci sono altre soluzioni per condividere dati tra due devices, soluzione che hanno certamente pregi e difetti da valutare caso per caso.

Quindi come vedi la ‘maniera’ per trasmettere andrebbe scelta sapendo cosa si deve trasmettere, quando e sotto che condizioni

Arrivati al punto di scrivere il programma queste sono analisi e scelte che dovrebbero essere già state fatte…

Certo hai fatto bene a specificarlo perché raramente accade al principiante di sapere quello che vuole
fare, figuriamo se conosce già “il come farlo”.

Nel codice postato manca la variabile di tipo struct su cui scrivere, cioè il destinatario, ma è abbastanza semplice.

    // altro puntatore a tipo struct di destinazione
    struct MyStruct myCollectionDest;
    uint8_t *byteDestPtr = (uint8_t*)&myCollectionDest;
    for (uint8_t idx = 0; idx < 8; idx++ ) {
        *(byteDestPtr++) = *(bytePtr++);
    }

    // qui proviamo a stampare temp e AuxFinal di myCollectionDest
    Serial.println(myCollectionDest.temp);
    Serial.println(myCollectionDest.AuxFinal);

In seriale dovrebbe stampare 50.05 due volte.
Usate sempre dati conosciuti per testare il funzionamento.

Il riassunto è:
Leggo byte per byte da myCollection e ogni byte lo scrivo in myCollectionDest.
Ovviamente poi nella implementazione finale myCollection si troverà nel sorgente
della board di chi invia e myCollectionDest si trova nel sorgente della board che riceve.

Ciao.

Condivido praticamente tutto,

Ma sei sicuro che realizzare quello che hai descritto tu sua più facile, per un neofita, che realizzare quello che ho descritto io?

Comunque propongo

byteDestPtr[idx] = bytePtr[idx];

Che è più chiaro per chi non ha dimestichezza coi puntatori, pur essendo formalmente uguale

Ma sei sicuro che realizzare quello che hai descritto tu sua più facile, per un neofita, che realizzare quello che ho descritto io?

No, sono sicuro che lo scoglio più grande sono struct, class, puntatori incluso l’uso degli operatori & e *, superato questo scoglio la cosa fila liscia (quasi sempre).

Comunque si può semplificare.
Preso codice da EEprom.h

// funzione di nome put che invia tramite seriale ogni tipo di dato passato come argomento
template< typename T > void put(const T &t ){
    const uint8_t *ptr = (const uint8_t*) &t;

    for (uint8_t idx = 0; idx < sizeof(T); idx++ )
        Serial.print *(ptr++); // forse qui sarebbe  più indicato Serial.write(*(ptr++))
    
}

Si usa semplicemente nel loop o nel setup così:

put(myCollection);

Al posto di myCollection si può passare qualunque argomento di diverso tipo.
Ovviamente prima di questa chiamata sarà necessario assegnare dei valori
a temp e AuxFinal.

Un codice arduino per inviare via seriale potrebbe essere il seguente (Attenzione Non Testato)

struct MyStruct {
    float temp;
    float AuxFinal;
};

// funzione di nome put che invia tramite seriale ogni tipo di dato passato come argomento
template< typename T > void put(const T &t ){
    const uint8_t *ptr = (const uint8_t*) &t;

    for (uint8_t idx = 0; idx < sizeof(T); idx++ )
        Serial.print( *(ptr++) ); // forse qui sarebbe  più indicato Serial.write( *(ptr++) )
    
}

void setup() {
    // put your setup code here, to run once:
    Serial.begin(57600);
}

void loop() {
    struct MyStruct myCollection;
    myCollection.temp = 50.05;
    myCollection.AuxFinal = 50.05;
    put(myCollection);
    delay(1000);

}

Se non ho dimenticato nulla dovrebbe quantomeno compilare.

Comunque propongo …

Ottima proposta, lascio all’utente axel39 il compito di verificare che entrambe i modi sono equivalenti,
poi sceglierà lui, io personalmente preferisco la soluzione di standardoil tuttavia finisco per usare
i puntatori nel modo classico.

In merito al nome della funzione di nome put l’ho copiata quasi uguale a quella presente nel
header file EEPROM.h, il nome andrebbe cambiato in qualcosa di più significativo, ad esempio
serialSendStruct o serialSendData o serializeData o altro a piacere.

Ora manca l’altra funzione che riceve i dati da seriale e li inserisce in myCollectionDest.
La posto dopo, prima vediamo se funziona.

Ciao.

Bella la funzione

Io conosco poco il C++

Pertanto debbo chiedere:

Ma funziona anche con char?
Ovvero riconosce la dimensione e trasmette i singoli elementi?

Se funziona con T che è un array dovrebbe andare

Ma non conosco abbastanza C++

… scusate ma la classica Serial.write(buf, len) a cui si passa ciò che si vuole trasmettere (… qualsiasi cosa) e la sua lunghezza (… accetta un puntatore ed un intero) proprio non vi piace? Mi sembra abbastanza semplice da usare senza dover inventare nulla … ::slight_smile:

Guglielmo

Mi sembra abbastanza semplice da usare senza dover inventare nulla ...

Vero c'è anche quella, non ci pensavo.

Ma funziona anche con char?
Ovvero riconosce la dimensione e trasmette i singoli elementi?

Si provato adesso sul PC e funziona anche con:

char myString[] = "Bella funzione";

La chiamata è sempre la stessa, cioè:

put(myString);

Mi stampa questo:

66
101
108
108
97
32
102
117
110
122
105
111
110
101
0

Ma non conosco abbastanza C++

Neanche io lo conosco bene, infatti ho solo copiato e consultato il manuale C++ al volo.
Colpo di c...o che ha funzionato subito sul PC, rimane da testare su arduino.

Ciao.

Se funziona in uno funziona anche nell’altro

Adesso mi viene in mente che ne abbiamo parlato di una storia simile, un paio di anni fa

Non ricordo i dettagli, anche perché allora non li ho capiti, non conoscevo praticamente pe nulla il cPP

Ma mi sa che era una cosa molto simile

Non può funzionare con la classe String perché la copia byte per byte sarebbe mancante di quella porzione di dati allocati dinamicamente da String. Servirebbe una copia profondo ma il vero problema è lato ricevente.

La funzione ricevente potrebbe essere simili a

template< typename T > void recv(const T &t ){
    const uint8_t *ptr = (const uint8_t*) &t;
    
    for (uint8_t idx = 0; idx < sizeof(T); idx++ ) {
        if (Serial.available()) {
            byte data = Serial.read();
            ptr[idx] = data;
        }
    }
}

Sempre da testare, questa poi non la ho testata neanche sul PC, quindi occhio.

Adesso mi viene in mente che ne abbiamo parlato di una storia simile, un paio di anni fa

Può essere, di sicuro ne abbiamo parlato al tempo in cui leo72 era nei panni di Guglielmo, poi
ci dovrebbero essere dei post di @astrobeed circa l’argomento, ma solo C nudo e crudo.

Attenzione all’uso dei template C++, essi generano codice specializzato durante la compilazione.
Un uso smodato dei template potrebbe fare gonfiare la dimensione del programma.

Ciao.

Ecco trovato cosa ricordavo

Naturalmente adesso mi è chiaro come se fosse la mia lingua madre, ma allora non avevo capito molto

Naturalmente adesso mi è chiaro come se fosse la mia lingua madre, ma allora non avevo capito molto

Beh ho capito male io, pensavo ti riferissi al modo di accedere bye per byte ad una variabile di tipo struct, invece tu ricordavi si fosse parlato di template. Comunque quel topic calza a pennello, se al posto di tutte quelle variabili float (vaganti) li avesse inserite in una struct le avrebbe poi salvate tutte in eeprom con una sola chiamata a put().

Se dovessimo considerare il cast seguente troppo orientato verso il C

const uint8_t *ptr = (const uint8_t*) &t;

La sua versione più orientata verso il C++ sarebbe la seguente

const uint8_t *ptr = reinterpret_cast<const uint8_t *>(&t);

Comunque axel39 non ha più postato, forse ha abbandonato, oppure ha risolto in altro modo.

Ciao.

Qui non deve salvarle in eprom, ma trasmetterle/riceverle da seriale

Poco cambia in effetti

Vedremo se lo OP torna oppure se conferma la mia regola di non badare a studenti