尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

UDS协议0x19服务0x0A子功能:读取全部支持DTC实战解析

UDS协议0x19服务0x0A子功能:读取全部支持DTC实战解析 做车载诊断的谁没跟0x19打过交道车出故障了诊断仪连上车第一步就是去读故障码而这一下走的正是UDS协议里的0x19服务ReadDTCInformation读取DTC信息。今天要聊的是0x19服务下很常用的一个子功能0x0A——reportSupportedDTC也就是“报告所有支持的DTC”。很多朋友一开始就把0x19 0x0A理解成“读全部故障码”这个说法对了一半。它和0x02那种“按状态掩码读DTC”不一样0x0A是让ECU把自己支持的所有DTC列表连同当前状态一次性吐出来就算这个故障从来没发生过只要ECU内部定义了这条DTC它也会出现在响应里。这篇文章适合刚接触UDS的测试工程师、想自己写诊断脚本的软件工程师也适合在台架和实车上做故障复现的老手。我会把0x19 0x0A的报文结构、状态字节含义、实操步骤和NRC排查全部分享出来保证你看完能直接上手。1. 0x19服务到底在干什么UDS里的故障码访问入口1.1 从UDS协议说起0x19的主场在哪UDS的全称是Unified Diagnostic Services统一诊断服务底层跑的是ISO 14229系列标准。诊断仪和ECU之间通过CAN、CAN FD或者以太网通信但应用层的“会话”规则是UDS定的。整个UDS服务体系里有0x10会话控制、0x27安全访问、0x22读数据、0x2E写数据、0x31例程控制还有我们今天的主角0x19。0x19服务的名字就叫ReadDTCInformation专门负责跟诊断故障码相关的所有操作。什么叫“诊断故障码”就是DTCDiagnostic Trouble Code。发动机灯亮了ECU里记下P0100这P0100就是一条DTC。0x19服务做的事情就是把这些DTC读出来并且能把DTC的状态、快照、扩展数据也一起带上。可以这么理解0x19是你在ECU里翻“病历本”的入口。0x0A则是这本病历本的目录页——不管你现在生没生病目录上列了哪些病名ECU都会给你看。相比0x01、0x02那种“只看当前有症状的病”0x0A的信息面更全做ECU功能开发或者产线检测的时候特别有用。1.2 0x19家族里的子功能地图0x0A排第几0x19下面有一堆子功能经常有人分不清。我列一个自己的速查表你做项目时可以直接拿去参考子功能名称响应内容典型用途0x01reportNumberOfDTCByStatusMask符合状态掩码的DTC数量快速查看当前有几个故障0x02reportDTCByStatusMask符合状态掩码的DTC及状态最常见的读故障码方式0x03reportDTCSnapshotIdentification快照记录列表查看快照数量0x04reportDTCSnapshotRecordByDTCNumber指定DTC的快照记录故障发生瞬间数据0x06reportDTCExtendedDataRecordByDTCNumber指定DTC的扩展数据特定工况计数、老化计数0x0AreportSupportedDTC全部支持DTC及当前状态读取ECU全部DTC列表0x0BreportFirstTestFailedDTC首次测试失败的DTC故障排查定位第一次失败0x0A的位置很有意思它和0x01、0x02最大的区别在于“不需要状态掩码参与筛选”。比如0x02你发请求时得带上DTCStatusMask告诉ECU“我只要故障状态字节里某一位为1的DTC”ECU才会过滤给你。0x0A则是全量返回ECU支持多少条DTC响应里就装多少条。哪怕一条DTC的状态是0x00从来没有任何测试失败过它也会在0x0A的响应里出现。1.3 什么场景下你会用到0x19 0x0A我实际接触下来0x0A主要在三个场景里高频出现。第一个是诊断仪的“全部故障码”页面。很多售后诊断仪在线检测时会先用0x19 0x0A把ECU支持的所有DTC拿回来再在本地根据状态字节过滤显示“未决故障”“已确认故障”。这样做的好处是只需要发一次请求后面怎么展示都是设备端的事。第二个是ECU开发阶段的功能验证。你在新项目里给ECU新增了一条DTC比如P0401废气再循环流量不足写完之后怎么确认这条DTC真正“进”到ECU里了发一脚0x19 0x0A如果响应列表里已经有了P0401说明DTC配置没问题。注意这时候故障可能根本没发生用0x02是找不到它的。第三个是产线终检和售后预排查。下线检测时想知道ECU有没有遗留故障0x0A能一次性把状态为“已确认”的故障全部带出来。配合0x14清码服务可以在一个检测循环里完成“读取—判断—清除—复测”的全流程。这几类场景做下来你对0x0A的理解就不只是停留在“会发请求”上了。2. 0x19 0x0A请求和响应一个字节一个字节拆给你看2.1 请求报文标准情况下就两个字节按照ISO 14229-1标准定义0x19 0x0A的请求格式非常简单就是SID加子功能请求结构为02 19 0A其中0x02是这条CAN诊断请求的长度不包括CAN头0x19是SID0x0A是子功能号。在CAN总线上的物理寻址请求帧CAN ID通常是0x7E0功能寻址是0x7DFDataField就是02 19 0A。比如一帧典型的请求报文这样显示ID: 7E0 DLC: 8 02 19 0A 00 00 00 00 00多说一句有些ECU供应商或者OEM内部规范里会把0x0A请求扩展成“带DTC状态掩码”的形式比如03 19 0A FF。这个做法并不符合ISO 14229-1的标准样式但现实世界里确实存在。我在某个供应商的协议文档里就见过这种变体它把0x0A当成了“带筛选条件的读全部DTC”。遇到这种ECU只能以对方诊断规范为准标准样式和变体都得有准备。2.2 响应结构59 0A打头后面跟着一串列表正常情况下ECU收到0x19 0x0A请求后会回复肯定响应应用层以59 0A开头。我直接给一个最典型的响应布局59 0A [DTCFormatIdentifier] [statusAvailabilityMask] [DTC1_HI DTC1_LO DTC1_Status] [DTC2_HI DTC2_LO DTC2_Status] ...第一个字节59是SID0x40表示对0x19的肯定响应。第二个0x0A是子功能回显。第三个字节是DTC格式标识最常见的值是0x01表示“ISO 14229-1格式的DTC2字节DTC码1字节状态”。如果ECU支持扩展DTC3字节DTC码这里可能会是其他值解析时要特别注意。第四个字节statusAvailabilityMask叫状态可用性掩码。它的作用是把ECU支持的DTC状态位告诉你。如果这个字节0xFF说明ECU对DTC状态字节的8个bit全部支持如果0x2F这种说明一些高位的bit比如warningIndicatorRequestedECU根本不用。看到这个字节后处理后续每个DTC状态时就能知道哪些bit是有效的。从第五个字节开始就是DTC数据区了。每个DTC固定3个字节前两个是DTC码第三个是当前状态字节。响应有多长取决于ECU支持多少条DTC。如果DTC很多报文会超过单帧CAN载荷ISO-TP传输层会自动拆成多帧这个后面实操部分细说。2.3 DTC状态字节的8个bit到底在说什么DTC状态字节是整个0x19服务的灵魂。很多刚入门的朋友读故障码只关心DTC编号比如P0100却完全忽略了后面的状态字节结果车明明修好了故障码还在“已确认”就是因为没看懂状态位。ISO 14229-1里DTC状态字节每个bit都有严格定义Bit位含义说明bit0testFailed该DTC最近一次测试失败bit1testFailedThisOperationCycle当前操作循环内测试失败过bit2pendingDTC待定故障一次失败未确认bit3confirmedDTC已确认故障达到确认阈值bit4testNotCompletedSinceLastClear上次清码后该DTC测试未完成bit5testFailedSinceLastClear上次清码后测试失败过bit6testNotCompletedThisOperationCycle当前循环内测试未完成bit7warningIndicatorRequested请求点亮故障灯这个字节怎么解析举例如果读取到的状态是0x29转成二进制是0010 1001从bit0往上看就是bit01testFailed、bit31confirmedDTC、bit51testFailedSinceLastClear。说明这个故障当前测试失败已经确认为故障而且自上次清码以来失败过是一个“实锤”故障。再比如状态0x08二进制0000 1000只有bit31表示这条DTC是已确认故障但最近一次测试没有失败。最常见的场景就是历史故障故障确实发生过已经确认了但当前工况下没有再测出问题。这时候维修技师会先记下故障码再做路试或者清码后复测判断故障是否真实存在。还有一个容易忽略的点0x0A响应里面DTC状态字节可能是0x00。很多ECU对“支持的DTC但从未测试过”的状态就是0x00这不能理解成“没有这条DTC”而是“支持但当前正常”。所以解析0x0A响应时别看到状态0就过滤掉要看你做诊断的目标。如果是产线检查支持列表状态0反而说明DTC配置成功。3. 亲手发一帧0x19 0x0A从CANoe到udsoncan的实操记录3.1 搭一个最简诊断环境先让链路跑通想亲眼看0x19 0x0A的响应最直接的办法是用CAN盒连ECU。真实项目里常用的是CANoe、PCAN、周立功CANTest或者直接在自己的测试台架上用Python脚本来发。如果你手头没有真实ECU可以用CANoe的仿真节点或者基于Python的虚拟CAN设备模拟一个响应。硬件连接上OBD口或者开发板上的CAN_H、CAN_L对应接好波特率一般跟ECU一致常见的是500kbps。注意供电问题ECU要正常上电并进入可以诊断的状态通常IG ON或者直接给常电具体看台架定义。连接好后先用CANoe的ISO-TP通道或者PCAN的CANMonitor工具确认总线没有大量bus-off链路就算通了。一个很有用的调试习惯先发0x10 01切换ECU到默认会话确保ECU能响应再发0x19 0x0A如果响应正常说明ECU在默认会话下就支持0x19服务。如果返回0x7F 19 0x22说明需要使用0x10 03切到扩展会话再试。这一步能帮你定位很多“发不出去”的问题。3.2 用Python udsoncan发送0x19 0x0A并解析Python生态里有个很好用的库叫udsoncan配合python-can可以快速写诊断脚本。先给一个最简示例import udsoncan from udsoncan.connections import PythonIsoTPSocketConnection from udsoncan.client import Client from udsoncan import services # 假设已经建立了ISO-TP连接物理寻址请求ID 0x7E0响应ID 0x7E8 conn PythonIsoTPSocketConnection(can0, rxid0x7E8, txid0x7E0) with Client(conn, request_timeout2) as client: # 构造0x19 0x0A请求ReadDTCInformation的子功能0x0A request udsoncan.Request(services.ReadDTCInformation, subfunction0x0A) response client.send_request(request) print(response data:, response.data.hex()) # 注意确认肯定响应0x59开头 if response.positive: data response.data # 跳过 59 0A读取格式标识 fmt data[2] avail data[3] print(fDTCFormatIdentifier: 0x{fmt:02X}) print(fStatusAvailabilityMask: 0x{avail:02X}) # 从索引4开始每3个字节一个DTC dtc_list [] for i in range(4, len(data), 3): dtc_raw data[i] 16 | data[i1] 8 | data[i2] status data[i2] dtc_list.append((dtc_raw, status)) print(DTC count:, len(dtc_list)) for dtc_raw, status in dtc_list[:5]: print(f DTC raw: 0x{dtc_raw:06X}, status: 0x{status:02X})实际执行效果上脚本会和我们在CANoe里手动发请求看到的响应数据一模一样。这里要提醒一下udsoncan本身对0x02有现成的API read_dtc_by_status_mask但0x0A在部分版本里没有直接封装所以直接用Request构造最稳妥。如果你的项目里必须要跑0x0A自己封装一层比较省心。3.3 实测报文走一遍从CAN帧到应用层数据我拿一个真实台架日志来做演示。CANoe Trace里先看到请求帧时间戳 ID DLC Data 10:00:01.123 7E0 8 02 19 0A AA AA AA AA AA注意Data里第二字节之后的填充是AA或者00这是CAN报文填充字节ECU不会处理只读取前面长度字段2个字节。紧接着ECU回的响应从ISO-TP角度看是连续几帧我这里把应用层拼起来59 0A 01 FF 01 00 21 01 01 08 01 20 29 ...这些字节的含义是59 0A表示肯定响应且子功能为0x0A01表示DTC格式为ISO 14229-1格式FF表示ECU支持全部8个状态bit位。然后第一组01 00对应DTC码0x0100状态0x21。0x0100从UDS编码看对应P0100空气流量传感器电路故障。0x21换算成二进制是0010 0001bit01且bit51表示测试失败且上次清码以来失败过。第二组01 01DTC码0x0101对应P0101空气流量传感器电路范围/性能故障状态0x08是已确认故障但当前测试正常。第三组01 20对应P0120节气门位置传感器电路故障状态0x29既是当前失败也是已确认故障。从这个例子就能看出0x0A返回的不只是故障列表而是“ECU全部支持的DTC实时状态”。它和0x02相比信息量大了不少。4. 那些年踩过的0x19 0x0A的坑NRC与状态掩码的真实经验4.1 NRC速查表遇到负响应怎么定位请求发出去不一定都成功ECU会回负响应码NRCNegative Response Code。0x19 0x0A常见的负响应是0x7F 19 XX。我做了一个表遇到NRC直接查NRC名称常见原因排查方向0x12subFunctionNotSupportedECU固件没实现0x0A查供应商诊断规范确认支持版本0x13incorrectMessageLengthOrInvalidFormat请求长度不对标准0x0A请求只发02 19 0A别多发0x22conditionsNotCorrect当前会话/电压/条件不满足先切到扩展会话0x10 03再试0x31requestOutOfRange请求参数越界常见于变体掩码确认OEM文档里0x0A是否带掩码0x33securityAccessDenied需要安全解锁个别ECU读DTC也需要先解锁少见0x7F 19 10generalReject内部条件拒绝检查ECU是否处于刷写/禁止诊断状态有个朋友问过我为什么发02 19 0A偶尔会收到0x13。后来查下来ECU的协议栈版本要求0x0A必须带一个字节的状态掩码也就是说他只接受03 19 0A FF这种变体。这类问题没有捷径只能说启动项目前一定把ECU的诊断规范拿到手别拿ISO标准生搬硬套。4.2 状态掩码怎么选为什么不能无脑填FF很多人以为状态掩码就是图方便填0xFF反正意思是什么故障都要。如果你用的是0x19 0x02填FF虽然没有越界但会把一堆无关的历史记录也读出来响应变得很长解析和人工判断反而麻烦。我常用的掩码推荐组合读当前失败故障 0x01 读待定故障 0x04 读已确认故障 0x08 读上次清码后有失败0x20 读所有状态 0xFF这些掩码可以按位或组合。比如想同时看已确认故障和上次清码后有失败的故障就填0x28。但注意0x0A标准请求是不带掩码的如果你用变体实现掩码的语义要跟供应商规范对齐。测试时最好是先读一次statusAvailabilityMask再根据它支持的位来组合掩码避免读到ECU根本不支持的bit位导致返回空列表。4.3 0x0A在不同标准版本里的差异别被供应商文档带偏这里要说一个容易踩的坑ISO 14229-1老版本和2020版对0x19 0x0A的定义略有出入加上OEM经常做扩展不同文档根本对不上。我最早做项目时查一份老供应商文档里面把0x0A写成“读取排放相关DTC”害得我以为0x0A只能读P0开头的码后来拿标准原文一比对才发现不是那么回事。按照ISO 14229-1:2013和之后的定义0x0A就是reportSupportedDTC功能是报告ECU所有支持的DTC。为什么有人觉得它跟“排放相关”有关原因是OBD系统里确实要读取排放相关DTC但那是ISO 15031-5定义的服务跟UDS的0x0A不是一回事。在UDS世界里0x0A就是纯纯的“支持列表”读取不会做功能组过滤。所以做项目时不要只百度0x0A的释义要去查阅你实际使用的ISO 14229-1版本再加上ECU供应商的诊断文档。两者冲突时以可以落地实现的那个文档为准同时留好问题记录。5. 和0x19 0x0A配套的周边服务与实战心得5.1 诊断全链路配合0x19、0x14、0x85怎么用才顺手0x0A不是孤立存在的。拿一个典型的故障排查流程来说先用0x19 0x0A拿到全部支持DTC列表确认ECU定义的故障集合。然后对感兴趣的故障用0x19 0x02带上特定状态掩码读取当前实际故障状态。如果需要看故障发生瞬间的数据再发0x19 0x04获取快照记录要查老化次数或者计数器就发0x19 0x06扩展数据。排查完之后就要用到0x14服务ClearDiagnosticInformation来清除DTC。清除的时候要带一个DTC组号通常填0xFFFFFF表示清除所有DTC。这里有个经验清码前最好确认ECU是否处于可擦除状态有些ECU在故障没有修复的情况下清完码后会立刻报出新的已确认故障这并不代表清码失败而是故障条件还在。还有0x85服务ControlDTCSetting作用是关闭DTC记录。它一般用于刷写过程中防止ECU一边刷写一边记录一堆错误DTC。刷写流程里常见顺序是0x10 03切扩展会话→0x27安全解锁→0x85关闭DTC记录→0x31或0x34/36/37刷写→0x85恢复DTC记录→0x14清码→0x19 0x0A检查DTC列表是否干净。刷完固件后用0x19 0x0A核对支持列表也是确认刷写后DTC配置没有丢失的好办法。5.2 在台架和实车上的几条实操心得第一请求要慢一点。0x0A响应特别长的时候ISO-TP会连续发很多帧如果诊断仪缓冲区不够容易丢帧。实测下来用CANoe连续循环发送0x19 0x0A时加一个至少200ms的发送间隔比狂发稳得多。真遇到丢帧先看底层错误帧再看应用层响应是否截断别一上来就怀疑ECU。第二多帧响应拼接时不要漏帧。CAN Classic单帧最多8字节0x0A响应很可能超过8字节这时候ISO-TP会拆成多帧FF、CF。解析时一定要等到最后一个CF到达后再拼数据。我见过很多次同事拿第一帧就解析结果只看到前面几个DTC。你可以用CANoe的Diagnostics窗口自动组包观察也可以写脚本做流控制帧管理总之别急。第三不同ECU对0x0A的支持程度不一样。有的ECU只支持0x02和0x01不支持0x0A一请求就回0x12。别把它当成ECU坏了这完全是可选子功能。做产品时最好在诊断能力描述文档里明确ECU支持哪些子功能这样测试和售后都有据可依。第四日志一定要留。实车调试时把带时间戳的CAN日志完整记录下来尤其是状态字节变化。我曾经排查过一个问题同一个DTC在冷车状态下状态字节是0x08热车后会变成0x21导致诊断仪界面上的故障状态跳来跳去。没有日志根本没法分析这种状态位变化。最后分享一个小技巧也当是给这篇文章收个尾解析0x19 0x0A响应时我习惯先打印statusAvailabilityMask再打印DTC数量最后才看具体DTC列表。因为只要availability mask明确后续每一个DTC状态字节的bit含义就都能对齐遇到ECU不支持的高位为1也不会慌。诊断这件事最怕的不是报文错而是对照着错误的标准去解读对的数据。0x0A这个服务本身不难真正拉开差距的恰恰是你对这些细节的理解和项目经验的积累。
返回列表