本帖最后由 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,通过串口以二进制数据帧的形式发送出来,上位机使用串口工具记录下二进制数据文件。
数据帧的格式由下面几个结构体定义:
- #include <stdint.h>
- typedef struct _mpu6000_accel_t
- {
- uint16_t ax;
- uint16_t ay;
- uint16_t az;
- } mpu6000_accel_t;
- typedef uint16_t mpu6000_temp_t;
- typedef struct _mpu6000_gyro_t
- {
- uint16_t gx;
- uint16_t gy;
- uint16_t gz;
- } mpu6000_gyro_t;
- typedef struct
- {
- mpu6000_accel_t acce;
- mpu6000_temp_t temp;
- mpu6000_gyro_t gyro;
- } mpu6000_data_t;
- #define UART_DATA_ADC_NUM 5
- typedef struct
- {
- uint8_t hdr; // 0xFE
- uint8_t cnt; // up-counter
- int16_t adc[UART_DATA_ADC_NUM];
- mpu6000_data_t imu;
- } 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如下:
- {
- "frame_size" : 26,
- "header_size" : 1,
- "header_hex" : "FE",
- "counter_size" : 1,
- "counter_offset" : 1,
- "payload_size" : 24,
- "payload_offset" : 2,
- "content" :
- [
- {"title":"vref_raw", "offset": 8, "s_fmt":"h", "hidden":1},
- {"title":"vsup", "offset": 0, "s_fmt":"h", "p_fmt":"%.2f", "lambda":"lambda x:11.0*1.205*x[0]/x[1]", "lambda_ref":[0]},
- {"title":"vsensor", "offset": 6, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:1.205/0.15*x[0]/x[1]", "lambda_ref":[0]},
- {"title":"ax", "offset":10, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:x*8.0/32768"},
- {"title":"az", "offset":14, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:x*8.0/32768"},
- {"title":"gy", "offset":20, "s_fmt":"h", "p_fmt":"%.4f", "lambda":"lambda x:x*2000.0/32768"},
- {"title":"temp", "offset":16, "s_fmt":"h", "p_fmt":"%.2f", "lambda":"lambda x:36.53+x/340.0"}
- ]
- }
复制代码前面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位有符号整数,公式计算结果是摄氏度。
一周热门 更多>