3、数据记录服务(0x22):ReadDataByIdentifier服务详解、DID(数据标识符)的定义与访问

好,咱们今天聊聊UDS里最常用的一个服务——0x22服务,也就是ReadDataByIdentifier。说白了,就是“读数据”。

你想想看,ECU里存了那么多参数,比如当前车速、电池电压、故障码状态、软件版本号……这些东西怎么读出来?就是靠这个0x22服务。我个人习惯把它叫做“数据快照服务”,因为它能让你随时抓取ECU内部任意一个数据点的当前值。

3.1 什么是DID?

DID,全称Data Identifier,数据标识符。你可以把它理解成每个数据点的“门牌号”。

ECU内部有成千上万个参数,每个参数都有一个唯一的DID编号。比如:

  • 0xF190:可能代表“当前车速”
  • 0xF191:可能代表“发动机转速”
  • 0xF18C:可能代表“电池包总电压”

DID是一个16位的数值,范围从0x0000到0xFFFF。但实际项目中,常用的DID范围是有规范的,比如ISO 14229-1里定义了标准DID,OEM也会自定义自己的DID。

重要概念:DID不仅仅是“读”的入口,它背后还绑定了数据的长度、格式、单位、换算关系。你读一个DID,得到的是原始字节,怎么解析成物理值,那是应用层的事。

3.2 0x22服务的请求与响应格式

这个服务的格式非常简洁。我直接上代码,你看一眼就明白了。

请求报文

| 字节 | 名称             | 值示例       | 说明                     |
|------|------------------|--------------|--------------------------|
| 0    | 服务ID           | 0x22         | ReadDataByIdentifier     |
| 1    | DID高字节        | 0xF1         | DID = 0xF190             |
| 2    | DID低字节        | 0x90         |                          |

嗯,就这么简单。客户端发3个字节,告诉ECU:“我要读DID 0xF190的数据。”

肯定响应

| 字节 | 名称             | 值示例       | 说明                     |
|------|------------------|--------------|--------------------------|
| 0    | 响应服务ID       | 0x62         | 0x22 + 0x40 = 0x62      |
| 1    | DID高字节        | 0xF1         | 回显请求的DID            |
| 2    | DID低字节        | 0x90         |                          |
| 3..N | 数据字节         | 0x00 0x64    | 数据内容,长度由DID定义  |

ECU收到请求后,会回复0x62(0x22+0x40),然后原样返回DID,最后跟上数据。比如车速是100km/h,可能返回0x00 0x64(100的十六进制)。

我的经验:我在调试一个BMS项目时,遇到过ECU死活不响应0x22服务。查了半天,发现是DID的访问权限没配好。有些DID只能在特定会话模式下才能读,比如扩展会话。所以,如果你发0x22没反应,先检查一下当前会话模式对不对。

3.3 DID的定义与访问规则

DID不是随便定义的。每个DID背后,都有一张“数据字典”。这张表里定义了:

  • DID编号:16位唯一标识
  • 数据长度:比如4字节、8字节
  • 数据类型:uint8、uint16、float、ASCII字符串等
  • 换算公式:原始值如何转成物理值,比如 物理值 = 原始值 * 0.1
  • 访问权限:只能在哪些会话模式下读?是否需要安全访问?

举个例子,一个典型的DID定义表:

DID 名称 长度 类型 换算 访问条件
0xF190 车速 2字节 uint16 原始值 * 0.01 km/h 默认会话
0xF191 发动机转速 2字节 uint16 原始值 * 0.125 rpm 默认会话
0xF18C 电池总电压 2字节 uint16 原始值 * 0.1 V 扩展会话
0xF195 VIN码 17字节 ASCII 直接ASCII解码 默认会话

你看,同样是读数据,有的DID在默认会话就能读,有的必须切到扩展会话。这是OEM为了保护敏感数据而设计的。

注意:千万不要以为所有DID都能随便读。我曾经在一个项目中,因为没注意访问权限,在默认会话下尝试读取一个需要安全解锁的DID,结果ECU直接回复了否定响应0x7F 0x22 0x33(securityAccessDenied)。搞得我排查了半天,最后发现是权限问题。

3.4 多DID读取:ReadDataByIdentifier的批量操作

0x22服务还支持一次请求读取多个DID。这在诊断工具里很常见,比如你想同时读车速、转速、电压,可以一次搞定。

请求格式稍微变一下:

| 字节 | 名称             | 值示例       | 说明                     |
|------|------------------|--------------|--------------------------|
| 0    | 服务ID           | 0x22         |                          |
| 1    | DID1高字节       | 0xF1         | 第一个DID = 0xF190       |
| 2    | DID1低字节       | 0x90         |                          |
| 3    | DID2高字节       | 0xF1         | 第二个DID = 0xF191       |
| 4    | DID2低字节       | 0x91         |                          |

响应也是类似的,ECU会按顺序返回每个DID的数据:

| 字节 | 名称             | 值示例       | 说明                     |
|------|------------------|--------------|--------------------------|
| 0    | 响应服务ID       | 0x62         |                          |
| 1    | DID1高字节       | 0xF1         |                          |
| 2    | DID1低字节       | 0x90         |                          |
| 3..4 | DID1数据         | 0x00 0x64    | 车速数据                 |
| 5    | DID2高字节       | 0xF1         |                          |
| 6    | DID2低字节       | 0x91         |                          |
| 7..8 | DID2数据         | 0x0A 0x00    | 转速数据                 |

我个人建议,批量读取时不要一次读太多DID。为什么呢?因为ECU的处理能力有限,一次读十几个DID,响应时间可能会很长,甚至超时。我一般控制在5个以内,比较稳妥。

3.5 实际开发中的避坑指南

做0x22服务开发,有几个坑我踩过,分享给你:

  • DID长度不匹配:ECU定义的DID长度是4字节,你只读了2字节,解析出来的数据肯定是错的。一定要核对数据字典。
  • 字节序问题:有些ECU用大端,有些用小端。比如车速0x0064,大端解析是100,小端解析是25600。差远了。我习惯在项目初期就统一约定好字节序。
  • 否定响应处理:0x22服务可能返回各种否定码,比如0x31(requestOutOfRange)、0x33(securityAccessDenied)、0x78(responsePending)。尤其是0x78,表示ECU正在忙,需要你等一下再发。很多新手看到0x78就慌了,其实这是正常流程。

我的小技巧:在开发阶段,我会写一个简单的测试脚本,遍历所有DID,看看哪些能读、哪些不能读,响应时间是多少。这样能快速发现配置问题。我曾经用这个方法,半天就排查出了3个DID配置错误。

3.6 总结

0x22服务是UDS诊断里最基础、最常用的服务。它简单直接,但背后涉及DID的定义、访问权限、数据解析等细节。说白了,你只要把DID这张“数据字典”搞清楚了,0x22服务就没什么难度。

下一章,我们会讲0x2E服务——WriteDataByIdentifier,也就是写数据。到时候你会发现,读和写其实是同一套逻辑,只是方向不同。