1:前言
Motorola S-record是由Motorola创建的一种文件格式,它以 ASCII十六进制文本形式传送二进制信息。这种文件格式也可以称为SRECORD、SREC、S19、S28、S37。它通常用于对微控制器、EPROM 和其他类型的可编程逻辑设备进行编程。
S-record格式是在1970年代中期为 Motorola 6800处理器创建的。该处理器和其他嵌入式处理器的软件开发工具将生成S-record格式的可执行代码和数据。程序员将读取S-record格式并将数据“刻录”到嵌入式系统中使用的PROM或EPROM中。
2:S19文件 格式分析
找份单片机工程编译下,基本都支持S19文件格式输出,本文分析一份STM32固件数据。
选择输出类型为:Motorola S-records 编译源代码输出*.s19文件。通过Notepad++打开,看到它通过颜色帮我们分辨出各个数据类型。
类型+长度+地址+[数据]+校验 数据可选
长度 = 地址+数据+校验
校验和 = 类型+长度+地址+[数据] 累计 低字节取反
05+08+00+4A+D1 = 0x128 0x28取反 = 0xD7
记录 类型 | 功能 | 地址 长度 | 描述 | 备注 |
S0 | 标题 | 2字节 | 用于记录文件信息如:文件名 | 第一行 |
S1 | 数据 | 2字节 | 记录带2个地址的数据 | |
S2 | 数据 | 3字节 | 记录带3个地址的数据 | |
S3 | 数据 | 4字节 | 记录带4个地址的数据 | |
S7 | 结束 | 4字节 | 结束S3数据记录 | 与S3配对出现 |
S8 | 结束 | 3字节 | 结束S2数据记录 | 与S2配对出现 |
S9 | 结束 | 2字节 | 结束S1数据记录 | 与S1配对出现 |
3:代码解析S19文件
通过C++解析S19文件得到若干个数据块,开发过程中没有设置段基本就是一块数据。我们定义数据结构如下:
class RecordBlock
{
public:
int address; //地址
QByteArray data;//数据
};
int S19Firmware::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;
while (!aFile.atEnd())
{
HLine = aFile.readLine();
if(HLine.size()<8) return -5;//数据长度错误
//step4:如果读取一行代码是以“S0”开头的话,则不进行解析
if (HLine[0] != 'S')//"S0"开头的不用打印
{
return -3;//不以“S”开头不解析 头格式错误
}
else
{
int type = HLine.mid(1, 1).toInt(&ok,16);//文件类型
if(ok==false) return -4; //类型错误
unsigned int address;
int lineLen;
int offse;
switch (type)
{
case 0:
HeadLineStr = QString(HLine);
// qDebug()<<"HeadLineStr"<<HeadLineStr;
break;
case 1://数据记录(Data Record) S1
case 2://数据记录(Data Record) S2
case 3://数据记录(Data Record) S3
{
//去掉"S0"
HLine.remove(0,2);
//数据长度
dataLenght = HLine.mid(0,2).toInt(&ok,16);
if(ok==false) return -4; //类型错误
lineLen = 2+ dataLenght*2;
if(HLine.size()<lineLen) return -5;//数据长度错误
//取地址 高在前
if(type==1)
{
address= HLine.mid(2,4).toInt(&ok,16);
if(ok==false) return -4; //类型错误
offse = 6;
}
else if(type==2)
{
address = HLine.mid(2,6).toInt(&ok,16);
if(ok==false) return -4; //类型错误
offse = 8;
}
else if(type==3)
{
address = HLine.mid(2,8).toInt(&ok,16);
if(ok==false) return -4; //类型错误
offse = 10;
}
//取加法校验
int checkNum = HLine.mid(lineLen-2,2).toInt(&ok,16);
if(ok==false) return -4; //类型错误
//HEX文件校验和的计算方式是将除了开头的“:”和最后两位的校验和去掉后的其他数相加
if (checkSum(data,HLine,lineLen-2,offse, checkNum))
{
//add
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();//
}
else
{
return -7;//校验错误
}
}
break;
case 7: //
case 8: //
case 9: //
TailLineStr = QString(HLine);
//qDebug()<<"TailLineStr"<<TailLineStr;
break;
}
}
}
aFile.close();
//升序排序
devListSort(recordLines);
/*for(int i = 0; i <recordLines.length(); i++)
{
qDebug()<< i << recordLines.at(i).address;
}
*/
//把文件中每一行的数据组合成若干个地址开头的整块数据,放在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 S19Firmware::devListSort(QList<RecordLine > &list)
{
qSort(list.begin(), list.end(), compare);
}
bool S19Firmware::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;
if ((0xff - lowSum) == checkNum)
{
return true;
}
else
{
return false;
}
}
获得数据后就可以通过CAN传输来给设备升级!!!
1:代码通过一行一行的解析数据,并对数据检验核实。
2:通过地址排序行数据。理论是不需要的,生成是按照地址顺序生成。
3:把连续地址合并成大块。好处在于升级可以按自定义数据长度传输如ISO-15765 最大一帧可以传输4k数据