嵌入式开发的引脚掩模约定

时间:2014-02-23 04:23:54

标签: c embedded

对于嵌入式C应用程序,我一直使用以下约定来定义GPIO引脚掩码:

'传统'示例:具有32位GPIO端口的32位CPU

断言GPIO输出寄存器中的bit5以打开LED。

#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);

在最近的多开发人员嵌入式项目中,一位开发人员选择了以下语法

'替代示例:具有32位GPIO端口的32位CPU

#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));

没有明确解释为什么选择这个。与所有轻微记录的代码一样,它似乎很神秘,以便“明确”保证其实现。当然,开发人员知道我没有做过的事情。在这种情况下,这个人是来自CPU驱动程序世界的智能cookie。

异议

我根本不喜欢'alt'方法。它看起来太可爱了。但我能说的唯一理由是:

  • 'LED_CTRL'不是编译时常量
    • 另外,你不能在IDE中下载/检查它
  • 'LED_CTRL_PIN_NUM'与uri-naming-schema完全相反
    • e.g。首选PIN_NUM_abc
  • 没有其他人这样做*
  • 看起来很奇怪

但这在我看来就像在抱怨;这些都不是真正的反对意见 使用'alt'方法。

问题

为什么有人会使用'alt'方法呢?这可能是桌面驱动程序中的寄存器使用方式吗?

任何常见的嵌入式MCU库,替代目标或编程语言都使用这种“alt”方法吗?

谢谢!

*在一天结束时;我可能会坚持'它看起来很奇怪':)。

4 个答案:

答案 0 :(得分:1)

两种方法几乎相同,只是风格不同,但问题是一样的。

通常,引脚不是由位数定义的,您还需要知道端口。

因此,在更改端口时,您不仅需要修改定义,还需要修改代码。

#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);

我更喜欢只定义一次引脚,并且所有不同的依赖项都是由宏或函数构建的。

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

然后我使用一些宏来定义获取端口方向,pushPull和其他寄存器 这些宏几乎不依赖于平台和工具链。

/**
 * 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))

答案 1 :(得分:0)

使用第一种方法,编译器将解释最后一行如下:

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

使用第二种方法,编译器将解释最后一行如下:

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

如果没有我这样说,你可能无法计算,但那里有一个括号问题。您错误输入#define PIN(x) (1u << x##_PIN_NUM)#define PIN(x) (1u << (x##_PIN_NUM),或者您调用的看起来很奇怪甚至无效。

如果您只是错误输入了它,那么它们都会被简化为同样的东西:

gpio_set(1u << 5);

最后,这只是风格问题。

注意:如果您要求提出意见,则不应该。

编辑#1:

好的,使用第二种方法,假设还有一些其他特殊引脚,你可以这样做:

#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));

但是,我个人不会用标记连接来做;我宁愿直接定义如下:

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

但话又说回来,如果按字面意思理解,将LED_CTRL定义为5意味着LED_CTRL5,尽管实际上它的引脚数是5。所以它以这种方式使用它会更有意义,你所显示的 alternate 方式。两者都更自然,并且具有字面意义,所以,是的......是的。

编辑#2:

当然,你也可以为传统的方法做点什么,但是你必须写更多的行,更多define s:

#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);

它仍然具有同样多的语法意义,但需要更多的行才能写出......

答案 2 :(得分:0)

假设“替代”示例中有拼写错误(在最后一行中,您指定了“LED_CTRL”,未定义...我假设您的意思是“LED_CTRL_PIN_NUM”),那么我相信这两个例子会产生相同的机器代码。所以,在这两个选择之间......这只是风格偏好的问题。 (第二个例子中的##只是一个嵌入式注释。)

就个人而言,我会完全采用不同的方法。我会使用内置的bit-field构造来修改逐位字段,这样编译器就可以决定执行此操作的最有效方法。示例如下:

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

请注意,“:4”和“:27”指的是寄存器中的其他位...您可能希望映射整个I / O寄存器,而不仅仅是它的一位。

答案 3 :(得分:0)

Bitfields:SGeorgiades将答案放在了高风险的业务上*

如果您正在使用C位域定义您的引脚,请非常小心。

  • bitfield'type'是依赖于实现的(需要引用; int / uint)
  • 默认情况下,字段不是按位连续的,而是提升为impl-dependent size
  • 跨越int-bitsize边界的
  • 位域在位域映射中不是连续的! (需要报价)

对于GCC / C11,我将使用以下位域语法(如果我使用它)

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

备注

  • 通过将引脚位置的概念合并到结构中,您无法与其他人共享它 在gpio中注册(即可能是DIR,IN,INT_EN,IFG等)。

  • 我将提出x86反汇编的例子,这里提出的点是tmrw。它们非常依赖于实现和编译器配置!

如果我弄错了,请纠正我。