当设备需要升级时,若每次都需通过有线连接ST-Link烧录固件会非常不便。为解决这一问题,可引入类似手机系统升级的OTA技术。
OTA(Over-The-Air,空中下载)是通过无线网络(蓝牙/WiFi/4G等)远程更新设备软件的技术,无需拆机或接线即可完成固件升级。
由于程序运行时无法擦写自身所在的Flash区域,因此需要采用bootloader-app分区:
-
bootloader,因其位于首地址0x8000000,设备启动时首先运行,负责擦写app区域,完成擦写后跳转至app
-
app,实现所需功能,也负责接收升级数据包,然后将数据写入指定存储区域
完整的 OTA 升级流程
c
1. App 运行中
↓ 收到升级包
2. App 将新固件存入外部 Flash 或预留分区
↓ 设置标志位,软复位
3. Bootloader 启动
↓ 检测到升级标志
4. Bootloader 将新固件写入 App 区域
↓ 校验文件,清除标志
5. Bootloader 跳转到新 App
↓
6. 升级完成!
已完成bootloader与app的基础功能实现:当bootloader检测到app起始地址非0xFFFFFFFF数据时,即可执行跳转操作。
bootloader实现
起始地址设置,存放第0扇区,大小16KB,目前一个扇区大小即可。第1扇区留着后续实现更多功能在打开,app起始位置在第2扇区。

跳转函数。
c
// 函数指针类型定义
typedef void (*pFunction)(void);
// App 起始地址
#define APP_START_ADDR 0x8008000
void JumpToApp(void)
{
// 1. 检查栈顶地址是否合法(防止跳转到空地址)
if ((*(__IO uint32_t*)APP_START_ADDR) == 0xFFFFFFFF) {
// 地址为空,不跳转
return;
}
// 2. 关闭所有外设中断
__disable_irq();
// 3. 关闭 SysTick 中断,清零计数器
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// 4. 复位所有外设时钟,如串口之类
// 注意:不要复位调试相关的外设,否则 ST-LINK 会断连
// 5. 设置主堆栈指针 (MSP) 为 App 的第一个字
__set_MSP(*(__IO uint32_t*)APP_START_ADDR);
// 6. 获取 App 的复位向量地址(App 起始地址 + 4 字节偏移)
uint32_t jumpAddr = *(__IO uint32_t*)(APP_START_ADDR + 4);
pFunction JumpToApplication = (pFunction)jumpAddr;
// 7. 重新使能中断
__enable_irq();
// 8. 跳转到 App
JumpToApplication();
// 9. 理论上不会执行到这里
while(1);
}
int main(void)
{
/* SysTick end of count event each 10ms */
RCC_GetClocksFreq(&RCC_Clocks);
SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);
/* Add your application code here */
/* Insert 50 ms delay */
Delay(5);
JumpToApp();
/* Infinite loop */
while (1);
}
app实现
起始地址设置,存放在第2扇区,大小规划为第2+3+4扇区即16+16+64 = 128KB。

app的代码按照之前实现的即可,在main()中小小修改即可。
c
#define APP_START_ADDR 0x8008000
int main(void)
{
// 设置中断向量表偏移到 App 起始地址
SCB->VTOR = APP_START_ADDR;
// 设置优先级分组为2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置蓝牙串口中断优先级
NVIC_BT_Configuration();
// 配置WIFI串口中断优先级
NVIC_WIFI_Configuration();
// 所有外设初始化
All_Peripherals_Init();
// 创建任务
All_Task_Create();
// 启动调度器
vTaskStartScheduler();
/* Infinite loop */
while (1);
}
如果没有 SCB->VTOR = APP_START_ADDR; 也能跳转到app,只是会一直重启。原因就是中断发生时,CPU 仍去 0x8000000 找中断向量,而那里是 Bootloader 的向量表,导致程序跑飞重启,若是app中不使用中断,就不会重启。但是用FreeRTOS不可避免使用 #define xPortSysTickHandler SysTick_Handler ,得使用滴答定时中断。
建议使用 STM32CubeProgrammer 官方工具进行固件烧录。该工具功能强大,支持指定地址擦除和烧录操作,能够精准烧录bootloader和app固件。若将bootloader与app固件合并为单一文件,则可实现一次性烧录。
官方工具地址
根据所需下载,注意需要个账号

工具配置,使用ST-Link,SWD

点击Connect之后,就可以对扇区进行擦除,与下载了。

尝试对扇区进行擦除

读取扇区数据,可以看到已经擦除成功,存储数据全为0xFF。

1,分别对bootloader与app烧录
单下载boot
查看地址数据,与bin文件数据一样。

单下载app,因为是烧录boot之后再烧录app,发现程序已经跑起来了,哪怕没有勾选 Run after programming

串口打印数据正常,没有重启
c
Serial communication is normal
Free heap before any task: 0 bytes
After Key_Task: 40584 bytes
After Feeddog_Task: 40352 bytes
2,若是对bootloader与app合并后下载就需要借助合并工具先合并。使用binJoin.exe工具
c
binJoin.exe boot.bin 0x8000000 app.bin 0x8008000 boot_app.bin
之后对boot_app.bin在地址0x8000000出烧录即可。

因为没有勾选 Run after programming ,需要手动按下复位键启动。