# Bootloader 说明
编写 bootloader 程序步骤说明:【源代码在文末】
- 根据程序代码区域的大小划分空间
- 编写 Bootloader
- 码 APP
# 1、阅读手册,划分空间
我用的是这个型号的,他的空间有 256KB 是可以用来运行代码的。
# 所以可以这样划分:
- Bootloader FLASH_BASE(((uint32_t)0x08000000U)) 32KB
- APP1 FLASH_BASE | 0x08000 64KB
- APP2 FLASH_BASE | 0x18000 64KB
# 程序流程规划:
# 2、编写 Bootloader
# 主函数:
#define BOOTLOADER_APP1_ADDRESS 0x08008000U // 定义 APP 地址 | |
#define BOOTLOADER_APP2_ADDRESS 0x08018000U | |
int main(){ | |
//1、初始化【串口、flash、定时器等外设】 | |
//2、初始化延时等待,例如等待多久后开始进入 app 区执行 | |
//3、判断升级标志位,如果有就更新程序 | |
while(1){ | |
//2 秒超时时间到、开始跳转 | |
if(isTimeOut(&time,2)){ | |
if(((*(__IO uint32_t *)(BOOTLOADER_APP1_ADDRESS + 4)) & 0xFF000000) == 0x08000000){ | |
iap_load_app(BOOTLOADER_APP1_ADDRESS); | |
}else{ | |
// 这里没有可执行程序 APP1 | |
// 检查 APP2 区域是否有程序 | |
if(((*(__IO uint32_t *)(BOOTLOADER_APP2_ADDRESS + 4)) & 0xFF000000) == 0x08000000){ | |
iap_load_app(BOOTLOADER_APP2_ADDRESS); | |
}else{ | |
//APP1 APP2 均无程序 | |
} | |
} | |
}else{ | |
// 等待其他操作,可以在这里选择启动模式【因为咱们有两个 APP 区,所以在这里可以选择强制从其中一个启动】 | |
... | |
} | |
} | |
} |
# iap.h
#ifndef __IAP_H__ | |
#define __IAP_H__ | |
#include "systick.h" | |
// 用户根据自己的需要设置 | |
#define STM32_FLASH_SIZE 1024 // 所选 STM32 的 FLASH 容量大小 (单位为 K) | |
#if STM32_FLASH_SIZE < 256 | |
#define STM_SECTOR_SIZE 1024 // 字节 | |
#else | |
#define STM_SECTOR_SIZE 2048 | |
#endif | |
////////////////////////////////////////////////////////////////////////////////////////////////////// | |
//FLASH 起始地址 | |
#define GD32_FLASH_BASE 0x08000000 //GD32 FLASH 的起始地址 | |
typedef void (*iapfun)(void); // 定义一个函数类型的参数. | |
void iap_load_app(uint32_t appxaddr); // 跳转到 APP 程序执行 | |
void iap_write_appbin(uint32_t appxaddr,uint8_t *appbuf,uint32_t applen); // 在指定地址开始,写入 bin | |
#endif |
# iap.c
iapfun jump2app; | |
// 设置栈顶地址 | |
//addr: 栈顶地址 | |
__asm void MSR_MSP(uint32_t addr) | |
{ | |
MSR MSP, r0 //set Main Stack value | |
BX r14 | |
} | |
//WriteAddr: 应用程序的起始地址,必须为某扇区的起始地址 | |
//pBuffer: 应用程序 CODE. | |
//NumToWrite: 应用程序大小 (字节). | |
void iap_write_appbin(uint32_t WriteAddr,uint8_t *pBuffer,uint32_t NumToWrite) | |
{ | |
uint32_t i = 0; | |
uint16_t temp; | |
if (WriteAddr < GD32_FLASH_BASE || (WriteAddr >= (GD32_FLASH_BASE + 1024 * STM32_FLASH_SIZE))) | |
{ | |
printf("地址越界!\r\n"); | |
return; | |
} | |
if(WriteAddr % STM_SECTOR_SIZE) | |
{ | |
printf("地址非FLASH扇区首地址!\r\n"); | |
return; | |
} | |
/* 开始写入 */ | |
fmc_unlock(); | |
while(1) | |
{ | |
if(NumToWrite == 0)// 上一个扇区刚好完整写完 | |
{ | |
break; | |
} | |
else if(NumToWrite < STM_SECTOR_SIZE)// 剩余要写入的内容不到一个扇区 | |
{ | |
fmc_page_erase(WriteAddr); // 擦除这个扇区 | |
for(i = 0; i < NumToWrite; i += 2) | |
{ | |
temp = (uint16_t)pBuffer[i + 1] << 8; | |
temp |= (uint16_t)pBuffer[i]; | |
fmc_halfword_program(WriteAddr, temp); | |
WriteAddr += 2; | |
} | |
break; // 写入结束,退出 while | |
} | |
else | |
{ | |
fmc_page_erase(WriteAddr); // 擦除这个扇区 | |
// 写入整个扇区 | |
for(i = 0; i < STM_SECTOR_SIZE; i += 2) | |
{ | |
temp = (uint16_t)pBuffer[i + 1] << 8; | |
temp |= (uint16_t)pBuffer[i]; | |
fmc_halfword_program(WriteAddr, temp); | |
WriteAddr += 2; | |
} | |
pBuffer += STM_SECTOR_SIZE; | |
NumToWrite -= STM_SECTOR_SIZE; | |
} | |
} | |
fmc_lock(); // 上锁 | |
} | |
// 跳转到应用程序段 | |
//appxaddr: 用户代码起始地址 | |
void iap_load_app(uint32_t appxaddr) | |
{ | |
unsigned char i = 0; | |
if(((*(__IO uint32_t*)appxaddr)&0x2FFE0000)==0x20000000) // 检查栈顶地址是否合法 | |
{ | |
/* 首地址是 MSP,地址 + 4 是复位中断服务程序地址 */ | |
jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4); | |
/* 关闭全局中断 */ | |
__set_PRIMASK(1); | |
/* 关闭滴答定时器,复位到默认值 */ | |
SysTick->CTRL = 0; | |
SysTick->LOAD = 0; | |
SysTick->VAL = 0; | |
/* 设置所有时钟到默认状态 */ | |
//RCC_DeInit(); | |
rcu_deinit(); | |
/* 关闭所有中断,清除所有中断挂起标志 */ | |
for (i = 0; i < 8; i++) | |
{ | |
NVIC->ICER[i]=0xFFFFFFFF; | |
NVIC->ICPR[i]=0xFFFFFFFF; | |
} | |
/* 使能全局中断 */ | |
__set_PRIMASK(0); | |
/* 在 RTOS 工程,这条语句很重要,设置为特权级模式,使用 MSP 指针 */ | |
__set_CONTROL(0); | |
MSR_MSP(*(__IO uint32_t*)appxaddr); // 初始化 APP 堆栈指针 (用户代码区的第一个字用于存放栈顶地址) | |
jump2app(); // 跳转到 APP. | |
/* 跳转成功的话,不会执行到这里,用户可以在这里添加代码 */ | |
printf("-----------jump error!-----------\n"); | |
while (1) | |
{ | |
} | |
} | |
} |
# 3、码 APP
那么 APP 区主要干几件事
# 这里记得设置一下
int main(){ | |
//0、配置中断向量表 | |
SCB->VTOR = FLASH_BASE | 0x08000; | |
//1、初始化 | |
while(1){ | |
//2、接收升级数据包 | |
updatarecieve(); | |
if(updata){ | |
//3、更新升级标志位 | |
//4、复位 | |
NVIC_SystemReset(); | |
} | |
} | |
} |
具体的代码有点多,也没有整理。大家感兴趣直接上 gitee 看吧。
项目全部代码