Bootloader + OTA --- Ymodem 协议版本
基于 Ymodem 文件传输协议 的 STM32F407 嵌入式 Bootloader 与 OTA 固件升级框架。
目录
- [Ymodem 协议详解](#Ymodem 协议详解)
- 代码架构
- [Flash 分区](#Flash 分区)
- 启动流程
- 集成与构建
- [上位机 Ymodem 发送工具](#上位机 Ymodem 发送工具)
- 移植指南
- [与 demo1 (自定义协议) 的区别](#与 demo1 (自定义协议) 的区别)
一、Ymodem 协议详解
1.1 协议背景
Ymodem 是 1980 年代由 Chuck Forsberg 开发的串行文件传输协议,是 Xmodem 的增强版本。被广泛用于嵌入式开发中的固件升级 (SecureCRT、Tera Term、minicom 的 Ymodem 发送功能)。
Ymodem 相比 Xmodem 的改进:
- 支持批处理:文件名和大小在传输前发送
- 更大数据包:1024 字节 (Xmodem 为 128 字节)
- 更好的 CRC:使用 16-bit CRC 校验
1.2 帧格式
Ymodem 定义了两种数据帧:
┌─────────────────────────────────────────────────────────┐
│ SOH (0x01) 128 字节数据帧 │
├──────┬──────┬──────┬──────────────────┬─────────────────┤
│ SOH │ 序号 │ ~序号 │ 128 字节数据 │ CRC-16 (2B) │
│ 1B │ 1B │ 1B │ 128B │ MSB first │
└──────┴──────┴──────┴──────────────────┴─────────────────┘
总长度: 133 字节
┌─────────────────────────────────────────────────────────┐
│ STX (0x02) 1024 字节数据帧 (Ymodem-1K) │
├──────┬──────┬──────┬──────────────────┬─────────────────┤
│ STX │ 序号 │ ~序号 │ 1024 字节数据 │ CRC-16 (2B) │
│ 1B │ 1B │ 1B │ 1024B │ MSB first │
└──────┴──────┴──────┴──────────────────┴─────────────────┘
总长度: 1029 字节
字段说明:
| 字段 | 大小 | 说明 |
|---|---|---|
| SOH / STX | 1 Byte | 帧类型标识: SOH=0x01 表示 128B 数据, STX=0x02 表示 1024B 数据 |
| 序号 | 1 Byte | 数据包序号。Packet 0 固定为 0x00, 数据包从 0x01 开始递增 (溢出后回绕到 0x00) |
| ~序号 | 1 Byte | 序号的按位取反 (one's complement), 用于帧头完整性校验 |
| 数据 | 128B / 1024B | 实际数据。不足时用 0x1A (^Z, SUB) 填充 |
| CRC-16 | 2 Bytes | 16-bit CRC 校验值, 覆盖数据区域 (不包含帧头), 高字节在前 (MSB first) |
1.3 Packet 0 (信息块)
Packet 0 是 Ymodem 区别于 Xmodem 的关键特性。它使用 SOH 128B 帧 , 序号固定为 0x00, 包含文件名和文件大小信息:
数据区格式:
┌──────────────────────────────────────────────────────┐
│ FILENAME.EXT\0SIZE\0\0\0\0... (填充至 128 字节) │
└──────────────────────────────────────────────────────┘
示例: "firmware.bin\00001048576\0\0\0..." (128 bytes)
└──文件名──┘ └─十进制ASCII大小──┘ └─NUL填充──┘
- 文件名以
\0(NUL) 结尾 - 文件大小以十进制 ASCII 字符串表示, 以
\0结尾 - 剩余字节填充 NUL (0x00) 或 SUB (0x1A)
1.4 控制字符
| 字符 | 十六进制 | 说明 |
|---|---|---|
| SOH | 0x01 | 128 字节数据帧开始 |
| STX | 0x02 | 1024 字节数据帧开始 |
| EOT | 0x04 | End of Transmission (文件传输结束) |
| ACK | 0x06 | Acknowledge (正确接收) |
| NAK | 0x15 | Negative Acknowledge (请求重传) |
| CAN | 0x18 | Cancel (取消传输, 需连续发送两次) |
| 'C' | 0x43 | 请求 CRC-16 模式传输 |
1.5 CRC-16 计算
Ymodem 使用 CRC-16 CCITT (Xmodem 变体),与常见的 CRC-CCITT 有细微差别:
| 参数 | Ymodem/Xmodem CRC |
|---|---|
| 多项式 | 0x1021 = x¹⁶ + x¹² + x⁵ + 1 |
| 初始值 | 0x0000 (非 0xFFFF!) |
| 最终异或 | 0x0000 |
| 输入处理 | 每字节 MSB 优先 |
| 传输顺序 | 高字节在前 (MSB first) |
| 校验范围 | 仅数据区 (不含 SOH/STX/序号/~序号) |
已知测试向量: 字符串 "123456789" → CRC = 0x31C3
c
/* C 语言实现 */
uint16_t ymodem_crc16(const uint8_t *data, uint32_t len) {
uint16_t crc = 0x0000;
while (len--) {
crc ^= (uint16_t)(*data++) << 8;
for (int i = 0; i < 8; i++) {
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021;
else
crc <<= 1;
}
}
return crc;
}
1.6 完整传输时序
Bootloader (接收端 / STM32) Host (发送端 / 上位机)
│ │
│====='C'====='C'====='C' (握手请求)==========→│
│ │
│←=== SOH 00 FF [文件名\0大小\0...] CRC CRC ====│ Packet 0
│===== ACK ====================================→│
│ │
│←=== STX 01 FE [1024 bytes data] CRC CRC =====│ Packet 1
│===== ACK ====================================→│
│ │
│←=== STX 02 FD [1024 bytes data] CRC CRC =====│ Packet 2
│===== ACK ====================================→│
│ │
│ ... (重复直到所有数据) ... │
│ │
│←=== SOH NN ~NN [<=128 bytes last data] CRC ==│ 末包 (SOH)
│===== ACK ====================================→│
│ │
│←=== EOT =====================================│ 文件结束
│===== NAK ====================================→│ (请求确认)
│←=== EOT =====================================│ 第二次 EOT
│===== ACK ====================================→│ (确认结束)
│====='C'======================================→│ (请求批次结束包)
│←=== SOH 00 FF [128 bytes 全0] CRC CRC =======│ 批次结束
│===== ACK ====================================→│
│ │
传输完成, Bootloader 开始校验固件
1.7 错误处理
┌─────────────────────────────────────────┐
│ CRC 校验失败 → 发送 NAK, 发送端重传 │
│ 序号不匹配 → 发送 NAK, 发送端重传 │
│ 接收超时 → 发送 NAK, 发送端重传 │
│ 超过重试次数 → 发送 CAN CAN, 主动中止 │
│ 收到 CAN CAN → 中止传输, 回到 IDLE 状态 │
│ NAK (EOT后) → 发送端重发 EOT (确认机制) │
└─────────────────────────────────────────┘
最大重试次数: 10 次 (可配置 YMODEM_MAX_RETRIES)
单帧超时: 3000ms (可配置 YMODEM_TIMEOUT_MS)
握手超时: 60000ms (可配置 YMODEM_INIT_RETRIES × YMODEM_HANDSHAKE_MS)
二、代码架构
┌──────────────────────────────────────────────────┐
│ 上位机 (PC Host) │
│ SecureCRT / Tera Term / lrzsz │
│ 通过 UART 发送 Ymodem 文件 │
└─────────────────────┬────────────────────────────┘
│ UART (115200 8N1)
┌─────────────────────▼────────────────────────────┐
│ 传输层 (Transport Layer) │
│ ┌─────────────────────────────────────────────┐ │
│ │ transport.h/c 统一接口 (init/send/recv) │ │
│ │ transport_uart.c UART 实现 (HAL 阻塞收发) │ │
│ │ transport_can.h CAN 实现 (可选) │ │
│ └─────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ Ymodem 协议层 (ymodem.h/c) │
│ ┌─────────────────────────────────────────────┐ │
│ │ ymodem_receive_start() 握手 + Packet 0 │ │
│ │ ymodem_receive_block() 逐个接收数据块 │ │
│ │ ymodem_receive_finish() EOT + 批次结束 │ │
│ │ ymodem_crc16() CRC-16 CCITT 计算 │ │
│ │ ymodem_cancel() 发送 CAN CAN 中止 │ │
│ └─────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ Flash 操作层 (flash_ops.h/c) │
│ ┌─────────────────────────────────────────────┐ │
│ │ flash_erase_sector/range 擦除扇区 │ │
│ │ flash_write_words/bytes 写入 Flash │ │
│ │ flash_read 直接映射读取 │ │
│ │ flash_calc_crc32 计算 CRC32 │ │
│ │ flash_copy_range OTA→APP 区域拷贝 │ │
│ └─────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ APP 校验与跳转 (app_validator.h/c) │
│ ┌─────────────────────────────────────────────┐ │
│ │ app_validate() SP/PC/Thumb 三检 │ │
│ │ app_jump() VTOR→MSP→BX Reset │ │
│ └─────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────┤
│ 配置区 (Config Area, 扇区 3) │
│ ┌─────────────────────────────────────────────┐ │
│ │ magic | boot_mode | ota_fw_size | ota_crc32 │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ APP 区域 │
│ ┌─────────────────────────────────────────────┐ │
│ │ ota_trigger.c OTA 标志设置 + 软复位 │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
2.1 源码文件清单
| 文件 | 行数 | 说明 |
|---|---|---|
Bootloader/Config/boot_config.h |
~100 | 全局配置: Flash 分区, Ymodem 常量, 条件编译 |
Bootloader/Core/ymodem.h |
~85 | Ymodem 协议 API: 错误码, 公开接口 |
Bootloader/Core/ymodem.c |
~280 | Ymodem 协议完整实现: CRC-16, 握手, 收帧, 批次结束 |
Bootloader/Core/flash_ops.h |
~75 | Flash 操作 API |
Bootloader/Core/flash_ops.c |
~220 | Flash 擦写读, CRC32, 区域拷贝 |
Bootloader/Core/app_validator.h |
~45 | APP 校验 + 跳转 API |
Bootloader/Core/app_validator.c |
~110 | SP/PC/Thumb 三检, VTOR 切换, 跳转 |
Bootloader/Core/bootloader.h |
~35 | Bootloader 主入口 |
Bootloader/Core/bootloader.c |
~180 | 主流程: 启动→配置→OTA or 跳转 |
Bootloader/Transport/transport.h |
~70 | 传输层抽象接口 + 全局指针 |
Bootloader/Transport/transport.c |
~20 | 传输层切换实现 |
Bootloader/Transport/transport_uart.h |
~20 | UART 配置参数 |
Bootloader/Transport/transport_uart.c |
~90 | UART HAL 实现 |
Bootloader/linker_stm32f407_boot.ld |
~60 | Bootloader 链接脚本 (48KB) |
App/ota_trigger.h |
~35 | APP 端 OTA 触发 API |
App/ota_trigger.c |
~55 | OTA 标志写入 + 软复位 |
App/linker_stm32f407_app.ld |
~80 | APP 链接脚本 (448KB) |
2.2 核心 API 设计
c
/* ── Ymodem 协议层 ── */
/* 启动 Ymodem 接收: 发送 'C' 握手, 接收 Packet 0 */
ymodem_result_t ymodem_receive_start(const void *transport,
ymodem_file_info_t *file_info);
/* 接收一个数据块 (128B 或 1024B), 内部自动 CRC 校验 + ACK */
/* 返回 YMODEM_OK + len=0 表示收到 EOT, 需调用 receive_finish */
ymodem_result_t ymodem_receive_block(const void *transport,
uint8_t *buffer, uint32_t *len);
/* 完成传输: EOT 双确认 + 批次结束包 */
ymodem_result_t ymodem_receive_finish(const void *transport);
/* 计算 CRC-16 CCITT (Ymodem/Xmodem 变体) */
uint16_t ymodem_crc16(const uint8_t *data, uint32_t len);
2.3 传输层抽象
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;
/* 全局传输层指针: 上层通过 g_transport->send/recv 透明调用 */
extern transport_ops_t *g_transport;
三、Flash 分区
STM32F407 内部 Flash 共 1MB, 划分为 4 个功能区域:
+──────────────────+ 0x08000000
│ Bootloader 48KB │ 扇区 0~2 (16KB+16KB+16KB)
+──────────────────+ 0x0800C000
│ Config 16KB │ 扇区 3 (独立扇区, 存放魔数/启动模式)
+──────────────────+ 0x08010000
│ APP 448KB │ 扇区 4~7 (64KB+128KB+128KB+128KB)
+──────────────────+ 0x08080000
│ OTA Temp 512KB │ 扇区 8~11 (4×128KB, 固件下载暂存区)
+──────────────────+ 0x08100000
设计要点:
- Bootloader 48KB 预留 3 个扇区, 代码扩展空间充足
- 配置区独占扇区 3 (16KB), 擦除不影响 Bootloader
- OTA 暂存区 (512KB) ≥ APP 区 (448KB), 确保能容纳完整固件
- 所有分区按扇区边界对齐, 简化擦除操作
- OTA 先写入暂存区 → CRC32 校验 → 拷贝到 APP 区, 保证升级原子性
四、启动流程
上电复位
│
▼
┌──────────────┐
│ 1. 初始化时钟 │ HSE 8MHz → PLL → 168MHz
│ 2. 初始化传输层 │ UART 115200 8N1
└──────┬───────┘
│
▼
┌──────────────┐
│ 读取配置区 │──── 魔数 ≠ 0x424F4F54? ──→ 初始化配置区(首次启动)
│ (0x0800C000) │
└──────┬───────┘
│ 魔数 = 0x424F4F54 ("BOOT")
▼
┌──────────────┐
│ boot_mode == │── 是 ──→ OTA 模式 ──→ Ymodem 握手 → 接收固件 → 拷贝 → 复位
│ OTA? │
└──────┬───────┘
│ 否 (正常启动)
▼
┌──────────────┐
│ APP 合法? │
│ SP/PC/Thumb │── 是 ──→ 跳转到 APP (不再返回)
└──────┬───────┘
│ 否
▼
进入 OTA 模式
(等待 Ymodem 升级)
OTA 模式详细流程:
- Ymodem 握手 --- 循环发送
C(0x43), 等待主机响应 Packet 0 - 解析文件信息 --- 提取固件文件名和大小
- 擦除 OTA 下载区 --- 根据大小擦除对应扇区
- 逐块接收 --- 调用
ymodem_receive_block()循环接收, 每块写入 Flash - EOT + 批次结束 ---
ymodem_receive_finish()处理结束握手 - CRC32 校验 --- 对整个下载区做 CRC32 校验
- 拷贝到 APP 区 ---
flash_copy_range()从 OTA 区搬运到 APP 区 - 验证新固件 ---
app_validate()检查合法性 - 清除 OTA 标志 --- 写配置区 boot_mode = NORMAL
- 系统复位 ---
NVIC_SystemReset(), 重启后加载新固件
五、集成与构建
5.1 工具链
- 编译器:
arm-none-eabi-gcc(ARM GNU Toolchain) - IDE: STM32CubeIDE / STM32CubeMX (HAL 库外设初始化)
- Flash/Debug: OpenOCD + ST-LINK V2
5.2 创建 Bootloader 工程
-
STM32CubeMX → 选择 STM32F407VG → 配置:
- RCC: HSE Crystal
- USART1: Asynchronous, 115200 8N1, PA9(TX)/PA10(RX)
- 不需要 FreeRTOS
-
生成代码, 选择 STM32CubeIDE 工具链
-
将本框架源文件复制到工程:
Bootloader/Config/*→Core/Inc/Bootloader/Core/*.c→Core/Src/,*.h→Core/Inc/Bootloader/Transport/*.c→Core/Src/,*.h→Core/Inc/
-
替换链接脚本: 使用
linker_stm32f407_boot.ld -
修改
main.c:cint main(void) { HAL_Init(); bootloader_run(); /* 不会返回 */ }
5.3 创建 APP 工程
-
STM32CubeMX → 选择 STM32F407VG → 配置:
- RCC: HSE Crystal
- 用户 APP 外设...
- 中断向量表偏移: 需要在代码中手动设置
-
替换链接脚本: 使用
linker_stm32f407_app.ld -
在
main()或SystemInit()中添加:cSCB->VTOR = 0x08010000; -
复制
App/ota_trigger.c和App/ota_trigger.h到工程 -
APP 需要触发 OTA 时调用:
cota_trigger_request(); /* 永不返回, 自动复位 */
六、上位机 Ymodem 发送工具
任何支持 Ymodem 文件发送的串口终端均可作为上位机:
| 工具 | 平台 | 使用方式 |
|---|---|---|
| SecureCRT | Windows/Linux | Transfer → Send Ymodem → 选择 .bin 文件 |
| Tera Term | Windows | File → Transfer → Ymodem → Send |
| minicom | Linux | Ctrl+A S → 选择 Ymodem → 选择文件 |
| lrzsz | Linux | sz --ymodem firmware.bin < /dev/ttyUSB0 |
| ExtraPuTTY | Windows | 支持 Ymodem 文件传输 |
6.1 传输步骤 (以 SecureCRT 为例)
- 将 STM32 设置为 OTA 模式 (APP 中调用
ota_trigger_request()) - Bootloader 启动后开始发送
C字符 - SecureCRT → Transfer → Send Ymodem → 选择
firmware.bin - SecureCRT 解析文件名和大小, 发送 Packet 0
- Bootloader ACK, 开始数据传输
- 进度条显示传输进度
- 传输完成后 Bootloader 自动校验、拷贝、复位
七、移植指南
将本框架移植到其他 STM32 芯片时需修改:
7.1 boot_config.h
| 参数 | F407 值 | 说明 |
|---|---|---|
FLASH_SIZE |
1024×1024 | 芯片 Flash 总容量 |
FLASH_SECTOR_COUNT |
12 | 扇区个数 |
BOOTLOADER_SIZE |
48KB | Bootloader 占用 |
APP_START_ADDR |
0x08010000 | APP 起始地址 |
APP_SIZE |
448KB | APP 最大容量 |
OTA_DOWNLOAD_SIZE |
512KB | OTA 下载区 (≥ APP_SIZE) |
7.2 flash_ops.c --- 扇区表
修改 sector_table[] 数组, 填入目标芯片的扇区分布。
7.3 app_validator.c --- SRAM 范围
修改 SRAM_BASE / SRAM_SIZE 宏, 以及 APP_START_ADDR / APP_SIZE。
7.4 链接脚本
修改 ORIGIN 和 LENGTH 值匹配新的 Flash 分区。
7.5 传输层 init()
适配目标芯片外设基地址和引脚配置。
八、与 demo1 的区别
| 特性 | demo1 (自定义协议) | demo2 (Ymodem) |
|---|---|---|
| 帧协议 | 自定义: STX(0xAA)+CMD+LEN+DATA+CRC16(MODBUS)+ETX(0x55) | Ymodem 标准: SOH/STX+SEQ+~SEQ+DATA+CRC16(CCITT) |
| CRC 算法 | CRC-16 MODBUS (poly=0x8005, init=0xFFFF) | CRC-16 CCITT Xmodem 变体 (poly=0x1021, init=0x0000) |
| 文件信息 | CMD_OTA_START 携带 fw_size, crc32, version | Packet 0 携带 filename 和 filesize |
| 上位机 | 需自制上位机 (如 Python 脚本) | 可使用 SecureCRT / Tera Term / lrzsz 等标准工具 |
| 传输层 | 相同 (函数指针表 + 条件编译) | 相同, 完全复用 demo1 的 transport 架构 |
| Flash 分区 | 相同 | 相同 |
| 配置区结构 | 相同 (魔数 + boot_mode) | 相同 |
| APP 校验 | 相同 (SP/PC/Thumb) | 相同 |
为什么选择 Ymodem:
- 上位机工具开箱即用, 无需开发专用发送软件
- 协议标准化, 经过数十年验证, 稳定性好
- 1KB 数据包 + CRC-16 保证传输效率和可靠性
- 1024 字节对齐 STM32F4 Flash 写入, 效率高
代码文件索引
demo2_Ymodem/
├── Bootloader/
│ ├── Config/
│ │ └── boot_config.h 全局配置、Flash 分区、条件编译
│ ├── Core/
│ │ ├── ymodem.h/c Ymodem 协议 (CRC-16, 收帧, 握手, 批次结束)
│ │ ├── flash_ops.h/c Flash 擦写读、CRC32、区域拷贝
│ │ ├── app_validator.h/c APP 校验 (SP/PC/Thumb) + 跳转
│ │ └── bootloader.h/c 主启动流程 + OTA 主循环
│ ├── Transport/
│ │ ├── transport.h/c 传输层抽象接口 + 切换
│ │ └── transport_uart.h/c UART 传输层 (HAL 实现)
│ └── linker_stm32f407_boot.ld Bootloader 链接脚本
├── App/
│ ├── ota_trigger.h/c APP 端 OTA 触发 + 软复位
│ └── linker_stm32f407_app.ld APP 链接脚本
└── README.md 本文件 (协议说明 + 使用指南)
注: 如果需要Ymoden协议,可联系博主发送源码学习。