Forum: Mikrocontroller und Digitale Elektronik Präprozessor


von C. W. (chefkoch)


Lesenswert?

Hallo *,

ich habe ein Makro für die Bitmanipulation:



struct bits {
  unsigned char b0:1;
  unsigned char b1:1;
  unsigned char b2:1;
  unsigned char b3:1;
  unsigned char b4:1;
  unsigned char b5:1;
  unsigned char b6:1;
  unsigned char b7:1;
  unsigned char b8:1;
  unsigned char b9:1;
  unsigned char b10:1;
  unsigned char b11:1;
  unsigned char b12:1;
  unsigned char b13:1;
  unsigned char b14:1;
  unsigned char b15:1;
} __attribute__((_packed_));


#define BIT( port, pin )  ((*(volatile struct bits*)&port).b##pin)

Beim Atmega ist das kein Problem, da z.B. PORTB direkt das 
Ausgangsregister ist. Beim STM32 ist funktioniert es, wenn ich es so 
angebe:

#define  LED_gruen      BIT( GPIOD->ODR, 12 )

Hier ist ja das ODR nur eine Element von GPIOx. Ich würde im Define, 
z.B. der LED, aber lieber nur "GPIOx" und den Operand "->" mit dem 
Element "ODR" gerne im Makro hinzufügen. Gibt es da eine Möglichkeit?

von W.S. (Gast)


Lesenswert?

C. W. schrieb:
> #define  LED_gruen      BIT( GPIOD->ODR, 12 )

Was willst du eigentlich?
Möglichst unlesbaren Code erzeugen?
Und wie müßte man schreiben, damit die LED rot oder gelb wird?

Das Ganze ist eine Bit-Verhunzung ohne wirklichen Nutzeffekt. Laß sowas 
einfach bleiben und befasse dich lieber mit dem effektiven Benutzen von 
Peripheriebits bei ARM und Cortex-Architekturen. Hier gibt es Register 
zum Setzen von Bits und andere zum Löschen von Bits. Das möglichst 
perfide Formulieren von Bits ist hingegen einfach nicht hilfreich.

W.S.

von Walter T. (nicolas)


Lesenswert?

W.S. schrieb:
> Was willst du eigentlich?
> Möglichst unlesbaren Code erzeugen?

Ich hole mal Popcorn. Nicht, daß Du Unrecht hättest. Aber der Post 
dürfte wenig dabei helfen, Einsicht zu erzeugen.

@TO:
Das Define ist wirklich perfekt dazu, Verwirrung zu erzeugen. Mach 
einfach zwei Defines:
1
#define LED_GPIO GPIOD
2
#define LED_Pin (1U<<PD12)
und schreib Dir Befehle, mit denen Du die oberen und unteren Bits des 
BSSR-Registers manipulieren kannst. Ungefähr so:
1
    /* Einzelnes oder mehrere Bits eines Ports setzen
2
     *
3
     * GPIOx
4
     * Pin: Bitmaske */
5
    static inline __attribute__((always_inline)) void
6
    SetBit(GPIO_TypeDef * const GPIOx, const uint16_t Pin) 
7
    {
8
        GPIOx->BSRR = Pin;
9
    }
10
11
12
13
    /* Einzelnes oder mehrere Bits eines Ports loeschen
14
     *
15
     * GPIOx
16
     * Pin: Bitmaske */
17
    static inline __attribute__((always_inline)) void
18
    ClearBit(GPIO_TypeDef * const GPIOx, const uint16_t Pin) 
19
    {
20
        GPIOx->BSRR = (uint32_t) Pin<<16U;
21
    }

von [c] (Gast)


Lesenswert?

C. W. schrieb:
> aber lieber nur "GPIOx" und den Operand "->" mit dem
> Element "ODR" gerne im Makro hinzufügen. Gibt es da eine Möglichkeit?
Ja:
1
#define GPIOx (GPIOD->ODR)
2
#define  LED_gruen      BIT( GPIOx , 12 );

W.S. schrieb:
> Was willst du eigentlich?
Vermutlich
1
LED_gruen = 1;
und
1
LED_gruen = 0;
verwenden können.

von [c] (Gast)


Lesenswert?

Sry, hab die Frage erst falsch verstanden. Die gesuchte Lösung ist:
1
#define BIT( port, pin )  ((*(volatile struct bits*)&port->ODR).b##pin)

von [c] (Gast)


Lesenswert?

Dann würde ich die Makro aber nicht mehr BIT nennen sondern GPIO_OUT_BIT 
oder ähnliches, weil man nicht mehr auf beliebige bits zugreifen kann, 
nur mehr auf die bits von port->ODR.

von Bernd K. (prof7bit)


Lesenswert?

Bau Dir ne Abstraktion aus vielen einzelnen statischen 
Inlinefunktionen für jeden Pin einzeln die es Dir erlaubt später im Code 
einfach zum Beispiel sowas zu schreiben:
1
    LED_GRUEN_high();
2
    LED_BLAU_low();
3
    LED_ROT_low();

Praktischerweise kann man die alte x-Makro Technik (Google das mal) 
verwenden um den Preprozessor automatisch dutzende von statischen 
inline-Funktionen erzeugen zu lassen, für jeden GPIO-Pin jeweils 
welche zum setzen, löschen, lesen, konfigurieren als Eingang, Ausgang, 
etc.

Ich habe leider kein Beispiel für den STM32 da aber zum Beispiel bei 
einem Kinetis sieht das dann zum Beispiel so aus:


(oben trägst Du die gewünschten Namen und Pinzuordnungen ein und der 
Präprozessor erzeugt für die unten verwendeten beispielhaften drei Pins 
insgesamt 21 statische inline-Funktionen (7 für jeden) plus eine 
init-Funktion)
1
/*
2
 * gpio.h
3
 *
4
 *  Created on: 14.05.2015
5
 *      Author: bernd
6
 */
7
8
#ifndef GPIO_H_
9
#define GPIO_H_
10
11
#include <MKL05Z4.h>
12
#include <stdbool.h>
13
14
15
#define DIGITAL_IN_LIST \
16
17
18
#define DIGITAL_OUT_LIST \
19
    PIN(LED_RED,                B,8) \
20
    PIN(LED_GREEN,              B,9) \
21
    PIN(LED_BLUE,               B,10) \
22
23
#define MUX_ANALOG_LIST \
24
25
26
#define MUX_2_LIST \
27
28
29
#define MUX_3_LIST \
30
31
32
33
/**
34
 * Some convenience macros, used in the implementations below
35
 */
36
37
#define STATIC_INLINE   static inline __attribute((always_inline))
38
39
#define ALL_DIGITAL_LIST \
40
    DIGITAL_IN_LIST \
41
    DIGITAL_OUT_LIST \
42
43
#define ALL_PINS_LIST \
44
    ALL_DIGITAL_LIST \
45
    MUX_ANALOG_LIST \
46
    MUX_2_LIST \
47
    MUX_3_LIST \
48
49
50
/**
51
 * Digital in/out functions
52
 */
53
54
#define PIN(name, p, n) STATIC_INLINE void name##_high() { FPT##p->PSOR = (1UL << n); }
55
ALL_DIGITAL_LIST
56
#undef PIN
57
58
#define PIN(name, p, n) STATIC_INLINE void name##_low() { FPT##p->PCOR = (1UL << n); }
59
ALL_DIGITAL_LIST
60
#undef PIN
61
62
#define PIN(name, p, n) STATIC_INLINE void name##_toggle() { FPT##p->PTOR = (1UL << n); }
63
ALL_DIGITAL_LIST
64
#undef PIN
65
66
#define PIN(name, p, n) STATIC_INLINE void name##_as_output() { FPT##p->PDDR |= (1UL << n); }
67
ALL_DIGITAL_LIST
68
#undef PIN
69
70
#define PIN(name, p, n) STATIC_INLINE void name##_as_input() { FPT##p->PDDR &= ~(1UL << n); }
71
ALL_DIGITAL_LIST
72
#undef PIN
73
74
#define PIN(name, p, n) STATIC_INLINE bool name##_is_high() { return FPT##p->PDIR & (1UL << n); }
75
ALL_DIGITAL_LIST
76
#undef PIN
77
78
#define PIN(name, p, n) STATIC_INLINE bool name##_is_low() { return !(FPT##p->PDIR & (1UL << n)); }
79
ALL_DIGITAL_LIST
80
#undef PIN
81
82
83
/**
84
 * Automatic initialization function for everything
85
 */
86
87
STATIC_INLINE void gpio_init() {
88
    SIM->SCGC5 |= (SIM_SCGC5_PORTA_MASK | SIM_SCGC5_PORTB_MASK);
89
90
    #define PIN(name, p, n) PORT##p->PCR[n] = PORT_PCR_MUX(0);
91
    MUX_ANALOG_LIST
92
    #undef PIN
93
94
    #define PIN(name, p, n) PORT##p->PCR[n] = PORT_PCR_MUX(2);
95
    MUX_2_LIST
96
    #undef PIN
97
98
    #define PIN(name, p, n) PORT##p->PCR[n] = PORT_PCR_MUX(3);
99
    MUX_3_LIST
100
    #undef PIN
101
102
    #define PIN(name, p, n) PORT##p->PCR[n] = PORT_PCR_MUX(1);
103
    ALL_DIGITAL_LIST
104
    #undef PIN
105
106
    #define PIN(name, p, n) name##_as_output();
107
    DIGITAL_OUT_LIST
108
    #undef PIN
109
110
    #define PIN(name, p, n) name##_as_input();
111
    DIGITAL_IN_LIST
112
    #undef PIN
113
}
114
115
116
#endif /* GPIO_H_ */

(vorsicht, das ist eine sehr frühe Variante, wahrscheinlich noch nicht 
vollständig oder fehlerhaft, die aktuelle hab ich grad nicht zur Hand)

Wenn Du die Hardware änderst dann muss nur noch dieses eine Headerfile 
geändert oder getauscht werden, die Tatsache daß ich das auch schon bei 
AVR genau so gemacht habe hat mir schon so manches Portieren erheblich 
vereinfacht.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

Bernd K. schrieb:
> Bau Dir ne Abstraktion aus vielen einzelnen statischen
> Inlinefunktionen für jeden Pin einzeln die es Dir erlaubt später im Code
> einfach zum Beispiel sowas zu schreiben:
>     LED_GRUEN_high();
>     LED_BLAU_low();
>     LED_ROT_low();



OK, ich habe jetzt eine Stunde gewartet, daß jemand hervorgesprungen 
kommt und "April April!"  schreibt.

[c] schrieb:
> Die gesuchte Lösung ist:#define BIT( port, pin )  ((*(volatile struct
> bits*)&port->ODR).b##pin)

Wobei hier das Beschreiben eines Pins alles andere als atomar ist. Über 
ODR ergibt das eine read-modify-write-Operation.

Atomare Pinfunktionen ergeben sich durch das Beschreiben von BSRR zum 
Setzen und Löschen - das Register ist für genau diesen Zweck da.

Wenn die Art von Schreiboperation:

> VermutlichLED_gruen = 1;
> und
> LED_gruen = 0;

wirklich eine echte Forderung ist, lautet mein Rat: Wechsel auf C++ und 
schreibe die entsprechenden Setter und Getter.

von Uwe Bonnes (Gast)


Lesenswert?

Zum Setzen und Zuruecksetzen eigent sich das BSR/BRR/BSRR besser.

von C. W. (chefkoch)


Lesenswert?

Also doch eher so:

#define LED_gruen_on GPIOD->BSRRL = GPIO_Pin_12
#define LED_orange_on GPIOD->BSRRL = GPIO_Pin_13
#define LED_rot_on GPIOD->BSRRL = GPIO_Pin_14
#define LED_blau_on GPIOD->BSRRL = GPIO_Pin_15

#define LED_gruen_off GPIOD->BSRRH = GPIO_Pin_12
#define LED_orange_off GPIOD->BSRRH = GPIO_Pin_13
#define LED_rot_off GPIOD->BSRRH = GPIO_Pin_14
#define LED_blau_off GPIOD->BSRRH = GPIO_Pin_15

?

von Bernd K. (prof7bit)


Lesenswert?

Walter T. schrieb:
> OK, ich habe jetzt eine Stunde gewartet, daß jemand hervorgesprungen
> kommt und "April April!"  schreibt.

Lol, warum?

von Bernd K. (prof7bit)


Lesenswert?

C. W. schrieb:
> Also doch eher so:
>
> #define LED_gruen_on GPIOD->BSRRL = GPIO_Pin_12
> #define LED_orange_on GPIOD->BSRRL = GPIO_Pin_13
> #define LED_rot_on GPIOD->BSRRL = GPIO_Pin_14
> #define LED_blau_on GPIOD->BSRRL = GPIO_Pin_15
>
> #define LED_gruen_off GPIOD->BSRRH = GPIO_Pin_12
> #define LED_orange_off GPIOD->BSRRH = GPIO_Pin_13
> #define LED_rot_off GPIOD->BSRRH = GPIO_Pin_14
> #define LED_blau_off GPIOD->BSRRH = GPIO_Pin_15
>
> ?

Kommt der Sache schon näher, ist aber irrsinniger Schreibaufwand und 
fehlerträchtig. Alternativen:

(0) Akzeptiere den Schreibaufwand und mache es so

(1) Ein separates Skript (Python, perl, whatever) das alle diese Makros 
einmalig (oder wann immer sich die Hardware ändert) aus einer simplen 
einfach strukturierten Pin-Zuordnungsliste erzeugt

(2) Wie (1) aber als Scriptsprache nimmst Du den C-Preprozessor selbst, 
allerdings kann der keine Makros erzeugen sondern nur reinen C-Code, 
also erzeugst Du dann halt eben Inlinefunktionen die das selbe tun wie 
obige Makros, das wäre meine Lösung die ich oben gepostet habe.

(3) Riskiere den eventuellen Verlust von ein paar Taktzyklen und mache 
universelle Funktionen die Port und Pin als Funktionsargument nehmen, 
wenn Du es geschickt anstellst und etwas Glück hast optimiert der 
Compiler das per Constant-Propagation auch noch soweit daß da zur 
Laufzeit ebenfalls kein Overhead mehr bleibt (-> ausprobieren, mit 
avr-gcc gehts definitiv, mit arm-gcc hab ichs noch nicht versucht).

(4) Selbes Prinzip wie (3) nur mit Makros statt Funktionen, da muss man 
aber etwas in die Trickkiste greifen um zwei Argumente (Pin und Port) 
als eines zu übergeben, geht aber meist auch ganz gut.

(5) C++ und Templates würde wahrscheinlich auch elegant funktionieren, 
das wäre dann sinngemäß wie (2) allerdings diesmal nicht über den 
Preprozessor sondern direkt unter Verwendung der vorhandenen (C++) 
Sprachfeatures.

Such Dir eins aus.

von Walter T. (nicolas)


Lesenswert?

Bernd K. schrieb:
> Walter T. schrieb:
>> OK, ich habe jetzt eine Stunde gewartet, daß jemand hervorgesprungen
>> kommt und "April April!"  schreibt.
>
> Lol, warum?

Weil hier mit viel Aufwand eine Abstraktionsebene eingezogen würde, die 
eigentlich gar nicht benötigt wird.

Direkte GPIO-Pinoperationen sind bei meinen Projekten zu schätzungsweise 
90% in irgendwelchen Peripherie-Treibern versteckt. Dort ist eine 
Abstraktion auf dem Level:
1
SetBit(ADC_CS_GPIO,ADC_CS_Pin);
als komfortabler, gut lesbarer Einzeiler anzusehen. Eine Zwischenschicht 
à la:
1
ADC_CS_HIGH();
macht das Ganze nicht besser lesbarer, bedeutet aber pro Pin wieder 
mindestens zwei neue Makros. Sprich: Auf Peripherie-Treiber-Ebene ist 
diese Makro-Zwischenschicht unnötig.

Es bleiben also die übrigen ca. 10%. Davon sind wiederum die meisten 
Debug-Ausgaben, die später auskommentiert werden, in der folgenden Form:
1
SetBit(LED_SPARE0_GPIO,LED_SPARE0_Pin); // DEBUG
2
doSomeWeirdAndLongCalculation();
3
ClearBit(LED_SPARE0_GPIO,LED_SPARE0_Pin); // DEBUG

Für diese wenigen Stellen, die ohnehin nicht lange im Quelltext 
verbleiben, würde ich auch keine Extra-Makro-Sammlung implementieren 
wollen.

Es verbleiben damit winzig wenige Fälle, wo ich auf 
"Anwendungsprogramm-Ebene" wirklich Pins behandeln will.

LEDs gehören allerdings nicht dazu. Bei LEDs bietet es sich an, alle 
LEDs in einer gemeinsamen led_refresh()- oder led_display()-Routine zu 
steuern, die wiederum einfach die Darstellung der internen 
Zustandsmaschine übernimmt. So lassen sich auch LED-Zustände wie "blink" 
komfortabel implementieren.

Nebenbei:
Was will ich eigentlich mit Pin-Bezeichnern wie "LED_GRUEN"? Selbst bei 
meinen Hobbyprojekten gehe ich mittlerweile davon aus, daß sich die 
LED-Farbe im Laufe des Projekts noch ändern kann. Insbesondere bei 
Projekten, die auch nachgebaut werden. Sinnvolle Pin-Bezeichner wären:
"LED_DATA", "LED_CONNECT", "LED_RDY", "LED_BUSY".

von Eric B. (beric)


Lesenswert?

Walter T. schrieb:
> Nebenbei:
> Was will ich eigentlich mit Pin-Bezeichnern wie "LED_GRUEN"? Selbst bei
> meinen Hobbyprojekten gehe ich mittlerweile davon aus, daß sich die
> LED-Farbe im Laufe des Projekts noch ändern kann. Insbesondere bei
> Projekten, die auch nachgebaut werden. Sinnvolle Pin-Bezeichner wären:
> "LED_DATA", "LED_CONNECT", "LED_RDY", "LED_BUSY".

Vielleicht baut er ja eine (Modelleisenbahn-)ampel. Dann ist LED_GRUEN 
usw schon sinnvoll ;-)

von Bernd K. (prof7bit)


Lesenswert?

Walter T. schrieb:
> bedeutet aber pro Pin wieder
> mindestens zwei neue Makros. Sprich: Auf Peripherie-Treiber-Ebene ist
> diese Makro-Zwischenschicht unnötig.

SetBit(ADC_CS_GPIO,ADC_CS_Pin);

Ist auch schon eine Abtraktion, nur daß Du hier pro Pin doppelt so viele 
Makros definieren musst wie ich. Warum strebst Du nicht stattdessen 
etwas an wie

SetBit(ADC_CS);

Das wäre erheblich sauberer und abstrahiert auch die willkürlichge 
Annahme daß ein Pin stets durch ein Tupel aus Port und Nummer bestimt 
ist. Was ist wenn manche Pins zusätzlich noch eine dritte Angabe 
brauchen, z.B. eine Busadresse? Oder sie sind durch IP-Adressen und 
Gerätenamen adressiert? Was dann? Schreibst Du dann alle Deine Treiber 
um?

von Walter T. (nicolas)


Lesenswert?

Bernd K. schrieb:
> nur daß Du hier pro Pin doppelt so viele
> Makros definieren musst wie ich.

Hm. Ich zähle bei mir zwei (xx_GPIO und xx_Pin) und bei Dir zwei (xx_on, 
xx_off).

Nur daß ich mit meinen zwei Makros
 a) die Pins auch initialisieren kann und
 b) für die Definition des I/O-Mappings für jede Plattform nur Angaben 
brauche, die auch im Schaltplan zu finden sind (PORT+Pin). Ich finde 
diese Form der Abstraktion sauberer als mit Pin+Register (BSSR) die 
Definition des I/O-Mappings (Hardware/Datenblatt) mit der 
Implementierung (Programming manual) zu vermischen.

Bernd K. schrieb:
> Was ist wenn manche Pins zusätzlich noch eine dritte Angabe
> brauchen, z.B. eine Busadresse?

Warum sollte ein Pin eine Busadresse benötigen? Durch Port+Pin ist der 
Pin eindeutig festgelegt.

: Bearbeitet durch User
von 900ss (900ss)


Lesenswert?

Bernd K. schrieb:
> Bau Dir ne Abstraktion

OT: Hmmmm..... eigentlich ist das eine ziemliche Schweinerei, die Makros 
so zu verwenden. Blickt später kein Fremder mehr durch.

Aber ich mach auch einiges so. :-) Das vereinfacht einige Dinge so 
dermaßen, dass selbst meine Kollegen, die erst einmal Schnappatmung 
bekamen, als sie das sahen, es jetzt auch verwenden. :-)

Ansonsten bin ich auch eher für klaren, gut lesbaren Code. Dazu gehört 
das definitiv nicht.

von Bernd K. (prof7bit)


Lesenswert?

Walter T. schrieb:
> Hm. Ich zähle bei mir zwei (xx_GPIO und xx_Pin) und bei Dir zwei (xx_on,
> xx_off).
>

Sprechen wir über den selben code? Ich glaube eher nicht, denn:

Ich zähle bei mir genau eine Definition pro Pin:
1
    PIN(LED_RED,                B,8) \
2
    PIN(LED_GREEN,              B,9) \
3
    PIN(LED_BLUE,               B,10) \

> Nur daß ich mit meinen zwei Makros
>  a) die Pins auch initialisieren kann und

Wenn Du mal weiter runter scrollen magst dann siehst Du die automatisch 
generierte gpio_init() die alle Pins automatisch initialisiert (samt 
Pin-Muxing entsprechend der Kategorie in die ich den Pin oben 
einsortiert habe) und andere auf der jeweiligen Hardware nötigen 
Initialisierungen (z.B. hier das clock gating Register im SIM)

>  b) für die Definition des I/O-Mappings für jede Plattform nur Angaben
> brauche, die auch im Schaltplan zu finden sind (PORT+Pin).

Ich finde die obigen Angaben (z.B. hier B8, B9, B10) immer bei mir im 
Schaltplan, denn das sind die logischen Pin-Namen die auch im Datenblatt 
und konsequent auch im Schaltplansymbol verwendet werden.

von Walter T. (nicolas)


Lesenswert?

Bernd K. schrieb:
> [...] dann siehst Du die automatisch
> generierte gpio_init() die alle Pins automatisch initialisiert [...]

OK, so langsam verstehe ich den Zweck Deiner Präprozessororgien. Da bin 
ich wirklich froh, daß meine Projekte bisher alle überschaubar genug 
waren, auf soetwas verzichten zu können. Ich bin ja der letzte, der 
etwas gegen Overengineering hat, solange er es nicht selbst machen muß 
:-)

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.