📄 bootldr.c
字号:
#include "bootcfg.h"
#include "bootldr.h"
//用户程序起始地址
#define PROG_START 0x0000
//接收缓冲区大小不能小于 SPM_PAGESIZE
#if BUFFERSIZE < SPM_PAGESIZE
#define BUFSIZE SPM_PAGESIZE
#else
#define BUFSIZE BUFFERSIZE
#endif
//接收缓冲区
unsigned char buf[BUFSIZE];
#if BUFSIZE > 255
unsigned int bufptr, pagptr;
#else
unsigned char bufptr, pagptr;
#endif
unsigned char ch, cl;
//当前Flash地址指针
#if FLASHEND > 0xFFFFUL
unsigned long int FlashAddr;
#else
unsigned int FlashAddr;
#endif
//更新一个Flash页
void write_one_page(unsigned char *buf)
{
boot_page_erase(FlashAddr); //擦除一个Flash页
boot_spm_busy_wait();
for(pagptr = 0; pagptr < SPM_PAGESIZE; pagptr += 2) //数据填入Flash缓冲页
{
boot_page_fill(pagptr, buf[pagptr] + (buf[pagptr + 1] << 8));
}
boot_page_write(FlashAddr); //将缓冲页数据写入一个Flash页
boot_spm_busy_wait(); //等待页编程完成
}
//跳转到用户程序
void quit()
{
boot_rww_enable(); //允许用户程序区读写
(*((void(*)(void))PROG_START))(); //跳转,这样比'jmp 0'节省空间
}
//写入数据到串口
void WriteCom(unsigned char dat)
{
UDRREG(COMPORTNo) = dat;
//等待数据发送完成
while(!(UCSRAREG(COMPORTNo) & (1<<TXCBIT(COMPORTNo))));
UCSRAREG(COMPORTNo) |= (1 << TXCBIT(COMPORTNo));
}
//等待串口数据
unsigned char WaitCom()
{
while(!DataInCom());
return ReadCom();
}
#if VERBOSE
void putstr(const char *str)
{
while(*str)
WriteCom(*str++);
}
#endif
//计算CRC校验:1021
void crc16(unsigned char *buf)
{
#if BUFSIZE > 255
unsigned int j;
#else
unsigned char j;
#endif
#if CRCMODE == 0
unsigned char i;
unsigned int t;
#endif
unsigned int crc;
crc = 0;
for(j = BUFFERSIZE; j > 0; j--)
{
#if CRCMODE == 0
//标准CRC校验
crc = (crc ^ (((unsigned int) *buf) << 8));
for(i = 8; i > 0; i--)
{
t = crc << 1;
if(crc & 0x8000)
t = t ^ 0x1021;
crc = t;
}
#else
#if CRCMODE == 1
//累加和校验
crc += (unsigned int)(*buf);
#endif
#endif
buf++;
}
ch = crc / 256;
cl = crc % 256;
}
unsigned char hex_to_ascii(unsigned char i)
{
i=i&0xff;
if(i<10)return i+'0';
else return i+'A'-10;
}
int main(void)
{
unsigned char cnt,del;
unsigned char packNO;
unsigned char crch, crcl;
#if InitDelay > 255
unsigned int di;
#elif InitDelay
unsigned char di;
#endif
#if BUFFERSIZE > 255
unsigned int li;
#else
unsigned char li;
#endif
//关闭中断,异常情况下的保护
cli();
#if WDG_En
//允许看门狗,设置看门狗超时时间
wdt_enable(WDTO_1S);
#else
//禁止看门狗
#ifndef MCUSR
#define MCUSR MCUCSR
#endif
MCUSR = 0;
wdt_disable();
#endif
//时钟初始化:CTC模式
TimerInit();
#if RS485
DDRREG(RS485PORT) |= (1 << RS485TXEn);
RS485Disable();
#endif
#if LEDEn
//设置LED端口为输出状态
DDRREG(LEDPORT) = (1 << LEDPORTNo);
#endif
//串口初始化
ComInit();
#if InitDelay
//某些型号的单片机需要延时,否则串口发出的数据是乱码
for(di = InitDelay; di > 0; di--)
__asm__ __volatile__ ("nop": : );
#endif
//串口启动Bootloader模式
#if VERBOSE
//提示等待密码
putstr("\r\n请输入 D 健进入升级模式\r\nDEBUG:> ");
#endif
cnt = TimeOutCnt;
cl = 0;
while(1)
{
if(TIFRREG & (1<<OCF1A)) //T1溢出
{
TIFRREG |= (1 << OCF1A);
#if LEDEn
//LED指示状态
LEDAlt();
#endif
if(--cnt == 0)
{
#if VERBOSE
//提示超时
putstr("\r\n进入用户程序...");
#endif
//跳转到用户程序
quit();
}
putstr("\b\b");WriteCom(hex_to_ascii(cnt>>4));WriteCom(hex_to_ascii(cnt));
}
#if WDGEn
//清除看门狗
wdt_reset();
#endif
if(DataInCom())
{char data_tmp;
data_tmp=ReadCom();
if( data_tmp== 'd'||data_tmp== 'D'){break;}
}
}
//每个时隙向PC机发送一个控制字符“C”,等待控制字〈soh〉
del = 1;
while(1)
{
if(TIFRREG & (1 << OCF1A)) //T1溢出
{
TIFRREG |= (1 << OCF1A);
#if LEDEn
//LED指示状态
LEDAlt();
#endif
if(--del == 0)
{ del=3;
WriteCom(XMODEM_RWC) ; //发送 "C"
}
}
#if WDGEn
//清除看门狗
wdt_reset();
#endif
if(DataInCom())
{
if(ReadCom() == XMODEM_SOH)break; //XMODEM命令开始
}
}
//关闭定时器
TCCR1B = 0;
//开始接受数据
packNO = 0;
bufptr = 0;
cnt = 0;
FlashAddr = 0;
do
{
packNO++;
ch = WaitCom(); //获取包序号
cl = ~WaitCom();
if ((packNO == ch) && (packNO == cl))
{
for(li = 0; li < BUFFERSIZE; li++) //接收完整一帧数据
{
buf[bufptr++] = WaitCom();
}
crch = WaitCom();
crcl = WaitCom();
crc16(&buf[bufptr - BUFFERSIZE]); //计算校验
if((crch == ch) && (crcl == cl))
{
#if BootStart
if(FlashAddr < BootStart) //避免写入Boot区
{
#endif
#if BUFFERSIZE < SPM_PAGESIZE
if(bufptr >= SPM_PAGESIZE) //缓冲区满,写入数据;否则继续接收
{ //接收多个帧,写入一页
write_one_page(buf); //写入缓冲区内容到Flash中
FlashAddr += SPM_PAGESIZE; //修改Flash页地址
bufptr = 0;
}
#else
while(bufptr > 0) //接收一帧,写入多个页面
{
write_one_page(&buf[BUFSIZE - bufptr]);
FlashAddr += SPM_PAGESIZE; //修改Flash页地址
bufptr -= SPM_PAGESIZE;
}
#endif
#if BootStart
}
else //超过BootStart,忽略写操作
{
bufptr = 0; //重置接收指针
}
#endif
//读取写入的Flash内容并和下载的缓冲区比较
#if (ChipCheck > 0) && (BootStart > 0)
#if BUFFERSIZE < SPM_PAGESIZE
if((bufptr == 0) && (FlashAddr <= BootStart))
#else
if(FlashAddr <= BootStart)
#endif
{
boot_rww_enable(); //允许读用户程序
cl = 1; //清除错误标志
for(pagptr = 0; pagptr < BUFSIZE; pagptr++)
{
#if FLASHEND > 0xFFFFUL
if(pgm_read_byte_far(FlashAddr - BUFSIZE + pagptr) != buf[pagptr])
#else
if(pgm_read_byte(FlashAddr - BUFSIZE + pagptr) != buf[pagptr])
#endif
{
cl = 0; //设置错误标志
break;
}
}
if(cl) //校验正确,发送正常反馈
{
WriteCom(XMODEM_ACK);
cnt = 0;
}
else
{
WriteCom(XMODEM_NAK); //校验错误,请求重发
cnt++; //错误计数加1
FlashAddr -= BUFSIZE; //修正FlashAddr地址
}
}
else //不校验Boot区,直接发送正确回应
{
WriteCom(XMODEM_ACK);
cnt = 0;
}
#else
//不校验,直接发送正确响应
WriteCom(XMODEM_ACK);
cnt = 0;
#endif
#if WDGEn
//清除看门狗
wdt_reset();
#endif
#if LEDEn
//LED指示状态
LEDAlt();
#endif
}
else //CRC
{
//要求重发数据块
WriteCom(XMODEM_NAK);
cnt++;
}
}
else //PackNo
{
//要求重发数据块
WriteCom(XMODEM_NAK);
cnt++;
}
//连续出错次数太多,中止升级
if(cnt > 3)break;
}while(WaitCom() != XMODEM_EOT);
WriteCom(XMODEM_ACK);
#if VERBOSE
if(cnt == 0)
{
//提示升级成功
putstr("\r\n升级成功");
}
else
{
//提示升级失败
putstr("\r\n升级失败");
#if WDGEn
//等待看门狗复位
while(1);
#endif
boot_rww_enable();
(*((void(*)(void))0x1c00))();
}
#endif
//退出Bootloader
quit();
return 0;
}
//End of file: bootldr.c
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -