2、刷写核心服务:安全访问(0x27)、诊断故障码读取(0x19)、通信控制(0x28)、例程控制(0x31)、请求下载(0x34)、数据传输(0x36)、请求退出传输(0x37)

刷写流程,说白了就是ECU升级的“七步走”。每一步都对应一个UDS服务。我刚开始带项目时,总觉得这些服务是孤立的,后来踩了坑才明白——它们是一环扣一环的链条,断一个,整个刷写就废了。

这一章,咱们把这七个核心服务拆开揉碎。你想想看,刷写时ECU最怕什么?怕被篡改、怕刷到一半断线、怕数据错位。这些服务就是用来解决这些问题的。

核心认知: 刷写不是“发数据”那么简单,而是一个状态机驱动的安全流程。每个服务都有前置条件,跳步执行必出问题。

2.1 安全访问 (0x27) —— 刷写的“门禁系统”

为什么刷写前要先过安全访问?因为ECU不想让随便什么人都能改写它的固件。0x27服务,就是ECU的“门禁卡”。

流程其实很简单:

  1. 请求种子(Seed):Tester发0x27 + 子功能0x01(请求种子),ECU返回一串随机数。
  2. 发送密钥(Key):Tester用算法算出Key,发0x27 + 子功能0x02(发送密钥)。
  3. 验证结果:ECU比对Key,对了就解锁,错了就锁死一段时间。

我个人习惯把种子和密钥的算法单独封装成一个函数。为什么?因为不同OEM的算法千奇百怪,有的用AES,有的用自定义异或,甚至有的直接用固定值——嗯,你没看错,真有厂商这么干。

小技巧: 刷写测试时,建议先手动验证一次种子-密钥流程。我曾经遇到过ECU返回的种子长度和文档不一致,导致自动化脚本全挂。先手工摸一遍,能省半天调试时间。

安全访问的时序要求很严格。从收到种子到发出密钥,通常有几百毫秒到几秒的超时限制。超时了?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的快照数据。这个在分析偶发故障时特别有用。
注意: 有些ECU在刷写过程中会临时报一些DTC(比如通信超时),刷完后会自动清除。如果你在刷写过程中读DTC,可能会被这些“假故障”误导。我的经验是:只在刷写前和刷写完成后读,中间别碰。

2.3 通信控制 (0x28) —— 让ECU“闭嘴”

刷写时最怕什么?怕ECU一边刷一边往外发报文。你想想看,刷写数据流和正常通信报文混在一起,轻则丢包,重则总线冲突。

0x28服务就是干这个的——让ECU暂时关闭非诊断通信。常用的子功能:

  • 0x03:禁用应用报文的发送和接收。ECU只响应诊断请求,其他报文一概不理。
  • 0x04:启用应用报文。刷写完成后恢复。

我记得有一次在项目中,刷写脚本忘了发0x28 03,结果刷到一半,ECU突然发了一个CAN报文,把刷写数据包冲掉了。从那以后,我把0x28服务写进了刷写流程的“强制检查项”。

关键点: 0x28服务必须在安全访问解锁之后才能执行。顺序错了,ECU会回复NRC 0x33(安全访问未解锁)。

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直接变砖。嗯,从那以后,我每次都会在脚本里显式指定字节序。

避坑指南: 0x34响应中的“最大数据块长度”很重要。它告诉Tester每次0x36能发多少字节。如果无视这个值,硬发大包,ECU会回复NRC 0x13(报文长度错误)。

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 = 内存写入失败
重要: 0x37响应中的校验结果一定要检查。如果返回失败,说明固件没写进去。这时候不要复位ECU,否则变砖。正确的做法是:重新走一遍刷写流程,从0x34开始。

2.8 知识体系总览

下面这张图,把这七个服务的关系画清楚了。你可以把它当作刷写流程的“地图”。

UDS刷写核心服务流程 UDS刷写核心服务流程 阶段一:准备阶段 0x27 安全访问 → 0x19 读取DTC → 0x28 通信控制 解锁ECU → 记录初始故障 → 关闭非诊断通信 阶段二:擦除阶段 0x31 例程控制(擦除例程)→ 擦除目标Flash区域 阶段三:下载阶段 0x34 请求下载 → 0x36 数据传输(循环)→ 0x37 请求退出传输 声明地址和大小 → 逐块发送数据 → 结束传输并校验 阶段四:验证阶段 0x31 例程控制(校验例程)→ 0x19 读取DTC → 0x28 通信控制(恢复) 注意:每个阶段都有前置条件,跳步执行会导致NRC错误或ECU变砖

你看,整个流程分四个阶段。每个阶段的服务都有明确的前置条件。比如,你不能在安全访问之前发0x28,也不能在请求下载之前发0x36。这些顺序,是UDS协议规定的,也是我踩坑踩出来的经验。