Hallo,
ich erarbeite mir das C programmieren im Bereich Microcontroller. Ich
habe aktuelle Schwierigkeiten folgenden Code zu verstehen:
1
typedefstruct
2
{
3
__IOuint32_tPMD;
4
__IOuint32_tOFFD;
5
__IOuint32_tDOUT;
6
__IOuint32_tDMASK;
7
__Iuint32_tPIN;
8
__IOuint32_tDBEN;
9
__IOuint32_tIMD;
10
__IOuint32_tIEN;
11
__IOuint32_tISRC;
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
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.
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_Tio_daten;// Struct mit dem Namen io_daten vom Typ GPIO_T
2
GPIO_Ttmp;// 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.
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.
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.
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.
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)
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.
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.
Bruno V. schrieb:>> Irgendwas Compiler internes?> Ja
Nein, nur Präprozessor-Makros.
Arduino F. schrieb:> define __IO volatile> #define __I const volatile
Hallo,
Danke.
Mit "Irgendwas Compiler internes ..." meinte ich die vorangestellten
"__", was für die Compiler Programmierer vorbehalten bleiben soll.
Soviel wusste ich noch.
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...
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.
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 */
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.
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.
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.
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.
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
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.
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.
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?
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.
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.
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?