union struct bit field在嵌入式编程中的使用
在嵌入式编程中,由于贴近硬件,需要经常对寄存器进行操作,涉及到比较多的位运算,例如
/// set val bit 3 to 1b
val = val | (1<<3)
/// clear val bit 4 to 0b
val = val & (~(1<<4))
如果涉及到多个 bit field 的清除,赋值操作,其实现更加复杂且容易出错,例如
// set bit 7:5 to 101b
val = val & (~(0x07<<5));
val = val | (0x05<<5);
// set bit 2:1 to 01b
val = val & (~(0x03<<1));
val = val | (0x01<<1);
有没有更加简单易行的方式实现这类运算呢?
在学习代码过程中,发现使用 union 搭配 struct 可以比较方便的实现此类操作,例如对于如下寄存器定义
该寄存器有3个 bit field,可以使用如下方式来定义
#pragma pack(push, 1)
// CMOS Register A define
typedef union {
UINT8 REG_A;
struct {
UINT8 RateSel : 4;
UINT8 Divider : 3;
UINT8 UpdInProg : 1;
}Bits;
} RTC_REG_A;
#pragma pack(pop)
如果需要对 bit 3:0 赋值,可以直接使用赋值运算符实现,而不会影响到其他 bit field,示例如下
void DemoFunc()
{
RTC_REG_A RtcRegA;
RtcRegA.REG_A = CmosRead8(0x0A);
RtcRegA.Bits.RateSel = 0x06;
CmosWrite8(0x0A, RtcRegA.REG_A);
}
与传统的位运算的实现相比,该方法实现简单,不过有几个地方需要特别注意:
- 需要确认编译器是否支持;
- 如上段代码中,typedef union 的前后需要使用 #pragma 包含,表示使用 1 字节对齐;使用 #pragma pack(1) #pragma pack() 和 #pragma pack(push, 1) #pragma pack(pop) 的效果基本相同,更推荐后者;
- 需要注意所在处理器平台的字节序,即大小端,在上述示例中,使用 x86 平台,即 Little-Endian,所以 bit 3:0 放在 struct 中的首位定义,对于大端平台,需要将高位 bit7 放在 struct 中的首位进行定义;
参考链接:
https://stackoverflow.com/questions/3497345/is-there-a-way-to-access-individual-bits-with-a-union
https://blog.csdn.net/weixin_64184244/article/details/127108876
https://www.codenong.com/cs105773667/
http://www.bioscentral.com/misc/cmosmap.htm