AVR BootLoader应用范例

嵌入式系统 时间:2016-11-13 来源:网络

/***********************************************
****AVRBootLoader应用范例 ***
**** ***
**** 作者: 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应用,实现智能升级
Boot Loader
XMODEM-CRC传输协议
CRC16校验

出于简化程序考虑,各种数据没有对外输出,学习时建议使用JTAG ICE硬件仿真器。
熔丝位设置
BOOTSZ1=0
BOOTSZ0=0 Boot区为1K字(2K字节)大小。
BOOTRST=0 复位向量位于Boot区。//Stephen: 设BOOTRST=1,允许启动

makefile中的程序基地址偏移
LDFLAGS += -Wl,--section-start=.text=0x3800 //0x3800字节=0x1C00字

移植程序时,可根据实际大小设定Boot区,但要注意更改makefile和更改BootAdd常数,以及页写的大小分配;

采用115200bps的通讯速率,升级14KB程序需要耗时约5秒[上位机是WINDOWS 2000的超级终端]

疑问:
1 用HEX文件烧录工作正常,但elf仿真有问题。
用AVRstudio仿真elf(熔丝设定BOOTRST=0,程序基地址偏移=0x3800)时,所有SRAM变量丢失初始化,
表现为put_s()的都是乱码或不可见字符。
但如果改成应用程序(熔丝设定BOOTRST=1,没有程序基地址偏移),则put_s()可以正常显示

2 XMODEM的结束应答(EOT/CAN)后需加 delay_ms(500)的延时(程序优化,统一写在跳转到用户程序前),
否则在下面的情况将会无法正常结束XMODEM的传输,但其实程序已经升级成功
特殊情况:用户程序里面使用了串口,而且波特率较低(如9600bps)且开机即发送大量数据

*/

#include <avr/io.h>
#include
//时钟定为外部晶体7.3728MHz,F_CPU=7372800 使用USART,115200bps
#include
/*
boot_page_erase ( address )
擦除FLASH 指定页,其中address 是以字节为单位的FLASH 地址
boot_page_fill ( address, data )
填充BootLoader 缓冲页,address 为以字节为单位的缓冲页地址(对mega16 :0~128),
而data 是长度为两个字节的字数据,因此调用前address 的增量应为2。
此时data 的高字节写入到高地址,低字节写入到低地址。
boot_page_write ( address )
boot_page_write 执行一次的SPM 指令,将缓冲页数据写入到FLASH 指定页。
boot_rww_enable ( )
RWW 区读使能

根据自编程的同时是否允许读FLASH 存储器,FLASH 存储器可分为两种类型:
可同时读写区( RWW Read-While-Write ) 和 非同时读写区( NRWW NotRead-While-Write)。
对于MEGA16 RWW 为前14K 字节 NRWW 为后2K 字节。
引导加载程序对RWW 区编程时MCU 仍可以从NRWW 区读取指令并执行,而对NRWW 区编程时MCU 处于挂起暂停状态。
在对RWW 区自编程(页写入或页擦除)时,由硬件锁定RWW 区 , RWW 区的读操作被禁止
在对RWW 区的编程结束后应当调用boot_rww_enable() 使RWW 区开放。
*/
#include
/*
GCCAVR内置函数,可以不用头痛CRC16了
关于CRC的详细说明,可以查看一下网站:
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
通常用于磁盘控制器(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通讯协议,等效于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通讯协议
*/

//管脚定义
#define PIN_RXD 0 //PD0
#define PIN_TXD 1 //PD1

//常数定义
#define SPM_PAGESIZE 128 //M16的一个Flash页为128字节(64字)
#define DATA_BUFFER_SIZE SPM_PAGESIZE //定义接收缓冲区长度
#define BAUDRATE 9600 //115200 //波特率采用115200bps
//#define F_CPU 7372800 //系统时钟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位长度,适应所有AVR的容量*/

unsigned char BlockCount; //数据块累计(仅8位,无须考虑溢出)

unsigned char STATUS; //运行状态
#define ST_WAIT_START 0x00 //等待启动
#define ST_BLOCK_OK 0x01 //接收一个数据块成功
#define ST_BLOCK_FAIL 0x02 //接收一个数据块失败
#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字节/页)有效
}
boot_page_write(FlashAddress); //将缓冲页数据写入一个Flash页
boot_spm_busy_wait(); //等待页编程完成
}

//发送采用查询方式
void put_c(unsigned char c) //发送采用查询方式
{
loop_until_bit_is_set(UCSRA,UDRE);
UDR=c;
}

//发送字符串
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //结尾发送回车换行
}


//接收指定字节数据(带超时控制,Timer0的1ms时基)
// *ptr 数据缓冲区
// len 数据长度
// timeout 超时设定,最长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< timeout--; //倒计时
}
}
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可能由应用程序中跳转过来,所以所用到的模块需要全面初始化
DDRA=0x00;
DDRB=0x00;
DDRC=0x00;
PORTA=0xFF; //不用的管脚使能内部上拉电阻
PORTB=0xFF;
PORTC=0xFF;
PORTD=0xFF;
DDRD=(1< GICR = (1< GICR = (0< asm volatile("cli": : ); //关全局中断
//这个BootLoader没有使用中断。

//初始化USART 115200 8, n,1 PC上位机软件(超级终端)也要设成同样的设置才能通讯
UCSRC = (1< UBRRL = (F_CPU/BAUDRATE/16-1)%256; //设定波特率
UBRRH = (F_CPU/BAUDRATE/16-1)/256;
UCSRA = 0x00;
UCSRB = (1< //初始化T/C0,CTC模式,256分频,1ms自动重载
OCR0 = 28;
TCCR0 = (1< //CTC模式下,溢出标志是输出比较匹配OCF0,对应的中断是输出比较匹配中断;

//向PC机发送开始提示信息
put_s("************************************************************");
//put_s(" ");
put_s("IC ATMega16 Firmware 智能升级引导程序(Bootloader)VER20070107");
put_s(" 使用Windows2000/xp 超级终端 串口发送 9600bps,8-N-1 ");
put_s("如需更新用户程序,请在3秒钟内按下[d]键,否则3秒后运行用户程序 ");
put_s(">");

//3秒种等待PC下发“d”,否则退出Bootloader程序,从0x0000处执行应用程序
c=0;
get_data(&c,1,3000); //限时3秒,接收一个数据
if ((c==d)||(c==D))
{
STATUS=ST_WAIT_START; //并且数据=d或D,进入XMODEM
put_s("请选择BIN文件,使用XMODEM协议传输,最大14KB");
}
else
{
STATUS=ST_OK; //退出Bootloader程序
}

//进入XMODEM模式
FlashAddress=0x0000;
BlockCount=0x01;
while(STATUS!=ST_OK) //循环接收,直到全部发完
{
if (STATUS==ST_WAIT_START)
{//XMODEM未启动
put_c(XMODEM_WAIT_CHAR); //发送请求XMODEM_WAIT_CHAR
}
i=get_data(&strXMODEM.SOH,133,1000); //限时1秒,接收133字节数据
if(i)
{
//分析数据包的第一个数据 SOH/EOT/CAN
switch(strXMODEM.SOH)
{
case XMODEM_SOH: //收到开始符SOH
if (i>=133)
{
STATUS=ST_BLOCK_OK;
}
else
{
STATUS=ST_BLOCK_FAIL; //如果数据不足,要求重发当前数据块
put_c(XMODEM_NAK);
}
break;
case XMODEM_EOT: //收到结束符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: //起始字节错误
put_c(XMODEM_NAK); //要求重发当前数据块
STATUS=ST_BLOCK_FAIL;
break;
}
}
if (STATUS==ST_BLOCK_OK) //接收133字节OK,且起始字节正确
{
if (BlockCount != strXMODEM.BlockNo)//核对数据块编号正确
{
put_c(XMODEM_NAK); //数据块编号错误,要求重发当前数据块
continue;
}
if (BlockCount !=(unsigned char)(~strXMODEM.nBlockNo))
{
put_c(XMODEM_NAK); //数据块编号反码错误,要求重发当前数据块
continue;
}
crc=strXMODEM.CRC16hi<<8;
crc+=strXMODEM.CRC16lo;
//AVR的16位整数是低位在先,XMODEM的CRC16是高位在先
if(calcrc(&strXMODEM.Xdata[0],128)!=crc)
{
put_c(XMODEM_NAK); //CRC错误,要求重发当前数据块
continue;
}
//正确接收128个字节数据,刚好是M16的一页
if (FlashAddress<(BootAdd-SPM_PAGESIZE))
{ //如果地址在应用区内
write_one_page(); //将收到128字节写入一页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); //回应已正确收到一个数据块
BlockCount++; //数据块累计加1
}
}

//退出Bootloader程序,从0x0000处执行应用程序
put_s("退出Bootloader升级程序!");
delay_ms(500); //很奇怪,见顶部的说明
loop_until_bit_is_set(UCSRA,UDRE); //等待结束提示信息回送完成
GICR = (1< GICR = (0< /* 无论BootLoader是否使用中断,将中断向量表迁移到应用程序区头部,会增强程序的健壮性*/
boot_rww_enable (); //RWW区读允许,否则无法马上执行用户的应用程序
asm volatile("jmp 0x0000": : ); //跳转到Flash的0x0000处,执行用户的应用程序
}

/*
FLASH程序存储器的编程方法常见的有以下几种:

(1)传统的并行编程方法;
(2)通过串行口进行在线编程ISP(In System Programmability) 对器件或电路甚至整个系统进行现场升级或功能重构;
(3)在运行中,应用程序控制下的应用在线编程IAP (In Applocation Programing) 简单地说就是在某一个section中运行程序,同时对另一个section进行擦除、读取、写入等操作。
ISP方式相对于传统方式有了极大的进步,它不需要将芯片从电路板上卸下就可对芯片进行编程,减少了开发时间,简化了产品制造流程,并大大降低了现场升级的困难。
而IAP方式是对芯片的编程处于应用程序控制之下,对芯片的编程融入在通信系统当中,通过各种接口(UART/SPI/IIC 等)来升级指定目标芯片的软件。

BootLoader 功能介绍
BootLoader 提供我们通常所说的IAP(In Applicaion Program)功能。
多数Mega系列单片机具有片内引导程序自编程功能(BootLoader)。
MCU 通过运行一个常驻FLASH 的BootLoader 程序,利用任何可用的数据接口读取代码后写入自身FLASH存储器中 ,实现自编程目的

基本设计思想(参考了马潮老师的文章)
1. Boot Loader程序的设计要点
Boot Loader程序的设计是实现IAP的关键,它必须能过通过一个通信接口,采用某种协议正确的接收数据,再将完整的数据写入到用户程序区中。本例Boot Loader程序的设计要点有:
1 采用ATmega16的USART口实现与PC之间的简易RS232三线通信;
2 采用Xmodem通信协议完成与PC机之间的数据交换;
3 用户程序更新完成后自动转入用户程序执行;
2. Xmodem通信协议
Xmodem协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。
这种协议以128字节块的形式传输数据,并且每个块都使用一个校验和过程来进行错误检测。
如果接收方关于一个块的校验和与它在发送方的校验和相同时,接收方就向发送方发送一个认可字节。
为了便于读者阅读程序,下面简要说明该协议的主要特点,有关Xmoden的完整的协议请参考其它相关的资料。
1 Xmodem的控制字符: 01H、 04H、 06H、 15H、 18H、 1AH、c 43H。
2 XMODEM有两种校验模式:
一种是一字节的checksum校验模式,不常用。
另一种是2字节的CRC16校验模式(X^16 + X^12 + X^5 + 1),纠错率高达99.9984%。
两种模式的选择由接收端发送的启动控制符来决定,启动发送后不能切换。
当发送端收到“NAK”控制字符时,它将会开始以checksum校验方式发送数据块。
当发送端收到“C”控制字符时,它将会开始以CRC校验方式发送数据块。
3 Xmodem-CRC传输数据块格式:“ <255-BlockNO> <…128个字节的数据块…> ”。
其中 为起始字节;
为数据块编号字节,每次加一;
<255-BlockNO>是前一字节的反码;
接下来是长度为128字节的数据块;
最后的 是128字节数据的CRC校验码,长度为2个字节,crc16hi,crc16lo。
5 接收端收到一个数据块并校验正确时,回送;接收错误回送 ;而回送 表示要发送端停止发送。
6 BlockNO的初值为0x01,每发送一个新的数据块 加1,加到OxFF后下一个数据块的 为零,即8位无符号数。
7 发送端收到后,可继续发送下一个数据块(BlockNO+1);而收到 则可再次重发上一个数据块。
8 发送端发送 表示全部数据发送完成。如果最后需要发送的数据不足128个字节,用 填满一个数据块。

*/

makefile中的程序基地址偏移
LDFLAGS += -Wl,--section-start=.text=0x3800 //0x3800字节=0x1C00字

即增加下图中的27行

然后在options 中勾择Use External Makefile 选中刚才改的Makefile


这是,编译完成的hex文件大约15k?? 好像是5k

升级的程序,不能是HEX文件,因为HEX文件是内含格式且每行信息可以不等长的(下图)。对于这个BOOTLOADER升级程序,只能接
收原始的二进制文件信息并覆写到相应的flash区内,因此只能使用BIN格式。将HEX转为BIN有一个小软件



而BIN文件是连续且等长的

关键词:AVRBootloade

加入微信
获取电子行业最新资讯
搜索微信公众号:EEPW

或用微信扫描左侧二维码

相关文章


用户评论

请文明上网,做现代文明人
验证码:
查看电脑版