【C语言笔记】操作位的技巧

一、操作位的方法

操作位有两种方法,一种是位字段,另一种是使用按位运算符。位字段的方法可查看往期笔记:【C语言笔记】位域。本文介绍使用按位运算符操作位的方法。下表为几种位操作符及其含义:

二、不改变其他位的值的状况下,对某几个位进行设值。

嵌入式编程中,常常需要对一些寄存器进行配置,有的情况下需要改变一个字节中的某一位或者几位,但是又不想改变其它位原有的值,这时就可以使用按位运算符进行操作。下面进行举例说明,假如有一个8位的TEST寄存器:

当我们要设置第0位bit0的值为1时,可能会这样进行设置:

TEST = 0x01;

但是,这样设置是不够准确的,因为这时候已经同时操作到了高7位:bit1~bit7,如果这高7位没有用到的话,这么设置没有什么影响;但是,如果这7位正在被使用,结果就不是我们想要的了。

在这种情况下,我们就可以借用&|进行配置。

对于二进制位操作来说,不管该位原来的值是0还是1,它跟0进行&运算,得到的结果都是0,而跟1进行&运算,将保持原来的值不变;不管该位原来的值是0还是1,它跟1进行|运算,得到的结果都是1,而跟0进行|运算,将保持原来的值不变。

所以,此时可以设置为:

TEST = TEST | 0x01;

其意义为:TEST寄存器的高7位均不变,最低位变成1了。在实际编程中,常改写为:

TEST |= 0x01;

这种写法可以一定程度上简化代码,是 C 语言常用的一种编程风格。

同样的,要给TEST的低4位清0,高4位保持不变,可以进行如下配置:

TEST &= 0xF0;

这个场景嵌入式开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操作,然后用|操作符设值。比如我要改变GPIOA的状态,可以先对寄存器的值进行&清零操作:

GPIOA->CRL &= 0XFFFFFF0F; //将第4-7位清0

然后再与需要设置的值进行|或运算:

GPIOA->CRL |= 0X00000040; //设置相应位的值,不改变其他位的值

移位操作提高代码的可读性。

移位操作在单片机开发中也非常重要,下面让我们看看固件库的GPIO初始化的函数里面的一行代码:

GPIOx->BSRR = (((uint32_t)0x01) << pinpos);

这个操作就是将BSRR寄存器的第pinpos位设置为1,为什么要通过左移而不是直接设置一个固定的值呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第pinpos位设置为1。如果你写成:

GPIOx->BSRR = 0x0030;

这样的代码就不好看也不好重用了。 类似这样的代码很多:

GPIOA->ODR |= 1 << 5; //PA.5输出高,不改变其他位

这样我们一目了然,5告诉我们是第5位也就是第6个端口,1告诉我们是设置为1了。

三、~取反操作使用技巧

SR寄存器的每一位都代表一个状态,某个时刻我们希望去设置某一位的值为0,同时其他位都保留为1,简单的作法是直接给寄存器设置一个值:

TIMx->SR = 0xFFF7;

这样的作法设置第3位为0,但是这样的作法同样不好看,并且可读性很差。看看库函数代码中怎样使用的:

TIMx->SR = (uint16_t)~TIM_FLAG;

而TIM_FLAG 是通过宏定义定义的值:

#define TIM_FLAG_Update  ((uint16_t)0x0001)
#define TIM_FLAG_CC1     ((uint16_t)0x0002)
#define TIM_FLAG_CC2     ((uint16_t)0x0004)
#define TIM_FLAG_CC3     ((uint16_t)0x0008)
#define TIM_FLAG_CC4     ((int16_t)0x0010)
#define TIM_FLAG_COM     ((uint16_t)0x0020)
#define TIM_FLAG_Trigger ((uint16_t)0x0040)
#define TIM_FLAG_Break   ((uint16_t)0x0080)
#define TIM_FLAG_CC1OF   ((uint16_t)0x0200)
#define TIM_FLAG_CC2OF   ((uint16_t)0x0400)
#define TIM_FLAG_CC3OF   ((uint16_t)0x0800)
#define TIM_FLAG_CC4OF   ((uint16_t)0x1000)

即设置SR第3位为0时可设置为:

TIMx->SR = (uint16_t)~TIM_FLAG_CC3;

以上就是关于位操作在嵌入式编程中的一些技巧,如有错误,欢迎指出!

四、参考资料

《STM32F1开发指南-库函数版本_V3.1 》

《手把手教你学51单片机》


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菲宇

shell脚本监控php-fpm并自动重启服务

监控php-fpm并自动重启服务的shell脚本,脚本的主要功能:不断检查网站的状态,如果异常就重启php-fpm服务

38010
来自专栏逆向技术

64位内核开发第五讲,调试与反调试

debugport是在EPROCESS结构中的.调试时间会通过DebugPort端口将调试事件发送给ring3进行调试的.如果设置为0.则ring3就无法进行调...

17320
来自专栏贺利权

Apk 反编译前期了解

LZ-Says:学习之路,似乎枯燥乏味,唯有耐着性子,独自前行,当光明笼罩的那一刻,一切,也仿佛明亮了许多。

15530
来自专栏linux驱动个人学习

liteos 异常接管(十五)

异常接管是操作系统对在运行期间发生异常的情况进行处理的一系列动作,譬如打印异常发生时当前函数调用栈信息、 cpu现场信息、任务的堆栈情况等。

13820
来自专栏安富莱嵌入式技术分享

【STM32H7教程】第25章 STM32H7的TCM,SRAM等五块内存基础知识

完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980

18020
来自专栏ChaMd5安全团队

Defcon China 1.0 胸卡破解笔记

不会IOT, 不会逆向, 也没去听破解胸卡的 workshop, 本菜鸡在 @hook 师傅指引下完成了这次”破解“,在此记录一下,供大家看个热闹。

19930
来自专栏逆向技术

x64汇编第三讲,64位调用约定与函数传参.

1.传参方式 首先说明一下,在X64下,是寄存器传参. 前4个参数分别是 rcx rdx r8 r9进行传参.多余的通过栈传参.从右向左入栈. 2.申请参数...

26720
来自专栏csdn

react 移动端项目配置 postcss-pxtorem

88310
来自专栏人人都是极客

Linux 设备和驱动的相遇

上一节的最后我们讲到设备树的三大作用,其最后一个作用也是最重要的作用:设备信息集合。这一节结合设备信息集合的详细讲解来认识一下设备和驱动是如何绑定的。所谓设备信...

33540
来自专栏MicroPython

基于MicroPython:TPYBoard心率监测器

这几年智能穿戴设备大火,尤其是手环类,从Apple Watch到荣耀手环,再到不知名的某些品牌,智能穿戴设备是铺天盖地的来了。而其中心率监测基本上是所有穿戴设备...

22850

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励