Nach dem ich mich jetzt endgültig in der Dokumentation verheddert habe
und es momentan auch leider nicht einfach mal ausprobieren kann, frage
ich mal ganz doof hier nach:
Ich habe einen PIC32, der per MPLAB-IDE programmiert wird. Dort habe ich
folgende Struktur definiert:
1
struct raw_data
2
{
3
union
4
{
5
unsigned char bData[4];
6
unsigned int iData;
7
} d;
8
};
Wen ich in iData jetzt z.B. 0xDEADBEEF reinschreibe - was steht dann in
bData[0], bData[1], bData[2] und bData[3] drin?
Gowron schrieb:> Peter D. schrieb:>> Schreib den Code sauber, dann spielt das keine Rolle.>> Schön dass du eine Frage beantwortest, die nie gestellt wurde...
Genau die Frage hast Du gestellt ;-)
Über welche Sprache reden wir denn hier?
Wilhelm M. schrieb:> Über welche Sprache reden wir denn hier?
Es ist C/C++, ich wüsste nicht, dass die bereits erwähnte MPLAB-IDE was
anderes kann. Und ich habe auch klar und deutlich die Frage nach der
verwendeten Byteorder gestellt, und nicht danach, wie ich das
Byteorder-Problem umgehen kann.
Also Danke an Mr.T und ansonsten Danke für nichts!
Gowron schrieb:> Schön dass du eine Frage beantwortest, die nie gestellt wurde...
Stimmt, mit steigender Programmiererfahrung kommst Du irgendwann auch
von selber darauf. Ich wollte Dir halt nur die Mühe ersparen.
Peter D. schrieb:> Gowron schrieb:>> Schön dass du eine Frage beantwortest, die nie gestellt wurde...>> Stimmt, mit steigender Programmiererfahrung kommst Du irgendwann auch> von selber darauf. Ich wollte Dir halt nur die Mühe ersparen.
Na dann erkläre es mir doch mal mit deiner fantastischen
Programmiererfahrung: Ich habe als Eingangsdatum den 32-Bit-Wert und
möchte diesen byteweise in einer definierten Reihenfolge auf eine
serielle Leitung schicken. Was genau hilft mir dein komischer Code
dabei?
Genau: gar nichts!
Gowron schrieb:> Was genau hilft mir dein komischer Code> dabei?
Er erklärt das Prinzip, wie es unabhängig von Compiler und Target immer
richtig ist.
Natürlich mußt Du für die Byteausgabe das Prinzip umdrehen:
mhm...
es gibt ja eigentlich 2 möglichkeiten und im zweifelsfalle probiert man
es einfach aus :)
aber beim PIC ist LE (soweit mir bekannt) bei allen Prozessoren üblich.
Der PIC32 als MIPS-Derivat macht hier keine Ausnahme.
morph1 schrieb:> es gibt ja eigentlich 2 möglichkeiten und im zweifelsfalle probiert man> es einfach aus :)
Der TO hat das Problem und/oder die Lösung dazu allerdings wohl gar
nicht verstanden.
Gowron schrieb:> Ich habe als Eingangsdatum den 32-Bit-Wert und> möchte diesen byteweise in einer definierten Reihenfolge auf eine> serielle Leitung schicken.
Dafür leihe ich mir, wenn verfügbar, Funktionen wie htonl/ntohl aus der
BSD Socket Lib aus. Dann kann mir die Byteorder egal sein.
>Die sind normalerweise Big Endian.PIC32MX Family Reference Manual (DS61113C-page 2-44)
CONFIG: Register; CP0 Register
bit 15 BE: Big Endian bit
Indicates the Endian mode in which the processor is running, PIC32MX
is always little endian.
0 = Little endian
1 = Big enidan <== das ist natürlich ein rechtschreibfehler im
Referenz Manual, bleibt aber LE.
;-)
Leider sind trailing-requires-clauses noch nicht so ganz richtig im gcc
implementiert. Sonst könnte man das static_assert() auch noch
sinnvollerweise ersetzen.
A. K. schrieb:> Dafür leihe ich mir, wenn verfügbar, Funktionen wie htonl/ntohl aus der> BSD Socket Lib aus.
Die nützen auf Big Endian Systemen aber nichts.
Dirk B. schrieb:> A. K. schrieb:> Dafür leihe ich mir, wenn verfügbar, Funktionen wie htonl/ntohl aus der> BSD Socket Lib aus.>> Die nützen auf Big Endian Systemen aber nichts.
Doch. Man hat immer eine definierte Bytereihenfolge für Ein-/Ausgabe -
egal, ob das System Little- oder Big-Endian ist.
Glaubst Du tatsächlich, dass Big-Endian-Systeme nicht am Internet
teilnehmen können?
Frank M. schrieb:> Doch. Man hat immer eine definierte Bytereihenfolge für Ein-/Ausgabe -> egal, ob das System Little- oder Big-Endian ist.>> Glaubst Du tatsächlich, dass Big-Endian-Systeme nicht am Internet> teilnehmen können?
Internet ist, wenn ich mich recht erinnere, Big Endian.
Diese Routinen (ntoh und hton) machen auf BE-Systemen also Nichts.
Damit kommst du auch nicht auf LE.
Der TO wollte auf LE konvertieren.
Dirk B. schrieb:> A. K. schrieb:>> Dafür leihe ich mir, wenn verfügbar, Funktionen wie htonl/ntohl aus der>> BSD Socket Lib aus.>> Die nützen auf Big Endian Systemen aber nichts.
Um eine dedizierte Byteorder (nicht nur BE) zu erreichen bietet sich
Dirk B. schrieb:> Der TO wollte auf LE konvertieren.
Wo steht das?
Ich finde nur das:
Gowron schrieb:> Ich habe als Eingangsdatum den 32-Bit-Wert und möchte diesen byteweise> in einer definierten Reihenfolge auf eine serielle Leitung schicken.
Er braucht eine definierte Reihenfolge. Von LE schreibt er nichts.
Gowron schrieb:> Ich habe als Eingangsdatum den 32-Bit-Wert und> möchte diesen byteweise in einer definierten Reihenfolge auf eine> serielle Leitung schicken.
Das union-Konstrukt aus uint32 und byte[4] ist für diese Anwendung
nur scheinbar der richtige Weg, denn es ist von Hause aus schon
alignment-abhängig und legt die Bytes daher nur unter bestimmten
Rand-Bedingungen in der richtigen Reihenfolge auf die serielle Leitung.
Du brauchst eine alignment-unabhängige Serialisierung und das ginge in
etwa wie folgt:
struct x
{
uint32 a;
uint32 b;
uint16 c;
}myStruct
byte buffer[64];
CopyUint32ToBuffer(myStruct.a, &buffer[0]);
CopyUint32ToBuffer(myStruct.b, &buffer[4]);
CopyUint16ToBuffer(myStruct.c, &buffer[8]);
void CopyUint32ToBuffer(uint32 value, byte* p)
{
#if BIG_ENDIAN
CopyUint32ToBufferBE
#else
CopyUint32ToBufferLE
}
void CopyUint32ToBufferBE(uint32 value, byte* p)
{
p[0] = (byte)((value&0xFF000000) >> 24);
p[1] = (byte)((value&0x00FF0000) >> 16);
p[2] = (byte)((value&0x0000FF00) >> 8);
p[3] = (byte)((value&0x000000FF));
}
void CopyUint32ToBufferLE(uint32 value, byte* p)
{
p[3] = (byte)((value&0xFF000000) >> 24);
p[2] = (byte)((value&0x00FF0000) >> 16);
p[1] = (byte)((value&0x0000FF00) >> 8);
p[0] = (byte)((value&0x000000FF));
}
...für UInt16, Int16, Int32, Float entsprechend im gleichen Muster.
Es gibt auch Libs, welche das Serialisieren und Deserialisieren
eleganter kapseln, als hier gezeigt. Das Beispiel oben illustriert
jedenfalls das Prinzip ganz anschaulich wie ich finde und obiger Code
läuft bei mir seit Jahren auf verschiedensten Prozessoren unauffällig.
Heinz schrieb:> Das union-Konstrukt aus uint32 und byte[4] ist für diese Anwendung> nur scheinbar der richtige Weg
Ja, ich stimme dir vol zu. Leider wird das immer wieder verwendet. Wie
es endianess unabhängig geht, wurde ja mehrfach gezeigt.
Beruflich habe ich immer wieder mit beiden Endianess-Varianten zu tun.
Und weil kaum jemand der Kollegen dieses berücksichtigt hat, ist das
wiederverwenden von SW oft ein riesen Aufwand, nur um den ganzen Krempel
aufzuräumen. :( Zumal natürlich auch nicht berücksichtigt wird, einmal
beim Empfang der Daten und beim Versenden anzupassen sondern das
schleppt sich durch die ganze SW-Struktur.
900ss D. schrieb:> Ja, ich stimme dir vol zu. Leider wird das immer wieder verwendet. Wie> es endianess unabhängig geht, wurde ja mehrfach gezeigt.
Und leider antworten viele Leute dann in dieser Art darauf:
Gowron schrieb:> Genau: gar nichts!
Heinz schrieb:> Du brauchst eine alignment-unabhängige Serialisierung und das ginge in> etwa wie folgt:
Mittlerweile erkennt der Compiler solche Konstrukte und optimiert die
auch.
Da wird nicht mehr geschoben, sondern die Bytes direkt getauscht (wenn
der Prozessor das unterstützt).
Hallo zusammen,
Das Problem des TE ist folgendes: Er versteht nicht, dass ein "int"
nicht zwangsläufig 32 bit groß sein muss. - Er kann je nach Architektur
8, 16, 32 oder was weiß ich wie viele Bits haben.
Wenn man etwas Erfahrung in C hat, dann weicht man automatisch auf die
entsprechenden Datentypen wie zum Beispiel "uint32_t" aus. Dann wird das
auch portabler Code, der auf jeglicher Architektur funktioniert. - Dies
ist es, was mit "Code ordentlich schreiben" gemeint wurde...
Nun zur eigentlichen Frage: Das Ergebnis ist von der Architektur
abhängig, ob sie little endian oder big endian ist.
Wenn die Dokumentation zum Controller nichts hergibt (was ich nicht
glaube), dann hilft ausprobieren.
Wenn Du zur Laufzeit die Endianess feststellen musst, dann kannst Du als
kleine Fingerübung eine Funktion schreiben, welche Dir mit statischen
Werten des eine oder das andere erwartete Ergebnis liefert.
Viele Grüße!
Sven
Sven L. schrieb:> Er kann je nach Architektur
Die durch die Angabe "PIC32" vorgegeben ist.
Sven L. schrieb:> Dann wird das> auch portabler Code, der auf jeglicher Architektur funktioniert.
Ist hier irrelevant, oder?!
Das hatte Peter D. aber schon geschrieben.
Sven L. schrieb:> Wenn die Dokumentation zum Controller nichts hergibt (was ich nicht> glaube), dann hilft ausprobieren.
Oder den Post von Peter D. lesen.
Zum Thema:
Wenn auf beiden Seite der Leitung die gleichen Systeme (Compile,
Controller) verwendet werden, ist die Byteorder doch egal.
Die Methode eine Union zu verwenden finde ich praktisch, wenn es sich
bei den Daten um eine struct handelt, und ich sie (lokal) in einem
EEPROM o.dergl speichere - da ist mir die Byteorder völlig egal;
notfalls halt als packed struct.
Sven L. schrieb:> Er versteht nicht, dass ein "int"> nicht zwangsläufig 32 bit groß sein muss. - Er kann je nach Architektur> 8, 16, 32 oder was weiß ich wie viele Bits haben.
8 Bit sind nicht vom Standard gedeckt, darum braucht man die nicht
betrachten.
Man kann Compiler (für Mikrocontroller) wohl dazu zwingen, aber dann
kann man char nehmen.
> Wenn man etwas Erfahrung in C hat, dann weicht man automatisch auf die> entsprechenden Datentypen wie zum Beispiel "uint32_t" aus. Dann wird das> auch portabler Code, der auf jeglicher Architektur funktioniert.
Dann wird er unportabler (wegen "was weiß ich wie viele Bits haben").
Historisch sind 9 Bit pro Byte oder bei DSP auch 24 Bit pro Byte.
Es gibt aber noch uint32_least_t oder uint32_fast_t, die sind dafür
besser geeignet.
Wenn es auf die genaue Anzahl Bits nicht ankommt, bleibt man bei int
Er wills doch überhaupt nicht portabel haben denn er hat explizit
gefragt wie es speziell auf dem PIC32 ist. Er hat nicht gefragt wie es
überall ist oder wie man irgendwas portabel macht. Da wirds wohl ein
offizielles ABI-Dokument geben und da wird dann schwarz auf weiß drin
stehen wie groß dort ein Byte ist, wie die Byte-Order ist, die
Alignment-Regeln und ob und wie sein union funktionieren wird.
Man kann nämlich auch mal die Kirche im Dorf lassen! Man kann auch
absichtlich Code schreiben für nur genau eine einzige Hardware die
niemals irgendwo anders laufen soll und sich dann einen ganzen Haufen
Extrawürste oder kleinste gemeinsame Nenner sparen und stattdessen
pragmatische Abkürzungen nehmen, solange man vorher die richtigen
Fragen stellt und dann in Erfahrung bringt was genau einem auf dieser
einen speziellen Plattform schriftlich garantiert wird und was nicht.
Bernd K. schrieb:> Er wills doch überhaupt nicht portabel haben denn er hat explizit> gefragt wie es speziell auf dem PIC32 ist. Er hat nicht gefragt wie es> überall ist oder wie man irgendwas portabel macht.
Das mag sein.
Allerdings ist die portable und immer korrekte Lösung per Bit-Shift im
späteren Code ja gar nicht anders gegenüber den speziellen Lösungen für
eine Architektur. Denn Bit-Shifts mit Vielfachen von der Bytegröße durch
einen entsprechenden Register / Memory-Zugriff zu ersetzen, gehört so
ziemlich zu den ältesten Compileroptimierungen. Vertraue Deinem
Compiler.
Hier meine universelle C++ Lösung für alle Fälle (ja, ich weiß ... er
hat aber ausdrücklich C/C++ geschrieben):
Wilhelm M. schrieb:> Denn Bit-Shifts mit Vielfachen von der Bytegröße durch> einen entsprechenden Register / Memory-Zugriff zu ersetzen, gehört so> ziemlich zu den ältesten Compileroptimierungen. Vertraue Deinem> Compiler.
Manchmal will man auch kompakten Quelltext da stehen haben und keine
Shift-Orgie nur um die Dogmatiker zu befriedigen, da wird dann halt mal
ein großes struct in einem Rutsch als Blob gesendet oder empfangen und
spart sich das mit einer shift-Orgie jedes Element einzeln zu
serialisieren nur um am Ende die exakt selben Daten zu haben.
Lieber plaziert man davor 2 oder 3 static asserts die die kritischen
Fragen auf dieser Plattform (Endianness, sizeof, alignment) abklären und
wenn die alle nicht anschlagen dann ist der darauf folgende Code
garantiert korrekt und nur einen Bruchteil so groß wie die
allgemeingültige Variante.
Die gesparte Lebenszeit steckt man dann in wichtigere Dinge.
Bernd K. schrieb:> Er wills doch überhaupt nicht portabel haben denn er hat explizit> gefragt wie es speziell auf dem PIC32 ist.
er ist aber auch nicht besonders kooperativ.
Nun gut statt hier rumzumaulen weil für ihn ungenügende Antworten kommen
wegen der "komischen" Aufgabenstellung hätte er locker einige Variablen
in ein Struct schreiben können und sich durch byteweises Auslesen die
Frage selber beantworten können.
Bernd K. schrieb:> Die gesparte Lebenszeit steckt man dann in wichtigere Dinge.
Genau.
Man schreibt einmal eine saubere Funktion dafür und muß sich dann nie
wieder darum kümmern. Man ruft fürderhin nur die entsprechende Funktion
auf.
Zeitverschwendung wäre, wenn man bei neuen Projekten mit anderen
Compilern und Targets jedesmal neu darüber nachdenken müßte und den Code
ändern muß.
Bernd K. schrieb:> Man kann nämlich auch mal die Kirche im Dorf lassen! Man kann auch> absichtlich Code schreiben für nur genau eine einzige Hardware die> niemals irgendwo anders laufen soll
Sicher niemals?
Die Versuchung ist nämlich groß, einmal geschriebenen Code in anderen
Projekten nachnutzen zu wollen, statt immer wieder vom Urschlein an
alles neu zu schreiben.
In der Regel hat man dann alle Fallgruben und dirty hacks vergessen und
fällt auf die Nase.
Peter D. schrieb:> Die Versuchung ist nämlich groß, einmal geschriebenen Code in anderen> Projekten nachnutzen zu wollen, statt immer wieder vom Urschlein an> alles neu zu schreiben.
ich hätte nie gedacht das ich puren C-Code aus meiner
Prüfgeräteentwicklungszeit von DOS über Atari nun zum Atmel und sogar zu
Arduino(sorry) bringe, aber man soll nie nie sagen und manchmal ist es
sogar einfach toll das es möglich ist und leichter als Pascal, 6502.asm
oder Basic weiterzunutzen.
Peter D. schrieb:> In der Regel hat man dann alle Fallgruben und dirty hacks vergessen und> fällt auf die Nase
Dann macht man darauf ein static assert und fertig. Ich finde es absurd,
ein unsigned char nicht bei 255 nach 0 überlaufen zu lassen, nur weil es
auch 9 Bit haben kann. Oder die int32-fetischisten, die glauben, damit
portabel zu sein, ohne an integral Promotion zu denken oder
Formatstrings portabel zu verunstalten.
Und ja, strukturierte Telegramme sind einfach lesbarer als hunderte von
1-4 Byte kopierorgien. Auch da reichen asserts, um sicherzustellen, dass
die Strukturen so gepackt sind wie erwartet.
Oder die Registerzugriffe mit bequemen structs des Herstellers: ich habe
die bitfetischisten nie verstanden, die statt reg0.xy=5 lieber ihre
schiebe und maskierformeln wollen. Ja, nicht portabel, aber welches
HW-register ist das schon?
Wobei das Beispiel im Threadstart natürlich Blödsinn ist und auf einen
Anfänger deutet, der erstmals serialisiert.
A. S. schrieb:> Oder die Registerzugriffe mit bequemen structs des Herstellers: ich habe> die bitfetischisten nie verstanden, die statt reg0.xy=5 lieber ihre> schiebe und maskierformeln wollen. Ja, nicht portabel, aber welches> HW-register ist das schon?
In diesem Forum gab es mal einen Thread zu folgendem Thema: es ging
darum, die von Atmel/MicroChip für AVR zur Verfügung gestellten Header
mit den SFR-Definition zu modifizierten Header umzuschreiben, bei denen
Bitfield-Typen für die SFRs verwendet werden. Und das ist grober Unfung.
Denn bei einem Register r (< 0x20) wird aus
1
r.bit0=1;
ein sbi (korrekt) und bei einem anderen Register (>= 0x20) wird daraus
ein RMW-Zyklus (ld, ori, st). Neben der Nicht-Atomarität ist das größere
Problem hier die Flag-Register, deren Flags durch das Schreiben einer 1
ins jeweilige Bit zurückgesetzt werden. Damit setzt man ggf. alle
gesetzten Flags im Flag-Register zurück, und nicht nur das gewünschte.
Deswegen sollte man so einen Unfug lassen. Gerade Leute, die auf
unterschiedlichen µC unterwegs sind, wollen sich über so etwas keine
Gedanken machen. Also schreibt man als überall korrektes Idiom
1
r=EnumName.BitName;
oder
1
r=r|EnumName.BitName;
(ab C++20 sind die compound-operator für volatile qualifizierte DT
deprecated)
Damit erreicht man Gleichförmigkeit im Code, eine klare Intentionalität
und immer korrektes Verhalten, egal welcher µC oder Compiler.
Hallole,
>was steht dann in bData[0], bData[1], bData[2] und bData[3] drin?
da ich gerade eben einen PIC32MX ausgebudelt habe und ich den Geruch von
der alten Phenolharz-Platine so anregend empfinde...
wie erwartet steht da das drin:
bData[0]:EF
bData[1]:BE
bData[2]:AD
bData[3]:DE
mfG
Peter ;-)
Peter D. schrieb:> Zeitverschwendung wäre, wenn man bei neuen Projekten mit anderen> Compilern und Targets jedesmal neu darüber nachdenken müßte und den Code> ändern muß.
Es ging als Vorbedingung für meine Aussage um Code der jeweils nur auf
dieser einen Hardware läuft (MCU), nicht um eine Library die in 30
Jahren jemand anders auf irgendwelchen außerirdischen Plattformen für
was völlig anderes nutzen kann. Und ich warte auch nicht bis ins hohe
Alter auf die Wiederauferstehung von Big-Endian und schreib schonmal
vorsorglich Code dafür (den ich dann aber leider nirgends testen kann).
Anstatt in solchen lokal begrenzten Projekten (MCU, Arm Linux, x86)
deren Code eh nur intern verwendet wird umständlichen Code zu schreiben
mit dem ich dann völlig unnötig (ich kenn ja mein ABI) jeden Parameter
einzeln schieben, einzeln in dem Sendepuffer kopieren oder da
rauskratzen muss schreibe ich lieber genau null zusätzlichen Code weil
ich das ganze struct einfach so über die Leitung donnern kann wie es im
Speicher liegt weil ich genau weiß dass alle beteiligten ABIs (x86,
amd64, arm, avr, RISC-V und mit an Sicherheit grenzender
Wahrscheinlichkeit alle zukünftigen ebenfalls) little-endian und
self-aligned oder weniger sind und genau 8 Bit pro Byte haben.
Darauf wette ich 2 Bildschirmseiten eingesparten Code in jedem Projekt.
Mal sehen wer gewinnt.
Also ganz sicher nie Coldfire? Oder läufts dann umgekehrt, es kommen
also eben deshalb querbeet in der Vernetzung nur Cores in Frage, die LE
arbeiten? Zugegeben, das sind die meisten, zumal mittlerweile sogar IBM
eingelenkt hat
A. K. schrieb:> Also ganz sicher nie Coldfire?
Mit an Sicherheit grenzender Wahrscheinlichkleit nicht. Die fallen ja
auch nicht einfach so vom Himmel und ich muss sie dann zwingend
unterstützen sonderen es orientiert sich an dem was man selbst nutzen
will wenn man seine eigene Hardware entwickelt und sich einen Controller
dafür aussucht.
A. K. schrieb:> in der Vernetzung nur Cores in Frage, die LE> arbeiten?
Sollte ich wirklich zufällig mal einen mit Big-Endian haben aus welchen
Gründen auch immer weiß ich was zu tun ist. Aber davon bleiben meine
ganzen ARM-Cortexe unberührt.
Ich muss momentan an einer Stelle auch ein Big-Endian-Protokoll
unterstützen und da muss ich dann tatsächlich den Schiebezirkus machen,
aber alle internen Protokolle und Datenformate sind bei mir LE und
bleiben das auch.
Bernd K. schrieb:> Ich muss momentan an einer Stelle auch ein Big-Endian-Protokoll> unterstützen und da muss ich dann tatsächlich den Schiebezirkus machen,
inline asm und REV machen das bei ARM in einem einzigen Befehl.