stm32_bootloader:基于STM32CubeMX/HAL的串口OTA升级项目

可用于STM32F103C8T6串口OTA升级和Bootloader参考。项目基于CubeMX/HAL提供启动引导、应用固件与PC发送工具,实现上电检测、固件搬运、跳转和Flash页写入。【此简介由AI生成】

Branch2Tags0
FilesLast commitLast update
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago

STM32F103C8T6 Bootloader + APP + OTA

基于 STM32CubeMX + HAL 库的 Bootloader 方案,支持串口 OTA 固件升级。

硬件

项目 参数
MCU STM32F103C8T6(64K Flash / 20K SRAM)
晶振 外部 8MHz HSE
系统时钟 72MHz(PLL x9)
调试串口 USART1 — PA9(TX) / PA10(RX) / 115200-8-N-1

Flash 内存布局

0x0800FFFF ┌──────────────────────┐
           │                      │
           │     OTA 下载区        │
           │     32K              │
           │     起始: 0x08008000  │
0x08008000 ├──────────────────────┤
           │                      │
           │     APP              │
           │     16K              │
           │     起始: 0x08004000  │
0x08004000 ├──────────────────────┤
           │                      │
           │     Bootloader       │
           │     16K              │
           │     起始: 0x08000000  │
0x08000000 └──────────────────────┘

OTA 区内部结构:

0x08008000:  [MAGIC: 4B]  [固件向量表 + 代码]  ...  [0xFF 填充]
             └ 0x5A5A5A5A  └ 从 APP .bin 拷贝

Bootloader 通过检查 0x08008000 处是否为 0x5A5A5A5A 来判断是否有待升级的新固件。


工程结构

Boot/
├── README.md                        # 项目说明
├── CSDN.md                          # 博客文章
├── send_bin.py                      # PC 端 OTA 固件发送工具
├── test.bin                         # 测试固件 (1KB)
├── APP2.bin                         # CLion 编译的 APP2 固件
├── APP2_keil.bin                    # Keil 编译的 APP2 固件
│
├── clion/                           # CLion 工程 (GCC + CubeMX)
│   ├── Bootloader/
│   │   ├── STM32F103XX_FLASH.ld     #   链接脚本: Flash 0x08000000, 16K
│   │   ├── startup_stm32f103xb.s    #   启动文件 (GCC)
│   │   ├── Drivers/                 #   HAL 库 + CMSIS
│   │   └── Core/
│   │       ├── Inc/
│   │       │   ├── bootloader.h     #   OTA Magic + APP 跳转宏定义
│   │       │   └── main.h / usart.h / gpio.h
│   │       └── Src/
│   │           ├── main.c           #   主流程: 初始化 → 延时 → 跳转
│   │           ├── bootloader.c     #   OTA2APP 搬运 + App_Loading 跳转
│   │           ├── stm32f1xx_it.c   #   中断服务 (SysTick)
│   │           ├── syscalls.c       #   newlib 系统调用 (printf 重定向)
│   │           ├── sysmem.c         #   newlib 堆管理
│   │           └── ...
│   │
│   └── APP/
│       ├── STM32F103XX_FLASH.ld     #   链接脚本: Flash 0x08004000, 16K
│       ├── startup_stm32f103xb.s    #   启动文件 (GCC)
│       ├── Drivers/                 #   HAL 库 + CMSIS
│       └── Core/
│           ├── Inc/
│           │   └── main.h / usart.h / gpio.h
│           ├── BSP/
│           │   ├── Serial.h         #   OTA 接收接口
│           │   └── Serial.c         #   串口中断状态机 + Flash 页写入
│           └── Src/
│               ├── main.c           #   初始化 + OTA 接收主循环
│               ├── stm32f1xx_it.c   #   中断服务 (SysTick + USART1)
│               ├── syscalls.c       #   newlib 系统调用
│               ├── sysmem.c         #   newlib 堆管理
│               └── ...
│
└── keil/                            # Keil MDK 工程 (ARM Compiler)
    ├── Bootloader/
    │   ├── Bootloader.ioc           #   CubeMX 工程文件
    │   ├── Drivers/                 #   HAL 库 + CMSIS
    │   ├── MDK-ARM/                 #   Keil 工程文件 (.uvprojx) + 启动文件 + scatter
    │   └── Core/
    │       ├── Inc/
    │       │   ├── bootloader.h     #   OTA Magic + APP 跳转宏定义
    │       │   └── main.h / usart.h / gpio.h
    │       └── Src/
    │           ├── main.c           #   主流程
    │           ├── bootloader.c     #   OTA2APP + App_Loading (printf 用 fputc)
    │           ├── stm32f1xx_it.c   #   中断服务 (SysTick)
    │           └── ...              #   (无 syscalls/sysmem — Keil 用 Microlib)
    │
    ├── APP/                         #   接收 OTA 数据的 APP
    │   ├── APP.ioc                  #   CubeMX 工程文件
    │   ├── Drivers/                 #   HAL 库 + CMSIS
    │   ├── MDK-ARM/                 #   Keil 工程文件 + 启动文件 + scatter
    │   └── Core/
    │       ├── Inc/
    │       ├── BSP/
    │       │   ├── Serial.h         #   OTA 接收接口
    │       │   └── Serial.c         #   串口中断状态机 + Flash 页写入
    │       └── Src/
    │           ├── main.c           #   初始化 + OTA 接收主循环
    │           └── stm32f1xx_it.c   #   中断服务 (SysTick + USART1)
    │
    └── APP2/                        #   OTA 升级的目标固件 (另一份 APP)
        ├── Drivers/
        ├── MDK-ARM/
        └── Core/
            ├── BSP/
            │   └── Serial.c         #   同上
            └── Src/
                ├── main.c           #   同上结构, 业务逻辑可不同
                └── stm32f1xx_it.c

三个 APP 的职责:

工程 位置 用处
clion/APP / keil/APP 烧录到 0x08004000 的运行时固件 正常运行 + 通过串口接收 OTA 数据写入暂存区
keil/APP2 编译生成 .bin, 通过 send_bin.py 发送 OTA 升级的目标固件
clion/Bootloader / keil/Bootloader 烧录到 0x08000000 上电检查 OTA Magic, 有则搬运, 无则跳转

Bootloader 工作流程

上电 / 复位
    │
    ▼
HAL_Init() / SystemClock_Config()
MX_GPIO_Init() / MX_USART1_UART_Init()
    │
    ▼
printf("Bootloader Start...\r\n")
HAL_Delay(3000)                    ← 等待 3 秒
    │
    ▼
App_Loading()
    │
    ├── *(0x08008000) == 0x5A5A5A5A ?
    │   ├── YES ──→ OTA2APP()
    │   │           ├── 擦除 APP 区 (0x08004000 起 16 页)
    │   │           ├── 从 OTA_DATA_ADDR(0x08008004) 逐字拷贝到 APP 区
    │   │           └── 擦除 OTA Flag 页 (清除 Magic)
    │   │
    │   └── NO ──→ 跳过
    │
    ├── 读取 APP 栈顶地址 (0x08004000 处第一个字)
    ├── 校验栈地址是否在 SRAM 合法范围内
    │   ├── 合法 → 设 MSP → 关中断 → 跳转到 APP Reset_Handler
    │   └── 非法 → printf("Address Not Found") → 死循环
    │
    ▼
APP 启动

APP 工作流程 (OTA 接收模式)

Reset_Handler
    │
    ▼
SystemInit() / HAL_Init() / SystemClock_Config()
SCB->VTOR = FLASH_BASE | 0x4000     ← 向量表指向 APP 区
__set_PRIMASK(0)                    ← 恢复中断
    │
    ▼
MX_GPIO_Init() / MX_USART1_UART_Init()
HAL_UART_Transmit("Hello World!\r\n")
    │
    ▼
OTA_Erase()                         ← 擦除 OTA 区 32KB (0x08008000 起)
write_addr = 0x08008000
HAL_UART_Receive_IT(...)            ← 启动串口中断接收
    │
    ▼
while (1) {
    if (rx_flag == 1) {
        OTA_update();               ← 写完一页后发 ACK, 进入 END_CHECK 状态
    }
}

串口中断状态机 (Serial.c):

                       ┌─────────────────────────────┐
                       │                             │
                       ▼                             │
                   ┌──────┐    收到 0xAA              │
           上电 ──→│ IDLE │────────────────────┐     │
                   └──────┘                    │     │
                       ▲                       ▼     │
                       │                 ┌──────────┐ │
                       │         回复 ACK │ HANDSHAKE│ │
                       │                 └──────────┘ │
                       │                       │     │
                       │              收满 1024 字节   │
                       │                       ▼     │
                       │              OTA_WritePage() │
                       │              回复 ACK        │
                       │                       │     │
                       │                       ▼     │
                       │                 ┌──────────┐ │
                       │    4B 不匹配    │ END_CHECK│ │
                       │    退回数据 ───→│ (收4字节) │ │
                       │                 └─────┬────┘ │
                       │                       │     │
                       │              4B == AA 55 FF 00
                       │                       │     │
                       └─────── 回复 ACK ──────┘     │
                                                    │
                              固件数据               │
                         (归入 HANDSHAKE 继续收) ────┘

OTA 串口协议

电平信号

信号 方向 值 含义
握手 PC → STM32 0xAA 请求开始升级
ACK STM32 → PC 0x06 操作成功
结束魔数 PC → STM32 AA 55 FF 00 (4B) 全部数据发送完毕

完整交互时序

PC (send_bin.py)                            STM32 APP
 │                                              │
 │──── 0xAA ────────────────────────────────→   │  握手
 │←──── 0x06 ────────────────────────────────   │  就绪 (OTA 区已擦除)
 │                                              │
 │──── 1024 bytes (raw) ────────────────────→   │  写第 1 页 Flash
 │←──── 0x06 ────────────────────────────────   │
 │──── 1024 bytes (raw) ────────────────────→   │  写第 2 页 Flash
 │←──── 0x06 ────────────────────────────────   │
 │         ... 重复所有块 ...                    │
 │──── 1024 bytes (raw) ────────────────────→   │  写最后 1 页 Flash
 │←──── 0x06 ────────────────────────────────   │
 │                                              │
 │──── AA 55 FF 00 ─────────────────────────→   │  结束魔数 (4字节)
 │←──── 0x06 ────────────────────────────────   │  传输完成

数据格式

PC 端在固件数据头部 插入 4 字节魔数 0x5A5A5A5A(小端序),然后对齐到 1KB 边界,以原始字节流形式发送。

发送数据 = [0x5A 0x5A 0x5A 0x5A] + [固件 .bin 原始内容] + [0xFF ... 填充到 1KB 对齐]

每发送 1024 字节后暂停,等待 STM32 回复 0x06,再发下一块。

防误触发

结束魔数使用 4 字节序列 AA 55 FF 00,误触发概率约为 1 / 2³² ≈ 1 / 42.9 亿。

STM32 端采用回退机制:如果收到的 4 字节与结束魔数不匹配,则将该 4 字节视为下一页固件数据的头部,切回接收模式继续累积。


上位机发送工具 (send_bin.py)

环境

依赖:pyserial

用法

# 发送固件
python send_bin.py COM24 APP2.bin

# 仅检查文件不发送
python send_bin.py COM24 APP2.bin --dry-run

# 查看帮助
python send_bin.py --help

注意 !!!

clion工程使用app2.bin keil工程使用App2_keil.bin 两种编译器对地址的处理不一样 混用容易导致问题

工作原理

  1. 读取 .bin 文件
  2. 在头部插入 OTA Magic 0x5A5A5A5A
  3. 用 0xFF 填充到 1KB 边界
  4. 握手(发 0xAA,等 0x06)
  5. 逐块发送 1KB 数据,每块等 0x06
  6. 发送结束魔数 AA 55 FF 00,等 0x06
  7. 完成

限制

  • 固件原始大小不能超过 32KB(OTA 区限制)
  • 自动对齐到 1KB 页边界

烧录方法

# 烧 Bootloader (0x08000000)
STM32_Programmer_CLI -c port=SWD -w Bootloader.elf

# 烧 APP (0x08004000)
STM32_Programmer_CLI -c port=SWD -w APP.elf

OTA 升级(串口)

# 1. 编译 APP 工程, 生成 .bin 文件
# 2. STM32 上电, 运行 APP 固件
# 3. 发送新固件
conda activate DM_control
python send_bin.py COM24 APP2.bin
# 4. 复位 STM32, Bootloader 自动检测 OTA Magic 并搬运

OTA 升级完整过程

① STM32 上电, 运行 APP (0x08004000)
       │
② PC 通过 send_bin.py 发送新固件
       │  APP 中断接收 → 写入 OTA 区 (0x08008000)
       │  首 4 字节 = 0x5A5A5A5A (Magic)
       │
③ 传输完成, 复位 STM32
       │
④ Bootloader 启动, 检查 OTA 区
       │  *(0x08008000) == 0x5A5A5A5A ?  YES!
       │
⑤ OTA2APP(): 擦除 APP 区 → 拷贝 OTA 数据到 APP → 擦除 OTA Magic
       │
⑥ App_Loading(): 校验 APP 栈 → 跳转到新固件
       │
⑦ 新固件运行!

串口输出示例

正常启动 (无 OTA 升级):

Bootloader Start...
APP SP: 20005000
Start To APP
Hello World!

有 OTA 升级:

Bootloader Start...
OTA firmware found, updating...
Erasing APP area...
Copying firmware from OTA to APP...
Clearing OTA flag...
OTA update complete!

APP SP: 20005000
Start To APP
Hello World!      <-- 新固件

注意事项

  1. 烧录顺序:先烧 Bootloader,后烧 APP。Flash 起始地址不同,不会冲突。
  2. APP 必须在 main 中手动设置 SCB->VTOR,否则中断向量表不匹配。
  3. APP 必须调用 __set_PRIMASK(0),否则 Bootloader 留下的 PRIMASK=1 会屏蔽所有中断。
  4. OTA 升级时 STM32 必须运行 APP,接收逻辑在 APP 中。
  5. OTA 区 32KB 在 APP 启动时会被擦除,Bootloader 搬运完成后也会擦除 Magic 所在页。
  6. 固件大小限制:有效固件约 16KB(APP 区大小),但 OTA 区有 32KB 可用于未来扩展。

Introduction

可用于STM32F103C8T6串口OTA升级和Bootloader参考。项目基于CubeMX/HAL提供启动引导、应用固件与PC发送工具,实现上电检测、固件搬运、跳转和Flash页写入。【此简介由AI生成】

Customize your domain