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