[AVR32 & C]Startadresse von struct weicht ab

Herakles

Profifragensteller
Moin!

Heute kann ich im "Programmieren" Unterforum mit einem kleinen Exkurs in die Welt der Mikroprozessoren aufwarten. Ich hoffe, ich bin damit bei bsdforen.de dennoch richtig :)

Ich habe ein Problem mit AVRStudio5, bzw. mit einem AVR32UC3A0512-Chip, der auf ein dafür vorgesehenes Development-Kit gebaut ist(EVK1100). Hier mal ein Link dazu: atmel.com

In meinem Programm gibt es ein struct, das mehrere uint8_t - Variablen enthält(für meine Fehlerbeschreibung hier ist es ein Pointer namens "struct rs232_telegramm *testvar"). Dieser Pointer wird zuerst angelegt, dann Speicher alloziert und dann auf "0" gesetzt.

Dann setze ich den ersten Wert des structs auf "7"(einfach aus Testzwecken, ich könnte die Variable auch auf "0xFF" setzen...), gebe danach auf meine debug-Schnittstelle(USART0) irgendeine Zahl aus(in diesem Fall 0x66, damit ich sehe, dass Daten grundsätzlich fließen und alles funktioniert), um anschließend den Wert meiner soeben gesetzten Variablen auszugeben.

Hier zunächst mal der Code:

Code:
struct rs232_telegramm {
	uint8_t lnr;
	uint8_t ack;
	uint8_t req;
	uint8_t typ;
	uint8_t idx;
	uint8_t pkt;
	uint16_t len;
	uint8_t daten[512];
	uint16_t crc;
} __attribute__((packed));

int main(void) {
    struct rs232_telegramm *testvar;
    int zahl;

    testvar = (struct rs232_telegramm*)malloc(sizeof(struct rs232_telegramm));
    memset( testvar, 0, sizeof(struct rs232_telegramm));
    testvar->lnr = 7;
    usart_putchar( USART0, 0x66 );
    zahl = (int)testvar->lnr;
    /*zahl = *(int*)testvar; <--------------------------->   mit dieser zeile funktioniert es nicht!!!! */
    usart_putchar( USART0, zahl );
}

Beim zweiten Ausgeben der Daten auf USART0 wird die Zahl "7" ausgegeben. Da das aber nun mit dem Zugriff auf die erste Variable im struct geschieht und nicht mit dem struct selbst, ändere ich den Code ein wenig ab und zwar ersetze ich die Zeile mit "zahl = (int)testvar->lnr;" durch die Zeile darunter.

Kompiliere ich das nun und führe es aus, dann bekomme ich als output vom zweiten USART0-Aufruf den Inhalt "0", also so, wie im memset angegeben. Ein Zugriff auf die erste Variable im struct bleibt mir verwehrt.

Ich habe das Ganze testweise auf meinem Firmenrechner in Linux nachprogrammiert und dort ist alles wie erwartet: Ein Zugriff auf die Speicheradresse des structs selbst liefert den Wert der ersten Variabel "lnr" zurück und die Adressen des structs und der ersten Variablen sind identisch.

Woran mag es liegen, dass der AVR(oder AVRStudio5 oder was auch immer) den Zugriff an dieser Stelle "verdreht"?

Ich bin nach 4 Stunden suchen ratlos und hoffe auf einen hilfreichen Tipp!

Beste Grüße
Herakles
 
Kurze Antwort: AVR32 ist big endian, deine (vermutlich x86 oder AMD64) Linuxkiste ist little endian. Und ein uint8_t ist kein int.

Auf üblichen Maschinen ist ein int 4 Byte groß. Ein uint8_t ist da ein unsigned char, also ein Byte. testvar->lnr liest also das erste Byte und usart_putchar gibt das aus. *(int*)testvar hingegen liest einen int, also 4 Byte aus dem Speicher. Der Speicher beginnend bei der Stelle, auf die testvar zeigt, als Bytes betrachtet sieht in dem Moment so aus: 7, 0, 0, 0, ...
Auf deiner little endian Linuxkiste liegt das niederwertigste Byte eines int an der niedrigsten Adresse. Das Lesen aus dem Speicher liefert den Wert 7: Das niederwertigste Byte ist 7, alle höherwertigeren Bytes sind 0. Das zusammengeschraubt zu einem int ergibt den Wert 7. Das unterste Byte des int wird ausgegeben: 7.
Nun der AVR32: Der ist big endian. Dort liegt also das höchstwertigste Byte zuerst im Speicher. Somit wird der Wert 117440512 geladen -- in Hexadezimal besser ersichtlich: 0x07000000. usart_putchar gibt hiervon das niederste Byte aus: 0.

Die C-Norm schreibt übrigens vor (ISO/IEC 9899:1999(E) §6.7.2.1, Absatz 13, Satz 2), dass die Adresse des ersten Elements eines structs mit der Adresse des structs übereinstimmt (also (void*)x == (void*)&x->erstes). Wenn du beim Lesezugriff den richtigen Typ verwendest, dann erhältst du auch das erwartete Ergebnis: zahl = *(uint8_t*)testvar;
 
In den Technischen Informatikvorlesungen haben die Dozenten immer darauf bestanden, dass Little- und Big-Endian zwei gleichwertige Konzepte sind.

Aber beim Programmieren sehe ich immer wieder Nachteile bei Big-Endian, vor allem wenn man Bit- oder Byte-Weise auf Daten zugreift. Ich finde Big-Endian immer wieder hässlich.
 
Welche Nachteile? Das hier ist einfach ein Fall von GIGO -- Garbage In, Garbage Out. Mit einem Cast wird ein Typ erzwungen, der schlicht falsch ist. Big Endian macht die Sache in diesem Fall nur etwas deutlicher und funktioniert nicht zufällig aufgrund glücklicher Umstände. Ein Übersetzer hätte noch weit schlimmere Dinge tun dürfen, denn der Zugriff mit dem falschen Typ resultiert in undefiniertem Verhalten.

Kamikaze schrieb:
Technischen Informatikvorlesungen
Bei Tamin?
 
Ich bin beeindruck, wie so oft, wenn ich hier unterwegs bin. Gestern Abend schreibe ich von einem Problem, das mich stundenlang beschäftigt hat und heute Morgen habe ich die Lösung, weil hier kompetente und Hilfsbereite Leute ihre Kommentare geben.

Tron, vielen herzlichen Dank! Deine Antwort war korrekt, ausführlich und fundiert. Ich habe es verstanden, es funktioniert jetzt und ich habe noch etwas dazugelernt. Besser kann eine Fragestellung in einem Forum nicht von der Hand gehen. Ich schulde Dir was! :)

Grüße
Herakles
 
Welche Nachteile? Das hier ist einfach ein Fall von GIGO -- Garbage In, Garbage Out.

Bei Little Endian funktioniert bitweise Adressierung von Datenstrukturen zum Beispiel sehr angenehm. Wenn du mit CAN-Bus arbeitest wirst du Motorola (Big-Endian) schnell verfluchen.

Z.b. CAN-Botschaften haben konfigurierbare Längen von 0-8 Byte. Das erzwingt quasi schon Little-Endian, sonst wäre das einzige Byte in einer Ein-Byte Botschaft mit 7 adressiert.

Sagen wir mal du hast in deiner CAN-Botschaft ein 10Bit Big-Endian Signal (gibt es leider), beginnend bei Bit 5.

Bei LE würdest du das niederwertigste Bit bei Byte 0 Bit 5 und das höchstwertigste bei Byte 1 (14/8) Bit 6 (14%8) finden. Bei BE ist das niederwertigste Bit an Byte 0 Bit 7 und das höchstwertige an Byte 0 Bit 6.

Code:
Byte   0    |    1    |    2    ...
LE: xxxxx012|3456789xx|xxxxxxxx
BE: xxxxx890|1234567xx|xxxxxxxx

Kurz gesagt, in die Umrechnung der Adressierung fließt immer die Länge des Signals mit ein. Wie du am Übergang 0:9 siehst hast du da ein ganz schönes Bit-geschubse vor dir. Das kann man wegabstrahieren, aber es kostet zumindest eine Fallunterscheidung (das heißt ein bedingter Sprung) und natürlich zusätzliche Rechenzeit.

Wer?
 
Ich habe deine Ausführen mehrfach gelesen und kann sie nicht nachvollziehen. Warum sollte ich die Daten im BE-Fall Mixed Endian schicken? Warum zudem nicht an Bytegrenzen ausgerichtet?
 
Hast du schon mal mit Bussystemen gearbeitet?

Ja - auch wenn ich nicht direkt gefragt war ;)

Meiner Meinung nach zeigt dein Beispiel nicht, warum LE prinzipiell einfacher zu handhaben ist als BE. Vielmehr zeigt es, dass es einfacher ist die Daten vom Bus zu verarbeiten, wenn sowohl die Byte- als auch die Bitorder vom Bus mit dem Host übereinstimmen. Und bitweise Daten, die nicht an Bytegrenzen ausgerichtet sind, sind in C immer irgendwie doof.
 
Die Welt in einem Fahrzeug ist voller 10Bit Werte. Willst du vielleicht immer 6 Bit wegschmeißen?
 
Die Welt in einem Fahrzeug ist voller 10Bit Werte. Willst du vielleicht immer 6 Bit wegschmeißen?

Nein, aber wie ich sagte, in C ist alles, was nicht an Byte-Grenzen ausgerichtet ist, immer mit zusätzlichen Operationen verbunden. Dabei ist es egal, ob die Grenzen nun “links“ oder “rechts“ nicht eingehalten werden. Sofern also Dein Controller nicht mit 5 oder 10-Bit breiten Bytes arbeitet, musst Du, um an den 10-Bit Wert zu kommen, immer shiften oder maskieren. Ob Du nun auch noch swappen musst, ist in den meisten Fällen egal, da man sich typischerweise eh ein Makro schreiben würde.

Aber vielleicht hab ich nur Dein Beispiel nicht verstanden. In dem Fall fände ich es gut, wenn Du ein paar erklärende Worte hinzufügen würdest, so dass Leute wie ich nicht dumm sterben müssen.
 
Du machst mit LE einfach weniger Knoten in dein Hirn. So einfach ist die Sache meiner Meinung nach.
 
Um dein Beispiel aufzugreifen, bei LE liest man das Wort so ein: (scheinbar sind die Bits von links nach rechts auch in LE-Reihenfolge, also nicht so wie man Zahlen üblicherweise hinschreibt)
Code:
val = data[0] >> 5 | (data[1] & 0x3F) << 3;
Und wenn man die Daten im BE-Fall nicht vermurkst, sondern tatsächlich in BE-Reihenfolge, ablegt (also nicht wie im Bild, sondern XXXXX987|6543210XX|...), dann sieht das Lesen so aus: (Ich gehe davon aus, da sind die Bits von links nach rechts in BE-Reihenfolge, also so wie man Zahlen üblicherweise hinschreibt)
Code:
val = (data[0] & 0x7) << 6 | data[1] >> 2;
Da besteht kein Unterschied im Aufwand.

Wenn es um bitweises Senden/Empfangen geht, dann ist das eine beim Senden und das andere beim Empfangen jeweils marginal einfacher, das schenkt sich also auch nichts.
 
Innerhalb eines Bytes gibt es keine Reihenfolge. Ein << 1 ist immer *2 und >> 1 ist immer /2. Ich schreibe die Bits einfach in Indexierungsreihenfolge auf.

Versand/Empfang ist immer Byteweise. Darin liegt die Krux, denn deine Daten sind es nicht.

Mit deiner nicht vermurksten Schreibweise müsste es übrigens so aussehen:
BE: 098xxxxx|xx7654321|xxxxxxxx
 
Innerhalb eines Bytes gibt es keine Reihenfolge.
Natürlich gibt es die. Das sieht man zum Beispiel an der Notation oben: Höchstwertigstes Bit links oder rechts schreiben? Das ist Endianness auf Bitebene. Wenn Daten bitweise verschickt werden, dann ist das ebenso wichtig.
Wenn man nur Bytes betrachtet, dann ist das ist das tatsächlich nicht relevant -- außer man schreibt die Bits in den Bytes nieder, dann muss man wieder wissen, welche Wertigkeit wo ist. Bei der Unterbringung von Bitfolgen innerhalb von Bytesequenzen muss man sich jedoch immer noch entscheiden, wo man diese in den Bytes ablegt.

Ein << 1 ist immer *2 und >> 1 ist immer /2.
Das ist richtig und bestreitet auch keiner (wir gehen mal von vorzeichenlos aus, sonst stimmt das mit dem / 2 nicht mehr).

Ich schreibe die Bits einfach in Indexierungsreihenfolge auf.
Die da wäre? Ist Bit 0 das nieder- oder höchstwertigste? Steht das links oder rechts? Ja, da differiert die Notation in diverser Dokumentation durchaus.

Versand/Empfang ist immer Byteweise.
In Ordnung.

Darin liegt die Krux, denn deine Daten sind es nicht.
Man muss die krummen Bitfolgen eben in den Bytestrom einbetten. Das kann man BE oder LE tun.

Mit deiner nicht vermurksten Schreibweise müsste es übrigens so aussehen:
BE: 098xxxxx|xx7654321|xxxxxxxx
Anmerkung: Mir fällt gerade auf, dass dein zweites Byte 9 Bit hat, ich streiche ein x. Meine vorherigen Ausführungen schleifen den Fehler auch weiter, aber das ändert nichts Grundsätzliches.
Bit 0 direkt neben Bit 9 des 10 Bit langen Wortes ist kein BE und auch kein LE, das ist einfach garnix. Es sollte demnach eher (warum nicht später) so aussehen: 987xxxxx|x6543210. Entsprechend wäre die Dekodierung: (data[0] & 0xE0) << 2 | (data[1] & 0x7F).
Allerdings werden, wenn man Bitsequenzen einbettet, Bytes bei BE üblicherweise vom höchstwertigsten Bit her besetzt (Bei LE entsprechend niederwertigstes; das spiegelt sich zum Beispiel beim GCC bei der Verwendung von Bitfeldern), daher liegt der 5 Bit lange Wert (xxxxx) in den höchsten Bits des ersten Bytes und die unteren 7 Bit des 10 Bit langen Wertes in den höchstwertigen des zweiten Bytes: xxxxx987|6543210x. Dann sieht die Dekodierung so aus: (data[0] & 0x07) << 7 | data[1] >> 1.
Wenn bei LE ensprechend das niederwertigste zuerst beschickt wird, dann sieht das so aus (wieder höchstwertigstes Bit links, so wie üblich notiert): 210xxxxx|x9876543. Das entspricht deinem ersten Anschrieb, der auch auf Bitebene LE war und entsprechend so aussah (niederwertigstes Bit links): xxxxx012|3456789x. Dekodierung: data[0] >> 5 | (data[1] & 0x7F) << 3.
 
Zurück
Oben