寄存器位操作函数

在嵌入式C语言底层寄存器控制中,位操作是核心基础,主要分为基于掩码(Mask-based)和基于位序(Bit-index-based)两种方式,以下是标准化的函数定义与实现示例。

核心位操作函数

基于掩码(Mask-based),此类函数直接使用掩码值(如 0x01、0x04)操作寄存器,适用于已知具体掩码的场景

IS_MASK_SET
作用:判断某个位是否被设置(1)
返回值:掩码对应的位是 1,返回1,否则返回 0

IS_MASK_CLEAR
作用:判断某个位是否被清零(0)
返回值:掩码对应的位是 0,返回1,否则返回 0

SET_MASK
作用:将掩码指定的位设置为 1(置位)
返回值:无

CLEAR_MASK
作用:将掩码指定的位设置为 0(清零)
返回值:无

TOGGLE_MASK
作用:翻转掩码指定的位(0 变 1,1 变 0)
返回值:无

实现参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdint.h> 

// 判断某个位是否被设置(1)
static inline uint8_t IS_MASK_SET(uint32_t reg, uint32_t mask)
{
return (reg & mask) ? 1 : 0;
}

// 设置掩码指定的位为1
static inline void SET_MASK(uint32_t *reg, uint32_t mask)
{
*reg |= mask;
}

// 清除掩码指定的位(置为0)
static inline void CLEAR_MASK(uint32_t *reg, uint32_t mask)
{
*reg &= ~mask;
}

// 翻转掩码指定的位
static inline void TOGGLE_MASK(uint32_t *reg, uint32_t mask)
{
*reg ^= mask;
}

扩展位操作函数

此类函数通过位序号(如 0 表示最低位,31 表示最高位) 操作寄存器,更直观,无需手动计算掩码。
SET_BIT
作用:将指定位序的位设置为 1
返回值:无

CLEAR_BIT
作用:将指定位序的位设置为 0
返回值:无

GET_BIT
作用:获取指定位序的位值
返回值:返回该位的原始值(0 或 1)

CHECK_BIT
作用:检查指定位序的位是否被设置
返回值:1 - 该位为 1,0 - 该位为 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 设置指定位序的位为1
static inline void SET_BIT(uint32_t *reg, uint8_t n)
{
*reg |= (1U << n); // 使用1U避免符号位扩展,适配嵌入式
}

// 清除指定位序的位(置为0)
static inline void CLEAR_BIT(uint32_t *reg, uint8_t n)
{
*reg &= ~(1U << n);
}

// 获取指定位序的位值
static inline uint8_t GET_BIT(uint32_t reg, uint8_t n)
{
return (reg >> n) & 1U;
}

// 检查指定位序的位是否被设置
static inline uint8_t CHECK_BIT(uint32_t reg, uint8_t n)
{
return GET_BIT(reg, n); // 与GET_BIT逻辑一致,仅语义更清晰
}

现在大多数开发都会基于厂家提供的固件库开发,很少自行实现位操作函数了。但如果从事单片机开发,位操作与运算是必须要懂的。这里还顺带提一下一个关键字。经常会在底层代码看到,主要用于在定义指向寄存器的指针变量时。

易失性关键字 (volatile)
因为寄存器是硬件,它的值可能会在你不知情的情况下被硬件自己改变,这个关键字作用就是告诉编译器这个值会改变,每次用的时候都需要从内存中读取最新的值。
示例代码:

1
2
3
4
uint32_t *status_reg = (uint32_t *)(base_addr + 0x10);
while ((*status_reg & 0x01) == 0) {
// 死循环
}

加上关键字之后,硬件状态改变就会跳出循环

1
2
3
4
5
volatile uint32_t *status_reg = (volatile uint32_t *)(base_addr + 0x10);
// 每次会读取一次寄存器的值
while ((*status_reg & 0x01) == 0) {
// 空循环
}

这个关键字还用在中断函数标志位和线程锁标志位上。后续应该还会总结下单片机一些基础知识。