第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
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字节数据]
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 排查过程
我当时的排查思路是这样的:
- 先用0x22读DID 0x0102 —— 确认ECU内部车速值是0。
- 再用0x23读内存 —— 找到车速信号在RAM(随机存取存储器)中的地址,直接读原始值。
- 对比发现:0x22读出来是0,但0x23读出来是0x1234(对应实际车速)。
这说明什么?说明ECU的DID映射层出了问题!
0x22服务读取DID时,走的是应用层代码。而0x23直接读内存,绕过了应用层。
最终定位到:DID 0x0102的映射函数里,有一个指针初始化错误,导致读取时返回了0。
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(安全访问拒绝)。记得先做解锁操作。
4.6 小结
这一章我们聊了:
- 0x22服务:通过DID读取数据,安全、标准、适合量产。
- 0x23服务:通过地址读取内存,灵活、危险、适合调试。
- 实际案例:0x22和0x23数据不一致时,问题在应用层。
- 避坑指南:DID长度、地址越界、安全访问。
下一章,我们会聊写入数据服务——0x2E和0x3D。到时候你会看到,写数据比读数据要小心得多,一个不小心就可能把ECU写“砖”了。
咱们下章见。