3、UDS服务基础:服务标识符(SID)概念、请求与响应机制、肯定响应与否定响应码(NRC)基础

好,我们正式开始聊UDS协议的核心——服务。你想想看,诊断仪和ECU之间怎么对话?总得有个规矩吧。这个规矩就是“请求-响应”机制,而服务标识符(SID)就是每条指令的“身份证号”。

我刚开始接触UDS时,总觉得这玩意儿就是发一串十六进制数,没啥技术含量。后来踩了坑才明白——SID选不对、响应格式写错、NRC没处理好,ECU直接就不理你了。咱们今天就把这些基础打牢。

3.1 服务标识符(SID)是什么?

SID,全称Service Identifier,占用1个字节。范围从0x00到0xFF,但UDS协议只定义了其中一部分。说白了,它就是诊断请求的“操作码”。

举个例子:你想读取ECU的故障码,那就发0x19;你想读写数据,那就发0x22或0x2E。每个SID背后都对应一套固定的子功能(Sub-function)和参数结构。

关键点:SID的高层分类很清晰——

  • 0x10 - 0x3E:诊断与通信管理类(如会话控制、安全访问)
  • 0x22 - 0x2E:数据传输类(如读取数据、写入数据)
  • 0x14 - 0x19:故障存储类(如清除故障码、读取故障码)
  • 0x31 - 0x3D:输入输出控制类(如例程控制、IO控制)
  • 0x34 - 0x36:上传下载类(如请求下载、传输数据)

我个人习惯把SID表打印出来贴在工位上。你想想看,调试时查手册多浪费时间?背下来几个常用的,效率翻倍。

3.2 请求与响应机制

UDS的通信模型很简单:诊断仪发请求 → ECU回响应。但这里有个细节——响应分两种:肯定响应和否定响应。

3.2.1 请求格式

一个标准的UDS请求长这样:

| SID (1字节) | Sub-function (1字节, 可选) | 数据参数 (N字节) |

举个例子,读取ECU的VIN码(数据标识符0xF190):

请求:22 F1 90
  → 0x22 = 读取数据服务
  → 0xF190 = 数据标识符

3.2.2 肯定响应格式

ECU处理成功时,会返回肯定响应。格式是:SID + 0x40,再加上具体数据。

肯定响应:62 F1 90 4C 46 56 33 30 30 30 30 30 30 30 30 30 30 30
  → 0x62 = 0x22 + 0x40
  → 0xF190 = 数据标识符
  → 后续字节 = VIN码 "LFV30000000000000"

嗯,这里要注意:肯定响应的第一个字节永远是请求SID加上0x40。这是协议硬性规定,别搞错了。

3.2.3 否定响应格式

如果ECU处理失败,它会返回否定响应。格式固定为三字节:

否定响应:7F [请求SID] [NRC]
  → 0x7F = 否定响应标识
  → 请求SID = 你刚才发的SID
  → NRC = 否定响应码,告诉你为什么失败

举个例子,你发了个0x22请求,但ECU当前会话不支持:

请求:22 F1 90
响应:7F 22 7F
  → 0x7F = 否定响应
  → 0x22 = 请求的SID
  → 0x7F = NRC "服务不支持当前会话"

我的经验:调试时看到0x7F开头,别慌。先看第三个字节的NRC,那才是关键。我曾经花了一下午查一个0x22请求失败的问题,最后发现是NRC 0x78(请求正确接收-响应待定)——ECU只是忙,等一会儿重试就好了。

3.3 否定响应码(NRC)基础

NRC是UDS协议里最实用的部分。它告诉你ECU为什么拒绝你的请求。常见的NRC就那么十几种,我列个表:

NRC (十六进制) 名称 含义
0x10 一般拒绝 请求格式正确,但ECU无法处理(比如正在执行其他任务)
0x11 服务不支持 你发的SID,ECU根本不认识
0x12 子功能不支持 SID认识,但子功能号不对
0x13 报文长度错误 你发的数据长度不对,多一个字节或少一个字节
0x22 条件不满足 ECU当前状态不允许执行该服务(比如没上电)
0x31 请求超出范围 参数值非法(比如数据标识符不存在)
0x33 安全访问拒绝 需要解锁才能执行该服务
0x35 密钥无效 安全访问时密钥不对
0x36 超过最大重试次数 安全访问尝试次数超限
0x37 所需时间未到 安全访问需要等待一段时间
0x78 请求正确接收-响应待定 ECU正在处理,稍后重试
0x7E 服务不支持当前会话 当前会话模式不支持该服务
0x7F 服务不支持当前会话(同0x7E) 同上,部分协议版本用0x7F

避坑指南:我曾经在OTA升级项目中遇到一个诡异问题——ECU总是返回0x78。查了半天,发现是ECU在处理大数据写入时,内部缓冲区满了,但协议栈没正确处理流控。解决方案是:客户端收到0x78后,等待100ms再重试,最多重试3次。如果还不行,就报超时错误。

3.4 请求-响应流程的完整示例

咱们用一个实际场景串一下:诊断仪想读取ECU的软件版本号(数据标识符0xF1A0)。

  1. 诊断仪发送请求:22 F1 A0
  2. ECU收到请求:
    • 检查SID 0x22是否支持 → 是
    • 检查数据标识符0xF1A0是否存在 → 是
    • 检查当前会话是否允许读取 → 是
    • 检查安全访问是否要求解锁 → 否(读取数据通常不需要解锁)
  3. ECU返回肯定响应:62 F1 A0 56 31 2E 30 2E 30
    • 0x62 = 肯定响应标识
    • 0xF1A0 = 数据标识符
    • 56 31 2E 30 2E 30 = ASCII码 "V1.0.0"

如果ECU当前处于编程会话(Programming Session),但该会话不允许读取数据标识符0xF1A0,它会返回:7F 22 7E。

3.5 知识结构图:UDS请求-响应核心流程

下面这张图帮你理清整个逻辑:

UDS请求-响应核心流程 UDS请求-响应核心流程 诊断仪(Tester) ECU(服务器) 请求(SID + 参数) ECU处理流程 1. 检查SID是否支持 2. 检查子功能/参数 3. 检查会话/安全条件 4. 执行服务逻辑 处理成功? 是 → 肯定响应 SID+0x40 + 数据 否 → 否定响应 0x7F + SID + NRC 关键点:肯定响应 = SID + 0x40;否定响应 = 0x7F + SID + NRC NRC 0x78 表示“响应待定”,客户端应等待后重试

3.6 实战中的几个坑

  • 坑一:SID+0x40溢出——如果SID是0xF0,加0x40后变成0x130,超过1字节范围。协议规定:只取低8位,即0x30。我见过新手直接写死0x40+SID,结果0xF0的服务永远收不到肯定响应。
  • 坑二:NRC优先级——ECU检查条件有顺序。比如先检查SID是否支持(NRC 0x11),再检查子功能(NRC 0x12),最后检查安全访问(NRC 0x33)。你发一个不支持的SID,ECU不会告诉你“安全访问拒绝”,而是直接报“服务不支持”。
  • 坑三:0x78的误用——有些工程师把0x78当成“ECU忙,别烦我”,但实际上0x78的意思是“请求已正确接收,正在处理,请稍后重试”。两者区别很大:前者是拒绝,后者是延迟响应。

我的习惯:在项目初期,我会写一个UDS请求-响应的状态机,把每个SID的肯定响应和否定响应都列出来。调试时对照着看,一眼就能发现问题。你想想看,如果ECU返回0x7F 0x22 0x31,你马上就知道是数据标识符超出范围,而不是SID不支持。