第4章:诊断服务之读取数据——通过ID读取数据服务、通过地址读取内存服务、实际案例分析

大家好,欢迎来到第四章。

这一章我们聊点实在的——读取数据。

你想想看,ECU(电子控制单元)出了故障,维修技师第一件事是什么?

肯定是读数据啊!

读故障码、读传感器值、读版本号……这些操作背后,就是UDS(统一诊断服务)里的两个核心服务:0x22 —— 通过ID读取数据,和0x23 —— 通过地址读取内存

这两个服务,我做了这么多年项目,几乎天天打交道。今天就把它们的区别、用法、以及实战中的坑,一次性给你讲透。

4.1 0x22服务:通过ID读取数据

先说0x22。这个服务,说白了就是“你给我一个ID,我还你一个值”。

这个ID是啥?是DID(数据标识符)。每个DID对应一个特定的数据项,比如发动机转速、车速、软件版本号等等。

我个人习惯把DID理解成“房间号”。你告诉ECU房间号,ECU就去那个房间把数据拿出来给你。

4.1.1 请求与响应格式

请求报文长这样:

| 字节 | 内容          | 说明               |
|------|---------------|--------------------|
| 0    | 0x22          | 服务ID             |
| 1    | DID高字节     | 如0xF1             |
| 2    | DID低字节     | 如0x90             |

正响应报文:

| 字节 | 内容          | 说明               |
|------|---------------|--------------------|
| 0    | 0x62          | 服务ID + 0x40      |
| 1    | DID高字节     | 原样返回           |
| 2    | DID低字节     | 原样返回           |
| 3..N | 数据          | 实际读取到的值     |

举个例子,读取ECU的软件版本号(DID = 0xF190):

请求:22 F1 90
响应:62 F1 90 01 02 03 04  // 版本号是 01 02 03 04
小提示:DID的分配不是随便来的。ISO 14229-1里定义了标准DID范围,但OEM(原始设备制造商)通常有自己的分配表。我建议你拿到项目后,第一件事就是找OEM要DID列表,别自己猜。

4.1.2 常见DID举例

DID 含义 数据长度
0xF190 软件版本号 4字节
0xF191 硬件版本号 4字节
0xF192 供应商代码 2字节
0x0101 发动机转速 2字节
0x0102 车速 2字节

嗯,这里要注意:DID的数据长度是固定的。你请求0xF190,ECU必须返回4字节,不能多也不能少。否则诊断仪那边会报错。

4.2 0x23服务:通过地址读取内存

0x23和0x22有啥区别?

我打个比方你就明白了:

  • 0x22 是“去前台拿资料”——你告诉前台你要什么(DID),前台帮你查好给你。
  • 0x23 是“自己去仓库翻”——你告诉ECU内存地址和长度,ECU直接给你原始数据。

0x23更底层,更灵活,但也更危险。我在项目中见过有人用0x23把ECU读死机的……嗯,后面会讲怎么避免。

4.2.1 请求与响应格式

请求报文:

| 字节 | 内容          | 说明               |
|------|---------------|--------------------|
| 0    | 0x23          | 服务ID             |
| 1    | 地址高字节     | 如0x08             |
| 2    | 地址中字节     | 如0x00             |
| 3    | 地址低字节     | 如0x00             |
| 4    | 长度高字节     | 如0x00             |
| 5    | 长度低字节     | 如0x10             |

正响应报文:

| 字节 | 内容          | 说明               |
|------|---------------|--------------------|
| 0    | 0x63          | 服务ID + 0x40      |
| 1..N | 数据          | 读取到的内存内容   |

举个例子,读取地址0x08000000开始的16字节:

请求:23 08 00 00 00 10
响应:63 [16字节数据]
警告:0x23服务可以读取任意内存地址。如果你不小心读到了受保护的区域(比如Flash的Bootloader区),ECU可能会进入异常状态。我曾经遇到过,读了一个不该读的地址,ECU直接复位了……从那以后,我都在代码里加了地址范围检查。

4.3 0x22 vs 0x23:什么时候用哪个?

对比项 0x22(通过ID读取) 0x23(通过地址读取)
使用场景 读取标准诊断数据 读取原始内存、调试
安全性 高(ECU内部映射) 低(直接访问内存)
灵活性 低(只能读定义好的DID) 高(任意地址)
开发难度 高(需要内存布局知识)
OEM偏好 量产车常用 研发阶段常用

我个人建议:量产项目中优先用0x22。0x23虽然强大,但风险也大。除非你是做Bootloader开发或者底层调试,否则尽量别碰0x23。

4.4 实际案例分析

好了,理论讲完了,咱们来看一个真实案例。

4.4.1 案例背景

我之前参与过一个项目,客户反馈说:ECU在某个工况下会报“车速信号无效”的故障码。

维修技师用诊断仪读取了DID 0x0102(车速),发现值是0。但用示波器量CAN(控制器局域网)总线,车速信号明明是有的。

问题出在哪?

4.4.2 排查过程

我当时的排查思路是这样的:

  1. 先用0x22读DID 0x0102 —— 确认ECU内部车速值是0。
  2. 再用0x23读内存 —— 找到车速信号在RAM(随机存取存储器)中的地址,直接读原始值。
  3. 对比发现:0x22读出来是0,但0x23读出来是0x1234(对应实际车速)。

这说明什么?说明ECU的DID映射层出了问题!

0x22服务读取DID时,走的是应用层代码。而0x23直接读内存,绕过了应用层。

最终定位到:DID 0x0102的映射函数里,有一个指针初始化错误,导致读取时返回了0。

关键结论:当0x22和0x23读出的数据不一致时,问题大概率出在应用层代码,而不是底层硬件。

4.4.3 修复与验证

修复了指针初始化问题后,再用0x22读DID 0x0102,值就对了。

嗯,这个案例让我深刻体会到:0x23服务在调试阶段真的很有用。它能帮你绕过应用层,直接看底层数据。

4.5 避坑指南

最后,分享几个我踩过的坑:

  • DID长度不匹配:我曾经在代码里把DID 0xF190的长度写成了3字节,但OEM要求4字节。结果诊断仪一直报负响应0x13(数据长度错误)。排查了半天才发现是长度定义错了。
  • 0x23地址越界:有一次我写测试脚本,不小心把0x23的地址参数写成了0xFFFFFFFF。ECU直接卡死,只能断电重启。从那以后,我都在代码里加了地址范围检查,超出范围就返回0x31(请求超出范围)。
  • 安全访问:有些DID和内存区域是需要安全访问(Security Access)才能读的。如果你没解锁就去读,ECU会返回0x33(安全访问拒绝)。记得先做解锁操作。
我的习惯:在开发阶段,我会把0x22和0x23的日志都打开。这样一旦数据对不上,我能快速定位是应用层问题还是底层问题。这个习惯帮我省了不少时间。

4.6 小结

这一章我们聊了:

  • 0x22服务:通过DID读取数据,安全、标准、适合量产。
  • 0x23服务:通过地址读取内存,灵活、危险、适合调试。
  • 实际案例:0x22和0x23数据不一致时,问题在应用层。
  • 避坑指南:DID长度、地址越界、安全访问。

下一章,我们会聊写入数据服务——0x2E和0x3D。到时候你会看到,写数据比读数据要小心得多,一个不小心就可能把ECU写“砖”了。

咱们下章见。