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

Bootloader核心原理与简单实现:从零写一个bootloader

目录

0 相关阅读

1 为什么需要boot loader?

2 写一个最简单的bootloader

原理与思想

步骤

bootloader工程

1. 选择工程代码烧录的区域

2. 定义APP的工程代码存放区域(方便后续跳转)

3. printf重定向和关闭外设函数

4.【重点】跳转APP程序函数

5. main()函数(bootloader主逻辑)

APP工程

1. 选择工程代码烧录区域

2. main()函数

最终效果:

3 常见问题

1. 从bootloader跳转到APP需要几步?

2. 在bootloader的跳转函数中为什么要设置MSP=app初始栈顶指针?

1. 栈的基本作用

2. 为什么必须重新设置MSP?

问题场景:

具体问题:

总结:

3. Bootloader跳转APP时需要注意什么?

1. 如果Bootloader里面有freertos,则跳转App之前需要关闭 systick定时器和关中断。在App中需要先反向初始化外设、时钟,然后再初始化外设和时钟,再开启中断。

2. 在跳转APP后应该在app的所有初始化(时钟)之前,先deinit,再init所有外设,像锁相环这种外设,不是你修改一下参数,就能重新整定的,它需要重新回到激励,重新设置参数


0 相关阅读

下面的文章是我之前写的相关博客,可配合本文食用:

STM32启动流程与bootloader全面解析:从上电复位到进入main函数

揭秘:基于Bootloader的IAP如何实现程序更新

1 为什么需要boot loader?

  1. 简化固件更新:Bootloader 允许通过串行接口(如UART、USB、SPI等)在不使用编程器的情况下更新单片机的固件。这使得开发和维护过程更加便捷,尤其是对于那些已经部署在现场的设备。

  2. 分离应用和编程逻辑:通过使用 Bootloader,可以将应用程序代码与编程和启动逻辑分开。这样可以简化应用程序的开发,因为开发者不需要处理底层的启动和初始化细节。

  3. 安全性增强:Bootloader 可以集成安全机制,如加密和签名验证,以确保只有经过验证和授权的固件能够被写入和执行。这有助于防止恶意代码的注入和固件篡改。

  4. 硬件初始化:在一些复杂的单片机应用中,Bootloader 可以处理初始的硬件配置和初始化工作,如配置时钟、初始化外设等,然后将控制权交给主应用程序。

  5. 多应用支持:Bootloader 可以支持多应用程序管理,允许在单片机上运行多个独立的应用程序,并在需要时选择启动不同的应用。

  6. 复原机制:如果在固件更新过程中出现错误,Bootloader 可以提供复原机制,如保持一个稳定的备份版本或进入安全模式,以确保设备不会因为更新失败而变砖。

2 写一个最简单的bootloader

我们现在来写一个最简单的bootloader:

程序内容只有三步:取出 app 的地址 -> 设置MSP寄存器的数值为APP地址(初始化APP栈顶指针)-> 跳转到APP

配置cubemx的过程略过。(简单配置一下时钟,再配置一个串口1为异步即可)

原理与思想

Bootloader 和 APP 是两个工程,两个工程都有自己的启动文件。bootloader如果存在,就会先进bootloader的启动文件,然后到bootloader的main()函数,执行完bootloader的流程,然后跳转到APP的Reset_Handler,执行APP的启动文件,再进APP的main()函数。

Flash布局 (0x08000000)
├── Bootloader区 (0x08000000 - 0x08019000)
│   ├── Bootloader的向量表
│   ├── Bootloader的启动代码
│   └── Bootloader的主逻辑
└── 应用程序区 (0x08019000 - (0x08019000+0x67000))├── 应用程序的向量表(已偏移)├── 应用程序的启动代码└── 应用程序的主逻辑

步骤

bootloader工程

1. 选择工程代码烧录的区域

2. 定义APP的工程代码存放区域(方便后续跳转)
#define APP_FLASH_ADDR 0x08019000
3. printf重定向和关闭外设函数
#ifdef __GNUC__#define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
#else#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__*//*******************************************************************@brief  Retargets the C library printf  function to the USART.*@param  None*@retval None
******************************************************************/
PUTCHAR_PROTOTYPE
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1,0xFFFF);// SEGGER_RTT_PutChar(0,ch);return ch;
}// 关闭外设函数
void DisablePeripherals(void)
{// turn off RTC timer__HAL_RCC_RTC_DISABLE();// disable irq__disable_irq();
}
4.【重点】跳转APP程序函数
typedef void (*pFunction)(void);
static pFunction JumpToApplication;void JumpToApp(void)
{uint32_t jumpAddr, armAddr;// read the first 4 bytes of ApparmAddr = *(uint32_t*)APP_FLASH_ADDR;for (uint16_t i = 0;i < 1000;++i){printf("bootloader running[%d]...\r\n",i);}// 1.the range of ram's addr is 0x20000000~0x2001FFFF// 2.This indicates that the application's entry point address is within the valid range of RAM.// 3.the first 4 bytes of APP_FLASH_ADDR indicates App's initial stack top pointer(SP)// 4.__IO == volatileif (((*(__IO uint32_t*)APP_FLASH_ADDR) & 0x2FFE0000) == 0x20000000){// 获取应用程序的入口地址(即应用程序的复位中断服务函数的地址)jumpAddr = *(__IO uint32_t*)(APP_FLASH_ADDR + 4);// 将函数指针 = 复位中断服务函数地址JumpToApplication = (pFunction)jumpAddr;// 设置栈顶指针为应用程序栈顶指针的初始值__set_MSP(*(__IO uint32_t*)APP_FLASH_ADDR);// 跳转到应用程序复位中断服务函数,开始执行JumpToApplication();}
}
  • 步骤拆解

读取 APP 的栈顶指针APP_FLASH_ADDR是 APP 在 Flash 中的起始地址,对应 APP 中断向量表的第 1 个元素(栈顶指针,见笔记MCU启动:从上电到运行main函数完整流程中的向量表结构)。

验证 APP 有效性(*(__IO uint32_t*) APP_FLASH_ADDR)&0x2FFE0000 == 0x20000000是关键检查:

  • STM32 的 SRAM 地址范围通常是0x20000000 ~ 0x2001FFFF(假设 SRAM 大小为 128KB,可以看参考手册的地址映射图)。

  • *(__IO uint32_t*) APP_FLASH_ADDR: 读取应用程序起始地址(APP_FLASH_ADDR)处的第一个字(初始栈指针值)。

  • & 0x2FFE0000: 这是一个掩码,用于检查地址是否落在有效的RAM范围内。    

    • 掩码 0x2FFE0000 的二进制形式:0010 1111 1111 1110 0000 0000 0000 0000

    • 目的是忽略地址的低位(如对齐位或保留位)低位都是偏移量不需要关心,只需要检查高位是否匹配RAM基地址。

    例子:

    经过 & 0x2FFE0000 操作后,一个有效的、指向 RAM 区域的栈指针,其高位部分必须恰好等于 0x20000000

  • (0x20001234 & 0x2FFE0000) = 0x20000000 -> 有效

  • (0x2001FFFF & 0x2FFE0000) = 0x20000000 -> 有效

  • (0x20020000 & 0x2FFE0000) = 0x20020000 -> 无效 (不在SRAM有效地址范围内)

  • (0x08001234 & 0x2FFE0000) = 0x00000000 -> 无效 (不在SRAM有效地址范围内)

  • (0x00000000 & 0x2FFE0000) = 0x00000000 -> 无效 (不在SRAM有效地址范围内)

  • == 0x20000000: 验证 masked 后的地址是否等于 0x20000000(STM32的RAM起始地址)。

获取 APP 入口地址APP_FLASH_ADDR + 4是 APP 向量表的第 2 个元素(复位向量),存储的是 APP 的入口函数地址(即 APP 启动文件中的Reset_Handler)。

设置栈指针并跳转

  • __set_MSP(...):将主栈指针(MSP)设置为 APP 的栈顶指针(APP 运行需要自己的栈空间)。

  • JumpToApplication():通过函数指针调用 APP 入口地址,完成跳转(此后 Bootloader 失去控制权)。

5. main()函数(bootloader主逻辑)
int main(void)
{// 设置中断向量表的偏移,我们将bootloader放在0x08000000的位置// 所以中断向量表偏移到0x08000000SCB->VTOR = 0x08000000 | 0x0;/* 系统的一些初始化 */HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();/* 系统的一些初始化 */// 关闭外设DisablePeripherals();// 跳转到应用程序复位中断服务函数JumpToApp();while (1){}
}

执行流程

① 初始化硬件:包括 HAL 库、系统时钟、GPIO、UART(为调试打印做准备)。

② 配置向量表:SCB->VTOR = 0x8000000指定 Bootloader 自己的中断向量表在0x08000000(Bootloader 运行时用自己的向量表响应中断)。

③ 调用JumpToApp()尝试跳转:若成功,不会返回;若失败(如 APP 无效),则进入死循环。

APP工程

1. 选择工程代码烧录区域
  • 之前我们在 Bootloader 中定义了 APP 的起始地址:#define APP_FLASH_ADDR 0x8019000(对应 Bootloader 占用0x08000000~0x08018FFF,共 100KB)。

  • 因此,这个 APP 必须被烧写到 Flash 的0x08019000地址开始的区域(需在编译时通过链接脚本配置 APP 的 Flash 起始地址,确保与 Bootloader 的定义一致)。

  • 若烧写地址错误(比如烧到0x08000000),会覆盖 Bootloader 工程烧写在 flash 上的代码,导致整个系统无法启动。

2. main()函数
int main(void)
{// 设置向量表偏移并使能全局中断SCB->VTOR = FLASH_BASE | 0x00019000;__enable_irq();/* 系统的一些初始化 */HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();/* 系统的一些初始化 */while (1){printf("hello world! at [%d] tick\r\n", HAL_GetTick());HAL_Delay(10);}
}
  • 重点:中断向量表的 “重定向”(即重新设置中断向量表的偏移)

//设置中断向量表偏移,并使能全局中断
SCB->VTOR = FLASH_BASE | 0x19000;
__enable_irq();

这是 APP 代码中最核心的配置,必须结合 Bootloader 和中断向量表的原理理解:

  • SCB->VTOR:是 Cortex-M 内核中 “中断向量表偏移寄存器”,用于指定当前使用的中断向量表在 Flash 中的起始地址。

    • FLASH_BASE:STM32 Flash 的基地址(0x08000000)。

    • 0x19000:偏移量,恰好对应 APP 在 Flash 中的起始地址(0x08000000 + 0x19000 = 0x08019000)。

    • 这句代码的作用:告诉 CPU“现在使用 APP 自己的中断向量表(位于0x08019000),而非 Bootloader 的向量表(位于0x08000000)”。

  • 为什么必须配置?Bootloader 运行时,会将SCB->VTOR设置为自己的向量表地址(0x08000000);当 Bootloader 跳转到 APP 后,若不重新配置SCB->VTOR,CPU 会继续使用 Bootloader 的向量表,导致 APP 的中断(如串口中断、定时器中断)无法正确响应(因为向量表中没有 APP 的中断服务函数地址)。

  • __enable_irq():Bootloader 在跳转前调用了__disable_irq()(禁用全局中断),避免跳转过程被中断干扰。因此 APP 启动后,需要重新使能全局中断,确保自己的中断功能正常。

最终效果:

运行完bootloader后跳转到应用程序的复位中断复位函数,开始运行应用程序的工程。

3 常见问题

1. 从bootloader跳转到APP需要几步?

三步:

  1. 取出 app 的地址

  2. 设置MSP寄存器的数值为APP地址(初始化APP栈顶指针)

  3. 跳转到APP

2. 在bootloader的跳转函数中为什么要设置MSP=app初始栈顶指针?

1. 栈的基本作用

首先理解栈在ARM Cortex-M中的重要性:

  • 函数调用时的局部变量存储

  • 中断发生时的上下文保存

  • 函数参数传递

  • 返回地址保存

2. 为什么必须重新设置MSP?

问题场景:
// Bootloader运行时的栈情况
Bootloader栈空间: 0x20001000 - 0x20001FFF (4KB)
当前栈指针: 0x20001500 (已经使用了一部分)// 如果直接跳转,不重置MSP:
应用程序期望的栈空间: 0x20002000 - 0x20002FFF (4KB)
但实际栈指针还是: 0x20001500 ← 这会导致严重问题!
具体问题:
  1. 栈空间重叠污染

    1. Bootloader栈数据会污染应用程序栈空间

    2. 应用程序的局部变量可能覆盖Bootloader的栈数据

  2. 栈溢出风险

    1. 应用程序不知道Bootloader已经使用了多少栈空间

    2. 可能很快耗尽剩余的栈空间,导致硬件错误

  3. 中断处理问题

    1. 中断发生时,上下文会保存在错误的栈位置

    2. 可能导致数据损坏或程序崩溃

总结:

设置MSP为应用程序的初始栈顶指针是必需的,因为:

  1. 栈空间隔离:确保应用程序使用自己独立的栈空间

  2. 避免污染:防止Bootloader栈数据影响应用程序

  3. 符合架构规范:模拟硬件复位时的标准行为

  4. 稳定性保障:避免栈溢出和内存冲突导致的崩溃

这就像给应用程序一个"干净的开始",确保它在预期的内存环境中正常运行。

3. Bootloader跳转APP时需要注意什么?

1. 如果Bootloader里面有freertos,则跳转App之前需要关闭 systick定时器和关中断。在App中需要先反向初始化外设、时钟,然后再初始化外设和时钟,再开启中断。

2. 在跳转APP后应该在app的所有初始化(时钟)之前,先deinit,再init所有外设,像锁相环这种外设,不是你修改一下参数,就能重新整定的,它需要重新回到激励,重新设置参数

http://www.dtcms.com/a/423250.html

相关文章:

  • MongoDB到关系型数据库:JSON字段如何高效转换?
  • 网站排名优化原理一个公司能备案多个网站吗
  • 苏大团队联合阿丘科技发表异常生成新方法:创新双分支训练法,同步攻克异常图像生成、分割及下游模型性能提升难题。
  • wordpress如何使用百度主动推送seo短视频网页入口引流下载
  • Docker 镜像加速安装MySQL操作步骤
  • 量子计算技术全景:从硬件路线到AI融合
  • 人工智能-机器学习day1
  • 济南网站制作企业建设部标准定额网站
  • 微服务组件-Eureka 技术详解
  • ARM架构下I/O内存映射全面技术分析
  • 大学网站建设管理办法岳阳市网站建设推广
  • Java 操作 XML 及动态生成报告:从解析到实战
  • 网络配置config.xml的android.mk解析
  • 网站导读怎么做wordpress二级目录创建
  • 分布式限流
  • ES-DE 前端模拟器最新版 多模拟器游戏启动器 含游戏ROM整合包 最新版
  • 【Linux网络】TCP协议
  • 分布式排行榜系统设计方案
  • 西双版纳住房和城乡建设局网站上海手机网站建设价格
  • oracle多租户环境CDB与PDB操作
  • 超市营销型网站建设策划书手机网站建站用哪个软件好
  • 使用宏实现高效的分页查询功能
  • 从语言到向量:自然语言处理中的核心转换技术与实践
  • 申请一个网站需要多少钱网站怎么添加统计代码
  • 基于机器学习的异常流量检测系统的设计与实现(原创)
  • 网站建设人员组成做网上商城网站
  • 新天力:食品容器安全与创新的领航者
  • C++_day4
  • 多解法详解与边界处理——力扣7.整数反转
  • 网站开发 python网站员工风采