Hallo,
eigentlich betrifft das alle möglichen structs die natürlich auch
members beinhalten, wie werden diese member angesprochen bzw.
adressiert ???
Die Struktur von z.B. GPIO_TypeDef ist wie folgt definiert:
// typedef struct
// {
// __IO uint32_t CRL;
// __IO uint32_t CRH;
// __IO uint32_t IDR;
// __IO uint32_t ODR;
// __IO uint32_t BSRR;
// __IO uint32_t BRR;
// __IO uint32_t LCKR;
// } GPIO_TypeDef;
Im Mikrocontroller sind die Register ja alle mit Adressen fest
"verdrahtet" (!)
GIOA ist mit #define als Zeiger auf ((GPIO_TypeDef *) 0x40010800)
definiert. Mit diesem Zeiger zeige ich auf die Anfangsadresse
0x40010800 dieser Peripherie/Struktur für GPIOA.
Die einzelnen Member sind doch alles Register(?) die alle 4byte
"breit" sind weil uint32_t ?
Werden die einzelen Member von CRL bis LCKR mit Zeigerarithmetik,
sprich alle 4byte weise adressiert ? also ab 0x40010800 ?
darf man die member in dieser Struktur von der Reihenfolge her
verändern ? oder ist das der Grund warum die alle als "volatile"
(__IO) definiert sind ?
Wenn ich z.B. folgende Befehlszeile angebe:
GPIOA->CRL = 0x200000;
greife ich auf den Member CRH in dieser Struktur an der Adresse
0x40010800 + x zu ? das "+ x" wäre dann die Integerbreite nach dem CRL
member ?
Oder wie findet der ab der Startatresse die einzelnen Member ?
Alexander S. schrieb: > Hallo, > > eigentlich betrifft das alle möglichen structs die natürlich auch > members beinhalten, wie werden diese member angesprochen bzw. > adressiert ??? > > Die Struktur von z.B. GPIO_TypeDef ist wie folgt definiert: > > // typedef struct > // { > // __IO uint32_t CRL; > // __IO uint32_t CRH; > // __IO uint32_t IDR; > // __IO uint32_t ODR; > // __IO uint32_t BSRR; > // __IO uint32_t BRR; > // __IO uint32_t LCKR; > // } GPIO_TypeDef; Das ist erstmal eine Typdefinition. Davon gibt es noch nirgendwo einen Speicherbereich, der diese Daten enthält. > Im Mikrocontroller sind die Register ja alle mit Adressen fest > "verdrahtet" (!) > > GIOA ist mit #define als Zeiger auf ((GPIO_TypeDef *) 0x40010800) > definiert. Mit diesem Zeiger zeige ich auf die Anfangsadresse > 0x40010800 dieser Peripherie/Struktur für GPIOA. > > Die einzelnen Member sind doch alles Register(?) die alle 4byte > "breit" sind weil uint32_t ? Es sind Speicherstellen. In diesem Fall vermutlich Register, das weiß aber der Compiler nicht. > Werden die einzelen Member von CRL bis LCKR mit Zeigerarithmetik, > sprich alle 4byte weise adressiert ? also ab 0x40010800 ? Was meinst du damit? Du sagst dem Leser mit den #defines "An der Adresse 0x40010800 beginnt eine Struktur vom Typ GPIO_TypeDef mit dem Namen GPIOA". > darf man die member in dieser Struktur von der Reihenfolge her > verändern ? In welcher Reihenfolge? Reihenfolge des Zugriffs? Ja. Reihenfolge in der Typdefinition? Im Prinzip ja, allerdings ändert sich dadurch die Hardware nicht. > oder ist das der Grund warum die alle als "volatile" > (__IO) definiert sind ? Nein, das ist "nur" ein Hinweis für den Compiler, dass sich die Daten an dieser Speicherstelle ändern können, ohne dass der Code sie ändert. Zum Beispiel wird bei folgendem Code...
1 | int funktion(void) |
2 | {
|
3 | int abc = 6; |
4 | abc = 8; |
5 | abc = abc + 4; |
6 | |
7 | return abc; |
8 | }
|
...ein halbwegs intelligenter Compiler die Funktion deutlich optimieren...
1 | int funktion(void) |
2 | {
|
3 | return 12; |
4 | }
|
...und alle Funktionsaufrufe durch den Wert 12 ersetzen. Wenn du aber abc als volatile deklarierst, geht der Compiler davon aus, dass sich zwischen
1 | abc = 8; |
und
1 | abc = abc + 4; |
der Wert von abc ändern kann. Das braucht man im Allgemeinen bei I/O-Registern oder globalen Variablen, die auch in ISRs geändert werden. > Wenn ich z.B. folgende Befehlszeile angebe: > > GPIOA->CRL = 0x200000; > > greife ich auf den Member CRH in dieser Struktur an der Adresse > 0x40010800 + x zu ? das "+ x" wäre dann die Integerbreite nach dem CRL > member ? Nein, auf den Member CRL.
1 | GPIOA->CRL |
ist eine Kurzform für
1 | (*GPIOA).CRL |
also Dereferenzierung des Pointers auf die Struktur mit anschließendem Zugriff auf den Member. MfG, Arno
Hallo Arno, vielen Dank für Deine Antwort. Arno schrieb: >> darf man die member in dieser Struktur von der Reihenfolge her >> verändern ? > > In welcher Reihenfolge? Reihenfolge des Zugriffs? Ja. Reihenfolge in der > Typdefinition? Im Prinzip ja, allerdings ändert sich dadurch die > Hardware nicht. das heisst wenn ich hier CRL mit CRH z.B. in der Typdefinition (siehe unten), vertausche, greife ich mit GPIOA->CRL dann auf das Register/Speicherstelle von eigentlich CRH zu ? typedef struct { __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR; } GPIO_TypeDef;
Alexander S. schrieb: > darf man die member in dieser Struktur von der Reihenfolge her > verändern ? oder ist das der Grund warum die alle als "volatile" > (__IO) definiert sind ? Nein, nein und nochmals nein. Manche Hersteller machen sehr gern irgendwelche struct's zurecht, um damit nach ihrer Ansicht schönere Formulierungen zu haben. Aber dabei muß man ALLERSTRENGSTENS beachten, daß man sowohl die richtige Reihenfolge als auch die richtigen Datenbreiten wählt. Sonst ist Chaos angesagt. Laß dir also lieber nicht einfallen, in solchen Structs herumzueditieren. Ebenso findet man bei solchen struct's häufig genug Dummy-Einträge, um über in der Hardware nicht belegte Adressen hinwegzukommen. Aber das alles ist nicht zwingend. Man kann ebenso auch ohne struct's und mit einzelnen #define's arbeiten. Das sieht dann etwa so aus:
1 | #define GPIOA 0x40010800 // GPIO Port A Section 8.5 on page 169
|
2 | |
3 | #define GPIOA_CRL (*((volatile unsigned long *) (GPIOA + 0x00)))
|
4 | #define GPIOA_CRH (*((volatile unsigned long *) (GPIOA + 0x04)))
|
und man greift dann nicht mit GPIP->CRL drauf zu, sondern mit GPIO_CRL. Und das volatile soll nur bewirken, daß allgemein der Compiler und insbesondere gerade der GCC sich nicht die Freiheit nimmt, auf das echte HW-Register garnicht zuzugreifen und stattdessen eine noch in einem CPU-Register vorhandene Kopie herzunehmen. W.S.
Alexander S. schrieb: > Hallo Arno, > > vielen Dank für Deine Antwort. > > Arno schrieb: >>> darf man die member in dieser Struktur von der Reihenfolge her >>> verändern ? >> >> In welcher Reihenfolge? Reihenfolge des Zugriffs? Ja. Reihenfolge in der >> Typdefinition? Im Prinzip ja, allerdings ändert sich dadurch die >> Hardware nicht. > > das heisst wenn ich hier CRL mit CRH z.B. in der Typdefinition (siehe > unten), vertausche, greife ich mit GPIOA->CRL dann auf das > Register/Speicherstelle von eigentlich CRH zu ? > > typedef struct > { > __IO uint32_t CRL; > __IO uint32_t CRH; > __IO uint32_t IDR; > __IO uint32_t ODR; > __IO uint32_t BSRR; > __IO uint32_t BRR; > __IO uint32_t LCKR; > } GPIO_TypeDef; Das ist jetzt ein wenig schwierig zu beantworten, denn woher "weiß" die Speicherstelle, dass sie CRL oder CRH heißt? Letzlich ja nur, weil das entweder im Datenblatt oder in der Definition der Struktur so festgelegt wurde. Aber: Ja, wenn laut Datenblatt oder irgendeiner anderen Festlegung CRL an Adresse 0x40010800 und CRH an Adresse 0x40010804 liegt und du in der Typdefinition CRL und CRH vertauschst, dann, ja, dann greifst du mit GPIOA->CRL auf die Speicherstelle zu, die laut Datenblatt CRH heißt. Eine weitere wichtige Stolperstelle: Diese Typdefinition geht implizit davon aus, dass die Members der Struktur alle direkt hintereinander im Speicher liegen. Das ist - speziell wenn sie unterschiedliche Größen haben und/oder kleiner sind als die "natürliche" Wortbreite des Controllers - nicht durch den C-Standard garantiert (wahrscheinlich aber durch den Compiler) MfG, Arno
Hallo W.S. Das leuchtet ein: Wäre fatal ! Die Fragestellung über das Vertauschen von members war ja absichtlich so gewählt weil ich eben genau diesen Punkt verstehen wollte "was passier wenn.." und du hast mir da sehr weitergeholfen. Weil bei einer MCU die register alle feste Adressen und Adressbereiche habe, wollte ich in Erfahrung bringen, wie es denn dann mit den einzelnen members einer struct (hier vom typ GPIO_TypeDef) aussieht. Die member sind alle vom typ int32_t, dass heisst, 4 bytes gross => 32 bit. Die member sind alle 32 bit Register. 7 Stück also 7 member befinden sich in dieser struct. Mit einem Adressoffset von 4 byte komme ich (theoretisch) von register zu register. Das sagt ja deine #define Beispiel für jedes einzelne member auch aus. im Anhang hab ich ein Foto angehängt um das mal Bildlich zu zeigen was ich meine. Stimmt das so?
Arno schrieb: > Eine weitere wichtige Stolperstelle: Diese Typdefinition geht implizit > davon aus, dass die Members der Struktur alle direkt hintereinander im > Speicher liegen. Das ist - speziell wenn sie unterschiedliche Größen > haben und/oder kleiner sind als die "natürliche" Wortbreite des > Controllers - nicht durch den C-Standard garantiert (wahrscheinlich aber > durch den Compiler) Hallo Arno ! Genial erklärt! das wäre meine nächste Frage gewesen: liegen die Speicherstellen der einzelnen members wie bei einem Array alle hintereinander (würde bei einer MCU ja auch Sinn machen und sieht man ja auch an den Adress-offsets) Fazit: 1. Die Datentypen der einzelnen Member zeigen u.a.den adress-offset zum nächsten Register 2. Die Register sind in ihrer Funktion fest implementiert und fest adressiert, so dass auch bei verwendung von falschen Datentypen oder vertauschten Bezeichnern in einer Struct viel Unfug mit den Register getrieben werden kann.
Alexander S. schrieb: > liegen die Speicherstellen der einzelnen members wie bei einem Array > alle hintereinander Normalerweise ja. Bei den Ausnahmen sieht man dann gerne sowas:
1 | typedef struct { |
2 | |
3 | _IO uint32_t regA; |
4 | _IO uint32_t regB; |
5 | |
6 | uint32_t Reserved[2]; |
7 | |
8 | _IO unt32_t regC; |
9 | |
10 | uint32_t Reserved2[4]; |
11 | |
12 | _IO uint32_t regD; |
13 | } FooPeripherial; |
Bei ARM Cortex-M hat man 16KB Addressraum pro Peripherial, die Struktur kann also auch schon mal recht komplex werden.
Jim M. schrieb: > Alexander S. schrieb: >> liegen die Speicherstellen der einzelnen members wie bei einem Array >> alle hintereinander > > Normalerweise ja. > > Bei den Ausnahmen sieht man dann gerne sowas: Da gehen zwei verschiedene Dinge durcheinander. Deine - korrekte - Antwort bezieht sich auf übliche Peripherie-Register der MCU. Was der C-Compiler macht, ist soweit ich weiß "implementation defined" - also vom jeweiligen Compiler abhängig. Meistens bekommt man keine Probleme, wenn alle Members gleich groß sind und gleich der natürlichen Wortbreite der MCU. Anders sieht es vermutlich bei manchen oder sogar vielen Compilern aus, wenn man z.B. eine
1 | typedef struct { |
2 | uint32_t postleitzahl, |
3 | uint16_t hausnummer, |
4 | char * strasse } adresse; |
auf einem 64Bit-System hätte - da wäre wahrscheinlich zwischen postleitzahl und hausnummer vier Bytes "leer" und zwischen hausnummer und strasse sogar sechs Bytes. Stichworte zur Suche "struct packed" und "struct member alignment". MfG, Arno
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.
