第3章:UDS 服务详解(中):读取数据 (0x22)、读取内存 (0x23)、读取故障码 (0x19)、写入数据 (0x2E)、写入内存 (0x3D)
各位工程师,欢迎来到第三讲。上一讲我们聊了会话控制和ECU复位,算是把UDS的“握手”和“重启”搞定了。今天这一讲,我们进入真正的“干活”环节——读数据、写数据、读故障码。
说白了,诊断的核心就三件事:看状态、查毛病、改参数。0x22、0x23、0x19、0x2E、0x3D这五个服务,就是干这些活的。我做了这么多年诊断测试,可以说90%的日常诊断工作都离不开它们。
3.1 读取数据 (0x22) —— 最常用的“体温计”
0x22服务,全称是ReadDataByIdentifier。它的作用很简单:你告诉ECU你想看哪个数据,ECU就把那个数据返回给你。
请求格式:
| Byte 0 | Byte 1 | Byte 2 | ... |
|--------|--------|--------|-----|
| 0x22 | DID高字节 | DID低字节 | 数据... |
响应格式:
| Byte 0 | Byte 1 | Byte 2 | Byte 3 | ... |
|--------|--------|--------|--------|-----|
| 0x62 | DID高字节 | DID低字节 | 数据字节1 | 数据字节2... |
这里有个关键概念——DID(Data Identifier)。每个DID对应一个特定的数据项,比如0xF190可能是电池电压,0xF191可能是电机转速。DID的分配由OEM自己定义,所以不同厂家的DID表完全不同。
重要提醒:0x22服务只能读取“静态数据”或“实时数据”,但不能读取内存地址。读取内存地址是0x23的事,别搞混了。
我个人习惯,在测试0x22时,会先确认ECU当前处于什么会话模式。因为有些DID只在扩展会话或编程会话下才能访问。我记得有一次,客户说某个DID读不到数据,我查了半天,发现是ECU还在默认会话里,根本没切到扩展会话。嗯,这种低级错误其实挺常见的。
3.2 读取内存 (0x23) —— 直接“扒开”ECU看
0x23服务,ReadMemoryByAddress。这个服务比0x22更底层——它直接读取ECU内存的某个地址范围。
请求格式:
| Byte 0 | Byte 1 | Byte 2-3 | Byte 4-5 | ... |
|--------|--------|----------|----------|-----|
| 0x23 | 地址长度+内存长度 | 起始地址 | 内存大小 | 数据... |
这里有个容易踩坑的地方:地址长度和内存长度的编码方式。Byte 1的高四位表示地址长度(字节数),低四位表示内存大小长度(字节数)。比如0x24表示地址占2字节,内存大小占4字节。
避坑指南:我曾经在测试一个项目时,0x23服务总是返回否定响应。查了半天,发现是地址长度编码错了。ECU要求地址用4字节表示,但我只发了2字节。记住:地址长度和内存大小长度必须严格按照ECU的规范来,多一个字节或少一个字节都不行。
0x23服务通常用于刷写、标定等场景。普通诊断很少用,因为直接操作内存风险很大。你想想看,万一写错了地址,ECU可能直接死机。
3.3 读取故障码 (0x19) —— 诊断的“照妖镜”
0x19服务,ReadDTCInformation。这是诊断协议里最复杂的服务之一,没有之一。它的子功能多达28种,但实际常用的也就那么几个。
常用子功能:
| 子功能 | 含义 | 说明 |
|---|---|---|
| 0x01 | 按状态掩码读取DTC | 最常用,指定DTC状态位来过滤 |
| 0x02 | 读取DTC快照数据 | 记录故障发生时的环境数据 |
| 0x03 | 读取DTC扩展数据 | 比如故障发生次数、老化计数器 |
| 0x04 | 读取所有DTC | 简单粗暴,全量返回 |
| 0x06 | 读取DTC故障码 | 只返回DTC编号,不返回状态 |
| 0x0A | 读取所有DTC(含状态) | 比0x04更详细 |
DTC状态掩码:每个DTC有8个状态位,分别是:
- Bit 0:测试失败(当前周期)
- Bit 1:测试失败(已完成)
- Bit 2:已确认(未清除)
- Bit 3:待确认(未完成测试)
- Bit 4:测试未完成(当前周期)
- Bit 5:测试未完成(历史)
- Bit 6:警告指示灯请求
- Bit 7:已清除(但未完全清除)
举个例子:如果你用状态掩码0x04(Bit 2),就只读取“已确认”的故障码。这在产线终检时特别有用——只看那些真正需要维修的故障。
实战技巧:我建议在测试0x19服务时,先发一个0x19 0x0A看看ECU能返回多少DTC。如果返回的DTC数量和你预期的不符,再逐步缩小范围。我曾经遇到一个ECU,0x19 0x04返回了50个DTC,但0x19 0x01(状态掩码0xFF)只返回了3个。后来发现是ECU把很多“历史故障”的状态位清零了,但DTC本身还保留着。
3.4 写入数据 (0x2E) —— 给ECU“打针”
0x2E服务,WriteDataByIdentifier。和0x22对应,一个读一个写。
请求格式:
| Byte 0 | Byte 1 | Byte 2 | Byte 3... |
|--------|--------|--------|-----------|
| 0x2E | DID高字节 | DID低字节 | 要写入的数据 |
响应格式:
| Byte 0 | Byte 1 | Byte 2 |
|--------|--------|--------|
| 0x6E | DID高字节 | DID低字节 |
0x2E服务通常用于写入配置参数、标定值等。但要注意:不是所有DID都支持写入。有些DID是只读的,你写它会返回否定响应(0x7F 0x2E 0x31,请求超出范围)。
避坑指南:我曾经在产线上遇到过一个问题——写入某个DID后,ECU直接进入了“安全模式”。后来发现,那个DID控制着某个关键安全参数,写入错误的值导致ECU认为系统异常。所以,写入数据前一定要确认:这个DID是干什么的?写入的值是否在合理范围内?
3.5 写入内存 (0x3D) —— 直接“改代码”
0x3D服务,WriteMemoryByAddress。和0x23对应,一个读一个写。
请求格式:
| Byte 0 | Byte 1 | Byte 2-3 | Byte 4-5 | Byte 6... |
|--------|--------|----------|----------|-----------|
| 0x3D | 地址长度+内存长度 | 起始地址 | 内存大小 | 要写入的数据 |
0x3D服务比0x2E更底层、更危险。它直接修改ECU内存中的内容,通常用于刷写Bootloader或应用程序。普通诊断几乎不会用到这个服务。
为什么?因为0x3D没有“校验机制”。你写进去的数据对不对,ECU不会帮你检查。如果写错了,ECU可能直接变砖。所以,实际刷写时,我们通常用0x34(请求下载)和0x36(传输数据)的组合,而不是直接用0x3D。
核心区别总结:
- 0x22/0x2E:通过DID读写“逻辑数据”,安全、规范、常用
- 0x23/0x3D:通过地址读写“物理内存”,底层、危险、少用
- 0x19:读取故障码,子功能多,需要仔细理解状态掩码
3.6 一致性测试要点
在认证测试中,这几个服务是必考的。我总结几个常见失败点:
- 0x22的DID范围:ECU必须明确声明支持的DID范围。测试时会随机选几个DID验证,如果返回否定响应,必须确认是“不支持”还是“当前不可用”。
- 0x19的状态掩码:很多ECU在状态掩码处理上有bug。比如,掩码为0x00时应该返回空,但有些ECU会返回所有DTC。
- 0x2E的安全访问:写入操作通常需要先进行安全访问(0x27服务)。如果没做安全访问就写,ECU必须返回否定响应(0x7F 0x2E 0x33,安全访问拒绝)。
- 0x23/0x3D的地址对齐:有些ECU要求地址必须按4字节对齐,否则返回否定响应。这个在测试规范里通常会写明。
好了,这一讲的内容就到这里。下一讲我们会聊剩下的几个服务——控制DTC设置 (0x85)、例程控制 (0x31)、安全访问 (0x27) 等。这些服务在诊断测试中同样重要,尤其是安全访问,几乎是所有写入操作的前置条件。
记住:诊断协议不是背出来的,是测出来的。多动手,多踩坑,才能真正理解。