1:什么是hex文件
以*.hex为后缀的文件我们称之为HEX文件。hex是intel规定的标准,hex的全称是Intel HEX,此类文件通常用于传输将被存于ROM或EEPROM中的程序和数据。是由一行行符合Intel HEX文件格式的文本所构成的ASCII文本文件。 HEX的英语原始意思是16进制。这种文件格式主要用于保存单片机固件。 整个文件以行为单位,每行以冒号开头,内容全部为16进制码,2个ASCII码字符表示1个Hex字节
2:hex文件 格式分析
找份单片机工程编译下,基本都支持HEX文件格式输出,本文分析一份STM32固件数据。
选择输出类型为:Intel Extended hex 编译源代码输出*.hex文件。通过Notepad++打开,看到它通过颜色帮我们分辨出各个数据类型。
2.1:数据类型
类型 | 描述 | 备注 |
: | 行开始 | 换行回车行结束 |
02 | 数据长度 | 2个字符 |
0000 | 地址 | 4个字符 |
04 | 功能 | 重点关注 |
08 00 | 数据 | 数据字符=数据长度*2 |
F2 | 校验和 | 前面四项相加补码 0x02+0x00+0x00+0x04+0x08+0x00 = 0x0e 取反+1 = 0xF2 |
2.2:功能码
功能码 | 描述 | 备注 |
00 | 数据行 | |
01 | 文件结束行 | 再文件最后一行出现 |
02 | 扩展段地址 | 单片机上不出现 |
03 | 开始段地址 | 单片机上不出现 |
04 | 扩展线性地址 | 单片机高地址两字节 0x0800xxxx stm32 flash地址 |
3:代码解析Hex文件
通过C++解析Hex文件得到若干个数据块,开发过程中没有设置段基本就是一块数据。我们定义数据结构如下:
class RecordBlock
{
public:
int address; //地址
QByteArray data;//数据
};
通过调用下面函数得到HEX数据块集合
int HexFirmware::ParseFile(QString path, QList<RecordBlock> &BlockList)
{
//step1:清空块列表
BlockList.clear();
//step2:清空记录行列表
recordLines.clear();
QByteArray HLine;
//step3:声明一个流读取类
QFile aFile(path);
if (!aFile.exists()) //文件不存在
return -1;
if (!aFile.open(QIODevice::ReadOnly | QIODevice::Text)) //打开失败
return -2;
QByteArray data;
int dataLenght;
bool ok;
ExtendedAddr = 0;
while (!aFile.atEnd())
{
//| MARK | RECLEN | OFFSET | RECTYP | DATA | CHKSUM |
HLine = aFile.readLine();
if(HLine.size()<11) return -5;//数据长度错误
if (HLine[0] != ':')//":"开头的不用打印
{
return -3;//不以“:”开头不解析 头格式错误
}
else
{
int type = HLine.mid(7, 2).toInt(&ok,16);//功能类型
if(ok==false) return -4; //类型错误
unsigned int address;
int lineLen;
int offse;
int checkNum;
switch (type)
{
case 0: //数据部分
HeadLineStr = QString(HLine);
HLine.remove(0,1); //移除":"
address = HLine.mid(2, 4).toInt(&ok,16);//文件类型
if(ok==false) return -5; //地址错误
if (ExtendedAddr != 0x0000)
{
address |= (ExtendedAddr<<16);
}
dataLenght = HLine.mid(0,2).toInt(&ok,16);
if(ok==false) return -5; //地址错误
lineLen = (dataLenght+5)*2; //
if((dataLenght+5)*2 > HLine.size()) //数据长度错误
return -6; //地址错误
checkNum = HLine.mid(lineLen-2,2).toInt(&ok,16);
if(ok==false) return -8; //校验错误
offse = 8;
if (checkSum(data,HLine,lineLen-2,offse, checkNum))
{
RecordLine S19Data;
S19Data.address = address;
//纯数据,10 = 总个数2 + 地址4 + 类型2 + 校验2
S19Data.data = data;
//纯数据长度,去掉地址2位,校验1位 = 3
S19Data.count = data.size();
recordLines.append(S19Data);
// qDebug()<< S19Data.data.toHex();//
}
break;
case 0x04:
HeadLineStr = QString(HLine);
HLine.remove(0,1); //移除":"
address = HLine.mid(2, 4).toInt(&ok,16);//文件类型
if(ok==false) return -5; //地址错误
dataLenght = HLine.mid(0,2).toInt(&ok,16);
if(ok==false) return -5; //地址错误
lineLen = (dataLenght+5)*2; //
if((dataLenght+5)*2 > HLine.size()) //数据长度错误
return -6; //地址错误
ExtendedAddr = HLine.mid(8, 4).toInt(&ok,16);
if(ok==false) return -9; //地址错误
break;
}
}
}
aFile.close();
//地址升序排序
devListSort(recordLines);
//把文件中每一行的数据组合成若干个地址开头的整块数据,放在s19Block里用于发送
int nextAddress = 0xFFFF;
RecordBlock S19Block;
for (int i = 0; i < recordLines.size(); i++)
{
if (i == 0)//第一个循环
{
// HexBlock = new HexRecordBlock();
//获取下一条记录的起始地址,并将本条记录存储再数据块中
nextAddress = getNextAddress(S19Block, recordLines, i);
if (i == recordLines.size() - 1)
{
//已经循环到底,只有一个block
BlockList.push_back(S19Block);
}
}
else//如果不是第一条记录
{
if (nextAddress == recordLines[i].address)
{
joinHexBlockData(S19Block.data, recordLines[i].data);
nextAddress = recordLines[i].address + recordLines[i].count;
if (i == recordLines.size() - 1)
{
//已经循环到底,只有一个block
BlockList.append(S19Block);
}
}
else if (nextAddress < recordLines[i].address)
{
BlockList.append(S19Block);
S19Block.data.clear();
nextAddress = getNextAddress(S19Block, recordLines, i);
if (i == recordLines.size() - 1)
{
//已经循环到底,只有一个block
BlockList.append(S19Block);
}
}
else
{
qDebug()<<"地址覆盖";
return -8;
}
}
}
return 0;
}
//排序
void HexFirmware::devListSort(QList<RecordLine > &list)
{
qSort(list.begin(), list.end(), compare);
}
//检验核对
bool HexFirmware::checkSum(QByteArray &hex,QByteArray data, int len, int offset,unsigned char checkNum)
{
//校验
unsigned int sum = 0;
bool ok;
hex.clear();
for (int i = 0; i <len; i = i + 2)
{
unsigned char value = data.mid(i,2).toInt(&ok,16);
if(ok==false) return false;
if(i>=offset)
hex.append(value);
sum += value;
}
//取低字节,1位
unsigned char lowSum = sum&0xff;
unsigned char temp = 0x100 - lowSum;
if (temp == checkNum)
{
return true;
}
else
{
return false;
}
}
获得数据后就可以通过CAN传输来给设备升级!!!
1:代码通过一行一行的解析数据,并对数据检验核实。
2:通过地址排序行数据。理论是不需要的,生成是按照地址顺序生成。
3:把连续地址合并成大块。好处在于升级可以按自定义数据长度传输如ISO-15765 最大一帧可以传输4k数据