Byteorder PIC32?

Gast #6076328
Lesenswert?

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?
Gast #6076394
Lesenswert?

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!
Gast #6076604
Lesenswert?

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!
#6076868
Lesenswert?

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:
1
void long2byte(uint8_t* b, uint32_t val)
2
{
3
  b[0] = val     & 0xFF;
4
  b[1] = val>>8  & 0xFF;
5
  b[2] = val>>16 & 0xFF;
6
  b[3] = val>>24 & 0xFF;
7
}
#6077236
Lesenswert?

Wer structured-bindings in C++ mag, kann auch mit dem tuple-Interface 
folgendes schreiben:
1
template<typename C, typename T>
2
requires (std::is_same_v<typename C::value_type, std::byte>) 
3
static inline void long2byte(C& b, T val) {
4
    static_assert(b.size() == size0f(T));
5
    [&]<auto... II>(std::index_sequence<II...>) {
6
        ((b[II] = std::byte(val >> (8 * II))), ...);  
7
    }(std::make_index_sequence<b.size()>{});    
8
}
9

10
volatile auto r = 0x04030201;
11

12
int main() {
13
    auto [lsb, b1, b2, msb] = bytesOf(r);
14
    return (int)lsb;
15
}

Leider sind trailing-requires-clauses noch nicht so ganz richtig im gcc 
implementiert. Sonst könnte man das static_assert() auch noch 
sinnvollerweise ersetzen.
Moderator Persönliche Seite #6077332
Lesenswert?

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?
#6077413
Lesenswert?

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.
Gast #6077425
Lesenswert?

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
1
$ man 3 endian
an, wenn man die Funktionen denn hat.

leo
Beitrag #6077502 wurde von einem Moderator gelöscht.
Gast #6077701
Lesenswert?

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.
#6077707
Lesenswert?

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.
#6077746
Lesenswert?

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
Gast #6077767
Lesenswert?

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.
#6077812
Lesenswert?

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
#6077876
Lesenswert?

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.
#6077883
Lesenswert?

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):
1
template<auto E = std::endian::little, typename T = void>
2
inline constexpr auto bytesOf(T value) {
3
    std::array<std::byte, sizeof(T)> data;
4
    if constexpr(E == std::endian::little) {
5
        [&]<auto... II>(std::index_sequence<II...>) {
6
            ((data[II] = std::byte(value >> ((8 * II)))), ...);  
7
        }(std::make_index_sequence<sizeof(T)>{});
8
    }
9
    else if constexpr (E == std::endian::big){
10
        [&]<auto... II>(std::index_sequence<II...>) {
11
            ((data[sizeof(T) - 1 - II] = std::byte(value >> ((8 * II)))), ...);  
12
        }(std::make_index_sequence<sizeof(T)>{});
13
    }
14
    else {
15
        static_assert(std::false_v<T>);
16
    }
17
    return data;
18
}
19

20
volatile auto r = 0x04030201;
21

22
int main() {
23
    auto [b1] = bytesOf(uint8_t{0x01});
24
    auto [lsb2, msb2] = bytesOf(0x0201);
25
    auto [lsb3, b31, b32, msb3] = bytesOf<std::endian::big>(r);
26
    auto [lsb4, b41, b42, msb4] = bytesOf(0x04030201);
27
    auto [lsb5, b51, b52, b53, b54, b55, b56, msb5] = bytesOf(0x0403020104030201UL);
28
         
29
    return (int)b1;
30
}
#6077899
Lesenswert?

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.
#6077937
Lesenswert?

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.
#6078027
Lesenswert?

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ß.
#6078049
Lesenswert?

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.
#6078128
Lesenswert?

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.
Gast #6078137
Lesenswert?

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.
#6078346
Lesenswert?

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.
#6078564
Lesenswert?

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.
#6078585
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren