写在前面
嵌入式当中,使用例如i2c
、spi
等外设时,需要获取某个寄存器的某些位的具体值,这里就来探讨如何实现。
需求
现有需求,要求从寄存器获取从 第 5 位开始,往后数 3 位的值。
第 5 位开始,我们标记为:S = 5
。
读取 3 个位,我们标记为:bit_len = 3
。
这里先要明确一个概念:寄存器的二进制值从 右 往 左 数,第 1 位 则是 bit 0。
假设寄存器的值已知:
0110 1001
-> 0x69
我们要获取:
0 1 1 0 1 0 0 1
8 7 6 5 4 3 2 1 (注:这里的下表是“第n位”,而并非 bit 位,不要混淆
^ ^ ^
从第 5 位开始,往后数 3 位,也就是我标记的位。
我们提取出来的值需要是:110
,补全8 bit
为:0000 0110
。
生成掩码
掩码生成公式:
((1 << bit_len) - 1)
利用公式生成掩码
已知我们要获取的长度是 3,即:
bit_len = 3;
则,先将数字1
左移bit_len
位:
1 << 3 -> 1000
可以看到,我们得到了1000
再将1000
减去1
:
1000 -> 0x8 - 1 -> 0x7
我们得到0x7
,
接下来将0x7
重新转换为二进制形式:
0x7 -> 111
这里,我们成功生成掩码:111
,补全为 8 bit
则为: 0000 0111
。
处理寄存器的值
我们前面提到过,寄存器的值是:
0 1 1 0 1 0 0 1
现在将寄存器的值向 右移S - 1
个位置,也就是右移 4 位。即:
移动前: 0 1 1 0 1 0 0 1
移动后: 0 0 0 0 0 1 1 0
获取需要的值
最终,我们得到掩码值:
0 0 0 0 0 1 1 1
寄存器右移后的值;
0 0 0 0 0 1 1 0
将它们进行按位与操作:
0 0 0 0 0 1 1 1
& 0 0 0 0 0 1 1 0
-------------------
. 0 0 0 0 0 1 1 0
总结
我们按要求从寄存器的值:0110 1001
中,从第 5 位开始读取 3 位,即提取出 110
。
可以看到,我们获取位掩码的操作达到预期。
示例代码
#include <stdio.h>
void print_binary(uint8_t value) {
char binary_str[10] = {0};
int index = 0;
for (int i = 7; i >= 0; i--) {
binary_str[index++] = (value & (1 << i)) ? '1' : '0';
if (i == 4) {
binary_str[index++] = ' ';
}
}
printf("%s\n", binary_str);
}
void read_bits(uint8_t bits_start, uint8_t bits_len, uint8_t bits_data) {
uint8_t mask = 0x00;
uint8_t final_data = 0x00;
uint8_t data = 0b01101100; // 0x6C
mask = ((1 << bits_len) - 1); // 取掩码
data >>= (bits_start - 1); // 要读取的数据与掩码有效位对齐
bits_data = mask & data; // 获取位
printf("bits_data: \t");
print_binary(bits_data);
}
int main(int argc, char *argv[]) {
uint8_t bits_start = 3;
uint8_t bits_len = 4;
uint8_t bits_data = 0b1100; // 0xC
read_bits(bits_start, bits_len, bits_data);
return 0;
}