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 | 含义 | 数据长度 |
|---|---|---|
| 0xF190 | ECU硬件版本号 | 4字节 |
| 0xF191 | ECU软件版本号 | 4字节 |
| 0xF192 | ECU序列号 | 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 四个服务的协同使用
在实际项目中,这四个服务经常一起出现。我给你画个典型场景:
- 诊断仪连接到ECU,先发0x10 0x03进入扩展会话。
- 发0x27 0x01请求种子,ECU返回种子值。
- 发0x27 0x02发送密钥,通过安全访问验证。
- 发0x2E 0xF191写入新的软件版本号。
- 发0x22 0xF191读取刚写入的版本号,确认写入成功。
- 发0x28 0x01 0x02禁用应用报文,准备刷写。
- 刷写过程中,每2秒发一次0x3E 0x00保持会话。
- 刷写完成后,发0x28 0x00 0x02恢复应用报文。
- 发0x11 0x01复位ECU,让新软件生效。
你看,这一套流程下来,0x22、0x2E、0x28、0x3E全用上了。它们之间配合默契,缺一不可。
个人经验:我建议你在开发诊断协议栈时,把这四个服务做成一个“工具包”。每个服务单独封装成一个函数,参数清晰,接口统一。这样在写应用层代码时,直接调用这些函数就行,不用每次都去拼报文。我自己的代码库里,这四个服务的函数已经用了五六年了,基本没改过。
好了,这一节的内容就到这里。下一节我们会聊更高级的服务——例程控制(0x31)和请求下载(0x34),这两个在刷写流程中至关重要。咱们到时候见。