1. bootloader项目实现
整体运行逻辑:
- 上电或复位后,程序先从
0x08000000 进入 bootloader。
- bootloader 完成系统时钟、GPIO、LED、KEY、COM1 串口初始化。
- bootloader 只在启动阶段读取一次
KEY 状态。
- 如果
KEY 未按下,并且 APP 有效,则跳转到 APP。
- APP 启动后打印 APP 状态,并控制
LED1 慢闪。
- 如果
KEY 已按下,则停留在 bootloader。
- bootloader 停留时打印状态,并控制
LED2 慢闪。
完成以上逻辑 我们就可以实现在bootloder内更新任意的用户APP (在线IAP的内容会在后面讲解实现包裹UART方式和FDCAN方式,本期测评首先实现基础的Bootloader)
2. 硬件资源
项目直接使用Nucleo-U3的默认BSP实现 初始化VCP串口用于Log信息打印
因此该部分硬件配置不再赘述
3. Flash 地址规划
bootloader 和 APP 我们需要放在不同 Flash 区域,防止后下载的程序会覆盖前一个程序。
| 区域 |
起始地址 |
大小 |
说明 |
| Bootloader |
0x08000000 |
64 KB |
APP 校验、APP 跳转 |
| APP |
0x08010000 |
0x001F0000 |
用户应用程序 |
| Flash 结束地址 |
0x08200000 |
2 MB 末尾 |
用于 APP 有效性检查 |
我们可以现在代码中提前定义好这三个宏定义

在keil中 我们可以需要直接更改链接设置 或者编写sct文件来将boot或者APP代码链接到指定位置

bootloader 链接地址:
LR_IROM1 0x08000000 0x00010000
ER_IROM1 0x08000000 0x00010000
4. 启动和跳转原理
STM32U3 是 Cortex-M33 内核。程序启动依赖中断向量表,向量表的前两个 32 位数据非常关键:
、
| 向量表偏移 |
内容 |
作用 |
0x00 |
初始 MSP 值 |
程序启动后的主栈指针 |
0x04 |
Reset_Handler 地址 |
程序复位入口函数 |
正常上电时,CPU 从 bootloader 的向量表启动:
0x08000000: bootloader 初始 MSP
0x08000004: bootloader Reset_Handler
当 bootloader 要跳转到 APP 时,需要模拟一次从 APP 启动的过程。APP 的向量表位于 0x08010000:
0x08010000: APP 初始 MSP
0x08010004: APP Reset_Handler
bootloader 跳转 APP 的步骤如下:
读取 APP 向量表中的初始 MSP。
读取 APP 向量表中的 Reset_Handler。
校验 MSP 是否位于有效 SRAM。
校验 Reset_Handler 是否位于 APP Flash 区域。
关闭中断,避免 bootloader 的中断在跳转过程中打断流程。
反初始化 HAL 和 RCC,让 APP 从干净状态重新初始化外设。
关闭 SysTick,并清除 NVIC 中断使能和挂起标志。
设置 SCB->VTOR = 0x08010000,让中断向量表切换到 APP。
设置 MSP 为 APP 向量表中的初始栈地址。
重新打开中断。
跳转到 APP 的 Reset_Handler。
在跳转前 一定要校验APP是否完整合法 不然会出现各种情况
校验和跳转的核心代码如下
static uint8_t Bootloader_IsAppValid(void)
{
const uint32_t appStackPointer = *(__IO uint32_t *)APP_START_ADDRESS;
const uint32_t appResetHandler = *(__IO uint32_t *)(APP_START_ADDRESS + 4U);
if ((appStackPointer < SRAM_START_ADDRESS) || (appStackPointer >= SRAM_END_ADDRESS))
{
return 0U;
}
if ((appResetHandler < APP_START_ADDRESS) || (appResetHandler >= FLASH_END_ADDRESS))
{
return 0U;
}
if ((appResetHandler & 0x1U) == 0U)
{
return 0U;
}
return 1U;
}
static void Bootloader_JumpToApp(void)
{
uint32_t index;
const uint32_t appStackPointer = *(__IO uint32_t *)APP_START_ADDRESS;
const uint32_t appResetHandler = *(__IO uint32_t *)(APP_START_ADDRESS + 4U);
pFunction jumpToApp = (pFunction)appResetHandler;
__disable_irq();
HAL_RCC_DeInit();
HAL_DeInit();
SysTick->CTRL = 0U;
SysTick->LOAD = 0U;
SysTick->VAL = 0U;
for (index = 0U; index < 8U; ++index)
{
NVIC->ICER[index] = 0xFFFFFFFFU;
NVIC->ICPR[index] = 0xFFFFFFFFU;
}
SCB->VTOR = APP_START_ADDRESS;
__set_MSP(appStackPointer);
__DSB();
__ISB();
__enable_irq();
jumpToApp();
}
核心运行代码如下

实现效果
整理来说最关键的部分就是理清APP跳转的原理 其余部分代码十分简单 难度不大
另外 因为代码中使能了串口打印Log 所以需要勾选MicroLib来实现串口的重定向功能 不然会出现各种奇怪的问题
实现效果(暂未烧录APP部分)

可以看到Bootloadder已经完成APP校验以及跳转判断的逻辑了 接下来我们烧录APP到指定位置看下效果

APP工程中已经提前设置好了向量偏移部分,APP 并不是从 0x08000000 启动,而是从 0x08010000 启动。如果 APP 仍然使用默认向量表地址,APP 中断会错误地去 0x08000000 查找中断入口,导致中断异常或程序跑飞。
因此 APP 工程需要在 SystemInit() 中设置向量表偏移:

APP成功运行
这个时候如果我们手动按住KEY 然后按下复位键 MCU默认也停留在Bootloader中
