write_bits函数掩码获取及计算

write_bits函数掩码获取及计算

写在前面 嵌入式当中,使用例如i2c、spi等外设时,需要对寄存器中的某些连续位进行写入操作,而不影响其他位,本文来探讨如何实现。 需求 现在有数据要求写入到寄存器,从第 3 位开始,连续写入 4 位的数据。写入的数据为:1100,我们假设寄存器原始数据为:0110 1100。 我们定义从第 3 位

写在前面

嵌入式当中,使用例如i2cspi等外设时,需要对寄存器中的某些连续位进行写入操作,而不影响其他位,本文来探讨如何实现。


需求

现在有数据要求写入到寄存器,从第 3 位开始,连续写入 4 位的数据。写入的数据为:1100,我们假设寄存器原始数据为:0110 1100

我们定义从第 3 位开始为:uint8_t bits_start = 0x3;

写入 4 位数据定义为:uint8_t bits_len = 0x4;

寄存器原始数据为:uint8_t data = 0b01101100;,即:uint8_t data = 0x6C;

准备写入的数据为:uint8_t bits_data = 0b1100;


注:这里我们在描述寄存器的位时,所用的方式是不同的,注意不要混淆。

  • 使用“第 N 位”时,我们从 1 开始计算比特位。
  • 使用bitN时,我们从 0 开始计算比特位。

例如:

  • 自然语言索引二进制比特位。
. 0.    1.    1.    0.     1.    1.    0.    0
第八位 第七位 第六位 第五位  第四位 第三位 第二位 第一位
  • BitN来索引二进制比特位。
.     0 1 1 0. 1 1 0 0
bitN. 7 6 5 4  3 2 1 0

实现

获取掩码

因为我们需要用掩码来对寄存器原始数据相应的位被清除为 0,这里,相应的位是:从第 3 位开始,写入 4 位数据,即:从bit2bit5

bits_len来获取掩码

uint8_t mask = ((1 << bits_len) - 1);

这里 bits_len = 41 向左移 4 位:
	1 << 4. ->. 1 0000,补全 8 位为:0001 0000

将 mask 减去 1:
	二进制 0001 0000 转换为十六进制为:0x10
	0x10 减去 1 为:0x10 - 0x1 = 0xF
	0xF 转换为二进制为: 1111

通过上面代码我们可以获取掩码为:

1111

这里我们得到了长度为bits_len的掩码。接下来我们将对他移动到对应比特写入的位置。

将掩码 mask 左移 bits_start - 1

已知bits_start = 3,所以需要将 mask左移3 - 1 = 2位。

mask左移2位:

0000 1111
0011 1100

我们得到~mask = 0011 1100。我们已经将掩码移动到了合适的位置,接下来我们将对掩码取反,目的是清除寄存器原始数据中需要修改的比特位。

将掩码mask取反

~ 0011 1100  =. 1100 0011

我们得到了最终掩码,现在可以用它来清除寄存器原始数据中需要清除的位。

清除写入位

data = 0110 1100
mask = 1100 0011

我们对他们进行按位与计算:

. 0110 1100 (data)
& 1100 0011 (mask)
-------------------
. 0100 0000

解释:

可以看到,寄存器的原始数据为:
0 1 1 0  1 1 0 0
.   ^.^  ^.^

我们需要从第 3 位开始写入,连续写 4 位。正如我标记的一样。然而,我们既然需要重新写入这些位的数据,就要清除当前位原来的数据对吧。

0 1 0 0  0 0 0 0

可以看到,我们清除掉相应的位之后,得到的结果和 用掩码来处理 的结果一样。

我们已经得到了处理好的寄存器数据,接下来只需要将bits_data数据整合到data中就可以了。

bits_data左移

我们已知bits_data = 1100。现在将其左移到用掩码清空的位置。

uint8_t bits_data = 1100;

我们将其左移 (bits_start - 1) 个位

1100 << (bits_start - 1)  ->. 1100 << 2  ->  0011 0000

我们得到数据有效位和掩码处理过的位相对应的二进制数据。接下来只需要简单的将bits_datadata按位或操作即可。

合并数据

. 0011 0000. (bits_data)
| 0100 0000. (data)
-------------------------
. 0111 0000

我们得到了最终数据,可知直接将其写入到寄存器中,而不对其他位产生影响。

总结

data = 0110 1100. 从寄存器读出来的数据
bits_start = 0x3  从第三位开始
bits_len = 0x4    写入四位的数据
bits_data = 1100  将要写入的数据

data: 0 1   1     0     1     1 0 0
            ^     ^     ^     ^
          第六位 第五位 第四位 第三位
bits_data:  1.    1.    0.    0. 用 bits_data 替换相应位

得:
0111 0000

代码示例

#include <stdint.h>
#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 write_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) << (bits_start - 1)); // 取掩码
  data &= mask;                                        // 清零写入位
  bits_data <<= (bits_start - 1); // 写入数据移动到写入位
  bits_data |= 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

  write_bits(bits_start, bits_len, bits_data);

  return 0;
}
Comment