27 KiB
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
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 校验流程
- 上位机计算固件BIN文件的CRC32
- 数据传输完成后,上位机发送 0x31 0x01 0xFF 0x02 请求校验
- 板子读回Flash中
fw_size字节,计算CRC32 - 板子返回CRC32结果
- 上位机比对,一致则发 0x11 复位
6.3 CRC32 实现
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伪代码)
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 | IAP模块头文件,定义和API |
| app_iap.c | IAP模块实现 |
| app_flash.h | Flash读写模块头文件 |
| app_flash.c | Flash读写模块实现 |
十二、待确认事项
- Bootloader独立工程: 当前App工程中包含IAP代码,实际应拆分为独立Bootloader工程(16KB) + App工程(44KB)
- 中断向量表偏移: App工程需设置
VECT_TAB_OFFSET=0x4000,或在system_stm32f1xx.c中修改VECT_TAB_OFFSET - 链接脚本: App工程的Flash起始地址需改为 0x08004000
- 安全访问算法: 当前
Key=~Seed过于简单,生产环境是否需要加强? - 数据模式丢帧处理: 纯数据模式无序号,如果CAN丢帧,CRC校验会失败。是否需要增加重传机制?