这次我们来看UDS(ISO 14229-1)里出镜率最高、也是诊断开发测试中几乎每天都会碰到的服务:19服务ReadDTCInformation(读取DTC信息)。它负责把ECU里存着的故障码、故障发生时的冻结帧数据、DTC老化计数、严重程度等级等信息完整读出来。搞过CAN诊断的朋友对19 01 FF这条请求一定不陌生,但真正理解19服务全部子功能的人并不多。实际上19服务下有十几个子功能,项目中最常用、最核心的就是01、02、04、06这四种。这篇文章就围绕这四个子功能展开,先讲清每个子功能的理论和报文结构,再给出一段一段的CAN报文实例,逐字节拆开解析,让你看完之后能直接对照自己的测试环境做验证。
先说结论:19服务是一个不需要过多安全机制就能完成大部分故障读取的服务,多数ECU对19 01不需要安全解锁,但部分OEM会对19 02、19 04、19 06设置安全等级限制。所以实际测试时最常遇到的否定响应是7F 19 31(请求超出范围)和7F 19 33(安全访问被拒绝)。本文所有报文实例都基于ISO 14229-1标准帧格式,具体解析时仍需结合被测ECU的DID数据字典确认扩展数据的实际含义。下面直接进入正文。
1. 19服务核心概念速览
先给出一张速览表,把19服务的基本规格说清楚。这张表对初学者来说是最有价值的“定位地图”,后续所有报文解析都可以回到这张表来对照。
| 项目 | 说明 |
|---|---|
| 服务名称 | ReadDTCInformation(读取DTC信息) |
| 服务ID(SID) | 0x19 |
| 肯定响应SID | 0x59(0x19 + 0x40) |
| 否定响应格式 | 0x7F 0x19 NRC |
| 常用子功能 | 01 报告DTC数量、02 报告DTC快照记录、04 报告DTC扩展数据、06 按严重程度报告DTC数量 |
| 是否需安全解锁 | 大多数ECU的19 01不需要解锁;02/04/06视OEM实现而定,可能返回0x33 |
| 传输层 | CAN、CAN FD、DoIP、FlexRay等,常见诊断传输层为ISO 15765-2 |
| 典型应用 | 开发阶段故障注入验证、产线EOL检测、4S店售后诊断、远程诊断数据采集 |
| 报文方向 | 诊断仪(Tester)发送请求给ECU,ECU返回肯定或否定响应 |
从这张表可以看出,19服务的定位是“读故障状态”,它本身不改变ECU内部状态,也不执行清除动作,所以它和14服务(ClearDiagnosticInformation,清除DTC)常常成对出现。读取故障码用19,清除故障码用14,清除后再用19确认是否真的清除成功。
另外需要明确一个概念:19服务返回的“DTC编号”在国际标准里由三个字节组成,但在CAN诊断报文里通常用两个字节表示DTC高字节和DTC低字节。ISO 14229-1中定义DTC有三种格式,最常见的是OBD-II两字节DTC格式,也就是报文里直接出现的B1 23、C4 56这种。某些OEM扩展格式会带第三字节,这时响应里的DTCFormatIdentifier字段会不同,解析时要注意区分。
2. 19服务适用场景与使用边界
19服务的适用范围非常广。ECU开发阶段的诊断测试中,测试人员需要通过故障注入让ECU报出特定DTC,然后用19服务确认DTC是否被正确记录和响应。产线EOL检测中,下线车辆需要通过19服务读取装配过程中产生的故障码,若有故障则触发报警。售后诊断中,诊断仪读取故障码后可以直接关联到维修手册,实现快速定位。远程诊断场景中,车联网平台通过T-Box向ECU发19服务请求,周期性采集故障数据,用于预测性维护。这些场景的共同特点是:都需要“非破坏性读取”,这正是19服务最安全的应用方式。
但19服务也有明确的使用边界。第一,19服务只能读取,不能清除故障码,如果业务上需要清码,必须配合14服务完成。第二,19服务返回的DTC快照记录和扩展数据,其字节含义不是协议层能完全解释的,必须结合OEM的DID数据字典或CDD/ODX诊断数据库。第三,在实车上执行19服务时,虽然读取本身是安全的,但反复对ECU进行诊断请求会增加总线负载,如果总线仲裁优先级配置不当,可能影响其他ECU的实时通信。第四,对故障码做清除操作前,必须保留原始快照数据,尤其是涉及车辆事故、保修纠纷的场景,故障数据具有法律效力。最后要强调合规与隐私边界:对道路车辆进行诊断读取时,应当遵守当地法律法规和车辆制造商的许可协议,不得将获取的车辆故障数据、位置信息、驾驶行为数据用于未授权用途。
3. 19服务测试环境准备与前置条件
在写报文实例之前,先准备好测试环境。这里给出一套通用检查清单,覆盖软件、硬件、网络和数据库几个层面。无论你使用CANoe、PCAN还是周立功USBCAN,流程基本一样。
3.1 硬件与网络连接
- 一个支持CAN/CAN FD的接口卡,例如Vector VN1610、PCAN-USB Pro、周立功USBCAN-FD。
- 一台运行Windows的电脑,安装对应接口卡的驱动,例如PCAN需要安装PCANBasic驱动,Vector需要安装Vector Drivers。
- 确认总线波特率。整车CAN通常是500 kbps,但部分车身网络可能是250 kbps或125 kbps,设置错误会导致收不到任何报文。
- 确认终端电阻。使用CANalyzer或PCAN时,如果直接连接ECU的独立CAN网络,需确认总线两端有120欧姆终端电阻。
- 确认ECU地址。物理寻址请求通常发到0x7E0,ECU响应从0x7E8返回;功能寻址请求发到0x7DF,所有ECU同时响应。
3.2 诊断数据库准备
- 获取被测ECU的CDD(CANdelaStudio数据库)或ODX(Open Diagnostic data eXchange)文件。这个文件里定义了ECU支持的诊断服务、子功能、DID、DTC表和安全等级。
- 如果没有CDD文件,至少准备一份DTC列表和DID映射表,否则
19 02、19 04返回的快照数据、扩展数据只能看到裸字节,无法解释成物理量。 - 确认ECU支持的传输层协议。CAN诊断使用ISO 15765-2,需要关注CAN ID的扩展帧/标准帧格式,以及是否使用多帧传输。19服务响应的DTC快照记录通常很长,经常出现超过单帧8字节的情况,这时要用ISO-TP的多帧传输来完成。
3.3 软件工具准备
- CANoe或CANalyzer:完整的总线分析工具,支持CAPL脚本、诊断控制台和报文追踪。
- PCAN-View + PCANBasic:轻量级工具,适合快速手动发送请求帧。
- 周立功CANTest:适合USBCAN用户,操作简单,也支持发送自定义CAN帧。
- Python + python-can库:适合自动化测试和批量任务,后面会给出代码示例。
4. 19 01 子功能:按状态掩码报告DTC数量和状态
4.1 子功能理论
19 01的完整名称是reportNumberOfDTCByStatusMask,作用是让ECU返回满足给定状态掩码的DTC数量,以及每个DTC的编号和状态字节。它解决的问题是“ECU现在记忆了哪些故障”。实际诊断流程中,诊断仪首先发送19 01 FF获取全部故障码列表,然后再针对具体故障码发送19 02或19 04获取更详细的数据。
请求报文格式为:
19 01 [StatusMask]其中StatusMask是1字节的状态掩码。DTC状态字节共有8个bit,每一位代表一种故障状态。掩码中为1的位表示“需要匹配该状态”,为0的位表示“不关心该状态”。如果掩码为0xFF,表示不过滤,返回所有DTC。如果掩码为0x0C(二进制00001100),表示只返回同时满足“pendingDTC”和“confirmedDTC”两个状态的DTC。
DTC状态字节的bit定义如下:
| 位 | 含义 | 说明 |
|---|---|---|
| bit0 | testFailed | 当前测试周期内故障测试失败 |
| bit1 | testFailedThisOperationCycle | 本次操作循环内测试失败 |
| bit2 | pendingDTC | DTC待确认(一次运行循环内检测到故障) |
| bit3 | confirmedDTC | DTC已确认(连续多个运行循环检测到故障) |
| bit4 | testNotCompletedSinceLastClear | 自上次清除DTC后测试未完成 |
| bit5 | testFailedSinceLastClear | 自上次清除DTC后测试失败过 |
| bit6 | testNotCompletedThisOperationCycle | 本次操作循环内测试未完成 |
| bit7 | warningIndicatorRequested | 请求点亮故障指示灯(如MIL灯) |
4.2 请求与响应报文实例
下面用标准CAN物理寻址演示一个最经典的请求响应过程。
方向 CAN ID 数据 请求 0x7E0 19 01 FF 响应 0x7E8 59 01 00 02 B1 23 2D C4 56 28请求报文逐字节解析:
| 字节 | 值 | 含义 |
|---|---|---|
| byte0 | 0x19 | 服务ID,ReadDTCInformation |
| byte1 | 0x01 | 子功能,reportNumberOfDTCByStatusMask |
| byte2 | 0xFF | 状态掩码,查询所有状态的DTC |
响应报文逐字节解析:
| 字节 | 值 | 含义 |
|---|---|---|
| byte0 | 0x59 | 肯定响应SID,由0x19+0x40得到 |
| byte1 | 0x01 | 子功能回显 |
| byte2 | 0x00 | DTC数量高字节 |
| byte3 | 0x02 | DTC数量低字节,这里表示共有2个DTC |
| byte4 | 0xB1 | 第1个DTC的高字节 |
| byte5 | 0x23 | 第1个DTC的低字节,DTC编号为0xB123 |
| byte6 | 0x2D | 第1个DTC的状态字节 |
| byte7 | 0xC4 | 第2个DTC的高字节 |
| byte8 | 0x56 | 第2个DTC的低字节,DTC编号为0xC456 |
| byte9 | 0x28 | 第2个DTC的状态字节 |
状态字节0x2D的二进制是0010 1101,对应bit0=1、bit2=1、bit3=1、bit5=1。翻译成人话就是:该DTC当前测试失败、处于pending状态、已经确认、并且自上次清除后曾经测试失败过。这是一个相当典型的“当前故障+历史故障”并存的状态。状态字节0x28的二进制是0010 1000,对应bit3=1、bit5=1,表示当前没有测试失败,但已经是确认故障且自上次清除后失败过。两者对比就能看出,状态字节能直观反映故障是“进行中”还是“历史残留”。
4.3 结果判断与失败排查
判断19 01是否成功的标准很明确:第一,收到响应帧且首字节为0x59;第二,DTC数量与实际故障注入的DTC数量一致;第三,状态字节中confirmedDTC位(bit3)与ECU故障确认策略一致。如果响应的DTC数量为0,说明ECU当前记忆中没有满足掩码条件的DTC。
常见失败情况有这么几种:如果响应是7F 19 13,说明请求报文长度不对,比如发成了19 01只有两个字节,或者多发了数据;如果响应是7F 19 12,说明ECU不支持01子功能;如果一直收不到响应,先检查CAN ID、波特率和物理寻址是否正确。
5. 19 02 子功能:按DTC编号报告快照记录
5.1 子功能理论
19 02的完整名称是reportDTCSnapshotRecordByDTCNumber,作用是返回指定DTC的快照记录(Snapshot Record),也就是行业内常说的“冻结帧”数据。快照记录里保存的是故障发生瞬间ECU的关键运行参数,比如车速、转速、冷却液温度、蓄电池电压等。这些数据对故障复现和根因分析极其重要,是售后诊断和研发测试中价值最高的数据之一。
请求报文格式:
19 02 [DTC高字节] [DTC低字节]响应报文中除了DTC编号和状态之外,还会包含快照记录编号、该DTC的快照记录总数,以及每条记录内部的DID参数。这里的DID编号和数据类型完全由OEM定义,协议层只负责传输,不解释物理含义。所以19 02响应报文的解析必须结合DID字典。
5.2 请求与响应报文实例
假设我们通过19 01读取到DTC0xB123,现在要读取它的快照记录,请求如下:
方向 CAN ID 数据 请求 0x7E0 19 02 B1 23 响应 0x7E8 59 02 B1 23 2D 01 03 02 01 0F 00 64 05 96 01 F4响应报文逐字节解析:
| 字节 | 值 | 含义 |
|---|---|---|
| byte0 | 0x59 | 肯定响应SID |
| byte1 | 0x02 | 子功能回显 |
| byte2 | 0xB1 | DTC高字节 |
| byte3 | 0x23 | DTC低字节,DTC编号0xB123 |
| byte4 | 0x2D | DTC状态字节 |
| byte5 | 0x01 | DTC快照记录编号,这里是第1条快照 |
| byte6 | 0x03 | DTC快照记录总数,表示该DTC共有3条快照记录 |
| byte7 | 0x02 | 本次快照中包含的DID个数,这里表示有2个DID参数 |
| byte8-9 | 0x01 0x0F | 第1个DID编号,0x010F |
| byte10-11 | 0x00 0x64 | 第1个DID的值,0x0064,具体物理量查DID字典 |
| byte12-13 | 0x05 0x96 | 第2个DID编号,0x0596 |
| byte14-15 | 0x01 0xF4 | 第2个DID的值,0x01F4 |
需要再次强调,0x010F、0x0596这些DID编号的具体含义,不同OEM可能完全不同。在整车厂的项目里,这些DID会在诊断规范中统一定义。比如某个OEM可能定义0x010F为发动机转速,0x0596为冷却液温度。解析不出来时先查DID字典,不要靠猜。
如果快照记录很长,超过单帧CAN报文8字节,ISO-TP会自动分帧传输。你在CANoe的Trace窗口里会看到10 14开头的首帧、21开头的连续帧,这时要对照诊断数据库进行多帧重组,不能只看单帧数据。
5.3 结果判断与失败排查
19 02成功的标准是:响应中能解析出DTC编号、状态、快照编号、总数和DID数据。如果发送的DTC编号在ECU中没有对应的快照记录,ECU通常会返回7F 19 31,含义是请求超出范围。如果ECU对该子功能有安全等级保护,会返回7F 19 33,此时需要先执行27服务安全解锁再重新发送。
一个常见的坑是:ECU可能对“已确认DTC”和“未确认DTC”的快照记录策略不同。有些ECU只在confirmedDTC状态时才记录快照,有些则在pending状态时就记录了。实际测试时不能想当然地认为所有DTC都有快照记录。判断标准以ECU诊断规范为准。
6. 19 04 子功能:按DTC编号报告扩展数据
6.1 子功能理论
19 04的完整名称是reportDTCExtendedDataRecordByDTCNumber,作用是返回指定DTC的扩展数据记录。扩展数据和快照记录有本质区别:快照记录是故障发生瞬间的环境快照,属于“瞬时数据”;扩展数据是DTC的生命周期统计信息,属于“累计数据”。常见的扩展数据包括DTC老化计数器、自上次清除后的发生次数、首次发生时的里程、最后一次发生时的里程、测试失败次数等。这些数据对评估故障严重程度、判断故障是否偶发、安排维修优先级非常有帮助。
请求报文格式:
19 04 [DTC高字节] [DTC低字节]响应格式中会包含DTCFormatIdentifier、DTC编号、状态、扩展数据记录号以及扩展数据内容。扩展数据长度和内容由OEM定义,通常不是固定长度。
6.2 请求与响应报文实例
请求读取DTC0xB123的扩展数据:
方向 CAN ID 数据 请求 0x7E0 19 04 B1 23 响应 0x7E8 59 04 01 B1 23 2D 01 02 00 64 00 28响应报文逐字节解析:
| 字节 | 值 | 含义 |
|---|---|---|
| byte0 | 0x59 | 肯定响应SID |
| byte1 | 0x04 | 子功能回显 |
| byte2 | 0x01 | DTCFormatIdentifier,0x01表示两字节DTC格式 |
| byte3 | 0xB1 | DTC高字节 |
| byte4 | 0x23 | DTC低字节,DTC编号0xB123 |
| byte5 | 0x2D | DTC状态字节 |
| byte6 | 0x01 | 扩展数据记录号,这里是第1条扩展数据记录 |
| byte7 | 0x02 | 扩展数据长度,表示后面的扩展数据内容长度为2字节 |
| byte8 | 0x00 | 扩展数据内容高字节 |
| byte9 | 0x64 | 扩展数据内容低字节,整体值为0x0064=100 |
这个例子中扩展数据记录号01的内容是0x0064,假设OEM规范定义记录号01是“老化计数器”,那么该DTC已经累计发生了100次。不同记录号的扩展数据含义不同,比如记录号02可能是首次发生里程、记录号03可能是最后一次发生里程。实际项目中要让ECU工程师提供一份《DTC扩展数据记录定义表》,否则只能验证数据能否读出,无法判断数值是否合理。
6.3 结果判断与失败排查
19 04成功的标准是:能完整解析出DTC的扩展数据记录号和内容,并且数值在合理范围内。如果ECU对某个DTC没有扩展数据,响应可能是7F 19 31。如果收到7F 19 22(条件不满足),说明ECU当前状态不满足读取扩展数据的条件,比如ECU处于某些特殊模式或DTC未满足记录条件。此时先检查ECU会话状态和DTC状态。
另外要注意,19 04和19 02一样,如果扩展数据总长度超过单帧容量,在CAN上会出现ISO-TP多帧传输。在Trace里看到连续帧时,不要只抓首帧就结束,要确保整个多帧序列完整接收。
7. 19 06 子功能:按严重程度掩码报告DTC数量
7.1 子功能理论
19 06的完整名称是reportNumberOfDTCBySeverityMaskRecord,这个子功能是ISO 14229-1标准升级后引入的,用于按严重程度掩码过滤DTC。它和19 01最大的区别是:19 01只按DTC状态过滤,19 06在状态过滤之外还增加了严重程度维度。严重程度等级可以表达“这个故障是否需要立即停车”“是否只是保养提醒”等语义,对功能安全等级较高的ECU尤其重要。
严重程度掩码的各bit定义大致如下:
| bit | 值 | 含义 |
|---|---|---|
| bit0 | 0x01 | noSeverity,无严重程度 |
| bit1 | 0x02 | maintenanceOnly,仅需保养维护 |
| bit2 | 0x04 | checkAtNextHalt,下次停车时检查 |
| bit3 | 0x08 | checkImmediately,需要立即检查 |
请求报文格式:
19 06 [SeverityMask]SeverityMask为0x0F时表示查询所有严重程度等级的DTC。响应中除了DTC编号和状态,还会多出DTC严重程度字段。
7.2 请求与响应报文实例
请求查询所有严重程度的DTC:
方向 CAN ID 数据 请求 0x7E0 19 06 0F 响应 0x7E8 59 06 01 2D B1 23 01 2D C4 56 02 28响应报文逐字节解析:
| 字节 | 值 | 含义 |
|---|---|---|
| byte0 | 0x59 | 肯定响应SID |
| byte1 | 0x06 | 子功能回显 |
| byte2 | 0x01 | DTCFormatIdentifier,0x01表示两字节DTC格式 |
| byte3 | 0x2D | StatusAvailabilityMask,表示响应中DTC状态字节可能用到的位集合 |
| byte4 | 0xB1 | 第1个DTC高字节 |
| byte5 | 0x23 | 第1个DTC低字节,DTC编号0xB123 |
| byte6 | 0x01 | 第1个DTC的严重程度,0x01表示noSeverity |
| byte7 | 0x2D | 第1个DTC状态字节 |
| byte8 | 0xC4 | 第2个DTC高字节 |
| byte9 | 0x56 | 第2个DTC低字节,DTC编号0xC456 |
| byte10 | 0x02 | 第2个DTC的严重程度,0x02表示maintenanceOnly |
| byte11 | 0x28 | 第2个DTC状态字节 |
可以明显看到,19 06的响应比19 01多了一个Severity字段,同时在响应开头多了StatusAvailabilityMask。StatusAvailabilityMask告诉诊断仪:后续DTC状态字节中哪些位是有意义的。这个设计是为了让诊断仪提前知道ECU使用了哪些状态位,避免把保留位误解析成有效状态。
7.3 结果判断与失败排查
19 06的测试重点在于确认ECU是否正确实现了严重程度映射。常见的验证方法是:通过故障注入让ECU产生已知严重程度的DTC,然后用19 06 0F读取,对比严重程度字段是否符合预期。如果ECU不支持06子功能,会返回7F 19 12。如果支持但不支持某个严重程度掩码,可能返回7F 19 31。这个子功能在国产ECU和部分日系ECU上支持度参差不齐,实测前先查诊断数据库确认。
8. 完整诊断流程:读取、清除、再确认
实际项目中,19服务很少单独使用。最常见的完整流程是:先用19 01读取当前故障码,再根据需求用19 02或19 04读取详细数据,然后执行14服务清除故障码,最后再用19 01确认清除结果。下面用Python + python-can库给出一个自动化测试序列,这个脚本可以直接接入到产线测试或实验室自动化环境中。
import can import time def send_request(bus, req_id, data, resp_id=0x7E8, timeout=1.0): msg = can.Message(arbitration_id=req_id, data=data, is_extended_id=False) bus.send(msg) start = time.time() while time.time() - start < timeout: resp = bus.recv(timeout=timeout - (time.time() - start)) if resp is not None and resp.arbitration_id == resp_id: return resp.data.hex(' ').upper() return None def clear_dtc(bus): # 14服务清除所有DTC,注意14 04 FF FF FF是全局清除 resp = send_request(bus, 0x7E0, [0x14, 0x04, 0xFF, 0xFF, 0xFF]) print(f"14服务响应: {resp}") def read_dtc_count(bus): # 19 01 FF读取所有DTC resp = send_request(bus, 0x7E0, [0x19, 0x01, 0xFF]) print(f"19 01响应: {resp}") if resp: data = bytes.fromhex(resp) if len(data) >= 4 and data[0] == 0x59: count = (data[2] << 8) | data[3] print(f"DTC数量: {count}") return resp def main(): bus = can.interface.Bus(channel='PCAN_USBBUS1', interface='pcan', bitrate=500000) print("=== 读取故障码 ===") read_dtc_count(bus) print("\n=== 清除故障码 ===") clear_dtc(bus) time.sleep(0.5) print("\n=== 清除后再次读取 ===") read_dtc_count(bus) bus.shutdown() if __name__ == "__main__": main()这个脚本执行完毕后,如果最后的DTC数量为0,说明清除成功。如果数量不变,说明14服务没有生效,或者ECU对14服务有安全等级要求。需要特别注意的是,14 04 FF FF FF是清除所有DTC,实车测试时务必先备份原数据,不要一上来就清掉客户的故障码。
如果要在产线或批量测试中使用19服务,建议把上面的逻辑扩展成一个批处理任务:读取一组ECU地址、统计每个ECU的DTC数量、把结果写入日志文件、对异常DTC自动截图保存原始报文。这种批量脚本能极大提高测试效率,但要注意对每个ECU的请求间隔做控制,避免总线上请求风暴导致ECU通信过载。
9. 19服务常见NRC错误与排查方法
19服务本身逻辑不复杂,但在实际测试中错误五花八门。下面整理了一份高频问题排查表,按“问题现象、可能原因、排查方式、解决方案”四个维度列出,可以直接打印出来贴在工位上。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 请求发出后无任何响应 | CAN ID错误、波特率不匹配、物理连接异常 | 检查总线负载和Trace,确认请求帧是否上总线 | 核对ECU物理寻址ID和波特率 |
| 响应为7F 19 11 | 服务不支持 | 查看诊断数据库确认ECU是否支持19服务 | 确认ECU诊断能力,更换支持19服务的ECU或版本 |
| 响应为7F 19 12 | 子功能不支持 | 确认发送的子功能编号是否在支持列表内 | 改为ECU支持的子功能,如01 |
| 响应为7F 19 13 | 报文长度错误或格式不正确 | 核对请求报文长度和参数 | 按标准格式补全或删减参数 |
| 响应为7F 19 31 | 请求超出范围,常见于DTC编号不存在或无快照/扩展数据 | 用19 01确认该DTC是否存在,核对DTC编号 | 修改请求中的DTC编号或状态掩码 |
| 响应为7F 19 33 | 安全访问被拒绝 | 确认ECU是否对该子功能设置了安全等级 | 先执行27服务解锁再重发 |
| 19 02响应只有首帧无连续帧 | ISO-TP多帧传输被中断,或Trace过滤设置导致漏帧 | 检查Trace是否完整,确认连续帧ID | 调整Trace过滤条件,重新抓包 |
| DTC数量与预期不符 | 状态掩码用错,或ECU有故障码合并策略 | 用FF掩码读取全部DTC,核对掩码bit定义 | 按实际DTC状态位重新设置掩码 |
| 清除后DTC仍存在 | 14服务被安全等级保护,或DTC是永久故障码 | 查看14服务响应,确认是否0x33 | 解锁后清除,或按OEM策略忽略永久故障码 |
这里要重点提醒:19 02和19 04出现0x31不一定是你请求错了,很可能是这个DTC本身没有快照记录或扩展数据记录。ECU对每个DTC的记录策略不同,有些DTC只记录快照,有些只记录扩展数据,有些都有。排查时先用19 01确认DTC存在,再对照诊断数据库看该DTC是否绑定了快照或扩展数据记录。
10. 19服务测试建议与最佳实践
最后给出几条可以直接落地的测试建议,都是我平时做诊断项目时踩过坑之后总结出来的经验。
第一,先看数据库再发包。很多刚接触诊断的工程师拿到ECU就直接发19 01 FF,虽然能收到响应,但解析时全靠猜。正确做法是先打开CDD或ODX文件,确认ECU支持的子功能、DTC列表、DID映射和状态位定义,再设计测试用例。这样后面解析19 02和19 04才不会卡住。
第二,优先用功能寻址做全车扫描,用物理寻址做单ECU精读。功能寻址0x7DF会把请求广播给所有ECU,适合快速排查整车网络中有哪些ECU报了故障码。但功能寻址响应是多个ECU同时回,CAN仲裁容易丢报,所以精读某个ECU的DTC快照和扩展数据时,必须切回物理寻址。
第三,对快照数据和扩展数据做二次校验。报文里读到的原始值,要结合信号定义换算成物理值,再和实际故障注入时的工况对比。比如你注入故障时发动机转速是1500rpm,快照里读到的转速原始值换算后也应该是1500rpm左右,偏差过大说明DID定义或换算公式用错了。
第四,版本兼容性要提前确认。ISO 14229-1从2013版到2020版,19服务增加了若干子功能,06子功能就是新版标准新增的内容。不同年份的ECU实现可能不同,老ECU很可能不支持06,甚至对19 02的响应格式也有差异。测试前确认ECU遵循的标准版本,比对着协议书硬调更高效。
第五,批量任务必须做日志和重试。如果你的测试系统每天要跑几千个ECU,19服务请求失败是常态。每个ECU的请求要独立记录日志,失败时至少重试3次,重试间隔大于500ms,同时把原始请求和响应帧都保存下来,方便后续定位是ECU问题还是台架问题。
第六,涉及实车数据采集和故障清除操作,必须拿到车辆所有者的授权。故障码和快照记录涉及车辆的健康数据,清除操作会改变ECU记忆状态,在保修和事故鉴定场景下不能随意操作。尽量在实验室台架上完成故障注入和清除验证,实车诊断以读取类服务为主。
19服务是UDS诊断体系里最基础的读取型服务,理解它的关键在于两点:一是把四个常用子功能的职责边界分清,01管数量,02管快照,04管扩展数据,06管严重程度;二是遇到响应报文时,先认SID、再认子功能、后认数据,逐字节拆解就不会乱。把这套流程跑熟,后面再接触27安全服务、22按DID读数据、2E按DID写数据,都会轻松很多。建议直接拿一块开发板或台架ECU,按本文的报文实例发一遍,验证ECU实际返回的数据结构和标准是否一致,这样理解最深。