开源一个二进制数据文件解码小工具

2020-01-04 18:59发布

本帖最后由 fnems 于 2019-7-7 14:53 编辑

github: https://github.com/Felix-ZJU/py_dataunpack

经常会有这种场合,用单片机获得、生成的数据,通过flash芯片或者串口以二进制帧/报文数据的形式记录、发送出来。
这些二进制数据上传到PC机后,需要解码成文本文件,后续用excel、matlab等其他工具进一步分析。

这个小工具的作用是把二进制文件以特定格式解码成文本文件。解码输出格式是逗号分隔的csv文件。

每个特定格式的二进制文件需要搭配一个描述其格式的json配置文件。
json配置文件里声明了二进制帧的长度、帧头、有无计数器、有效载荷的长度位置以及按照何种格式(长度、有无符号数、解码前进行的函数运算、不同载荷之间的相互引用计算等)解码。


github上有示例。这里就cap-udata-adc&imu-20190622_2225这个例子说明一下工具的用法,和json配置文件规则。

cap-udata-adc&imu-20190622_2225这个数据是一个输出4-20mA电流信号的倾角传感器标定数据。我通过比对倾角传感器度数和一个IMU(MPU6050)度数,来判断倾角传感器的响应速度。
倾角传感器电流信号由150欧姆精密电阻采样,经1kHz截止频率的RC前置滤波器进行抗混滤波调理后,由STM32的ADC采样得到。IMU传感器数据由I2C总线得到。数据记录和输出速率是200Hz,通过串口以二进制数据帧的形式发送出来,上位机使用串口工具记录下二进制数据文件。

数据帧的格式由下面几个结构体定义:

  1. #include <stdint.h>

  2. typedef struct _mpu6000_accel_t
  3. {
  4.   uint16_t ax;
  5.   uint16_t ay;
  6.   uint16_t az;
  7. } mpu6000_accel_t;

  8. typedef uint16_t mpu6000_temp_t;

  9. typedef struct _mpu6000_gyro_t
  10. {
  11.   uint16_t gx;
  12.   uint16_t gy;
  13.   uint16_t gz;
  14. } mpu6000_gyro_t;

  15. typedef struct
  16. {
  17.   mpu6000_accel_t acce;
  18.   mpu6000_temp_t  temp;
  19.   mpu6000_gyro_t  gyro;
  20. } mpu6000_data_t;

  21. #define UART_DATA_ADC_NUM 5

  22. typedef struct
  23. {
  24.   uint8_t        hdr; // 0xFE
  25.   uint8_t        cnt; // up-counter
  26.   int16_t        adc[UART_DATA_ADC_NUM];
  27.   mpu6000_data_t imu;
  28. } uart_data_t;
复制代码uart_data_t就是最终通过串口发出来的结构体,或者说数据帧。
帧头hdr总是0xFE,方便查找帧的起始。
帧头后面是计数器cnt,用来记录帧id,本意是方便检查有无丢帧,这里没有用到,但是可以辅助确定帧的起始。
后面的数据都是载荷payload。载荷包括ADC的数据,5个int16_t有符号整数;和经过发封装的IMU数据,7个int16_t有符号整数。
其中ADC采样里面,通道1是供电电压分压值Vsupply(电阻分压比是1/11),通道4是电流传感器采样值Vsensor(采样电阻150欧姆),通道5是STM32内部的参考电压Vref。
IMU数据里面,前三个是加速度(ax, ay, az),后面三个是陀螺仪(gx, gy, gz),中间是温度。其中有用的数据是ax、az和gy
原始二进制数据的结构就介绍完了。

解码工具为了解析上面的数据,需要知道数据的格式。格式并非固化在工具里面,而是通过一个json配置文件指定。json如下:
  1. {
  2.   "frame_size"     : 26,
  3.   "header_size"    : 1,
  4.   "header_hex"     : "FE",
  5.   "counter_size"   : 1,
  6.   "counter_offset" : 1,
  7.   "payload_size"   : 24,
  8.   "payload_offset" : 2,
  9.   "content" :
  10.   [
  11.     {"title":"vref_raw", "offset": 8, "s_fmt":"h", "hidden":1},
  12.     {"title":"vsup",     "offset": 0, "s_fmt":"h", "p_fmt":"%.2f", "lambda":"lambda x:11.0*1.205*x[0]/x[1]", "lambda_ref":[0]},
  13.     {"title":"vsensor",  "offset": 6, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:1.205/0.15*x[0]/x[1]", "lambda_ref":[0]},
  14.     {"title":"ax",       "offset":10, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:x*8.0/32768"},
  15.     {"title":"az",       "offset":14, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:x*8.0/32768"},
  16.     {"title":"gy",       "offset":20, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:x*2000.0/32768"},
  17.     {"title":"temp",     "offset":16, "s_fmt":"h", "p_fmt":"%.2f", "lambda":"lambda x:36.53+x/340.0"}
  18.   ]
  19. }
复制代码前面frame_size、header、counter、payload等描述看字面意思都能理解,这里重点介绍一下content数组。
数组里每一条元素都对应一个数据。
第一条元素是vref_raw,ADC采样的VREF参考电压。它的地址是载荷的第8字节,解码格式s_fmt是“h”即halfword,16位有符号整数。s_fmt请参考python的struct.unpack里面的格式字符串规则,很丰富,可以指定大小端、数据长度、有/无符号数等。hidden属性表示这个元素不被输出到csv文件。
第二条元素是vsup,供电电压。它的地址位于载荷的第0字节,解码格式也是16位有符号数,输出格式是"%.2f",含有小数点后2位数字的小数,"lambda_ref"是[0]表示参考了索引是0的元素即vref_raw,“lambda”函数"lambda x:11.0*1.205*x[0]/x[1]",表示供电电压的输出规则是11.0*1.205*x[0]/x[1],其中x[0]是本通道的原始解码数据,x[1]是lambda_ref数组中的一个元素,如果lambda_ref引用了k个元素,那么它们在lambda函数中依次是x[1]、x[2]、...、x[k]。因此根据lambda函数vsup_output = 11.0*1.205*vsup/vref_raw。公式里面1.205是参考电压的标定值,11是分压电阻比例。经过计算后输出的浮点数即以伏特V为单位的物理量值,而不是原始的ADC采样数据。
最后一条元素是IMU模块测量的温度数据temp。它的计算规则是"lambda":"lambda x:36.53+x/340.0",由于没有引用其他通道,所以lambda公式中没有用到数组,temp_output = 36.53+temp/340.0,这条公式来自MPU6050寄存器说明书,原始数据是16位有符号整数,公式计算结果是摄氏度。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。