背景:设备中包含大量参数,需要配合非程序员同事修改参数进行测试。没有可用的人机交互接口,只能通过串口AT指令或烧录的方式访问。
目标是做成一款人人可以上手的工具,快速对设备进行自定义的配置。
目录
bin文件生成
bin文件格式定义
excel设计要点
文件生成
写入准备
参数读取
文件填充
生成crc
bin文件调用
更新源文件再生成bin
bin文件生成
输入源选择了大众比较容易接受和熟悉的excel,不需要花费大量时间做输入接口界面,且用户基本都安装有可编辑excel的软件。这一部分需要先定义bin文件格式,设计excel表呈现数据,编写python脚本解析excel表并转换成指定格式的bin文件。
bin文件格式定义
固件中打算使用结构体指针的方式访问bin文件,因此定义以下格式存储bin文件
excel设计要点
1、为了方便python读取数据,尽量将需要转换的数据设计到同一列,每个数据需要包含数据本身和这个数据占用的字节数,用于后续转换进bin文件。
另外需要注意的是,尽量将一样大小的数据放在一个区域,这样pandas读取时不需要分太多块。当然本着用户优先的原则,表格的可读性和可查性还是需要放在第一位考虑
设计表格时第一行用于python读入数据,给需要python读入的列命名。可以将它的属性设置为隐藏,打开工作表保护后用户就看不见了。
2、需要限制表格的可更改区间,避免用户误修改导致文件生成失败
1)选中需要修改的表格,右键菜单选择设置单元格格式,在保护标签中,去掉锁定的选定
2)在审阅窗格中,选择保护工作表,在弹出窗口中设置取消保护的密码,只选择选定解除锁定的单元格允许用户修改。
3、对可编辑的数据进行简单的判断和验证,限制可设置的区间,格式等
选择需要验证的单元格,在数据窗格中选择数据验证
在弹出的窗口中按照需求设置验证条件,提示信息,出错警告信息等内容即可。这里只写一个比较复杂的验证,要求数据输入值是0-1000中50的倍数。
公式如下:
=(A1>0)*AND(A1<1001)*AND(MOD(A1,50)=0) (A1替换为要做验证的单元格位置)
文件生成
使用python做bin文件生成脚本,需要使用的库有pandas,numpy,os.path
1)清空文件,写入header;
2)然后按照结构体定义的顺序,逐个读入配置参数数据,转换成bytes格式,写入bin文件中;
结构体定义时需要注意,优先占空间更大的数据,避免C语言结构体数据对齐导致的数据浪费。这样生成bin文件时也可以不需要刻意考虑数据对齐问题。
例如:
下图中param_struct1占用6个字节,生成bin文件需要考虑占位问题
而param_struct2只占4个字节,生成bin文件时不需要考虑占位,直接向文件末尾append数据即可
typedef struct { uint8_t a; uint16_t b; uint8_t c; } param_struct1; typedef struct { uint16_t b; uint8_t a; uint8_t c; } param_struct2;
3)计算文件大小,填充0xff,保证bin文件大小固定(方便将默认参数bin文件嵌入固件中);
4)计算crc写入文件结尾。
写入准备
由于写入使用追加的方式,重新生成文件时需要将文件清空从头开始写
def clear_binfile(fpath):
binfile = open(fpath, 'ab+')
#定位到文件最开始
binfile.seek(0)
#截断文件,定位后面的内容全部删除
binfile.truncate()
binfile.close()
参数读取
使用pandas库,读入数据,为了避免excel表读入时数据格式异常,可以优先定义dtype读入为指定格式数据。
import pandas as pd
import numpy as np
dtype = {
'data1': np.int64,
'data2': np.int64
}
alldata = pd.read_excel(inputpath, index_col='index', sheet_name='main', dtype=dtype)
选中指定区域,一块一块的读取,转换成bytes写入bin文件
def write_binfile(data,length,fpath):
#pandas读入格式为np.int64,to_bytes方法是int的方法,所以需要先转换
if type(data) == np.int64:
data = data.item()
#little表示小端序的方式转换数据到bytes,length表示转换后占用多少字节
content = data.to_bytes(length, 'little')
binfile = open(fpath, 'ab+')
binfile.write(content)
#print("content:", content)
binfile.close()
def write_data_block(data,fpath,config):
for i in data.index:
write_binfile(data[config[0]][i],data[config[1]][i],fpath)
#这里要根据表格的情况自行调用,可以进一步封装
config = ['data1', 'length1']
datablock1 = alldata[config]
datablock1 = datablock1.iloc[4:]
write_data_block(datalock1, outputpath, config)
文件填充
根据当前文件大小,计算出需要填充0xff的数量,使用write_binfile填写到bin文件后面
def get_length_tobe_filled(fpath):
#获取当前文件中已经写入的字节数
size = os.path.getsize(fpath)
return target_length - size - crc_length
生成crc
这部分要写的和mcu解析那端一致,才能达到校验效果。
参考资料:CRC校验原来这么简单-腾讯云开发者社区-腾讯云、
一个crc库:https://github.com/whik/crc-lib-c
crc8算法摘录如下:
bin文件调用
当文件烧录入ROM指定地址后,可以通过结构体const指针的方式访问。
定义一个全局的const结构体指针,根据不同的需求,将const指针指向想要使用的数据地址。只需要将调用参数的方式改成通过指针访问即可。
例如:
//定义const指针并指向默认参数
const param_struct2 * pp = (const param_struct2*)(ROM_ADDR_PARAM_NORMAL);
//需要使用参数的时候通过指针访问,当前指针指向哪个地址,就访问的哪套参数
use_param1(pp->param1);
更新源文件再生成bin
由于生成bin文件比较慢,当源文件没有更新时,可以跳过这个过程。
python提供了三个函数分别获取文件的生成时间,修改时间,修改属性的时间:
os.path.getctime('path')
os.path.getmtime('path')
os.path.getatime('path')
最终决定比较源文件的修改时间,和bin文件的修改时间,若源文件修改时间更新,则重新生成bin;否则不再重新生成bin文件。