擊上方「單片機」,選擇「置頂/星標公眾號」
乾貨下載:添加微信好友「5834434」(必須備註「單片機:學校/公司+研究方向」,否則無法通過驗證),回復「單片機資料」,獲取單片機教程及其他學習資料!
單片機程序寫好之後,我們都要把程序下載到單片機的內存中,單片機才會按照程式設計師的邏輯執行命令實現功能。之前也講過下載單片機的幾種方式,比如ISP下載,JTAG下載,下載文件的格式最常見的是hex文件,這個格式大家都是知道的,還有一種bin文件是單片機的下載文件。下面介紹這兩種格式的區別。
選擇單片機型號
選擇串口號
設置波特率(或者默認)
選擇下載的文件
點擊下載按鈕下載
在串口工具中,操作如下圖紅框所示。
經過這幾步後,程序下載工作就完成了,在以上的步驟中我們並沒有選擇要把程序下載到單片機的哪塊內存中,即不需要設置地址。因為HEX文件內部的信息已經包括了地址,相關文章推薦:在SMT32的HEX文件裡加入固件版本信息。
單片機一般是下載hex文件。
BIN
BIN文件格式只包括了數據本身,沒有包含地址。燒寫BIN文件的時候,用戶是一定需要指定地址信息的。
所以在下載bin文件時需要選擇內存的起始地址和終止地址,即要把bin文件下載到指定的內存空間,相關文章移步此處:STM32單片機中Hex、Bin文件的區別與應用。
通常需要指定程序內存地址的晶片為ARM晶片和DSP晶片。
文件大小而對HEX文件而言,你看到的文件大小並不是實際的數據的大小。一是因為HEX文件是用ASCII來表示數據,二是因為HEX文件本身還包括別的附加信息。
(前方高壓預警!)
Hex和BIN數據格式詳解
Intel HEX文件是記錄文本行的ASCII文本文件,在Intel HEX文件中,每一行是一個HEX記錄,由十六進位數組成的機器碼或者數據常量。Intel HEX文件經常被用於將程序或數據傳輸存儲到ROM、EPROM,大多數編程器和模擬器使用Intel HEX文件。
很多編譯器的支持生成HEX格式的燒錄文件,尤其是Keil c。但是編程器能夠下載的往往是BIN格式,因此HEX轉BIN是每個編程器都必須支持的功能。HEX格式文件以行為單位,每行由「:」(0x3a)開始,以回車鍵結束(0x0d,0x0a)。行內的數據都是由兩個字符表示一個16進位字節,比如」01」就表示數0x01;」0a」,就表示0x0a。對於16位的地址,則高位在前低位在後,比如地址0x010a,在HEX格式文件中就表示為字符串」010a」。
下面為HEX文件中的一行:
:10000000FF0462FF051EFF0A93FF0572FF0A93FFBC
「:」表示一行的開始。 「:」後的第1,2個字符「10」表示本行包含的數據的長度,這裡就是0x10即16個。 第3,4,5,6個字符「0000」表示數據存儲的起始地址,這裡表示從0x0000地址開始存儲16個數據,其中高位地址在前,低位地址在後。 第7,8個字符「00」表示數據的類型。該類型總共有以下幾種: 00 ----數據記錄 01 ----文件結束記錄 02 ----擴展段地址記錄 04 ----擴展線性地址記錄
這裡就是0x00即為普通數據記錄。自後的32個字符就是本行包含的數據,每兩個字符表示一個字節數據,總共有16個字節數據跟行首的記錄的長度相一致。最後兩個字符表示校驗碼。每個HEX格式的最後一行都是固定為:
:00000001FF
以上的信息其實就足夠進行HEX轉BIN格式的程序的編寫。首先我們只處理數據類型為0x00及0x01的情況。0x02表示對應的存儲地址超過了64K,由於我的編程器只針對64K以下的單片機,因此在次不處理,0x04也是如此。
記錄格式
一個Intel HEX文件可以包含任意多的十六進位記錄,每條記錄有五個域,下面是一個記錄的格式:
:llaaaatt[dd...]cc
每一組字母是獨立的一域,每一個字母是一個十六進位數字,每一域至少由兩個十六進位數字組成,下面是字節的描述.
:冒號 是每一條Intel HEX記錄的開始
ll 是這條記錄的長度域,他表示數據(dd)的字節數目。
aaaa 是地址域,他表示數據的起始地址<如果是數據記錄,這表示將要燒錄的這條記錄中的數據在eprom中的偏移地址,對於不支持擴展段地址和擴展線性地址的,如89c51,這就是此條記錄的起始地址>
tt 這個域表示這條HEX記錄的類型,他有可能是下面這幾種類型 00 ----數據記錄 01 ----文件結束記錄 02 ----擴展段地址記錄 04 ----擴展線性地址記錄
dd 是數據域,表示一個字節的數據,一個記錄可能有多個數據字節,字節數目可以查看ll域的說明
cc 是效驗和域,表示記錄的效驗和,計算方法是將本條記錄冒號開始的所有字母對<不包括本效驗字和冒號> 所表示的十六進位數字<一對字母表示一個十六進位數,這樣的一個十六進位數為一個字節>都加起來然後模除256得到的餘數,最後求出餘數的補碼,即是本效驗字節cc。
<例如: :0300000002005E9D cc=0x01+NOT((0x03+0x00+0x00+0x00+0x02+0x00+0x5E)%0x100)=0x01+0x9C=0x9D
C語言描述: UCHAR cc; cc=(UCHAR)~(0x03+0x00+0x00+0x00+0x02+0x00+0x5E); cc++; >
數據記錄
Intel HEX文件由若干個數據記錄組成,一個數據記錄以一個回車和一個換行結束<回車為0x0d換行為0x0a> 比如下面的一條數據記錄 :10246200464C5549442050524F46494C4500464C33 10 是此行記錄數據的字節數目 2462 是數據在內存<將要燒寫的eprom地址>中的起始地址 00 是記錄類型00(是一個數據記錄) 464C 到 464C 是數據 33 是此行記錄的效驗和
擴展線性地址記錄(HEX386) 擴展線性地址記錄也可稱為32位地址記錄/HEX386記錄,這個紀錄包含高16(16-31位)位數據地址,這種擴展的線性記錄總是有兩個字節數據,像下面這樣: :02000004FFFFFC 02 是記錄的數據字節數目 0000 是地址域,這在擴展地址記錄中總是0000 04 是記錄類型04(擴展地址記錄) FFFF 是高16位地址 FC 是記錄效驗和,計算方法如下: 01h + NOT(02h + 00h + 00h + 04h + FFh + FFh) 當一個擴展線性地址記錄被讀到後,擴展線性地址記錄的數據區域將被保存,並應用到後面從Intel HEX文件中讀出的記錄,這個擴展線性記錄一直有效,直到讀到下一個擴展線性記錄。 絕對內存地址 = 數據記錄中的地址 + 移位後的擴展線性地址 下面舉例說明這個過程:從數據記錄的地址域得到地址 2462,從擴展線性地址記錄的地址域得到地址 FFFF,絕對內存地址 FFFF2462
擴展段地址記錄 (HEX86)
擴展段地址記錄也被稱為HEX86記錄,包含 4-19位的數據地址段,這個擴展段地址記錄總是有兩字節數據,如下: :020000021200EA 02 是 記錄中的數據字節數目 0000 是地址域,在擴展段地址記錄中,這個域總是0000 02 是記錄類型,02(擴展段地址的標示) 1200 是該段的地址 EA 是效驗和 計算如下: 01h + NOT(02h + 00h + 00h + 02h + 12h + 00h). 當擴展段地址記錄被讀後,擴展段地址將被存儲並應用到以後從Intel HEX文件讀出的記錄,這個段地址一直有效直到讀到下一個擴展段地址記錄 絕對內存地址 = 數據記錄中的地址 + 移位後的擴展段地址 數據記錄中的地址域,移位後擴展段地址記錄中的地址域。 下面舉例說明這個過程:從數據記錄的地址域得到地址 2 4 6 2,從擴展段地址記錄的地址域得到地址 1 2 0 0,絕對內存地址 0 0 0 1 4 4 6 2
文件結束記錄(EOF) 一個Intel HEX文件必須有一個文件結束記錄,這個記錄的類型域必須是01, 一個EOF記錄總是這樣: :00000001FF 00是記錄中數據字節的數目 0000這個地址對於EOF記錄來說無任何意義 01記錄類型是01(文件結束記錄標示) FF是效驗和計算如下:01h + NOT(00h + 00h + 00h + 01h).
格式:BBAAAATTHHHH...HHHHCC
BB: Byte AAAA:數據記錄的開始地址,高位在前,地位在後。因為這個格式只支持8bits,地址被倍乘。所以,為了得到實際的PIC的地址,需要將地址除以2 TT: Type 00 數據記錄 01 記錄結束 04 擴展地址記錄(表示32位地址的前綴,當然這種只能在 INHX32) HHHH:一個字(Word)的數據記錄,高Byte在前,低Byte在後。TT之後,總共有 BB/2 個字 的數據 CC: 一個Byte的CheckSum
因為PIC16F873A只有4K的程序空間,所以,不會有 TT=04的 Linear Address Record
hex和bin文件格式 Hex文件,這裡指的是Intel標準的十六進位文件,也就是機器代碼的十六進位形式,並且是用一定文件格式的ASCII碼來表示。具體格式介紹如下: Intel hex 文件常用來保存單片機或其他處理器的目標程序代碼。它保存物理程序存儲區中的目標代碼映象。一般的編程器都支持這種格式。
Intel hex 文件全部由可列印的ASCII字符組成,如下例所示:
:2000000012014c75a800e4f508f509780a7a78e4f608dafcd283fcfded240af9a7050dbd81 :2000200000010ced2488ec34ff50edc283e4fcfded240af9e76d7013ed33e43c700d0dbd2a :2000400000010ced2488ec34ff50e50509e50970020508e50924a8e50834fd50aee4f50874
Intel hex 由一條或多條記錄組成,每條記錄都由一個冒號「:」打頭,其格式如下:
:CCAAAARR...ZZ
其中: CC 本條記錄中的數據字節數
AAAA 本條記錄中的數據在存儲區中的起始地址
RR 記錄類型: 00 數據記錄 (data record) 01 結束記錄 (end record) 02 段記錄 (paragraph record) 03 轉移地址記錄 (transfer address record)
... 數據域
ZZ 數據域校驗和
校驗值:每一行的最後一個值為此行數據的校驗和。例如:
:1000000018F09FE518F09FE518F09FE518F09FE5C0 這行中的 0xC0
:1000100018F09FE5805F20B9F0FF1FE518F09FE51D 這行中的 0x1D
校驗和的算法為:計算從0x3A 以後(不包括0x3A)的所有各字節的和模256的餘。即各字節二進位算術和,不計超過256的溢出值,然後用0x100減去這個算數累加和,得出得值就是此行得校驗和。
Intel hex文件記錄中的數字都是16進位格式,兩個16進位數字代表一個字節。CC域是數據域中的實際字節數,地址、記錄類型和校驗和域沒有計算在內。校驗和是取記錄中從數據字節計數域(CC)到數據域(...)最後一個字節的所有字節總和的2的補碼。
而Bin文件是最純粹的二進位機器代碼,沒有格式,或者說是"順序格式"。按assembly code順序翻譯成binary machine code。由於分析出來Hex文件中的數據域ASCII碼表示的十六進位與二進位一一對應,而且我公司DSP又是16位的,以一個word為最小單位,所以四個十六進位ASCII碼代表一條機器指令單位或者地址,借於上面分析,編寫了工具代碼。大體原理是用fscanf函數在每行的數據域讀入四個ASCII碼,以短整形(short int 16bit)形式儲存,在把這個短整形變量順序fwrite到文件流中去即可。
舉一例說明: 表1
ORG 0000H
LJMP START
ORG 040H
START:
MOV SP, #5FH ;設堆棧
LOOP:
NOP LJMP LOOP ;循環
END ;結束
表2
:03000000020040BB :0700400075815F000200431F
表3
3
表1為源程序,表2是彙編後得到的HEX文件,表3是由HEX文件轉換成的目標文件,也就是最終寫入EPROM的文件,它由編程器轉換得到,也可以由 HEXBIN一類的程序轉換得到。學過手工彙編者應當不難找出表3與表1的一一對應關係,值得注意的是從02 00 40後開始的一長串『FF』,直到75 81,這是由於偽指令:ORG 040H造成的結果。
/*使用方法 : bin2hex -b adress filename
-b : 指示hex文件起始地址
address : hex文件的起始地址(FIXME:當前版本只支持k字節邊界)
filename: 待轉換的文件名
示例 : bin2hex -b 32k rom.bin*/#include #include #include FILE *fp_read; /* 待讀取文件句柄 */FILE *fp_write; /* 待寫入文件句柄 */unsigned long start_adr; /* 轉換成Hex格式的起始地址 */unsigned short cur_base; /* 轉換成Hex格式的當前地址高16位 */unsigned short cur_offset; /* 轉換成Hex格式的當前地址低16位 */unsigned char read_buf[16];
unsigned char write_buf[48];void calc_start_adr (char *buf)
{
unsigned int len;
len = strlen(buf);
if ((buf[len-1] != 'k') && (buf[len-1] != 'K')) {
printf ("Invalid argument.\n");
exit (-1);
}
buf[len-1] = 0;
start_adr = atoi (buf);
start_adr = start_adr * 1024;
cur_base = start_adr >> 16;
cur_offset = (unsigned short)start_adr;
}void start_convert (void)
{
unsigned char cnt;
unsigned char read_num;
unsigned char cksum, highc, lowc;
/* 設置當前地址高16位 */
highc = cur_base >> 8;
lowc = (unsigned char)cur_base;
cksum = 2 + 4 + highc + lowc;
cksum = 0xFF - cksum;
cksum = cksum + 1;
sprintf (write_buf, ":02000004%04x%02x", cur_base, cksum);
write_buf[15] = 0x0D; write_buf[16] = 0x0A;
fwrite (write_buf, 1, 17, fp_write);
read_num = fread (read_buf, 1, 16, fp_read); while (read_num == 16) { /* 寫入讀取的16位元組 */
highc = cur_offset >> 8;
lowc = (unsigned char)cur_offset;
cksum = 0x10 + highc + lowc;
for (cnt=0; cnt<16; cnt++) {
cksum += read_buf[cnt];
}
cksum = 0xFF - cksum;
cksum = cksum + 1;
sprintf ( write_buf, ":10%02x%02x00%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
highc, lowc,
read_buf[0], read_buf[1], read_buf[2], read_buf[3],
read_buf[4], read_buf[5], read_buf[6], read_buf[7],
read_buf[8], read_buf[9], read_buf[10], read_buf[11],
read_buf[12], read_buf[13], read_buf[14], read_buf[15],
cksum);
write_buf[43] = 0x0D; write_buf[44] = 0x0A;
fwrite (write_buf, 1, 45, fp_write);
/* 計算當前地址低16位,當越限時寫入當前地址高16位 */ if (cur_offset == 65520) {
cur_offset = 0;
cur_base ++;
highc = cur_base >> 8;
lowc = (unsigned char)cur_base;
cksum = 2 + 4 + highc + lowc;
cksum = 0xFF - cksum;
cksum = cksum + 1;
sprintf (write_buf, ":02000004%04x%02x", cur_base, cksum);
write_buf[15] = 0x0D; write_buf[16] = 0x0A;
fwrite (write_buf, 1, 17, fp_write);
} else {
cur_offset += 16;
}
read_num = fread (read_buf,1,16,fp_read);
} /* 寫入剩餘的字節 */ if (read_num) {
highc = cur_offset >> 8;
lowc = (unsigned char)cur_offset;
cksum = read_num + highc + lowc; for (cnt=0; cnt<read_num; cnt++) {
cksum += read_buf[cnt];
}
cksum = 0xFF - cksum;
cksum = cksum + 1;
sprintf (write_buf, ":%02x%02x%02x00", read_num, highc, lowc); for (cnt=0; cnt<read_num; cnt++) {
sprintf (&write_buf[9 + cnt * 2], "%02x", read_buf[cnt]);
}
sprintf (&write_buf[9 + cnt * 2], "%02x", cksum);
write_buf[11 + read_num * 2] = 0x0D;
write_buf[12 + read_num * 2] = 0x0A;
fwrite (write_buf, 1, 13 + read_num * 2, fp_write);
} /* 寫入終止序列 */
sprintf (write_buf, ":00000001FF");
write_buf[11] = 0x0D; write_buf[12] = 0x0A;
fwrite (write_buf, 1, 13, fp_write);
}int main (int argc, char *argv[])
{
if (argc != 4) {
printf ("Usage : %s -b address filename.xxx\n", argv[0]);
printf ("-b : indicate the starting address convert to.\n");
printf ("address : starting address.\n");
printf ("filename.xxx : file to be converted.\n");
printf ("output : filename.hex\n");
printf ("example : %s -b 64k rom.bin\n", argv[0]); return -1;
} if (strcmp (argv[1], "-b")) {
printf ("Invalid argument.\n"); return -1;
};
fp_read = fopen (argv[3], "rb"); if (fp_read == NULL) {
printf ("Can't open file %s", argv[3]); return -1;
}
fp_write = fopen ("rom.hex", "w"); if (fp_write == NULL) {
printf ("Can't create file rom.hex"); return -1;
}
calc_start_adr (argv[2]);
start_convert ();
fclose (fp_read);
fclose (fp_write);
printf("Convert Seccessfully!\n");
return 0;
}
推薦好文 點擊標題即可跳轉
☞《單片機初學者必看》
☞《周立功寫給學單片機的年輕人的話》
☞《牛人談:軟體與硬體的入門難度與精通時間跨度》
☞《學習51單片機有感;學習用書推薦;必寫的幾個程序》
☞《幾種使用過的單片機比較》
☞《ARM+LINUX學習路線(學習順序,知識點及書籍推薦)》
☞《ARM/DSP/FPGA/CPLD/SOPC/SOC區別和聯繫》
☞《趣味電子製作:藝術家手中的食品發電-電子diy》
☞《我的經歷: 從一名產線工人到單片機工程師》
☞《硬體工程師要學習的東西》
☞《學習51單片機的感悟:一通則百通》
☞《各種傳感器工作原理動態圖,這也太全了吧!》
☞《小心!這幾項都符合說明你的手機已被監控》
☞《這小東西其實是個發電機?自己動手做個就不用怕停電了》
☞《國外的DIY網站和電子類的網站收集(各種奇怪高能項目)》
☞《電子工程師們都在用哪些APP?》
☞《一張圖看懂程式設計師的職業規劃》
☞《10個輕鬆上手製作的Arduino項目》
☞《手機在充電時到底是先插手機還先插電源?》
☞《學習模電的真諦》
☞《男生必學的撩妹搭訕的7個機械原理實驗》