文章目录
1 位操作
运算符 | 含义 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
~ | 取反 |
<< | 左移 |
>> | 右移 |
c 语言中存在以上 6 个位操作运算符,且它们只能用于整形操作数。
总结:对于原二进制数来说,&0
是屏蔽,&1
是不变。
总结:对于原二进制数来说,|0
是不变,|1
是置1。
总结:对于原二进制数来说,^0
是不变,^1
是反转。
1.1 按位与
按位与的定义是:同一二进制位上的数字都是1的话,& 的结果为1,否则为0。
运算 | 结果 |
---|---|
0 & 0 | 0 |
0 & 1 | 0 |
1 & 0 | 0 |
1 & 1 | 1 |
根据这个特性,& 操作常常用来屏蔽特定的二进制位。
例如:0000 1111 & 0000 0011 = 0000 0011
与运算 | 0 0 0 0 1 1 1 1 |
& | 0 0 0 0 0 0 1 1 |
结果 | 0 0 0 0 0 0 1 1 |
可以看见,1111的前两位被屏蔽成为0了。
所以如果想清空数据,只需要将原二进制数与上 &0 就可以了。0的位数对应原二进制数的位数,对各位进行屏蔽,全部置0。
相对的,&可以利用0来屏蔽,也可以用1来读取。
例如: 一个二进制数 1101 1001,我只想要它的后四位,怎么办呢?
只需要进行如下操作:1101 1001 & 0000 1111即可。
与运算 | 1 1 0 1 1 0 0 1 |
& | 0 0 0 0 1 1 1 1 |
结果 | 0 0 0 0 1 0 0 1 |
其实该方法是屏蔽和读取的结合,&0保证消除无用位,&1保证有用数据的完整性。
总结:对于原二进制数来说,&0
是屏蔽,&1
是不变。
1.2 按位或
定义:只要参与运算的双方其中有一个是1,结果就是1.同0才为0。
运算 | 结果 |
---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
主要用作将某些特定位置1。
例如:1010 0000 | 0000 1111 = 1010 1111。
或运算 | 1 0 1 0 0 0 0 0 |
| | 0 0 0 0 1 1 1 1 |
结果 | 1 0 1 0 1 1 1 1 |
总结:对于原二进制数来说,|0
是不变,|1
是置1。
1.3 按位异或
只要参与运算的双方互异,结果就为1,否则为0。
运算 | 结果 |
---|---|
0 ^ 0 | 0 |
0 ^ 1 | 1 |
1 ^ 0 | 1 |
1 ^ 1 | 0 |
可以通过上面的定义看到,一个数^1 的话就会0变成1,1变成0,而^0则不对原数进行改变。所以根据此特性可以对特定位进行0 1 反转。
例如: 1100 1100 ^ 0000 1100 = 1100 0000。
异或运算 | 1 1 0 0 1 1 0 0 |
^ | 0 0 0 0 1 1 0 0 |
结果 | 1 1 0 0 0 0 0 0 |
同样的,如果对一个数进行^0,代表保留原值。
总结:对于原二进制数来说,^0
是不变,^1
是反转。
1.4 取反
对一个二进制数进行取反。1变0,0变1。
唯一需要注意的一点是,~的优先级是逻辑运算符中最高的,必须优先计算。
1.5 左移
左移与右移比较类似,是将目标二进制数字向左/右移动相应的位数。
左移补0:1111 1111 << 1 == 1111 1110,换算十进制的话是原来数值的2倍。
左移 | 1 1 1 1 1 1 1 1 |
<< | 1 |
结果 | 1 1 1 1 1 1 1 0 |
1.6 右移
右移看情况:负数补1,正数补0。需要看符号位。同样,换算为十进制数值变为原来的1/2.
右移 | 1 1 1 1 1 1 1 1 |
>> | 1 |
结果 | 0 1 1 1 1 1 1 1 |
总结:左乘右除。
2 单片机中常用操作
2.1 不改变其他位时,对某几个位设定值
比如要改变 GPIOA 的状态,可以先对寄存器的值进行 &
清零操作
GPIOA -> CRL &= 0XFFFFFF0F; // 将第 4-7 位清 0
然后再与需要设置的值进行 |
或运算
GPIOA -> CRL |= 0X00000040; // 设置相应位的值,且不改变其他位的值
2.2 移位操作提高代码可读性
以固件库的 GPIO 初始化的函数里一行代码为例
GPIOx -> BSRR = (((uint32_t)0x01) << pinpos);
这个操作就是将 BSRR 寄存器的第 pinpos 位设置为 1。
为什么要通过左移而不是直接设定呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可以直观明了的知道,是将第 pinpos 位设置为 1。
如果写成
GPIOx -> BSRR = 0x0030;
这样的代码就不容易看出,也不好重用了。
类似的代码还有:
GPIOA -> ORT |= 1<<5; // PA.5 输出高,不改变其他位
这样我们一目了然,5 告诉我们是第 5 位也就是第 6 个端口,1 告诉我们是设置成了 1。
2.3 取反操作使用技巧
SR 寄存器的每一位都代表一个状态,某个时刻我们希望去设置某一位的值为 0,同时其他位都保留位 1,简单的作法是直接给寄存器设置一个值:
TIMx -> SR = 0xFFF7;
这样做法可读性交较差。
看看库函数中代码是如何使用的:
TIMx -> SR = (uint16_t)~TIM_FLAG;
而 TIM_FLAG 是通过宏定义完成的值:
#define TIM_FLAG_Update ((uint16_t)0x0001)
#define TIM_FLAG_CC1 ((uint16_t)0x0002)
看这个就容易明白,可以直接从宏定义重看出 TIM_FLAG_Update 就是设置的第 0 位了,可读性较强。
Ref: C语言中的逻辑运算符:按位与,按位或,按位异或,取反,左右移位
Ref: STM32 不完全手册 - 立创开源