一、STM32 BootLoader 的基本概念
BootLoader 是 STM32 系列微控制器中的一段引导程序,负责在芯片启动时初始化硬件、加载主应用程序,并支持APP应用程序固件更新(IAP/OTA)。它通常存储在 Flash 的固定地址(如 0x08000000 或系统存储器 0x1FFF0000),是设备启动和升级的核心组件。
二、BootLoader 的使用场景
1、出厂预置 BootLoader使用场景:
STM32 内置系统 BootLoader(System Memory BootLoader)区别于内部FLASH的常用存储区段,有独立的存储空间,支持通过串口、USB、CAN 等接口烧录程序,这个内置的BootLoader当作设备急救箱来使用,当我们自定义的BootLoader出错时,需要使用出厂BootLoader来对自定义的BootLoader程序进行修复更新。
2、自定义 BootLoader使用场景:
用户自己开发的 BootLoader,通过它进行应用程序的下载和更新,实现APP应用程序的在线升级等功能,注意,自定义的BootLoader需要独立的FLASH存储空间,和APP部分的代码区分开来。
三、BootLoader 需要配置的核心功能
1、硬件初始化:
配置时钟、GPIO、串口等外设,并关闭中断响应。
2、固件更新:
通过串口、USB、SD 卡等接口接收新固件程序,写入指定 Flash 区域。
3、跳转执行:
根据标志位或超时判断是否进入APP应用程序升级模式,或直接跳转执行应用程序。
四、进入 BootLoader 的方式
1、硬件引脚配置:
设置 BOOT0 和 BOOT1 引脚(如 BOOT0=1,BOOT1=0),从系统存储器启动,进入系统内置的出厂BootLoader程序。
2、软件跳转:
在应用程序中配置串口接收数据,当接收到自己定义的”BootLoader更新命令“时,将APP更新标志位置1,存储在FLASH等非易失性存储器中,调用 NVIC_SystemReset() 函数进行软件复位,进入BootLoader程序后通过判断APP更新标志位进行软件更新。
五、自定义 BootLoader 的实现步骤
1、Flash 分区设计:
BootLoader 区:通常占用前 16KB(0x08000000~0x08004000),取决于BootLoader程序文件的大小。
应用程序区:剩余 Flash(0x08004000~0x0807FFFF)。
标志位存储区:预留 Flash 或 EEPROM 用于保存升级标志(如 0x08003000)。
2、硬件初始化:
配置时钟(如 HSI/LSI)、GPIO、通信接口(UART/USB)、关闭中断。
3、 通信协议实现:
接收升级命令(如串口接收到 0x55AA判定为升级命令),设置标志位并触发复位。
4、固件验证与写入:
擦除应用程序 Flash 区域。
写入新固件APP程序(有条件的可以在写入之前对固件程序进行备份,防止出错)。
跳转到应用程序,设置向量表偏移(VTOR)和堆栈指针(MSP),运行更新后的APP程序。
5、实现步骤:
1、将自 定义的BootLoader程序下载到内部FLASH中,放在最前区域。
2、开机,初始化时钟,GPIO和串口,屏蔽中断。
3、根据串口接收到的指令,来判断是否需要更新下载APP应用程序。
4、如果需要更新,进行APP代码部分FLASH备份与擦除,FLASH成功擦除后,将接收到的新APP程序写入内部FLASH原APP应用程序的固定地址中。
5、完成4后,读入中断向量表地址,同时重新设置主堆栈指针MSP的地址(默认在程序最头部),设置APP函数入口地址(默认在MSP地址偏移量+4)。
6、运行APP函数。
六、BootLoader代码示例及注释
#include "stm32g070xx.h"
#include "main.h"
#define BOOTLOADER_ADDR 0x08000000 //BootLoader的首地址
#define APP_ADDR 0x08008000 //自定义的APP程序头地址
#define FLASH_APP_CODE_SIZE 0x18000 //APP程序大小
typedef void (*pFunction)(void); //函数指针,用来调用同一类型的函数
void ERROR_Process(); //错误处理函数
/*****************************************************************************
[函数名称]BootLoader_JumpToApp
[函数功能]BootLoader跳转到APP函数
[参 数]app_addr:APP程序入口地址
*****************************************************************************/
void BootLoader_JumpToApp(uint32_t app_addr)
{
pFunction jumo_to_application; //跳转函数指针,指向APP运行函数头地址后调用函数
uint32_t jump_address; //跳转地址变量
jump_address = *(__IO uint32_t*)(app_addr + 4); //计算APP运行函数头地址,为MSP主堆栈指针+4个地址偏移量
jumo_to_application = (pFunction)jump_address; //函数指针指向APP运行函数头地址
__set_MSP(*(__IO uint32_t*)app_addr); //设置主堆栈指针
jumo_to_application(); //跳转到APP应用程序,开始运行应用主程序
}
/*****************************************************************************
[函数名称]BootLoader_UpdateApp
[函数功能]BootLoader的APP更新程序
[参 数]app_addr:APP程序入口地址
*****************************************************************************/
void BootLoader_UpdateApp(uint32_t app_addr)
{
//如果有足够的FLASH空间,记得备份原始APP数据,防止BootLoader升级出错,可以回退版本
//擦除APP应用程序内存部分的FLASH空间
HAL_FLASH_Unlock(); //解锁FLASH
FLASH_EraseInitTypeDef EraseInitStruct; //配置FLASH结构体
uint32_t PageError = 0; //页错误默认为0
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; //配置按页擦除
EraseInitStruct.Page = (FLASH_APP_CODE_SIZE + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE;; //配置擦除页起始地址,保证页地址对齐
EraseInitStruct.NbPages = 60; //配置擦除多少页数,根据自己的APP代码大小而定
EraseInitStruct.Banks = FLASH_BANK_1; //配置块区域为1(F4系列芯片存在两个BANK区域,需要选择)
HAL_StatusTypeDef status = HAL_FLASHEx_Erase(&EraseInitStruct, &PageError); //开始擦除存储APP源代码的FLASH内存
if(status != HAL_OK) //擦除失败处理
{
ERROR_Process(); //擦除失败错误,进入死循环,可增加自定义的处理代码
}
//假设使用串口DMA来接收新的APP应用数据,串口自定义开启配置,本文不再展现
uint32_t u32DatsSize = 10; //假设u32DatsSize为串口接收缓存数据个数
uint8_t u8ReceiveBuffer[u32DatsSize] = {0}; //假设u8ReceiveBuffer为串口接收缓存
static uint32_t APP_Write_Addr = APP_ADDR; //记录APP应用程序写入实时地址
while(u32DatsSize-- > 0) //将接收到的APP应用程序数据写入固定位置的FLASH内存中
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_FAST, APP_Write_Addr++, u32ReceiveBuffer);//地址偏移1个字节量
}
HAL_FLASH_Lock(); //写入完成,锁定FLASH
}
/*****************************************************************************
[函数名称]main
[函数功能]主函数
[参 数]
*****************************************************************************/
int main()
{
//正常情况下,APP更新标志位需要存在FLASH中,掉电不丢失
uint8_t IsCodeUpdate = 0; //假设IsCodeUpdate为BootLoader更新判断标志位
//初始化程序
HAL_Init(); //HAL库初始化
SystemClock_Config(); //时钟初始化
MX_GPIO_Init(); //GPIO口初始化
MX_UART_Init(); //串口初始化
__disable_irq(); //关闭中断
//通过串口接收到的命令设置IsCodeUpdate标志位,判断是否需要更新APP程序
if(IsCodeUpdate == 1) //IsCodeUpdate标志位应该存在内部FLASH非易失性存储器中,防止数据丢失
{
IsCodeUpdate = 0; //清除APP更新标志位(实际应该通过FLASH擦除清除,这里只是演示)
BootLoader_UpdateApp(APP_ADDR); //更新APP应用程序
}
else //如果没有更新指令
BootLoader_JumpToApp(APP_ADDR); //跳转到APP运行函数
//更新指令通过串口接收,如果有更新指令,将IsCodeUpdate标志位置1,然后运行软件复位
}
//错误处理函数
void ERROR_Process()
{
while(1)
;
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/DearJULY/article/details/148410053
|