基于STM32实现OTABootLoader 第三章——构建BootLoader程序

📅 发布时间:2026/7/16 15:27:45 👁️ 浏览次数:
基于STM32实现OTABootLoader 第三章——构建BootLoader程序
参考教程https://www.bilibili.com/video/BV1SatHeBEVG/?spm_id_from333.1387.favlist.content.click一、分区跳转功能实现1、规划AB分区参数1STM32F103C8T6的Flash容量是64KB一个扇区的大小为1KB则扇区的编号为0~63。2首先对Flash的空间做一个初步的规划B区占用20KB0~19号扇区A区占用44KB20~63号扇区。3在User组中添加main.h文件用于存放BootLoader相关及其它的比较重要的宏定义等内容。#ifndef __MAIN_H #define __MAIN_H #define STM32_FLASH_SADDR 0x08000000 //FLASH程序区起始地址 #define STM32_PAGE_SIZE 1024 //FLASH一个扇区的字节数 #define STM32_PAGE_NUM 64 //FLASH的扇区数页数 #define STM32_B_PAGE_NUM 20 //B区所占页数 #define STM32_A_PAGE_NUM STM32_PAGE_NUM-STM32_B_PAGE_NUM //A区所占页数 #define STM32_A_STAET_PAGE STM32_B_PAGE_NUM //A区第一个扇区的编号 #define STM32_A_SADDR STM32_FLASH_SADDR STM32_A_STAET_PAGE * STM32_PAGE_SIZE //A区起始地址 #endif2、OTA Flag的定义、读取及判定1OTA Flag的定义①在main.h文件中定义OTA信息相关结构体其中包含成员OTA Flag。#define OTA_SET_FLAG 0xAABB1122 //FLAG为该值时代表OTA Flag置位 typedef struct{ uint32_t OTA_Flag; }OTA_InfoCB; extern OTA_InfoCB OTA_Info;②在main.c文件中定义OTA信息相关结构体变量。OTA_InfoCB OTA_Info;2在AT24C02.c文件中增加读取OTA信息相关结构体的函数并在AT24C02.h文件中声明。OTA信息相关结构体存储在AT24C02地址为0的地方。①AT24C02.c文件增加内容使用memset函数需包含string.h文件void AT24C02_ReadOTAInfo(void) { memset(OTA_Info, 0, sizeof(OTA_InfoCB)); //将OTA_Info整块内存空间清零 AT24C02_ReadData(0, (uint8_t *)OTA_Info, sizeof(OTA_InfoCB)); //读出OTA_Info内容 }②AT24C02.h文件增加内容void AT24C02_ReadOTAInfo(void);3在Hardware组中添加Boot.c文件和Boot.h文件并编写判定OTA Flag的逻辑。①Boot.c文件#include stm32f10x.h // Device header #include main.h #include Serial.h #include Boot.h void BootLoader_Branch(void){ if(OTA_Info.OTA_Flag OTA_SET_FLAG){ Serial_Printf(OTA更新\r\n); } else{ Serial_Printf(跳转A分区\r\n); } }②Boot.h文件#ifndef __BOOT_H #define __BOOT_H void BootLoader_Branch(void); #endif3、跳转A分区前的工作1Corte-M3处理器拥有R0‐R15的寄存器组其中R13作为堆栈指针SP。SP有两个但在同一时刻只能有一个可以看到这也就是所谓的“banked”寄存器。①R0~R12都是32位通用寄存器用于数据操作。②Cortex-M3拥有两个堆栈指针然而它们是banked因此任一时刻只能使用其中的一个。主堆栈指针MSP复位后缺省使用的堆栈指针用于操作系统内核以及异常处理例程包括中断服务例程进程堆栈指针PSP由用户的应用程序代码使用③当呼叫一个子程序时由R14存储返回地址。④R15指向当前的程序地址如果修改它的值就能改变程序的执行流。2运行B区的程序时通用寄存器中会保存一些数据用于BootLoader程序如果要从B区跳转到A区那么本次单片机上电循环内不会再返回运行BootLoader程序也就是说从B区跳转到A区时可以直接舍弃B区在通用寄存器中保存的数据、SP指针、PC指针以及R14中保存的返回地址不需要“保存现场”。3当指令指针跳转到A区时对于A区的程序而言相当于按下了复位按键在进入A区程序的main函数以前会先运行__main函数对通用寄存器进行初始化所以不需要再去手动初始化通用寄存器。4实际上前面说“单片机上电/复位后总是从Flash的起始位置开始运行”这个说法并不准确上电/复位后CPU首先从内存的固定位置即向量表的起始地址获取两样东西先设置好主堆栈指针MSP再拿到复位中断服务函数的入口地址Reset_Handler然后跳转过去执行所以当指令指针跳转到A区时实际上并非真正意义上的复位应仿照按下复位按键的逻辑手动设置SP指针和PC指针的初始值。①如果不人为干预向量表将存放在Flash的起始位置这是默认没有BootLoader程序的情况如果有BootLoader程序那么A区程序的向量表应该存放在A区的起始位置0x08005000。②向量表的第一个成员__initial_sp就是SP指针的初始值初始值为多少取决于编译结果而编译结果则取决于程序中设置了多少全局变量等。无论SP指针的初始值为多少它都是可以通过一段固定代码访问到的也就是存放的位置固定为0x08005000那么从B区跳转到A区时可以读出此值并赋给SP指针以完成初始值的设置。③向量表的第二个成员Reset_Handler就是PC指针的初始值同样的无论PC指针的初始值为多少它都是可以通过一段固定代码访问到的也就是存放的位置固定为0x08005004那么从B区跳转到A区时可以读出此值并赋给PC指针以完成初始值的设置。5在BootLoader中初始化了一些外设在将“运行权”交给A区应用功能程序前BootLoader应当将使用过的外设恢复为默认状态若A区应用功能程序需要使用这些外设则由A区应用功能程序重新初始化。4、无OTA事件时的分区跳转实现1在STM32中若需要直接访问寄存器须通过汇编指令。在Boot.c文件中封装函数负责初始化跳转A区后的SP指针。__asm void MSR_SP(uint32_t addr) { MSR MSP, r0 BX r14 //主调函数返回相当于return语句 } //进入该函数后函数的第一个形参addr会存在通用寄存器R0中汇编指令仅支持访问寄存器不支持访问变量2编写将使用过的外设恢复为默认状态的函数。①在Boot.c文件中定义外设恢复默认状态函数。void BootLoader_Clear(void) { USART_DeInit(USART1); GPIO_DeInit(GPIOA); GPIO_DeInit(GPIOB); }②在Boot.h文件中声明刚刚定义的函数。void BootLoader_Clear(void);3由于无法直接修改PC寄存器的值需要定义指向函数的指针指针指向A区程序的复位向量调用该函数指针就相当于调用子函数从而间接修改PC寄存器的值。①在Boot.c文件中定义函数指针并封装函数负责初始化跳转A区后的SP指针和PC指针。load_a load_A; //函数指针 void LOAD_A(uint32_t addr) { /* 先判断addr索引得到的__initial_sp是否在RAM的地址范围中是则对SP指针和PC指针赋初始值 */ if((*(uint32_t *)addr 0x20000000)((*(uint32_t *)addr 0x20004FFF))) { MSR_SP(*(uint32_t *)addr); //对SP指针赋初始值 load_A (load_a)(*(uint32_t *)(addr 4)); //取出复位中断服务函数的地址 BootLoader_Clear(); //恢复外设默认状态 load_A(); //直接访问复位中断服务函数修改PC指针 } }②在Boot.h文件中重命名void类型的函数指针并声明刚刚定义的函数。typedef void (*load_a)(void); __asm void MSR_SP(uint32_t addr); void LOAD_A(uint32_t addr);③更新BootLoader_Branch函数判断无OTA事件时调用跳转A分区的子函数。void BootLoader_Branch(void) { if(OTA_Info.OTA_Flag OTA_SET_FLAG) { Serial_Printf(OTA更新\r\n); } else { Serial_Printf(跳转A分区\r\n); LOAD_A(STM32_A_SADDR); } }5、功能开发阶段性调试1在main.c文件中添加如下调试代码。#include stm32f10x.h // Device header #include Serial.h #include MyDMA.h #include Delay.h #include MyI2C.h #include W25Q64.h #include AT24C02.h #include MyFLASH.h #include main.h #include Boot.h OTA_InfoCB OTA_Info; int main(void) { /*串口模块初始化*/ Serial_Init(); U0Rx_PtrInit(); MyDMA_Init(); /*AT24C02模块初始化*/ MyI2C_Init(); /*W25Q64模块初始化*/ W25Q64_Init(); AT24C02_ReadOTAInfo(); //读取OTA Flag BootLoader_Branch(); while (1) { } }2本章构建的工程是B分区的程序A分区的程序需要另外准备。①选择一个古早开发的成熟工程按照下图所示将起始地址更改为0x08005000。②将向量表偏移字段由0x0更改为0x5000注意是偏移量应为相对于Flash起始地址的差值。3依次将A分区程序和B分区程序下载到单片机中理想情况下串口助手输出“跳转A分区”字样A分区程序随后开始运行。二、程序更新功能实现