Bootloader + OTA -Ymodem 协议

Bootloader + OTA --- Ymodem 协议版本

基于 Ymodem 文件传输协议 的 STM32F407 嵌入式 Bootloader 与 OTA 固件升级框架。

目录

  1. [Ymodem 协议详解](#Ymodem 协议详解)
  2. 代码架构
  3. [Flash 分区](#Flash 分区)
  4. 启动流程
  5. 集成与构建
  6. [上位机 Ymodem 发送工具](#上位机 Ymodem 发送工具)
  7. 移植指南
  8. [与 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 模式详细流程:

  1. Ymodem 握手 --- 循环发送 C (0x43), 等待主机响应 Packet 0
  2. 解析文件信息 --- 提取固件文件名和大小
  3. 擦除 OTA 下载区 --- 根据大小擦除对应扇区
  4. 逐块接收 --- 调用 ymodem_receive_block() 循环接收, 每块写入 Flash
  5. EOT + 批次结束 --- ymodem_receive_finish() 处理结束握手
  6. CRC32 校验 --- 对整个下载区做 CRC32 校验
  7. 拷贝到 APP 区 --- flash_copy_range() 从 OTA 区搬运到 APP 区
  8. 验证新固件 --- app_validate() 检查合法性
  9. 清除 OTA 标志 --- 写配置区 boot_mode = NORMAL
  10. 系统复位 --- NVIC_SystemReset(), 重启后加载新固件

五、集成与构建

5.1 工具链

  • 编译器: arm-none-eabi-gcc (ARM GNU Toolchain)
  • IDE: STM32CubeIDE / STM32CubeMX (HAL 库外设初始化)
  • Flash/Debug: OpenOCD + ST-LINK V2

5.2 创建 Bootloader 工程

  1. STM32CubeMX → 选择 STM32F407VG → 配置:

    • RCC: HSE Crystal
    • USART1: Asynchronous, 115200 8N1, PA9(TX)/PA10(RX)
    • 不需要 FreeRTOS
  2. 生成代码, 选择 STM32CubeIDE 工具链

  3. 将本框架源文件复制到工程:

    • Bootloader/Config/*Core/Inc/
    • Bootloader/Core/*.cCore/Src/, *.hCore/Inc/
    • Bootloader/Transport/*.cCore/Src/, *.hCore/Inc/
  4. 替换链接脚本: 使用 linker_stm32f407_boot.ld

  5. 修改 main.c:

    c 复制代码
    int main(void) {
        HAL_Init();
        bootloader_run();   /* 不会返回 */
    }

5.3 创建 APP 工程

  1. STM32CubeMX → 选择 STM32F407VG → 配置:

    • RCC: HSE Crystal
    • 用户 APP 外设...
    • 中断向量表偏移: 需要在代码中手动设置
  2. 替换链接脚本: 使用 linker_stm32f407_app.ld

  3. main()SystemInit() 中添加:

    c 复制代码
    SCB->VTOR = 0x08010000;
  4. 复制 App/ota_trigger.cApp/ota_trigger.h 到工程

  5. APP 需要触发 OTA 时调用:

    c 复制代码
    ota_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 为例)

  1. 将 STM32 设置为 OTA 模式 (APP 中调用 ota_trigger_request())
  2. Bootloader 启动后开始发送 C 字符
  3. SecureCRT → Transfer → Send Ymodem → 选择 firmware.bin
  4. SecureCRT 解析文件名和大小, 发送 Packet 0
  5. Bootloader ACK, 开始数据传输
  6. 进度条显示传输进度
  7. 传输完成后 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 链接脚本

修改 ORIGINLENGTH 值匹配新的 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协议,可联系博主发送源码学习。

相关推荐
又见情义19 分钟前
RK3568 + RTL8211F 网络唤醒(WOL)功能适配全记录
android·网络·驱动开发
Titan202433 分钟前
HTTPS基础知识梳理
服务器·网络·c++·网络协议·学习·http·https
自然石人40 分钟前
石都随笔:深耕不张扬,平凡自有千钧力
网络·经验分享·百度·传媒·新浪微博
新时代牛马1 小时前
epoll 源码路径:从epoll_ctl 到ep_poll 的就绪唤醒
网络·数据库·网络协议
简单Janeee1 小时前
[Vue 3 从零到上线]-第九篇:更美更强——引入 UI 库与网络请求 (Axios)
网络·vue.js·ui
学习星球1 小时前
空天地一体化网络(NTN)深度解析:从Starlink D2C到3GPP NTN,卫星直连手机是如何实现的?
网络·人工智能·算法·智能手机·php
艾莉丝努力练剑2 小时前
【AI大模型接入SDK】WebSocket & SSE 协议
网络·c++·websocket·网络协议·学习·面试
xieliyu.2 小时前
计算机网络‑IP 协议解析:核心特性总结
网络·笔记·网络协议·学习·tcp/ip·计算机网络
SendTomo2 小时前
send.wang:基于浏览器WebRTC实现无客户端文件互传
网络·python·网络协议·webrtc·p2p