UDS 19服务核心子功能01/02/04/06报文解析与实战

发布时间:2026/9/2 22:00:43
UDS 19服务核心子功能01/02/04/06报文解析与实战 19 服务0x19ReadDTCInformation是 UDS 诊断协议里出场率最高的服务之一。无论是产线终检、售后诊断、刷写前置检查还是 CANoe 仿真测试都离不开对 DTC 状态的读取。很多初学诊断协议的同学卡在同一个地方文档里写“19 服务有 28 个子功能”但实际工作中翻来覆去只用到 01、02、04、06 这几个。文末完整代码建议收藏。这篇文章不做概念堆砌直接拆开这四个子功能每一段都给出请求报文、正响应报文、否定响应报文和字段解释。报文中的数据全部对齐 ISO 14229-1 标准格式示例统一使用 DTC P0100报文码为01 00。读完你可以直接拿这套报文去 CANoe、CANalyzer 或者诊断仪上对照验证。1. 核心能力速览能力项说明服务名称ReadDTCInformation读取诊断故障码信息服务 ID0x19常用子功能01 报告 DTC 数量、02 按掩码报告 DTC、04 读取快照记录、06 读取扩展数据记录适用协议UDS / ISO 14229-1传输层CAN / CAN FD / LIN本文按 CAN 的 ISO-TP 为例典型应用产线末检、售后诊断、刷写前故障检查、DTC 清除后验证关键前置条件ECU 已进入诊断会话默认会话或扩展会话均可否定响应风险0x12、0x13、0x22、0x31、0x72适合读者诊断开发工程师、测试工程师、Bootloader 刷写开发、OBD 相关开发者这里特别说明一个容易混淆的点19 服务虽然子功能非常多但从报文格式上只分两种一种是“按 DTC 状态掩码查询”另一种是“按 DTC 码查询详细信息”。01 和 02 属于前者04 和 06 属于后者。把这个结构理解清楚后面的报文就很容易记住。2. 19 服务基础与 DTC 状态字节学习 19 服务之前必须先把 DTC 状态字节DTC Status Byte搞清楚。这个字节只有 8 位但整组 01/02 子功能的查询逻辑都是围绕它设计的。DTC 状态字节各位定义如下BIT含义bit0testFailed当前测试失败bit1testFailedThisOperationCycle本操作循环测试失败bit2pendingDTC待定 DTCbit3confirmedDTC已确认 DTCbit4testNotCompletedSinceLastClear上次清除后测试未完成bit5testFailedSinceLastClear上次清除后测试失败bit6testNotCompletedThisOperationCycle本操作循环测试未完成bit7warningIndicatorRequested已请求报警指示灯实际报文里这个字节以十六进制掩码形式出现。比如0x09表示 bit0 和 bit3 同时为 1也就是“当前测试失败且已确认”的 DTC0x0A表示 bit1 和 bit3 同时为 1即“本操作循环失败且已确认”。掩码不匹配的 DTC在 01/02 服务中会被过滤掉。19 服务的基本请求格式如下请求19 [子功能] [附加参数...] 响应59 [子功能] [数据...] 否定7F 19 [否定响应码]无论哪个子功能正响应 SID 都固定是0x59也就是 0x19 0x40。子功能字节要原样回显。如果 ECU 返回7F 19 xx就说明服务执行失败最后的字节就是 NRC否定响应码。这里顺便把常用 NRC 列出来后面报文实例会反复用NRC含义出现场景0x12子功能不支持请求了 01/02/04/06 以外的子功能0x13报文长度或格式错误子功能与报文长度不匹配0x22条件不满足当前会话不允许读取某些 DTC 数据0x31请求超出范围DTC 码不存在、快照编号无效、扩展数据编号无效0x72一般编程失败Flash 刷写场景下相关数据不可用3. 子功能 01报告 DTC 数量子功能 01 的作用是查询满足特定状态掩码的 DTC 一共有多少个。请求格式如下请求19 01 [DTC状态掩码可选参数] 响应59 01 DTC状态可用性掩码 DTC格式标识符 DTC数量2字节如果请求不带状态掩码直接发19 01ECU 就返回所有 DTC 的数量。但实际项目里一般都会带上掩码比如19 01 09只统计当前故障中“测试失败且已确认”的 DTC。来看一个完整的报文实例请求19 01 09 响应59 01 09 01 00 01逐字节解析19服务 ID。01子功能。09请求中携带的 DTC 状态掩码。59正响应 SID。01回显子功能。09DTCStatusAvailabilityMask表示 ECU 支持的 DTC 状态位。这里是 bit0 和 bit3说明 ECU 当前只上报“测试失败”和“已确认”两个状态位。01DTCFormatIdentifier0x01 表示 DTC 格式符合 ISO 14229-1 标准的三字节 DTC2 字节 DTC 码 1 字节状态。00 01DTC 数量为 1。这个数量字段是 2 字节高字节在前。实际应用里子功能 01 经常被 Bootloader 作为刷写前的“故障检查”来用。上位机先发一条19 01 09如果返回数量不为 0就会在界面上提示“当前存在故障请先清除故障码”防止带着故障进入刷写流程。一个容易踩的坑如果响应里的 DTCStatusAvailabilityMask 是0x00说明 ECU 当前没有上报任何 DTC 状态位并不是说“没有故障”。这种情况通常需要先运行一遍 DTC 检测逻辑或者进入特定诊断会话再读取。4. 子功能 02按状态掩码报告 DTC子功能 02 和 01 的区别在于01 只返回 DTC 数量02 返回满足掩码条件的每一个 DTC 的具体码和状态。请求报文必须携带状态掩码。请求19 02 [DTC状态掩码] 响应59 02 DTC状态可用性掩码 DTC格式标识符 DTC数量2字节 DTC13字节 DTC23字节...每个 DTC 记录固定占 3 字节2 字节 DTC 码 1 字节状态。比如 DTC P0100 在 UDS 报文里就是01 00后面的01是这个 DTC 当前的状态字节。看一个返回两个 DTC 的完整实例请求19 02 09 响应59 02 09 01 00 02 01 00 01 01 15 09逐字节解析响应59 02 09 01正响应 SID、子功能、状态可用性掩码、DTC 格式标识符。00 02DTC 数量为 2。01 00 01第一个 DTC码是01 00状态是0x01。0x01 表示 testFailed 置位即当前测试失败。01 15 09第二个 DTC码是01 15状态是0x09。0x09 表示 testFailed 和 confirmedDTC 同时置位即测试失败且已确认。这个响应在真实 CAN 总线上的传输结构和应用层略有差异。当 DTC 数量很多、总响应长度超过 7 字节时ISO-TP 层会把响应拆成单帧加多帧发送。比如上面这个响应总长度是 11 字节在 CAN 上会看到如下分段帧110 0B 59 02 09 01 00 02 帧221 01 00 01 01 15 0910 0B首帧0x0B 表示总长度 11 字节。21第一个后续帧携带剩余 7 字节数据。22如果有更多数据会继续编号直到发完。连接诊断仪测试时如果看到7F 19 13先检查报文长度。比如19 02后面漏了状态掩码或者多加了一个字节都会触发 0x13。5. 子功能 04读取快照记录子功能 04 是 19 服务里最能体现“故障现场还原”能力的一个。它会返回某个 DTC 发生时的环境数据比如发动机转速、车速、冷却液温度、系统电压等。这一块的内容由整车厂自行定义协议层只定义外层封装格式。请求格式请求19 04 [DTC高字节] [DTC低字节] [快照记录编号] 响应59 04 DTC状态 DTC高字节 DTC低字节 快照记录编号 快照数据...快照记录编号有特殊含义0x01表示请求第一组快照也就是 DTC 第一次锁定时的数据0xFF表示请求该 DTC 的所有快照记录。看一个读取 P0100 第一组快照的报文实例请求19 04 01 00 01 响应59 04 01 01 00 01 02 0A 23 40 0F逐字节解析59 04正响应 SID 和子功能。01DTC 状态当前状态为 0x01即 testFailed。01 00DTC 码对应 P0100。01快照记录编号0x01。02快照记录中第一个数据标识符这里代表“发动机转速”。0A 23转速值2 字节高字节在前。按厂商算法换算后可得实际转速。40第二个数据标识符代表“冷却液温度”。0F温度值1 字节。快照数据的内部格式不是 UDS 标准强约束的具体每个数据标识符代表什么物理量、多少字节、如何换算要查对应 ECU 的 DTC 快照规范。这意味着同一份报文在不同项目里解析出来的语义可能完全不同。测试的时候不要想当然把厂商 A 的快照格式套到厂商 B 上。如果请求的快照记录编号不存在会得到否定响应请求19 04 01 00 F0 响应7F 19 310xF0这个编号在 ECU 中没有对应记录NRC 返回 0x31请求超出范围。快照数据一般会绑定 DTC 第一次发生到当前时刻的“冻结帧”。在售后诊断场景里读快照可以快速判断故障发生时的车辆运行条件是定位偶发故障最常用的手段之一。偶尔出现的问题如果只靠读 DTC 码很难定位配合快照里的车速、转速、电压维修工程师能缩小排查范围。6. 子功能 06读取扩展数据记录子功能 06 返回 DTC 关联的扩展数据。典型内容包括故障发生次数、老化计数器、首次发生里程、最后发生里程、累计工作时间等。这类数据和 04 的快照不同快照更像是一次性的“冻结现场”扩展数据是持续的“故障统计档案”。请求格式请求19 06 [DTC高字节] [DTC低字节] [扩展数据记录编号] 响应59 06 DTC状态 DTC高字节 DTC低字节 扩展数据记录编号 扩展数据...扩展数据记录编号的规则与快照编号一致0x01到0xFE是具体编号0xFF表示请求该 DTC 的所有扩展数据记录。看一个读取 P0100 扩展数据记录编号 1 的报文实例请求19 06 01 00 01 响应59 06 01 01 00 01 0A这里的0A是扩展数据内容具体代表什么要看 ECU 的 DTC 扩展数据定义。比如有的项目里扩展数据记录0x01表示故障发生次数0x02表示老化计数器。不同 OEM 定义差异很大。继续看一个更复杂的实例读取 P0115 的扩展数据记录编号 2请求19 06 01 15 02 响应59 06 0A 01 15 02 12 34 56 7859 06正响应 SID 和子功能。0ADTC 状态0x0A 表示本操作循环测试失败且已确认。01 15DTC 码对应 P0115。02扩展数据记录编号。12 34 56 78扩展数据内容共 4 字节。根据厂商定义可能表示“首次发生里程”或者“故障累计时长”。扩展数据经常被用于“故障计数”和“排放相关诊断”的逻辑。在产线末检时如果 ECU 上报了某个 DTC但状态位没有完全置位读一下扩展数据里的失败计数可以判断故障是真实发生够阈值还是偶发干扰。注意子功能 06 的请求长度是 5 字节子功能 04 也是 5 字节。如果发送方把请求长度写错比如19 06 01 00ECU 会因为没有扩展数据记录编号而返回7F 19 13也就是报文长度或格式错误。7. 报文实例验证方法拿到上面这些报文之后建议先在仿真环境里跑一遍。这里给出一套通用的验证流程适用于 CANoe、PEAK CAN 配合 zlgiua 等工具或 Python 串接 USBCAN 设备。7.1 用 CANoe CAPL 发送 19 02 请求CAPL 脚本可以直接通过诊断通道发送请求。下面是一个通用的测试脚本发送19 02 09请求并打印响应on key d { byte request[3] {0x19, 0x02, 0x09}; byte response[64]; int length; length DiagnosticSendRequest(CD_ECU, request, 3); write(Request sent: 19 02 09); }这里的CD_ECU是 CANoe 诊断配置里的 ECU 名称实际使用时需要替换成你自己的诊断描述文件节点。如果工程里没有配置诊断通道可以改用普通报文发送函数on key r { message 0x7E0 msg; msg.byte(0) 0x03; msg.byte(1) 0x19; msg.byte(2) 0x02; msg.byte(3) 0x09; msg.byte(4) 0x00; msg.byte(5) 0x00; msg.byte(6) 0x00; msg.byte(7) 0x00; output(msg); }物理寻址还是功能寻址取决于具体工程的 CAN ID 分配。功能寻址时多个 ECU 会同时给响应容易造成总线冲突所以读取 DTC 建议使用物理寻址。7.2 用 Python 发送并解析响应没有 CANoe 环境时可以用支持 CAN 接口的 Python 库。下面是一个基于 socketcan 的通用模板import socket import struct # 创建 CAN 套接字需根据实际网卡名替换 sock socket.socket(socket.AF_CAN, socket.CAN_RAW) sock.bind((can0,)) # 用 ISO-TP 发送请求时可以使用自定义封装 def send_uds_request(arbitration_id, payload): can_id arbitration_id length len(payload) pci 0x00 if length 7: pci 0x00 | length # 单帧 else: pci 0x10 | ((length 8) 0x0F) # 多帧首帧 # 这里仅作为结构示例实际 ISO-TP 需要处理流控帧和连续帧 frame struct.pack(IB3x, can_id, (pci 24) | (payload[0] 16) | (payload[1] 8) | payload[2]) sock.send(frame) # 调用示例 payload [0x19, 0x02, 0x09] # 19 02 请求 send_uds_request(0x7E0, payload)使用这个模板时需要注意ISO-TP 多帧传输需要客户端处理流控帧完整实现建议使用can-isotp内核模块或现成库。否则只适合功能简单、响应长度不超过 7 字节的请求。如果只是想快速验证 ECU 的逻辑用诊断仪或 CANoe 会更省事。7.3 用 Python 解析 19 02 响应收到响应后解析逻辑可以按照下表展开。以响应59 02 09 01 00 02 01 00 01 01 15 09为例response bytes.fromhex(590209010002010001011509) dtc_count int.from_bytes(response[4:6], big) print(fDTC count: {dtc_count}) offset 6 for i in range(dtc_count): dtc_high response[offset] dtc_low response[offset 1] status response[offset 2] print(fDTC[{i}] code0x{dtc_high:02X}{dtc_low:02X} status0x{status:02X}) offset 3输出DTC count: 2 DTC[0] code0x0100 status0x01 DTC[1] code0x0115 status0x09这个解析逻辑可以直接嵌入产线测试脚本或售后诊断工具中批量读取故障码并生成报告。8. 性能观察与资源占用诊断服务虽然不是大流量业务但在批产测试和刷写流程里时间消耗和总线负载仍然值得关注。实测时重点看三个指标指标观察方法说明响应时间CANoe Trace 窗口的时间戳正常情况下 20ms 到 100ms 内应返回总线负载CANoe Statistics 窗口大量 DTC 响应会造成短时总线占用响应长度报文 DLC 与 ISO-TP 总长度决定是否需要多帧传输19 02 响应的长度跟 DTC 数量强相关。假设同时存在 20 个满足掩码条件的 DTC响应总长度为 1 1 1 1 2 20×3 66 字节。在 500kbps 的 CAN 总线上多帧收发大概要占用十几条报文整个过程通常也就是几个毫秒但如果在报文 ID 优先级不高的情况下和其他周期性报文竞争可能造成瞬时延迟。降低诊断链路负载可以从这几个方向入手优先使用子功能 01 查询 DTC 总数只有总数大于 0 时才发 02 读详细列表。状态掩码尽量收窄比如用0x09而不是0xFF避免把历史 DTC、老化 DTC 全部拉出来。使用 CAN FD 传输诊断报文单帧可以放 64 字节响应 20 个 DTC 只需一条帧。这里要特别提醒19 02 一次能返回多少个 DTC取决于 ECU 内部缓存和传输层能力不是客户端想拉多少就拉多少。如果 DTC 数量很大ECU 也可能按内部的“批次大小”分多次响应客户端需要处理截断和续传逻辑。9. 常见问题与排查方法问题现象可能原因排查方式解决方案请求后无响应寻址方式错误、ECU 不在目标会话检查 CAN ID 是物理寻址还是功能寻址改用物理寻址并确认诊断会话返回 7F 19 12子功能不支持确认 ECU 支持的 19 子功能范围通过 0x57? 或厂商文档确认支持列表返回 7F 19 13报文长度错误核对请求字节数02 必须带掩码04/06 必须带 DTC 码和编号返回 7F 19 31DTC 码、快照编号或扩展数据编号无效确认 DTC 是否存在于 ECU 中先用 19 02 读取有效 DTC 列表返回 7F 19 22条件不满足检查当前诊断会话和 ECU 状态切换到扩展诊断会话再试响应多帧乱序帧序列号错误或总线争用检查 Trace 中帧编号确认 ISO-TP 层处理逻辑02 读到的 DTC 数量与 01 不一致掩码使用不一致或 DTC 状态在两次请求间发生变化对比两个请求的掩码统一状态掩码后再对比最常遇到的就是 0x13 和 0x31。0x13 基本都是客户端拼包长度算错比如 04 子功能请求写成19 04 01 00少了快照记录编号。0x31 则是 DTC 码不存在或编号越界。遇到这两个码先核对请求字节再核对 DTC 定义基本都能解决。还有一个实际问题清除故障码之后马上读 19 01数量不为 0。这不是服务异常而是 DTC 状态字节里的老化位bit5、bit4还保留着。清除操作不会立刻重置所有状态位ECU 需要通过一个完整的操作循环测试后状态位才会逐步更新。测试脚本里遇到这种情况不要当成 bug先确认 ECU 的上电循环和 DTC 检测时机。10. 最佳实践与使用建议把 19 服务接入实际项目时下面这些经验可以减少返工第一次开发诊断功能时先跑通19 01再跑通19 02最后再去处理 04 和 06。01 和 02 的逻辑最简单能快速确认服务、寻址、会话都配置正确。所有请求报文最好做成配置项不要把 DTC 码写死在代码里。DTC 定义会随软件版本变化最好像下面这样集中管理{ dtc_list: [ {name: P0100, code_high: 0x01, code_low: 0x00}, {name: P0115, code_high: 0x01, code_low: 0x15} ], snapshot_ids: [1, 2, 3], extended_ids: [1, 2] }批量读取 DTC 时一定要给每一条请求设置超时。多帧响应过程中可能发生丢帧如果客户端无限等待整个测试任务会卡死在串口等待上。建议超时设置为 500ms 到 1000ms超过就重试一次重试失败再记录错误。04 和 06 的响应数据解析必须依赖 ECU 的 DTC 规范文档。没有文档时把解析结果打印成 hex不要强行翻译成物理值防止误导后续人员。涉及车辆真实故障数据时要注意数据脱敏和隐私边界。诊断工具如果会上传车辆 VIN、故障记录需要确认数据流向和合规要求。测试车辆也要使用经过授权的设备和账号。在产线批量场景里建议把 19 服务封装成统一接口比如read_dtc_list(mask)、read_dtc_snapshot(dtc, record_id)。这样上层无论对接 MES 还是售后系统都不用关心底层报文细节。11. 总结与下一步19 服务是 UDS 诊断开发绕不开的一个服务。01 子功能解决“有没有故障”02 子功能解决“故障是什么”04 子功能还原“故障发生时的现场”06 子功能统计“故障的长期趋势”。把这四个子功能吃透已经可以覆盖绝大多数产线和售后诊断需求。建议你下一步做两件事第一在 CANoe 或诊断仪上把本文的报文实例跑一遍观察正响应和否定响应的差异第二从你的实车或 ECU 测试规范里找一份真实的 DTC 列表按 02 的格式自行组包读取确认 DTC 码、状态位和快照格式的映射关系。跑通之后再去看 19 服务剩余的子功能03、05、0A 等会轻松很多。