Trasmissione I2C di numeri con decimali

Salve a tutti, per un progetto più complesso mi sto istruendo con le varie funzionalità di Arduino ed ESP32 più precisamente ora mi ritrovo con la comunicazione I2C.

Io ho un ESP32 come master che invia richieste ad uno o più Arduino Nano in modalità slave.

Allego i due codici molto semplici, che ho cercato in rete e modificato.

Lo slave al momento genera dei numeri casuali da 0 a 2000 con un decimale quindi da 0.0 a 2000.0 (anche se a monitor seriale c'è sempre un secondo decimale che è sempre 0 tipo 459.30 e non capisco il perché) per simulare quelli che saranno i vari dati trasmessi nel progetto finale, i numeri vengono trasformati in stringa e resi disponibili alla chiamata del master.

Ora tutto funziona alla grande ma quando il numero inviato dallo slave è più piccolo dei 6 byte che il master si aspetta (esempio 39.5 o 5.2) nel monitor seriale mi ritrovo 39.5§§ o 5.2§§§ e uno, due o tre caratteri strani che non mi servono e non hanno nulla a che vedere con il numero originale.
Come faccio a sapere io se il numero trasmesso sarà 1.6 o 1976.5 per poter determinare il numero di byte da decodificare?
Allego i codici e ringrazio anticipatamente.
Alberto.

SLAVE

#include <Wire.h>

int slaveA = 0X7A;              //ID esadecimale della scheda per la comunicazione I2C
char t[10];                     //Array vuoto che conterrà la stringa da far leggere al master

void setup() {
  Wire.begin(slaveA);           //Slave id
  Wire.onRequest(requestEvent); //Funzione per inviare il dato
  Serial.begin(9600);
}

void loop() {
  float x = random(0, 20000);   //Numero casuale da 0 a 20000 
  x=x/10;
  dtostrf(x, 3, 1, t);          //convers the float or integer to a string. (floatVar, minStringWidthIncDecimalPoint, numVarsAfterDecimal, empty array);
  Serial.println(x);            //Stampo nel monitor seriale la stringa inviata per il debug
 delay(1000);
}

void requestEvent(){            //Funzione da richiamare per invio dati
  Wire.write(t); 
}

MASTER

#include <Wire.h>

int slaveA = 0X7A;    //ID esadecimale della scheda SLAVE
char t[10] = {};      //Array dove inserire i dati provenienti dallo slave

void setup() {
  Wire.begin();        //Avvio la libreria Wire.h
  Serial.begin(9600);
  }

void loop() {
  Wire.requestFrom(slaveA, 6);    //Riciesta bytes dallo slave (il numero più grande sarà 2000.0 quindi 6 bite)

//gathers data comming from slave
int i = 0;                        //Conteggio per ogni byte in arrivo
  while (Wire.available()) { 
    t[i] = Wire.read();           //Ogni carattere in arrivo viene messo in ordine nell'array "t"
    i = i + 1;
  }

Serial.println(t);                //Stampo nel serial monitor il cntenuto dell'array "t" per il debug
delay(1000);

}

Che senso ha questa cosa? Se trasmetti direttamente il numero che quindi sarà un float, avrai sempre 4 byte e non devi fare un'inutile manipolazione di stringhe oltre a tutti i problemi che stai riscontrando per gestire correttamente il teminatore di stringa.

La classe Serial quando deve stampare un float come stai facendo in questi esempi, stampa con 2 posizioni decimali se non diveramente indicato. Tu stai dividendo un intero che va da 0 a 2000 per 10, quindi la seconda cifra decimale sarà sempre 0.
Se vuoi stampare solo una posizione devi specificarlo nel println()

Serial.println(x, 1);  

Problema affrontato spesso
Prova a vedere in "aiutateci ad aiutarvi" e le sue discussioni sorelle

Troverai "tutto" quello che ti serve

Buon lavoro

Vero, condivido

Ma bisogna conoscere l'allineamento dei byte tra le varie cpu e per le varie dimensioni delle variabile

Secondo trasmettere in testo chiaro è la soluzione migliore all'inizio, e poi non serve di cambiarla
Chissà come mai i vari strumenti con porta seriale tipo le bilancie trasmettono in testo chiaro

Perché nascono per essere connessi a dei personal computer in origine.

Non lo so, capisco che può sembrare più facile perchè "vedi" il valore, però secondo me quando si parla di bus dati tipo I2C o SPI è sempre preferibile ragionare in termini di byte "grezzi" cosi si capisce anche come vengono rappresentati i diversi tipi di valori in una MCU che male non fa dal punto di vista didattico.

Comunque @maredentro72 sta usando un ESP32 ed un ATMega328 (presumo, non specifica di quale Nano si tratta) e tutte e due le MCU usano 4 byte per i float con rappresentazione little-endian (secondo lo standard IEEE 754).

Si @cotestatnt ho ESP32 e Atmega328P.

Credevo di aver risolto il problema trasmissione dei numeri e invece me lo sono complicato.

La notte mi ha portato a riflettere e pensavo di inviare due valori con lo slave al master, prima determinare la dimensione in byte del numero (valore tra 3 e 6 - 0.0 e 2000.0) e spedirla in modo da ovviare al problema ma da quanto mi dite la questione è più semplice.

Riparto da zero con il float a 4 byte fissi.

Vediamo cosa ne esce
Alberto.

Ancora in alto mare.

Posto i due codici Master e Slave che ho ripescato in rete a questo link:
https://tasnemulhasannehal.wixsite.com/diary/post/sending-float-data-i2c-bus
ma ci sono lati oscuri che purtroppo non riesco a decifrare:

#include<Wire.h>

void bytesToInt(byte b0, byte b1);       //converts bytes to int
int a;                                   //converted int is stored here

void setup() 
{
 Wire.begin();             //only for master to create communication 
 Serial.begin(9600);       //MCU & Serial Monitor communication start
}

void loop() 
{  
    Wire.requestFrom(0x7C, 2);   
    while(Wire.available() > 0)   
    {
     byte b0=Wire.read();          //reads data
     byte b1=Wire.read();          //reads data
     
     bytesToInt(b0, b1);           //converts incoming bytes to integer

     float f= (float) a / 100;       //integer to float
     Serial.println(f);  
    }
delay(1000);
}


void bytesToInt(byte b0, byte b1)
{
  a = (b0 << 8) | (b1);            //using Bitwise operators
}

Slave:

#include<Wire.h>

void intToBytes(int x);           //converts int to bytes
byte b0,b1;                       //stores incoming data

void setup() 
{
  Wire.begin(0x7C);         //slave address to call by Master
  Serial.begin(9600);       //MCU & Serial Monitor communication starts
  
  Wire.onRequest(sendData); 
}

void loop() 
{

  float f = random(0, 20000);   //Creo Numero casuale da 0 a 20000 per test
  f = f / 10;                                  //Diventa un float tra 0.0 e 2000.0
  Serial.print(f);              //Stampo il numero casuale da inviare
  Serial.print("---> ");

  int x= f*100;
  intToBytes(x);             //converts integer to bytes before sending
  
  Serial.print(b0);         //In teoria la prima parte intera del float
  Serial.print(".");        //Punto separatore
  Serial.println(b1);       //In teoria la parte decimale
  delay(1000);
}

void sendData()
{
  Wire.write(b0);           //sending data at request
  Wire.write(b1);
}

void intToBytes(int x)
{
  b0 = (x >> 8);            //bitwise operation
  b1 = x & 0xFF;
}

Nello salve non capisco la funzione void intToBytes(int x) e comunque in generale come fa a "rompere" il float e ad inviarlo.

Poi lo sketch funziona ma il serial monitor del master non ricostruisce sempre il numero esatto inviato dal master.

Alberto

Ho letto la presentazione e penso che sai poco di elettronica ed informatica, quindi ho pensato di mostrarti delle immagini della calcolatrice che uso con Ubuntu.

La prima immagine mostra il valore decimale 1 e sotto 64 bit, il bit meno significativo (LSb) è acceso tutti gli altri spenti. Nelle immagini compare anche il valore espresso in ottale ed esadecimale.

Ok , vedi quei due "pulsanti" << ed >> sono operatori di shift.
Se scrivo: 1 << 1 = 0000 ' 0010.
Il byte è composto da 8 bit, metà byte si chiama nibble.
Se scrivo 1 << 7 = 1000 ' 0000.
Il bit (LSb) è stato shifted di 8 posizioni a sinistra, quindi ora il bit più significativo (MSb) è acceso.

LSb = Least Significant bit
MSb = Most Significant bit

L'immagine seguente mostra il risultato di 1 << 8. Nota che la posizione del primo bit a destra è il bit 0.

Il simbolo | è l'peratore OR.

Quindi supponiamo che io riceva 2 byte: 15 e 30.
15 << 8 = 3840 (0000 ' 1111 ' 0000 ' 0000) 16-bit (int su AVR)
3840 | 30 = 3870 ( (0000 ' 1111 ' 0001 ' 1110)

Il simbol V è l'operatore OR per questa calcolatrice.

Cerca in rete, byte, nibble, operatore shift, operatore OR, AND, XOR e NOT.

Purtroppo online non ho trovato nessuna calcolatrice online simile a quella che uso io.

Ciao.

Non "sembra" più facile,
è più facile,
come si è appena dimostrato

Io sono dell'idea che le cose diventano facili dal momento in cui le comprendi fino in fondo ed impari a "dominare" l'argomento.
Tutto quello che c'è prima, sono pezze a colori.

Per smartphone Android c'è questa ottima app.

Certo, se sei già almeno partito

Ma se sei un principiante stai solo mettendoti dei 'sassi nello zaino'

Ho affrontato e risolto questi problemi innumerevoli volte

Sarebbe bastato fare 2 ricerche e adesso si sarebbe già finito

Amen

Stiamo entrando nel regno delle preferenze personali (oltre ad andare fuori tema).

Siamo stati (e saremo) tutti principianti in qualcosa. Personalmente, quando devo affrontare un percorso, io cerco di fare da A a B senza passare per C.

Passare per un array di char per inviare dei float quando dall'altra parte ho bisogno di float, oltre che dilettantesco, distoglie l'attenzione dal problema di base ovvero capire e gestire come vengono rappresentati i numeri in un calcolatore.

Io almeno sono di questa opinione.

Ci sto lavorando e questo per me comporta rispolverare nozioni basilari dimenticate molti anni fa.

Sono riuscito ad inviare i numeri trasformandoli da float a string e aggiungendo zeri dove il numero è più piccolo di XXXX.X per completare i 6 byte e potrei anche fermarmi.
Ho visto però che l'invio direttamente del float opportunamente trattato è più semplice ma meno intuitivo (per me), aggiungo che userò questo sistema perché dovrò fare il controllo dell'esattezza del dato trasmesso.... Visto che ci siamo facciamola fino in fondo.
La maggior parte di questa documentazione è in inglese e purtroppo non sono nato ad Oxford quindi la strada è in salita ma io ho la e-bike carica.

Non serve nulla di tutto questo
Basta stamparli, la conversione la fa la stampa

Come ti è stato suggerito, ti stai solo complicando la vita ... ci sono varie soluzioni molto semplici per trasmettere un float da 32 bit ... una tra le tante, ad esempio, è vederlo sia come un float, sia come 4 bytes separati ... conosci la "union"?

Ti permette di definire una stessa area di memoria in più modi differenti (più variabili che sono nello stesso spazio di memoria) quindi ... ti faccio un esempio di come un float può essere visto sia come float che come 4 bytes separati a cui puoi accedere singolarmente ...

union {
  float f;
  byte  b[4];
} floatUnion;

void setup() {
  Serial.begin(115200);
  while (!Serial) delay(100);

  floatUnion.f = 12.34;

  Serial.print("Il valore del float è: ");
  Serial.println(floatUnion.f);
  Serial.println();

  for (byte i = 0; i < 4; i++) {
    Serial.print ("Il valore del byte ");
    Serial.print (i);
    Serial.print (" del float è : ");
    Serial.println(floatUnion.b[i]);
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

Che ti stamperà i seguenti valori:

Il valore del float è: 12.34

Il valore del byte 0 del float è : 164
Il valore del byte 1 del float è : 112
Il valore del byte 2 del float è : 69
Il valore del byte 3 del float è : 65

Ripeto questa è UNA delle tante possibilità che hai, ma ... è sicuramente meglio che andare a fare conversioni in stringhe :wink:

Guglielmo

Ma anche questo è ucas
Se vuoi stare leggero e presupponi di 'sapere bene' il c ti basta trasmettere i byte uno a uno passando per un for e un puntatore
Impari anche di più che usare un costrutto vecchio e stantio come la union
E non ti serve nemmeno conoscere la dimensione della variabile in trasmissione

:joy: :joy: :joy: ... l'ho detto ... "è UNA delle tante possibilità" ... visto che ne sta provando tante, questa è un'altra :wink:

... ovviamente, è la strada più semplice, ma devi conoscere bene l'uso dei puntatori e ... l'OP non mi sembra molto smaliziato con il 'C'.
La "union" sarà pure "vecchia e stantia", ma magari gli semplifica la "visione" di ciò che fa.

Ripeto, tante sono le strade percorribili ... :slight_smile:

Guglielmo

basta fare così:

void trasmetti( uint8_t * dati, size_t dimensione)
{
   for (size_t i=0; i< dimensione; i++)
   {
    Serial.print ("Il valore del byte ");
    Serial.print (i);
    Serial.print (" della variabile è : ");
    Serial.println(* dati[i]);
   }
}

che naturalmente si usa con

trasmetti(&variabile, sizeof variabile);

così si che si impara roba interessante, con con la ammuffita Union

In realtà la libreria Wire.h prevede già il metodo per inviare più byte per volta ciclando con un for.

Basterebbe fare

float value =17.76;
.......
.......
  Wire.beginTransmission(indirizzo_i2c);
  Wire.write((uint8_t*)&value, sizeof(float));
  Wire.endTransmission();