对于嵌入式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'方法。它看起来太可爱了。但我能说的唯一理由是:
但这在我看来就像在抱怨;这些都不是真正的反对意见 使用'alt'方法。
问题
为什么有人会使用'alt'方法呢?这可能是桌面驱动程序中的寄存器使用方式吗?
任何常见的嵌入式MCU库,替代目标或编程语言都使用这种“alt”方法吗?
谢谢!
*在一天结束时;我可能会坚持'它看起来很奇怪':)。
答案 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_CTRL
是5
,尽管实际上它的引脚数是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位域定义您的引脚,请非常小心。
对于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。它们非常依赖于实现和编译器配置!