745 lines
27 KiB
Markdown
745 lines
27 KiB
Markdown
# 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校验会失败。是否需要增加重传机制?
|