UDS诊断0x19服务0x06子功能详解:按DTC读取扩展数据记录

发布时间:2026/9/25 5:41:58
UDS诊断0x19服务0x06子功能详解:按DTC读取扩展数据记录 这条卡片机一样的东西想来想去还是那张故障码列表最能说明问题。UDS诊断里0x19服务叫“读取故障信息”ReadDTCInformation它下面挂了一堆子功能0x06只是其中一个。搞懂它之前先把0x19这棵树的枝干捋清楚。1.1 0x19服务是干什么的汽车电子控制系统里ECU会记录各种故障信息。DTC是“诊断故障码”Diagnostic Trouble Code你可以把它理解成ECU的“病历本”——哪个传感器异常、哪个执行器开路、哪个通信丢了帧都会落成一条条DTC记录。0x19服务就是读取这个病历本的统一入口。它下面细分了很多子功能比如0x01报告DTC数量0x02按状态掩码报告DTC列表0x04报告DTC快照记录发生故障时的环境数据0x06报告DTC扩展数据记录故障码相关的附加数据0x0A报告DTC状态位0x0B报告DTC快照记录按记录号0x0C报告DTC扩展数据记录按记录号0x06在ISO 14229-1标准里的标准名称是 reportExtendedDataRecordByDTC翻译过来就是“按DTC报告扩展数据记录”。很多人第一次接触0x06都会问0x02已经能把故障码读出来了为什么还要多此一举因为0x02只告诉你“现在有哪些故障码、状态位是什么”不告诉你这个故障背后更详细的数值信息。扩展数据记录里通常包含老化计数器、故障发生次数、首次/最近发生时的里程和时间、环境温度、系统电压等等。售后工程师拿到这些才能判断是偶发还是永久故障开发人员拿到这些才能复现问题、定位根因。所以0x06是“从知道有故障”到“知道故障细节”的桥。1.2 0x06子功能的定位0x06的具体作用是给定一个DTC读取它对应的扩展数据记录。注意这里的关键词是“给定一个DTC”——请求里必须带上具体的DTC三字节编码服务器才会返回这条故障码的扩展数据。你可以把0x06理解成“按病名查病历明细”。普通门诊0x02只告诉你得了什么病病历明细0x06告诉你这个病是什么时候开始的、犯过几次、当时心率多少、血压多少。这个“查明细”的动作靠的就是0x06。1.3 什么场景会用到0x06实际开发和测试中0x06最常见的几个用途故障老化分析很多ECU通过老化计数器决定DTC从“确认”恢复成“历史”或者从“待定”升级成“确认”。这个老化计数器的数值通常就放在扩展数据记录里。偶发故障排查偶发故障最讨厌的地方在于“复现不了”。扩展数据里如果记录了发生次数、发生时的环境温度等参数对复现非常有帮助。下线检测EOL和售后诊断有些产线工位不满足于只读状态还要确认故障码发生次数是不是在允许范围内这时候就会去读0x06的数据。刷写后的验证FOTA或者售后刷写流程中刷写完成后需要确认软件版本和DTC状态有些团队会在刷写后读一遍DTC扩展数据来判断故障是否恢复。一句话总结0x19服务是整个UDS诊断里的“读故障”总入口0x06是其中一个“按DTC查扩展数据”的细分动作。搞清楚了这棵树的位置后面看协议细节就不会迷路。2. 0x06子功能的协议细节逐字节拆给你看下面进入重点把0x06的请求、响应、参数逐字节拆开。这一部分建议对着ISO 14229-1标准原文看我这边讲的都是实际开发里一定能用上的解读。2.1 请求报文格式0x06子功能的请求报文实际发到CAN总线上一共是5个或6个字节字节名称说明Byte 0服务ID固定0x19Byte 1子功能0x06或0x86Byte 2DTC高字节故障码第一个字节Byte 3DTC中字节故障码第二个字节Byte 4DTC低字节故障码第三个字节Byte 5扩展数据记录号可选指定读哪一条扩展数据记录比如你要读DTC 0x123456的默认扩展数据请求就是19 06 12 34 56如果想指定读第1条扩展数据记录请求就是19 06 12 34 56 01这里面有几个地方需要特别留意。第一Byte 1是子功能字节bit7是抑制肯定响应位。如果发0x06ECU正常处理后会回一个肯定响应如果发0x86ECU只执行但不回肯定响应只有失败时才回NRC。0x06这种读数据的功能一般不会用0x86因为你本来就想要响应里的数据抑制了响应等于白问。但在批量脚本排查时有些工程师会用0x86配合轮询来“只确认能不能读”减少CAN总线负载这种用法也确实存在。第二Byte 5“扩展数据记录号”是可选参数。ISO 14229-1标准里如果省略这个参数服务器按默认记录号处理如果带上就必须在ECU支持的范围内否则会回NRC。问题在于“默认记录号”到底是什么标准在不同版本和OEM规范里处理不完全一样。有些ECU默认0x00代表“第一条记录”有些默认0x01还有少数把0xFF定义为“全部记录”。这一点在没有规范在手的时候特别容易踩坑。我的建议是第一次交互先不带Byte 5看ECU响应什么数据如果NRC 0x31再尝试记录号0x00、0x01逐一试。下文第5章会专门讲这个坑。2.2 响应报文格式如果请求正常处理ECU返回的肯定响应如下字节名称说明Byte 0响应服务ID固定0x59Byte 1子功能0x06Byte 2DTC高字节与请求一致Byte 3DTC中字节与请求一致Byte 4DTC低字节与请求一致Byte 5DTC状态掩码1个字节反映当前DTC状态Byte 6..N扩展数据记录长度可变由ECU定义举个例子完整响应可能是59 06 12 34 56 48 01 F4 03 E8这里0x12 0x34 0x56是对应的DTC0x48是DTC状态掩码后面0x01 0xF4 0x03 0xE8就是扩展数据内容。具体每一位的含义要看OEM的诊断规范。有的OEM把前两个字节定义为老化计数器和发生次数后面放里程或者时间戳。特别提醒一点响应里DTC状态掩码是1个字节不是2个别在代码里解析错位。我见过新同事写解析逻辑时多读了1个字节结果后面扩展数据全部错位排查了半天。2.3 DTC编码与状态位难点DTC在UDS协议里固定占3个字节编码规则来自ISO 15031-6。第一个字节的高两位表示“故障码所属系统”00表示动力系统P01表示底盘系统C10表示车身系统B11表示网络通信系统U。剩下的位和后续两个字节共同编码故障码内容。举例来说OBD里常见的P0101“空气流量传感器电路范围/性能问题”在很多ECU的UDS映射表里对应三字节0x01 0x01 0x01或0x01 0x01 0x71具体取决于OEM映射。这里记住一个结论就行UDS里的DTC三字节和OBD五字符DTC不是简单的一一对应实际开发中不要自己算直接从0x19服务读出来的DTC列表原样拷贝或者查OEM的DTC映射表。手动去换算很容易出错。DTC状态掩码statusOfDTC占用1个字节按位定义这是读DTC相关服务必背的一张表bit位含义bit0testFailed 当前测试失败bit1testFailedThisOperationCycle 本操作周期测试失败bit2pendingDTC 待定故障bit3confirmedDTC 已确认故障bit4testNotCompletedSinceLastClear 自上次清除以来测试未完成bit5testFailedSinceLastClear 自上次清除以来测试失败bit6testNotCompletedThisOperationCycle 本操作周期测试未完成bit7warningIndicatorRequested 请求点亮MIL灯实战里最常见的判断是“这个故障码是不是已确认故障”也就是检查bit3是否为1。如果要清码一般确认了故障码才需要走维修只是待定状态的话可能多跑几个周期就自己恢复了。0x06响应里的状态位和0x02读到的状态位是同一个字节逻辑完全一致。2.4 扩展数据记录号到底怎么填这是0x06最容易翻车的地方单独拿出来说。ISO 14229-1标准定义了扩展数据记录号但每个ECU支持的记录数量、记录号范围、每个记录的长度完全由OEM自己决定。有的ECU只支持一条记录记录号填0x01有的ECU支持多条比如0x01放老化计数0x02放发生次数0x03放发生时刻的里程和温度。实战中的经验是优先不传记录号看服务器默认响应什么。如果服务器回NRC 0x31再依次尝试记录号0x00、0x01、0x02观察哪个能读出有意义的数据。记录号的范围可以在ODX/PDX诊断数据库比如CANdela Studio生成的诊断描述文件里查不要盲猜。有些ECU在“无故障记录”时即使你传了合法记录号也会回NRC 0x31。这不是你的请求写错了而是这条DTC当前没有扩展数据可以返回。还有一个小细节扩展数据记录如果超过单帧CAN的8字节会触发传输层组包也就是ISO-TP的连续帧机制。解析响应时要注意收到的数据不能简单按单帧处理必须先做ISO-TP的解包重组。3. 0x06和0x04、0x02的关系别搞混UDS的0x19服务子功能很多0x02、0x04、0x06是三个最容易弄混的。它们都在“读故障信息”的大伞下但读出来的东西完全不一样。3.1 0x02按状态掩码读一遍DTC列表0x02子功能是“报告DTC列表按状态掩码筛选”。请求格式19 02 [状态掩码]比如读所有已经确认的故障码19 02 08 // 08 confirmedDTCECU响应里会返回一个DTC列表每个DTC跟着一个状态字节。0x02只给你“有哪些故障码、状态是什么”不给你任何附加数据。它的作用是定位故障码清单相当于病历本封面上的“疾病列表”。3.2 0x04DTC快照记录事件环境0x04子功能是“按DTC报告快照记录”。快照记录是故障发生的瞬间ECU冻结的一组环境数据比如发动机转速、车速、冷却液温度、进气压力、电压、里程等。请求时也是带一个DTC三字节19 04 12 34 56响应里除了DTC和状态位后面会跟一长串快照数据。快照里每个数据项都有固定的DID标识和长度解析时必须按诊断数据库定义来。快照的作用是“记录案发现场”——告诉你故障发生时整车状态是什么样的。3.3 0x06扩展数据记录计数/老化/里程0x06和0x04的区别在于快照记录是“故障发生那一刻的现场数据”扩展数据记录是“故障相关的累计数据和附加信息”。扩展数据最常见的几个内容老化计数器aging counter用于故障老化确认和自动恢复故障发生次数计数器最近一次故障发生时的里程或时间戳环境温度、电压等补充信息可以说0x04偏“环境”0x06偏“统计”。一个是抓拍一个是台账。3.4 三个子功能连起来用实战里一个完整的故障排查流程是先用0x19 0x02按状态掩码0x08已确认故障读一遍拿到DTC清单。对清单里每个DTC用0x19 0x04读快照看故障发生的环境数据。再用0x19 0x06读扩展数据看故障发生次数和老化计数器。三个子功能连起来才能把一条故障码的命运轨迹完整还原出来。所以不要把0x06孤立地理解成“另一个读故障码的指令”它是整个故障信息链条上的一环。4. 实战用Python调一遍0x06从报文到解析理论讲得再多不如跑一遍。这里演示一个用Python写的最小UDS客户端通过CAN接口去读ECU的0x06扩展数据。实际项目中你可能用CANoe、PCAN、ZLG或自研工具原理完全一样。4.1 环境准备与连接CAN首先保证电脑上有python-can库pip install python-can然后初始化一个CAN接口这里以Linux socketcan为例import can bus can.interface.Bus(channelcan0, interfacesocketcan)用USB-CAN盒子的话interface可能要换成pcan或者其他驱动看你的硬件。4.2 发送请求与接收响应写一个通用的单帧UDS请求函数import time def uds_request(bus, data, req_id0x7E0, resp_id0x7E8, timeout1.0): msg can.Message( arbitration_idreq_id, datadata, is_extended_idFalse, ) bus.send(msg) end_time time.time() timeout while time.time() end_time: resp bus.recv(0.2) if resp is not None and resp.arbitration_id resp_id: return resp.data return None这里0x7E0是诊断仪物理寻址请求ID0x7E8是ECU响应ID这是最常见的默认值。实际项目里以ECU的寻址配置为准。4.3 读取DTC列表并解析0x06响应先用0x02把已确认故障码读出来得到DTC清单# 请求DTC列表状态掩码0x08表示已确认故障 resp uds_request(bus, [0x19, 0x02, 0x08]) if resp and resp[0] 0x59: dtc_count resp[2] print(DTC数量:, dtc_count) # 跳过DTC格式标识(Byte3?)和数量字段从第4字节开始解析 # 标准响应: 59 02 格式标识 数量 DTC1高 DTC1中 DTC1低 DTC1状态 ...响应里DTC列表的结构是“每个DTC三字节一个状态字节”逐条解析def parse_dtc_list(resp): dtcs [] idx 4 count resp[2] for _ in range(count): dtc resp[idx:idx3] status resp[idx3] dtcs.append((dtc, status)) idx 4 return dtcs拿到DTC清单后对第一个已经确认的故障码发0x06请求if dtcs: dtc dtcs[0][0] # 例如 b\x12\x34\x56 req [0x19, 0x06, dtc[0], dtc[1], dtc[2]] print(发送:, bytes(req).hex().upper()) ext_resp uds_request(bus, req) if ext_resp and ext_resp[0] 0x59: print(响应:, ext_resp.hex().upper()) status ext_resp[5] ext_data ext_resp[6:] print(fDTC状态掩码: 0x{status:02X}) if status 0x08: print(该故障码是已确认故障) print(f扩展数据: {ext_data.hex().upper()}) # 根据OEM规范解析扩展数据具体含义 # 这里假设前2字节是老化计数器 aging_counter int.from_bytes(ext_data[0:2], big) print(f老化计数器: {aging_counter}) else: print(0x06读取失败NRC:, hex(ext_resp[2]) if ext_resp else 无响应)如果ECU要求指定记录号把req改成req [0x19, 0x06, dtc[0], dtc[1], dtc[2], 0x01]4.4 把0x020x06串成一个完整流程实际工具里不会只读一条而是循环读取所有已确认故障码的扩展数据def read_all_extended_data(bus): resp uds_request(bus, [0x19, 0x02, 0x08]) if not resp or resp[0] ! 0x59: print(读取DTC列表失败) return dtcs parse_dtc_list(resp) print(f共 {len(dtcs)} 个已确认故障) for dtc, status in dtcs: req [0x19, 0x06, dtc[0], dtc[1], dtc[2]] ext_resp uds_request(bus, req) if ext_resp and ext_resp[0] 0x59: ext_data ext_resp[6:] print(f{dtc.hex().upper()} 状态0x{status:02X} 扩展数据:{ext_data.hex().upper()}) else: print(f{dtc.hex().upper()} 读取失败 NRC:{hex(ext_resp[2]) if ext_resp else 无响应})这个流程已经可以当一个小工具的雏形用了。实际工程里还要加入ISO-TP组包解包、多帧响应处理、超时重试、会话切换这些逻辑但在实验室环境里上面的代码足以验证0x06的收发流程。5. 我踩过的坑0x06常见的几个NRC和诡异现象0x06看着简单实战里我踩过不少坑也帮人排查过不少问题。这里整理几个高频问题每个后面都附排查思路。5.1 NRC 0x13——报文长度和格式是最容易错的NRC 0x13表示“报文长度或格式无效”。0x06请求最常见的0x13原因有三个一是请求里带了扩展数据记录号但ECU的协议实现不接受这个参数。有些ECU固件严格按早期版本实现0x06请求固定5个字节多出第6字节直接回0x13。解决方法是先发不带记录号的请求试试。二是ECU既要求必须带记录号你又没带。这种情况下也会回0x13。需要看OEM规范确认0x06请求长度是5字节还是6字节。三是DTC三字节传错了比如从0x02响应里没有正确跳过DTC格式标识位导致DTC拼错。排查时我习惯先打印请求字节流和响应字节流不要肉眼凭空对。5.2 NRC 0x31——DTC没有扩展数据/记录号不对NRC 0x31表示“请求超出范围”在0x06里通常意味着三种情况请求的DTC在ECU里不存在。DTC存在但这条DTC没有配置扩展数据记录。扩展数据记录号不在支持范围内。我之前遇到过一台ECU0x02能读出某个历史故障码但0x06一读就是0x31。后来查规范才发现这条DTC只配置了快照记录0x04压根没配扩展数据。所以遇到0x31先别怀疑收发逻辑先查诊断数据库看看这个DTC到底有没有扩展数据。还有一种情况是DTC处于“待定”状态还没形成确认故障某些ECU实现里也读不到扩展数据也会回0x31。5.3 NRC 0x12——ECU压根没实现0x06NRC 0x12表示“子功能不支持”。这个错误看着低级但实际项目里真有ECU不支持0x06。尤其是一些早期或者简化版的ECU0x19服务只实现了0x01、0x02和0x040x06直接没做。遇到0x12别硬磕确认一下ECU支持的子功能范围。可以用0x19服务的一个特殊子功能——0x00报告支持哪些子功能去查询。如果你在同一个ECU上既有0x06又有0x12返回那基本可以断定固件版本没实现这个子功能。5.4 状态位和DTC大小端问题0x06响应里DTC三字节要和请求里的DTC一致。如果你是从0x02响应里解析出来的DTC一般不会错。最容易错的是手写DTC时大小端写反。CAN总线报文是Motorola格式DTC三字节顺序是高字节在前。有些工具显示DTC时习惯显示成“0x123456”但实际在总线上发送时是12 34 56。如果你从某个诊断仪界面上抄了一个DTC抄的时候没注意字节序填进0x06请求里发现读不到很可能就是高低字节颠倒了。另一个常见错误是解析状态掩码时把bit位搞错比如把bit2pending当成bit3confirmed导致后续判断逻辑全错。我的习惯是在代码里把每个bit都解出来打印一遍调试时一目了然。5.5 功能寻址 vs 物理寻址的坑0x06一般建议用物理寻址发送。原因是0x06请求里带了具体的DTC如果总线上挂了多个ECU而且多个ECU都记录了这个DTC功能寻址会导致多个ECU同时响应报文在CAN总线上冲突。功能寻址通常请求ID为0x7DF更适合0x01、0x02这类“所有ECU都要回答”的广播式请求。0x06这种带条件的深度读取老老实实一个ECU一个ECU地物理寻址既安全又干净。5.6 会话切不进去怎么办有些ECU规定0x06必须在扩展会话Extended Session下才能使用。如果你发现在默认会话下发0x06总是NRC 0x22条件不满足或者0x13先切到扩展会话再试10 03切会话之后一般要等一下再发下一条请求有些ECU完成会话切换需要几十毫秒。另外切完扩展会话后ECU可能进入看门狗管理状态在测试台架上要注意按时发送周期性报文以防ECU复位。5.7 多帧响应的处理0x06的响应如果扩展数据很长比如超过7个字节ECU会通过ISO-TP的多帧机制发送。底层是先收到一个单帧首帧0x10开头里面带总长度然后跟一串连续帧0x21、0x22...最后再拼成完整的响应字节流。如果你在写自己的工具一定要做ISO-TP解包。很多看起来很离谱的“响应解析错位”其实就是只处理了单帧没处理连续帧导致只拿到了数据的前几个字节后面全丢了。直接用现成的ISO-TP库比如can-isotp能省掉不少麻烦pip install can-isotp6. 一点收尾建议0x06子功能本身不难难的是它背后的那套诊断数据定义。协议栈只负责搬运字节真正决定“扩展数据里是什么”的是OEM的诊断数据库也就是ODX或者CDD文件。做开发调试时手里一定要有一份对应ECU的诊断规范不然你读出来的16进制扩展数据在别人眼里是乱码。最后分享一个个人习惯每次连上一台新ECU我都会先用0x19 0x02把DTC列表读一遍挑一个已确认故障码用0x06和0x04各读一次把响应存成文件。这样做一次“协议冒烟测试”既能验证自己的UDS协议栈收发链路又能确认ECU实际支持哪些子功能后面写自动化脚本时能少踩很多坑。0x06读出来的那串字节单独看可能只是一堆十六进制但结合老化计数和发生次数往往能还原出一条故障码完整的生命周期。做诊断开发越久我越觉得协议本身只是皮真正值钱的是你对诊断数据的理解。希望这篇文章能帮你把0x06这条链路走通少走我当年绕过的弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询