目录
前言:很多MCU代码编译器都会生成hex文件,hex文件的用途很多。有些直接把它放进U盘,然后给MCU自举升级,在MCU读取hex文件的时候需要将hex里的数据转换之后才能写到自身的flash里,给自身升级。当然,也可以先把hex转换成bin文件,再放进U盘给MCU自举升级,这样MCU从bin里读到的数据可以直接写到flash里,从而提高大大提高升级速度。
1.hex文件分析
下面截取从hex文件截图了几行:
第一行:02000040800F2 即: 0x02 0x00 0x00 0x04 0x08 0x00 0xF2,都是一个个的十六进制数。一行数据中不同的颜色代表不同的信息域:
以上面的数据行格式分析下面一行:
各个域的信息分别为:
数据长度:0x01
数据地址:0x0000
数据类型:0x00
数据:0807002001010008CB020008C3020008
校验值:0x15
其中,数据类型有以下几类:
00:数据记录
01:文件结束记录
02:扩展段地址记录
03:开始段地址记录
04:扩展线性地址记录
05:开始线性地址记录
扩展段地址记录和扩展线性地址记录是用来干什么的呢?
行格式里的数据地址用两个字节保存,最大值0xFFFF,只能表示64k;一般很多的MCU里的flash都超过64k,为了保存高地址的数据,就有了扩展段地址和扩展线性地址。
当读取这行的数据类型是0x04(扩展线性地址)时,那么,这行的数据就是随后数据的基地址。例如:
第一行的数据类型是0x04(扩展线性地址),它的数据0x0800,就是第二行数据的基地址。第二行的数据类型是0x00(数据记录),它的地址是0x0000,那么它的数据0807002001010008CB020008C3020008要写入flash的地址为(0x0800<<16)|0x0000 ,也就是写入flash的0x0800 0000这个地址。第三行的数据的写入地址为0x0800 0010,该基地址保留到下一个扩展记录为止。
当读取这行的数据类型是0x02(扩展段地址)时,例如:
第一行的数据类型是0x02(扩展段地址) ,它的数据是0x1200;那么,第二行的数据00000000472105014B21050100000000要写flash的地址为(0x1200<<2)+0x2460,也就是写入flash的0x0001 4460这个地址。第三行也是以0x1200为基地址,同样的计算出真正写入的地址。
如果用Notepad+编辑器打开hex文件,它会把不同的数据域标记不同的颜色,而且,如果最后的校验值是的对话,标注为绿色,若校验值错误,标注为黄色。
到此,我们已经大概知道怎么解读hex文件了。下面可以开始尝试把它应用起来。
2.hex文件和bin文件的区别
第一点:
hex文件和bin文件都是进行MCU开发时生成的烧录文件,hex文件是携带了数据和其数据对应在MCU flash存放的地址;bin文件纯属数据,那么怎么知道它的数据该怎么保存在flash里呢?bin里的数据是没有地址,但是它的数据时连续的,只要确定数据的写入起始地址,那么所有数据就在这个起始地址开始递增写入就行了。当我们用上位机Flash Loader Demo烧录芯片,用的烧录文件就是bin文件,烧录的时候是要先配置flash起始烧录的地址。
第二点:
hex文件的数据需要经过转换才能写入flash,这个转换过程可以发生在烧录器里、烧录的上位机里或者是MCU自举升级时先转换在写flash。bin文件数据可以直接复制到相应的flash位置上,不需要再经过任何处理。
第三点:
同一个工程编译出来的hex文件肯定比bin文件大,所以bin文件具有节省内存、便于传输的优点。为什么hex文件会比bin文件大呢?看下面例子:
char hex[9] = "12487936";//占用9字节
char bin[4]={0x12,0x48,0x79,0x36};//只占用4字节
hex文件里的数据时可以打印显示出来的,而bin文件是二进制数据。
接下来我们尝试怎么把hex文件转换成bin文件。
3.基于qt开发hex转bin工具
如果用keil工具进行编译,keil提供了直接生成bin文件的插件,在配置里加入:
这样就能直接生成bin文件,文件名为UPDATA.bin。而接下来要做的是,基于qt自己编写一个exe程序,通过读入hex文件转换生成bin文件。
1.新建一个Qt控制台应用:
2.先看main函数:
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <stdlib.h>
#include <QByteArray>
#include <QDataStream>
typedef struct{
unsigned char datalen;//数据字节
unsigned short addr;//地址域
unsigned char datatype;//类型
unsigned char databuf[16];//数据记录
unsigned char checkout;//校验和
}HexFormatForLine ;
bool ReadHexLineData(HexFormatForLine* out,const QByteArray & ba);//false: 校验错误 true:校验成功
char HexToBin(HexFormatForLine* ba,QDataStream & out);//return 0: ok 1:hex文件结束 2:hex文件有误
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QString filepath=QString(argv[1]);//获取hex文件路径
QFile hexfile(filepath);
QFile outfile;
outfile.setFileName(QString(argv[2]));//获取bin文件的保存路径
if(outfile.open(QIODevice::WriteOnly)==false){
qDebug("创建bin文件失败!");
a.exit(0);
}
QDataStream out(&outfile);
if(hexfile.open(QIODevice::ReadOnly)==false){
qDebug("打开hex文件失败!");
outfile.close();
a.exit(0);
}
QByteArray alinedata;
HexFormatForLine HexDataStr;
while(!hexfile.atEnd()){//循环处理,至hex文件读完
/*若: alinedata =QByteArray::fromHex(":12345678");
则: alinedara ={0x12,0x23,0x45,0x78};*/
alinedata = QByteArray::fromHex(hexfile.readLine());//从hex文件中读取一行
bool ret = ReadHexLineData(&HexDataStr,alinedata);//将一行数据解读到HexDataStr结构体
if(!ret){
qDebug("校验出错,hex文件有误.");
outfile.remove();//删除输出的bin文件
hexfile.close();//关闭输入文件
a.exit(0);
}
ret=HexToBin(&HexDataStr,out);//将解读后的数据写入bin文件
if(ret!=0){
break;
}
}
qDebug() <<"hex2bin ok.";
hexfile.close();
outfile.close();
a.exit(0);
}
编程要点:
- 通过argv参数获取hex文件的路径和配置bin文件的保存路径
- QFile文件读写操作
- 利用fromHex函数将hex里的字符串转换成16进制编码的数组。
- 在while循环里,每一次循环读取hex文件的一行数据进行处理,ReadHexLineData函数将一行数据里的每一个域解析并保存在HexDataStr结构体里面。 然后将该结构体传入HexToBin函数处理。
3.下面提供ReadHexLineData和HexToBin函数:
bool ReadHexLineData(HexFormatForLine* out,const QByteArray & ba)//false: 校验错误 true:校验成功
{
unsigned char i,checkoutCal=0;
//计算校验值
for(i=0;i < ba.size()-1;i++){
checkoutCal += (unsigned char)ba.at(i);
}
checkoutCal = 0x100-checkoutCal;
//获取个部分域的值
out->datalen =(unsigned char)ba.at(0);
out->addr = ((unsigned char)ba.at(1)<<8)|(unsigned char)ba.at(2);
out->datatype = (unsigned char)ba.at(3);
memset(out->databuf,0,sizeof(out->databuf));
for(i = 0;i<out->datalen;i++){
out->databuf[i] = (unsigned char)ba.at(4+i);
}
out->checkout = (unsigned char)ba.at(4+i);
#if 0 //调试时打开
qDebug("datalen=%X",out->datalen);
qDebug("addr=%X",out->addr);
qDebug("datatype=%X",out->datatype);
qDebug("checkout=%X",out->checkout);
qDebug("checkoutCal=%X",checkoutCal);
#endif
//比较读取的校验值和计算的校验值是否一致
if(checkoutCal == out->checkout){
return true;
}
return false;
}
char HexToBin(HexFormatForLine* ba,QDataStream & out)//return 0: ok 1:hex文件结束 2:hex文件有误
{
static unsigned int ExStageAddr = 0x00;//扩展段地址
static unsigned int ExLineAddr = 0x00;//扩展线性地址
static unsigned int absoluteAddrLocal = 0x00;//本地记录绝对地址
unsigned int absoluteAddrCurrent = 0x00;//计算当前记录的绝对地址
unsigned int Bytesskipped = 0;//被跳过的字节数
switch(ba->datatype){
case 0x00://数据记录
//计算出当前记录的绝对地址
if(ExStageAddr != 0){
absoluteAddrCurrent = (ba->addr+ExStageAddr);
}else if(ExLineAddr != 0){
absoluteAddrCurrent = (ba->addr|ExLineAddr);
}else{
absoluteAddrCurrent = ba->addr;
}
//hex文件第一条数据记录时,将本地绝对地址absoluteAddrLocal同步等于当前记录的绝对地址absoluteAddrCurrent
if(absoluteAddrLocal == 0){
absoluteAddrLocal = absoluteAddrCurrent;
}
Bytesskipped = absoluteAddrCurrent-absoluteAddrLocal;//比较当前记录的绝对地址absoluteAddrCurrent和本地的绝对地址absoluteAddrLocal是否有偏差
break;
case 0x01://文件结束记录
return 1;
break;
case 0x02://扩展段地址记录
ExStageAddr = (ba->databuf[0]<<8|ba->databuf[1])<<2;
ExLineAddr = 0x00;
return 0;//return ok
break;
case 0x04://扩展线性地址记录
ExLineAddr = (ba->databuf[0]<<8|ba->databuf[1])<<16;
ExStageAddr = 0x00;
return 0;//return ok
break;
default:
return 2;
break;
}
for(unsigned int i = 0;i < Bytesskipped;i++){//被跳过的地址,填充0
out <<(unsigned char)0x00;
}
if(Bytesskipped!=0){
qDebug() <<Bytesskipped;
}
absoluteAddrLocal += Bytesskipped;//本地绝对地址absoluteAddrLocal累加
for(unsigned int i = 0;i < ba->datalen;i++){
out <<ba->databuf[i];
}
absoluteAddrLocal += ba->datalen;//本地绝对地址absoluteAddrLocal累加
return 0;//return ok
}
将上面两块代码复制在工程,选择Release,点击build,编译出exe程序。这里不能点击run,因为点击run默认不会写argv参数。我们需要argv参数提供hex文件路径和bin文件的保存路径(这里可以在代码里加入一个默认路径,这样,若argv为空则使用默认路径)。
4.将生成的exe文件复制到单独的文件夹:
在电脑左下角搜索qt,找到Qt 5.4 for Desktop (MinGW 4.9 32 bit) 工具,点击打开,切换到刚才保存exe文件的目录,输入windeployqt hex2bin.exe,这个就可以把exe需要的库文件复制到exe保存路径下:
红框里的文件可以删除来节约空间:
5.接下来把需要转换的hex文件也放进这个文件夹,同时增加了run_hex2bin.bat文件,这个是为了调用hex2bin.exe,bat里面的内容是:
hex2bin.exe ./UPDATA.hex ./UPDATA.bin
点击run_hex2bin.bat,就会运行hex2bin.exe文件,然后生成的bin文件就会保存在该目录下。
6.前面提到过用keil自带的插件可以生成bin文件,那么现在可以在keil工程配置那里,after build之后凋用我们自己做的hex2bin.exe来将hex文件转换成bin文件。当然前提要把hex2bin.exe文件及其需要的dll文件全部复制到keil工程文件uvproj所在的目录下。再进行如下配置:
将keil工程编译一次,在对比用fromelf和自己做的hex2bin.exe生成的bin文件是否完成一致,我试过是一样的。