3. UDS协议核心概念

UDS(Unified Diagnostic Services,统一诊断服务)是ISO 14229定义的一套应用层诊断协议。在嵌入式开发中,无论你面对的是CAN、CAN FD还是DoIP,UDS报文的“骨架”始终由四个核心概念构成:服务标识符SID、子功能Sub-function、数据参数Data Parameter,以及肯定响应与否定响应格式。理解这四者,是读懂诊断请求、构造诊断响应、排查诊断失败的第一步。

一、SID:诊断服务的“身份证”

SID(Service Identifier)是UDS报文的第一个字节,用来告诉ECU“你要我做什么”。它决定了后续字节如何被解析。SID通常分为两类:

  • 请求SID:客户端(诊断仪)发给ECU,例如0x10、0x22、0x2E、0x31。
  • 响应SID:ECU回复给客户端。肯定响应的SID = 请求SID + 0x40;否定响应则固定使用0x7F。

例如,请求0x22(ReadDataByIdentifier)的肯定响应SID是0x62;请求0x2E(WriteDataByIdentifier)的肯定响应SID是0x6E。这个“+0x40”规则是UDS报文解析中最基础的规律,也是嵌入式代码中判断响应类型的第一道分支。

二、子功能:同一服务下的“动作开关”

很多UDS服务在SID之后紧跟一个字节,称为子功能(Sub-function)。它用于在同一服务下选择不同操作。例如0x10(DiagnosticSessionControl)的子功能有0x01默认会话、0x02编程会话、0x03扩展会话;0x31(RoutineControl)的子功能有0x01启动例程、0x02停止例程、0x03请求例程结果。

子功能字节的最高位(bit7)是抑制肯定响应位(suppressPosRspMsgIndicationBit)。当该位为1时,ECU执行成功后不发送肯定响应,只发否定响应。这在刷写或高频轮询场景中能显著减少总线负载。嵌入式开发中,解析子功能时必须先屏蔽bit7,再判断实际子功能值。

关键规则:子功能字节 = 实际子功能值 | 抑制位。例如0x10服务的扩展会话请求,若抑制肯定响应,则字节为0x83(0x03 | 0x80)。

三、数据参数:SID与子功能之后的“载荷”

数据参数是UDS报文中承载具体信息的部分,其格式由SID和子功能共同决定。常见的数据参数包括:

  • DID(Data Identifier):2字节,用于0x22/0x2E/0x2F等服务,标识要读写的具体数据对象。
  • RoutineIdentifier:2字节,用于0x31服务,标识要执行的例程。
  • 数据记录:读写操作中实际的数据内容,长度由DID定义决定。
  • 安全等级种子/密钥:用于0x27服务,种子和密钥长度由ECU安全算法决定。

在嵌入式代码中,数据参数通常以字节数组形式接收,需要根据SID进行长度校验和边界检查。例如0x22请求必须至少包含2字节DID,否则应返回否定响应0x13(incorrectMessageLengthOrInvalidFormat)。

四、肯定响应与否定响应格式

UDS响应格式是诊断开发中必须严格遵循的“语法”。肯定响应和否定响应结构不同,处理逻辑也不同。

响应类型 首字节 后续结构 示例
肯定响应 SID + 0x40 子功能(若请求含子功能)+ 数据参数 请求22 F1 90 → 响应62 F1 90 + 数据
否定响应 0x7F 请求SID + NRC(否定响应码) 请求22 F1 90 → 响应7F 22 31

否定响应中的NRC(Negative Response Code)是排障的核心线索。常见NRC包括:

  • 0x11:服务不支持(ServiceNotSupported)
  • 0x12:子功能不支持(SubFunctionNotSupported)
  • 0x13:报文长度或格式错误(IncorrectMessageLengthOrInvalidFormat)
  • 0x22:条件不满足(ConditionsNotCorrect)
  • 0x31:请求超出范围(RequestOutOfRange)
  • 0x33:安全访问被拒绝(SecurityAccessDenied)
  • 0x78:响应挂起(RequestCorrectlyReceived-ResponsePending)

0x78不是错误:它表示ECU已收到请求,但需要更多时间处理,后续会再发一个最终响应。嵌入式开发中,收到0x78后应继续等待,而不是立即判定失败。

五、完整报文解析案例

假设诊断仪请求读取DID 0xF190(VIN码),ECU支持该DID且当前会话允许读取。报文交互如下:

请求:22 F1 90
  22        → SID:ReadDataByIdentifier
  F1 90     → DID:VIN码

肯定响应:62 F1 90 57 50 30 5A 5A 5A ...
  62        → 0x22 + 0x40,肯定响应
  F1 90     → 回显DID
  57 50 ... → VIN数据(ASCII)

若DID不支持:
否定响应:7F 22 31
  7F        → 否定响应固定SID
  22        → 原请求SID
  31        → NRC:RequestOutOfRange

在嵌入式代码中,解析这段报文通常按以下流程:先判断首字节是否为0x7F,若是则提取NRC并进入错误处理;否则判断首字节是否等于请求SID+0x40,再校验DID回显是否一致,最后提取数据。这个顺序能避免把否定响应误判为肯定响应。

六、常见误区与复盘

  • 误区一:忽略子功能抑制位。直接把0x83当作子功能0x83处理,导致分支判断错误。正确做法是先与0x7F做按位与。
  • 误区二:把0x78当失败。0x78是“响应挂起”,需要继续等待最终响应,否则会误报诊断超时。
  • 误区三:肯定响应不校验DID回显。某些ECU可能返回错误DID,若不校验会导致数据错位。
  • 误区四:否定响应只记录不分类。NRC应分类处理:0x11/0x12属于配置问题,0x13属于报文构造问题,0x33属于安全访问顺序问题,0x31属于参数范围问题。

开发建议:在ECU诊断栈中,建议为每个SID建立独立的请求解析函数和响应构造函数,并统一维护NRC返回表。这样既能保证格式一致,也便于后续增加新服务时快速扩展。

UDS报文结构与响应判定 请求报文(Client → ECU) SID 子功能 数据参数 例:22 F1 90(读VIN) 子功能可选,DID为2字节 肯定响应(ECU → Client) SID+0x40 子功能回显 数据参数 例:62 F1 90 + VIN数据 首字节 = 请求SID + 0x40 否定响应(ECU → Client) 0x7F 请求SID NRC 例:7F 22 31(DID超出范围) 响应判定顺序 1. 首字节 == 0x7F ? → 否定响应,提取NRC 2. 首字节 == 请求SID+0x40 ? → 肯定响应 3. 校验子功能/DID回显 → 提取数据 常见NRC速查: 0x11 服务不支持 | 0x12 子功能不支持 | 0x13 长度/格式错误 | 0x22 条件不满足 | 0x31 超出范围 | 0x33 安全拒绝 | 0x78 响应挂起

掌握SID、子功能、数据参数以及两类响应格式,就相当于掌握了UDS诊断的“字母表”。后续无论是实现0x27安全访问、0x31例程控制,还是0x34/0x36/0x37刷写序列,都只是在这套基础语法上组合出更长的“句子”。在嵌入式开发中,建议把SID解析、子功能屏蔽、NRC返回封装成独立模块,让诊断栈的每一层都只关心自己的职责,这样代码才经得起30个章节所覆盖的复杂场景考验。