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 知识体系总览

下面这张图把本章的核心服务串起来了,你可以对照着看:

UDS刷写服务知识体系 刷写三剑客 34 请求下载 36 数据传输 37 退出传输 配套服务 31 例程控制 28 通信控制 85 DTC控制 典型刷写流程 1. 进入编程会话 2. 关闭通信/DTC 3. 34请求下载 4. 36循环传输 5. 37退出 关键说明 • 34服务必须在非默认会话下执行 • 36服务的块序列号最大255,大固件需分段 • 37服务后建议用31服务校验完整性

嗯,到这里,刷写服务的核心内容就讲完了。34/36/37是骨架,31/28/85是血肉,组合起来才能完成一次可靠的刷写。下次你写Bootloader的时候,记得把这几个服务的时序和参数理清楚,尤其是超时和错误处理,别让ECU在刷写过程中“死机”。