Forum: Mikrocontroller und Digitale Elektronik Präprozessormagie: Kommasepariert aufteilen


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich habe bei mir die folgenden Definitionen:
1
    #define PA0  GPIOA, GPIO_Pin_0
2
    #define PA1  GPIOA, GPIO_Pin_1
3
4
    [...]
5
6
    #define HW_X1_PIN PA0    
7
    #define HW_X2_PIN PA1
Die Makros in den oberen Zeilen sind wiederum in den Headern der 
Standard Peripheral Library für die STM32 festgelegt.

Sinn der Sache war es, in den Pinout-Konfiguration sinnvoll nach ganzen 
Pins durchsuchen zu können ("Ist PA0 schon belegt, oder was war da 
nochmal dran?".

In manchen, seltenen Fällen, will man dann doch die Standard Peripheral 
Funktionen nutzen, in denen man Ports und Pins wieder einzeln benötigt.

Das hier funktioniert:
1
static_inline GPIO_TypeDef * gpio_port(GPIO_TypeDef * const GPIOx, const uint16_t Pin)
2
{
3
    (void) Pin;
4
    return GPIOx;
5
}
6
7
static_inline uint16_t gpio_pin(GPIO_TypeDef * const GPIOx, const uint16_t Pin)
8
{
9
    (void) GPIOx;
10
    return Pin;
11
}
12
13
void foo(void)
14
{
15
    // Setup SCL und SDA pins
16
    GPIO_InitTypeDef GPIO_InitStruct;
17
    GPIO_InitStruct.GPIO_Pin   = gpio_pin(HW_X1_PIN);
18
    GPIO_Init(gpio_port(HW_X1_PIN), &GPIO_InitStruct);
19
}

Das hier funktioniert nicht:
1
#define PORT(a, b) (#a)
2
#define PIN(a, b) (#b)
3
4
void foo(void)
5
{
6
    // Setup SCL und SDA pins
7
    GPIO_InitTypeDef GPIO_InitStruct;
8
    GPIO_InitStruct.GPIO_Pin   = PIN(HW_X1_PIN);
9
    GPIO_Init(PORT(HW_X1_PIN), &GPIO_InitStruct);
10
}

Die Fehlermeldung "macro "PIN" requires 2 arguments, but only 1 given" 
verrät mir auch den Grund. Zum Zeitpunkt, an dem der Präprozessor 
arbeitet, ist diese Komma-Liste nur ein einziges Argument. Wenn der 
Präprozessor fertig ist, sieht die Funktion wieder zwei Argumente.

Nur aus Neugier: Gibt es eine Möglichkeit, in der Präprozessor-Magie 
diese Komma-Sachen wieder aufzulösen?

von Stefan F. (Gast)


Lesenswert?

Irgendwie geht das vielleicht, aber alleine schon die Tatsache, dass du 
nicht innerhalb weniger Sekunden von selbst drauf kommst verleitet mich 
zu dem Ratschlag: lass' es bleiben

Der Punkt ist, das solche Makros-Tricks dem Quelltext am Ende schlechter 
lesbar machen. Hier im Forum wurden schon viele Ansätze präsentiert, wie 
man Port und Pin Nummer zusammen bekommt, aber es läuft am Ende immer 
auch (für nicht eingeweihte) schlecht lesbaren Code hinaus oder 
funktionale Einschränkungen - insbesondere wenn du Dinge ansprichst, die 
mehrere Pins haben (z.B. Displays mit 4/8bit Anschluss).

Wesentlich sinnvoller finde ich ganz normale Funktionen oder meinetwegen 
auch Makros mit lesbaren Namen, wie:

- powerLedOn();
- powerledOff();
- sendCharToDisplay(c);
- leftButton();
- rightButton();

usw. Innerhalb dieser Funktionen dürfen dann gerne die üblichen 
Bezeichner (PORTx, PINx, DDRx, PBx) verwendet werden. Ich sammle solche 
Funktionen gerne in einer oder wenigen Quelltext-Dateien, damit ich sie 
schnell wieder finde falls ich mal die Pin-Zuordnung ändern muss.

Wenn du mal solche Funktionen mit Makros vergleichst wirst du 
feststellen, das das (zumindest beim gcc) performance-mäßig keinen 
nennenswerten Unterschied macht. Die ganz kleinen Funktionen werden vom 
Compiler automatisch ge-inlined, genau wie bei Makros. Oft läuft es im 
Maschinencode auf nur 1-3 Opcodes hinaus (was will man mehr)?

von Thomas Z. (usbman)


Lesenswert?

vieleicht hilft folgendes. Das hab ich schonmal für USB gemacht
1
// use this macros to set a contition on a specific endpoint without changing any other bits 
2
// params ep:  EP number 1..4
3
//        dir: T or R; T selects IN R selects OUT
4
#define UNSTALL(ep,dir) (UEP ##ep ##_CTRL &= (~MASK_UEP_ ##dir ##_RES) | UEP_ ##dir ##_RES_TOUT) // 01

UNSTALL(1,T) expandiert nach
UEP1_CTRL &= ((~MASK_UEP_T_RES) | UEP_T_RES_TOUT)
Thomas

von foobar (Gast)


Lesenswert?

Die Magie lautet hier Indirektion:
1
#define _arg1(a,b) a
2
#define _arg2(a,b) b
3
4
#define PORT(ab)   _arg1(ab)
5
#define PIN(ab)    _arg2(ab)

Ich würde es eher vermeiden ...

von Peter D. (peda)


Lesenswert?

Ich mache mir erstmal einen Schaltplan, d.h ich plaziere das MC-Symbol 
und schreibe dann an alle belegten Pins die Netznamen dran.
Dann schreibe ich alle Netznamen in eine "hardware.h" und weise ihnen 
dann die Portpins zu. Für die zusätzlichen Register hänge ich dann 
Endungen an, _in (Input), _oe (direction), _pu (pullup).
Z.B.:
1
#define W1              PORT_E3
2
#define W1_oe            DDR_E3
3
#define W1_in            PIN_E3

von Stefan F. (Gast)


Lesenswert?

Wie gesagt halte ich nicht viel von diesen Pin-Listen. Die funktionieren 
nur bei einfachen alleine stehenden GPIO Pins. Das Konzept versagt 
schon, sobald du SPI, I²C, parallele Ports, teilweise auch PWM usw. 
verwendest.

Schreibe lieber Funktionen mit aussagekräftigen Namen.

von Walter T. (nicolas)


Lesenswert?

foobar schrieb:
> Die Magie lautet hier Indirektion:

Danke! Ich bin zwar mit der Inline-Funktion nicht unglücklich, aber die 
Makro-Variante hat den angenehmen Nebeneffekt, daß man sie bei 
"static_assert()" nutzen kann.


Peter D. schrieb:
> Ich mache mir erstmal einen Schaltplan,

Bei eigenen Boards: Ich auch. Bei Eval-Boards gibt es nur eine 
(hoffentlich) gut gepflegte Liste.

Peter D. schrieb:
> Dann schreibe ich alle Netznamen in eine "hardware.h" und weise ihnen
> dann die Portpins zu.

Ich auch. Nur wird die (bei mir heißt sie "hw_config.h") bei einem 
64-Pinner doch lang genug, daß man froh ist, wenn man darin (optisch 
oder mit Strg-F) nach "PC1" suchen kann, anstelle der zwei Zeilen
#define HW_X1_GPIO GPIOC
#define HW_X1_Pin  GPIO_Pin_0


Stefanus F. schrieb:
> Das Konzept versagt
> schon, sobald du SPI, I²C, parallele Ports, teilweise auch PWM usw.
> verwendest.

Eigentlich funktioniert das ganz genauso:
1
#define I2C_SDA_PIN   PB9
2
#define I2C_SCL_PIN   PB8
3
4
#define HARD_ENC0     encoder_STM32F4XX_TIM5_PA0_PA1
5
6
#define USART_NO      uart_STM32F4XX_USART2_PA2_PA3
7
#define USART_NO_BAUDRATE 1843200
8
9
#define BUZZER_PWM    buzzer_STM32F4XX_TIM4_PB7
So habe ich weniger Probleme, die Übersicht über bereits belegte 
Peripherie zu verlieren.

Stefanus F. schrieb:
> Schreibe lieber Funktionen mit aussagekräftigen Namen.

Diese Aussage klingt für mich wie "Pizza ist besser als Rotwein." 
Pinlisten und Funktionsnamen sollten meiner laienhaften Meinung nach 
nichts miteinander zu tun haben.

Egal. Das ist irgendwie hier sowieso Off-Topic.

Warum funktioniert:
1
#define _arg1(a,b) a
2
#define PORT(ab)   _arg1(ab)
und nicht:
1
#define PORT(a,b) a

Woran liegt das und wo lernt man das?

von foobar (Gast)


Lesenswert?

> Warum funktioniert: [...] und nicht: [...]. Woran liegt das
> und wo lernt man das?

Das liegt daran, zu welchem Zeitpunkt Makros bzw ihre Argumente ersetzt 
werden.  Ein Anfang wäre z.B.

http://gcc.gnu.org/onlinedocs/cpp/Macro-Pitfalls.html

Ansonsten mal nach "C-Preprocessor magic" suchen.

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.