当前位置: 首页 > news >正文

嵌入式C语言位操作的几种常见用法

作为一名老单片机工程师,我承认,当年刚入行的时候,最怕的就是看那些密密麻麻的寄存器定义,以及那些让人眼花缭乱的位操作。

尤其是遇到那种“明明改了寄存器,硬件就是不听话”的情况,简直想把示波器砸了!那时心里默默吐槽:这谁设计的寄存器,就不能给个明确的开关按钮吗,非要让我扭来扭去?

其实,每个单片机工程师都经历过这段“痛苦”的旅程。在第一家公司,我特别佩服那个把NXP单片机寄存器玩得溜溜转的大佬,同时又对那些藏在代码深处的位操作充满恐惧。毕竟,一个不小心,就可能让你的程序跑飞,硬件罢工。

如果你也正为位操作而苦恼,那么恭喜你,找到了组织!这篇文章不会教你背诵晦涩的位操作定义,而是会用最通俗易懂的语言,带你掌握嵌入式C语言中位操作的几种常见用法。

学完之后,你不仅能轻松应对各种硬件控制任务,还能在代码优化方面更上一层楼。告别抓耳挠腮,让你也能在位操作的世界里“横着走”!

记得有一次做消费类产品,我负责一个资源非常紧张的51单片机项目,Flash和RAM都快要爆炸了。当时,我想尽各种办法优化代码,最后发现,使用位操作可以极大地压缩数据存储空间,提高程序的运行效率。

通过巧妙地运用位操作,我成功盘下了这个项目,节省了硬件成本,还赢得了老板欢心,后面我离职了几年,又以技术入股的方式把我请回去,从此踏入更大的坑,算了,血泪史,不说也罢。。。从那以后,我就也深刻体会到,掌握位操作,真的是单片机工程师的必备技能。

1.位操作,其实没那么可怕!

1.1 位操作的基石:二进制世界

在深入位操作之前,我们需要先回到二进制的世界。

单片机本质上就是处理二进制数据的机器,一切指令、数据,最终都会转化为0和1。所以,理解二进制是掌握位操作的基础。

举个例子,我们常说的“8位单片机”,指的是它的数据总线宽度是8位,也就是一次可以处理8个二进制位的数据。比如,0xAA(十六进制)在二进制中表示为10101010。而位操作,就是对这些二进制位进行各种各样的操作。

1.2 位与(&):提取信息的过滤器

位与操作符(&)的作用是,将两个操作数的对应位进行“与”运算。只有当两个位都为1时,结果才为1,否则为0。

uint8_t a = 0b10101010; 
uint8_t b = 0b00001111; 
uint8_t result = a & b; // result 的值为 0b00001010

位与操作最常见的应用场景是清除特定位和提取特定位。

  • 清除特定位: 假设我们需要清除一个寄存器reg的第3位(从0开始计数),我们可以使用以下代码:

reg = reg & (~(1 << 3)); // 将第3位清零

这里,(1 << 3) 会生成一个掩码0b00001000,然后取反得到0b11110111。再与reg进行位与操作,就可以将第3位清零,而其他位保持不变。

  • 提取特定位: 假设我们需要提取reg的第4位到第7位,可以使用以下代码:

    uint8_t extracted = (reg >> 4) & 0x0F; // 提取第4位到第7位

这里,(reg >> 4) 会将reg右移4位,使得第4位到第7位移动到最低位。然后与0x0F(0b00001111)进行位与操作,就可以提取出第4位到第7位的值。

1.3 位或(|):设置信息的开关

位或操作符(|)的作用是,将两个操作数的对应位进行“或”运算。只要两个位中有一个为1,结果就为1,否则为0。

uint8_t a = 0b10101010; 
uint8_t b = 0b00001111; 
uint8_t result = a | b; // result 的值为 0b10101111

位或操作最常见的应用场景是设置特定位。

  • 设置特定位: 假设我们需要设置reg的第2位为1,可以使用以下代码:

reg = reg | (1 << 2); // 将第2位设置为1

这里,(1 << 2) 会生成一个掩码0b00000100。然后与reg进行位或操作,就可以将第2位设置为1,而其他位保持不变。

1.4 位异或(^):翻转信息的魔法棒

位异或操作符(^)的作用是,将两个操作数的对应位进行“异或”运算。当两个位不同时,结果为1,相同时为0。

uint8_t a = 0b10101010; 
uint8_t b = 0b00001111; 
uint8_t result = a ^ b; // result 的值为 0b10100101

位异或操作最常见的应用场景是翻转特定位和简单加密。

  • 翻转特定位: 假设我们需要翻转reg的第5位,可以使用以下代码:

  • reg = reg ^ (1 << 5); // 翻转第5位

这里,(1 << 5) 会生成一个掩码0b00100000。然后与reg进行位异或操作,就可以将第5位翻转(0变1,1变0)。

  • 简单加密: 位异或操作可以用于简单的加密和解密。同一个数据与同一个密钥进行两次位异或操作,就可以恢复原始数据。

uint8_t data = 0x5A; 
uint8_t key = 0x3C; 
uint8_t encrypted = data ^ key; // 加密 
uint8_t decrypted = encrypted ^ key; // 解密,恢复为 data

1.5 位取反(~):反转世界的钥匙

位取反操作符(~)的作用是,将操作数的每一位取反。

uint8_t a = 0b10101010; 
uint8_t result = ~a; // result 的值为 0b01010101

位取反操作通常用于生成掩码,配合其他位操作实现更复杂的功能。比如,前面清除特定位的例子中,我们就用到了位取反。

1.6 左移(<<)和右移(>>):移形换影的魔术

左移操作符(<<)的作用是,将操作数的每一位向左移动指定的位数,右边补0。

右移操作符(>>)的作用是,将操作数的每一位向右移动指定的位数,左边补0(无符号数)或补符号位(有符号数)。

uint8_t a = 0b00000011; 
uint8_t result_left = a << 2; // result_left 的值为 0b00001100 
uint8_t result_right = a >> 1; // result_right 的值为 0b00000001

左移和右移操作最常见的应用场景是**乘以或除以2的幂**、**提取特定位**和**组合数据**。

  • 乘以或除以2的幂: 左移n位相当于乘以2的n次方,右移n位相当于除以2的n次方。这比直接使用乘除法运算更快。

  • 提取特定位: 就像前面提取reg的第4位到第7位的例子。

  • 组合数据: 假设我们有两个8位数据,需要将它们组合成一个16位数据:

uint8_t high = 0x12; 
uint8_t low = 0x34; 
uint16_t combined = (high << 8) | low; // combined 的值为 0x1234

2. 实战演练:GPIO控制

说了这么多,我们来一个实战演练:使用位操作控制GPIO。

假设我们需要控制一个LED的亮灭,LED连接到GPIO的第5个引脚。

#define LED_PIN (1 << 5) // 定义LED引脚对应的掩码// 点亮LED
void led_on() {GPIO_PORT |= LED_PIN; // 设置GPIO引脚为高电平
}// 熄灭LED
void led_off() {GPIO_PORT &= ~LED_PIN; // 设置GPIO引脚为低电平
}// 翻转LED状态
void led_toggle() {GPIO_PORT ^= LED_PIN; // 翻转GPIO引脚状态
}

这个例子清晰地展示了位操作在控制硬件方面的简洁和高效。

3. 注意事项:别踩这些坑!

  • 位宽问题: 确保操作的变量类型足够容纳所需的位数,避免数据溢出。

  • 符号扩展: 在对有符号数进行右移操作时,注意符号位的扩展。

  • 移位溢出: 移位位数不应超过变量的位宽,否则行为未定义。

  • 优先级: 位操作符的优先级比较低,需要注意加括号,避免运算顺序错误。

位操作是嵌入式C语言的精髓,也是单片机工程师的必备技能。掌握位操作,你就能更高效地控制硬件,更巧妙地优化代码,在单片机世界里施展你的魔法。

希望这篇文章能帮助你打开位操作的大门,让你在嵌入式开发的道路上越走越远!记住,位操作不仅是技术,更是一种思考方式,它能让你以更精巧、更高效的方式解决问题。干吧,骚年。


最近很多粉丝问我单片机怎么学,我根据自己从业十年经验,累积耗时一个月,精心整理一份「单

片机最佳学习路径+单片机入门到高级教程+工具包」全部无偿分享给铁粉!!!

除此以外,再含泪分享我压箱底的22个热门开源项目,包含源码+原理图+PCB+说明文档,让你迅速进阶成高手

教程资料包和详细的学习路径可以看我下面这篇文章的开头

《单片机入门到高级开挂学习路径(附教程+工具)》

《单片机入门到高级开挂学习路径(附教程+工具)》

《单片机入门到高级开挂学习路径(附教程+工具)》

相关文章:

  • 视频编解码种类/技术/区别/优缺点汇总
  • 多模态记忆融合:基于LSTM的连续场景生成——突破AI视频生成长度限制
  • 【Qt】初识Qt(二)
  • Oracle 11g通过dg4odbc配置dblink连接PostgreSQL
  • 2021-11-09 C++倍数11各位和为13
  • 25MathorCup选题浅析(睡醒扫一眼题目版)
  • C++程序设计基础实验:C++对C的扩展特性与应用
  • 免费将静态网站部署到服务器方法(仅支持HTML,CSS,JS)
  • 混合精度(Mixed Precision)在科学计算领域应用
  • HAL详解
  • 优化自旋锁的实现
  • npx 的作用以及延伸知识(.bin目录,npm run xx 执行)
  • 大语言模型减少幻觉的常见方案
  • 软考-信息系统项目管理师-2 信息技术发展
  • 360蜘蛛IP完整版,360搜索引擎蜘蛛IP列表.pdf
  • 吃透LangChain(五):多模态输入与自定义输出
  • ftok函数 ---- 生成一个唯一的 System V IPC 键值
  • IP检测工具“ipjiance”
  • ProfibusDP转ModbusRTU网关,流量计接入新方案!
  • CentOS系统中排查进程异常终止的日志
  • 伊朗外长访华将会见哪些人?讨论哪些议题?外交部回应
  • 王忠诚出任四川遂宁代市长,此前为成都市政府秘书长
  • 支持医企协同创新研究,上海已设立一系列产学研医融合项目
  • 2025年上海车展后天开幕,所有进境展品已完成通关手续
  • “HPV男女共防计划”北半马主题活动新闻发布会在京举办
  • 同济研究生开发AI二维码走红拿下大奖,新一代00开发者掀起AI创业潮