Hallo,
ich versuche im Moment mit einem Nucleo-F411RE Board die 4 Bytes eines
Floats über UART zu senden. Dazu habe ich mit CubeMX das Grundgerüst
generiert, welches eigentlich nur den UART2 mit 115200 Baud
initialisiert und folgenden Code geschrieben:
Mit diesem Code empfange ich allerdings auf der PC-Seite 6 Byte. Wenn
ich f=0.1412 setze, empfange ich sogar 8 Byte.
Wenn ich einen Text sende kommt dieser richtig an.
Ich vermute mal, dass dein Empfänger mit den nicht-ASCII-Bytes
durcheinanderkommt. Was empfängst du denn? (Die Bytes bitte, nicht die
dadurch repräsentierten ASCII-Zeichen.) Was kommt an, wenn du z.B.
"\x3e\x10\x96\xbc",4 sendest?
Du solltest Dich mit dem Unterschied zwischen Strings und einer
beliebigen Folge von Bytes in C beschäftigen.
Woran erkennt Dein HAL_UART_Transmit das Ende der Übertragung?
Vielleicht an einer `\0`?
Und selbst wenn es Dir gelingt, eine Float-Zahl binär zu übertragen,
muss das auf der Empfängerseite nicht das Gleiche bedeuten wie auf der
Senderseite.
schmurx schrieb:> Woran erkennt Dein HAL_UART_Transmit das Ende der Übertragung?> Vielleicht an einer `\0`?
Nö. Die Funktion will die Länge des Arrays mitgeteilt bekommen. Die ist
in diesem Fall mit 4 angegeben, was der Länge eines floats in Bytes
entspricht.
@TO:
Welches Terminalprogramm verwendest du denn?
Die Methode mit der Union hat sich bei mir bewährt, solange die Endians
Maschinen übereinstimmen.
Das heir ist definitiv in Ordnung:
> float f = 1.0;> HAL_UART_Transmit(&huart2, (uint8_t*)&f, 4, HAL_MAX_DELAY);
Die 4 weist den Mikrocontroller an, genau 4 Byte zu senden. Und das
funktioniert auch, da bin ich 100% sicher.
Im Ruhezustand hat die serielle Leitung HIGH Pegel. Vielleicht hast du
mangels Pull-Up während der Start-Phase kurzzeitig zufällige Signale auf
der Leitung, die dein Empfänger mit zählt.
Benutze mal das Hammer Terminal oder einen Logic Analysator zum
Untersuchen.
Unabhängig vom eigentlichen Problem (das ich nicht kenne), hast du hier
undefiniertes Verhalten, weil du float* nach uint8_t* castest (strict
aliasing).
Regeln für C++, aber gelten analog für C ebenso:
https://en.cppreference.com/w/cpp/string/byte/memcpy.html> Where strict aliasing prohibits examining the same memory as values of> two different types, std::memcpy may be used to convert the values.https://en.cppreference.com/w/cpp/language/object#Strict_aliasing> Accessing an object using an expression of a type other than the type> with which it was created is undefined behavior in many cases, see> reinterpret_cast for the list of exceptions and examples.
D.h. du solltest es eher so machen:
Der compiler erkennt dann, dass du float nach uint8_t array casten
möchtest und optimiert das memcpy komplett raus. Es ist also identisch
zu deinem (uint8_t*) cast, nur ohne undefiniertes Verhalten.
W. W. schrieb:> Unabhängig vom eigentlichen Problem (das ich nicht kenne), hast du hier> undefiniertes Verhalten, weil du float* nach uint8_t* castest (strict> aliasing).
Das bezweifle ich stark. Ein Pointer ist ein Pointer.
Solange der Compiler das zulässt (warum sollte er nicht?) wird der
Pointer never ever zu einer falschen Position im Speicher zeigen.
Und selbst wenn ich mich irren sollte, so wird doch aus der 4 keine 6.
Die Schnittstelle müsste also trotzdem 4 Bytes senden.
Stefanus F. schrieb:> W. W. schrieb:>> Unabhängig vom eigentlichen Problem (das ich nicht kenne), hast du hier>> undefiniertes Verhalten, weil du float* nach uint8_t* castest (strict>> aliasing).>> Das bezweifle ich stark. Ein Pointer ist ein Pointer.> Solange der Compiler das zulässt (warum sollte er nicht?) wird der> Pointer never ever zu einer falschen Position im Speicher zeigen.>> Und selbst wenn ich mich irren sollte, so wird doch aus der 4 keine 6.> Die Schnittstelle müsste also trotzdem 4 Bytes senden.
Ich schrieb ja, es sei unabhängig vom eigentlichen Problem, aber
solche casts sind schlichtweg UB. Die funktionieren eventuell
vielleicht bei Vollmond und Compiler Version X. GCC macht i.d.R.
vermutlich das Richtige, aber es ist und bleibt UB und darf jederzeit
anders (falsch) funktionieren.
Vielleicht ist es für den Fall eines uint8_t* doch OK:
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
§6.5. Abs. 7:
> An object shall have its stored value accessed only by an lvalue expression that
has one of
> the following types: 88)> — a type compatible with the effective type of the object,> — a qualified version of a type compatible with the effective type of the
object,
> — a type that is the signed or unsigned type corresponding to the effective type
of the
> object,> — a type that is the signed or unsigned type corresponding to a qualified
version of the
> effective type of the object,> — an aggregate or union type that includes one of the aforementioned types among
its
> members (including, recursively, a member of a subaggregate or contained union),
or
> — a character type.
§6.2.5 Abs. 15:
> The three types char, signed char, and unsigned char are collectively called> the character types. The implementation shall define char to have the same
range,
> representation, and behavior as either signed char or unsigned char. 45)
Demnach ist es also zulässig nach uint8_t* zu casten.
Das Problem könnte aber sein, dass uint8_t nicht zwingend ein unsigned
char sein muss.
W. W. schrieb:> Das Problem könnte aber sein, dass uint8_t nicht zwingend ein unsigned> char sein muss.
Damit bewegen wir uns allerdings in Richtung einer akademischen
Diskussion fernab jeder Realität. Die Sprache C verleitet uns regelrecht
dazu, da sie einerseits hardware-nah sein will, andererseits aber
Annahmen über die Hardware verbietet.
Und jetzt zurück zur Praxis:
Auf welchem Computer ist ein unsigned char nicht gleichen einem uint8_t?
Ich denke: keine.
Und ich erwarte auch nicht, dass sich daran jemals etwas ändert.
W. W. schrieb:> Ich schrieb ja, es sei unabhängig vom eigentlichen Problem, aber> solche casts sind schlichtweg UB.
M.E. in diesem Fall nicht.
Pointer aliasing inkompatibler Typen ist undefiniertes Verhalten, das
ist richtig, aber pointer aliasing zu char Typen (und das ist hier
gegeben) ist (wg. endianess) lediglich implementation defined (zumindest
in C, in C++ weiß ich's nicht).
Wär' das nicht so, wäre deine memcpy()-Lösung auch UB.
Stefanus F. schrieb:> Benutze mal das Hammer Terminal oder einen Logic Analysator zum> Untersuchen.
Vielen dank. Es lag tatsächlich an meinem Terminal. Sowohl cutecom als
auch das Serial Terminal Plugin für CLion zeigen mir hier einfach
falsche Werte an...
Stefanus F. schrieb:> Ich denke: keine. Und ich erwarte auch nicht, dass sich daran jemals> etwas ändert.
Bei GCC und Clang gab es Bestrebungen uint8_t als extended Integer, also
non-character Type zu definieren, gescheitert ist das hauptsächlich an
den großen Änderungen, die an den Compilern nötig wären! (Mal davon
abgesehen, dass das gar nichts mit der Zielplatform zu tun hat)