Problème d'envoi de float sous forme d'octet

Bonjour tout le monde,

J'essaie d'envoyer des variables float sous forme d'octet mais je rencontre des soucis lors de la compilation de mon code dont une partie est donnée ci-dessous. Dans ma condition if, quand je mets tout simplement Serial.write (Voltage), la compilation marche mais ce n'est pas la bonne façon de faire car ma variable Voltage est un float (4 octets) et non un simple octet. Je voudrais que ça marche avec les 4 octets mais il affiche des erreurs que je ne parviens pas à résoudre.
Si quelqu'un pourrait m'aider ça serait top :slight_smile:

union {
  uint8_t bytes[4];
  float d_value;
} bytefloat;
float send_buffer[36]; // Nombre tottal d'octet à envoyer

bytefloat.d_value = Voltage;

    send_buffer[0] = bytefloat.bytes[0]; // Envoie du premier octet
    send_buffer[1] = bytefloat.bytes[1]; // Envoie du deuxième octet
    send_buffer[2] = bytefloat.bytes[2]; // Envoie du troisième octet
    send_buffer[3] = bytefloat.bytes[3]; // Envoie du quatrième octet

    if (!Serial.write (Voltage,4)) {
      while (1);             // Attendre que l'envoie de donnée soit fini
      }

En C++ l'usage d'une union pour accéder aux octets est "Undefined Behavior", ça veut dire que le code peut faire n'importe quoi.

faites simplement:

float v = 123.456;
Serial.write((uint8_t*) &v, sizeof v);

PS:
write() retourne le nombre d'octets émis, donc ceci n'est pas une boucle d'attente d'envoi

if (!Serial.write (Voltage,4)) {
  while (1);             // Attendre que l'envoie de donnée soit fini
}

vous avez écrit: si write n'a pas pu envoyer les données alors le programme se coince dans une boucle infinie...

Merci @J-M-L pour votre réponse. J'utilise du C et ,non du C++. Est ce que c'est pareil ?
Pour votre remarque importante, je pensais que cette ligne de code pourrait permettre à l'arduino d'attendre que l'envoie du premier float soit fini avant d'envoyer le suivant. Comment puis-je faire cela alors ?
Merci d'avance !

Si vous compilez avec l'IDE c'est un compilateur C++... Mais oui, ça fonctionnera pareil en C

si vous voulez attendre la fin de l'émission (pourquoi ?) faites un

Serial.flush(); // attente de l'émission

juste après le write()

Est ce que vous voulais dire aussi que je n'ai plus besoin de ces commandes du coup ?

bytefloat.d_value = Voltage;

    send_buffer[0] = bytefloat.bytes[0]; // Envoie du premier octet
    send_buffer[1] = bytefloat.bytes[1]; // Envoie du deuxième octet
    send_buffer[2] = bytefloat.bytes[2]; // Envoie du troisième octet
    send_buffer[3] = bytefloat.bytes[3]; // Envoie du quatrième octet

Vos deux lignes de code font quoi concrètement ?

float v = 123.456;
Serial.write((uint8_t*) &v, sizeof v);

ça me semble très simple par rapport à ce que j'ai proposé. J'aimerais en savoir un peu plus si possible :slight_smile:

cette ligne de code

Serial.write((uint8_t*) &v, sizeof v);

va envoyer les 4 octets d'un coup pour vous, vous n'avez pas besoin de l'union.

&vc'est l'adresse en mémoire du nombre décimal, un pointeur vers le premier octet. Comme on pointe vers un nombre de type float, ce pointeur est de type (float*) mais la fonction write ne gère pas ce type de pointeur. Le C ou C++ vous laisse transformer ce pointeur vers un type de pointeur d'octets, et donc c'est pour cela que je mets (uint8_t*) devant pour le convertir en pointeur vers un octet, comme attendu par la fonction write(). Ensuite je dis combien d'octets envoyer, l'opérateur sizeofsert exactement à cela (ici combien d'octets sont nécessaires pour représenter la variable v)

ça me semble très simple par rapport à ce que j'ai proposé

Qui a dit que ça devait être compliqué :wink:

Si vous voulez en savoir plus , j'ai un tuto intitulé: Introduction à la mémoire et aux pointeurs sur Arduino

Il faut aussi se poser la question du récepteur.
S'il s'agit d'un autre logiciel en C sur PC ou ARDUINO ou autre, pas de problème. La norme IEEE-754 impose le format (23 bits pour la mantisse, 8 bits pour l'exposant, 1 bit pour le signe).

S'il s'agit d'un logiciel codé dans un autre langage, cela risque d'être problématique.
Exemple : PYTHON où les floats sont codés sur 64bits.

La norme IEEE-754 impose également le format des nombres flottants sur 64 bits (52 bits pour la mantisse, 11 bits pour l'exposant, 1 bit pour le signe).

Si la vitesse de transmission n'est pas un critère essentiel, pourquoi ne pas envoyer ce nombre sous forme ASCII ?
Serial.print(123.456);

Merci pour cette explication précise. Du coup dans mon cas je peux faire que ça ?

float Voltage;
    SerialUSB.write((uint8_t*) &Voltage, sizeof Voltage);

En tout cas ça marche en compilation mais je ne sais pas si j'ai raison de faire comme ça...

parce que ça va envoyer 123.45 et on va perdre le 6 :wink:

oui ça va fonctionner (au sens où les 4 octets seront émis)

Serial.print permet de préciser le nombre de décimales :
Serial.print(123.456, 3);

c'est mieux :slight_smile: (un séparateur aiderait aussi à la lecture du côté récepteur)

Le langage du logiciel de réception va normalement être en C donc je pense qu'il y aura pas de problème.

Ne pas oublier non plus qu'en cas de ratage d'un octet, le récepteur risque de se désynchroniser, et attendre longtemps un quatrième octet qui n'arrive pas.
Serial.println(123.456, 3);
Serial.println() ajoutera un caractère fin de ligne que le récepteur pourra facilement interpréter comme terminateur.
Voir par exemple Serial.readBytesUntil() : si le récepteur est un ARDUINO, ou ESP.

Ensuite utiliser atof() pour convertir en float côté récepteur.

Est ce que la norme impose l'ordre des octets?

Dans quel cas ce problème arrive ? et comment y remédier ? Je ne compte pas utiliser le Serial.print()...

La norme ne le précise pas.
Il n'y a pas de raison de penser qu'un float soit stocké de la même façon en fonction de l'endianness du processeur.

A mon avis il faut bannir atof() et atoi() et plutôt recommander utiliser strtod() et strtol() en utilisant le pointeur. ça permet de savoir si la fonction a retourné 0 parce que le nombre était bien 0 plutôt que parce que la chaîne fournie ne contenait pas un nombre décodable.

exemple de code:

char d1[] = "123.456ABCD";
char d2[] = "ABCD";

void setup() {
  Serial.begin(115200);
  char* ptr;
  double x;

  x = atof(d1);
  Serial.print("atof(d1) = ");
  Serial.println(x, 10);

  x = strtod(d1, &ptr);
  if (ptr == d1) Serial.println("d1 Erreur de contenu");
  else {
    Serial.print("strtod(d1) = ");
    Serial.println(x, 10);
    if (*ptr != '\0') {
      Serial.print("Il y a avait du texte après le scan ");
      Serial.println(ptr);
    }
  }

  Serial.println();

  x = atof(d2);
  Serial.print("atof(d2) = ");
  Serial.println(x, 10);

  x = strtod(d2, &ptr);
  if (ptr == d2) Serial.println("d2 Erreur de contenu");
  else {
    Serial.print("strtod(d2) = ");
    Serial.println(x, 10);
    if (*ptr != '\0') {
      Serial.print("Il y a avait du texte après le scan ");
      Serial.println(ptr);
    }
  }
}

void loop() {}

Comme il s'agit d'une tension, la surcharge occasionnée par l'envoi en ASCII devrait être faible.

"12.36" -> 6 octets en comptant le terminateur
Ensuite le temps de conversion côté récepteur ne devrait pas être excessif, sans commune mesure avec le temps de conversion ADC côté émetteur.