4. UDS服务基础(下):通信控制、测试仪在线、读取与写入数据

好,咱们接着聊UDS诊断协议栈。上一节我们把会话控制、ECU复位和安全访问讲透了,这一节我挑四个在实际项目中用得最频繁的服务来拆解。通信控制(0x28)、测试仪在线(0x3E)、读取数据(0x22)、写入数据(0x2E)。这四个服务,说白了就是诊断通信的“四件套”,你搞懂了它们,基本就能应付90%的日常诊断需求。

4.1 通信控制(0x28)—— 别让ECU“乱说话”

通信控制服务,它的核心作用是什么?就是让诊断仪能够控制ECU的通信行为。你可以把它理解成一个“通信开关”,想开就开,想关就关。

我个人习惯把0x28服务分成两类场景:

  • 场景一:刷写前的“静默”。刷写过程中,ECU如果还在往外发CAN报文,很容易干扰刷写流程。所以刷写前,我们会先发一个0x28服务,让ECU停止发送应用报文。
  • 场景二:网络管理测试。测试ECU在特定通信状态下的行为,比如模拟总线故障。

0x28服务的请求格式很简单:

请求:0x28 + 子功能 + 通信类型
响应:0x68 + 子功能 + 通信类型(正响应)

子功能有三个:

子功能含义说明
0x00启用Rx和Tx恢复所有通信
0x01禁用Rx和Tx完全静默
0x02禁用Tx只收不发
0x03禁用Rx只发不收

通信类型字段,一般用0x02表示“应用报文”,0x03表示“网络管理报文”。

实际案例:我在做某个OEM的刷写项目时,遇到过一个问题:刷写过程中ECU突然超时了。排查了半天,发现是刷写前发的0x28服务,子功能用了0x01(禁用Rx和Tx),结果刷写完成后忘了恢复通信。ECU一直处于“聋哑”状态,自然无法响应后续请求。从那以后,我每次发0x28都会在代码里加一个“恢复通信”的步骤,确保不会遗漏。

避坑指南:我曾经在测试中发现,某些ECU对0x28服务的响应时间特别慢,尤其是从“禁用”状态恢复到“启用”状态时。如果你在刷写流程中连续发送多个0x28请求,一定要留足间隔时间,否则ECU可能还没准备好,直接给你回一个NRC 0x78(请求正确接收-响应待定)。

4.2 测试仪在线(0x3E)—— 别让ECU“睡着”

0x3E服务,也叫“Tester Present”。它的作用很简单:告诉ECU“我还活着,别超时”。

你想想看,ECU在诊断会话中是有超时机制的。如果一段时间内没有收到任何诊断请求,ECU会自动退出当前会话,回到默认会话。这个超时时间,不同OEM定义不同,一般是2到5秒。

0x3E服务的请求格式:

请求:0x3E + 子功能
响应:0x7E + 子功能(正响应)

子功能只有两个:

  • 0x00:零抑制(Zero Suppression)。ECU不会发送正响应,只用来刷新超时计时器。
  • 0x01:有响应。ECU会回复一个正响应,告诉你“我收到了”。

我个人习惯用0x00子功能。为什么?因为省带宽。你想想看,如果每秒钟发一次0x3E,每次ECU都回一个响应,那总线上就多了很多无用报文。用0x00子功能,ECU只刷新计时器,不回复,干净利落。

小技巧:我建议把0x3E的发送间隔设置为超时时间的一半。比如ECU的超时时间是4秒,那你就每2秒发一次0x3E。这样即使丢了一帧,还有一帧能续命。我曾经见过有人把间隔设成3.9秒,结果网络稍微一抖动,ECU就超时了,得不偿失。

4.3 读取数据(0x22)—— 诊断的“眼睛”

0x22服务,用来读取ECU内部的数据。这些数据可以是传感器值、状态标志、版本号、故障码等等。说白了,它就是诊断仪的“眼睛”,让你能看到ECU内部发生了什么。

请求格式:

请求:0x22 + DID(2字节)
响应:0x62 + DID + 数据

DID(Data Identifier)是数据标识符,每个DID对应一个特定的数据。比如:

DID含义数据长度
0xF190ECU硬件版本号4字节
0xF191ECU软件版本号4字节
0xF192ECU序列号8字节
0x0101发动机转速2字节
0x0102车速2字节

嗯,这里要注意:DID的分配是有规范的。0xF0xx系列一般是OEM自定义的,0x01xx系列是ISO 14229-2中定义的通用DID。我建议你在开发时,先把DID列表整理清楚,避免重复或冲突。

实际案例:我记得有一次,客户反馈说读取ECU软件版本号时,返回的数据总是乱码。我查了一下代码,发现DID 0xF191对应的数据长度定义错了。ECU实际返回了4字节,但代码里只读了2字节,导致后续数据错位。从那以后,我每次定义DID时,都会在文档里明确标注数据长度和格式,避免这种低级错误。

4.4 写入数据(0x2E)—— 诊断的“手”

0x2E服务,和0x22正好相反。它用来向ECU写入数据。你可以修改ECU的配置参数、写入标定值、或者设置一些特殊模式。

请求格式:

请求:0x2E + DID(2字节)+ 数据
响应:0x6E + DID + 数据

写入数据时,有几个关键点要注意:

  • 安全访问:大多数写入操作都需要先通过安全访问(0x27服务)的验证。否则ECU会直接拒绝,返回NRC 0x33(安全访问拒绝)。
  • 数据格式:写入的数据必须和DID定义的长度、格式完全一致。多一个字节或少一个字节,都会导致NRC 0x13(消息长度错误或格式无效)。
  • 写入时机:有些DID只能在特定会话下写入。比如标定数据,一般需要在扩展会话或编程会话下才能写。

避坑指南:我曾经在量产项目中遇到一个严重问题:产线工人用诊断仪写入车辆配置参数时,不小心写错了DID。本来应该写0xF190(硬件版本号),结果写成了0xF191(软件版本号)。虽然数据长度一样,但含义完全不同。这导致后续的软件升级流程全部乱套。从那以后,我强烈建议在ECU端增加一个“写入校验”机制,比如写入前先读取当前值,确认DID正确后再写入。

4.5 四个服务的协同使用

在实际项目中,这四个服务经常一起出现。我给你画个典型场景:

  1. 诊断仪连接到ECU,先发0x10 0x03进入扩展会话。
  2. 发0x27 0x01请求种子,ECU返回种子值。
  3. 发0x27 0x02发送密钥,通过安全访问验证。
  4. 发0x2E 0xF191写入新的软件版本号。
  5. 发0x22 0xF191读取刚写入的版本号,确认写入成功。
  6. 发0x28 0x01 0x02禁用应用报文,准备刷写。
  7. 刷写过程中,每2秒发一次0x3E 0x00保持会话。
  8. 刷写完成后,发0x28 0x00 0x02恢复应用报文。
  9. 发0x11 0x01复位ECU,让新软件生效。

你看,这一套流程下来,0x22、0x2E、0x28、0x3E全用上了。它们之间配合默契,缺一不可。

个人经验:我建议你在开发诊断协议栈时,把这四个服务做成一个“工具包”。每个服务单独封装成一个函数,参数清晰,接口统一。这样在写应用层代码时,直接调用这些函数就行,不用每次都去拼报文。我自己的代码库里,这四个服务的函数已经用了五六年了,基本没改过。

好了,这一节的内容就到这里。下一节我们会聊更高级的服务——例程控制(0x31)和请求下载(0x34),这两个在刷写流程中至关重要。咱们到时候见。