Forum: Mikrocontroller und Digitale Elektronik GPIO_TypeDef wie werden deren member adressiert ?


von Alexander S. (integra)


Lesenswert?

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 ?

von Arno (Gast)


Lesenswert?

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

von Alexander S. (integra)


Lesenswert?

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;

von W.S. (Gast)


Lesenswert?

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.

von Arno (Gast)


Lesenswert?

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

von Alexander S. (integra)


Angehängte Dateien:

Lesenswert?

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?

von Alexander S. (integra)


Lesenswert?

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.

von Jim M. (turboj)


Lesenswert?

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.

von Arno (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.