Forum: Mikrocontroller und Digitale Elektronik Registerzugriff C Code frage


von Epimeth (Gast)


Lesenswert?

Hallo,

ich versuche gerae mit elm chan Code Beispiel einen stm32 mit fatfs zum 
laufen zu bekommen.
Jedoch scheitere ich gerade beim Verständnis, wie ein bestimmtes 
Register beschrieben wird.

Es handelt sich um das
AHB peripheral clock enable register (RCC_AHBENR)
Address offset: 0x14
Reset value: 0x0000 0014

der Zugriff erfolgt über den Befehl
1
__enable_peripheral(SPI1EN); // wobei SPI1EN = 44

im stm32f100.h steht dann
1
#define __enable_peripheral(p)  (&RCC_AHBENR)[p/32] |= 1 << (p % 32)
1
(&RCC_AHBENR)[p/32] |
Ich weiß es handelt sich um einen Pointer aber wozu [p/32]? und was 
macht "|"?

Grüße und
Danke im Voraus

Link zum Manual Kapitel 6.3.6 
http://www.st.com/content/ccc/resource/technical/document/reference_manual/a2/2d/02/4b/78/57/41/a3/CD00246267.pdf/files/CD00246267.pdf/jcr:content/translations/en.CD00246267.pdf

von Dr. Sommer (Gast)


Lesenswert?

Epimeth schrieb:
> [p/32]

Jedes Register hat 32 Bits; so wird der Index des Eintrags auf die 
Nummer des Registers umgerechnet. Der Pointer &RCC_AHBENR wird hier als 
Array betrachtet, und das in [] ist der Index.

Epimeth schrieb:
> und was
> macht "|"?
Das steht in jedem C(++)-Buch im Kapitel über Operatoren.

von stefan us (Gast)


Lesenswert?

Heisst das, der Parameter p könnte auch mehr als 32bit adressieren, also 
auf das Register hinter RCC_AHBENR "zeigen"?

von Jaffa (Gast)


Lesenswert?

stefan us schrieb:
> Heisst das, der Parameter p könnte auch mehr als 32bit adressieren, also
> auf das Register hinter RCC_AHBENR "zeigen"?

Ja, macht er ja bei diesem Beispiel, er zeigt auf RCC_APB2ENR.

von W.S. (Gast)


Lesenswert?

Epimeth schrieb:
> Jedoch scheitere ich gerade beim Verständnis, wie ein bestimmtes
> Register beschrieben wird.

Na ist doch ganz einfach.
Ein kleines Beispiel:
1
#define RCC     0x40021000        //- 0x400213FF 1 K RCC
2
...
3
#define RCC_AHBENR   (*((volatile dword *) (RCC + 0x14)))
4
...
5
6
RCC_AHBENR  = (1<<17) |   // Port A enable
7
              (1<<18) |   // Port B enable
8
              (1<<19) |   // Port C enable
9
              (1<<20) |   // Port D enable
10
              (1<<22);    // Port F enable
So, die passenden #define's findest du in der zugehörigen .h Datei für 
deinen Chip und die Bedeutung der Bits im Register findest du im 
Referenzmanual zum Chip.

Mehr Brimborium braucht es nicht.

W.S.

von Jim M. (turboj)


Lesenswert?

W.S. schrieb:
> RCC_AHBENR  = (1<<17) |   // Port A enable
>               (1<<18) |   // Port B enable
>               (1<<19) |   // Port C enable
>               (1<<20) |   // Port D enable
>               (1<<22);    // Port F enable

Vieeel zuviele "magische Zahlen" im Code. Der Hersteller liefert Dir ja 
eine Header Datei mit den ganzen Bitdefinitionen - die sollte man dann 
auch verwenden.

Da ist ein
1
__enable_peripheral(SPI1EN);

deutlich einfacher lesbar, auch wenn $HERSTELLER die Verwendung der 
reservierten "__" im Support um die Ohren gehaut bekommen sollte.

von Markus F. (mfro)


Lesenswert?

Jim M. schrieb:
> deutlich einfacher lesbar, auch wenn $HERSTELLER die Verwendung der
> reservierten "__" im Support um die Ohren gehaut bekommen sollte.

ich (die) denke(n), das dürfen die.

__ -> reserved for Implementation. Damit ist üblicherweise die 
Kombination aus Compiler + Std Library gemeint. Dein $HERSTELLER 
betrachtet seine Header eben als Teil der Implementierung. Kann man so 
sehen, finde ich.

von Dr. Sommer (Gast)


Lesenswert?

Markus F. schrieb:
> Dein $HERSTELLER
> betrachtet seine Header eben als Teil der Implementierung. Kann man so
> sehen, finde ich.

Wenn schon müssten aber alle internen Bezeichner mit __ anfangen, und 
API's eben nicht. __enable_peripheral ist ein API, und viele interne 
Bezeichner nutzen eben kein __ . Daher ist das also inkonsistent.
Der C Standard definiert nichts mit Peripherie-Takten, daher ist eine 
Funktion dafür eher nicht in der Standard Bibliothek zu suchen. Die 
Funktionen der ST Library braucht man nicht für Standard-Konforme 
Programme, man kann ganz ohne sie alle Funktionalität des Standards 
nutzen. Die ST Library ist nur eine ganz normale externe Bibliothek.
Das __ ist nur für interne Namen von Standard-Headern, und 
Compiler-Builtins erforderlich, damit 100% Standard-Konformer Code, 
welcher ausschließlich Standard-Header inkludiert, keine Kollisionen 
produzieren kann. D.h. wenn folgender Code einen Fehler produzieren 
würde, wäre das __ erforderlich:
1
#include <stdio.h>
2
3
int main () {
4
  puts ("Hello, World!");
5
}
Dem ist aber nicht so; ein korrekterweise "enable_peripheral" genanntes 
Makro sorgt nur für Kollisionen wenn man den ST-Header inkludiert, und 
dann ist's eh nicht mehr Standard.

Tatsächlich ist die grassierende Nutzung von "__"  oder _ + 
Großbuchstabe eher auf Unwissen zurückzuführen, auch gerne bei 
Include-Guards.

Ein klassisches Gegenbeispiel ist das M_PI bei der GNU Toolchain. Laut 
C-Standard ist dieses Programm absolut korrekt:
1
#include <math.h>
2
3
int main () {
4
  int M_PI = 2;
5
}
Aber wenn man es direkt mit "gcc" kompiliert gibt's Fehler, weil der 
C-Header math.h fälschlicherweise ein M_PI definiert, was im Standard 
nicht vorgesehen ist. Daher müsste das Makro eigentlich "__M_PI" o.ä. 
heißen. Allerdings kompiliert der GCC default-mäßig nicht in einem 
standard-konformen Modus; wenn man "-std=c99" o.ä. übergibt ist's 
korrekt.
Wäre das __enable_peripheral in einem Standard-Header wie "stdlib.h" 
o.ä. enthalten, dann wäre das __ erforderlich und korrekt.

von Rolf M. (rmagnus)


Lesenswert?

Dr. Sommer schrieb:
> Aber wenn man es direkt mit "gcc" kompiliert gibt's Fehler, weil der
> C-Header math.h fälschlicherweise ein M_PI definiert, was im Standard
> nicht vorgesehen ist. Daher müsste das Makro eigentlich "__M_PI" o.ä.
> heißen. Allerdings kompiliert der GCC default-mäßig nicht in einem
> standard-konformen Modus; wenn man "-std=c99" o.ä. übergibt ist's
> korrekt.

Wenn du im nicht-ISO-C-Modus compilierst (und der ist eben bei gcc der 
Default), kann man kaum davon sprechen, dass die Definition falsch ist, 
weil ISO C sie nicht erlaubt.

von Dr. Sommer (Gast)


Lesenswert?

Ja, sie wäre nur im ISO Modus falsch. Es ist nur ein Beispiel was falsch 
wäre.

von W.S. (Gast)


Lesenswert?

Jim M. schrieb:
> Vieeel zuviele "magische Zahlen" im Code.
> ...
> Da ist ein __enable_peripheral(SPI1EN);
>
> deutlich einfacher lesbar,

Hä?
Und dann schreibst du für jeden Peripherie-Core einen separaten 
"__enable_peripheral(...)" hin, ja?

Oder du hangelst dich durch ein Dickicht von zusätzlich deklarierten 
Verschiebe- und Mask-Hilfsdeklarationen durch, bloß weil du partout 
NICHT ins zuständige Manual schauen willst?

Mannomann, warum suchen Leute wie du immer nur nach der uneffektivsten, 
fehlerträchtigsten und unleserlichsten Variante, irgend was zu 
programmieren?

kopfschüttel...

W.S.

von Epimeth (Gast)


Lesenswert?

Hallo,

erstmal Danke für die vielen guten Antworten. :-)
1
#define __enable_peripheral(p)  (&RCC_AHBENR)[p/32] |= 1 << (p % 32)
mit p = 44 wird dann
1
 // [p/32]--> 44/32= 1 --> [1] und p%32 --> 12
2
&RCC_AHBENR)[1] |= 1 << 12  // &RCC_AHB2ENR = 1 << 12

Ich dachte vorher nur man müsste wenn man einen Index verwendet, dies 
vorher definieren.

Eine Frage nur noch zum Verständnis eine Speicheradresse hält immer ein 
Byte bzw 8 Bit und dadurch kommt der Offset von 0x004 von einer 
Registeradresse zur nächsten Registeradresse zustande, wenn die 
Registerlänge 32 Bit ist?

von S. R. (svenska)


Lesenswert?

Epimeth schrieb:
> Ich dachte vorher nur man müsste wenn man einen Index verwendet, dies
> vorher definieren.

Der Präprozessor macht Textersetzung, der Compiler sieht dort 
tatsächlich nur die nackten Zahlen.

Epimeth schrieb:
> Eine Frage nur noch zum Verständnis eine Speicheradresse hält immer ein
> Byte bzw 8 Bit und dadurch kommt der Offset von 0x004 von einer
> Registeradresse zur nächsten Registeradresse zustande, wenn die
> Registerlänge 32 Bit ist?

Ja, per Konvention sind alle Adressen Byte-Adressen.

Das ist aber nicht überall so. Ein Gegenbeispiel sind Flash-Adressen auf 
dem AVR, die zeigen immer auf einen 16 Bit-Wert und werden vom 
AVR-Assembler auch so behandelt. Einzelne Bytes lassen sich dort nicht 
adressieren. (Die GNU-Toolchain für AVR, also auch der GNU-Assembler, 
nutzen aber auf jeder Architektur Byte-Adressen.)

von Rolf M. (rmagnus)


Lesenswert?

S. R. schrieb:
> Ein Gegenbeispiel sind Flash-Adressen auf
> dem AVR, die zeigen immer auf einen 16 Bit-Wert und werden vom
> AVR-Assembler auch so behandelt. Einzelne Bytes lassen sich dort nicht
> adressieren.

Das stimmt so nicht. Der Speicher ist 16 Bit breit, und alle 
Instruktionen sind entweder 16 oder 32 Bit breit, daher macht natürlich 
eine Byte-Adresse als Sprungziel keinen Sinn.
Aber man kann auf Assembler-Ebene die Daten 8-Bittig adressieren. Muss 
man ja können, da ein Register nicht mehr als 8 Bit aufnehmen kann. 
Siehe 
https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_LPM.html

Beitrag #5454387 wurde von einem Moderator gelöscht.
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.