一,单片机为什么要使用bootloader
1、使用bootloader的好处
1) 程序隔离:可以同时存在多个程序,只要flash空间够大,或者通过外挂flash,可以实现多个程序共存,在多个程序之间切换使用。
2)方便程序升级和后期维护:多个程序相互独立运行,可以在一个程序对另一个程序更新,普通单片机程序只能通过isp或者jtag、swd等调试接口实现程序烧录。而使用bootloader程序则可以通过usart、485、can、iic、spi、sd、4g、wifi卡等等任意可以实现数据传输的通信方式进行设备ota升级,也不必必须依赖烧录器。
2、不建议使用bootloader的原因
1)占用flash空间:多一个程序必然会多占一部分flash空间。
2)增加程序烧录的步骤:项目量产时出厂烧录程序会不太方便。
二、设计要点
使用bootloader至少需要开发两个程序,也就是创建两个工程,一个bootloader程序,一个应用程序;bootloader程序负责初始化部分硬件,提供一些通信方式实现应用程序的ota功能;如果有多个应用程序在bootloader程序内需要对应用程序进行启动选择。
1、flash分区,两个程序要想不相互影响,需要将两个程序烧录到flash的不同位置
2、flash编程,要实现程序ota功能,需要提供对单片机flash的编程功能。
3、初始化通信接口,规定ota协议。
4、配置中断向量表地址。
5、配置堆栈地址
5、跳转应用程序地址。
三、工程配置
1、bootloader程序flash地址配置
2、app程序flash地址配置
四、关键函数代码
1、Flash编程函数
不同单片机flash编程函数也不一样,可以自行修改,这里只提供实现思路。
#define FMC_PAGE_SIZE ((uint16_t)0x800U)
uint8_t fmc_tmp_page[FMC_PAGE_SIZE];
void flash_program(uint32_t addr,uint8_t *data,uint16_t size)
{
uint32_t prog_addr = (uint32_t)addr;
uint8_t * data_addr = data;
uint16_t i,j;
uint16_t pages;
uint16_t pg_idx = 0;
uint16_t wr_size = size;
uint32_t * pdata;
uint32_t * pobj = (uint32_t *)fmc_tmp_page;
if(size == 0){
return;
}else if(size < FMC_PAGE_SIZE-prog_addr%FMC_PAGE_SIZE){
pages = 1;
}else{
pages = 1+(size-prog_addr%FMC_PAGE_SIZE+FMC_PAGE_SIZE-1)/FMC_PAGE_SIZE;
}
/* unlock the flash program/erase controller */
fmc_unlock();
/* clear all pending flags */
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
for(i=0;i<pages;i++){
pg_idx = prog_addr%FMC_PAGE_SIZE;
prog_addr = prog_addr/FMC_PAGE_SIZE*FMC_PAGE_SIZE;
pdata = (uint32_t*)prog_addr;
wr_size = FMC_PAGE_SIZE-pg_idx<size?FMC_PAGE_SIZE-pg_idx:size;
size -= wr_size;
for(j=0;j<FMC_PAGE_SIZE/4;j++){
pobj[j]=*pdata;
pdata++;
}
for(j=pg_idx;j<wr_size+pg_idx;j++){
fmc_tmp_page[j]=*(data_addr);
data_addr++;
}
fmc_page_erase(prog_addr);
/* clear all pending flags */
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
/* program flash */
for(j=0;j<FMC_PAGE_SIZE/4;j++){
fmc_word_program(prog_addr+j*4, pobj[j]);
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
}
prog_addr += FMC_PAGE_SIZE;
}
/* lock the main FMC after the erase operation */
fmc_lock();
}
2、修改中断向量表地址
部分单片机库函数未提供修改向量表地址函数,这里我自己模仿写了个。
void BootLoader_SetVectorTable(uint32_t NVIC_VectTab,uint32_t Offset)
{
SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80);
}
3、修改主堆栈地址
网上很多实现都过于复杂,写了一大堆汇编代码,我这里只尽量只用c语言的方式去实现,便于理解与调用。
void BootLoader_MSP(uint32_t addr)
{
__ASM volatile("LDR r2, [addr]");
__ASM volatile("MSR msp, r2");
}
4、跳转应用程序
一个函数完成所有功能。
void BootLoader_App_Startup(uint32_t offset)
{
application_t app;
uint32_t msp_addr = (FLASH_BASE|offset);
uint32_t * entry_addr = (uint32_t *)(FLASH_BASE|offset|0x4);
app = (application_t)*entry_addr;
BootLoader_SetVectorTable(FLASH_BASE,offset);
BootLoader_MSP(msp_addr);
app();
}
5、使用例程
#define APP_OFFSET_ADDR 0x10000
int main(void)
{
Debug_UartCfg();
while(1){
delay_ms(500);
debug_printf("hello,0x%x!\r\n",123);
BootLoader_App_Startup(APP_OFFSET_ADDR);
}
}
五、关键库全部代码
//bootloader.c
#include "bootloader.h"
#define FMC_PAGE_SIZE ((uint16_t)0x800U)
uint8_t fmc_tmp_page[FMC_PAGE_SIZE];
void BootLoader_MSP(uint32_t addr)
{
__ASM volatile("LDR r2, [addr]");
__ASM volatile("MSR msp, r2");
}
void BootLoader_SetVectorTable(uint32_t NVIC_VectTab,uint32_t Offset)
{
SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80);
}
void BootLoader_App_Startup(uint32_t offset)
{
application_t app;
uint32_t msp_addr = (FLASH_BASE|offset);
uint32_t * entry_addr = (uint32_t *)(FLASH_BASE|offset|0x4);
app = (application_t)*entry_addr;
BootLoader_SetVectorTable(FLASH_BASE,offset);
BootLoader_MSP(msp_addr);
app();
}
void flash_program(uint32_t addr,uint8_t *data,uint16_t size)
{
uint32_t prog_addr = (uint32_t)addr;
uint8_t * data_addr = data;
uint16_t i,j;
uint16_t pages;
uint16_t pg_idx = 0;
uint16_t wr_size = size;
uint32_t * pdata;
uint32_t * pobj = (uint32_t *)fmc_tmp_page;
if(size == 0){
return;
}else if(size < FMC_PAGE_SIZE-prog_addr%FMC_PAGE_SIZE){
pages = 1;
}else{
pages = 1+(size-prog_addr%FMC_PAGE_SIZE+FMC_PAGE_SIZE-1)/FMC_PAGE_SIZE;
}
/* unlock the flash program/erase controller */
fmc_unlock();
/* clear all pending flags */
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
for(i=0;i<pages;i++){
pg_idx = prog_addr%FMC_PAGE_SIZE;
prog_addr = prog_addr/FMC_PAGE_SIZE*FMC_PAGE_SIZE;
pdata = (uint32_t*)prog_addr;
wr_size = FMC_PAGE_SIZE-pg_idx<size?FMC_PAGE_SIZE-pg_idx:size;
size -= wr_size;
for(j=0;j<FMC_PAGE_SIZE/4;j++){
pobj[j]=*pdata;
pdata++;
}
for(j=pg_idx;j<wr_size+pg_idx;j++){
fmc_tmp_page[j]=*(data_addr);
data_addr++;
}
fmc_page_erase(prog_addr);
/* clear all pending flags */
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
/* program flash */
for(j=0;j<FMC_PAGE_SIZE/4;j++){
fmc_word_program(prog_addr+j*4, pobj[j]);
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
}
prog_addr += FMC_PAGE_SIZE;
}
/* lock the main FMC after the erase operation */
fmc_lock();
}
//bootloader.h
#ifndef _BOOTLOADER_H_
#define _BOOTLOADER_H_
#include "gd32f30x.h"
typedef void (*application_t)(void);
void BootLoader_MSP(uint32_t addr);
void BootLoader_SetVectorTable(uint32_t NVIC_VectTab,uint32_t Offset);
void BootLoader_App_Startup(uint32_t addr);
#endif