mot文件介绍
mot s19 bin hex都可以用于固件文件的存储,其中bin文件就是固件原始数据,只记录固件的二进制数据流,mot文件实际上就是Motorola S-records文件,是摩托罗拉公司定义的一种S开头的数据记录文件格式。mot文件不需要记录固件中每一个地址的数据,它可以按照地址和数据来记录固件信息,固件中没有使用的地址将不会记录,这样也使得mot文件相对于bin文件,体积会小很多。jflash是支持各种固件的文件,我们用就flash打开.mot文件,就可以看到真实的固件信息,包括固件的起始地址,真实数据,结束地址等等。
我们可以用文本工具打开mot文件,可以看出它是以字符的方式进行数据的记录的
可以看到文件都是以S开头的,而且是每一行作为一个单位长度。每一行数据都包含 数据类型 数据长度 地址域数据 数据域数据 CRC这5个部分。
每一行开头的S0 S3 S7为头部信息,下面用一个表格来解释每一行数据的意义:
数据类型 | 数据长度 | 地址域数据 | 数据域数据 | CRC校验 |
---|---|---|---|---|
S0:标题信息 S1:地址域为2字节,数据域为要写入的数据 S2:地址域为3字节,数据域为要写入的数据 S3:地址域为4字节,数据域为要写入的数据 S5:地址域做2字节整形值,代表之前传输的S1,S2,S3记录的总数,无数据域数据 S7:地址域为4字节,代表代码起始运行地址,无数据域数据 S8:地址域为3字节,代表代码起始运行地址,无数据域数据 S9:地址域为2字节,代表代码起始运行地址,无数据域数据 | 2个字符,占1个字节,代表地址域数据到CRC的长度 | 数据长度由数据类型决定 | 数据长度由数据类型和实际数据大小决定 | 占1个字节,数据长度,地址域数据,数据域数据中对应的所有字节值相加后按位取反得到的值 |
我们截取几行数据来进行分析下
S0030000FC S0标题信息
S30D00000000015A015A0000813C7F
数据类型为S3
数据长度0X0D
地址0X0000
该地址的数据01 5A 01 5A 00 00 81 3C
CRC校验 7F
我们用jflash打开该mot文件 发现跟我们解析的是一致的
注意:mot文件并不会从程序的起始地址一直记录到程序的结束地址,而是只记录有效数据,无效数据(全是0XFF的数据)是不会被记录的,我们在转换成bin的时候需要注意,如果传输整个固件的话,mot没有记录的地址我们需要按照0XFF传输并且烧写,如果单片机支持分地址烧写,那么只需要解析地址和数据然后烧写即可。
开始解析
首先需要几个string转int的函数
/**
* @brief 单字节hex字符转数字
* @param hex hex字符
* @return int
* - -1:失败
* - >=0:成功
*/
static int CharToInt(char hex)
{
if (hex>='0' && hex <='9')
return hex - '0';
if (hex>='A' && hex <= 'F')
return hex-'A'+10;
if(hex>='a' && hex <= 'f')
return hex-'a'+10;
return -1;
}
/**
* @brief 两字节hex字符串转数字
* @param hex 两字节hex字符串
* @return int
* - >=0:成功
* - <0:失败
*/
static int StringToInt(const char *hex)
{
int high = CharToInt(hex[0]);
int low = CharToInt(hex[1]);
if(high != -1 && low != -1)
return high*0x10 + low;
else
{
printf("---StringToInt error!! %s\n",hex);
return -1;
}
}
/**
* @brief 指定长度hex字符串转数字
* @param hex hex字符串,除去空格后字符串长度必须是偶数
* byte_array 存放转换后数据的数组
* len 需要转换的字节数,这个参数是实际转换后数组的长度,必须确保小于hex数组长度的一半
* -1表示整个hex字符串都转换
* @return int
* - >=0:成功,返回值代表转换后数组的长度
* - <0:失败
*/
static int StringToByteArray(char *hex,uint8_t *byte_array,int len)
{
if(hex==NULL || byte_array==NULL)
return -1;
/* 删除所有空格 */
char *str_hex = hex;
int i,j=0;
int high=0,low=0;
int byte = 0;
for(i=0;hex[i]!='\0';i++)
{
if(CharToInt(hex[i])>=0)
str_hex[j++]=hex[i];
}
str_hex[j]='\0';
if(len == 0)
return 0;
else if(len > 0)
{
for(i=0,j=0;i<len;i++)
{
if((str_hex[i*2]!='\0'&&str_hex[i*2+1]!='\0'))
{
byte = StringToInt(&str_hex[i*2]);
if(byte != -1 )
{
byte_array[j++] = (uint8_t)byte;
}
else
{
return -1;
}
}
else
{
break;
}
}
}
else if(len < 0)
{
for(i=0,j=0;(str_hex[i]!='\0'&&str_hex[i+1]!='\0');i+=2)
{
byte = StringToInt(&str_hex[i]);
if(byte != -1 )
{
byte_array[j++] = (uint8_t)byte;
}
else {
return -1;
}
}
}
return j;
}
CRC校验函数
我们的校验函数做了十六进制字符串到二进制数组的转换 后面直接使用数组即可
//校验MOT文件的单行数据
//校验MOT文件的单行数据
//line_file 单行文件原始数据
//byte_array 十六进制字符转数组后的存储地址
//len 数组的长度
int mot_line_file_check_crc(uint8_t *line_file,uint8_t *byte_array,int len)
{
if(line_file == NULL)
return -1;
uint8_t buf[256] = {0};
uint8_t data_buf_len = 0;
/* 转换成数组 最前面2个字符的S开头的头部信息,不做转换 */
if((data_buf_len=StringToByteArray((char*)line_file+2,buf,sizeof(buf)))<0)
return -1;
/* 复制转换后的数组 */
if(byte_array != NULL)
{
memcpy(byte_array,buf,(len>data_buf_len?data_buf_len:len));
}
/* 校验CRC */
uint8_t add_sum = 0;
int i=0;
for(i=0;i<data_buf_len-1;i++)
{
add_sum += buf[i];
}
add_sum = ~add_sum;
if(add_sum == buf[data_buf_len-1])
{
/* 检查数据长度 byte数组的第一个字节为剩余数据长度 */
//qDebug("data_buf_len:%d buf[0]:%d",data_buf_len,buf[0]);
if((data_buf_len-buf[0]) == 1)
return data_buf_len; //校验成功返回数组长度(一行数据除了2个字符头部后的byte数组长度)
else
return -1; //如果数据长度错误也返回错误
}
else
return -1;
}
有了这些转换函数,我们才能更方便地将字符串表示地十六进制数转换成二进制数据。
然后我们需要实现文件的读取,这个过程需要根据实际的语言或者软件来实现,只要能将文件读取到内存即可。
我们需要一些结构体变量来记录单行文件和整个文件的信息
#define MOT_LINE_DATA_MAX_LEN 256
/* MOT文件控制变量 */
typedef struct{
uint8_t type; //数据类型
uint8_t length; //数据长度
uint8_t addr_byte_length; //数据域长度1-4
uint8_t data_addr[4]; //地址域数据
uint32_t addr; //地址域数据的变量值
uint8_t data[MOT_LINE_DATA_MAX_LEN]; //地址域数据
uint8_t data_byte_length; //固件数据的长度 = 数据长度-(地址域数据占用的字节1-4)-(CRC占用的1字节)
uint8_t crc; //CRC
}MOT_LINE_FILE_T; //mot单行文件
typedef struct{
uint32_t start_addr; //起始地址
uint32_t end_addr; //结束地址
uint32_t program_addr; //编程地址
uint32_t firmware_length; //固件长度(结束地址-起始地址)
uint32_t program_memory_size; //编程内存大小(结束地址-编程地址)
}MOT_FILE_T; //整个MOT文件的解析
然后就是按行读取文件,然后按行解析文件,将解析出来的固件数据填充到转换后的bin文件,下面展示用C++ QT写的文件解析代码
void MainWindow::on_pushButton_openfile_clicked()
{
QString path = "D:/";
QString fileName = QFileDialog::getOpenFileName(this,("打开固件文件"),path,tr("mot文件(*.mot)"));
ui->lineEdit_file_path->setText(fileName);
if(fileName.isEmpty())
return;
QFile file(fileName);
QFileInfo fileinfo(fileName);
if(!file.open(QIODevice::ReadOnly ))
{
QMessageBox::warning(this,"error","打开文件失败",QMessageBox::Ok);
app_info("打开文件失败");
return;
}
MOT_LINE_FILE_T mot_line_file; //单行文件的控制变量
memset(&mot_file_control,0xff,sizeof(mot_file_control)); //将整个mot文件的控制变量清除为ff
uint32_t current_addr = 0; //从一行数据里面解析出来的地址加上实际地址后的偏移
uint32_t new_line_addr_offset = 0; //最新解析一行的数据的地址跟上一行地址的偏移
QByteArray file_line_bytearray; //单行文件的bytearray
uint32_t line_index = 0; //行号
int i = 0;
while(!(file_line_bytearray = file.readLine()).isEmpty()) //每次读取一行数据直到文件结束
{
mot_line_file_prase((uint8_t*)file_line_bytearray.data(),
file_line_bytearray.length(),&mot_line_file); //单行文件解析函数
if(mot_line_file.type>=1 && mot_line_file.type<=3) //只解析S123的
{
if(mot_file_control.start_addr == 0xffffffff) //如果mot文件控制变量第一次使用 就记录固件文件的起始地址
{
mot_file_control.start_addr = mot_line_file.addr; //起始地址
mot_file_control.end_addr = mot_line_file.addr; //结束地址 结束地址在每行解析后都会重新赋值
}
current_addr = mot_line_file.addr + mot_line_file.data_byte_length; //一行文件的起始地址加上数据长度,得到偏移后地址
new_line_addr_offset = current_addr-mot_file_control.end_addr;
if(new_line_addr_offset == mot_line_file.data_byte_length) //判断文件是否是连续地址
{
/* 文件是连续的 直接复制 */
for(i=0;i<mot_line_file.data_byte_length;i++)
{
firmware_array.append(mot_line_file.data[i]);
}
}
else
{
/* 文件不是连续的 在中间追加0xff */
for(i=0;i<(new_line_addr_offset-mot_line_file.data_byte_length);i++)
{
firmware_array.append(0xff);
}
/* 填充固件数据 */
for(i=0;i<mot_line_file.data_byte_length;i++)
{
firmware_array.append(mot_line_file.data[i]);
}
}
mot_file_control.end_addr = current_addr; //记录固件文件的结束地址
}
}
mot_file_control.firmware_length = mot_file_control.end_addr - mot_file_control.start_addr; //固件的长度=结束地址-起始地址
mot_file_control.program_addr = 0x8000; //固件的编程地址 在解析的时候用不到 根据协议拟定
mot_file_control.program_memory_size = mot_file_control.end_addr - mot_file_control.program_addr; //需要编程的固件长度=结束地址-编程地址
/* 将bin文件保存 */
QFile bin_file(fileinfo.path()+"/conversionQt.bin");
bin_file.open(QIODevice::ReadWrite);
bin_file.write(firmware_array);
bin_file.close();
qDebug()<<"写入完成";
file.close();
}
//mot单行文件解析
int MainWindow::mot_line_file_prase(uint8_t *data, uint16_t len, MOT_LINE_FILE_T *t)
{
if(data == NULL)
return 0;
if(t == NULL)
return 0;
uint8_t data_buffer[256] = {0}; //存储一行数据除了头部两个字符以外转换后的byte数据
uint16_t data_len = 0; //一行数据的byte数组的长度
data_len = mot_line_file_check_crc(data,data_buffer,sizeof(data_buffer)); //先校验CRC
if(data_len<=0)
return 0;
int temp = 0;
temp = CharToInt(data[1]); //数据类型
if(temp < 0)
return -1;
t->type = (uint8_t)temp; //数据类型
t->length = data_buffer[0]; //单行文件数据类型
t->data_byte_length = 0; //单行文件数据长度
/* 根据类型进行解析 */
switch (t->type) {
case 0:qDebug("头部信息:%s",data);
break;
case 1:
case 2:
case 3:{
t->addr_byte_length = 1 + t->type; // 数据地址域长度
memcpy(t->data_addr,&data_buffer[1],t->addr_byte_length); //单行文件的数据地址
for(int i=0;i<t->addr_byte_length;i++) //数据地址的数组形式转成uint格式
{
t->addr <<= 8;
t->addr |= t->data_addr[i];
}
t->data_byte_length = t->length-t->addr_byte_length-1; //单行文件的数据域长度
memcpy(t->data,&data_buffer[1+t->addr_byte_length],t->data_byte_length); //复制固件数据
}break;
case 7:
case 8:
case 9:{ //程序运行地址
t->addr_byte_length = 11 - t->type; // 数据地址域长度
memcpy(t->data_addr,&data_buffer[1],t->addr_byte_length);
for(int i=0;i<t->addr_byte_length;i++)
{
t->addr <<= 8;
t->addr |= t->data_addr[i];
}
t->data_byte_length = 0;
qDebug("程序运行地址:%08X",t->addr);
}break;
default:break;
}
return 0;
}
需要注意的是,要按单行解析,校验CRC,解析出固件的 地址 数据等信息,解析后判断固件是否连续,如果不连续,需要在中间空闲的空间增加0XFF的填充,解析后用jflash转换后的bin文件进行比对。