Convert float werte in eine Byte-array

Hallo zusammen,
ich hoffe diese Frage wurde noch nicht allzu oft beantwortet. Ich suche ein Beispiel im "Arduinostyle".
Ich möchte 6 float-Werte in ein Byte.array packen, um es dann an ein Processingprogramm zu via Serial.write senden. Umgekehrt habe ich es u.a. mit ByteBuffer geschafft.
Vielen Dank im Voraus
Kucky

Float hat 4 Byte. Das sollte also genau wie ein long zu wandeln gehen:

void floatToByte(byte* arr, float value)
{
      long l = *(long*) &value;

      arr[0] = l & 0x00FF;
      arr[1] = (l >> 8) & 0x00FF;
      arr[2] = (l >> 16) & 0x00FF;
      arr[3] = l >> 24;
}

void loop()
{
     byte arr[4];
     float test = 1.234;
     floatToByte(arr, test);
}

Der Trick dabei ist die Binär-Darstellung des Floats in einem int/long zu erhalten. Das habe ich mir hier abgeguckt:

Dabei wird die Adresse des floats auf einen long pointer gecastet und dann dereferenziert. Als Alternative kann man dazu auch eine Union nehmen.

In Visual C++ funktioniert das. Hier der Test Code:

#include "stdafx.h"
#include <iostream>
#include <bitset>
using namespace std;

void floatToByte(unsigned char* arr, float value)
{
      int i = *(int*) &value;
      arr[0] = i & 0x00FF;
      arr[1] = (i >> 8) & 0x00FF;
      arr[2] = (i >> 16) & 0x00FF;
      arr[3] = (i >> 24);
}

float byteToFloat(unsigned char* arr)
{
      int i = arr[0] | (arr[1] << 8) | (arr[2] << 16) | (arr[3] << 24);
      return *(float*) &i;
}

int _tmain(int argc, _TCHAR* argv[])
{
   unsigned char arr[4];
   float test = 6.6666;
   floatToByte(arr, test);
	
   int i = *(int*) &test;
   cout << test << endl << bitset<32>(i) << endl;
   cout << bitset<8>(arr[3]) << bitset<8>(arr[2]) << bitset<8>(arr[1]) << bitset<8>(arr[0]) << endl;
   cout << byteToFloat(arr) << endl;

   char c;
   cin >> c;
   return 0;
}

Vielen vielen Dank,
werde ich ausprobieren und dann melden.
LG
Kucky

@serenifly: Hallo, da brauchst Du gar keine Tricks :smiley:

Hallo,

wie schon erwähnt, ist der richtige Datentyp für solche Umwandlungen in C++ die Union. Hier ein Sketch, der das Gewünschte leistet:

void setup() {
  // Serielle Schnittstelle mit 9600bps initialisieren
  Serial.begin(9600);
}
  
  
void loop()
{
    union speicherTeiler {
        // lang enthält den Wert Float-Zahl
        float lang;
        struct {
            byte b1;
            byte b2;
            byte b3;
            byte b4;
        } geteilt;
        // Die Struktur geteilt teilt sich den Speicherplatz
        // mit der float Variablen, b1 bis b4 enthalten also den Wert
        // von lang als 4 einzelne Bytes
    };
      
    // Eine Variable vom Typ speicherTeiler definieren
    union speicherTeiler st;
      
    // Alle Bits in st.lang auf 1
    st.lang = 3.14f;
      
    // Wert von st.lang ausgeben
    Serial.print("st.lang:          ");
    Serial.println(st.lang);
      
    // Wert von st.geteilt.kurz1 ausgeben
    Serial.print("st.geteilt.b1: ");
    Serial.println(st.geteilt.b1);
    Serial.print("st.geteilt.b2: ");
    Serial.println(st.geteilt.b2);
    Serial.print("st.geteilt.b3: ");
    Serial.println(st.geteilt.b3);
    Serial.print("st.geteilt.b4: ");
    Serial.println(st.geteilt.b4);
      
      
    // 3 Sekunden warten, bevor loop() erneut durchlaufen wird
    delay(3000);
}

Zur Erklärung wie das genau funktioniert verweise ich auf den C++-Kurs.

Die Rückwandlung geht entsprechend: Vier sinnvolle Bytes in die Union-Members b1 bis b4 eintragen und dann enthält st.lang die Fließkommazahl.

Gruß,
Ralf

PS: Das funktioniert natürlich auch in Visual C++ und allen anderen C- und C++-Dialekten.

Habe ja noch hingeschrieben, dass man auch Unions nehmen kann :slight_smile:

Der Vorteil das in ein richtiges Byte-Array zu verpacken statt ein struct liegt darin, dass man dann mit einer for-Schleife sehr schön darüber iterieren kann. Vor allem wenn man das von einem Float Wert auf 6 erweitert. Dann muss man nur das Ziel-Array 24 Bytes groß machen und die Indizes anpassen (4 * i + x). Man kann dann ein Float-Array übergeben und bekommt ein Byte-Array zurück. Und Serial.write() schluckt Byte-Arrays in einem Befehl, wenn man die Größe mit angibt.

Alles in Unions zu machen belegt natürlich weniger Speicher, da man die Werte nicht in zwei Darstellungs-Arten abspeichern muss. Aber das ist bei den paar Bytes nicht so kritisch. Letztlich Geschmackssache :slight_smile:

Oder man macht es so ähnlich:

  union memory
  {
     float floats[6];
     byte bytes[24];
  };

  memory values;

Dann schreibt man die 6 Float Werte rein und kann das Array direkt mit Serial.write(values.bytes, 24) verschicken :slight_smile:

Serenifly:
Habe ja noch hingeschrieben, dass man auch Unions nehmen kann :slight_smile:

Aber nur ganz klein und ganz weit hinten auf meinem Bildschirm. Deswegen gilt das nicht richtig. ]:smiley:

Gruß,
Ralf

Vielen Dank für alle Antworten. Ich habe ein wenig Erfahrung mit C/C++, aber sobald es um Serial... und Bytes geht, steh ich auf dem Schlauch.
Brauche etwas um das alles umzusetzten, aber dann melde ich mich.

LG
Kucky

Also, ich habe gesehen, dass die 4 Zahlen (195, 245, 72 und 64) einem 4*8 Bitmuster entsprechen. Warum gerade diese Zahlen, ist mir im Moment nicht klar. Aber da kommt noch :~. Vorab aber noch 2 Fragen bitte:

  1. Welchen Werte trage ich bei Serial.write(x, y) ein?
  2. Wie kann ich mehrere Werte in ein Bitmuster bekommen? Oder wäre dies ein Bytearray von "union speicherTeiler st[6];"?

LG Kucky

  1. Welchen Werte trage ich bei Serial.write(x, y) ein?

Siehe hier:
http://arduino.cc/en/Serial/Write

x ist das Byte Array. y ist die Anzahl der Bytes

Wie kann ich mehrere Werte in ein Bitmuster bekommen? Oder wäre dies ein Bytearray von “union speicherTeiler st[6];”?

Das würde gehen. Aber praktischer ist meiner Meinung nach das:

union memory
  {
     float floats[6];
     byte bytes[24];
  };

  memory values;

  values.floats[0] = ....;
  values.floats[1] = ....;
  ....

Es sei den du willst die jeweiligen Bytes die zu einem Float gehören aus irgendeinem Grund getrennt behandeln. Dann ist ein Array aus Unions wiederum besser.

Bei der anderen Lösung sollte das gehen:

void floatToByte(byte* arr, float* values)
{
      for(int i = 0; i < 6; i++)
      {
         long l = *(long*) &values[i];

         arr[i * 4] = l & 0x00FF;
         arr[i * 4 + 1] = (l >> 8) & 0x00FF;
         arr[i * 4 + 2] = (l >> 16) & 0x00FF;
         arr[i * 4 + 3] = l >> 24;
     }
}

Und dann Arrays von Größe 24 und 6 übergeben. Das Verunden ist übrigens glaube ich gar nicht nötig, da da eine Typumwandlung von long nach byte erfolgt.

Sorry, Frage falsch gestellt:
Was wäre in Deinem Beispiel das "Byte-Array"? Die Anzahl ist wohl 4.

Den 2ten Teil muss ich sacken lassen. :wink:

LG
Kucky

Wenn du den Code von Schachmann hast, dann hast du gar kein Byte Array. Der hat die Bytes in einem struct anlegt. Die müsstest du dann einzeln verschicken.

Wenn du eine Union pro Float mit Byte Array hast:

  union memory
  {
     float fl;
     byte bytes[4];
  };

  union memory value;

Dann geht das verschicken einfach so:
Serial.write(value.bytes, 4);

Oder wenn du die 4 Floats in eine Union packst:

  union memory
  {
     float floats[4];
     byte bytes[16];
  };

  union memory values;

  Serial.write(values.bytes, 16);

Die Anzahl ist eigentlich egal. Das Byte Array muss nur 4 mal so groß wie das Float Array sein. Man kann das auch schön so machen:

  #define NUM_VALUES 4
  #define NUM_BYTES (NUM_VALUES * 4)

  union memory
  {
     float floats[NUM_VALUES];
     byte bytes[NUM_BYTES];
  };

  union memory values;

Dann muss man nur die Konstante NUM_VALUES ändern und alles andere passt sich automatisch an. Bei Serial kannst du dann auch Serial.write(values.bytes, NUM_BYTES) machen und es passt sofort.

EDIT:
Sorry. Da war noch ein Fehler bei Serial drin. Muss natürlich "values.bytes" heißen. Beim ersten mal hatte ich noch richtig gemacht. Das ist eigentlich genau die Stelle um die es ging :frowning:

Ich glaube jetzt sehe ich schon viel klarer. Ist wahrscheinlich ganz einfach, wenn man es einmal verstanden hat. Werde dies nun für meine Zwecke ausprobieren.

Vielen Dank für die Hilfe und die Geduld.

LG
Kucky

EDIT Gesehen, danke.

Habe ich es hinbekommen?

  #define NUM_VALUES 9
  #define NUM_BYTES (NUM_VALUES * 4)

union memory{                
  byte asBytes[NUM_BYTES];    
  float asFloat[NUM_VALUES];    
};                      
union memory sendToArduino;                   

//  0  - 3:  float Xangle
//  4  - 7:  float Yangle
//  8  - 11: float Zangle  
//  12 - 15: float Altitude
//  16 - 19: float Temperature
//  20 - 23: float Rpses0
//  24 - 27: float Rpses1
//  28 - 31: float Rpses2
//  32 - 35: float Rpses3

void setup()
{
	sendToArduino.asBytes[0] = 12.;
	sendToArduino.asBytes[1] = 3.4;
	sendToArduino.asBytes[2] = 0;
	sendToArduino.asBytes[3] = 52.0;
	sendToArduino.asBytes[4] = 24.1;
	sendToArduino.asBytes[5] = 555;
	sendToArduino.asBytes[6] = 558;
	sendToArduino.asBytes[7] = 556;
	sendToArduino.asBytes[8] = 559;
}

void loop()
{
	Serial.write(sendToArduino.asBytes, NUM_BYTES);
}

Kucky

sendToArduino.asBytes[0] = 12.; <— da kommt wahrscheinlich ein Fehler

Ansonsten sollte theoretisch funktionieren. Musst halt mal schaun was am anderen Ende ankommt :slight_smile:

In loop solltest du allerdings ein Delay einbauen, damit Zeit ist den Ausgangspuffer zu leeren.

Den Fehler habe ich schon korrigiert, danke. Im richtigen Progi sollte Serial.flush das wohl erledigen, denke ich.
Kucky

Hey,
also aus meiner Sicht funktioniert es. Ich glaube, ich habe es auch verstanden. :slight_smile: Die Ausgabe in VS per Serial.print lautet:
asBytes[0]: 12
asBytes[1]: 3
asBytes[2]: 0
asBytes[3]: 52
asBytes[4]: 24
asBytes[5]: 43
asBytes[6]: 46
asBytes[7]: 44
asBytes[8]: 47

Dieses Processingprogi macht aber seltsames:

import processing.serial.*;
import java.nio.ByteBuffer;

Serial myPort;

void setup() {
  println(Serial.list());
  myPort = new Serial(this, Serial.list()[1], 9600);
  myPort.write(65); // Only for test
}

void draw() {
  // Expand array size to the number of bytes you expect
  byte[] inBuffer = new byte[8];  
  while (myPort.available() > 0) {
    inBuffer = myPort.readBytes();
    myPort.readBytes(inBuffer);
    if (inBuffer != null) {
       println(inBuffer);
    }
  }
}

Die Augabe lt.:
[0] 12
[1] 3
[0] 52
[1] 24
[2] 43
[3] 46
[4] 44
[5] 47 ← Bis hierhin OK, aber falscher Index
Dann wird´s für mich “seltsam”
[0] 13
[1] 10
[2] 97
[3] 115
[4] 66
[5] 121
[6] 116
[7] 101
[8] 115
[9] 91
[10] 48

Könnte ihr hier nochmal bitte helfen? Ich meine in Bezug auf convertieren nach float.

Gruß
Kucky

Glaube da ist noch ein grundlegender Fehler drin, den ich übersehen hatte:

void setup()
{
	sendToArduino.asBytes[0] = 12.;
	sendToArduino.asBytes[1] = 3.4;
	sendToArduino.asBytes[2] = 0;
	sendToArduino.asBytes[3] = 52.0;
	sendToArduino.asBytes[4] = 24.1;
	sendToArduino.asBytes[5] = 555;
	sendToArduino.asBytes[6] = 558;
	sendToArduino.asBytes[7] = 556;
	sendToArduino.asBytes[8] = 559;
}

Du musst diese Werte in asFloat eintragen! Und dann das asBytes Array verschicken

Dann ist der Eingangspuffer zu klein um mehr als 8 Byte einzulesen:
byte inBuffer = new byte[8]

Und du musst die Werte auf der Empfangsseite wieder in Float konvertieren (das kannst du auf die gleiche Art machen nur anders herum). Oder aus Serial direkt Float auslesen wenn das geht.

Habe ich geändert. In habe es nun mit 9*4 Byte probiert. Es kann doch nur 9 oder 36 sein oder?
[0] -102
[1] -103
[2] 65
[0] 65
[1] -102
[2] -103
[3] 89
[4] 64
[5] 80
[6] 66
[7] -51
[8] -52
[9] -64
[0] 65
[1] -64
[2] 10
[3] 68
Wieder stimmt der Index nicht. Wie gesagt, wenn ich einmal den richtigen Weg sehe, ist alles gut.
In Java gibt es kein "union", muss ich also anders hinbekommen.
Gruß
Kucky

In Java gibt es kein "union", muss ich also anders hinbekommen.

--> java.nio.FloatBuffer