2、UDS协议核心:UDS服务分类与SID详解

好,咱们直接进入正题。

UDS协议里最核心的东西是什么?我个人觉得,就是那26个服务。你把它搞明白了,诊断开发就通了八成。剩下的两成,是时间参数和会话管理那些边边角角。

这26个服务,ISO 14229-1把它们分成了6大类。我刚开始学的时候,觉得分类没啥用,背SID就完了。后来踩了坑才发现——分类决定了你代码的模块怎么划分,测试用例怎么组织。嗯,这里很重要。

2.1 六大服务分类概览

先看一张总表,心里有个谱:

分类 功能描述 典型SID
诊断与通信管理 会话控制、ECU复位、安全访问等 0x10, 0x11, 0x27, 0x3E
数据传输 读写DID、内存地址 0x22, 0x23, 0x2E
存储数据传输 读写DTC信息 0x14, 0x19
输入输出控制 强制控制引脚/信号 0x2F
远程激活 启动例程、下载程序 0x31, 0x34, 0x36
上传/下载 刷写相关 0x34, 0x35, 0x36, 0x37

注意,上传下载类在有些文档里被归到远程激活里。我个人习惯按功能分,刷写就是刷写,跟例程激活分开,代码结构更清晰。

2.2 诊断与通信管理类

这是最基础的一类。你想想看,ECU都不知道你在哪个会话模式,怎么响应你的请求?

0x10 - 诊断会话控制

这个服务用来切换会话模式。默认会话、扩展会话、编程会话,就靠它。

子功能参数:

  • 0x01:默认会话(Default Session)
  • 0x02:编程会话(Programming Session)
  • 0x03:扩展会话(Extended Session)

我在项目中遇到过一个问题:ECU从编程会话切回默认会话时,没有正确释放资源,导致下次进编程会话失败。后来加了个状态机清理逻辑才搞定。

避坑指南: 会话切换后,之前使能的服务可能被禁用。比如扩展会话下才能用的0x2F,切回默认会话就失效了。

0x11 - ECU复位

这个简单,就是让ECU重启。子功能有硬复位、软复位、快速断电再上电等。

我曾经见过一个案例:复位后ECU没有及时发送响应,导致上位机超时报错。其实ECU已经复位成功了,但响应报文被丢掉了。解决方案是在复位前先发一个肯定响应,再执行复位动作。

0x27 - 安全访问

这个服务有点意思。它通过种子和密钥的交换,来验证访问权限。

流程是这样的:

  1. 客户端发0x27 0x01请求种子
  2. ECU返回种子(比如4字节随机数)
  3. 客户端计算密钥,发0x27 0x02带上密钥
  4. ECU验证,通过则解锁

说白了,就是一把锁和一把钥匙。但要注意,种子和密钥的算法是OEM自己定的,没有统一标准。我见过用AES的,也见过简单的异或算法。

注意: 安全访问有次数限制。连续失败几次后,ECU会锁定一段时间。这是防暴力破解的机制。

0x3E - 保持激活

这个服务叫Tester Present。它的作用就是告诉ECU:我还活着,别超时退出会话。

子功能只有0x00(请求响应)和0x80(无响应)。我一般用0x80,省带宽。

2.3 数据传输类

这类服务用来读写ECU内部的数据。数据以DID(Data Identifier)来标识。

0x22 - 按标识符读取数据

这是最常用的服务之一。比如读VIN码、读软件版本号、读电压值,都用它。

请求格式:0x22 + DID(2字节)

响应格式:0x62 + DID + 数据

// 示例:读取VIN码,DID = 0xF190
请求:22 F1 90
响应:62 F1 90 4C 56 4B 33 32 33 30 31 32 33 34 35 36 37 38 39
      // "LVK3230123456789" 的ASCII码

0x2E - 按标识符写入数据

跟0x22对应,用来写数据。但要注意,不是所有DID都能写。能写的DID需要OEM在规范里定义好。

我遇到过一个问题:写DID时,数据长度不对,ECU直接回了NRC 0x13(长度错误)。后来我养成了习惯,先读一下DID的长度,再拼写请求。

0x23 - 按地址读取内存

这个服务更底层。它直接读写内存地址,不经过DID映射。一般用于调试或刷写。

说实话,这个服务我用的不多。因为0x22已经能满足大部分需求了。只有在做底层调试时,才会用0x23直接看内存。

2.4 存储数据传输类

这类服务专门处理DTC(诊断故障码)。

0x14 - 清除诊断信息

就是清DTC。子功能固定为0xFF。

请求:14 FF FF FF(三个FF表示清除所有DTC)

注意:清DTC前,通常需要先做安全访问解锁。否则ECU会拒绝。

0x19 - 读取DTC信息

这个服务比较复杂,子功能很多。常用的有:

  • 0x01:按状态掩码读取DTC数量
  • 0x02:按状态掩码读取DTC列表
  • 0x04:读取快照数据
  • 0x06:读取扩展数据

状态掩码是个8位数据,每一位代表一种状态。比如bit0表示"当前激活",bit1表示"历史记录"。

小技巧: 如果你只想看当前有哪些故障,用0x19 0x02,掩码设0x01就够了。别一次读太多,ECU响应报文可能超长。

2.5 输入输出控制类

这个类只有一个服务,但非常实用。

0x2F - 输入输出控制

它可以强制控制ECU的某个输出引脚,或者模拟某个输入信号。比如强制继电器吸合、强制风扇转动。

子功能参数:

  • 0x00:返回控制状态
  • 0x01:复位为默认值
  • 0x02:冻结当前值
  • 0x03:强制为指定值

我在做HIL测试时,经常用0x2F来模拟传感器故障。比如强制温度传感器输出-40度,看ECU会不会报相关DTC。

警告: 0x2F控制的是真实物理输出。如果控制的是电机或继电器,一定要确保安全。我曾经在台架上用0x2F控制风扇,结果忘了关,风扇转了一整夜...

2.6 远程激活类

这类服务用来执行ECU内部的程序或例程。

0x31 - 例程控制

这个服务很灵活。它可以启动、停止、查询某个例程的执行状态。

子功能:

  • 0x01:启动例程
  • 0x02:停止例程
  • 0x03:查询例程结果

例程ID是2字节,由OEM定义。比如0x0202可能是"擦除Flash",0xFF01可能是"自检"。

0x34 - 请求下载

这是刷写流程的第一步。客户端告诉ECU:我要下载数据了,大小是多少,存在哪个地址。

请求格式:0x34 + 数据格式 + 地址 + 大小

ECU会返回一个块大小,告诉客户端每次最多发多少字节。

0x36 - 传输数据

这是刷写流程的第二步。客户端把数据分块发过来。

请求格式:0x36 + 块序号 + 数据

注意:块序号从1开始,每次递增。ECU用这个序号来校验数据是否丢包。

0x37 - 请求退出传输

刷写完成后,用这个服务结束传输。

2.7 我的经验总结

说了这么多,我总结几条实战经验:

  1. SID别死记硬背。用多了自然就记住了。我刚开始时,桌上贴了一张SID速查表,一个月后就不需要了。
  2. 子功能要分清。有些服务(如0x19)子功能很多,每个子功能的响应格式都不一样。写代码时一定要对照规范。
  3. NRC比响应更重要。调试时,先看ECU回了什么NRC。0x22(条件不满足)、0x31(请求超出范围)、0x13(长度错误),这些NRC能帮你快速定位问题。
  4. 安全访问是很多服务的前置条件。写0x2E、清0x14、做0x31,通常都需要先解锁。别漏了这一步。

好了,这一章就到这里。下一章我们讲UDS的报文格式和时间参数,那才是真正考验协议栈实现的地方。