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