Trasmissione I2C di numeri con decimali

Grazie grazie dei suggerimenti. Datemi modo di "digerire" i vostri consigli.

Prometto che posterò il codice dello slave e del master funzionante a breve senza uso di stringhe convertite da float ma in maniera snella e secondo i dettami più o meno ortodossi del C.

Alberto

Più che digerire, il consiglio è quello di studiare e approfondire e se vuoi "capire" cosa fa l'istruzione Wire.write((uint8_t*)&value, sizeof(float)); che poi è del tutto analoga a quella che ha suggerito @C1P8 , l'argomento è quello dei puntatori.

I puntatori sono tipicamente uno degli argomenti con cui si fa più fatica, ma sono fondamentali in C/C++.
Online trovi tantissimo materiale che può aiutarti a schiarire le idee come ad esempio le varie dispense dei corsi universitari (se ne trovano un po' di tutte le università).

Provo a darti qualche spunto, ma non aspettarti che un post in un forum possa essere esaustivo ed il resto ce lo devi mettere da solo.
La libreria Wire come detto include questo metodo:

size_t TwoWire::write(const uint8_t *data, size_t quantity)

Il metodo come vedi si aspetta due parametri: un puntatore a uint8_t (unsigned char, ovvero byte) ed un valore size_t che rappresenta la quantità di byte da trasmettere. L'uso del qualificatore const sta ad indicare che data non sarà modificato in alcun modo dal metodo write().

Tu stai lavorando con i float che come abbiamo già detto, nel mondo dei microcontrollori in questione vengono rappresentati con 4 byte (lasciamo perdere il come per adesso, ma se vuoi approfondire prova a giocare con questo tool online)

Per usare la funzione quindi devi passare il puntatore alla tua variabile float e questo si ottiene con l'operatore & che in poche parole restituisce un valore numerico che rappresenta l'indirizzo di memoria dove viene conservata la variabile float.

Il metodo però accetta un puntatore a uint8_t e non a float quindi è necessario fare il cast del puntatore con l'espressione tra parentesi (uint8_t*) posta prima del puntatore a value.
In questo tipo di funzioni si usano dei puntatori a "generici" byte proprio perché in questo modo si può inviare qualsiasi tipo di variabile con lo stesso metodo: un float un long, un array, una C string (ovvero un array di char), una struct etc etc.
Basta specificare indirizzo di memoria e lunghezza della variabile.

Dal lato opposto dovrai fare l'operazione contraria e ricostruire il float a partire dai 4 byte inviati sul bus.
image

Seriamente

Questa sarebbe la 'strada semplice'?

Io fossi in voi mi farei alcune domande

Tipo:
per quale motivo uno dovrebbe faticare e applicarsi per imparare a comprendere meglio un argomento quando invece potrebbe usare soluzioni raffazzonate di più facile comprensione e rimanere tranquillo nella propria scarsa conoscenza dell'argomento?

Un'altra domanda potrebbe essere:
chissà per quale ragione le librerie del framework Arduino tipo Wire o SPI non usano la manipolazione delle stringhe per inviare dati come float o int?

Se devi trasmettere numeri con un solo decimale, moltiplicali per 10, trasmetti un intero e poi dividili per 10... :slight_smile:

Devo inviare numeri che vanno da 0.0 a 2000.0.

Alberto

Il problema rimane a prescindere dai valori che devi inviare perché comunque il bus i2c, come tutti i bus seriali, invia un byte per volta.

Dai un'occhiata a questo piccolo esempio dove ho giocato un po' con puntatori, array e float. Secondo me ci sono tutti gli elementi che ti servono.

[OFF-TOPIC]

... emmm ... talmente vecchio e stantio che ... :roll_eyes:

R7FA4M1AB.h (1.3 MB)

(... è per tutti i moduli delle varie MCU è la stessa cosa) :joy:

Guglielmo

[/OFF-TOPIC]

Più che vecchio e stantio, l'uso come quello che ne hai fatto per l'esempio di questo thread è illegale secondo lo standard del c++:

The union is at least as big as necessary to hold its largest data member, but is usually not larger. The other data members are intended to be allocated in the same bytes as part of that largest member. The details of that allocation are implementation-defined, except that all non-static data members have the same address. It is undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union. (https://en.cppreference.com/w/cpp/language/union).

Poi di fatto è usabile perchè evidentemente gcc (e credo anche la stragrande maggioranza degli altri compilatori) supporta quel tipo di accesso.

Ciao, Ale.

... perdona, non c'arrivo :woozy_face: mi spieghi dove è esattamente l'errore?

Guglielmo

Dal punto di vista teorico non capisco nemmeno io dove sia l'errore o la difficoltà, però, secondo lo standard, data una union come quella in esempio (1 float,4 uint8_t), se assegni un valore al float questi è il "most recently written", e leggere uno dagli int senza averlo scritto è un "undefined behavior". Ripeto, non capisco tecnicamente perchè, visto che i valori devono avere lo stesso indirizzo... :roll_eyes:
E comunque non è un errore, è solo fuori standard (e vai a sapere perchè...).

Ciao, Ale.

... mi sembra molto strano, condividendo appunto la stessa area di memoria e ... gli esempi che trovo sui siti di C++ sono come il mio :smile:

Del resto il compilatore della R4 (dove l'ho provato) è un C++, compila tranquillamente senza alcun warning (ed io ho il livello di warning alto) e ... FUNZIONA! :wink:

Mah ... un altra delle sexxx mentali del C++ ... :roll_eyes:

Guglielmo

P.S.: Ah, comunque, per uno che NON è abituato a giocare con i puntatori ed al momento NON vuole approfondire ... la mia soluzione, sia lato TX che RX è veramente la più banale.

... mi auto quoto ... gironzolando sui siti alla ricerca di questa cosa (union e C++) ho trovato uno che, anche lui, evidentemente NON ama molto il C++ ... questo quello che scrive :joy:

Premesso che il C++ è un'abominazione senza fine. Un mix di paradigmi di programmazione, che ha prodotto un linguaggio elefantiaco, impossibile da padroneggiare a fondo per chiunque e quindi prono ai bug.

Detto questo:

  • non a tutti piace il modello di programmazione ad oggetti ( ok, puoi programmare proceduralmente in C++, ma è equivalente ad usare il C )
  • non a tutti piacciono le eccezioni ( eh si, c'è gente là fuori che preferisce gestire esplicitamente gli errori )
  • non a tutti piace perdere quel 15–20% in prestazioni, soprattutto quando si parla di programmazione embedded ( leggi "computer poco potenti, dove ogni ciclo di cpu in più è una grazia ricevuta da Dio" )
  • non tutti i sistemi su cui andrà a girare il programma hanno un runtime in grado di supportare le funzionalità del C++. Quando scrivi un sistema operativo, parti da un punto in cui le eccezioni non sono implementabili, la gestione dinamica della memoria nemmeno, il RAII manco a parlarne, le info RTTI nemmeno. Si ok, puoi usare solo le funzionalità "supportate", ma a quel punto useresti solo i costrutti ereditati dal C
    per la tua applicazione è un problema il name mangling. C++ fa dei tripli salti mortali carpiati per assegnare i nomi ai metodi delle varie classi. E ogni compiltare ha le sue convenzioni a riguardo. Inutile dire che non puoi mischiare moduli oggetto ( che sia obj o dll ) prodotti da compilatori C++ diversi. Questo è un grosso problema soprattutto per i sistemi operativi
    la complessità descritta fino ad ora, introduce problemi anche a livelli di compilatori. Le ottimizzazioni troppo aggressive portano all'eliminazione di codice utile ( doh, è successo davvero! ), alla cattiva interpretazione della semantica di blocchi di codice e relativa produzione di codice macchina sbagliato, la non ortogonalità del modello di programmazione introduce lacune nello stesso, comportamenti indefiniti ( molto peggio che nel C ), inefficienze di vario genere

Il punto 1 in particolare è indicativo. Fai caso che nessuno dei linguaggi moderni ( Go, Rust, Javascript per esempio ) implementa il modello OOP canonico, ma hanno tutti optato per un modello basato su prototipi o, più recentemente, sui trait.

... sua opinione :grin:

Guglielmo

Qui intervengo io, che gioco su velluto
in casa, oltretutto

vediamo un po':

significa che NON E' garantito che la union sia grande come il più grande dei suoi elementi,
potrebbe essere più grande, mandando a donne allegre il calcolo dei byte da trasmettere

questo significa che le union statiche, ovvero le globali per esempio, potrebbero avere i membri non allineati,
di nuovo mandando a donne allegre i calcoli coi puntatori

Ma peggio ancora:

significa che,
essendo i due elementi della union identificati attraverso due differenti identificatori,
le ottimizzazioni del compilatore potrebbero usare il valore vecchio del membro che non è l'ultimo aggiornato,
è un problema simile a quello che capita con le ISR,
ma temo che dichiararle "volatile" non serva...

Quindi, il mio parere è: evitate le union

Evitate le union
ora e per sempre
in saecula saeculorum
amen e così sia

PS per ilguargua, le mie parole volevano essere una conferma delle tue

Come vedi le "soluzioni raffazzonate" non sono le mie

Io lavoro sempre seriamente, se sbaglio lo ammetto e mi prendo tutte le mie responsabilità

E non voglio essere associato alla parola "raffazzonato"

E adesso, ma seriamente, mi aspetto delle scuse

Grazie

Mmmm ... mi sembra che si stia andando un po' fuori tema e si stia cominciando a fare "guerre di religione" e "scarne polemiche" ... tutte cose che NON aiutano il OP che, difatti, è un po' che è silente ... direi di piantarla qui ed attendere il ritorno del OP.

Grazie.

Guglielmo

Mi sembra che si stia partendo per la tangente.

Ho usato il sistema della union e dopo una settimana (non continuativamente ovviamente) ci sono riuscito.

//Slave
#include <Wire.h>

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

float_byte myFloat;
boolean receivedRequest = false;

void requestEvent() {
  // Quando viene richiesta una lettura, inviamo i 4 byte del float
  Wire.write(myFloat.b, 4); // Invia i 4 byte
}

void setup() {
  Serial.begin(9600);
  Wire.begin(0x7A);                // join i2c bus with address #7A
  Wire.onRequest(requestEvent); // register event
}

void loop() {
  myFloat.f = 0.12; // Imposta il valore del float da inviare

  if (receivedRequest) {
    Serial.println("Dati inviati");
    receivedRequest = false;
  }
}
//Master
#include <Wire.h>

//Assegno gli ID dei 5 arduino nano in rete I2C
const int slaveAddresses[] = {0x7A, 0x7B, 0x7C, 0x7D, 0x7E}; //WIND, RAIN, SUN/UV, BARO, AUX
//In base al numero di ID dell'array calcolo la quantità di dispositivi
const int numSlaves = sizeof(slaveAddresses) / sizeof(slaveAddresses[0]);

//Assegno ad f un valore che occupa 4 byte dell'unione dentro all'array
union FloatToBytes {
  float f;
  byte b[4];
};

void setup() {
  Serial.begin(9600);
  Wire.begin();
}

void loop() {
  for (int j = 0; j < numSlaves; j++) {//Ciclo for per leggere in sequenza i byte dai "n" dispositivi in rete
    Wire.requestFrom(slaveAddresses[j], 4);//Richiesta 4 byte del float
    if (Wire.available() == 4) {//Se disponibile leggi l'array dei quattro byte del float ricevuto
      FloatToBytes receivedFloat;
      for (int i = 0; i < 4; i++) {
        receivedFloat.b[i] = Wire.read();
      }
      Serial.print("Valore ricevuto da ");
      Serial.print(slaveAddresses[j], HEX);
      Serial.print(": ");
      Serial.println(receivedFloat.f);
    }
  }
  Serial.println("--------------");
  delay(1000);
}

Era probabilmente quello più semplice di tutti SE non si hanno conoscenze specifiche (vd. puntatori e loro uso).

Però ... però ... ora ... consiglio spassionato ... dedicacelo un po' di tempo allo studio più approfondito del linguaggio e magari, un domani, riesci a modificare il codice in modo da evitare "union" e "stringhe" ed utilizzare altri sistemi per fare la stessa cosa :wink:

Guglielmo

Guglielmo sono costretto ad imparare visto la "complessità" di tutto il progetto.
Per me è complesso ovviamente non per voi qui del forum.

Devo realizzare tutti gli step (sketch) a piccoli passi e partendo dalle cose che meno conosco come sta cosa dell'invio di numeri float.

Poi quando tutto funziona nel singolo faccio "l'assemblaggio" dello sketch finale.

Mi ci vorrà qualche mese.... Ma visto che non sono tipo da bar e guardo poca tv spendo così il mio tempo per gli hobby.

Non scappate che ve ne chiederò ancora. :wink:

Ma questo è un ucas gigantesco

Oltre che sbagliato
La .write richiede un puntatore
Solo per caso questo lo è
Sono certo che non è stato intenzionale