在mega48上实现bootloader功能,对于高手来说应该不算很难,但是对于新手来说就太不容易了,而悲摧的我,刚练习AVR,就开始了这一段崎岖的征程。好在皇天不负苦心人,最终还是被我搞定了。当时就想把整个过程写出来,同时把相关资料和大家分享,可是因为自己有点忙(或者说有点空儿也要偷懒)的缘故,所以一直没发出来。
刚看到在以前的帖子后面有人需要这个程序,想想自己曾经郁闷抓狂的时候,就忍不住的要把以前那点东西发出来,以能让像我这样的新手少走一些弯路。好了,搞技术的像我这样废话的人估计不是很多,那我也就打住吧,直接进入正题。
我采用的编程软件是:WinAVR-20071221(最高版本优化时出现过问题,解决办法之一是采用老版本,或将temp变量定义为volatile),调试采用avrstudio
在实现bootloader过程中的难点有几个:
1、怎样将mega48的复位向量设置到自己想要的位置?
请参考附件中“AVR绝对定位面面观.doc”的介绍。
顺便说一下,我的作法是在Makefile文件的Linker Options中增加这一项:
LDFLAGS += -Wl,--section-start=.text=0x0b80
0x0b80以字节为单位,即可将编译后的bootloader程序编译在flash的指定位置(0x0b80)之后。
2、用bootloader下载主程序时,主程序的复位向量(0x0000)中的内容要修改为跳转到0x0b80,即下载完主程序后,重新上电,程序自动跳转运行bootloader程序。这个实现方法就是在下载时修改第一个下载数据,修改为0xc5bf(可参考bootloader.c和AVR机器码和汇编指令.doc),而0xc5bf刚好是跳转到0x0b80的机器码(具体修改值可根据你bootloader程序中设定的复位向量位置而改变)。
3、要下载的主程序最后要更改为二进制文件,我从网上下载的工具软件都不好用,最后使用双龙的下载软件,用它的“编辑”功能保存为二进制文件。注意在保存为二进制文件的时候,保存地址最好不要大于0x0b80。
4、用的工具软件为超级终端,波特率等与你的软件设置一致,而“数据流控制”则要选“无”。
大概就这些吧。因为离写好这个程序有段日子了,有些细节也记得不是很清楚,虽然当时碰到了种种的问题,但最难的似乎就是这几个。程序的主体和相关的软件、参考资料都是免费从网上下载的,非常感谢高手的无私帮助,希望我这篇东西可以对和我一样的新手有些帮助。
顺便加一句:因为做试验时老怀疑通讯延迟的问题,所以实际是通过232芯片和电脑串口连接,是全双工的,所以没有485的收发使能问题。
#include <avr/io.h>
#include <util/delay.h>
#include <avr/boot.h>
#define SPM_PAGESIZE 64 //M48的一个Flash页为64字节(32字)
#define BAUD 4800 //波特率采用4800bps
#define CRYSTAL 1000000 //系统时钟1MHz
#define uchar unsigned char
#define uint unsigned int
#define SET_1(a,b) a|=(1<<b)
#define CLR_0(a,b) a&=~(1<<b)
//计算和定义M48的波特率设置参数
#define BAUD_H 0x00
#define BAUD_L 0x0c
#define DATA_BUFFER_SIZE 128 //定义接收缓冲区长度,由XModem协议规定
//定义Xmodem控制字符
#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'
//定义全局变量
typedef void (*pFunc)(void);
const char startString[]="Type 'd' within 3s";// download. ,Others run app
uchar data[DATA_BUFFER_SIZE];
uint address = 0;
//uint addr_jump=0;
pFunc appReset=(pFunc)0x001a; //app区返回地址
uint temp=0;
//擦除(code=0x03)和写入(code=0x05)一个Flash页
/*void boot_page_ew(uint p_address,uchar code)
{
asm("movw r30,%0\n"
:
:"r"(p_address)
:"r30","r31"); //将页地址放入Z寄存器
SPMCSR = code; //寄存器SPMCSR中为操作码
asm("spm\n"); //对指定Flash页进行操作
}*/
//填充Flash缓冲页中的一个字
void boot_page_f(uchar address,uint data)
{
asm volatile("movw r0,%1;" "mov r30,%0\n" "clr r31"
:
:"r"(address),"r"(data)
:"r0");
SPMCSR = 0x01;
asm("spm\n");
}
//等待一个Flash页的写完成并重新使能SPM指令
void wait_page(void)
{
while(SPMCSR&0x01);
SPMCSR = 0x11;
asm("spm\n");
while(SPMCSR&0x01);
}
//更新Flash页的完整处理
void write_two_page(void)
{
uchar i;
boot_page_erase(address); //擦除一个Flash页
wait_page(); //等待擦除完成
//boot_rww_enable ( );
for(i=0;i<SPM_PAGESIZE;i+=2) //将数据填入Flash缓冲页中
{
boot_page_fill_safe(i,data+((uint)data[i+1]<<8));
}
boot_page_write(address); //将缓冲页数据写入一个Flash页
wait_page();
//_delay_ms(200);
boot_page_erase(address+SPM_PAGESIZE); //擦除两个Flash页
wait_page(); //等待擦除完成
for(i=0;i<SPM_PAGESIZE;i+=2) //将数据填入Flash缓冲页中
{
temp=data[65+i];
temp<<=8;
temp+=data[64+i];
boot_page_fill_safe(i,temp);
}
boot_page_write(address+SPM_PAGESIZE); //将缓冲页数据写入一个Flash页
wait_page(); //等待写入完成 //等待写入完成
}
//从RS232发送一个字节
void uart_putchar(uchar c)
{
//SET_1(PORTC,PC1); //485输出使能
//_delay_us(100); //等待电平稳定
while(!(UCSR0A & 0x20)); //等待UDR为空
UDR0 = c;
while(!(UCSR0A & 0x40)); //等待发送完成标志
SET_1(UCSR0A,6);
//CLR_0(PORTC,PC1);
}
//从RS232接收一个字节
int uart_getchar(void)
{
uchar status,res;
// CLR_0(PORTC,PC1);
// _delay_us(20);
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接收一个有效的字节
uchar uart_waitchar(void)
{
int c;
while((c=uart_getchar())==-1);
return (uchar)c;
}
//计算CRC
uint calcrc(uchar *ptr, uchar count)
{
uint crc = 0;
uchar i;
while(count--)
{
crc=crc^((uint)(*ptr)<<8);
ptr++;
for(i=0;i<8;i++)
{
if (crc&0x8000)
crc=(crc<<1)^0x1021;
else
crc<<=1;
}
}
return crc;
}
//退出Bootloader程序,从0x001a处执行应用程序
void quit(void)
{
uart_putchar('O');
uart_putchar('K');
uart_putchar(0x0d);
uart_putchar(0x0a);
while(!(UCSR0A&0x20)); //等待结束提示信息回送完成
_delay_ms(500);
//等待结束提示信息回送完成
MCUCR = 0x01;
MCUCR = 0x00; //将中断向量表迁移到应用程序区头部
//asm("ldi r30,0x00\n"
// "ldi r31,0x00\n"
// "ijmp\n");
appReset(); //跳转到应用程序区
}
//主程序
int main(void)
{
uchar i = 0;
uchar timercount = 0;
uchar packNO = 1;
uint crc=0;
//初始化M48的USART0
UCSR0C = 0x0E; //Set frame. format: 8data, 2stop bit
UBRR0H = BAUD_H;
UBRR0L = BAUD_L; //Set baud rate
UCSR0B = 0x18; //Enable Receiver and Transmitter
//初始化M48的T/C0,64分频,普通模式,定时16.32ms
TCCR0B = 0x03;
//初始化通信使能引脚为输出
SET_1(DDRC,DDC1);
SET_1(PORTC,PC1); //平时处于接收状态
//向PC机发送开始提示信息
while(startString!='\0')
{
uart_putchar(startString);
i++;
}
//3秒种等待PC下发"d",否则退出Bootloader程序,执行应用程序
while(1)
{
if(uart_getchar()=='d')
{
break;
}
if (TIFR0 & 0x04) //timer0 overflow
{
if (++timercount > 200)
quit(); //200*16.32ms = 3.264s
TIFR0 |=0x04; //写1清除溢出标志
}
}
//每秒向PC机发送一个控制字符"C",等待控制字〈soh〉
while(uart_getchar()!=0x01) //wait for the start of Xmodem
{
if(TIFR0 & 0x04) //timer0 over flow
{
if(++timercount > 62) //wait about 1 second
{
uart_putchar(XMODEM_RECIEVING_WAIT_CHAR); //send a 'C'
timercount=0;
}
TIFR0 |=0x04;
}
}
//开始接收数据块
do
{
if((packNO==uart_waitchar())&&(packNO==(uchar)(~uart_waitchar())))
{ //核对数据块编号正确
for(i=0;i<128;i++) //接收128个字节数据
{
data= uart_waitchar();
}
crc = uart_waitchar()<<8;
crc += uart_waitchar(); //接收2个字节的CRC校验字
if(calcrc(data,128)==crc) //CRC校验验证
{ //正确接收128个字节数据
if(address==0) //更改复位向量,跳转到boot区
{
//addr_jump=data[0]+((uint)data[1]<<8)-0xc000;
//appReset=(pFunc)addr_jump;
data[0]=0xbf;
data[1]=0xc5;
}
else if(address>=0xb80) //如果程序超过了应用区大小,输出错误后停止
{
uart_putchar('E');
uart_putchar('R');
uart_putchar('R');
uart_putchar('O');
uart_putchar('R');
uart_putchar('!');
uart_putchar(0x0d);
uart_putchar(0x0a);
while(!(UCSR0A&0x20)); //等待结束提示信息回送完成
// _delay_ms(1);
return 0;
}
write_two_page(); //写入一页Flash中
address=address+2*SPM_PAGESIZE; //Flash页加2
packNO++; //数据块编号加1
uart_putchar(XMODEM_ACK); //正确收到一个数据块
}
else
{
uart_putchar(XMODEM_NAK); //要求重发数据块
}
}
else
{
uart_putchar(XMODEM_NAK); //要求重发数据块
}
}while(uart_waitchar()!=XMODEM_EOT); //循环接收,直到全部发完
uart_putchar(XMODEM_ACK); //通知PC机全部收到
quit();
return 0;
}
|