ATmega128具備引導加載支持的用戶程序自編程功能(In-System Programming by On-chipBoot Program),它提供了一個真正的由MCU本身自動下載和更新(采用讀/寫同時"Read-While-Write"進行的方式)程序代碼的系統程序 自編程更新的機制。利用AVR的這個功能,可以實現在應用編程(IAP)以及實現系統程序的遠程自動更新的應用。
IAP的本質就是,MCU可以靈活地運行一個常駐Flash的引導加載程序(Boot Loader Program),實現對用戶應用程序的在線自編程更新。引導加載程序的設計可以使用任何的可用的數據接口和相關的協議讀取代碼,或者從程序存儲器中讀取 代碼,然后將代碼寫入(編程)到Flash存儲器中。
引導加載程序有能力讀寫整個Flash存儲器,包括引導加載程序所在的引導加載區本身。引導加載程序還可以對自身進行更新修改,甚至可以將自身刪除,使系 統的自編程能力消失。引導加載程序區的大小可以由芯片的熔絲位設置,該段程序區還提供兩組鎖定位,以便用戶選擇對該段程序區的不同級別的保護。
本節將給出一個實際的的Boot Loader程序,它可以配合Windows中的超級終端程序,采用Xmodem傳輸協議,通過RS232接口下載更新用戶的應用程序。
5.2.1 基本設計思想
1. Boot Loader程序的設計要點
Boot Loader程序的設計是實現IAP的關鍵,它必須能過通過一個通信接口,采用某種協議正確的接收數據,再將完整的數據寫入到用戶程序區中。本例Boot Loader程序的設計要點有:
(1)采用ATmega128的USART口實現與PC之間的簡易RS232三線通信;
(2) 采用Xmodem通信協議完成與PC機之間的數據交換;
(3)用戶程序更新完成后自動轉入用戶程序執行;
(4) Boot Loader程序采用C語言內嵌AVR匯編方式編寫,閱讀理解方便,可移植性強,代碼小于1K字。
2. Xmodem通信協議
Xmodem協議是一種使用撥號調制解調器的個人計算機通信中廣泛使用的異步文件運輸協議。這種協議以128字節塊的形式傳輸數據,并且每個塊都使用一個 校驗和過程來進行錯誤檢測。如果接收方關于一個塊的校驗和與它在發送方的校驗和相同時,接收方就向發送方發送一個認可字節。為了便于讀者閱讀程序,下面簡 要說明該協議的主要特點,有關Xmoden的完整的協議請參考其它相關的資料。
(1) Xmodem的控制字符: 01H、 04H、 06H、 15H、 18H、 1AH。
(2) Xmodem傳輸數據塊格式:"
個字節的數據塊...> "。其中為起始字節;
為數據塊編號字節,每次加一;是前一字節的反碼;接下來是長度為128字節的數據塊;最后的是128字節數據的CRC校驗碼,長度為2個字節。
(3)接收端收到一個數據塊并校驗正確時,回送;接收錯誤回送;而回送表示要發送端停止發送。
(4) 發送端收到后,可繼續發送下一個數據塊(packNO+1);而收到則可再次重發上一個數據塊。
(5)發送端發送表示全部數據發送完成。如果最后需要發送的數據不足128個字節,用填滿一個數據塊。
(6) 控制字符"C"有特殊的作用,當發送端收到"C"控制字符時,它回重新開始以CRC校驗方式發送數據塊(packNO = 1)。
(7) 每發送一個新的數據塊
加1,加到OxFF后下一個數據塊的
為零。
(8) 校驗方式采用16位CRC校驗(X^16 + X^12 + X^5 + 1)。
5.2.2 源程序代碼
下面給出的源程序是在ICCAVR中實現的。
/*****************************************************
采用串行接口實現Boot_load應用的實例
華東師大電子系 馬 潮 2004.07
Compiler: ICC-AVR 6.31
Target: Mega128
Crystal: 16Mhz
Used: T/C0,USART0
*****************************************************/
#include
#define SPM_PAGESIZE 256 //M128的一個Flash頁為256字節(128字)
#define BAUD 38400 //波特率采用38400bps
#define CRYSTAL 16000000 //系統時鐘16MHz
//計算和定義M128的波特率設置參數
#define BAUD_SETTING (unsigned char)((unsigned long)CRYSTAL/(16*(unsigned long)BAUD)-1)
#define BAUD_H (unsigned char)(BAUD_SETTING>>8)
#define BAUD_L (unsigned char)BAUD_SETTING
#define DATA_BUFFER_SIZE SPM_PAGESIZE //定義接收緩沖區長度
//定義Xmoden控制字符
#define XMODEM_NUL 0x00
#define XMODEM_SOH 0x01
#define XMODEM_STX 0x02
#define XMODEM_EOT 0x04
#define XMODEM_ACK 0x06
#define XMODEM_NAK 0x15
#define XMODEM_CAN 0x18
#define XMODEM_EOF 0x1A
#define XMODEM_RECIEVING_WAIT_CHAR 'C'
//定義全局變量
const char startupString[]="Type 'd' download, Others run app.\n\r\0";
char data[DATA_BUFFER_SIZE];
long address = 0;
//擦除(code=0x03)和寫入(code=0x05)一個Flash頁
void boot_page_ew(long p_address,char code)
{
asm("mov r30,r16\n"
"mov r31,r17\n"
"out 0x3b,r18\n"); //將頁地址放入Z寄存器和RAMPZ的Bit0中
SPMCSR = code; //寄存器SPMCSR中為操作碼
asm("spm\n"); //對指定Flash頁進行操作
}
//填充Flash緩沖頁中的一個字
void boot_page_fill(unsigned int address,int data)
{
asm("mov r30,r16\n"
"mov r31,r17\n" //Z寄存器中為填沖頁內地址
"mov r0,r18\n"
"mov r1,r19\n"); //R0R1中為一個指令字
SPMCSR = 0x01;
asm("spm\n");
}
//等待一個Flash頁的寫完成
void wait_page_rw_ok(void)
{
while(SPMCSR & 0x40)
{
while(SPMCSR & 0x01);
SPMCSR = 0x11;
asm("spm\n");
}
}
//更新一個Flash頁的完整處理
void write_one_page(void)
{
int i;
boot_page_ew(address,0x03); //擦除一個Flash頁
wait_page_rw_ok(); //等待擦除完成
for(i=0;i//將數據填入Flash緩沖頁中
{
boot_page_fill(i, data+(data[i+1]//將緩沖頁數據寫入一個Flash頁
wait_page_rw_ok(); //等待寫入完成
}
//從RS232發送一個字節
void uart_putchar(char c)
{
while(!(UCSR0A & 0x20));
UDR0 = c;
}
//從RS232接收一個字節
int uart_getchar(void)
{
unsigned char status,res;
if(!(UCSR0A & 0x80)) return -1; //no data to be received
status = UCSR0A;
res = UDR0;
if (status & 0x1c) return -1; // If error, return -1
return res;
}
//等待從RS232接收一個有效的字節
char uart_waitchar(void)
{
int c;
while((c=uart_getchar())==-1);
return (char)c;
}
//計算CRC
int calcrc(char *ptr, int count)
{
int crc = 0;
char i;
while (--count >= 0)
{
crc = crc ^ (int) *ptr++ //退出Bootloader程序,從0x0000處執行應用程序
void quit(void)
{
uart_putchar('O');uart_putchar('K');
uart_putchar(0x0d);uart_putchar(0x0a);
while(!(UCSR0A & 0x20)); //等待結束提示信息回送完成
MCUCR = 0x01;
MCUCR = 0x00; //將中斷向量表遷移到應用程序區頭部
RAMPZ = 0x00; //RAMPZ清零初始化
asm("jmp 0x0000\n"); //跳轉到Flash的0x0000處,執行用戶的應用程序
}
//主程序
void main(void)
{
int i = 0;
unsigned char timercount = 0;
unsigned char packNO = 1;
int bufferPoint = 0;
unsigned int crc;
//初始化M128的USART0
UBRR0H = BAUD_H;
UBRR0L = BAUD_L; //Set baud rate
UCSR0B = 0x18; //Enable Receiver and Transmitter
UCSR0C = 0x0E; //Set frame. format: 8data, 2stop bit
//初始化M128的T/C0,15ms自動重載
OCR0 = 0xEA;
TCCR0 = 0x0F;
//向PC機發送開始提示信息
while(startupString!='\0')
{
uart_putchar(startupString);
i++;
}
//3秒種等待PC下發"d",否則退出Bootloader程序,從0x0000處執行應用程序
while(1)
{
if(uart_getchar()== 'd') break;
if (TIFR & 0x02) //timer0 over flow
{
if (++timercount > 200) quit(); //200*15ms = 3s
TIFR = TIFR|0x02;
}
}
//每秒向PC機發送一個控制字符"C",等待控制字〈soh〉
while(uart_getchar()!=XMODEM_SOH) //receive the start of Xmodem
{
if(TIFR & 0x02) //timer0 over flow
{
if(++timercount > 67) //wait about 1 second
{
uart_putchar(XMODEM_RECIEVING_WAIT_CHAR); //send a "C"
timercount="0";
}
TIFR="TIFR" | 0x02;
}
}
//開始接收數據塊
do
{
if ((packNO == uart_waitchar()) && (packNO ==(~uart_waitchar())))
{ //核對數據塊編號正確
for(i=0;i//接收128個字節數據
{
data[bufferPoint]= uart_waitchar();
bufferPoint++;
}
crc = (uart_waitchar()//接收2個字節的CRC效驗字
if(calcrc(&data[bufferPoint-128],128)==crc) //CRC校驗驗證
{ //正確接收128個字節數據
while(bufferPoint >= SPM_PAGESIZE)
{ //正確接受256個字節的數據
write_one_page(); //收到256字節寫入一頁Flash中
address += SPM_PAGESIZE; //Flash頁加1
bufferPoint = 0;
}
uart_putchar(XMODEM_ACK); //正確收到一個數據塊
packNO++; //數據塊編號加1
}
else
{
uart_putchar(XMODEM_NAK); //要求重發數據塊
}
}
else
{
uart_putchar(XMODEM_NAK); //要求重發數據塊
}
}while(uart_waitchar()!=XMODEM_EOT); //循環接收,直到全部發完
uart_putchar(XMODEM_ACK); //通知PC機全部收到
if(bufferPoint) write_one_page(); //把剩余的數據寫入Flash中
quit(); //退出Bootloader程序,從0x0000處執行應用程序
}
程序的主體部分采用C高級編寫,結構性好,程序的相應部分都給出了比較詳細的注釋說明,讀者非常容易讀懂和理解。下面再對程序做進一步的說明。
(1) 函數"void write_one_page(void)" 實現了對ATmega128一個Flash頁的完整編程處理。當程序從串口正確接收到256個字節后,(ATmega128一個Flash頁為128個 字),便調用該函數將其寫入ATmega128一個Flash頁中。函數先將一個指定的Flash頁進行擦除;然后將數據填入Flash的緩沖頁中,最后 將Flash 緩沖頁的數據寫入到該指定的Flash頁中(詳細技術細節見第二章相關內容的介紹)。
(2) 一個Flash頁的擦除、寫入,以及填充Flash緩沖頁的函數采用內嵌AVR匯編完成,在ICCAVR中,寄存器R16、R17、R18、R19用于傳遞一個C函數的第1、2個參數(int類型)或第1個乘數(long類型),具體參考ICCAVR應用說明。
(3) 函數"void quit(void)"的用途是退出Bootloader程序,從Flash的0x0000處執行用戶的應用程序。在執行強行跳轉指令"jmp 0x0000"前,對寄存器MCUCR的操作是將中斷向量地址遷移回應用程序區的頭部,因為在ICCAVR環境中編譯Bootloader程序時,其自動 把中斷向量地址遷移到了Bootloader區的頭部。為了保證能正確執行用戶的程序,在跳轉前需要把中斷向量地址遷再移回應用程序區的頭部。
(4)在這段Bootloader程序中使用的硬件資源為T/C0和USART0,用戶在編寫其應用程序時,應首先對這兩個硬件資源相關的寄存器重新做初始化。
(5) Bootloader程序占具并住留在Flash的最高1K字空間內,因此實際的應用程序空間為63K字(126K字節),所以用戶編寫的應用程序不得超 出126K字節。同時應將ATmega128的熔絲位BLB12、BLB11的狀態設置為"00",禁止SPM和LPM指令對Bootloader區的讀 寫操作,已確保Bootloader程序不被改寫和擦除。
5.2.3 IAP的實現與應用
1. Bootloader程序的編譯與下載
首先在ICCAVR中新建一個工程項目,并按照生成Bootloader程序代碼的要求進行正確的設置。打開Project -> Options的Compiler Options設置選項窗口,見圖5.1:
(1) 在Device Configration欄中選定器件ATMega128;
(2) 選定Use RAMPZ/ELPM項(ATMega128的Flash > 64K字節);
(3) Program Type選定為Boot Loader;
(4)Boot Size選擇1K Words。
正確設置好編譯選項后輸入C的源代碼,然后編譯生成.HEX的下載代碼程序。
在下載HEX文件前還要對ATmega128芯片的熔絲位進行正確的配置:
(1) 配置M103C熔絲位,使芯片工作于ATmega128方式;
(2) 配置BOOTSZ1和BOOTSZ0熔絲位,設定BOOTLOADER區的大小為1024個字,起始首地址為0xFC00;
(3)配置BOOTRST熔絲位,設定芯片上電起動從BOOTLOADER區的起始地址處開始,即每次RESET復位后從0xFC00處執行Bootloader程序;
(4)下載Bootloader程序的HEX文件;
(5) 配置LB2和LB1熔絲位,加密程序;
(6)配置BLB12和BLB11熔絲位,對BOOTLOADER區進行安全鎖定。
特別注意的是,以上對芯片熔絲位的配置以及Bootloader程序的下載,需要由ISP、或JTAG、或并行方式實現,既要實現IAP,首先還需要使用一次非IAP的編程方式來建立IAP的應用環境。
2. IAP應用
當你按照上面的方法將Bootloader程序下載完成后,就可以使用它來下載你的應用程序了。具體操作如下。
(1) 編寫你的應用程序,編譯生成HEX文件;
(2)使用HEX2BIN.EXE轉換程序,將HEX文件轉換成BIN文件;
(3)使用普通的RS232電纜將PC機的串口與ATmega128的串口連接;
(4)打開WINDOWS中的超級終端軟件,正確設置COM口的參數:38400,1,8,無,2,無(使用2位停止位提高通信可靠性);
(5)ATmega128上電,在PC超級終端收到"Type 'd' download, Others run app."的Bootloader程序啟動的提示詳細;
(6)3秒鐘內在PC上按下"d"鍵,通知Bootloader程序轉入接收數據并更新應用程序的處理。3秒鐘內沒有按"d"鍵,PC超級終端收 到"OK"提示,Bootloader程序退出,自動轉入執行芯片內原有的用戶應用程序(如果有的話,否則再次啟動Bootloader程序);
(7)當PC超級終端收到"C"(一秒鐘一個),說明Bootloader程序轉入接收數據和更新應用程序的處理流程,正在等待PC下發數據;
(8)在PC超級終端上的工具欄中選擇"傳送->發送文件",在發送文件窗口選擇協議"Xmodem",文件欄中選定要下載應用程序的BIN文件,單擊發送按鈕;
(9) 此時出現文件發送窗口,顯示文件發送的過程和進度,以及是否出錯;
(10)當文件全部正確發送完成后,PC超級終端收到"OK"提示,Bootloader程序退出,自動轉入執行剛更新的用戶應用程序。
在ATmega128中燒入這樣一個Bootloader程序,建立了IAP后,最基本的開發AVR的環境就簡化成"PC+RS232電纜+目標板"。讀 者在掌握了Bootloader程序編寫的原理后,可以編寫自己的Bootloader程序,實現系統程序的自動遠程網絡更新等應用。
AVR的BOOTLOADER功能同其它一些芯片不同,它的BOOTLOADER程序沒有固化(固定)在芯片內部(出廠為空),而是需要由用戶設計實現 (實際上,你第一次下載BOOTLOADER程序還必須使用其它的方式編程,如ISP、JTAG等),因此對一般的用戶掌握起來有一定的困難,不如一些其 它芯片的固化IAP使用方便。但對高手來講,可以根據實際需要編寫高級、高效、專用的BOOTLOADER程序,如從一個U盤讀取數據,更新用戶的應用程 序;編寫一個時間炸彈,或對用戶的密碼進行驗證,10次不對則將系統程序銷毀等等。簡單意味著使用方便,但靈活和適應性差,而靈活性需要你具備更高的能力 去駕馭它。可能會有一天,在單片機的系統上也出現了"病毒"程序,其原因就是使用了固化的BOOTLOADER程序。由于固化(固定)的程序采用規定公開 (開放)的接口,那么用一個帶"病毒"的應用程序更新原來的應用程序也就輕而易舉了。
|