Kompletten HEX-String ausgeben; HEX zu float

Hallo,

bei meinem aktuellen Projekt muss/ soll der Arduino einen kompletten HEX-String ausgeben.
Zum Beispiel: 01 10 F0 07 1F C7

Meine aktuelle Lösung sieht so aus:

char string[6] = { 0x01, 0x10, 0xF0, 0x07, 0x1F, 0xC7 };
for (int i = 0; i < 6; i++){
   Serial.write(string[i]);
}

Dieser Code liefert auch das Ergebnis, welches ich haben möchte. Kann man dies auch anders lösen (Einzeiler?)? Wenn ja, wie müsste der Code aussehen?

Natürlich sendet der Arduino nicht nur einen HEX-String sondern soll auch HEX-Daten empfangen und auswerten. Leider stehe ich da momentan komplett auf dem Schlauch!

Ich habe in einem char-Array die Einzelteile von einem HEX-Wert.

char pieces[4] = { "00", "00", "B4", "41" };

Diese Einzelteile müssen zum HEX-Wert 41B40000 zusammengesetzt werden. Danach muss dieser Wert zum float-Wert 22.5 konvertiert werden. Leider habe ich momentan keine Ahnung, welche Funktionen sich da anbieten...

Vielen Dank im Voraus für Eure Hilfe

Gruß... Jan

Einzeiler :grin:

char string[6] = { 0x01, 0x10, 0xF0, 0x07, 0x1F, 0xC7 }; for (int i = 0; i < 6; i++) Serial.write(string[i]);

Kürzer nicht bekannt.

float val = ((float)comp_string)/10;

char pieces[4] = { "00", "00", "B4", "41" };

Das ist syntaktischer Unfug und kompiliert nicht.

Generell macht man eine Hex -> Integer Wandlung mit strtoul():
http://www.cplusplus.com/reference/cstdlib/strtoul/

Ich lasse das mal außen vor und konzentriere mich auf Integer Array nach Float:

unsigned char pieces[] = { 0x41, 0xB4, 0x00, 0x00 };
unsigned long i = pieces[0] << 24 | pieces[1] << 16 | pieces[2] << 8 | pieces[3];
float f = *(float*)&i;

Setzt erst ein Integer Array zu einem einzelnen Integer zusammen.

Dann nimmt die Adresse des Integer, castet diesen auf einen Zeiger auf Float und dereferenziert den Zeiger

Hallo,

vielen Dank für Eure raschen Antworten.

Setze ich die Codes falsch ein, oder wieso bekomme ich immer das Ergebnis 0.00 und nicht 22.50?

Der Code oben ist für ein Integer Array nach Float. Wenn du einen String hast, musst du den String erst mal nach Integer wandeln.

Das habe ich weggelassen weil es da verschiedene Optionen gibt wie man das macht und dein Code nicht lauffähig war. Das einfachste wäre wenn du den String gleich so hast:

char str[] = "41B40000";

Also in einem Stück und in der richtigen Reihenfolge

Dann kann man direkt das machen:

char str[] = "41B40000";
unsigned long i = strtoul(str, NULL, 16);
float f = *(float*)&i;
Serial.println(f);

wenn du 0x41b40000 als float zahl interpretieren willst, musst du ein bisschen mehr tricksen:

entweder

union {long l; float x;} data;
data.long = 0x41b40000;
Serial.println(data.x,2);

oder

long data = 0x41b40000;

float * fp = (float*) &data; // wir interpretieren data als float
Serial.println( *fp );

Vielen vielen Dank für die Antworten und Codes!

Der Code von Serenifly in Post #2 würde mich sehr interessieren, da die Einzelteile in einem großen Buffer stehen. Der Beispielcode funktioniert bei mir leider nicht und ich bin auch nicht in der Lage ihn zu verbessern.. :frowning: Woran kann es liegen, dass der Code nicht richtig funktioniert?

Du hast bisher noch nicht klar gemacht in welchem Format deine Daten überhaupt genau vorliegen.

Liest du die Daten wirklich als String ein, oder als Integer? Das sind zwei völlig verschiedene Dinge.

Wenn es sich tatsächlich um einen C String handelt, d.h. ein null-terminiertes Array aus ASCII Zeichen, könnte man es so in einem einzigen char Array haben:

char str[] = "41B40000";

Dann hast du hier mal angedeutet dass die Reihenfolge anders herum wäre:

char pieces[4] = { "00", "00", "B4", "41" };

Das Problem ist da verwirrend beschrieben

Ich muss gestehen, dass ich das Programm für den Arduino gerade nur theoretisch entwickeln kann, da ich das Gerät, welches angeschlossen werden soll, noch nicht habe.

Das Gerät hat ein eigenes Binäres-Protokoll. So wie ich das jetzt verstanden habe, müssten die Werte als int vorliegen, aber nicht als HEX-Werte sondern DEC-Werte. Wenn das Gerät z.B. 2F (HEX) sendet, entspricht dies '/' (ASCII) bzw. 47 (DEC). Im Buffer-Array müsste dann 47 stehen (da buffer[index++] = Serial.read();).

Genau, die Reihenfolge im Array ist anders herum. Also 00 00 B4 41 (HEX) bzw. 000 000 180 065 (DEC). Jeder der "Einzelwerte" hat einen eigenen Index im Array!

Also überhaupt nichts mit Strings!

Dann geht das in #Reply 2 eigentlich. Du musst das nur richtig einlesen. Die Reihenfolge im Array ist da erst mal egal. Du kannst das dann bei der Wandlung in einen Integer herumdrehen wenn nötig

Wenn es so im Array steht kommt tatsächlich 22.5 heraus:

unsigned char pieces[] = { 0x41, 0xB4, 0x00, 0x00 };

Wenn jetzt die 0x41 als letztes kommt, dreht man bei der Wandlung in einen Integer einfach die Indizes herum:

unsigned long i = pieces[3] << 24 | pieces[2] << 16 | pieces[1] << 8 | pieces[0];

EDIT:
Fehler gefunden. Man kann natürlich einen 16-Bit int (der Shift wird in int gerechnet, auch wenn es ein char ist) nicht um 24 oder 16 nach links schieben. So ist es korrekt (hoffentlich)

unsigned char pieces[] = { 0x00, 0x20, 0xB4, 0x41 };
unsigned long i = (unsigned long)pieces[3] << 24 | (unsigned long)pieces[2] << 16 | pieces[1] << 8 | pieces[0];
float f = *(float*)&i;
Serial.println(f);

Nachtrag:
Die Lösung mit der Union genaugenommen ist besser. Der Trick mit dem Zeiger funktioniert zwar, aber verletzt den C Standard, da man eigentlich Speicher nur durch einen Zeiger mit einem anderen Datentyp ansprechen darf (Ausnahme: char*). Produziert auch eine Warnung. Stichwort: strict aliasing, type punning
Wenn man bei sowas übertreibt kann der Compiler den Code nicht mehr richtig optimieren

Der Compiler kann das eh nicht richtig optimieren, da er nicht weiss was wie über die Leitung kommt.

Bei atmel avr Prozessoren und beim PC werden integer mit dem LSB (least significant byte) auf der niedrigsten Adresse gespeichert, Texte mit dem ersten Zeichen, und floats mit der niederwertigsten Mantisse-Byte. Der Exponent (0x41) ist im höchstwertigen Byte auf der Adresse +3.

Auf dem PC liefert dies

	union { int i; float f; unsigned char c[4]; } data;

	data.i = 0x41b40000;
	printf("%08x = %02x %02x %02x %02x = %f\n", 
           data.i, data.c[0], data.c[1], data.c[2], data.c[3], data.f);

jedenfalls diese Ausgabe wie gewünscht:

41b40000 = 00 00 b4 41 = 22.500000

michael_x:
Der Compiler kann das eh nicht richtig optimieren, da er nicht weiss was wie über die Leitung kommt.

Das war auch noch nicht auf diese Anwendung hier bezogen. Ich habe das auch schon so gemacht. :slight_smile:

Auf einem 8 Bit System ist es meistens egal. Es funktioniert sogar auf dem PC mit 32 Bit ints. Bei komplexeren Sachen und wenn es um Dinge wie endianess, alignment und padding geht (z.B. wenn man structs oder Klassen castet) wird es richtig problematisch (wobei letztere zwei auf einem 8 Bit System wieder irrelevant sind).
Aber auch sonst kann es wohl manchmal vorkommen, dass der Compiler da Fehler produziert was man so liest, da er bestimmte Annahmen trifft und man diese umgeht.

beim PC werden integer mit dem LSB (least significant byte) auf der niedrigsten Adresse gespeichert

Das ist nicht universell. Das ist System an sich ist zwar Little Endian, aber Java z.B. ist Big Endian!