ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

UDS协议0x19 0x0A服务详解:读取DTC支持列表与工程实践

UDS协议0x19 0x0A服务详解:读取DTC支持列表与工程实践 做整车诊断测试的对0x19这个服务应该都有印象。0x19叫ReadDTCInformation在UDS协议里负责“读故障码相关的一切信息”。子功能0x0A叫reportSupportedDTC中文理解就是“上报受支持DTC列表”。它跟0x02“按状态读DTC”不一样0x0A不管你当前有没有故障只要ECU配置里存在这个DTC它就列出来。所以0x19 0x0A服务特别适合验证ECU诊断配置、做产线下线检测、刷写后完整性检查。刚入门UDS、准备写诊断脚本或者正在做DTC测试的同学这篇就从请求帧到响应帧再讲到我实际踩过的坑可以直接拿来用。1. 0x19 0x0A服务到底在做什么1.1 它属于0x19服务树里的哪一枝UDS里读故障码不是简单发一条“读故障码”就完事而是由0x19一个服务带一串子功能覆盖DTC数量、状态、快照、扩展数据、支持列表等。你不需要一上来背表但要先理解两个维度一个是“读数量”一个是“读明细”。0x01统计数量0x02读当前有故障的DTC列表0x0A读ECU整个支持列表。0x03到0x06负责快照和扩展数据0x07到0x09按严重程度读0x0B到0x0E读最近测试失败、最近确认等状态再往后还有永久DTC、DTC组相关子功能。0x0A在整棵服务树里的位置就是“静态全量列表”它不关心DTC当前是不是激活不关心测试有没有失败过只关心ECU的DTC配置表里定义了哪些DTC。我在实际项目里经常把0x0A叫作“DTC户口本”。因为它返回的东西本质上就是ECU软件里DTC种子表、诊断配置和底层故障管理模块经过编译后呈现出来的完整集合。无论当前有没有故障这个集合都在。所以它非常适合用来做基线对比软件变更之后用0x0A拉一次全表和上一版配置做diff哪些DTC删了、哪些DTC加了、状态位定义有没有变化一眼就能看出来。1.2 和0x01、0x02的分工差异很多人刚接触时会问既然0x02 FF也能读DTC列表为什么还要单独用0x0A因为两者读的东西根本不是一回事。0x02后面要带一个DTCStatusMask比如0xFF就是“把所有状态位都匹配上”ECU返回的是当前状态满足掩码的DTC。也就是说0x02返回的是“当前正在发生的、历史确认过的、或测试失败的DTC”是运行时动态结果。0x01则只告诉你数量不告诉你具体是哪些码。0x0A返回的是“这个ECU支持的所有DTC”哪怕一个故障都没发生ECU也会把全表列出来。可以拿一个具体场景说明同一台ECU0x02 FF可能只返回两三个当前确认的故障码而且每个码的状态字节各不相同0x0A返回的可能是上百条记录其中绝大多数状态字节是0x00表示“支持但当前没有故障”。所以如果你想验证ECU的DTC配置是否刷对用0x02会漏掉大量未激活的码必须用0x0A。收到0x0A响应后如果发现某条应该在故障码表里的DTC没出现那基本可以断定软件配置或标定有问题而不是故障没发生。这个逻辑在做产线检测和刷写后自检时特别重要。2. 请求和响应报文拆解2.1 请求报文、PCI字节和0x0A的入参先看请求。0x19是服务ID0x0A是子功能所以最基础的UDS请求数据只有两个字节19 0A。如果走CAN TP单帧发送实际CAN报文里的数据是 02 19 0A其中第一个字节0x02是ISO-TP的PCI字节表示后面有效数据长度是2字节。很多刚接触诊断工具的人会困惑为什么我配置的发送报文是02 19 0A工具界面上却显示19 0A因为工具把ISO-TP的PCI字节自动处理掉了。02是传输层用的不是UDS应用层的一部分。如果你用CAPL、Python或其他脚本直接发CAN帧记得要把PCI字节算进去否则ISO-TP层会解析不了。关于0x0A这个子功能本身标准里一般不需要额外的状态掩码参数。有些协议栈、有些OEM实现允许你发19 0A FF把FF当成DTCStatusAvailabilityMask但也有很多ECU只接受19 0A两个字节多传一个字节会直接回NRC 0x13。所以我在写脚本时第一件事永远是查诊断调查表确认这个ECU的0x0A到底要不要带参数不能想当然。还有一个细节UDS子功能的最高位是抑制正响应位。如果你发的是19 8A而不是19 0AECU会正常执行但不回正响应。测试过程中不要乱用这个位否则你会等不到0x59很容易误判成ECU没响应。2.2 响应报文逐字节看0x0A的正响应UDS应用层格式一般是这样的第一个字节0x59正响应SID等于请求SID 0x19加0x40第二个字节0x0A回显子功能第三个字节DTCStatusAvailabilityMask表示ECU支持哪些DTC状态位第四个字节DTCFormatIdentifier表示后面DTC编号的编码格式从第五字节开始一组DTC记录每条记录包含DTC编号和DTC状态字节我拿一条演示数据来说。假设响应UDS数据是59 0A FF 00 00 10 20 01 00 30 40 08解析59正响应0A回显0x0AFF状态可用掩码表示0到7这8个状态位都支持00DTCFormatIdentifier为0x00通常表示ISO 14229-1定义的三字节DTC格式第一条DTC记录00 10 20 01其中00 10 20是DTC编号01是状态字节第二条DTC记录00 30 40 08其中00 30 40是DTC编号08是状态字节注意我这里只是演示解析不要把这几个DTC编号直接对应到真实故障码。真实项目里DTC编号怎么编码完全看DTCFormatIdentifier尤其是OEM自定义格式一定要以诊断规范为准。DTC状态字节各位的含义我列一下方便对照bit0testFailed最近一次测试失败bit1testFailedThisOperationCycle本次操作循环内测试失败bit2pendingDTC待确认故障bit3confirmedDTC已确认故障bit4testNotCompletedSinceLastClear上次清除后未完成测试bit5testFailedSinceLastClear上次清除后测试失败bit6testNotCompletedSinceLastOperationCycle本次操作循环内未完成测试bit7warningIndicatorRequested请求点亮警告灯前面响应里的状态字节01表示bit0被置位也就是最近一次测试失败状态字节08表示bit3被置位也就是已确认故障。如果你看到0x09那通常是testFailed和confirmedDTC同时置位属于“既测试失败又确认”的常见组合。2.3 多帧响应和超时参数0x0A返回的是全量支持列表ECU支持几十上百个DTC非常常见。一条响应很容易超过单个CAN帧的最大8字节所以会走ISO-TP多帧传输。多帧传输第一个帧是首帧PCI字节以0x10开头后面跟的是总长度后续连续帧以0x21、0x22、0x23这样的PCI开头。写脚本解析时不要假设响应一定能在单帧里收完更不要把多帧响应拆成多个独立诊断响应。要用ISO-TP协议栈先完成组包再拿到完整的UDS数据去解析。很多工具已经内置了组包你只管设置好超时时间就行。超时怎么设UDS里P2是ECU从收到请求到开始响应的时间默认通常是50ms但0x0A这种需要遍历整张DTC表的服务ECU可能要跑更久所以还要考虑P2*。实际测试中我习惯P2设50msP2*设2000ms多帧完整接收超时设5000ms。如果ECU在准备响应过程中回复了NRC 0x78也就是responsePending应用层要重新计时继续等下一帧不能直接判失败。3. 测试脚本与场景落地3.1 用诊断脚本拉全表不管是用CANoe的CAPL、千兆网测试框架还是Python自己拼报文0x0A的核心逻辑都很单一发19 0A收59 0A然后把后面的数据一条条拆出来。用Python写的话伪代码大概是这个样子# 伪代码核心是发请求、收ISO-TP完整响应、按子功能解析 request bytes([0x19, 0x0A]) response isotp_send_recv(request, can_id0x7E0, rsp_id0x7E8) if response[0] 0x59 and response[1] 0x0A: availability_mask response[2] dtc_format_id response[3] records response[4:] if (len(records) % 4) ! 0: print(记录长度异常) for i in range(0, len(records), 4): dtc_number records[i:i3] dtc_status records[i3] print(dtc_number.hex(), hex(dtc_status))这段代码虽然简单但关键点都在判断正响应SID、回显子功能、用DTCFormatIdentifier确定记录长度、用取余判断响应是否被截断。实际项目里建议不要手写ISO-TP组包层直接用pcan-can、python-can加isotp模块把传输层交给靠谱库应用层只做业务解析。如果你用的是CANoe导入CDD诊断文件后可以直接调用诊断函数不用手拼报文。尤其是有ODX/CDD的项目工具会根据诊断描述自动把19 0A发出去并把响应结构化成DTCList对象比手撕字节高效很多。但工具再智能你也要知道底层到底是什么格式否则CDD解析出来的表格对不上现场数据时你根本不知道问题出在哪一环。3.2 配合0x14和0x02做DTC验证0x0A单独用价值有限实战里我喜欢把它和其他服务组合起来做验证。一个很经典的链路是先发0x14清除故障信息再发0x02 FF读当前故障最后发0x0A读支持列表。清除操作完成后0x02 FF返回的通常是空列表或者所有状态字节清零0x0A返回的仍应该是完整的支持列表。如果清除之后0x0A少了DTC那说明清除逻辑把DTC配置表也清了这是不正常的。再说一个我踩过的坑有次测试一个BMS的故障管理模块刚开始用0x02 FF去拉故障码结果只拉到两个低压相关的码我一度以为软件配置只做了两个DTC。后来无意中发了0x0A发现ECU支持三十多个DTC但大部分因为测试条件没满足状态字节一直是0x00。所以只看0x02会严重低估ECU的诊断能力0x0A才是判断“ECU支持哪些故障码”的权威接口。在自动化测试里我还会把0x0A的结果做成基线文件。每次软件版本更新后自动执行一次把返回的DTC编号集合和基线做diff。新增的、删除的、改状态可用掩码的全部自动标出来。这个做法在ECU软件频繁迭代的项目里非常省事避免人为去翻几十页诊断规范。3.3 产线、刷写和售后怎么用产线下线检测里0x0A一般用于刷写后的配置项确认。ECU下线前会先刷写软件再执行一系列扫描其中一道就是读0x0A支持列表和产线MES系统里的标准配置表做比对。只要多一个DTC或少一个DTC就说明软件刷错或者配置字选错直接把不合格品拦截住。刷写流程里0x0A也常用来做刷写前备份和刷写后校验。如果新版本软件支持列表和旧版本差异很大售后诊断仪又拿着旧版本的测试用例来操作就很容易误判故障。这时候先发一条0x0A把当前版本的支持列表拉出来再决定后续测试步骤能省掉很多无头绪的排查。售后场景中0x0A更适合给诊断仪做自检。比如诊断仪连接ECU后先读0x0A确认通讯链路正常、ECU诊断配置可读然后再进具体故障码界面。如果0x0A返回都异常那大概率不是某个故障码的问题而是诊断链路、CAN总线或ECU诊断状态的问题。4. 常见NRC与解析坑4.1 NRC速查表0x0A服务虽然简单但实际测试中NRC种类不少。我整理了一张速查表NRC含义常见原因0x12subFunctionNotSupportedECU固件没实现0x0A或诊断配置裁剪掉了这个子功能0x13incorrectMessageLengthOrInvalidFormat请求长度不对比如0x0A不带参数但你发了参数或反过来0x22conditionsNotCorrect当前会话不允许、车速/挡位/电源模式等条件不满足0x31requestOutOfRange子功能支持但参数范围不对0x0A一般少见多半是协议栈兼容问题0x33securityAccessRequired比较少见于0x19但OEM有权限隔离策略时可能触发0x78responsePendingECU正在忙需要继续等待不要立刻判超时遇到0x12别急。先看OEM的诊断规范确认这个ECU到底支持不支持0x0A。有些ECU只实现了0x02没实现0x0A这不是bug而是需求就这样。遇到0x22时先确认诊断会话是不是在扩展会话或编程会话很多DTC相关服务在默认会话也能跑但也有OEM要求先进10 03。遇到0x78要用等待机制把总超时拉长然后继续收响应直到正响应或真正的NRC出现。4.2 响应解析对不齐的坑0x0A响应最容易出的解析问题就是“对不齐”。一旦DTC记录长度算错后面的数据全部错位解析出来全是乱码。我见过几种典型情况第一种把DTCFormatIdentifier当成可选项忽略了。这样会把后面本该是格式ID的字节当成DTC编号的一部分所有记录偏移一位或两位。第二种默认DTC记录一定是4字节。其实DTCFormatIdentifier不同DTC编号长度可能不同。有些OEM用两字节DTC加一字节状态有些是三字节DTC加一字节状态还有些把状态字节放在DTC编号前面。是否带DTCStatusAvailabilityMask也会影响偏移。第三种大小端搞混。DTC编号里的字节顺序不同OEM定义可能不一样。你用上一个项目写好的解析函数直接套新项目十有八九会踩坑。正确做法是先用手工抓包分析一条已知DTC的响应确认字节顺序再写解析器。我自己的经验是拿到一个新ECU先看诊断规范里的DTCFormatIdentifier说明和示例报文再拿诊断工具实测对比一下工具解析出来的结果是否和规范里的DTC编码一致。两边对上了再写到自动化脚本里。4.3 时序冲突和Busoff问题还有一个容易忽略的问题是时序冲突。DTC全表比较大的ECU0x0A响应可能跨很多帧如果总线负载高连续帧之间被其他报文挤占就可能出现丢帧或超时。这时不要只想程序逻辑也要看一下物理层和总线调度。我遇到过一例台架上ECU运行中回0x0A连续帧间隔抖动很大部分帧来了但响应不完整脚本判定失败。后来排查发现是测试设备发周期性报文太密集占用了大量总线资源。把周期调整后0x0A再没出过问题。所以多帧响应失败时先检查总线负载再看协议栈超时。另一个常见假象是Busoff。ECU在通讯开启瞬间或长期离线后刚恢复会出现总线关闭状态这时候任何诊断请求都不回。你发0x0A收不到响应不一定服务有问题而是ECU总线状态还没恢复。先确认ECU在总线上有正常周期性报文再做诊断服务验证逻辑更稳。5. 工程落地建议5.1 先把支持列表变成基线我接手一个新诊断项目时第一件事不是写一堆复杂测试用例而是先发一条0x19 0x0A把支持列表拉出来保存。这份列表就是后面所有测试的基线。有了基线你才能知道0x02返回的某个码到底是不是ECU该支持的。比如你读到一个奇怪的DTC第一反应不是查规范而是先和基线比对基线里如果没有更可能是软件bug或者解析错了基线里有再看状态字节和触发条件。这个思维顺序对排查问题很重要。把基线做成文件后建议纳入版本管理。每次ECU软件发布前自动跑一次0x0A提交一份当前版本的支持列表。版本发布说明里可以直接带上诊断测试人员也能快速评估影响范围。这套流程成本很低收益却很直接能避免很多低级配置遗漏。5.2 别忽略DTCStatusAvailabilityMask很多人在解析0x0A时只盯着DTC编号和状态字节把DTCStatusAvailabilityMask当成一个固定值忽略掉。这样做在单项目里可能没事但跨项目时很容易埋雷。状态可用掩码告诉你ECU到底支持哪些状态位。如果AvailabilityMask是0xFF那状态字节的每个bit都有意义如果是0x1F像bit5、bit6、bit7就可能用不了。你拿别的ECU脚本硬套会把不支持的bit解析成真实状态白白增加误判。所以我建议在自动化测试的报告中单独记录这个掩码。每次软件变更后如果掩码变了说明DTC状态管理逻辑有调整不是小事。尤其是在多个ECU平台共线生产的项目里掩码不一致却没有被发现很容易稀里糊涂把问题流到售后。5.3 一个个人习惯把0x0A放在每个测试用例最前面最后分享一个我自己的习惯。凡是涉及DTC的测试用例我都喜欢在用例开始前先发一次0x0A把支持列表和状态可用掩码打印出来。这样做有两个好处一是确认ECU当前诊断配置和预期一致避免后续测试结论建立在错误配置上二是当用例失败时日志里已经留了一条完整的基线记录方便回到现场复现。有次我在做整车道路测试时某个ECU在行驶过程中丢失了部分DTC故障码表被重置现象很诡异。回到实验室后我重新发0x0A发现支持列表确实少了好几条。后来定位到是底层错误处理逻辑把DTC配置表的一部分覆盖了。如果没有整车上预先记录的0x0A基线这个问题真不好复现。0x0A这个小服务看起来简单但在诊断链路里承担的角色一点都不简单。熟练掌握它不只是会发一条报文更是对整个ECU诊断配置和故障管理逻辑的理解。下次拿到一辆车或一块控制器先别急着去看故障码发一条0x19 0x0A把ECU的“DTC户口本”先翻出来看很多问题就已经答案揭晓了。
返回列表