嵌入式项目中打造自己的utils库-二进制转换

前言

在嵌入式开发中,不可避免要和驱动打交道。很多外设的寄存器都是使用2进制形式进行配置的。如果每次配寄存器,或回顾以前代码,对着16进制凭借大脑或者计算器来做2进制转换,就会非常麻烦。那么何不写一些代码,让2进制看起来更直观呢。
虽然GCC是支持0b开头的语法的(参考0x),但过于依赖会降低可移植性,不如自己手写一个吧。

image.png

实现

下面直接贴代码:

#define BIN(n)              ((0x##n>>21 & 0x80) | \
                             (0x##n>>18 & 0x40) | \
                             (0x##n>>15 & 0x20) | \
                             (0x##n>>12 & 0x10) | \
                              (0x##n>>9 & 0x08) | \
                              (0x##n>>6 & 0x04) | \
                              (0x##n>>3 & 0x02) | \
                                 (0x##n & 0x01) )

示例:

uint8_t val = BIN(10100101);
printf("val:%X\n", val);

结果:

val:A5

代码分析

uint8_t val = BIN(10100101)

// 宏展开
((0x10100101>>21 & 0x80) | \
 (0x10100101>>18 & 0x40) | \
 (0x10100101>>15 & 0x20) | \
 (0x10100101>>12 & 0x10) | \
  (0x10100101>>9 & 0x08) | \
  (0x10100101>>6 & 0x04) | \
  (0x10100101>>3 & 0x02) | \
     (0x10100101 & 0x01) )

// 用2进制表示
((0x10100101>>21 & 0b10000000) | \
 (0x10100101>>18 & 0b01000000) | \
 (0x10100101>>15 & 0b00100000) | \
 (0x10100101>>12 & 0b00010000) | \
  (0x10100101>>9 & 0b00001000) | \
  (0x10100101>>6 & 0b00000100) | \
  (0x10100101>>3 & 0b00000010) | \
     (0x10100101 & 0b00000001) )

注意:此时10100101是一个16进制的数字,各位数据如下:
image.png

// 2进制表示和移位过程
0b00010000000100000000000100000001 >> 21 & 0b10000000 = 0b00010000000 & 0b10000000                      = 0b10000000
0b00010000000100000000000100000001 >> 18 & 0b01000000 = 0b00010000000100 & 0b01000000                   = 0b00000000
0b00010000000100000000000100000001 >> 15 & 0b00100000 = 0b00010000000100000 & 0b00100000                = 0b00100000
0b00010000000100000000000100000001 >> 12 & 0b00010000 = 0b00010000000100000000 & 0b00010000             = 0b00000000
0b00010000000100000000000100000001 >>  9 & 0b00001000 = 0b00010000000100000000000 & 0b00001000          = 0b00000000
0b00010000000100000000000100000001 >>  6 & 0b00000100 = 0b00010000000100000000000100 & 0b00000100       = 0b00000100
0b00010000000100000000000100000001 >>  3 & 0b00000010 = 0b00010000000100000000000100000 & 0b00000010    = 0b00000000
0b00010000000100000000000100000001       & 0b00000001 = 0b00010000000100000000000100000001 & 0b00000001 = 0b00000001

最后各位相加(与),是不是组合得到0b10100101(0xA5)了?

课外小知识

不知道大家对宏定义的#号语法是否还有印象,这里一起复习一下:

/*
  单个#
  作用是将#后面的参数转成字符串
*/
#define PRINT_VAR(a) printf("%s:%d\n", #a, a)
uint8_t length = 123;
PRINT_VAR(length);
// 输出:length:123

/*
  两个##
  作用是连接##前后两部分内容
*/
#define VAR_DEF(a) int _var_##a
#define VAR(a) _var_##a

VAR_DEF(length);
VAR(length) = 123;
printf("length:%d\n", VAR(length));
// 输出:length:123

应用

随意打开一个传感器的数据手册,里面对寄存器的配置说明长这样:
image.png
用上刚才的代码,做如下配置:
陀螺仪输出速率:104Hz(0100)
陀螺仪量程:1000dps(10)
另外一个不管它,保持默认为0。

uint8_t ctrl2_g_config = BIN(01001000);
i2c_reg_write(&dev, 0x11, ctrl2_g_config);

是不是比写0x48直观多了,同时也方便下次修改配置。

uint8_t ctrl2_g_config = 0x48;
i2c_reg_write(&dev, 0x11, ctrl2_g_config);

再把寄存器注释写在代码上,下次维护代码一目了然。

/*
  ODR_G [7:4] Gyroscope output data rate selection. Default value: 0000
           ODR_G0 ODR [Hz] when G_HM_MODE = 1   ODR [Hz] when G_HM_MODE = 0
  0 0 0 0: Power down Power down
  0 0 0 1: 12.5 Hz (low power)                  12.5 Hz (high performance)
  0 0 1 0: 26 Hz (low power)                    26 Hz (high performance)
  0 0 1 1: 52 Hz (low power)                    52 Hz (high performance)
  0 1 0 0: 104 Hz (normal mode)                 104 Hz (high performance)
  0 1 0 1: 208 Hz (normal mode)                 208 Hz (high performance)
  0 1 1 0: 416 Hz (high performance)            416 Hz (high performance)
  0 1 1 1: 833 Hz (high performance)            833 Hz (high performance)
  1 0 0 0: 1.66 kHz (high performance)          1.66 kHz (high performance)
  1 0 0 1: 3.33 kHz (high performance           3.33 kHz (high performance)
  1 0 1 0: 6.66 kHz (high performance           6.66 kHz (high performance)
  1 0 1 1: Not available                        Not available
  
  FS_G [3:2] Gyroscope full-scale selection. Default value: 00
  (00: 245 dps; 01: 500 dps; 10: 1000 dps; 11: 2000 dps)
  
  FS_125[1] Gyroscope full-scale at 125 dps. Default value: 0
  (0: disabled; 1: enabled)
  
  Reserve[0] 0
*/
uint8_t ctrl2_g_config = BIN(01001000);
i2c_reg_write(&dev, 0x11, ctrl2_g_config);

但需要注意的是,此方法仅支持8位2进制的表示,要支持16位、32位的2进制,一是参数存储字节会超32位;二是存在字节序的问题。有兴趣的朋友可以自己扩展实现一下。

End

本篇文章只有一行代码,但却是嵌入式开发中一个非常有用的工具,通过这个工具,可以节省很多时间。
c语言的开发,自身的语法糖并不多,而嵌入式的开发中,IDE带来的辅助效果更匮乏。所以为了提升效率,建议大家收集自己常用的工具代码,整理成模块,多次复用起来,这也是标题utils库的由来。
打造utils库会是一系列的文章,会跟大家分享有用的代码。多数会以宏定义的方式实现,也会有c代码级别的实现。期待我们下一次见面。

18 声望
3 粉丝
0 条评论
推荐阅读
float类型中NaN和Inf是什么
做嵌入式的同学应该知道,c语言中int的大小,是和平台有关的,有的占4字节,有的占2字节。所以我们对有期望长度的变量,很少直接用int定义,而更多用uint8_t, uint16_t, uint32_t。但到了float,我们为什么没有fl...

银翼Neal阅读 639

Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...

码哥字节6阅读 1.4k

封面图
C 程序眼中的 Unicode
去年写了一篇文章「在 C 程序中处理 UTF-8 字符串」,介绍了如何使用 GLib 提供的 UTF-8 字符串处理函数来实现基本的 UTF-8 文本处理。不过,GLib 是一个功能比较全面的 C 程序库,C 字符串处理仅仅是它的一个很...

garfileo3阅读 5.7k评论 5

手写一个同步服务端时间的小工具
在前端开发的过程中,开发者经常会用到 new Date() 来获取当前时间,但是 new Date() 是获取的当前操作系统的时间,由于用户可以修改当前电脑时间,所以它是不准确的。

jump__jump2阅读 876

计算机如何表示整数
在计算机中,任何的数据都是用二进制: 0 和 1 来表示。整数也不例外。生活中的 10,在 8 个字节的整数中表示为 00001010。但是这样子只能表示正数和零。怎么表示负数呢?于是有了符号位的概念。在 8 个字节的整...

kang2阅读 3k评论 7

麒麟操作系统 (kylinos) 从入门到精通 - 研发环境 - 第二十一篇 C++/C语言开发环境搭建
类别:笔记本型号:中国长城 NF14C硬件平台:飞腾处理器(ArmV8 指令集)系统:银河麒麟操作系统 V10 SP1(2203) 关键词:信创,麒麟系统,linux,c++,c,内核飞腾,arm

码上世界1阅读 2.4k评论 1

封面图
浙大版《C语言程序设计》第四版(何钦铭颜晖) 第5章 函数 课后习题答案
你也可以上程序咖([链接]),打开大学幕题板块,不但有答案,讲解,还可以在线答题。一、选择题1.在 C 语言程序中,若对函数类型未加显式说明,则函数的隐含类型为( )。A. voidB. double C. charD. int答:D解析...

茹茹1阅读 1k

18 声望
3 粉丝
宣传栏