Hallo Bit Freunde,
ich möchte gerne weiter meine C Kenntnisse auffrischen..
Dieser Variable möchte ich gerne die Adresse zuordnen, jedoch zeigt der
Online Compiler ein Fehler an (was vermutlich auch richtig ist..)
Was mache ich falsch?
Jan H. schrieb:> jedoch zeigt der Online Compiler ein Fehler an (was
Sollen wir jetzt raten, welcher Compiler welchen Fehler anzeigt?
> ich möchte gerne weiter meine C Kenntnisse auffrischen..
Wozu einen neuen Thread für das exakt selbe Problem? Reicht der
Beitrag "Mit dem ESP32C3 laufen lernen" nicht?
Jan H. schrieb:> Dann bin ich mal auf deine Erklärung gespannt..
Denk mal da drüber nach:
- https://onlinegdb.com/HZ-zbCPMXY
Gibt es eine Möglichkeit die Struktur "Out_Func_Reg" an die Adresse
"GPIO_BASE + 0x554" zu mappen also quasi das direkt schon bei der
definition des Structs? Wie würde man sowas lösen?
Jan H. schrieb:> Gibt es eine Möglichkeit die Struktur "Out_Func_Reg" an die Adresse> "GPIO_BASE + 0x554" zu mappen also quasi das direkt schon bei der> definition des Structs?
Nein.
> Wie würde man sowas lösen?
mit einem Datenfeld entsprechender Größe dazwischen. unsigned char
_dummy[0x554-4*14]; oder so
Oder 2 Structs. Wenn es zwei Bereiche sind, warum dann nicht auch zwei
Strukturen?
Jan H. schrieb:> Wie würde man sowas lösen?
Nachdem du zum Glück nicht der erste bist, der solche
Registerdefinitionen in seinem C-Programmen verwednen will, wäre es
sicher sinnvoll, mal zu schauen, wie seit vielen Jahren andere derartige
Probleme lösen.
Bruno V. schrieb:> mit einem Datenfeld entsprechender Größe dazwischen. unsigned char> _dummy[0x554-4*14]; oder so
Ich finde diese verschachtelten Structs ungünstig, weil die jeder
Compiler so interpretieren kann, wie es ihm gerade gefällt. Oder
andersrum: es ist im Grunde Zufall wie diese Structs im Speicher
abgelegt werden. Es kann sein, dass das mit den Dummy-Bytes zwischen den
structs heute funktioniert. Es kann aber auch sein, dass es nach einem
Compilerupdate oder nach dem Einschalten von Optimierungen nicht mehr
funktioniert.
Kann ich denn irgendwie sehen was der Compiler für Adressen als
"Endergbnis" liefert?
Ich bin gerade dabei bei einem Board eine LED zum Leben zu erwecken aber
ich habe das Gefühl das irgendwas mit den Adressen nicht ganz so
hinhaut..
Jan H. schrieb:> sGPIO_Func_Sel_Reg->Func8_Out_Sel_Cnfg |= 0x80;> sGPIO_Cnfg_Reg->Enable_Reg |= 1<<8;> // gpio8 auf low setzen> sGPIO_Cnfg_Reg->Out_Reg &= ~(1<<8);
sollen die alle das gleiche bit beschreiben? Mal 0x80, mal 0x100.
Lothar M. schrieb:> Ich finde diese verschachtelten Structs ungünstig,
Ich auch. Deshalb war meine Antwort auch lakonisch
Bruno V. schrieb:> Jan H. schrieb:>> Gibt es eine Möglichkeit ...> Nein.
Bruno V. schrieb:> Jan H. schrieb:>> sGPIO_Func_Sel_Reg->Func8_Out_Sel_Cnfg |= 0x80;>> sGPIO_Cnfg_Reg->Enable_Reg |= 1<<8;>> // gpio8 auf low setzen>> sGPIO_Cnfg_Reg->Out_Reg &= ~(1<<8);>> sollen die alle das gleiche bit beschreiben? Mal 0x80, mal 0x100.
Nein natürlich nicht.
Habe mich hier an dieses Beispiel gehalten (mit der Struktur und den
ganzen Registern)
https://blog.feabhas.com/2019/01/peripheral-register-access-using-c-structs-part-1/
Moin,
Jan H. schrieb:> Kann ich denn irgendwie sehen was der Compiler für Adressen als> "Endergbnis" liefert?
Fuer die Mutigen mit gcc/binutils toolchain, die sich durch den
Assembler lesen wollen:
entweder gcc -save-temps ....
und dann im .s File stoebern
oder objdump -D bla.o | less
und dann g'schwind lesen.
Gruss
WK
Jan H. schrieb:> Bruno V. schrieb:>> Jan H. schrieb:>>> sGPIO_Func_Sel_Reg->Func8_Out_Sel_Cnfg |= 0x80;>>> sGPIO_Cnfg_Reg->Enable_Reg |= 1<<8;>>> // gpio8 auf low setzen>>> sGPIO_Cnfg_Reg->Out_Reg &= ~(1<<8);>>>> sollen die alle das gleiche bit beschreiben? Mal 0x80, mal 0x100.>> Nein natürlich nicht.> Habe mich hier an dieses Beispiel gehalten (mit der Struktur und den> ganzen Registern)>> https://blog.feabhas.com/2019/01/peripheral-register-access-using-c-structs-part-1/
Ich verstehe deine Antwort nicht.
Die Register haben jeweils unterschiedliche Werte.
Jan H. schrieb:> Ich verstehe deine Antwort nicht.> Die Register haben jeweils unterschiedliche Werte.
Ich kenne Dein System nicht. Du schreibst nur
Jan H. schrieb:> Ich bin gerade dabei bei einem Board eine LED zum Leben zu erwecken aber> ich habe das Gefühl das irgendwas mit den Adressen nicht ganz so> hinhaut..
aber nicht, was nicht funktioniert oder (noch wichtiger) was
funktioniert.
Wenn Du Angst hast, dass die Adresse falsch sein könnte ... dann schreib
sie direkt hin, probier es aus und wenn es funktioniert, dann nimm den
Struct-Weg und probier erneut. Das brauchst Du ja nur ein Mal
exemplarisch zu machen.
Oder Du hast einen Debugger am laufen, dann setze den Brakepoint davor,
schaue Dir das Register an und geh im Singlestep drüber.
Ohne Debugger scheitert es an den unmöglichsten Dingen, angefangen von
Konfigurationsbits und der Quarz läuft gar nicht los. Oder das Signal
toggelt so schnell, dass Du es nicht siehst. Hast Du Debugger, LSA oder
Oszi?
Die Struct-Adressfrage ist ja genügend beleuchtet. Das ist aber Kür wenn
der direkte Weg funktioniert.
Bruno V. schrieb:> Ohne Debugger scheitert es an den unmöglichsten Dingen, angefangen von> Konfigurationsbits und der Quarz läuft gar nicht los. Oder das Signal> toggelt so schnell, dass Du es nicht siehst. Hast Du Debugger, LSA oder> Oszi?
Für ATMEL habe ich alles da, ja. Oszi. habe ich auch. Bringt mir jetzt
gerade nur nichts.
Es muss an meiner Initalisierung von dem Struct liegen bzw. der
Adressen.
Leider kann ich mir nicht erklären was genau das Problem ist.
Wenn ich es mit fester Adresse teste, funktioniert es:
Mache ich es mit der Struktur haut es aufeinmal nicht mehr hin..
Ich vermute mal das der Compiler? Da andere Adressen interpretiert als
ich mir das vorstelle?!
Hier ist mein Zeiger auf die Basis Adresse des GPIO´s
So wie ich mir das jetzt vorstelle. Meine Struktur mit den ganzen
Konfigurations Register:
1
typedefstruct
2
{
3
// mapped @GPIO_BASE
4
uint32_tBt_Select_Reg;
5
uint32_tOut_Reg;
6
uint32_tOut_W1TS_Reg;
7
uint32_tOut_W1TC_Reg;
8
uint32_tEnable_Reg;
9
uint32_tEnable_W1TS_Reg;
10
uint32_tEnable_W1TC_Reg;
11
uint32_tStrap_Reg;
12
uint32_tIn_Reg;
13
uint32_tStatus_Reg;
14
uint32_tStatus_W1TS_Reg;
15
uint32_tStatus_W1TC_Reg;
16
uint32_tPCPU_Int_Reg;
17
uint32_tStatus_Next_Reg;
18
19
}GPIO_Cnfg_Reg_t;
Halte ich mit der Maus jetzt auf den ersten Member so erhalte ich
folgendes Bild (siehe Anhang).
Dort zeigt mir die Arduino IDE an das "Bt_Select_Reg" einen Offset von 0
hat. Gehe ich mit der Maus jetzt auf den letzten Eintrag
"Status_Next_Reg" sagt die IDE mir das es sich um einen Offset von 52
Bytes handelt.
Das würde dann dem letzten Register (Adresse) vom Datenblatt entsprechen
oder verstehe ich das falsch?
Jan H. schrieb:> Dort zeigt mir die Arduino IDE an das "Bt_Select_Reg" einen Offset von 0> hat. Gehe ich mit der Maus jetzt auf den letzten Eintrag> "Status_Next_Reg" sagt die IDE mir das es sich um einen Offset von 52> Bytes handelt.
Das sind die Offsets der Strukturelemente relativ zum Beginn der
Struktur.
Mit Deinen Registeradressen hat das, wenn überhaupt, nur rein zufällig
zu tun.
Wozu soll bei Deinem Projekt eigentlich die Struktur dienen? Welches
Problem meinst Du, damit lösen zu können?
Ich sehe hier nur eine zusätzliche Abstraktionsebene, die Du einziehst,
und die Dich heftigst herausfordert.
Harald K. schrieb:> Wozu soll bei Deinem Projekt eigentlich die Struktur dienen? Welches> Problem meinst Du, damit lösen zu können?
Damit möchte ich einfach vermeiden das ich die ganzen Register Adressen
von Hand in den Code tippen muss..
So wie hier..
Jan H. schrieb:> Damit möchte ich einfach vermeiden das ich die ganzen Register Adressen> von Hand in den Code tippen muss..
Musst Du ja auch nicht. Du machst an einer Stelle ein #define, und das
war's dann.
So arbeitet der Rest der Welt, und so sind i.d.R. auch die von
Controllerherstellern gelieferten Headerdateien für ihre µCs aufgebaut.
Und man kann so ein Macro auch so definieren, daß man es direkt als
"Pseudovariable" verwenden kann.
Das dereferenziert die I/O-Adresse, so daß Du im Code einfach so etwas
schreiben kannst:
1
Func_Reg=4;
2
3
intx=Func_Reg;
Eine Struktur ist da nicht erforderlich, sondern höchstens ein besseres
Namensschema (ich würde das Ding GPIO_FUNC_REG nennen, auch um zu
zeigen, daß das ein Macro/#define ist).
Danke für den Vorschlag.
Meinen Ansatz hatte ich mal in einen Online C Compiler gehauen und da
hatte es komischerweise gepasst mit den Adressen von daher bin ich
ausgegangen das es so funktionieren kann. Auf einigen Webseiten wird es
für diverse Atmels auch so praktiziert.
Jan H. schrieb:> Auf einigen Webseiten wird es> für diverse Atmels auch so praktiziert.
Auf Webseiten wird viel praktiziert.
Es gibt im Zusammenhang mit HAL-Abstraktionen verschiedener
µC-Hersteller die Tendenz, statt einzelner Registerdefinitionen (wie
oben) Pseudostrukturen zu definieren, die ein Peripherieobjekt abbilden.
Wenn bei einem Peripheriebaustein die zusammengehörenden Register
monoton aufsteigend angeordnet sind, dann kann das so aussehen, wie hier
diskutiert:
Beitrag "Ich verstehe diesen Code nicht"
Die Registerstruktur im ESP32, um den es Dir ja letztendlich geht, ist
für diese Herangehensweise nicht geeignet, da die Register recht
willkürliche Adressen haben (wie Dein Offset von 0x574 ja schon zeigt).
Da müsste man die Struktur mit sehr, sehr vielen "Füllregistern" für
unbenutzte Registeradressen definieren):
1
typedefstruct
2
{
3
__IOuint32_tGPIO_BT_SELECT_REG;// 0x0000
4
__IOuint32_tGPIO_OUT_REG;// 0x0004
5
__IOuint32_tGPIO_OUT_W1TS_REG;// 0x0008
6
__IOuint32_tGPIO_OUT_W1TC_REG;// 0x000C
7
__IOuint32_tUNUSED_BLOCK_0[4];
8
__IOuint32_tGPIO_ENABLE_REG;// 0x0020
9
...etc.
10
__IOuint32_tGPIO_PIN21_REG// 0x00C8
11
__IOuint32_tUNUSED_BLOCK_x[35];
12
__IOuint32_tGPIO_FUNC0_IN_SEL_CFG_REG// 0x0154
13
...etc.
14
15
16
}ESP32_GPIO_T;
(bezogen auf das Datenblatt, Seite 171, Tabelle mit GPIO-Registern)
Du siehst, daß das sehr unhandlich und vor allem fehlerträchtig wird.
Lothar M. schrieb:> Oder andersrum: es ist im Grunde Zufall wie diese Structs> im Speicher abgelegt werden.
Natürlich ist es kein Zufall, sondern es wird im [E]ABI (Applicaton
Binary Interface) festgelegt / beschrieben.
Nicht alle Features von C/C++ werden im Sprachstandard festgelegt; es
gibt auch Dinge, die sind Implementation Defined. Einige davon werden
von der C/C++ Implmemtation festgelegt, für GCC etwa:
https://gcc.gnu.org/onlinedocs/gcc/C-Implementation.htmlhttps://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Implementation.html
andere werden im ABI festgelegt, welches idR Hardware- und OS-abhängig
ist. Und auch Eigenschaften des Binärformats (z.B. welche RELOCs im ELF
wie festgelegt sind) sind Sache des ABIs.
Jan H. schrieb:> volatile uint32_t* Enable_Reg = (uint32_t*)(GPIO_BASE + 0x0020);
das ist ein offset von 32, also das 9te Element.
Jan H. schrieb:> typedef struct> {> // mapped @GPIO_BASE> uint32_t Bt_Select_Reg;> uint32_t Out_Reg;> uint32_t Out_W1TS_Reg;> uint32_t Out_W1TC_Reg;> uint32_t Enable_Reg;> uint32_t Enable_W1TS_Reg;> uint32_t Enable_W1TC_Reg;
Hier ist es das 5te Element.
Ein Problem ist, dass Du zwar mehrere Beispiele hast, aber niemals dabei
sagst, ob es das gleiche oder was verschiedenes zeigt.
Ich werde
Bruno V. schrieb:> Jan H. schrieb:>> volatile uint32_t* Enable_Reg = (uint32_t*)(GPIO_BASE + 0x0020);>> das ist ein offset von 32, also das 9te Element.>> Jan H. schrieb:>> typedef struct>> {>> // mapped @GPIO_BASE>> uint32_t Bt_Select_Reg;>> uint32_t Out_Reg;>> uint32_t Out_W1TS_Reg;>> uint32_t Out_W1TC_Reg;>> uint32_t Enable_Reg;>> uint32_t Enable_W1TS_Reg;>> uint32_t Enable_W1TC_Reg;>> Hier ist es das 5te Element.>> Ein Problem ist, dass Du zwar mehrere Beispiele hast, aber niemals dabei> sagst, ob es das gleiche oder was verschiedenes zeigt.
Ich werde leider nicht wirklich aus deinen Kommentaren schlau.
Was genau meinst Du? Es ist immer noch das gleiche Projekt was ich vor
mir habe.
Jan H. schrieb:> Damit möchte ich einfach vermeiden das ich die ganzen Register Adressen> von Hand in den Code tippen muss..> So wie hier..volatile uint32_t* Func_Reg = (uint32_t*)(GPIO_BASE +> 0x0574);
Ist zwar O.T., aber wenn die Adressen sowieso ungeordnet bzw.
fragmentiert vorliegen, ist die Tipparbeit selbst mit Editoren aus dem
letzten Jahrhundert in Summe geringer.
Du schreibst Die Namen und Adressen runter
1
Func_Reg 0x0574
2
Dings_Reg 0x0588
3
...
und machst den Pre- ("volatile uint32t* ") Mid- (" =
(uint32_t*)(GPIO_BASE +
") und Postfix (");") in wenigen Sekunden für alle.
Der Vorteil: Du siehst später sofort den echten Wert und musst ihn nicht
abzählen. Hat zwar auch ein paar Nachteile, aber vorerst reicht das.
Jan H. schrieb:> Ich werde leider nicht wirklich aus deinen Kommentaren schlau.> Was genau meinst Du? Es ist immer noch das gleiche Projekt was ich vor> mir habe.
Du sagst
Jan H. schrieb:> wenn ich es mit fester Adresse teste, funktioniert es:> ...> volatile uint32_t* Enable_Reg = (uint32_t*)(GPIO_BASE + 0x0020);
hast bei der "Struktur-Version" Enable_Reg aber an einer ganz anderen
Adresse (GPIO_BASE + 20 statt +0x20)
Mag sein, dass das ein Tippfehler ist (0x20 statt 20), mag sein, dass
Füll-Elemente fehlen.
Okay.
Also laut Datenblatt sind die Konfigurations Register 32 Bit lang, also
4 Bytes und fangen bei der Adresse "0x0000".
Das "Enable" - Register liegt bei Adresse "0x0020".
Wenn ich jetzt eine Struktur habe wo alle Elemente 32 Bit lang sind also
4 Bytes und das "Enable" Register an der richtigen Stelle steht, müsste
es doch von der Adresse passen?!
Jan H. schrieb:> Wenn ich jetzt eine Struktur habe wo alle Elemente 32 Bit lang sind also> 4 Bytes und das "Enable" Register an der richtigen Stelle steht, müsste> es doch von der Adresse passen?!
Ja. Dann sähe dein Struct aber so aus. Mit, wie
Harald K. schrieb:> sehr vielen "Füllregistern" für unbenutzte Registeradressen [..]> typedef struct>
1
>{
2
>__IOuint32_tGPIO_BT_SELECT_REG;// 0x0000
3
>__IOuint32_tGPIO_OUT_REG;// 0x0004
4
>__IOuint32_tGPIO_OUT_W1TS_REG;// 0x0008
5
>__IOuint32_tGPIO_OUT_W1TC_REG;// 0x000C
6
>__IOuint32_tUNUSED_BLOCK_0[4];
7
>__IOuint32_tGPIO_ENABLE_REG;// 0x0020
8
>...etc.
In Deinem Beispiel fehlen die Füllregister, hier UNUSED_BLOCK_0
Bruno V. schrieb:> Oder, falls Du wirklich den Unterschied zwischen 0x20 und 20 nicht> kennst: Das wievielte Element soll es ein? Das 5te oder das 9te?
Du hast natürlich vollkommen Recht! Ich habe den Wald voller Bäume nicht
gesehen.
Habe gar nicht bemerkt das nach 0x000C nicht 0x0010 kommt^^
Klaus schrieb:> Jan H. schrieb:>> Habe gar nicht bemerkt das nach 0x000C nicht 0x0010 kommt>> Erneuter Vertipper oder immer noch Verständnisschwierigkeiten?
Habe gerade im Datenblatt gesehen das es ab dem "GPIO_OUT_W1TC_REG"
nicht mehr der Reihe nach geht. Das war mir vorher nicht bewusst.
Bruno V. schrieb:>> volatile uint32_t* Enable_Reg = (uint32_t*)(GPIO_BASE + 0x0020
Das ist wohl ein Tippfehler. Es sollte + 0x10 lauten, dann bist du
wirklich am fünften Element.
Jan H. schrieb:> Mache ich es mit der Struktur haut es aufeinmal nicht mehr hin..
Ich war genauso blind, hab den Fehler einfach übersehen.
In solchen Fällen sind assertions überaus hilfreich. Dass und auch wo im
code der Unterschied herkommt hat mir
assert(&sGPIO_Cnfg_Reg->Out_Reg == Out_Reg);
assert(&sGPIO_Cnfg_Reg->Enable_Reg == Enable_Reg);
assert(&sGPIO_Func_Sel_Reg->Func8_Out_Sel_Cnfg == Func_Reg);
gezeigt.
Harald schrieb:> Jan H. schrieb:>> Mache ich es mit der Struktur haut es aufeinmal nicht mehr hin..>> Ich war genauso blind, hab den Fehler einfach übersehen.> In solchen Fällen sind assertions überaus hilfreich. Dass und auch wo im> code der Unterschied herkommt hat mir>> assert(&sGPIO_Cnfg_Reg->Out_Reg == Out_Reg);> assert(&sGPIO_Cnfg_Reg->Enable_Reg == Enable_Reg);> assert(&sGPIO_Func_Sel_Reg->Func8_Out_Sel_Cnfg == Func_Reg);>> gezeigt.
Mit assert habe ich noch nicht gearbeitet. Muss ich mir mal anschauen.
Danke für deine Hilfe Harald! Einer von wenigen der nicht nur "ironisch"
helfen will.
So funktioniert es ;)
Johann L. schrieb:> Lothar M. schrieb:>> Oder andersrum: es ist im Grunde Zufall wie diese Structs>> im Speicher abgelegt werden.>> Natürlich ist es kein Zufall, sondern es wird im [E]ABI (Applicaton> Binary Interface) festgelegt / beschrieben.
Zudem gibt es auch in C static_assert, sizeof() und Direktiven zum
Ausrichten und Packen von structs. Damit kann man sicherstellen dass
(oder zumindest prüfen ob) Daten z.B. auf einem avr genauso abgebildet
werden wie auf x64.
SPI_LL_GET_HW bekommt die Adresse von "GPSPI2", richtig?
1
/**
2
* Initialize SPI peripheral (master).
3
*
4
* @param hw Beginning address of the peripheral registers.
5
*/
6
staticinlinevoidspi_ll_master_init(spi_dev_t*hw)
7
{
8
//Reset timing
9
hw->user1.cs_setup_time=0;
10
hw->user1.cs_hold_time=0;
11
12
//use all 64 bytes of the buffer
13
hw->user.usr_miso_highpart=0;
14
hw->user.usr_mosi_highpart=0;
15
16
//Disable unneeded ints
17
hw->slave.val=0;
18
hw->user.val=0;
19
20
hw->clk_gate.mst_clk_active=1;
21
hw->clk_gate.mst_clk_sel=1;
22
23
hw->dma_conf.val=0;
24
hw->dma_conf.tx_seg_trans_clr_en=1;
25
hw->dma_conf.rx_seg_trans_clr_en=1;
26
hw->dma_conf.dma_seg_trans_en=0;
27
}
Hier wird mit (vermutlich) schon den Hardware Registern gearbeitet, sehe
ich das richtig? Zumindest sehe ich sonst keine Funktion mehr die die
Register behandelt.
Wo bekommt "*hw" bzw. "GPSPI2" die Basisadresse von der SPI Einheit her?
Die muss doch irgendwo zugewiesen werden?
Die "spi_struct.h" die sich im Anfang befindet definiert "GPSPI2" stellt
jedoch noch keine "Basis Adresse" da, lediglich evtl. die Offsets für
die einzelnen Register?
Jan H. schrieb:> SPI_LL_GET_HW bekommt die Adresse von "GPSPI2", richtig?
Das Makro wählt mittels ID zwischen verschiedenen Instanzen aus. Hier
allerdings ist nur GPSPI2 für ID==1 vorhanden.
Solche Wrapper sollte man nur verwenden, wenn man sie braucht, also wenn
der Code mehrere Instanzen händelt.
Jan H. schrieb:> Wo bekommt "*hw" bzw. "GPSPI2" die Basisadresse von der SPI Einheit her?
hw bekommt die Adresse beim Aufruf.
GPSPI2 beim Beispiel vorher ist bei seiner Definition an die richtige
Adresse gelegt worden.
Es gibt eine ähnliche Zeile wie
> extern spi_dev_t GPSPI2;
Die heißt dann z.B.
> spi_dev_t GPSPI2 @0x10000;
wobei das @xxx, also die Adresszuweisung, compilerspezifisch ist.
Bruno V. schrieb:>> spi_dev_t GPSPI2 @0x10000;>> wobei das @xxx, also die Adresszuweisung, compilerspezifisch ist.
Das wird dann aber ganz normal in einer .c oder .h gemacht oder im
Linker?