嵌入式 C:寄存器位操作宏
更新时间:2026-09-21。嵌入式/裸机视角。寄存器映射见 寄存器 MMIO 读写。
一句话回答
配置寄存器基本就四种操作:用 |= 置 1、用 &= ~ 清 0、用 ^= 翻转、用 & 读取某一位;把它们封装成 SET_BIT/CLR_BIT/TOGGLE_BIT/GET_BIT 宏,操作指定位而不影响同一寄存器里的其他位。
一、四个基本宏
c
#include <stdint.h>
#define BIT(n) (1UL << (n))
#define SET_BIT(reg, m) ((reg) |= (m))
#define CLR_BIT(reg, m) ((reg) &= ~(m))
#define TOGGLE_BIT(reg, m) ((reg) ^= (m))
#define GET_BIT(reg, m) (((reg) & (m)) ? 1U : 0U)用法:
c
SET_BIT(GPIOA->DIR, BIT(3)); // 把 bit3 配为输出
CLR_BIT(GPIOA->DIR, BIT(3)); // 清 bit3,不影响别的位
TOGGLE_BIT(GPIOA->OUT, BIT(3)); // 翻转 bit3(翻转 LED)
if (GET_BIT(UART->STAT, BIT(5))) { /* bit5 为 1 */ }二、为什么清位要 &= ~
reg |= BIT(n):目标位变 1,0|1=1、1|1=1,其他位保持;reg &= ~BIT(n):先把该位对应的掩码取反(那一位是 0、其余是 1),与上后只把目标位清 0,其他位不动;- 若直接
reg &= BIT(n)会把除该位外所有位都清零,这是经典错误。
三、一次配置多个位 / 多位字段
c
SET_BIT(REG, BIT(2) | BIT(1)); // 同时置 bit1、bit2
REG = (REG & ~(0x3u << 4)) | (0x2u << 4); // 清 bit[5:4] 再写入 0x2常见坑
- 清位漏写
~:reg &= BIT(n)误清所有其他位。 - 移位溢出:对 16 位寄存器用
1 << 15(int 为 16 位时是 UB),应写1UL << n。 - 读改写不是原子的:
|=、&=实际是「读—改—写」三步,期间若被中断打断且 ISR 也改同一寄存器,会丢更新;共享寄存器位需关中断或用硬件位带/置位寄存器(见 临界区与关中断)。 - 位数和掩码混淆:宏里传的是掩码
BIT(n),别直接传数字位序。 - 对保留位/只读位写:可能产生未定义效果,看手册确认。
总结
寄存器操作靠 |= 置位、&= ~ 清位、^= 翻转、& 读位,封装成四个宏并传 BIT(n) 掩码即可只改目标位;务必记住清位用取反、移位用 1UL,而读改写在中断共享下不原子,需要临界区保护。