# BMS 从机 CAN-UDS IAP 升级方案 > 基于 ISO 14229 (UDS) + ISO 15765 (CAN-TP) 标准 > 硬件: STM32F103C8T6 (64KB Flash, 20KB RAM) > 通信: CAN 500kbps, 标准帧 > 日期: 2026-08-06 --- ## 一、Flash 存储分区 ### 1.1 地址布局 | 区域 | 起始地址 | 结束地址 | 大小 | 页数 | 说明 | |------|----------|----------|------|------|------| | Bootloader | 0x08000000 | 0x08003FFF | 16KB | 16 | 引导程序,不可升级 | | Application | 0x08004000 | 0x0800EFFF | 44KB | 44 | 应用程序,IAP目标区 | | 参数区 | 0x0800F000 | 0x0800FFFF | 4KB | 4 | 升级标志 + 用户参数 | ### 1.2 升级标志定义(参数区首字 0x0800F000) | 标志值 | 含义 | |-------|------| | 0xFFFFFFFF | 空闲,无升级任务 | | 0xA5A55A5A | 有新固件待处理 | | 0x55AA55AA | 升级进行中(断电续传标志) | | 0x12345678 | 升级完成,可跳转App | ### 1.3 参数区布局(断电续传) ``` 偏移 字段 大小 说明 0x00 flag 4字节 升级标志(见1.2) 0x04 fw_size 4字节 固件总大小 0x08 bitmap_lo 4字节 进度位图(块0-31, 1=未完成, 0=完成) 0x0C bitmap_hi 4字节 进度位图(块32-63) ``` **进度位图原理**: - Flash 擦除后全 1 (0xFFFFFFFF),表示所有块未完成 - 每完成一个 1KB 块,将对应位清 0 (1→0,符合 Flash 物理特性) - 上电后扫描位图,第一个为 1 的位即为续传起点 - **优点**: 每次仅写 1 个字(~40μs),无需擦除参数页,不阻塞 CAN 接收 ### 1.4 启动流程 ``` 上电 ↓ Bootloader 读取 0x0800F000 标志 ├─ IAP_FLAG_COMPLETE (0x12345678) → 检查App合法性 → 跳转App ├─ IAP_FLAG_UPGRADING(0x55AA55AA) → 读取进度位图 → 等待IAP续传 ├─ IAP_FLAG_NEW_FW (0xA5A55A5A) → 等待IAP升级 └─ IAP_FLAG_NONE (0xFFFFFFFF) → 检查App栈顶 → 跳转或等待 ``` --- ## 二、UDS 服务详解 (ISO 14229) ### 2.1 服务总览 | SID | 服务名称 | 阶段 | 说明 | |-----|---------|------|------| | 0x10 | DiagnosticSessionControl | 握手 | 切换诊断会话 | | 0x22 | ReadDataByIdentifier | 握手 | 读取版本/固件信息 | | 0x27 | SecurityAccess | 握手 | 安全解锁(Seed/Key) | | 0x31 | RoutineControl | 校验 | 擦除Flash / CRC校验 | | 0x34 | RequestDownload | 传输 | 请求下载,声明固件大小 | | 0x36 | TransferData | 传输 | 传输固件数据 | | 0x37 | RequestTransferExit | 传输 | 结束传输 | | 0x11 | ECUReset | 完成 | 复位跳转App | ### 2.2 0x10 DiagnosticSessionControl **功能**: 切换诊断会话模式,升级前必须进入编程会话。 **请求格式**: ``` [A0] [SID] [SubFunction] 0x10 0x01 = Default Session 0x02 = Programming Session 0x03 = Extended Session ``` **正响应**: ``` [A0] [SID|0x40] [SubFunction echo] 0x50 0x02 ``` **否定响应**: ``` [A0] [0x7F] [SID] [NRC] 0x7F 0x10 见NRC表 ``` **本项目实现**: 收到 0x10 0x02 后进入 `IAP_STATE_SESSION`,允许后续升级服务。 --- ### 2.3 0x22 ReadDataByIdentifier **功能**: 通过DID读取数据,用于握手和版本确认。 **请求格式**: ``` [A0] [SID] [DID_High] [DID_Low] 0x22 0xF1 0x00 (DID=0xF100: 版本信息) ``` **正响应**: ``` [A0] [SID|0x40] [DID_High] [DID_Low] [Data...] 0x62 0xF1 0x00 "IAP01" (5字节ASCII) ``` **DID 定义**: | DID | 含义 | 数据格式 | |-----|------|---------| | 0xF100 | Bootloader版本 | 5字节ASCII "IAP01" | | 0xF101 | 固件大小 | 4字节大端 | | 0xF102 | CRC32 | 4字节大端 | --- ### 2.4 0x27 SecurityAccess **功能**: 安全访问,升级前必须解锁。采用Seed/Key机制。 **请求Seed (子功能=0x01)**: ``` [A0] [SID] [SubFunction] 0x27 0x01 ``` **正响应(返回Seed)**: ``` [A0] [SID|0x40] [SubFunction] [Seed 4字节] 0x67 0x01 0x12 0x34 0x56 0x78 ``` **发送Key (子功能=0x02)**: ``` [A0] [SID] [SubFunction] [Key 4字节] 0x27 0x02 0xED 0xCB 0xA9 0x87 ``` **Key算法**: `Key = ~Seed` (简化版,实际项目应使用更复杂算法) | Seed | Key | |------|-----| | 0x12345678 | 0xEDCBA987 | | 0xAABBCCDD | 0x55443322 | **正响应**: ``` [A0] [SID|0x40] [SubFunction] 0x67 0x02 ``` **否定响应**: Key错误时返回 NRC=0x35 (InvalidKey) 或 0x36 (ExceededNumberOfAttempts)。 --- ### 2.5 0x31 RoutineControl **功能**: 例程控制,用于擦除Flash和CRC校验。 **请求格式**: ``` [A0] [SID] [SubFunction] [RoutineID_High] [RoutineID_Low] 0x31 0x01 0xFF 0x01 (EraseFlash) 0x01 0xFF 0x02 (VerifyCRC) ``` **RoutineID 定义**: | RoutineID | 含义 | 触发动作 | |-----------|------|---------| | 0xFF01 | EraseFlash | 擦除App区44页 | | 0xFF02 | VerifyCRC | 读回Flash计算CRC32 | **正响应**: ``` [A0] [SID|0x40] [SubFunction] [RoutineID_High] [RoutineID_Low] 0x71 0x01 0xFF 0x01 ``` --- ### 2.6 0x34 RequestDownload **功能**: 请求下载,声明固件大小。标准ISO 14229格式。 **标准请求格式**: ``` [A0] [SID] [DataFormatId] [AddrSizeFormatId] [MemoryAddr] [MemorySize] 0x34 0x00 0x44 4字节 4字节 ``` | 字段 | 值 | 说明 | |------|-----|------| | DataFormatId | 0x00 | 无压缩,无加密 | | AddrSizeFormatId | 0x44 | 地址4字节,大小4字节 | | MemoryAddr | 0x08004000 | App区起始地址 | | MemorySize | 固件大小 | 大端4字节 | **本项目简化格式**(CAN 8字节限制): ``` [A0] [SID] [Size_3] [Size_2] [Size_1] [Size_0] 0x34 0x00 0x00 0x5A 0x00 (size=23040=0x5A00) ``` **正响应(支持断电续传)**: ``` [A0] [SID|0x40] [LengthFormatId] [Offset_3] [Offset_2] [Offset_1] [Offset_0] 0x74 0x08 0x00 0x00 0x00 0x00 ``` | 字段 | 说明 | |------|------| | LengthFormatId | 0x08, 每帧可接收8字节数据 | | Offset | 续传偏移量(大端4字节), 0=全新升级, >0=从该偏移续传 | **断电续传判断逻辑**: - 板子收到 0x34 后,检查参数区 `flag` 和 `fw_size` - 若 `flag == IAP_FLAG_UPGRADING` 且 `fw_size` 匹配 → **续传模式** - 不擦除 App 区,从进度位图计算 offset - 上位机从固件 offset 处开始发送数据 - 否则 → **全新升级** - 擦除 App 区,初始化参数区(flag=UPGRADING, fw_size, bitmap=0xFFFFFFFF) - offset = 0 --- ### 2.7 0x36 TransferData **功能**: 传输固件数据。 #### 标准格式(带块序号) **请求格式**: ``` [A0] [SID] [BlockSeqCounter] [Data 0~6字节] 0x36 0x01 [固件数据] ``` **BlockSequenceCounter**: 从0x01开始递增,到0xFF后回绕到0x00。 **正响应**: ``` [A0] [SID|0x40] [BlockSeqCounter echo] 0x76 0x01 ``` 每帧有效数据: `8 - 2 = 6字节`(SID + 序号占2字节)。 #### 本项目优化格式(纯数据模式 + 乒乓缓冲区) 为提高传输效率,采用**两阶段数据传输 + 乒乓缓冲区**: **阶段1: 命令帧(0x36 0x01)** ``` [A0] [SID] [BlockSeqCounter] 0x36 0x01 → 正响应 0x76, 进入纯数据模式 ``` **阶段2: 纯数据帧(无SID,无序号,乒乓缓冲区)** ``` [A0] [Data 0] [Data 1] [Data 2] [Data 3] [Data 4] [Data 5] [Data 6] [Data 7] 8字节全部是固件数据, 写入乒乓缓冲区, 无响应 ``` **乒乓缓冲区机制**: - 两个1KB缓冲区交替使用(共2KB RAM) - CAN数据先写入活跃缓冲区(纯内存操作,极快) - 缓冲区满(128帧×8字节=1KB)后切换到另一个缓冲区 - 上一轮满缓冲区在下一帧到来时批量写入Flash(只Unlock/Lock一次) - **每完成1KB块写入Flash后,更新进度位图(断电续传)** - Flash写入期间,CAN中断继续往frame_buf写入,实现半并行 ``` 帧1~128: 写入缓冲区A (纯内存, ~0μs/帧) 帧129: 缓冲区A满 → 切换到缓冲区B → 批量写Flash(缓冲区A, 1KB=256个字, ~10ms) → 更新进度位图(块0完成, 位0清0, ~40μs) → 帧129写入缓冲区B 帧130~256: 写入缓冲区B (纯内存) 帧257: 缓冲区B满 → 切换到缓冲区A → 批量写Flash(缓冲区B, 1KB) → 帧257写入缓冲区A ... 循环往复 ``` 按固件 `size` 计数,收够自动退出数据模式,发送最终正响应: ``` [A0] [SID|0x40] 0x76 (传输完成通知) ``` **效率对比**: | 模式 | 每帧有效数据 | 44KB所需帧数 | 传输时间@500kbps | |------|------------|-------------|-----------------| | 标准模式(带序号) | 6字节 | 7467帧 | 0.97s | | 纯数据模式(乒乓) | 8字节 | 5600帧 | 0.73s | | 提升 | +33% | -25% | -25% | **Flash写入效率对比**: | 指标 | 每帧写Flash | 乒乓缓冲区(1KB) | |------|------------|------------------| | HAL_FLASH_Unlock/Lock | 每8字节2次 | 每1KB 2次 | | Flash写入粒度 | 2个字/帧 | 256个字/批 | | Unlock/Lock次数(44KB) | 11264次 | 88次 | | Flash写入耗时/批 | ~80μs | ~10ms | | 写入期间CAN缓冲 | frame_buf(200帧) | frame_buf(200帧) | | RAM占用 | 0 | 2KB (2×1KB) | --- ### 2.8 0x37 RequestTransferExit **功能**: 结束数据传输。 **请求格式**: ``` [A0] [SID] 0x37 ``` **正响应**: ``` [A0] [SID|0x40] 0x77 ``` --- ### 2.9 0x11 ECUReset **功能**: ECU复位,跳转到新App。 **请求格式**: ``` [A0] [SID] [SubFunction] 0x11 0x01 = Hard Reset 0x02 = Key Off On Reset 0x03 = Soft Reset ``` **正响应**: ``` [A0] [SID|0x40] [SubFunction echo] 0x51 0x01 ``` 正响应发送后,延时100ms确保帧发出,然后 `NVIC_SystemReset()`。 --- ## 三、完整升级时序 ### 3.1 标准升级流程 ``` 上位机 (Tester) 板子 (ECU) │ │ │═══ 阶段1: 握手 ═══════════════════════│ │── 0x10 0x02 ──────────────────────→ │ 进入编程会话 │←── 0x50 0x02 ────────────────────── │ │ │ │── 0x22 0xF1 0x00 ────────────────→ │ 读版本 │←── 0x62 0xF1 0x00 "IAP01" ──────── │ │ │ │── 0x27 0x01 ──────────────────────→ │ 请求Seed │←── 0x67 0x01 [4字节Seed] ────────── │ │ │ │── 0x27 0x02 [4字节Key] ───────────→ │ 发送Key │←── 0x67 0x02 ────────────────────── │ 解锁成功 │ │ │═══ 阶段2: 数据传输 ═══════════════════│ │── 0x31 0x01 0xFF 0x01 ────────────→ │ 擦除App区 │←── 0x71 0x01 0xFF 0x01 ──────────── │ 擦除完成 │ │ │── 0x34 [固件大小4字节] ────────────→ │ 请求下载 │←── 0x74 0x08 ────────────────────── │ 允许,每帧8字节 │ │ │── 0x36 0x01 ──────────────────────→ │ 开始数据传输 │←── 0x76 0x01 ────────────────────── │ 进入纯数据模式 │ │ │── [8字节数据 #1] ─────────────────→ │ 写入Flash (无响应) │── [8字节数据 #2] ─────────────────→ │ 写入Flash (无响应) │── [8字节数据 #3] ─────────────────→ │ 写入Flash (无响应) │ ... │ │── [8字节数据 #N] ─────────────────→ │ 写入最后一块 │←── 0x76 ──────────────────────────── │ 传输完成(自动) │ │ │── 0x37 ───────────────────────────→ │ 传输退出 │←── 0x77 ──────────────────────────── │ │ │ │═══ 阶段3: 校验烧写 ═══════════════════│ │── 0x31 0x01 0xFF 0x02 ────────────→ │ CRC校验 │←── 0x71 0x01 0xFF 0x02 [CRC 4字节]─ │ 校验通过 │ │ │── 0x11 0x01 ──────────────────────→ │ 硬复位 │←── 0x51 0x01 ────────────────────── │ │ │ 复位,跳转新App ``` ### 3.2 超时机制 | 等待事件 | 超时时间 | 超时处理 | |---------|---------|---------| | 会话请求 (0x10) | 5s | 回到IDLE | | 安全访问 (0x27) | 5s | 回到SESSION | | 数据帧间隔 | 1s | 回到DOWNLOADING,发NRC | | 传输完成 (0x37) | 10s | 回到SESSION | | 整体升级 | 60s | 回到IDLE | ### 3.3 断电续传时序 ``` 上位机 (Tester) 板子 (ECU) │ │ │═══ 首次升级(传输到50%断电) ═══════════│ │── 0x34 [size] ────────────────────→ │ 全新升级,擦除App区 │←── 0x74 0x08 [offset=0] ──────────── │ flag=UPGRADING, bitmap=0xFF... │── 0x36 0x01 ──────────────────────→ │ 开始传输 │←── 0x76 0x01 ────────────────────── │ │── [数据帧...传输到50%] ────────────→ │ 已写22KB(22块完成) │ ✗ 断电 │ bitmap: 0xFFC00000 (块0-21=0) │ │ │═══ 重新上电,续传 ═════════════════════│ │ │ Bootloader读flag=UPGRADING │ │ 扫描bitmap, offset=22KB │── 0x10 0x02 ──────────────────────→ │ 进入会话 │←── 0x50 0x02 ────────────────────── │ │── 0x27 0x01 ──────────────────────→ │ 安全访问 │←── 0x67 0x01 [Seed] ──────────────── │ │── 0x27 0x02 [Key] ─────────────────→ │ │←── 0x67 0x02 ────────────────────── │ │ │ │── 0x34 [size] ────────────────────→ │ 检查: flag=UPGRADING, size匹配 │←── 0x74 0x08 [offset=22528] ──────── │ 续传模式! 不擦除App区 │ │ │── 0x36 0x01 ──────────────────────→ │ 开始传输 │←── 0x76 0x01 ────────────────────── │ │── [从offset=22KB处开始发数据] ─────→ │ 继续写入Flash │── [数据帧...] ─────────────────────→ │ │←── 0x76 ──────────────────────────── │ 传输完成 │── 0x37 ───────────────────────────→ │ │←── 0x77 ──────────────────────────── │ │── 0x31 0x01 0xFF 0x02 ────────────→ │ CRC校验 │←── 0x71 0x01 0xFF 0x02 [CRC] ─────── │ 校验通过, flag=COMPLETE │── 0x11 0x01 ──────────────────────→ │ 复位跳转 │←── 0x51 0x01 ────────────────────── │ ``` --- ## 四、NRC (否定响应码) 定义 ### 4.1 标准NRC表 (ISO 14229) | NRC | 名称 | 触发条件 | |-----|------|---------| | 0x10 | GeneralReject | 通用拒绝(Flash擦写失败等) | | 0x11 | ServiceNotSupported | 不支持的SID | | 0x22 | ConditionsNotCorrect | 状态不满足(未解锁就请求下载) | | 0x31 | RequestOutOfRange | 参数越界(固件大小=0或超限) | | 0x33 | SecurityAccessDenied | 未解锁就访问受保护服务 | | 0x35 | InvalidKey | Key错误 | | 0x36 | ExceededNumberOfAttempts | 超过最大尝试次数 | | 0x72 | GeneralProgrammingFailure | Flash编程失败 | | 0x73 | WrongBlockSequenceCounter | 块序号错误 | | 0x78 | ResponsePending | 响应延迟(处理中) | ### 4.2 本项目使用的NRC ```c NRC_GENERAL_REJECT 0x10 /* Flash擦写失败 */ NRC_SERVICE_NOT_SUPPORTED 0x11 /* 未知SID */ NRC_CONDITIONS_NOT_CORRECT 0x22 /* 状态不满足 */ NRC_REQUEST_OUT_OF_RANGE 0x31 /* 参数越界 */ ``` --- ## 五、状态机定义 ### 5.1 状态转换图 ``` ┌──────────────────────────────────────────┐ ↓ │ ┌────────┐ 0x10 0x02 ┌─────────┐ 0x34 ┌──────────────┐ │ │ IDLE │ ──────────→ │ SESSION │ ─────→ │ DOWNLOADING │ │ └────────┘ └─────────┘ └──────────────┘ │ ↑ │ │ │ │ │ 0x11 │ 0x36 0x01 │ │ 0x11 ↓ ↓ │ │ ┌─────────┐ ┌──────────────┐ │ │ | RESET │ │ DATA_MODE │ │ │ └─────────┘ └──────────────┘ │ │ │ │ │ 收够size │ │ │ ↓ │ │ ┌──────────────┐ │ └───────────────────────────────── │TRANSFER_DONE │────┘ 0x11 └──────────────┘ ``` ### 5.2 状态说明 | 状态 | 允许的SID | 说明 | |------|----------|------| | IDLE | 0x10 | 仅接受会话控制 | | SESSION | 0x10, 0x22, 0x27, 0x34, 0x11 | 握手+下载请求 | | DOWNLOADING | 0x36 | 等待数据传输开始 | | DATA_MODE | (纯数据) | 8字节直接写Flash,不解析SID | | TRANSFER_DONE | 0x37, 0x31, 0x11 | 传输完成,校验/退出/复位 | --- ## 六、CRC32 校验 ### 6.1 算法 - 多项式: 0xEDB88320 (IEEE 802.3, 反射形式) - 初始值: 0xFFFFFFFF - 最终异或: 0xFFFFFFFF - 输入: 固件数据(按字节) - 输出: 4字节CRC32 ### 6.2 校验流程 1. 上位机计算固件BIN文件的CRC32 2. 数据传输完成后,上位机发送 0x31 0x01 0xFF 0x02 请求校验 3. 板子读回Flash中 `fw_size` 字节,计算CRC32 4. 板子返回CRC32结果 5. 上位机比对,一致则发 0x11 复位 ### 6.3 CRC32 实现 ```c uint32_t iap_crc32(const uint8_t *data, uint32_t len) { uint32_t crc = 0xFFFFFFFFU; for (uint32_t i = 0; i < len; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 1) crc = (crc >> 1) ^ 0xEDB88320U; else crc >>= 1; } } return crc ^ 0xFFFFFFFFU; } ``` --- ## 七、安全机制 ### 7.1 安全访问(Seed/Key) - 升级前必须通过 0x27 安全访问 - Seed由板子随机生成(或基于HAL_GetTick) - Key算法: `Key = ~Seed` (简化版,生产环境应使用更复杂算法) ### 7.2 固件大小校验 - 0x34 请求下载时检查固件大小 - 限制: `0 < fw_size <= 44KB (IAP_APP_SIZE)` - 超限返回 NRC=0x31 ### 7.3 栈顶地址校验 - 跳转App前检查栈顶地址 - 合法范围: `0x20000000 <= SP <= 0x20005000` (20KB RAM) - 不合法则不跳转,留在Bootloader ### 7.4 Flash写保护 - 参数区(0x0800F000)只有升级标志操作可写 - Bootloader区(0x08000000-0x08003FFF)不可擦写 - App区(0x08004000-0x0800EFFF)仅IAP模式可擦写 --- ## 八、与标准UDS的差异说明 ### 8.1 本项目简化项 | 标准要求 | 本项目实现 | 原因 | |---------|-----------|------| | 0x34 带地址+大小格式标识符 | 仅带大小(4字节) | CAN 8字节帧限制 | | 0x36 带块序号,每帧响应 | 纯数据模式+乒乓缓冲区,无序号,无响应 | 提升传输效率33%,Flash批量写入 | | 0x36 块序号校验 | 按固件size计数 + CRC32校验 | 简化协议,乒乓缓冲区1KB/批 | | 0x78 ResponsePending | 未实现 | Flash擦写<10ms,不需要 | | ISO 15765 CAN-TP分包 | 未实现 | 固件44KB,纯数据模式够用 | ### 8.2 风险评估 | 风险 | 影响 | 缓解措施 | |------|------|---------| | 纯数据模式无序号,丢帧无法检测 | 固件损坏 | CRC32校验 + 上位机重传 | | 乒乓缓冲区Flash写入~10ms,期间帧积压 | 最多积压78帧 | frame_buf(200帧)足够缓存 | | 无CAN-TP分包,单帧8字节 | 传输慢 | 44KB@500kbps仅需0.73s,可接受 | | 安全访问算法简单 | 可被逆向 | 生产环境应换为AES或自定义算法 | | 乒乓缓冲区占用2KB RAM | RAM减少 | F103有20KB RAM,可接受 | | 断电续传位图写失败 | 续传位置不准 | Flash写入有HAL返回值检查 | | 断电发生在块写入中途 | 该块数据不完整 | 续传时该块重传(位图未清0) | | 断电发生在位图清0前 | 该块需重传 | 最多重传1KB,可接受 | --- ## 九、上位机实现指南 ### 9.1 升级步骤(Python伪代码) ```python def iap_upgrade(can, fw_path): fw_data = open(fw_path, 'rb').read() fw_size = len(fw_data) fw_crc = crc32(fw_data) # 阶段1: 握手 can.send(0x180, [0x10, 0x02]) assert can.recv(0x181)[0] == 0x50 can.send(0x180, [0x22, 0xF1, 0x00]) resp = can.recv(0x181) assert resp[0] == 0x62 can.send(0x180, [0x27, 0x01]) resp = can.recv(0x181) seed = resp[2:6] key = bytes([~b & 0xFF for b in seed]) can.send(0x180, [0x27, 0x02] + list(key)) assert can.recv(0x181)[0] == 0x67 # 阶段2: 数据传输(支持断电续传) can.send(0x180, [0x31, 0x01, 0xFF, 0x01]) # 擦除(仅首次,续传时板子自动跳过) assert can.recv(0x181)[0] == 0x71 can.send(0x180, [0x34] + list(fw_size.to_bytes(4, 'big'))) # 请求下载 resp = can.recv(0x181) assert resp[0] == 0x74 offset = int.from_bytes(resp[2:6], 'big') # 读取续传偏移量 can.send(0x180, [0x36, 0x01]) # 开始传输 assert can.recv(0x181)[0] == 0x76 # 纯数据模式: 从offset处开始发送, 8字节/帧, 无响应 for i in range(offset, fw_size, 8): chunk = fw_data[i:i+8] chunk = chunk.ljust(8, b'\xFF') # 不足8字节补0xFF can.send(0x180, list(chunk)) # 等待传输完成响应 assert can.recv(0x181, timeout=2000)[0] == 0x76 can.send(0x180, [0x37]) # 传输退出 assert can.recv(0x181)[0] == 0x77 # 阶段3: 校验 can.send(0x180, [0x31, 0x01, 0xFF, 0x02]) # CRC校验 resp = can.recv(0x181) calc_crc = int.from_bytes(resp[4:8], 'big') assert calc_crc == fw_crc can.send(0x180, [0x11, 0x01]) # 复位 assert can.recv(0x181)[0] == 0x51 print("升级成功!") ``` ### 9.2 上位机CAN帧参数 | 参数 | 值 | |------|-----| | 帧类型 | 标准帧 | | 请求ID | 0x180 | | 响应ID | 0x181 | | DLC | 8 | | 波特率 | 500kbps | | 帧间隔 | ≥1ms (数据模式可连续) | --- ## 十、测试验证 ### 10.1 功能测试项 | 测试项 | 预期结果 | 状态 | |--------|---------|------| | 握手流程(0x10→0x22→0x27) | 全部正响应 | ☐ | | 擦除App区(0x31 0xFF01) | App区全0xFF | ☐ | | 下载1KB固件 | CRC校验通过 | ☐ | | 下载44KB固件(满载) | CRC校验通过,0.73s完成 | ☐ | | 跳转App(0x11) | App正常运行 | ☐ | | 错误Key | NRC=0x35 | ☐ | | 固件大小超限 | NRC=0x31 | ☐ | | 未解锁就下载 | NRC=0x22 | ☐ | | 断电续传: 传输50%断电 | 重新上电后从断点续传 | ☐ | | 断电续传: 传输1KB后断电 | 重新上电后从1KB处续传 | ☐ | | 断电续传: 块写入中途断电 | 该块重传,CRC校验通过 | ☐ | ### 10.2 压力测试 | 测试项 | 预期结果 | 状态 | |--------|---------|------| | 连续升级10次 | 每次成功 | ☐ | | 断电续传5次 | 最终CRC校验通过 | ☐ | | 数据帧丢1帧 | CRC校验失败,拒绝跳转 | ☐ | | 总线错误(bus-off) | 自动恢复,重试 | ☐ | --- ## 十一、文件清单 | 文件 | 作用 | |------|------| | [app_iap.h](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/Core/Inc/app_iap.h) | IAP模块头文件,定义和API | | [app_iap.c](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/Core/Src/app_iap.c) | IAP模块实现 | | [app_flash.h](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/Core/Inc/app_flash.h) | Flash读写模块头文件 | | [app_flash.c](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/Core/Src/app_flash.c) | Flash读写模块实现 | --- ## 十二、待确认事项 1. **Bootloader独立工程**: 当前App工程中包含IAP代码,实际应拆分为独立Bootloader工程(16KB) + App工程(44KB) 2. **中断向量表偏移**: App工程需设置 `VECT_TAB_OFFSET=0x4000`,或在system_stm32f1xx.c中修改 `VECT_TAB_OFFSET` 3. **链接脚本**: App工程的Flash起始地址需改为 0x08004000 4. **安全访问算法**: 当前 `Key=~Seed` 过于简单,生产环境是否需要加强? 5. **数据模式丢帧处理**: 纯数据模式无序号,如果CAN丢帧,CRC校验会失败。是否需要增加重传机制?