Bootloader + OTA 升级框架笔记
支持 UART 和 CAN 两种通信方式的 STM32F407 嵌入式 Bootloader 与 OTA 固件升级框架。
一、基本概念
什么是 Bootloader
Bootloader 是芯片上电后最早运行的引导程序,负责:
- 硬件初始化(时钟、外设)
- 检查是否有固件升级请求
- 验证 APP 固件合法性
- 跳转到 APP 或进入 OTA 升级模式
什么是 RAM 和 Flash
| 特性 | Flash | RAM (SRAM) |
|---|---|---|
| 用途 | 存储程序代码和常量 | 运行时变量和栈 |
| 容量 (F407) | 1MB | 128KB |
| 持久性 | 掉电不丢失 | 掉电丢失 |
| 写入方式 | 需擦除后编程 (字对齐) | 直接读写 |
| 速度 | 较慢 (需等待状态) | 快 (单周期访问) |
什么是 OTA
OTA (Over-The-Air) 指通过通信接口(UART/CAN/蓝牙/WiFi)远程升级固件,无需物理连接调试器。本框架使用有线方式(UART/CAN),但传输层抽象设计允许扩展到无线模块。
二、Flash 内存分区(STM32F407)
STM32F407 内部 Flash 共 1MB,分为 12 个扇区:
| 扇区 | 起始地址 | 大小 | 用途 |
|---|---|---|---|
| 0 | 0x08000000 | 16KB | Bootloader |
| 1 | 0x08004000 | 16KB | Bootloader |
| 2 | 0x08008000 | 16KB | Bootloader |
| 3 | 0x0800C000 | 16KB | 配置区(魔数/标志) |
| 4 | 0x08010000 | 64KB | APP |
| 5 | 0x08020000 | 128KB | APP |
| 6 | 0x08040000 | 128KB | APP |
| 7 | 0x08060000 | 128KB | APP |
| 8 | 0x08080000 | 128KB | OTA 下载暂存区 |
| 9 | 0x080A0000 | 128KB | OTA 下载暂存区 |
| 10 | 0x080C0000 | 128KB | OTA 下载暂存区 |
| 11 | 0x080E0000 | 128KB | OTA 下载暂存区 |
+------------------+ 0x08000000
| Bootloader (48KB)| 扇区 0~2
+------------------+ 0x0800C000
| Config (16KB) | 扇区 3
+------------------+ 0x08010000
| APP (448KB)| 扇区 4~7
+------------------+ 0x08080000
| OTA Temp (512KB)| 扇区 8~11
+------------------+ 0x08100000
设计要点:
- Bootloader 48KB 留有充足的代码扩展空间
- 配置区独占一个扇区,擦除配置区不影响 Bootloader 或其他区域
- OTA 下载区 (512KB) >= APP 区 (448KB),确保能容纳完整固件
- APP 和 OTA 下载区按扇区边界对齐,简化擦除操作
三、启动流程(魔数机制)
上电复位
│
▼
┌──────────────┐
│ 初始化时钟 │
│ 初始化传输层 │
└──────┬───────┘
│
▼
┌──────────────┐
│ 读取配置区 │──── 魔数无效? ──→ 初始化配置区(首次启动)
│ (0x0800C000) │
└──────┬───────┘
│ 魔数 = 0x424F4F54 ("BOOT")
▼
┌──────────────┐
│ boot_mode == │── 是 ──→ 进入 OTA 模式
│ OTA? │
└──────┬───────┘
│ 否
▼
┌──────────────┐
│ APP 合法? │──── 否 ──→ 进入 OTA 模式 (等待升级)
│ (检查SP+PC) │
└──────┬───────┘
│ 是
▼
┌──────────────┐
│ 跳转到 APP │
└──────────────┘
魔数机制说明:
- 配置区魔数
0x424F4F54("BOOT" 的 ASCII),标识配置区已被正确初始化 - 芯片首次上电时 Flash 为空 (0xFF),魔数不匹配,Bootloader 自动初始化配置区
boot_mode字段控制启动路径:0xAA55AA55= OTA 模式,0x00000000= 正常启动
四、OTA 核心状态机
三个命令(Host → Bootloader)
| 命令 | 代码 | 说明 |
|---|---|---|
| CMD_OTA_START | 0x01 | 开始 OTA,携带固件大小、CRC32、版本号 |
| CMD_OTA_DATA | 0x02 | 传输固件数据块 |
| CMD_OTA_ABORT | 0x03 | 中止当前传输,状态机回 IDLE |
四个状态码(Bootloader → Host)
| 状态码 | 代码 | 说明 |
|---|---|---|
| STATUS_ACK | 0x10 | 数据帧接收成功,等待下一帧 |
| STATUS_NAK | 0x11 | 帧错误(CRC/格式/状态不允许) |
| STATUS_BUSY | 0x12 | 设备忙(正在执行 Flash 操作) |
| STATUS_COMPLETE | 0x14 | OTA 完成,固件校验通过,设备即将复位 |
完整升级时序
Host (上位机) Bootloader (STM32)
│ │
│ │ [IDLE]
│── CMD_OTA_START ────────────────────→│
│ (fw_size, crc32, version) │ 擦除 OTA 下载区
│←─ STATUS_READY ─────────────────────│ [START_RCVD]
│ │
│── CMD_OTA_DATA (chunk 0) ───────────→│ 写入 Flash
│←─ STATUS_ACK ───────────────────────│ [DOWNLOADING]
│ │
│── CMD_OTA_DATA (chunk 1) ───────────→│ 写入 Flash
│←─ STATUS_ACK ───────────────────────│
│ ... (循环 N 次) ... │
│ │
│── CMD_OTA_DATA (chunk N, 最后一块) ─→│ 写入 Flash
│←─ STATUS_COMPLETE ──────────────────│ [COMPLETE]
│ │
│ │ CRC32 校验通过
│ │ 拷贝到 APP 区
│ │ 系统复位...
│ │
│ │ [IDLE] 重新启动
│ │ 跳转到新 APP
帧格式:
| STX(0xAA) | CMD/STATUS(1B) | LEN(2B,BE) | DATA(NB) | CRC16(2B) | ETX(0x55) |
1B 1B 2B 0~1024B 2B 1B
- CRC16 采用 MODBUS 多项式,校验范围:STX 到 DATA 末尾
- LEN 为大端序,最大 1024 字节
- 单帧最大长度 = 4(头) + 1024(数据) + 3(尾) = 1031 字节
五、传输层架构(核心设计)
统一接口
c
typedef struct transport_ops {
int32_t (*init)(void *params);
int32_t (*deinit)(void);
int32_t (*send)(const uint8_t *data, uint32_t len, uint32_t timeout);
int32_t (*recv)(uint8_t *buf, uint32_t len, uint32_t timeout);
void (*flush)(void);
} transport_ops_t;
上层(OTA 状态机)只调用 transport_send() / transport_recv(),不感知底层是 UART 还是 CAN。
切换协议方法
c
/* 运行时切换传输层, 自动 deinit 旧层、init 新层 */
transport_switch(&transport_uart); /* 切换到 UART */
transport_switch(&transport_can); /* 切换到 CAN */
条件编译保护
c
/* boot_config.h */
#define TRANSPORT_UART_ENABLED // 启用 UART
//#define TRANSPORT_CAN_ENABLED // 启用 CAN (二选一或共存)
- 启用多个传输层时,
DEFAULT_TRANSPORT宏选定默认使用的协议 - 编译时如果未启用任何传输层,
#error编译报错 - 每个传输层的
.c文件内使用#ifdef TRANSPORT_XXX_ENABLED守卫
六、串口传输层详解
文件: Bootloader/Transport/transport_uart.c
- 基于 STM32 HAL 库
HAL_UART_Transmit/HAL_UART_Receive阻塞收发 - 配置参数通过
uart_config_t传入(UART 句柄 + 波特率) - 默认波特率 115200,8N1 无流控
- 收发都带超时参数,防止死等
flush()清空 UART DR 寄存器,丢弃残留数据
不使用 HAL 时: 文件内含裸机寄存器实现(通过 #else 分支),直接操作 USART SR/DR 寄存器。
七、Flash 擦写操作
文件: Bootloader/Core/flash_ops.c
核心操作:
| 函数 | 说明 |
|---|---|
flash_erase_sector(sector) |
擦除单个扇区(F407 扇区 0~11) |
flash_erase_range(addr, size) |
擦除地址范围(自动计算覆盖扇区) |
flash_write_words(addr, data, words) |
按 32-bit 对齐写入 |
flash_write_bytes(addr, data, len) |
字节数组写入(内部处理对齐和补齐) |
flash_read(addr, buf, len) |
直接内存映射读取 |
flash_calc_crc32(addr, len) |
计算 Flash 区域的 CRC32 |
注意事项:
- Flash 写入前必须擦除(擦除后为 0xFF)
- STM32F4 支持 Word (32-bit) 编程,写入地址必须 4 字节对齐
- 擦除/写入期间 Flash 不可读,需等待 BSY 标志清除
- 所有 Flash 操作函数内部自动 unlock/lock,调用者无需手动操作
八、APP 验证与跳转
文件: Bootloader/Core/app_validator.c
验证 APP 合法性
c
int app_validate(uint32_t app_addr, uint32_t app_size, uint32_t expected_crc);
验证项:
- 栈指针 (MSP) 检查 --- 初始 SP 值必须落在 SRAM 范围内 (0x20000000 ~ 0x20020000)
- 复位向量检查 --- Reset_Handler 地址必须在 APP Flash 范围内 (0x08010000 ~ 0x08080000)
- Thumb 模式检查 --- Reset_Handler 的 bit0 必须为 1(Cortex-M 要求)
- CRC32 校验(可选)--- 全固件完整性检查
跳转到 APP
c
void app_jump(uint32_t app_addr) __attribute__((noreturn));
跳转步骤:
cpsid i--- 关闭全局中断SCB->VTOR = app_addr--- 重设向量表基址(APP 中断生效)MSR MSP, sp_init--- 设置 APP 栈指针BX reset_handler--- 跳转到 APP 的 Reset_Handler
九、APP 端触发升级
文件: App/ota_trigger.c
APP 在运行时可通过以下方式触发 OTA:
c
/* 仅设置标志, 不立即复位 */
ota_trigger_set_flag(); // 写入 boot_mode = BOOT_MODE_OTA 到配置区
// ... 等待合适的时机 ...
NVIC_SystemReset(); // 手动触发复位
/* 或一步到位:设置标志并立即复位 */
ota_trigger_request(); // 设置标志 + 软复位, 函数不返回
工作原理:
- APP 调用
ota_trigger_request() - 函数写入配置区
boot_mode = BOOT_MODE_OTA (0xAA55AA55) - 通过
SCB->AIRCR触发系统软复位 - Bootloader 启动后检测到 OTA 标志,进入 OTA 模式
十、链接脚本
Bootloader 链接脚本 (Bootloader/linker_stm32f407_boot.ld)
| 区域 | 起始地址 | 大小 |
|---|---|---|
| FLASH | 0x08000000 | 48KB |
| SRAM | 0x20000000 | 128KB |
- 栈大小: 2KB, 无堆(Bootloader 不需要动态内存)
APP 链接脚本 (App/linker_stm32f407_app.ld)
| 区域 | 起始地址 | 大小 |
|---|---|---|
| FLASH | 0x08010000 | 448KB |
| SRAM | 0x20000000 | 128KB |
- 栈大小: 4KB, 堆大小: 1KB
- APP 的向量表位于 0x08010000,需在 startup 中设置
SCB->VTOR = 0x08010000
十一、移植清单
将本框架移植到其他 STM32 芯片时,需要修改以下内容:
1. boot_config.h --- Flash 分区
| 参数 | F407 值 | 说明 |
|---|---|---|
FLASH_BASE |
0x08000000 | Flash 基址(F1/F2/F4 相同) |
FLASH_SIZE |
1024*1024 | 芯片 Flash 总容量 |
FLASH_SECTOR_COUNT |
12 | 扇区个数 |
BOOTLOADER_SIZE |
48KB | 根据需要调整 |
APP_START_ADDR |
0x08010000 | APP 起始地址 |
APP_SIZE |
448KB | APP 最大容量 |
OTA_DOWNLOAD_SIZE |
512KB | OTA 区容量 (>= APP_SIZE) |
2. flash_ops.c --- 扇区表
修改 sector_table[] 数组,填入目标芯片的扇区分布。
3. app_validator.c --- SRAM 范围
修改 SRAM_BASE 和 SRAM_SIZE 宏。
4. 链接脚本
修改 ORIGIN 和 LENGTH 值。
5. 传输层 init()
根据目标芯片的外设基地址和 HAL 库配置调整。
十二、整体架构总结
┌──────────────────────────────────────────────────────┐
│ 上位机 (Host) │
│ (UART/CAN 发送 OTA 帧) │
└─────────────────────┬────────────────────────────────┘
│ UART 或 CAN
┌─────────────────────▼────────────────────────────────┐
│ 传输层 (Transport) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ transport.h 统一接口 (init/send/recv) │ │
│ │ transport_uart.c UART 实现 │ │
│ │ transport_can.c CAN 实现 (分段传输) │ │
│ └─────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────┤
│ OTA 状态机 (ota_state_machine) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 3 条命令: START / DATA / ABORT │ │
│ │ 4 个状态: IDLE → START_RCVD → DOWNLOADING │ │
│ │ → VERIFYING → COMPLETE │ │
│ └─────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────┤
│ Flash 操作层 (flash_ops) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ erase / write / read / crc32 │ │
│ └─────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────┤
│ APP 校验与跳转 (app_validator) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ SP 检查 / PC 检查 / Thumb 检查 / CRC32 校验 │ │
│ │ 跳转: SCB->VTOR → MSP → BX ResetHandler │ │
│ └─────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────┤
│ 配置区 (Config) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 扇区 3: 魔数 | boot_mode | OTA参数 | APP标志 │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ APP 区域 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ota_trigger.c: 设置 OTA 标志 + 软复位 │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
代码文件索引:
| 文件 | 作用 |
|---|---|
Bootloader/Config/boot_config.h |
全局配置、Flash 分区、协议常量 |
Bootloader/Core/flash_ops.h/c |
Flash 擦除/写入/读取/CRC32 |
Bootloader/Core/ota_state_machine.h/c |
OTA 帧解析、状态机、CRC16 |
Bootloader/Core/app_validator.h/c |
APP 校验 (SP/PC/CRC) 与跳转 |
Bootloader/Core/bootloader.h/c |
主启动流程 + OTA 主循环 |
Bootloader/Transport/transport.h/c |
抽象接口 + 协议切换 |
Bootloader/Transport/transport_uart.h/c |
UART 传输层 |
Bootloader/Transport/transport_can.h/c |
CAN 传输层 (分段协议) |
Bootloader/linker_stm32f407_boot.ld |
Bootloader 链接脚本 |
App/ota_trigger.h/c |
APP 端触发 OTA |
App/linker_stm32f407_app.ld |
APP 链接脚本 |