2、刷写核心服务:安全访问(0x27)、诊断故障码读取(0x19)、通信控制(0x28)、例程控制(0x31)、请求下载(0x34)、数据传输(0x36)、请求退出传输(0x37)
刷写流程,说白了就是ECU升级的“七步走”。每一步都对应一个UDS服务。我刚开始带项目时,总觉得这些服务是孤立的,后来踩了坑才明白——它们是一环扣一环的链条,断一个,整个刷写就废了。
这一章,咱们把这七个核心服务拆开揉碎。你想想看,刷写时ECU最怕什么?怕被篡改、怕刷到一半断线、怕数据错位。这些服务就是用来解决这些问题的。
2.1 安全访问 (0x27) —— 刷写的“门禁系统”
为什么刷写前要先过安全访问?因为ECU不想让随便什么人都能改写它的固件。0x27服务,就是ECU的“门禁卡”。
流程其实很简单:
- 请求种子(Seed):Tester发0x27 + 子功能0x01(请求种子),ECU返回一串随机数。
- 发送密钥(Key):Tester用算法算出Key,发0x27 + 子功能0x02(发送密钥)。
- 验证结果:ECU比对Key,对了就解锁,错了就锁死一段时间。
我个人习惯把种子和密钥的算法单独封装成一个函数。为什么?因为不同OEM的算法千奇百怪,有的用AES,有的用自定义异或,甚至有的直接用固定值——嗯,你没看错,真有厂商这么干。
安全访问的时序要求很严格。从收到种子到发出密钥,通常有几百毫秒到几秒的超时限制。超时了?ECU直接拒绝,你得重新请求种子。
| 步骤 | 请求报文 | 响应报文 | 说明 |
|---|---|---|---|
| 请求种子 | 02 27 01 | 06 27 01 [Seed1] [Seed2] [Seed3] [Seed4] | 种子通常4字节 |
| 发送密钥 | 06 27 02 [Key1] [Key2] [Key3] [Key4] | 02 27 02 | 正响应表示解锁成功 |
| 解锁失败 | — | 03 7F 27 35 | NRC 0x35 = 无效密钥 |
2.2 诊断故障码读取 (0x19) —— 刷写前的“体检报告”
刷写前为什么要读DTC?你想想看,如果ECU已经报了一堆故障,你直接刷写,刷完故障还在,你怎么判断是刷写引起的还是原来就有的?
0x19服务,说白了就是给ECU做个体检。我建议在刷写流程中至少读两次DTC:
- 刷写前:记录初始故障状态,作为基线。
- 刷写后:再次读取,对比差异,判断刷写是否引入了新问题。
常用的子功能就两个:
- 0x02:按状态掩码读取DTC。比如掩码0x2F表示读取所有当前激活的故障。
- 0x0A:读取所有DTC的快照数据。这个在分析偶发故障时特别有用。
2.3 通信控制 (0x28) —— 让ECU“闭嘴”
刷写时最怕什么?怕ECU一边刷一边往外发报文。你想想看,刷写数据流和正常通信报文混在一起,轻则丢包,重则总线冲突。
0x28服务就是干这个的——让ECU暂时关闭非诊断通信。常用的子功能:
- 0x03:禁用应用报文的发送和接收。ECU只响应诊断请求,其他报文一概不理。
- 0x04:启用应用报文。刷写完成后恢复。
我记得有一次在项目中,刷写脚本忘了发0x28 03,结果刷到一半,ECU突然发了一个CAN报文,把刷写数据包冲掉了。从那以后,我把0x28服务写进了刷写流程的“强制检查项”。
2.4 例程控制 (0x31) —— 刷写中的“小工具”
0x31服务,你可以把它理解成ECU内置的“小工具”。刷写过程中,它主要干三件事:
- 擦除内存:刷写前,先擦掉旧固件。这个通常用0x31 FF 00(擦除例程)来实现。
- 检查完整性:刷完后,ECU自己算一遍CRC,和Tester发过来的对比。
- 复位:刷写完成后,让ECU重启。
例程控制的报文结构:
请求:03 31 [例程控制类型] [例程ID高字节] [例程ID低字节]
响应:02 31 [例程控制类型]
例程控制类型:
0x01 = 启动例程
0x02 = 停止例程
0x03 = 查询例程结果
我个人习惯把擦除例程单独拎出来做超时处理。为什么?因为擦除Flash的时间因芯片而异,有的几百毫秒,有的好几秒。如果超时设得太短,脚本会误判失败。
2.5 请求下载 (0x34) —— 告诉ECU“我要发数据了”
0x34服务,是刷写数据流的“开场白”。它的作用就是告诉ECU:我要发多少数据,发到哪个地址。
报文结构:
请求:04 34 [数据格式] [地址长度] [内存地址...] [内存大小...]
响应:06 34 [数据格式] [最大数据块长度...]
这里有个坑:地址和大小的字节顺序。有的ECU用Intel格式(小端),有的用Motorola格式(大端)。我曾经因为搞混了字节序,数据写到了错误的Flash地址,结果ECU直接变砖。嗯,从那以后,我每次都会在脚本里显式指定字节序。
2.6 数据传输 (0x36) —— 真正的“搬砖”环节
0x36服务,就是刷写的主体力活。它把固件数据一块一块地搬到ECU的Flash里。
报文结构很简单:
请求:04 36 [块序列号] [数据...]
响应:02 36 [块序列号]
块序列号从1开始,每发一包加1。ECU用这个序号来检测丢包或乱序。
我建议在自动化脚本里加一个重传机制:如果某包数据超时没收到正响应,重发3次。如果还不行,记录当前块号,然后终止刷写。为什么?因为强行刷下去,固件不完整,ECU大概率变砖。
| 场景 | 块序列号 | 数据长度 | 说明 |
|---|---|---|---|
| 第一包 | 0x01 | 最大块长度 | 正常发送 |
| 中间包 | 0x02 ~ 0xN-1 | 最大块长度 | 连续发送 |
| 最后一包 | 0xN | 剩余数据 | 可能小于最大块长度 |
| 超时重传 | 不变 | 不变 | 重发同一包,最多3次 |
2.7 请求退出传输 (0x37) —— 刷写的“收尾工作”
数据发完了,不能直接断电。0x37服务就是告诉ECU:数据发完了,你可以做收尾工作了。
ECU收到0x37后,会做两件事:
- 校验完整性:检查收到的数据和预期是否一致。
- 更新Flash:把缓存的数据真正写入Flash。
报文结构:
请求:01 37
响应:02 37 [校验结果]
校验结果:
0x00 = 成功
0x01 = 校验失败
0x02 = 内存写入失败
2.8 知识体系总览
下面这张图,把这七个服务的关系画清楚了。你可以把它当作刷写流程的“地图”。
你看,整个流程分四个阶段。每个阶段的服务都有明确的前置条件。比如,你不能在安全访问之前发0x28,也不能在请求下载之前发0x36。这些顺序,是UDS协议规定的,也是我踩坑踩出来的经验。