4、UDS刷写服务实战:34/36/37服务与配套服务
刷写,说白了就是给ECU换脑子。你想想看,一辆车下线后发现问题,总不能把ECU拆下来寄回原厂吧?UDS的34/36/37服务就是干这个的。今天咱们把这套流程掰开揉碎了讲,再带上31、28、85这几个配套服务,让你彻底搞明白刷写到底怎么玩。
4.1 刷写三剑客:34/36/37服务
这三个服务是刷写的核心,缺一不可。我习惯把它们叫做“开门、搬砖、关门”。
4.1.1 34服务:请求下载(Request Download)
34服务的作用,就是告诉ECU:“我要往你内存里写数据了,你准备好。” 它携带的关键信息包括:数据存放的地址、数据长度、以及内存空间类型(比如Flash还是EEPROM)。
34服务请求报文格式(简化):
请求: 0x34 + 3字节数据格式标识 + 地址参数 + 长度参数
响应: 0x74 + 最大数据块长度(MaxNumberOfBlockLength)
ECU收到34请求后,会检查地址是否合法、空间是否足够。如果没问题,就回复一个最大数据块长度。这个值很关键——它告诉Tester每次用36服务能发多少字节。我在项目中遇到过,有些ECU的MaxNumberOfBlockLength只有256字节,你非得一次发1KB,那肯定报错。
注意:34服务必须在非默认会话(通常是扩展会话或编程会话)下才能执行。如果在默认会话下发34,ECU会直接回NRC 0x7F(服务不支持)。
4.1.2 36服务:数据传输(Transfer Data)
36服务就是“搬砖”环节。Tester把固件数据分成若干块,一块一块地发给ECU。每发一块,ECU会回复一个块序列号,告诉Tester“我收到了,继续发下一块”。
| 参数 | 说明 | 示例值 |
|---|---|---|
| 块序列号 | 当前数据块的序号,从1开始递增 | 0x01, 0x02, ... |
| 数据块 | 实际固件数据,长度不超过MaxNumberOfBlockLength | 最多4095字节 |
这里有个坑:块序列号是单字节的,最大255。如果固件很大,块数超过255怎么办?我建议的做法是:在应用层自己维护一个更大的计数器,或者干脆把固件分成多个段,每段单独走一次34/36/37流程。
4.1.3 37服务:请求退出传输(Request Transfer Exit)
数据发完了,得告诉ECU“活干完了,你收尾吧”。37服务就是干这个的。ECU收到37后,会做两件事:一是校验收到的数据完整性(比如CRC),二是把数据从临时缓冲区写入目标内存(比如Flash)。
个人经验:37服务响应时间可能比较长,尤其是写Flash的时候。我曾经遇到过ECU写Flash花了5秒,Tester超时了,结果ECU以为传输失败,把数据全丢了。所以,37服务的P2超时时间一定要设得足够长,或者用P2*扩展机制。
4.2 31服务:例程控制(Routine Control)
31服务是刷写流程中的“瑞士军刀”。它有三个子功能:开始、停止、获取结果。刷写中常用的例程包括:擦除Flash、校验完整性、复位ECU等。
4.2.1 31服务报文格式
请求: 0x31 + 子功能(01=开始,02=停止,03=获取结果) + 例程ID(2字节) + 可选参数
响应: 0x71 + 子功能 + 例程ID + 例程状态/结果
举个例子,擦除Flash的例程ID通常是0xFF00。Tester发31 01 FF00,ECU就开始擦除。擦除完成后,ECU回复71 01 FF00 00(00表示成功)。
刷写中常用的例程ID(OEM自定义,以下为常见值):
- 0xFF00:擦除Flash(Erase Memory)
- 0xFF01:校验数据完整性(Check Integrity)
- 0xFF02:复位ECU(Reset ECU)
- 0xFF03:获取刷写进度(Get Progress)
我记得有一次,客户反馈刷写时偶尔会失败。排查后发现,是擦除例程执行时间不稳定,有时快有时慢。Tester那边设了固定超时,结果超时了ECU还在擦。后来我建议用31服务的“获取结果”子功能,Tester先发“开始”,然后轮询“获取结果”,直到ECU返回完成状态。这样既不会超时,又能实时知道进度。
4.3 28/85服务:通信控制与DTC设置
刷写过程中,ECU会收到各种乱七八糟的报文。如果不加控制,可能会干扰刷写流程。28和85服务就是用来“清场”的。
4.3.1 28服务:控制通信(Communication Control)
28服务可以关闭或开启ECU的某些通信通道。刷写时,我们通常会把非诊断报文(比如应用报文)关掉,只保留诊断通信。这样能减少总线负载,也能防止应用层逻辑干扰刷写。
| 子功能 | 含义 | 刷写中的典型用法 |
|---|---|---|
| 0x00 | 开启所有通信 | 刷写完成后恢复 |
| 0x01 | 关闭所有非诊断通信 | 刷写开始前执行 |
| 0x02 | 关闭所有诊断通信 | 很少用 |
| 0x03 | 开启所有非诊断通信 | 刷写完成后恢复 |
注意:28服务关闭通信后,ECU不会回复任何非诊断报文。如果你在刷写过程中还想看应用层数据,那得提前想好。我一般建议刷写前先发28 01,刷完再发28 00恢复。
4.3.2 85服务:控制DTC设置(Control DTC Setting)
刷写过程中,ECU可能会因为数据不完整、校验失败等原因,误报DTC。85服务可以暂时关闭DTC记录,避免刷写过程中产生一堆假故障码。
请求: 0x85 + 子功能(0x02=关闭DTC记录,0x01=开启DTC记录)
响应: 0xC5 + 子功能
我习惯在刷写流程开始前先发85 02,刷写完成后发85 01恢复。这样ECU不会在刷写过程中记录DTC,刷完后再清一次DTC,干干净净。
4.4 刷写流程实战:一个完整的例子
下面是一个典型的刷写流程,我用伪代码展示一下:
// 步骤1:进入编程会话
发送: 10 03
接收: 50 03
// 步骤2:关闭非诊断通信
发送: 28 01
接收: 68 01
// 步骤3:关闭DTC记录
发送: 85 02
接收: C5 02
// 步骤4:请求下载(假设地址0x8000,长度0x10000)
发送: 34 00 44 00 00 80 00 00 01 00 00
接收: 74 00 00 01 00 // MaxNumberOfBlockLength = 256
// 步骤5:循环发送数据块
for (block = 1; block <= totalBlocks; block++) {
发送: 36 01 + 数据块
接收: 76 01 + block
}
// 步骤6:请求退出传输
发送: 37
接收: 77
// 步骤7:恢复DTC记录
发送: 85 01
接收: C5 01
// 步骤8:恢复通信
发送: 28 00
接收: 68 00
避坑指南:我曾经在项目里遇到一个奇怪的问题——刷写完成后ECU不工作。排查了半天,发现是忘了发85 01恢复DTC记录。ECU的DTC记录被关闭后,某些内部监控逻辑也停了,导致ECU进入保护模式。所以,恢复操作一定要做全,别偷懒。
4.5 知识体系总览
下面这张图把本章的核心服务串起来了,你可以对照着看:
嗯,到这里,刷写服务的核心内容就讲完了。34/36/37是骨架,31/28/85是血肉,组合起来才能完成一次可靠的刷写。下次你写Bootloader的时候,记得把这几个服务的时序和参数理清楚,尤其是超时和错误处理,别让ECU在刷写过程中“死机”。