AVR BootLoader應用范例

/***********************************************
**** AVR BootLoader應用范例 ***
**** ***
**** 作者: HJJourAVR ***
**** 編譯器:WINAVR20050214 ***
**** ***
****www.OurAVR.com2005.10.17 ***
***********************************************/
//程序參考 馬潮老師的M128 Boot_load應用的實(shí)例,ICCAVR版本
//Stephen更改:9600bps, MEGA16, 8M INTERNAL RC,BOOTSZ1=0, BOOTSZ0=0, BOOTRST=1
/*
本程序簡(jiǎn)單的示范了AVR ATMEGA16的IAP應用,實(shí)現智能升級
Boot Loader
XMODEM-CRC傳輸協(xié)議
CRC16校驗
出于簡(jiǎn)化程序考慮,各種數據沒(méi)有對外輸出,學(xué)習時(shí)建議使用JTAG ICE硬件仿真器。
熔絲位設置
BOOTSZ1=0
BOOTSZ0=0 Boot區為1K字(2K字節)大小。
BOOTRST=0 復位向量位于Boot區。//Stephen: 設BOOTRST=1,允許啟動(dòng)
makefile中的程序基地址偏移
LDFLAGS += -Wl,--section-start=.text=0x3800 //0x3800字節=0x1C00字
移植程序時(shí),可根據實(shí)際大小設定Boot區,但要注意更改makefile和更改BootAdd常數,以及頁(yè)寫(xiě)的大小分配;
采用115200bps的通訊速率,升級14KB程序需要耗時(shí)約5秒[上位機是WINDOWS 2000的超級終端]
疑問(wèn):
1 用HEX文件燒錄工作正常,但elf仿真有問(wèn)題。
用AVRstudio仿真elf(熔絲設定BOOTRST=0,程序基地址偏移=0x3800)時(shí),所有SRAM變量丟失初始化,
表現為put_s()的都是亂碼或不可見(jiàn)字符。
但如果改成應用程序(熔絲設定BOOTRST=1,沒(méi)有程序基地址偏移),則put_s()可以正常顯示
2 XMODEM的結束應答(EOT/CAN)后需加 delay_ms(500)的延時(shí)(程序優(yōu)化,統一寫(xiě)在跳轉到用戶(hù)程序前),
否則在下面的情況將會(huì )無(wú)法正常結束XMODEM的傳輸,但其實(shí)程序已經(jīng)升級成功
特殊情況:用戶(hù)程序里面使用了串口,而且波特率較低(如9600bps)且開(kāi)機即發(fā)送大量數據
*/
#include <avr/io.h>
#include
//時(shí)鐘定為外部晶體7.3728MHz,F_CPU=7372800 使用USART,115200bps
#include
/*
boot_page_erase ( address )
擦除FLASH 指定頁(yè),其中address 是以字節為單位的FLASH 地址
boot_page_fill ( address, data )
填充BootLoader 緩沖頁(yè),address 為以字節為單位的緩沖頁(yè)地址(對mega16 :0~128),
而data 是長(cháng)度為兩個(gè)字節的字數據,因此調用前address 的增量應為2。
此時(shí)data 的高字節寫(xiě)入到高地址,低字節寫(xiě)入到低地址。
boot_page_write ( address )
boot_page_write 執行一次的SPM 指令,將緩沖頁(yè)數據寫(xiě)入到FLASH 指定頁(yè)。
boot_rww_enable ( )
RWW 區讀使能
根據自編程的同時(shí)是否允許讀FLASH 存儲器,FLASH 存儲器可分為兩種類(lèi)型:
可同時(shí)讀寫(xiě)區( RWW Read-While-Write ) 和 非同時(shí)讀寫(xiě)區( NRWW NotRead-While-Write)。
對于MEGA16 RWW 為前14K 字節 NRWW 為后2K 字節。
引導加載程序對RWW 區編程時(shí)MCU 仍可以從NRWW 區讀取指令并執行,而對NRWW 區編程時(shí)MCU 處于掛起暫停狀態(tài)。
在對RWW 區自編程(頁(yè)寫(xiě)入或頁(yè)擦除)時(shí),由硬件鎖定RWW 區 , RWW 區的讀操作被禁止
在對RWW 區的編程結束后應當調用boot_rww_enable() 使RWW 區開(kāi)放。
*/
#include
/*
GCCAVR內置函數,可以不用頭痛CRC16了
關(guān)于CRC的詳細說(shuō)明,可以查看一下網(wǎng)站:
http://www.nongnu.org/avr-libc/user-manual/group__avr__crc.html
函數原形
static __inline__ uint16_t _crc16_update(uint16_t __crc, uint8_t __data);
多項式Polynomial: x^16 + x^15 + x^2 + 1 (0xa001)
crc初始值Initial value: 0xffff
通常用于磁盤(pán)控制器(disk-drive controllers)
static __inline__ uint16_t _crc_xmodem_update(uint16_t __crc, uint8_t __data);
多項式Polynomial: x^16 + x^12 + x^5 + 1 (0x1021)
crc初始值Initial value: 0x0
專(zhuān)用于XMODEM通訊協(xié)議,等效于C寫(xiě)的
uint16_t crc_xmodem_update (uint16_t crc, uint8_t data)
{
int i;
crc = crc ^ ((uint16_t)data << 8);
for (i=0; i<8; i++)
{
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021;
else
crc <<= 1;
}
return crc;
}
static __inline__ uint16_t _crc_ccitt_update (uint16_t __crc, uint8_t __data)
多項式Polynomial: x^16 + x^12 + x^5 + 1 (0x8408)
crc初始值Initial value: 0xffff
專(zhuān)用于PPP和IrDA通訊協(xié)議
*/
//管腳定義
#define PIN_RXD 0 //PD0
#define PIN_TXD 1 //PD1
//常數定義
#define SPM_PAGESIZE 128 //M16的一個(gè)Flash頁(yè)為128字節(64字)
#define DATA_BUFFER_SIZE SPM_PAGESIZE //定義接收緩沖區長(cháng)度
#define BAUDRATE 9600 //115200 //波特率采用115200bps
//#define F_CPU 7372800 //系統時(shí)鐘7.3728MHz
//定義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_WAIT_CHAR C
//定義全局變量
struct str_XMODEM
{
unsigned char SOH; //起始字節
unsigned char BlockNo; //數據塊編號
unsigned char nBlockNo; //數據塊編號反碼
unsigned char Xdata[128]; //數據128字節
unsigned char CRC16hi; //CRC16校驗數據高位
unsigned char CRC16lo; //CRC16校驗數據低位
}
strXMODEM; //XMODEM的接收數據結構
unsigned long FlashAddress; //FLASH地址
#define BootAdd 0x3800 //Boot區的首地址(應用區的最高地址)
/* GCC里面地址使用32位長(cháng)度,適應所有AVR的容量*/
unsigned char BlockCount; //數據塊累計(僅8位,無(wú)須考慮溢出)
unsigned char STATUS; //運行狀態(tài)
#define ST_WAIT_START 0x00 //等待啟動(dòng)
#define ST_BLOCK_OK 0x01 //接收一個(gè)數據塊成功
#define ST_BLOCK_FAIL 0x02 //接收一個(gè)數據塊失敗
#define ST_OK 0x03 //完成
//長(cháng)延時(shí) max 65536ms
void delay_ms(unsigned int t)
{
while(t--)
{
_delay_ms(1);
}
}
//更新一個(gè)Flash頁(yè)的完整處理
void write_one_page(void)
{
unsigned char i;
unsigned char *buf;
unsigned int w;
boot_page_erase(FlashAddress); //擦除一個(gè)Flash頁(yè)
boot_spm_busy_wait(); //等待頁(yè)擦除完成
buf=&strXMODEM.Xdata[0];
for(i=0;i
w =*buf++;
w+=(*buf++)<<8;
//boot_page_fill(FlashAddress+i, w); //原句
boot_page_fill(i, w); //只是低7位(128字節/頁(yè))有效
}
boot_page_write(FlashAddress); //將緩沖頁(yè)數據寫(xiě)入一個(gè)Flash頁(yè)
boot_spm_busy_wait(); //等待頁(yè)編程完成
}
//發(fā)送采用查詢(xún)方式
void put_c(unsigned char c) //發(fā)送采用查詢(xún)方式
{
loop_until_bit_is_set(UCSRA,UDRE);
UDR=c;
}
//發(fā)送字符串
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //結尾發(fā)送回車(chē)換行
}
//接收指定字節數據(帶超時(shí)控制,Timer0的1ms時(shí)基)
// *ptr 數據緩沖區
// len 數據長(cháng)度
// timeout 超時(shí)設定,最長(cháng)65.536S
// 返回值 已接收字節數目
unsigned char get_data(unsigned char *ptr,unsigned char len,unsigned int timeout)
{
unsigned count=0;
do
{
if (UCSRA & (1<
*ptr++=UDR; //如果接收到數據,讀出
count++;
if (count>=len)
{
break; //夠了?退出
}
}
if(TIFR & (1<
TIFR|=(1<
}
}
while (timeout);
return count;
}
//計算CRC16
unsigned int calcrc(unsigned char *ptr, unsigned char count)
{
unsigned int crc = 0;
while (count--)
{
crc =_crc_xmodem_update(crc,*ptr++);
}
return crc;
}
//主程序
//int main() __attribute__((section(".stephen_bootloader"))); //stephen_bootloader
int main()
{
unsigned char c;
unsigned char i;
unsigned int crc;
//考慮到BootLoader可能由應用程序中跳轉過(guò)來(lái),所以所用到的模塊需要全面初始化
DDRA=0x00;
DDRB=0x00;
DDRC=0x00;
PORTA=0xFF; //不用的管腳使能內部上拉電阻。
PORTB=0xFF;
PORTC=0xFF;
PORTD=0xFF;
DDRD=(1<
//這個(gè)BootLoader沒(méi)有使用中斷。
//初始化USART 115200 8, n,1 PC上位機軟件(超級終端)也要設成同樣的設置才能通訊
UCSRC = (1<
UBRRH = (F_CPU/BAUDRATE/16-1)/256;
UCSRA = 0x00;
UCSRB = (1<
OCR0 = 28;
TCCR0 = (1<
//向PC機發(fā)送開(kāi)始提示信息
put_s("************************************************************");
//put_s(" ");
put_s("IC ATMega16 Firmware 智能升級引導程序(Bootloader)VER20070107");
put_s(" 使用Windows2000/xp 超級終端 串口發(fā)送 9600bps,8-N-1 ");
put_s("如需更新用戶(hù)程序,請在3秒鐘內按下[d]鍵,否則3秒后運行用戶(hù)程序 ");
put_s(">");
//3秒種等待PC下發(fā)“d”,否則退出Bootloader程序,從0x0000處執行應用程序
c=0;
get_data(&c,1,3000); //限時(shí)3秒,接收一個(gè)數據
if ((c==d)||(c==D))
{
STATUS=ST_WAIT_START; //并且數據=d或D,進(jìn)入XMODEM
put_s("請選擇BIN文件,使用XMODEM協(xié)議傳輸,最大14KB");
}
else
{
STATUS=ST_OK; //退出Bootloader程序
}
//進(jìn)入XMODEM模式
FlashAddress=0x0000;
BlockCount=0x01;
while(STATUS!=ST_OK) //循環(huán)接收,直到全部發(fā)完
{
if (STATUS==ST_WAIT_START)
{//XMODEM未啟動(dòng)
put_c(XMODEM_WAIT_CHAR); //發(fā)送請求XMODEM_WAIT_CHAR
}
i=get_data(&strXMODEM.SOH,133,1000); //限時(shí)1秒,接收133字節數據
if(i)
{
//分析數據包的第一個(gè)數據 SOH/EOT/CAN
switch(strXMODEM.SOH)
{
case XMODEM_SOH: //收到開(kāi)始符SOH
if (i>=133)
{
STATUS=ST_BLOCK_OK;
}
else
{
STATUS=ST_BLOCK_FAIL; //如果數據不足,要求重發(fā)當前數據塊
put_c(XMODEM_NAK);
}
break;
case XMODEM_EOT: //收到結束符EOT
put_c(XMODEM_ACK); //通知PC機全部收到
STATUS=ST_OK;
put_s(" 用戶(hù)程序升級成功!");
break;
case XMODEM_CAN: //收到取消符CAN
put_c(XMODEM_ACK); //回應PC機
STATUS=ST_OK;
put_s("警告:用戶(hù)取消升級,用戶(hù)程序可能不完整");
break;
default: //起始字節錯誤
put_c(XMODEM_NAK); //要求重發(fā)當前數據塊
STATUS=ST_BLOCK_FAIL;
break;
}
}
if (STATUS==ST_BLOCK_OK) //接收133字節OK,且起始字節正確
{
if (BlockCount != strXMODEM.BlockNo)//核對數據塊編號正確
{
put_c(XMODEM_NAK); //數據塊編號錯誤,要求重發(fā)當前數據塊
continue;
}
if (BlockCount !=(unsigned char)(~strXMODEM.nBlockNo))
{
put_c(XMODEM_NAK); //數據塊編號反碼錯誤,要求重發(fā)當前數據塊
continue;
}
crc=strXMODEM.CRC16hi<<8;
crc+=strXMODEM.CRC16lo;
//AVR的16位整數是低位在先,XMODEM的CRC16是高位在先
if(calcrc(&strXMODEM.Xdata[0],128)!=crc)
{
put_c(XMODEM_NAK); //CRC錯誤,要求重發(fā)當前數據塊
continue;
}
//正確接收128個(gè)字節數據,剛好是M16的一頁(yè)
if (FlashAddress<(BootAdd-SPM_PAGESIZE))
{ //如果地址在應用區內
write_one_page(); //將收到128字節寫(xiě)入一頁(yè)Flash中
FlashAddress+=SPM_PAGESIZE; //Flash頁(yè)加1
}
else
{
put_c(XMODEM_CAN); //程序已滿(mǎn),取消傳送
put_c(XMODEM_CAN);
put_c(XMODEM_CAN);
STATUS=ST_OK;
put_s(" 程序已滿(mǎn),取消傳送!");
break;
}
put_c(XMODEM_ACK); //回應已正確收到一個(gè)數據塊
BlockCount++; //數據塊累計加1
}
}
//退出Bootloader程序,從0x0000處執行應用程序
put_s("退出Bootloader升級程序!");
delay_ms(500); //很奇怪,見(jiàn)頂部的說(shuō)明
loop_until_bit_is_set(UCSRA,UDRE); //等待結束提示信息回送完成
GICR = (1<
boot_rww_enable (); //RWW區讀允許,否則無(wú)法馬上執行用戶(hù)的應用程序
asm volatile("jmp 0x0000": : ); //跳轉到Flash的0x0000處,執行用戶(hù)的應用程序
}
/*
FLASH程序存儲器的編程方法常見(jiàn)的有以下幾種:
(1)傳統的并行編程方法;
(2)通過(guò)串行口進(jìn)行在線(xiàn)編程ISP(In System Programmability) 對器件或電路甚至整個(gè)系統進(jìn)行現場(chǎng)升級或功能重構;
(3)在運行中,應用程序控制下的應用在線(xiàn)編程IAP (In Applocation Programing) 簡(jiǎn)單地說(shuō)就是在某一個(gè)section中運行程序,同時(shí)對另一個(gè)section進(jìn)行擦除、讀取、寫(xiě)入等操作。
ISP方式相對于傳統方式有了極大的進(jìn)步,它不需要將芯片從電路板上卸下就可對芯片進(jìn)行編程,減少了開(kāi)發(fā)時(shí)間,簡(jiǎn)化了產(chǎn)品制造流程,并大大降低了現場(chǎng)升級的困難。
而IAP方式是對芯片的編程處于應用程序控制之下,對芯片的編程融入在通信系統當中,通過(guò)各種接口(UART/SPI/IIC 等)來(lái)升級指定目標芯片的軟件。
BootLoader 功能介紹
BootLoader 提供我們通常所說(shuō)的IAP(In Applicaion Program)功能。
多數Mega系列單片機具有片內引導程序自編程功能(BootLoader)。
MCU 通過(guò)運行一個(gè)常駐FLASH 的BootLoader 程序,利用任何可用的數據接口讀取代碼后寫(xiě)入自身FLASH存儲器中 ,實(shí)現自編程目的
基本設計思想(參考了馬潮老師的文章)
1. Boot Loader程序的設計要點(diǎn)
Boot Loader程序的設計是實(shí)現IAP的關(guān)鍵,它必須能過(guò)通過(guò)一個(gè)通信接口,采用某種協(xié)議正確的接收數據,再將完整的數據寫(xiě)入到用戶(hù)程序區中。本例Boot Loader程序的設計要點(diǎn)有:
1 采用ATmega16的USART口實(shí)現與PC之間的簡(jiǎn)易RS232三線(xiàn)通信;
2 采用Xmodem通信協(xié)議完成與PC機之間的數據交換;
3 用戶(hù)程序更新完成后自動(dòng)轉入用戶(hù)程序執行;
2. Xmodem通信協(xié)議
Xmodem協(xié)議是一種使用撥號調制解調器的個(gè)人計算機通信中廣泛使用的異步文件運輸協(xié)議。
這種協(xié)議以128字節塊的形式傳輸數據,并且每個(gè)塊都使用一個(gè)校驗和過(guò)程來(lái)進(jìn)行錯誤檢測。
如果接收方關(guān)于一個(gè)塊的校驗和與它在發(fā)送方的校驗和相同時(shí),接收方就向發(fā)送方發(fā)送一個(gè)認可字節。
為了便于讀者閱讀程序,下面簡(jiǎn)要說(shuō)明該協(xié)議的主要特點(diǎn),有關(guān)Xmoden的完整的協(xié)議請參考其它相關(guān)的資料。
1 Xmodem的控制字符:
2 XMODEM有兩種校驗模式:
一種是一字節的checksum校驗模式,不常用。
另一種是2字節的CRC16校驗模式(X^16 + X^12 + X^5 + 1),糾錯率高達99.9984%。
兩種模式的選擇由接收端發(fā)送的啟動(dòng)控制符來(lái)決定,啟動(dòng)發(fā)送后不能切換。
當發(fā)送端收到“NAK”控制字符時(shí),它將會(huì )開(kāi)始以checksum校驗方式發(fā)送數據塊。
當發(fā)送端收到“C”控制字符時(shí),它將會(huì )開(kāi)始以CRC校驗方式發(fā)送數據塊。
3 Xmodem-CRC傳輸數據塊格式:“
其中
<255-BlockNO>是前一字節的反碼;
接下來(lái)是長(cháng)度為128字節的數據塊;
最后的
5 接收端收到一個(gè)數據塊并校驗正確時(shí),回送;接收錯誤回送
6 BlockNO的初值為0x01,每發(fā)送一個(gè)新的數據塊
7 發(fā)送端收到后,可繼續發(fā)送下一個(gè)數據塊(BlockNO+1);而收到
8 發(fā)送端發(fā)送
*/
makefile中的程序基地址偏移
LDFLAGS += -Wl,--section-start=.text=0x3800 //0x3800字節=0x1C00字
即增加下圖中的27行
然后在options 中勾擇Use External Makefile 選中剛才改的Makefile
這是,編譯完成的hex文件大約15k?? 好像是5k
升級的程序,不能是HEX文件,因為HEX文件是內含格式且每行信息可以不等長(cháng)的(下圖)。對于這個(gè)BOOTLOADER升級程序,只能接
收原始的二進(jìn)制文件信息并覆寫(xiě)到相應的flash區內,因此只能使用BIN格式。將HEX轉為BIN有一個(gè)小軟件
而B(niǎo)IN文件是連續且等長(cháng)的
評論