一,单片机为什么要使用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 0x10000int 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