当前位置: 首页 > news >正文

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

参考教程:https://www.bilibili.com/video/BV1SatHeBEVG/?spm_id_from=333.1387.favlist.content.click

一、分区跳转功能实现

1、规划AB分区参数

(1)STM32F103C8T6的Flash容量是64KB,一个扇区的大小为1KB,则扇区的编号为0~63。

(2)首先对Flash的空间做一个初步的规划,B区占用20KB(0~19号扇区),A区占用44KB(20~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区起始地址 #endif

2、OTA Flag的定义、读取及判定

(1)OTA 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); #endif

3、跳转A分区前的工作

(1)Corte-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分区程序随后开始运行。

二、程序更新功能实现

http://www.jsqmd.com/news/412592/

相关文章:

  • Storm监控与运维:保障大数据处理系统稳定运行
  • 智能写作领域Top10:多维度解析AI文本改写工具的核心优势
  • AIGC论文助手权威榜单:十大AI文本优化工具全面解析
  • 提示工程架构师解读:提示工程如何优化用户培养
  • 大数据领域Kafka的网络拓扑优化
  • 华为OD机考双机位C卷 - 根据IP查找城市(Java Python JS GO C++ C)
  • MAC地址硬刷工具|修改网卡物理地址,BIOS级写入,重装系统不还原
  • OKX 客户 Colo 内网域名接入方式
  • seedance 2.0牛在哪里?
  • 基于深度学习的违章停车检测系统的设计与实现
  • 如何看待OpenClaw(曾用名:Clawdbot、Moltbot)?
  • C++/Python混合编程之Pybind11的使用
  • SRE 应用稳定性看板-从应用维度监控服务健康状态,基于 Apdex 评分体系
  • 大数据领域数据中台的质量评估方法
  • 使用 Terraform + Terragrunt 管理 AWS 基础设施项目说明
  • **4皇后问题回溯搜索过程**的图文解析、关键函数说明及核心考点总结,结构清晰、逻辑准确
  • 系统思考:自由职业背后的悖论
  • Sora2 免费去水印网站
  • **回溯法在两个经典问题(0-1背包、n皇后)中的应用**的清晰解读,涵盖了搜索树结构、剪枝策略、可行解识别与核心约束条件
  • Learning on the Manifold: Unlocking Standard Diffusion Transformers withRepresentation Encoders
  • **分支限界法(结合回溯思想)求解0-1背包问题**的核心流程与结果
  • 20260225 之所思 - 人生如梦
  • build_fsd_luyan_from_rm——注释
  • 回溯法的两种实现方式(迭代与递归)本质上都是对解空间树进行深度优先搜索(DFS),区别在于控制搜索过程的机制不同
  • WPF implement DelCommand inherited from ICommand from scratch
  • **0-1 背包问题的分支限界法(Branch and Bound)求解框架**,核心融合了**贪心松弛上界估计**与**精确剪枝策略**
  • N9e配置电话告警,实现故障的电话(语音)通知
  • Grafana + Loki 使用说明
  • windows上子系统WSL下载和使用
  • linux系统 Qt 通常的目录结构