
嵌入式开发天天和寄存器打交道而寄存器操作的本质就是位运算。这篇整理7种最常用的位操作每种都有寄存器实战代码看完就能用。一、为什么嵌入式离不开位运算/* STM32让PA5输出高电平 */GPIOA-BSRR(15);/* 读PA0输入 */uint8_tval(GPIOA-IDR0)1;/* 设置波特率分频 */USART1-BRR625;寄存器是32位的每一位控制一个功能。你要操作某个bit就必须用位运算。二、7种基本位操作操作1置位把某位设为1/* 把第n位置1 */reg|(1n);/* 示例把GPIOA的第5位置1PA5输出高 */GPIOA-ODR|(15);/* 同时置多个位 */reg|(13)|(15);/* 第3和第5位都置1 */操作2清零把某位设为0/* 把第n位清零 */reg~(1n);/* 示例PA5输出低 */GPIOA-ODR~(15);/* 同时清多个位 */reg~((13)|(15));为什么用~(1n)先1n得到只有第n位为1的值取反后第n位为0其他为1运算后只清除第n位。操作3取反0变11变0/* 把第n位取反 */reg^(1n);/* 示例LED翻转 */GPIOA-ODR^(15);/* PA5状态翻转 */XOR特性x ^ 1 ~x取反x ^ 0 x不变。操作4判断某位是否为1/* 判断第n位是否为1 */if(reg(1n)){/* 第n位是1 */}/* 示例判断PA0是否为低电平按键按下 */if((GPIOA-IDR(10))0){/* PA00按键按下 */}操作5读取连续多位/* 读取第n到第m位nm的值 */uint32_tval(regm)((1(n-m1))-1);/* 示例读ADC数据的低12位 */uint16_tadcADC1-DR0xFFF;/* 0xFFF (112)-1 */示例读寄存器的bit[7:4]4位uint8_tval(reg4)0x0F;/* 右移4位再mask低4位 */操作6写入连续多位/* 先清除再写入 */#defineBIT_MASK(start,width)(~(((1width)-1)start))/* 清除bit[start:startwidth-1] */regBIT_MASK(start,width);/* 写入新值 */reg|(new_valuestart);/* BIT_MASK(2, 4) 展开推导过程 * 1 4 0b00010000 0x10 * 0x10 - 1 0b00001111 0x0F 得到4个1 * 0x0F 2 0b00111100 0x3C 移到bit2-5的位置 * ~0x3C 0b11000011 0xC3 取反bit2-5为0其余为1 * 所以 reg 0xC3 会清除bit2-5其他位不变 *//* 示例设置GPIO模式PA5在CRL寄存器的bit20-23 */GPIOA-CRL~(0x0F20);/* 清除PA5的配置位注意是PA5不是PC5因为是GPIOA */GPIOA-CRL|(0x0320);/* 写入推挽输出50MHz */操作7快速计算对齐值/* 向上对齐到4的倍数 */#defineALIGN4(x)(((x)3)~3)/* 原理加3是为了向上取整13316 ~3是清除低2位实现4字节对齐 * ~3 0b...11111100 ~3就是把低2位置0相当于向下取整到4的倍数 * 13316, 16 ~3 16已经是4的倍数 * 14317, 17 ~3 16 *//* 示例 */intsizeALIGN4(13);/* 16 */intsizeALIGN4(20);/* 20 */应用DMA传输要求数据对齐协议帧长度对齐。三、常用位运算宏封装/* 置位 */#defineSET_BIT(reg,bit)((reg)|(1U(bit)))/* 清零 */#defineCLR_BIT(reg,bit)((reg)~(1U(bit)))/* 读取 */#defineGET_BIT(reg,bit)(((reg)(bit))1U)/* 翻转 */#defineTOGGLE_BIT(reg,bit)((reg)^(1U(bit)))/* 读取多位 */#defineGET_BITS(reg,start,width)(((reg)(start))((1U(width))-1))四、实战STM32寄存器操作4.1 配置GPIO为输出/* 配置PC13为推挽输出50MHz *//* CRH寄存器bit[23:20]控制Pin13 */GPIOC-CRH~(0x0F20);/* 清零 */GPIOC-CRH|(0x0320);/* 0011推挽输出50MHz */4.2 点亮LED/* PC13输出低电平点亮LED */GPIOC-ODR~(113);/* 清零第13位 输出低 */4.3 读取按键/* 读取PA0状态 */uint8_tkey(GPIOA-IDR0)1;/* 0按下1松开 */五、位域Bit-field5.1 定义structflags{uint8_tenable:1;/* 1 bit */uint8_tmode:3;/* 3 bits */uint8_tpriority:2;/* 2 bits */uint8_treserved:2;/* 2 bits凑满1字节 */};/* sizeof 1字节 */5.2 优缺点优点缺点省内存跨平台行为不一致代码可读性好不能取地址编译器处理位操作位序依赖实现位序位域中各字段的排列顺序有的编译器从低位排有的从高位排不同编译器可能不同建议位域在跨平台通信中不推荐用字节序和对齐不确定寄存器操作还是用位运算宏更安全。六、常见踩坑坑1131的符号问题intx(131);/* 1是int左移31位 → 符号位变1负数 *//* 正确用无符号 */uint32_tx(1U31);/* 加U无符号 0x80000000 */规则位运算永远加U后缀1U避免符号位问题。坑2位运算优先级低于比较if(reg0x010)/* 实际是 reg (0x01 0) reg 0 0 *//* 永远为真 *//* 正确加括号 */if((reg0x01)0)/* 判断bit0是否为0 */规则位运算和比较混用时位运算必须加括号。七、总结操作代码置位reg | (1 n)清零reg ~(1 n)取反reg ^ (1 n)判断if (reg (1 n))读多位(reg start) mask写多位先清再写|对齐(x 3) ~3一句话总结嵌入式位运算的核心就是置位用或、清零用与非、取反用异或、判断用与记住这四个口诀90%的寄存器操作都能搞定。C语言基础系列到这篇完结。下一篇开始新的系列——嵌入式核心概念。下一篇预告《中断详解从原理到NVIC优先级嵌套》如果这个系列对你有帮助点赞 收藏 关注这是我持续更新的动力有问题欢迎评论区交流我会逐条回复。作者嵌入式阿蔡