目錄
前言:很多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文件是否完成一致,我試過是一樣的。