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 可以比较方便的实现此类操作,例如对于如下寄存器定义 20230612172245.png 该寄存器有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);
}

与传统的位运算的实现相比,该方法实现简单,不过有几个地方需要特别注意:

  1. 需要确认编译器是否支持;
  2. 如上段代码中,typedef union 的前后需要使用 #pragma 包含,表示使用 1 字节对齐;使用 #pragma pack(1) #pragma pack() 和 #pragma pack(push, 1) #pragma pack(pop) 的效果基本相同,更推荐后者;
  3. 需要注意所在处理器平台的字节序,即大小端,在上述示例中,使用 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

@mengencn
加入
更多来自 mengencn