总体目标

在嵌入式开发中,寄存器是 32/16/8 位宽的整数,我们经常需要:

  1. 只清零某几个连续位(其他位不变)
  2. 对这几个位写入新值(其他位不变)
  3. 对单个位取反(其他位不变)

这三个操作是 位操作(bit manipulation) 的核心技巧,广泛用于驱动开发、硬件配置等。


把变量的某几个连续位清零

场景假设

  • 变量 a 是 8 位(为便于演示)

  • 把每 2 位 分成一组:

    bit7 bit6 | bit5 bit4 | bit3 bit2 | bit1 bit0
       组3      |    组2    |   组1    |   组0
    
  • 目标:清零第1组(bit3、bit2)


核心公式

a &= ~(mask);
  • mask要清零的那些位为 1,其他位为 0 的掩码
  • ~mask 就是 要清零的位为 0,其他位为 1
  • a 按位与 → 要清零的位变成 0,其他位保留原值

构造 mask:(3 << 2*组号)

符号 含义
3 二进制 11b,表示组内 2 位全为 1
2 每组位数 = 2
组号 从 0 开始计数
示例:清零第1组(组号=1)
mask = 3 << (2 * 1);   // 3 << 2

计算过程:

3   = 00000011 b
<<2 = 00001100 b   → bit3、bit2 为 1

所以:

~(3 << 2*1) = ~00001100 b = 11110011 b

完整操作

a &= ~(3 << 2*1);
举例验证
a = 10011111 b   // 原始值
a           = 1001 1111
~mask       = 1111 0011
& 运算后 a  = 1001 0011

bit3、bit2 被清零,其他位不变!


通用公式总结

// 清零第 n 组(每组 k 位)
a &= ~(( (1<<k) - 1 ) << (k * n));

等价写法(更清晰):

#define CLR_BITS(a, n, k)   ((a) &= ~(((1<<(k))-1) << ((k)*(n))))
  • n:组号(从 0 开始)
  • k:每组位数
  • (1<<k)-1:生成 k 个 1(如 k=2 → 3)

其他例子

目标 代码
清零第2组(bit5、bit4) a &= ~(3 << 2*2);
清零第0组(bit1、bit0) a &= ~(3 << 0);
每组4位,清零第1组(bit7~bit4) a &= ~((15) << 4*1);

对变量的某几位进行赋值

前提

已经 清零了目标位,现在可以安全写入新值。

方法:按位或 |= + 移位

a |= (value << (k * n));
  • value:要写入的值(不超过 k 位)
  • k * n:目标组的起始位偏移

示例:设置第2组为 01b

// 假设 a 已经清零了 bit5、bit4
a |= (1 << (2*2));   // 1 << 4

计算:

1 << 4 = 00010000 b

所以 bit4 置 1,bit5 仍为 0 → 写入 01b


完整前后对比

a = 1000 0011 b
// 清零第2组
a &= ~(3 << 4);        // a = 1000 0011 → 1000 0011 (bit5,4=00)
// 写入 01
a |= (1 << 4);         // a = 1001 0011

成功!只改了 bit5、bit4


通用赋值宏

#define SET_BITS(a, n, k, val) \
    do { \
        (a) &= ~(((1<<(k))-1) << ((k)*(n))); \
        (a) |= (((val) & ((1<<(k))-1)) << ((k)*(n))); \
    } while(0)

用法:

SET_BITS(a, 2, 2, 1);  // 第2组写 01b
SET_BITS(a, 1, 2, 3);  // 第1组写 11b

对变量的某位取反

目标

某一位 翻转(0→1,1→0),其他位不变

方法:异或 ^= 1<<pos

a ^= (1 << pos);

原理:异或是 相同为0,相异为1

原位 1<<pos 结果
0 1 1
1 1 0

示例:翻转 bit6

a = 1001 0011 b
a ^= (1 << 6);     // 1<<6 = 01000000 b
a        = 1001 0011
mask     = 0100 0000
^ 结果   = 1101 0011

bit6 从 0 → 1,其他位不变


通用取反宏

#define FLIP_BIT(a, pos)  ((a) ^= (1 << (pos)))

总结表格

操作 代码模板 说明
清零连续 k 位(第 n 组) a &= ~(((1<<k)-1) << (k*n)); 构造掩码后取反
设置连续 k 位为 val a |= (val << (k*n)); 需先清零
取反第 pos 位 a ^= (1 << pos); 异或翻转

实际应用场景

// 配置 UART 波特率寄存器
UART_BAUD = (UART_BAUD & ~0xFF) | 0x68;  // 设置低8位
// 使能 GPIO 输出
GPIO_CRH &= ~(0xF << 4);   // 清零 pin10 模式位
GPIO_CRH |=  (0x3 << 4);   // 设置为输出
// 翻转 LED
LED_PIN ^= (1 << 5);

推荐练习

  1. 写一个函数:void set_bits(uint32_t *reg, int start, int len, uint32_t val)
  2. 实现 toggle_bit(uint8_t *reg, int pos)
  3. 用宏封装上述操作
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐