1

I just started doing some C (coming from Java). I'm trying to figure out what the language's approach to a conditional based on a define's name is.

e.g. I have a huge header file that I can't(shouldn't) edit with a lot of defines.

#define GPIO_OTYPER_OT_0                     ((uint32_t)0x00000001)
#define GPIO_OTYPER_OT_1                     ((uint32_t)0x00000002)
#define GPIO_OTYPER_OT_2                     ((uint32_t)0x00000004)
#define GPIO_OTYPER_OT_3                     ((uint32_t)0x00000008)
#define GPIO_OTYPER_OT_4                     ((uint32_t)0x00000010)
#define GPIO_OTYPER_OT_5                     ((uint32_t)0x00000020)

And so on;

I want to make a function/declaration (or whatever to the solution is) to act on the _# part of the define.

(pseudocode)

void initialize(int X) {
  GPIOA->MODER |= GPIO_MODER_MODER%X_5;
  GPIOA->OTYPER &= ~GPIO_OTYPER_OT_%X;
  GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR%X;
  GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;
  GPIOA->ODR |= GPIO_ODR_ODR_%X;
}

Where %X is int X

All I can think of is a switch statement for each X but X has a large range so the source file would be huge.

edit: https://github.com/espruino/Espruino/blob/master/targetlibs/stm32f4/lib/stm32f411xe.h is the header file.

Guillaume
  • 1,022
  • 1
  • 9
  • 17
  • 2
    I'd generate a second header that mimics the original (unchangeable) header, but does so sanely. For example: `#define GPIO_OTYPER_OT(x) ((uint32_t)1 << (x))` and then perhaps `#define GPIO_OTYPER_OT_0 GPIO_OTYPER(0)` etc. And generate code to cross-check the results. Then use the sane header, not the insane one. – Jonathan Leffler Aug 07 '16 at 19:42
  • 1
    I would look at the problem you are trying to solve to see why what you are doing is the wrong way to solve it. – stark Aug 07 '16 at 19:55
  • 1
    Correct me if I'm wrong, but `initialize()` smells an awful lot like the next thing you'd do is call it in a loop "to initialise each GPIO pin"; which would be a definite case of thinking at the wrong level. If you're initialising, do so at the _port_ level, i.e. `GPIOA->OTYPER = GPIO_OTYPER_OT_0 | GPIO_OTYPER_OT_2 | GPIO_OTYPER_OT_5;`. A barrage of read-modify-write operations on the same register is a complete waste of cycles. I suspect you're not jumping _straight_ into bit-banging bidirectional protocols or anything else which might require reconfiguring arbitrary GPIOs at runtime. – Notlikethat Aug 07 '16 at 21:23
  • Why are you using a macro? Just shift by X with `1< – artless noise Aug 08 '16 at 13:45

2 Answers2

1

There is no way to insert an arbitrary integer into a macro name. However, your integers are going to be small (smaller than 32 for sure, because all your constants are of a 32-bit type). So you can convert a switch-statement into a one-line expression, like so:

x == 0 ? GPIO_OTYPER_OT_0 : \
x == 1 ? GPIO_OTYPER_OT_1 : \
x == 2 ? GPIO_OTYPER_OT_2 : \
...
x == 31 ? GPIO_OTYPER_OT_31 : 0

Here, you can even make a "default" expression that will generate a runtime error - something like (abort(), (uint32_t)0).

To make this more generic, separate the GPIO_OTYPER_OT_ part into a macro argument, and use the "paste operator" ##:

#define MY_MACRO(name, x) \
x == 0 ? name ## 0 : \
x == 1 ? name ## 1 : \
x == 2 ? name ## 2 : \
...
x == 31 ? name ## _31 : \
(abort(), name ## 0)

Usage example:

GPIOA->ODR |= MY_MACRO(GPIO_ODR_ODR_, x);

You have to make a separate macro for those names that have x in the middle:

#define MY_MACRO2(prefix, x, suffix) ( \
(x) == 0 ? prefix ## 0 ## suffix : \
(x) == 1 ? prefix ## 1 ## suffix : \
...
(x) == 31 ? prefix ## 31 ## suffix : \
(abort(), prefix ## 0 ## suffix))

Here I also added the necessary parentheses (around x and around the whole macro), like it is customary with C macros.


P.S. If your large header file doesn't define macros with numbers up to 31, but has a smaller limit, you cannot use the macro that mentions all these names, because you would get a compilation error. In that case, insert the maximum into the name of the macro. Then you can define them in a "recursive" way:

#define MY_MACRO_MAX1(prefix, x) \
x == 0 ? prefix ## 0 ## suffix : prefix ## 1 ## suffix

#define MY_MACRO_MAX2(prefix, x) \
x == 2 ? prefix ## 2 ## suffix : MY_MACRO_MAX1(prefix, x)

#define MY_MACRO_MAX3(prefix, x) \
x == 3 ? prefix ## 3 ## suffix : MY_MACRO_MAX2(prefix, x)

#define MY_MACRO_MAX4(prefix, x) \
x == 4 ? prefix ## 4 ## suffix : MY_MACRO_MAX3(prefix, x)

#define MY_MACRO_MAX5(prefix, x) \
x == 5 ? prefix ## 5 ## suffix : MY_MACRO_MAX4(prefix, x)

...
anatolyg
  • 26,506
  • 9
  • 60
  • 134
1

Use ST's GPIO abstraction layer, which can be found here. Notably, see GPIO_InitTypeDef which gives you a structure for what you're doing above, and GPIO_Init which will actually do what you want. The initialization structure takes pins as a bit mask, so as @artless noise suggested in a comment, you can just do 1<<X to create your mask. All the MCU-specific behavior and register mapping is hidden away from your code.

If you are trying to implement your own driver layer as an exercise or because you think the ST library is not very good, then I would still take a look at how they implemented GPIO_Init in the C file. They use shifting, but you'll note that when dealing with the registers, it is not always as easy as 1<<X (though, note that for their configuration structure it is always that easy). Some registers have multiple bits for each pin (mode: 2 bits, pull config: 2 bits, alternate function: 4 bits, split across multiple registers).

edit: I'm not suggesting adding more libraries that you don't already have. The library/code base you referenced the header file in already includes ST's peripheral library, so it makes sense (to me) to use it.

rjp
  • 1,760
  • 13
  • 15
  • Thank you, I wanted to implement it thru the CMSIS library to better understand how it works (without the "abstraction"). I'll definitely check out how the HAL was implemented. And probably start using when I'm comfortable with my understanding of things. – Guillaume Aug 08 '16 at 18:14
  • It's a good exercise to write your own drivers, though it's not the most exciting. I'd usually suggest getting a feel for how low level drivers are usually structured before trying your own, but everyone learns differently. Do what you think will help you the most. – rjp Aug 08 '16 at 21:32
  • I would also suggest checking for a newer version of the header from ST. I believe NXP/Freescale has started suppying macros that take pin number as a parameter, so there may be a newer version from ST that has something similar. – rjp Aug 08 '16 at 21:33