3. UDS服务分类与格式:服务标识符(SID)定义、请求/响应报文结构、肯定响应与否定响应码(NRC)

好,我们进入第三章。这一章讲的是UDS最基础的东西——服务的分类、报文长什么样、以及怎么判断诊断成功了还是失败了。

说实话,很多工程师做了好几年诊断开发,对SID和NRC的理解还是模棱两可。我见过有人把0x10(诊断会话控制)和0x11(ECU复位)的请求报文格式搞混,结果刷写流程死活跑不通。嗯,咱们今天就把这些基础打扎实。

3.1 服务标识符(SID)的定义与分类

SID就是服务的身份证号。每个UDS服务都有一个唯一的8位ID,范围从0x00到0xFF。但并不是所有值都能用,ISO 14229-1把SID分成了几个功能组。

我个人习惯把SID分成三大类:

  • 诊断与通信管理类:0x10~0x3E,负责会话控制、ECU复位、安全访问等
  • 数据传输类:0x22~0x2E,读写数据、按标识符读写、动态定义标识符
  • 存储数据传输类:0x14~0x1A,清除/读取诊断信息、例程控制

你想想看,这其实跟医院的科室划分很像。挂号(会话控制)、检查(读取数据)、治疗(例程控制)、出院(ECU复位)。

核心要点:SID的高4位决定了服务的功能组。比如0x2X系列全是数据传输类服务,0x3X系列全是输入输出控制类。记住这个规律,查规范的时候能快很多。

我在项目中遇到过一个问题:某供应商的Bootloader把0x31(例程控制)的SID写成了0x30,结果ECU死活不响应。查了两天才发现是SID定义错了。所以,SID的二进制值一定要对着规范逐位核对。

3.2 请求/响应报文结构

UDS的报文结构其实很简单。一个诊断请求由三部分组成:

  1. SID:服务标识符,1字节
  2. 子功能(Sub-function):可选,1字节,最高位表示是否需要肯定响应
  3. 数据参数(Data):可变长度,具体内容由服务定义

举个例子,读取数据标识符服务(0x22)的请求报文:

请求:22 F1 90
  - 22:SID,表示读取数据标识符
  - F1 90:数据标识符,2字节,这里表示读取VIN码

响应报文分两种:肯定响应和否定响应。

肯定响应报文格式:

响应:62 F1 90 4C 46 56 33 33 30 41 42 43 44 45 46 47 48 49 4A
  - 62:SID + 0x40,表示肯定响应
  - F1 90:数据标识符
  - 4C 46 56...:实际数据,这里是VIN码"LFV330ABCDEFGHIJ"

否定响应报文格式:

响应:7F 22 31
  - 7F:固定值,表示否定响应
  - 22:请求的SID
  - 31:否定响应码,这里表示请求超出范围

这里有个细节:肯定响应的SID = 请求SID + 0x40。比如0x22变成0x62,0x10变成0x50。这个规律在所有UDS服务中都适用。

个人技巧:我习惯在代码里用宏定义来区分请求和响应:#define SID_POSITIVE_RESP(sid) ((sid) | 0x40)。这样写出来的代码可读性高,也不容易出错。

3.3 肯定响应与否定响应码(NRC)

肯定响应好理解,就是ECU说“我收到了,也执行了”。但否定响应才是诊断开发的重头戏。NRC就是ECU告诉你“为什么没执行”。

常用的NRC有这些:

NRC值 名称 含义
0x10 一般拒绝 ECU当前无法处理该请求
0x11 不支持该服务 SID不在ECU支持列表中
0x12 子功能不支持 SID支持,但子功能不支持
0x13 报文长度错误 请求报文长度不对
0x22 条件不满足 当前会话/安全等级不允许
0x31 请求超出范围 数据参数无效
0x33 安全访问拒绝 未解锁或解锁失败
0x78 请求正确但正在处理 需要额外时间,请等待

我曾经踩过一个坑:在Bootloader里做刷写时,发送了0x34(请求下载)后,ECU返回了0x78。我以为出错了,直接终止了流程。后来才发现,0x78的意思是“我收到了,正在准备,你等会儿再发数据”。

避坑指南:0x78是一个非常特殊的NRC。它表示ECU正在处理,但还没准备好接收后续数据。收到0x78后,诊断仪应该等待一段时间再重新发送相同的请求,而不是直接放弃。我见过很多新手在这里翻车。

3.4 知识结构图:UDS服务与报文格式

下面这张图把本章的核心知识点串起来了。从SID分类到报文结构,再到响应机制,一目了然。

UDS服务分类与报文格式 诊断与通信管理 (0x10~0x3E) 数据传输 (0x22~0x2E) 存储/例程控制 (0x14~0x1A) 请求报文结构 SID (1字节) + 子功能 (可选, 1字节) + 数据参数 (可变长度) 示例: 22 F1 90 → 读取VIN码 肯定响应 SID + 0x40 + 数据 示例: 62 F1 90 + VIN数据 否定响应 0x7F + SID + NRC 示例: 7F 22 31 (请求超出范围) 常用NRC示例 0x10: 一般拒绝 0x22: 条件不满足 0x31: 请求超出范围 0x78: 正在处理 注意:0x78表示ECU需要额外时间,不是错误

3.5 实战中的注意事项

最后,分享几个我在项目中积累的经验:

  • SID的优先级:诊断会话控制(0x10)和ECU复位(0x11)的优先级最高。无论ECU在做什么,这两个服务必须立即响应。我在Bootloader里就把它们放在中断里处理。
  • NRC的返回顺序:ECU检查NRC是有顺序的。先检查SID是否支持(0x11),再检查子功能(0x12),然后检查报文长度(0x13),最后检查条件(0x22)和数据(0x31)。这个顺序在ISO 14229-1里有明确规定。
  • 0x78的处理:收到0x78后,诊断仪应该等待至少50ms再重试。我见过有些工具等100ms,有些等200ms。建议在代码里做成可配置的,方便调试。

个人习惯:我在设计诊断栈时,会把NRC检查做成一个状态机。每个服务注册自己的检查函数,按优先级顺序执行。这样代码结构清晰,也方便后期维护。