序言
这里所说的hex和bin文件是在嵌入式开发时,IDE生成的hex文件或bin文件。它们都是有一定的标准的制式文件。
- hex文件:不能直接烧录到主控芯片的Flash里面去,烧录进入后也不能运行,它是为了方便烧录工具/软件烧录。换句话说就是hex文件里面的有用的数据是被“打包”到一行一行的数据包中去了。
- bin文件:可以直接烧录到主控芯片的Flash里面去,就直接“拷贝”到主控芯片的0x8000000地址(一般来说ARM内核的芯片)地址累加,就可以运行了。
如果你想自己写个烧录软件,那就有必要写个算法将hex文件转为bin文件,然后,使用软件直接将转换好的bin文件“拷贝”到芯片的相关地址(一般是0x8000000)就可以了。
Hex文件格式解析
Hex文件如果用特殊的程序来查看(一般记事本就可以实现)。打开后可发现,整个文件以行为单位,每行以冒号开头,内容全部为16进制码(以ASCII码形式显示)。Hex文件可以按照如下的方式进行拆分来分析其中的内容:
例如:
:020000040000FA
, 我把它看做 0x02 0x00 0x00 0x04 0x00 0x00 0xFA
第一个 0x02 为数据长度。
紧跟着后面的0x00 0x00 为地址。
再后面的0x04为数据类型,类型共分以下几类:
- ‘00’ Data Record:数据记录
- ‘01’ End of File Record:文件结束记录
- ‘02’ Extended Segment Address Record:扩展段地址记录
- ‘03’ Start Segment Address Record:开始段地址记录
- ‘04’ Extended Linear Address Record:扩展线性地址记录
- ‘05’ Start Linear Address Record:开始线性地址记录
然后,接着0x04后面的两个 0x00 0x00就是数据。最后一个0xFA是校验码。
HEX文件的每一行都是这样的格式:
HEX文件的格式
Intel HEX格式的文件是由多条记录组成,而每条记录又是由6个字段组成。这些记录是由一些代表机器语言代码和常量的16进制数据组成的。Intel HEX 文件常用来传输要存储在 ROM 或者 EPROM 中的程序和数据。大部分的 EPROM 编程器能使用 Intel HEX文件记录的基本格式如下:
RecordMark | RecordLength | LoadOffset | RecordType | Data | Checksum |
---|---|---|---|---|---|
记录标志 | 记录长度 | 装载偏移 | 记录类型 | 数据 | 校验和 |
其中,RecordMark字段其实就是每条记录的首部,其值为0x3A,在ASCII码中就是冒号“:”。该字段在HEX文件中,这个头部只占有一个字节。
RecordLength表示每条记录包含的数据的长度,以字节为单位,最大描述255个字节,表现为2个16进制的字符,该字段在HEX文件中占2个字节。
LoadOffset表示该记录中的数据在整个存取器空间中的偏移,用4个十六进制字符描述一个16位数据,在HEX文件中该字段占有4个字节。
RecordType表示记录类型,表现为2个十六进制字符。取值有以下几种:
-
00 表示数据记录;
-
01 表示文件结束记录;
-
02 描述拓展段地址记录;
-
03 描述开始段地址记录;
-
04 描述扩展线性地址记录;
-
05 描述开始线性地址记录。
Data字段表示数据的具体内容,描述方法仍是两个16进制的字符表示1字节的数据。此字段的长度由该记录的RecordLength决定,数据的解释取决于记录类型(RecordType)。
Checksum字段为校验和。这个校验和是这么来的,将RecordMark(“:”)后的所有的数据按字节相加,即成对相加起来,然后模除256得到余数,再对这个余数求补码,最终得出的结果就是校验和。所以检测方法也很简单:在每一条记录内,将RecordMark(“:”)后的所有数据(包括Checksum)按字节相加后得到的8位数据为0,则说明数据无误,否则说明出错了。
至于什么是拓展段地址记录、开始段地址记录、扩展线性地址记录、开始线性地址记录这里不做详细的介绍,在芯艺的《AVR单片机GCC程序设计》的附录部分有详细的说明。而在Arduino的HEX文件中,记录类型只有两种,数据记录和文件结束记录。所以RecordType这个字段的值不是0x00就是0x01。
数据记录适用于8位、16位和32位格式,其详细格式如下:
记录名 | RecordMark | RecordLength | LoadOffset | RecordType | Data | Checksum |
---|---|---|---|---|---|---|
记录名 | 记录标志 | 记录长度 | 装载偏移 | 记录类型 | 数据 | 校验和 |
内容 | “:” | X | - | “00” | - | - |
字节数 | 1 | 1 | 2 | 1 | X | 1 |
文件结束记录适用于8位、16位和32位格式,其详细格式如下:
记录名 | RecordMark | RecordLength | LoadOffset | RecordType | Checksum |
---|---|---|---|---|---|
记录名 | 记录标志 | 记录长度 | 装载偏移 | 记录类型 | 校验和 |
内容 | “:” | “00” | “0000” | “01” | “FF” |
字节数 | 1 | 1 | 2 | 1 | 1 |
比如说,有如下一条数据记录:“:1001A000808184608083808182608083808181609F
”,则,其RecordMark为“:”,RecordLength为”10”,这里是16进制的,对应10进制为16,也就是说Data字段有16个字节;LoadOffset的值为01A0,也就是说在该条记录中,数据字段在内存中的起始地址为01A0;RecordType为00,表示是记录类型;Data的值为80818460808380818260808380818160,一共有16个字节;Checksum的值为9F。用计算器按照上面的方式验证一下是可以得到一个低8位为0的数据,也就是说这条记录是合法的。下面开始编码实现对HEX文件的解析。
使用C语言将Hex文件转成Bin文件
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;
}
//比较当前记录的绝对地址absoluteAddrCurrent和本地的绝对地址absoluteAddrLocal是否有偏差
Bytesskipped = 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;
}
void do_hex2bin(void)
{
QFile hexfile(ui->edtFileName->text());//获取hex文件路径
// 就在原目录下生成一个bin文件
QFile outfile(ui->edtFileName->text().replace("hex", "bin")); // 生成的目标bin文件
if (!hexfile.open(QIODevice::ReadOnly)) // hex文件
{
qDebug("file open fail!");
return;
}
if (!outfile.open(QIODevice::WriteOnly)) // bin文件
{
qDebug("file open fail!");
return;
}
QDataStream out(&outfile);
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();//关闭输入文件
}
ret=HexToBin(&HexDataStr,out);//将解读后的数据写入bin文件
if(ret!=0){
break;
}
}
qDebug("hex2bin ok");
hexfile.close();
outfile.close();
}