J-Dizzle
J-Dizzle

Reputation: 5153

Pin Mask Conventions for Embedded Development

For embedded C applications I have always used the following convention for defining GPIO pin masks:

'Traditional' Example: 32-bit CPU with 32-bit GPIO port

Assert bit5 in the GPIO output register to turn an led on.

#define BIT5 (1u << 5)      //or maybe even just hardcode as 0x20
...
#define PIN_LED_CTRL (BIT5) //LED_CTRL is Pin #5
...
void gpio_set(uint16_t pin_mask) {
    GPIO_OUT |= pin_mask;
}
...
//turn the led on
gpio_set(PIN_LED_CTRL);

On a recent multi-developer embedded project, one developer has chosen the following syntax

'Alternative Example: 32-bit CPU with 32-bit GPIO port

#define PIN(x) (1u << (x##_PIN_NUM))
...
#define LED_CTRL_PIN_NUM (5)
...
void gpio_set(uint16_t pin_mask) {
    GPIO_OUT |= pin_mask;
}
...
//turn the led on
gpio_set(PIN(LED_CTRL));

No real clear explanation of why this was chosen was given. As with all lightly documented code, it seems mysterious enough to 'cleary' warrant its implementation. Surely the developer knew something I did not. And in this case, the guy is a smart cookie from the CPU-driver world.

Objection

I don't like the 'alt' method at all. It seems too cute for its own good. But the only justifications I can give are:

But this just appears to me like complaining; none of these are real objections to using the 'alt' method.

Question

Why would someone use the 'alt' method at all? Is this a hand-me-down from register-usage in the desktop driver land perhaps?

Do any common embedded MCU libraries, alternative targets or programming languages use this 'alt' method?

Thanks!

*at the end of the day; I'm likely to stick with 'it looks weird' :).

Upvotes: 1

Views: 3362

Answers (4)

jeb
jeb

Reputation: 82390

Both methods are nearly the same, only the style is different, but the problems are the same.

Normally a pin isn't defined by a bit number only, you also need to know the port.

So when changing the port, you need to modify not only the definitions, you also need to modify code.

#define PIN_LED_CTRL (BIT5) //LED_CTRL is Pin #5
...
void gpio_set_port2(uint16_t pin_mask) {
    GPIO2_OUT |= pin_mask;
}
...
//turn the led on
gpio_set_port2(PIN_LED_CTRL);

I prefere to define the pin only once and all the different dependencies are build by macros or in functions.

#define HW_SPI_CLOCK    2,5    // uses port2,bit5
#define HW_RESET_EXT    4,3    // uses port4,bit3

Then I use some macros to define get the port direction, pushPull and other registers.
These macros depends hardly on the plattform and the toolchain.

/**
 * Generic GH-Macros
 */
#define PASTE2(a,b)       a##b
#define PASTE3(a,b,c)     a##b##c
#define __PASTE3(a,b,c)     PASTE3(a,b,c)
#define PASTE4(a,b,c,d)   a##b##c##d
#define PASTE6(a,b,c,d,e,f)     a##b##c##d##e##f
#define __PASTE6(a,b,c,d,e,f)     PASTE6(a,b,c,d,e,f)

#define GH_PORT(port,pin)       port
#define GH_PIN(port,pin)       pin
#define GPIO_PIN(gh)      __PASTE6(FIO,GH_PORT(gh),PIN_bit.P,GH_PORT(gh),_,GH_PIN(gh))
#define GPIO_DIR(gh)      __PASTE6(FIO,GH_PORT(gh),DIR_bit.P,GH_PORT(gh),_,GH_PIN(gh))

Upvotes: 1

J-Dizzle
J-Dizzle

Reputation: 5153

Bitfields: SGeorgiades put an answer above that is risky business*

if you're making use of C Bitfields to define your pins, be very darn careful with it.

  • bitfield 'type' is implementation-dependent (needs quote; int/uint)
  • fields aren't contiguous bitwise by default and get promoted to impl-dependent size
  • bitfields that span across the int-bitsize boundary will not be contiguous in bitfield-mapping! (needs quote)

for GCC/C11 the following bitfield-syntax is what I would use (if I were to use it)

struct gpio_out {
    int b0        : 1;
    int led_ctrl  : 1;
    ....
    int b30       : 1;
    int b31       : 1;
} __attribute__((packed));

Notes

  • by merging the concept of pin location into the struct, you can't share it with other registers in gpio (i.e. maybe DIR, IN, INT_EN, IFG, etc).

  • I will put up x86 disassembly examples of the points made here tmrw. they are very dependent on implementation and compiler configuration!

*correct me if I'm mistaken.

Upvotes: 0

SGeorgiades
SGeorgiades

Reputation: 1821

Assuming a typo in the "Alternative" example (in the last line, where you specify "LED_CTRL", which is not defined... I assume you meant "LED_CTRL_PIN_NUM"), then I believe these two examples will produce identical machine code. So, between these two choices... it's just a matter of style preference. (the ## in the second example is just an embedded comment.)

Personally, I would use a different approach entirely. I would use the built-in bit-field construct to modify a bit-wise field, and that way the compiler can decide what is the most efficient way to do this. Example follows:

struct {
    unsigned int :4, LED_CTRL:1, :27;
} GPIO_OUT;
...
GPIO_OUT.LED_CTRL = 1;

Note that the ":4" and ":27" refer to the other bits in the register... you would likely want to map the entire I/O register, not just one bit of it.

Upvotes: 0

Utkan Gezer
Utkan Gezer

Reputation: 3069

With the first method, the compiler will interpret the last line as following:

gpio_set(((1u << 5)));

And with the second method, the compiler will interpret the last line as following:

gpio_set((1u << ((5)));

You may fail to count without me saying it, but there is a parenthesis issue there. Either you have mistyped the #define PIN(x) (1u << x##_PIN_NUM) as #define PIN(x) (1u << (x##_PIN_NUM), or the one that you call looks weird won't even work.

In case you have simply just mistyped it, then both of them get simplified into the same thing:

gpio_set(1u << 5);

In the end, it is just a matter of style.

Note: If you are asking for opinions, you shouldn't.

Edit #1:

Okay, with the second approach, assuming that there are some other special pins, you can do things like this:

#define NAMEFOR0_PIN_NUM    (0)
#define NAMEFOR1_PIN_NUM    (1)
#define NAMEFOR2_PIN_NUM    (2)
#define NAMEFOR3_PIN_NUM    (3)
#define NAMEFOR4_PIN_NUM    (4)
#define LED_CTRL_PIN_NUM    (5)
#define NAMEFOR6_PIN_NUM    (6)
#define NAMEFOR7_PIN_NUM    (7)
#define NAMEFOR8_PIN_NUM    (8)
// and so on...

#define PIN(x) (1u << (x##_PIN_NUM))
...
// and then gpio_set(PIN(nameyouwant)) whichever pin you want
// for example

gpio_set(PIN(LED_CTRL));
// or
gpio_set(PIN(NAMEFOR3));

However, I personally wouldn't do it with token concatenation; I would rather directly define like:

#define LED_CTRL 5
...
#define PIN(x) (1u << x)
...
gpio_set(PIN(LED_CTRL));

But then again, if you take this literally, defining LED_CTRL as 5 would imply LED_CTRL is 5, although actually rather it's pin number is 5. So it would make more sense to use it that way, the alternate way you've shown. Both more natural and makes literal sense, so, yeah...

Edit #2:

Of course, you could do something for the traditional approach as well, but then you'd have to write more lines, more defines:

#define BIT0 (1u << 0)
#define BIT1 (1u << 1)
...
#define BIT5 (1u << 5)
// and so on...

#define PIN_NAMEFOR0 (BIT0)
#define PIN_NAMEFOR1 (BIT1)
...
#define PIN_LED_CTRL (BIT5)
// and so on...

#define PIN(x) (1u << (x##_PIN_NUM))
...
// and then gpio_set(PIN_nameyouwant) whichever pin you want
// for example

gpio_set(PIN_LED_CTRL);
// or
gpio_set(PIN_NAMEFOR3);

It still makes just as much syntactic sense, but requires more lines to get written...

Upvotes: 1

Related Questions