3. UDS服务基础:服务标识符(SID)概念、服务分类与服务格式编码

好,咱们今天聊聊UDS协议里最基础、也最核心的东西——服务标识符,也就是SID。

说实话,我刚接触UDS那会儿,看着那一堆十六进制数,心里也犯过嘀咕:这玩意儿到底怎么组织的?后来做网关项目做多了,慢慢就摸清了门道。今天我把这些经验掰开揉碎了讲给你听。

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

SID,全称Service Identifier。说白了,它就是UDS协议里每条请求的“身份证”。

你想想看,诊断仪发一条报文给ECU,ECU怎么知道你想干嘛?是想读数据?还是想写数据?还是想执行某个动作?

答案就在报文的第一个字节——SID。

核心概念:每个UDS服务都有一个唯一的SID,范围是0x00到0x3E。诊断仪发请求时,SID放在请求报文的首字节。ECU回复时,会在SID基础上加0x40,表示“响应成功”。

举个例子:

  • 你发 0x22,表示“我要读数据”
  • ECU回 0x62,表示“好的,数据给你”
  • 如果ECU回的是 0x7F,那说明出错了,后面会跟具体错误码

嗯,这里要注意:0x7F 是负响应码,不是某个服务的SID。我见过不少新手把 0x7F 当成服务来用,结果调试半天找不到问题。

3.2 服务分类:六大功能组

UDS协议把26个服务分成了6大类。我个人习惯把这六类记成“诊断六脉神剑”,哈哈,开个玩笑。但确实,每类服务解决一类问题。

分类 SID范围 典型服务 一句话说明
诊断与通信管理 0x10 ~ 0x3E 0x10(诊断会话控制)、0x11(ECU复位)、0x28(通信控制) 管连接、管模式、管通信
数据传输 0x22 ~ 0x2E 0x22(读数据)、0x2E(写数据) 读写DID,最常用
存储数据传输 0x14 ~ 0x19 0x14(清除诊断信息)、0x19(读取DTC信息) 管故障码的
输入输出控制 0x2F 0x2F(输入输出控制) 强制控制某个引脚或执行器
远程激活 0x31 0x31(例程控制) 执行一段预定义程序
上传下载 0x34 ~ 0x37 0x34(请求下载)、0x36(传输数据)、0x37(请求传输退出) 刷写固件用的

我在项目中遇到过最尴尬的事:有个同事把 0x19(读DTC)和 0x22(读数据)搞混了。他以为读故障码也能用 0x22 加DID的方式,结果ECU一直回负响应。后来我帮他一看,服务都选错了,能不报错吗?

3.3 服务格式与编码

每个UDS服务都有固定的格式。我总结了一个通用模板:

请求格式: [SID] [Sub-function/Data] [参数...]
正响应格式: [SID + 0x40] [Sub-function/Data] [参数...]
负响应格式: [0x7F] [SID] [NRC]

咱们拿最常用的 0x22(读数据)来举例:

请求报文:

22 F1 90
|  |   |
|  |   +-- DID高字节 (0x90)
|  +------ DID低字节 (0xF1)
+--------- SID = 0x22

正响应报文:

62 F1 90 01 02 03 04
|  |   |  |  |  |  |
|  |   |  +--+--+--+-- 数据内容 (4字节)
|  |   +-- DID高字节
|  +------ DID低字节
+--------- SID + 0x40 = 0x62

负响应报文:

7F 22 31
|  |  |
|  |  +-- NRC = 0x31 (请求超出范围)
|  +------ 请求的SID
+--------- 负响应标识

我的小技巧:调试时,先看响应报文的第一个字节。如果是 0x7F,直接看第三个字节的NRC,能省一半排查时间。我曾经靠这个习惯,在客户现场10分钟定位了一个刷写失败的问题。

3.4 子功能参数:Sub-function

有些服务支持子功能,比如 0x10(诊断会话控制)。子功能放在SID后面,占一个字节。

这里有个坑:子功能的最高位(bit 7)有特殊含义。

  • bit 7 = 0:表示“请求执行该子功能”
  • bit 7 = 1:表示“请求抑制正响应”

举个例子:

10 01    → 请求切换到默认会话,并期望ECU回复
10 81    → 请求切换到默认会话,但ECU不要回复

为什么要抑制正响应?我解释一下:在刷写过程中,如果每发一条报文ECU都回一条,总线负载会很高。抑制正响应能减少总线流量,提高刷写速度。嗯,这个技巧在批量刷写时特别有用。

注意:不是所有服务都支持子功能。比如 0x22(读数据)就没有子功能,它的SID后面直接跟DID。如果你给 0x22 加了个子功能字节,ECU会回 0x7F 0x22 0x13(报文长度或格式错误)。

3.5 实际项目中的编码习惯

我建议你在设计诊断栈时,把SID定义成枚举类型。这样代码可读性高,也不容易写错。

typedef enum {
    UDS_SID_DIAG_SESSION_CTRL   = 0x10,
    UDS_SID_ECU_RESET           = 0x11,
    UDS_SID_READ_DATA           = 0x22,
    UDS_SID_WRITE_DATA          = 0x2E,
    UDS_SID_ROUTINE_CTRL        = 0x31,
    UDS_SID_REQUEST_DOWNLOAD    = 0x34,
    UDS_SID_TRANSFER_DATA       = 0x36,
    UDS_SID_REQUEST_TRANSFER_EXIT = 0x37
} UdsSid_t;

另外,响应超时时间也要注意。ISO 14229-1里建议:

  • 默认会话:响应超时 50ms
  • 扩展会话:响应超时 50ms
  • 编程会话:响应超时 500ms ~ 5000ms(取决于具体操作)

我曾经在编程会话里把超时设成了50ms,结果刷写时ECU擦除Flash花了200ms,网关直接判定超时,刷写失败。后来改成2000ms,问题解决。所以,超时时间要根据实际场景灵活调整。

3.6 小结

今天咱们把UDS服务的骨架搭起来了:

  • SID是服务的身份证,范围0x00~0x3E
  • 六大服务分类,各管一摊
  • 请求/响应有固定格式,负响应用0x7F开头
  • 子功能的bit 7控制是否抑制正响应

下一章,咱们会深入第一个服务——0x10 诊断会话控制。到时候我会讲讲不同会话模式之间的切换逻辑,以及我在网关项目中踩过的那些坑。

好,今天就到这儿。有什么问题,咱们留言区见。