Forum: Mikrocontroller und Digitale Elektronik Ich verstehe diesen Code nicht


von Jack S. (jack1505)


Lesenswert?

Hallo,
ich erarbeite mir das C programmieren im Bereich Microcontroller. Ich 
habe aktuelle Schwierigkeiten folgenden Code zu verstehen:
1
typedef struct
2
{
3
    __IO uint32_t PMD;                                  
4
    __IO uint32_t OFFD;          
5
    __IO uint32_t DOUT;            
6
    __IO uint32_t DMASK;             
7
    __I  uint32_t PIN;              
8
    __IO uint32_t DBEN;                
9
    __IO uint32_t IMD;                   
10
    __IO uint32_t IEN;                   
11
    __IO uint32_t ISRC;                   
12
13
} GPIO_T;
14
15
#define PA                  ((GPIO_T *) PA_BASE)

Wenn ich das richtig verstehe, wird hier ein Makro mit dem Namen PA 
definiert. Dieses Makro castet die PA_BASE Adresse zu einem Zeiger vom 
Typ  der GPIO_T Struktur.
Ich kann mir nun schlecht vorstellen, wie der Zeiger genau aussieht. Was 
zeigt hier auf was? Ich verstehe den Zusammenhang zwischen Adresse und 
Struktur nicht.
Vielen Dank und viele Grüße

von Rainer W. (rawi)


Lesenswert?

Jack S. schrieb:
> Ich kann mir nun schlecht vorstellen, wie der Zeiger genau aussieht.

Ein Zeiger ist ein Zeiger (Pointer) auf eine Speicherstelle, d.h. eine 
Adresse. Der Rest ist (nur) eine Information für den Compiler.

von Falk B. (falk)


Lesenswert?

Jack S. schrieb:
> Wenn ich das richtig verstehe, wird hier ein Makro mit dem Namen PA
> definiert. Dieses Makro castet die PA_BASE Adresse zu einem Zeiger vom
> Typ  der GPIO_T Struktur.

Stimmt.

> Ich kann mir nun schlecht vorstellen, wie der Zeiger genau aussieht.

Ein Zeiger ist eine Adresse, welche angibt wo Daten zu finden sind.

> Was
> zeigt hier auf was?

Das #define mit dem Macro ist eher fragwürdig. Eine normale 
Zeigerdefinitione tut't auch und ist transparent. Und Typdefinitionen 
schreibt man in meistens klein. Reine Großschreibung sollte man nur für 
MACROS verwenden.
1
GPIO_T io_daten;    // Struct mit dem Namen io_daten vom Typ GPIO_T
2
GPIO_T tmp;         // noch ein Struct vom Typ GPIO_T
3
GPIO_T *pointer;    // Zeiger auf GPIO_T
4
pointer = &io_daten;  // zeigt jetzt auf io_daten
5
6
tmp = *pointer;     // io_daten wird mittels Zeiger in tmp kopiert

> Ich verstehe den Zusammenhang zwischen Adresse und
> Struktur nicht.

Ist genau der gleiche wie bei einfachen Variablen.

: Bearbeitet durch User
von Monk (roehrmond)


Lesenswert?

Jack S. schrieb:
> Ich kann mir nun schlecht vorstellen, wie der Zeiger genau aussieht.

Der Zeiger ist die Adresse im Speicher wo diese Struktur beginnt. Also 
eine Zahl.

Der Typ des Zeigers sagt dem Compiler, auf was für eine Datenstruktur er 
zeigt. Diese Info mutzt der Compiler, um passende Offsets zum Zeiger zu 
addieren, wenn du auf die einzelnen Elemente der Struktur zugreifst.

Davon weiß die CPU allerdings nichts, für sie ist das einfach nur eine 
Adresse/Integer-Zahl.

: Bearbeitet durch User
von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

Nicht gezeigt ist, dass irgendwo im Code PA_BASE definiert ist. 
Wahrscheinlich in einer Include-Datei.

PA_BASE kann ganz einfach definiert sein, einfach eine Integer-Zahl. Es 
kann aber auch sein, dass PA_BASE mit einer Reihe verschachtelter Macros 
definiert ist. Egal wie, PA_BASE hat letztendlich eine Wert, ist eine 
Zahl.

Die Zahl wird auf (GPIO_T *) gecastet, was für "Pointer auf eine 
Datenstruktur vom, Typ GPIO_T" steht. Das heißt, dem Compiler wird 
gesagt er soll die Zahl in PA_BASE als Pointer behandeln.

Nun ist das so, dass hier ausgenutzt wird, dass ein Pointer von vielen 
Compilern direkt als Speicheradresse verwendet wird. Die nehmen den Wert 
eines Pointers und benutzen ihn unverändert als eine Adresse im 
Speicher. Konzeptionell ist das nicht ganz sauber, aber der Trick 
"Pointer == Speicheradresse" ist absolut üblich wenn man embedded 
programmiert. Die Hardware hat nun mal ihre Register an festen 
Speicherpositionen liegen (memory-mapped). Diese Speicherpositionen muss 
man dem Compiler irgendwie verklickern. Welche das genau sind steht im 
Handbuch oder Datenblatt des µC.

GPIO_T gibt dann nur noch wieder, wie der Speicher ab PA_BASE aufgebaut 
ist. Also wo der Compiler Register wie PMD oder OFFD findet. Auch das 
ist ein etwas unsauberer aber absolut üblicher Trick bei der 
Embedded-Programmierung. Man muss nur zusehen, dass kein zusätzliche 
Alignment-Bytes in die Struktur geschoben werden, sonnst stimmt der 
Zugriff nicht mehr.

Durch die Struktur die ab PA_BASE im Süeicher liegt weiß der Compiler 
dann, dass PMD auf Adresse PA_BASE + 0, OFFD auf PA_BASE + 32, DOUT auf 
PA_BASE + 64 liegt, usw.

von Klaus (feelfree)


Lesenswert?

Hannes J. schrieb:
> MD auf Adresse PA_BASE + 0, OFFD auf PA_BASE + 32, DOUT auf
> PA_BASE + 64

Nö, es sei denn man zählt Adressen in Bits statt in Bytes.

von Jack S. (jack1505)


Lesenswert?

Danke für die Ganzen Antworten das hat mir sehr weitergeholfen. Hannes 
deine Erklärung hat mir am besten weitergeholfen.
Vielen Dank
(Und ja PA_BASE hat die Adresse 0x4000 0000)

: Bearbeitet durch User
von Frank O. (fop)


Lesenswert?

Bleibt nur noch kurz zu sagen, dass unter den "Speicheradressen" im 
Zielsystem nicht nur normaler Speicher zu finden ist, sondern auch 
etwas, das sich special function register nennt. Werte, die man dorthin 
schreibt, bestimmen, wie sich die Peripherie verhält. Werte, die man 
liest, verraten den Zustand der Peripherie. Dadurch kann es durchaus 
sein, dass man nicht den Wert liest, den man kurz vorher noch 
geschrieben hat. Schreibt man auf das Datenregister der seriellen 
Schnittstelle, löst man z.B. auf manchen Systemen das Senden dieses 
Wertes über die Schnittstelle aus. Wenn man jedoch von der Adresse 
liest, bekommt man den zuletzt empfangenen Wert.

Dein Codeschnipsel erinnert mich an das CMSIS. Das ist Code, den die 
Firma Arm zur Verfügung stellt, um auf Mikrocontrollern, die auf Technik 
von Arm basieren, die Peripherieeinheiten zu bedienen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

was ist denn __IO und __I?
Irgendwas Compiler internes, nur was? Noch nie gesehen.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Veit D. schrieb:
> was ist denn __IO und __I?

Ich kann ja mal tippen/raten...
1
#define __IO volatile
2
#define __I const volatile
Vielleicht nicht exakt so, aber sinngemäß.

von Bruno V. (bruno_v)


Lesenswert?

Veit D. schrieb:
> was ist denn __IO und __I?
vermutlich Input/Output und Input.
> Irgendwas Compiler internes?
Ja
> Noch nie gesehen.
Hat praktisch jeder Compiler für µC. Für Deinen Compiler googlen oder 
nachschlagen. Für das was

Frank O. schrieb:
> Bleibt nur noch kurz zu sagen, dass unter den "Speicheradressen" im
> Zielsystem nicht nur normaler Speicher zu finden ist, sondern auch
> etwas, das sich special function register nennt. Werte, die man dorthin
> schreibt, bestimmen, wie sich die Peripherie verhält. Werte, die man
> liest, verraten den Zustand der Peripherie. Dadurch kann es durchaus
> sein, dass man nicht den Wert liest, den man kurz vorher noch
> geschrieben hat. Schreibt man auf das Datenregister der seriellen
> Schnittstelle, löst man z.B. auf manchen Systemen das Senden dieses
> Wertes über die Schnittstelle aus. Wenn man jedoch von der Adresse
> liest, bekommt man den zuletzt empfangenen Wert.

von Klaus (feelfree)


Lesenswert?

Bruno V. schrieb:
>> Irgendwas Compiler internes?
> Ja

Nein, nur Präprozessor-Makros.

Arduino F. schrieb:
> define __IO volatile
> #define __I const volatile

von Flunder (flunder)


Lesenswert?

Beim STM32 sieht das z.B. so aus :
1
/**************************************************************************//**
2
 * @file     core_cm4.h
3
 * @brief    CMSIS Cortex-M4 Core Peripheral Access Layer Header File
4
 * @version  V5.0.8
5
 * @date     04. June 2018
6
 ******************************************************************************/
7
/*
8
 * Copyright (c) 2009-2018 Arm Limited. All rights reserved.
9
 *
10
 * SPDX-License-Identifier: Apache-2.0
11
 *
12
 * Licensed under the Apache License, Version 2.0 (the License); you may
13
 * not use this file except in compliance with the License.
14
 * You may obtain a copy of the License at
15
 *
16
 * www.apache.org/licenses/LICENSE-2.0
17
 *
18
 * Unless required by applicable law or agreed to in writing, software
19
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
20
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
 * See the License for the specific language governing permissions and
22
 * limitations under the License.
23
 */
24
25
/* IO definitions (access restrictions to peripheral registers) */
26
/**
27
    \defgroup CMSIS_glob_defs CMSIS Global Defines
28
29
    <strong>IO Type Qualifiers</strong> are used
30
    \li to specify the access to peripheral variables.
31
    \li for automatic generation of peripheral register debug information.
32
*/
33
#ifdef __cplusplus
34
  #define   __I     volatile             /*!< Defines 'read only' permissions */
35
#else
36
  #define   __I     volatile const       /*!< Defines 'read only' permissions */
37
#endif
38
#define     __O     volatile             /*!< Defines 'write only' permissions */
39
#define     __IO    volatile             /*!< Defines 'read / write' permissions */
40
41
/* following defines should be used for structure members */
42
#define     __IM     volatile const      /*! Defines 'read only' structure member permissions */
43
#define     __OM     volatile            /*! Defines 'write only' structure member permissions */
44
#define     __IOM    volatile            /*! Defines 'read / write' structure member permissions */
45
46
/*@} end of group Cortex_M4 */

von Adam P. (adamap)


Lesenswert?

Flunder schrieb:
> Beim STM32 sieht das z.B. so aus :

Das wird so bei allen Cortex-M4 µC aussehen, da die Datei zum CMSIS 
gehört.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke.
Mit "Irgendwas Compiler internes ..." meinte ich die vorangestellten 
"__", was für die Compiler Programmierer vorbehalten bleiben soll. 
Soviel wusste ich noch.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Monk schrieb:

> Der Typ des Zeigers sagt dem Compiler, auf was für eine Datenstruktur er
> zeigt. Diese Info mutzt der Compiler, um passende Offsets zum Zeiger zu
> addieren, wenn du auf die einzelnen Elemente der Struktur zugreifst.
>
> Davon weiß die CPU allerdings nichts, für sie ist das einfach nur eine
> Adresse/Integer-Zahl.

Das muss nicht sein. Hängt von den Adressierungsarten ab, die die 
Zielmaschine bereitstellt. Es kann durchaus sein (bei Verfügbarkeit der 
Adressierungsart "Indirekt mit Offset"), dass sich der Compiler 
überhaupt nicht darum kümmern muss, irgendwelche Offsets zu 
irgendwelchen Adressen zu addieren. Das macht dann die Maschine selber.

Jedenfalls, wenn der Compiler klug genug ist, diese Adresseierungsart 
bei Verfügbarkeit auch zu benutzen.

Naja, andererseits: oft lohnt das aber wiederum nur, wenn nacheinander 
auf mehrere Felder der Struktur zugegriffen wird, ansonsten ist es oft 
wiederum effizienter, "von Hand" zu addieren und "Direkt" oder 
"Indirekt" zuzugreifen. Das gilt vor allem dann, wenn sich die Sache 
schon zur Compilezeit auflösen läßt.

Als C-ler muss man halt einfach nur hoffen, dass der Compiler es je nach 
Situation schon bestmöglich umsetzen wird. Viele glauben sogar 
regelrecht und mit voller Inbrunst, dass er das immer und unter allen 
Umständen tut. Und die ganz Harten machen sogar ein Axiom daraus und 
behaupten, dass es wirklich so wäre...

von Bruno V. (bruno_v)


Lesenswert?

Ob S. schrieb:
> Als C-ler muss man halt einfach nur hoffen, dass der Compiler es je nach
> Situation schon bestmöglich umsetzen wird. Viele glauben sogar
> regelrecht und mit voller Inbrunst, dass er das immer und unter allen
> Umständen tut.
Immer sicher nicht, da es auch schlechte Compiler bzw. Optimierer 
gibt. Es ist aber sehr wahrscheinlich, dass etablierte Compiler auf's 
Byte bzw. auf den Taktzyklus genau ausrechnen, welche Adressierungsart 
beim gegebenen Code die bessere ist.

Und das jedes Mal: Nicht nur beim ersten Entwurf, sondern jedes Mal wenn 
eine Zeile hinzukommt oder wegfällt. OB sich jetzt nicht der komplett 
andere Ansatz lohnt, weil z.B. jetzt mit ++ statt +3 gearbeitet werden 
kann.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Falk B. schrieb:
> Das #define mit dem Macro ist eher fragwürdig. Eine normale
> Zeigerdefinitione tut't auch und ist transparent.

Nö, nicht, wenn du sowas als Hersteller in einer include-Datei liefern 
willst. Wenn diese innerhalb eines Exectuables in mehreren 
Übersetzungseinheiten inkludiert wird, dann würde der Linker die 
Definition mehrfach sehen. Je nach Linker (und Optionen) beschwert er 
sich dann drüber.

Ist doch völlig normal, du hast dann halt Zugriffe a la:
1
PA->DOUT = 42;

um Daten auf Port A auszugeben (wäre jetz meine Vermutung anhand der 
Feldnamen in der struct).

Bei den Xmega-artigen AVRs läuft das übrigens genauso, da hast du dann 
sowas wie
1
#define PORTA                (*(PORT_t *) 0x0400) /* I/O Ports */
2
#define PORTC                (*(PORT_t *) 0x0440) /* I/O Ports */
3
#define PORTD                (*(PORT_t *) 0x0460) /* I/O Ports */
4
#define PORTF                (*(PORT_t *) 0x04A0) /* I/O Ports */

in der include-Datei.

von Hans-Georg L. (h-g-l)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> Danke.
> Mit "Irgendwas Compiler internes ..." meinte ich die vorangestellten
> "__", was für die Compiler Programmierer vorbehalten bleiben soll.
> Soviel wusste ich noch.

Nicht für den Compiler sondern für den Linker sind die __ interessant.
Aber bei Macros ist das egal, weil weder Compiler noch Linker die zu 
sehen bekommen. Der Präprozessor läuft vorher und ersetzt alle Macro 
durch die Definitionen.

von Harald K. (kirnbichler)


Lesenswert?

Ob S. schrieb:
> Viele glauben sogar regelrecht und mit voller Inbrunst, dass er
> das immer und unter allen Umständen tut.

Und andere glauben ganz doll fest und mit religiöser Inbrunst daran, daß 
das nie der Fall sein kann und ihr handgeklöppelter Assemblercode schon 
aus Prinzip allem anderen überlegen sein muss - und treten das hier bei 
jeder Gelegenheit breit. Was übrigens nichts mit "C-ler" (was für ein 
beknacktes Kindergartenwort!) zu tun hat, sondern mit dem Dir ja bestens 
bekannten Unterschied zwischen Assembler und jeder beliebigen 
Hochsprache, egal, ob die nun C, Pascal, Rust, Ada oder nächste Woche 
auch GelbrosaRüsselschwein heißt.

von Peter D. (peda)


Lesenswert?

Ob S. schrieb:
> Jedenfalls, wenn der Compiler klug genug ist, diese Adresseierungsart
> bei Verfügbarkeit auch zu benutzen.

Viele moderne Compiler sind stinkend faul. Ehe sie umständlich Code für 
das Target erzeugen müssen, der alles erst zur Laufzeit ausrechnet, 
versuchen sie erstmal alle konstanten Ausdrücke soweit zu vereinfachen, 
wie zur Compilezeit nur irgendwie möglich.
Sogar Funktionsaufrufe, z.B. aus der math.h werden schon zur Compilezeit 
aufgelöst.
1
int main(void)
2
{
3
  float i = sin(2.3);
4
  PORTB = i * 100;
5
  28:  4a e4         ldi  r20, 0x4A  ; 74
6
  2a:  42 b9         out  0x02, r20  ; 2

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Hans-Georg L. schrieb:
>> Danke.
>> Mit "Irgendwas Compiler internes ..." meinte ich die vorangestellten
>> "__", was für die Compiler Programmierer vorbehalten bleiben soll.
>> Soviel wusste ich noch.
>
> Nicht für den Compiler sondern für den Linker sind die __ interessant.
> Aber bei Macros ist das egal, weil weder Compiler noch Linker die zu
> sehen bekommen.

Nö und nö.

Alle Bezeichner, die mit zwei Unterstrichen oder einem Unterstrich und 
einem Großbuchstaben beginnen, sind in C für die Implementierung (also 
Compiler und Standardbibliothek) reserviert. Anwendercode darf derartige 
Bezeichner nicht selbst erfinden und sie nur entsprechend irgendeiner 
Dokumentation der Implementierung verwenden. Beispiel: "_Bool" gibt es 
seit C99 auch bereits, ohne dass man stdbool.h inkludiert hat.

In diesem Falle betrachtet sich die CMSIS, die diese Makros definiert, 
natürlich als Teil der Implementierung.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> In diesem Falle betrachtet sich die CMSIS, die diese Makros definiert,
> natürlich als Teil der Implementierung.

Entweder das, oder den Autoren ist diese Regel einfach egal. Weil welche 
Gründe könnten sie haben das "__" voranzustellen?

- Kollisionen mit dem Nutzercode vermeiden, weil dieser ja keine 
Bezeichner dieser Art definieren darf. Aber warum hat die CMSIS dann 
auch Bezeichner wie "SCB", die kurz und kollisionsanfällig sind aber 
keine Unterstriche haben? Was wenn z.B. die GCC-Autoren "__I" als 
Built-In definieren?
- Einfach nur "I" ist zu kurz, "INP" oder "I__" fanden sie doof, weshalb 
man einfach "__I" gewählt hat
- "__I" sieht cool aus

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Niklas G. schrieb:

> Entweder das, oder den Autoren ist diese Regel einfach egal. Weil welche
> Gründe könnten sie haben das "__" voranzustellen?

Sie betrachten sich genauso als "system library", wie AVR-LibC das 
beispielsweise für den AVR-Bereich macht.

> - Kollisionen mit dem Nutzercode vermeiden, weil dieser ja keine
> Bezeichner dieser Art definieren darf.

Genau das.

> Aber warum hat die CMSIS dann
> auch Bezeichner wie "SCB", die kurz und kollisionsanfällig sind aber
> keine Unterstriche haben?

Die bekommst du aber nur, wenn du das entsprechende Headerfile 
inkludierst.

Diese __I und __IO bekommst du ja "hinten herum", d.h. die Headers, die 
das definieren, kannst du dir nicht selbst auswählen. An der Stelle 
sollte man halt schon als Implementierer auf Kollisionsfreiheit achten.

Ähnliches macht AVR-LibC auch: PORTA usw. bekommst du explizit via 
avr/io.h, aber die darin verwendeten _SFR_IO8 etc. bekommst du 
"hintenrum", daher sind sie im implementation namespace angelegt.

> Was wenn z.B. die GCC-Autoren "__I" als
> Built-In definieren?

Die Ersteller einer solchen Bibliothek müssen sich im Zweifelsfalls 
schon mit den Erstellern des Compilers in irgendeiner Form absprechen, 
beide sind "the implementation" aus Sicht des C-Standards. Teilweise 
erfolgen solche "Absprachen" halt auch durch Personalunion. :) Beispiele 
dafür kannst du bei Georg-Johann Lays Arbeit an AVR-GCC und AVR-LibC 
finden.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Diese __I und __IO bekommst du ja "hinten herum"

Ja nö, das "__I" und das "SCB" sind beide in der cortex_cm*.h definiert. 
Das eine kann kollidieren und das andere nicht. Man kriegt entweder 
beides oder keins.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ja, gut, für SCB stimmt das.

Solange es dokumentiert ist … irgendwo ;-)

von Bruno V. (bruno_v)


Lesenswert?

Peter D. schrieb:
> Sogar Funktionsaufrufe, z.B. aus der math.h werden schon zur Compilezeit
> aufgelöst.

Wie geht das? Also woher kann der Compiler wissen, welche "sin" der 
Linker sich aussuchen würde?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Bruno V. schrieb:
> Also woher kann der Compiler wissen, welche "sin" der
> Linker sich aussuchen würde?

Spielt keine Rolle - die Funktionalität von "sin" ist im C-Standard 
vordefiniert, daher kann der Compiler selbst berechnen was rauskommt, 
ggf. sogar mit höherer Genauigkeit.

von Jürgen S. (starblue) Benutzerseite


Lesenswert?

Niklas G. schrieb:
> Aber warum hat die CMSIS dann auch Bezeichner wie "SCB",
> die kurz und kollisionsanfällig sind aber keine Unterstriche haben?

Damit der Benutzer sie benutzen kann.
Die __IO usw. werden nur intern in den Headern verwendet.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Jürgen S. schrieb:
> Die __IO usw. werden nur intern in den Headern verwendet.

d.h. Funktionen wie __WFI() oder __enable_irq() soll man gar nicht 
nutzen?

Man kann also z.B. im SCB->SCR das SLEEPDEEP Bit aktivieren für den 
Deep-Sleep-Modus, kann diesen dann aber nicht betreten weil __WFI() 
intern ist und eigentlich nicht aufgerufen werden soll?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Das ist sicherlich inkonsistent, "historisch gewachsen", wie's scheint.

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.