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,也就是写数据。到时候你会发现,读和写其实是同一套逻辑,只是方向不同。