read_bits 函数掩码获取及计算

read_bits 函数掩码获取及计算

写在前面 嵌入式当中,使用例如i2c、spi等外设时,需要获取某个寄存器的某些位的具体值,这里就来探讨如何实现。 需求 现有需求,要求从寄存器获取从 第 5 位开始,往后数 3 位的值。 第 5 位开始,我们标记为:S = 5。 读取 3 个位,我们标记为:bit_len = 3。 这里先要明确一个

写在前面

嵌入式当中,使用例如i2cspi等外设时,需要获取某个寄存器的某些位的具体值,这里就来探讨如何实现。


需求

现有需求,要求从寄存器获取从 第 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;
}
Comment