☰
UDS诊断入门:搞懂ISO 14229报文、服务与NRC排查
2026/10/3 8:06:38 网站建设 项目流程

做车载的人,早晚会遇到这么一幕:产线或售后反馈某台控制器“诊断失败”,你接上CANoe,对着ECU敲了一条请求,Trace窗口滚出几行报文。供应商工程师扫了一眼说:“7F 22 31,你肯定没先切会话。”你盯着那串十六进制,大脑空白。这个“7F 22 31”就是ISO 14229定义的统一诊断服务(UDS)世界里每天都在发生的对话。

这篇内容就是写给刚接触UDS诊断的新手。我会从标准定位讲到报文怎么传,再到新手必须背下来的几个服务,最后用一组完整报文串起来。看完你至少能做到:拿到任一条UDS报文,能说出每个字节什么意思;ECU不回正确响应时,知道往哪个方向排查。适合做嵌入式、BSP、测试、生产设备、售后诊断的工程师,也适合刚入行汽车电子但对诊断协议还发怵的同学。

1. 为什么搞车载的人绕不开UDS

1.1 从修车靠猜,到一条报文定位问题

早些年ECU功能简单,故障排查基本靠维修手册里写的“检查线束、量电压、换件试”,效率低,还特别依赖老师傅经验。后来法规要求车辆必须能报告排放相关的故障,才有了OBD这套东西。OBD的特点是标准化、强制、只覆盖排放相关,用的报文结构偏向“查表式PID”,能解决法规问题,但满足不了整车所有ECU的开发、生产、诊断需求——BCM要读门锁状态,ESP要标定横摆角传感器,BMS要读SOC和绝缘电阻,这些OBD根本不关心。

于是ISO 14229出现了。它叫UDS(Unified Diagnostic Services),核心思想是:把诊断动作抽象成“服务”,比如读数据、写数据、执行例程、读故障码、清故障码,无论哪个ECU都用同一套语义。供应商A做的BCM和供应商B做的EPS,在测试仪看来是“同一套对话规则”,只是对话内容里的DID(数据标识符)、RID(例程标识符)、DTC(故障码)完全不同。这就是Unified的含义。

1.2 ISO 14229在诊断架构里的位置

很多人一上来就翻ISO 14229-1,被几百页的表格吓到。其实标准讲得很清楚,UDS只是应用层。一条诊断报文从测试仪到ECU,要经过分层:

层级标准作用
应用层ISO 14229-1定义服务、子功能、NRC、DID/RID语义
传输层/网络层ISO 15765-2(DoCAN)解决CAN一帧装不下的问题,做多帧传输
数据链路层/物理层ISO 11898CAN本身的帧格式与电平

还有一个常见分支是ISO 13400(DoIP),把UDS搬到以太网上,应用层仍然是14229。所以你会看到“UDS over CAN”和“UDS over DoIP”的说法,前半段相同,后半段完全不同。新手常犯的误区就是把CAN ID和UDS请求里的字节混为一谈。记住一句话:UDS是对话规则,CAN是电话线路;CAN ID决定电话打给谁,UDS字节决定说什么话。

2. 先看懂报文,再谈服务

2.1 请求/响应模型与两种寻址方式

UDS是严格的客户端/服务器模型。测试仪(Tester,也叫Client)发请求,ECU(Server)回响应。响应分两种:

  • 正向响应:把请求SID的最高位置1,即SID + 0x40。比如请求10 03,成功响应开头就是50 03。
  • 负向响应:固定7F开头,后面跟着请求的SID和NRC。比如7F 10 7F表示“当前会话不支持10服务的这个子功能”。

寻址方式是最先要考虑的。CAN诊断里常见两种:

  • 物理寻址:点对点,一个CAN ID对应一个ECU。经典诊断请求ID 0x7E0,响应ID 0x7E8,就是物理寻址。你发到7E0的请求只有那一个ECU应答。
  • 功能寻址:广播,请求ID 0x7DF,所有支持该功能寻址的ECU都会收到并响应。适合全体轮询类的动作,比如OBD扫描工具用7DF读排放数据。

到29位ID时代,ISO 15765-2定义了更规范的Normal Fixed Addressing,比如常见的0x18DB33F1、0x18DAF10E这类。前几位其实包含了目标地址和源地址,但新手入门阶段,先把7E0/7E8/7DF这类11位ID的经典模型吃透就够用了。重点理解一点:物理寻址和功能寻址的区别不在UDS字节里,而在CAN ID上。

2.2 传输层多帧拆装:SF、FF、FC、CF一次讲清

经典CAN一帧数据场最多8字节。可UDS很多响应远超8字节,比如读一串DTC、读VIN、回快照。这个矛盾由ISO 15765-2的传输层解决。传输层会在每个诊断数据前面加PCI(Protocol Control Information)字节,然后按能力拆成若干CAN帧。新手必须认得这四种帧:

帧类型PCI样子含义
单帧 SF第一个字节高4位=0x0,低4位=数据长度一条请求/响应用一帧发完
第一帧 FF第一个字节高4位=0x1,低12位=总长度多帧传输的开始,告诉对方总共有多少字节
流控帧 FC第一个字节高4位=0x3,低4位=流控状态接收方回复“你可以继续发了”
后续帧 CF第一个字节高4位=0x2,低4位=帧序号后续数据帧,序号从1开始,循环

举个实际例子。请求19 02 FF(按状态掩码读DTC)只有3个字节,传输层加一个PCI字节0x03,完整CAN数据场就是03 19 02 FF。

ECU回了多个DTC,总长超过7字节,抓包就会看到这样的多帧序列:

Tx 03 19 02 FF // SF,请求读DTC Rx 10 1B 59 02 FF 01 25 00 // FF,总长0x1B=27字节,带前6字节数据 Tx 30 00 00 // FC,允许继续发送 Rx 21 28 10 02 00 61 03 05 // CF,序号1 Rx 22 00 32 04 A1 00 15 05 // CF,序号2 Rx 23 12 00 82 06 33 00 12 // CF,序号3,数据全部收齐

这里特别容易犯迷糊的是FF和CF。0x10 0x1B里,0x1B是27,指从FF帧数据区开始,一直到最后一个CF数据区的总字节数,不是CAN帧数。0x30 0x00 0x00里,第二个字节0x00表示流控状态是“继续”,BS=0表示不限一次能发多少块,第三个字节STmin=0表示后续帧可以连续发。如果你在写上位机,解析多帧时必须维护状态机:收到FF→回FC→收CF,并按序号拼接,否则数据就乱套了。

CAN FD普及以后,数据场从8字节扩到64,很多新ECU一个FD帧就把原来要拆七八帧的响应装完了。但多帧逻辑还是同一套,所以经典的8字节模型一定要先搞清楚。

3. 新手必背SID:六大核心服务逐个拆解

3.1 10服务:所有的诊断操作都从“换会话”开始

ECU上电默认处在“默认会话”,这个会话下很多服务是拒绝响应的,目的是降低负载、防止误操作。要读深度数据、清故障码、写数据、刷写,先要切到扩展会话或编程会话。10服务的三个核心子功能:

  • 01 默认会话
  • 02 编程会话(刷写用)
  • 03 扩展会话(开发、产线、售后诊断用)

请求和响应非常简单:

Tx 02 10 03 Rx 06 50 03 00 32 13 88 13 88

响应里50 03表示成功,后面6个字节是三个时间参数:P2Server=0x0032=50ms,P2Server=0x1388=5000ms,S3Server=0x1388=5000ms。P2表示ECU正常情况下响应一个请求的最大时间,超过这个时间ECU必须先回一个0x78(后面专门讲NRC再说);P2是ECU临时的延长响应时间;S3是会话超时时间,进入扩展会话后如果超过S3没有任何诊断请求,ECU自动跳回默认会话。

这个S3是新手第一个高频坑。写脚本调试时,前一条请求和后一条请求隔了几分钟,中间没有保活请求,ECU自动回默认会话了,下一条扩展会话才能用的服务直接返回7F xx 7F或7F xx 22。所以长时间调试,要么定期发个10 03,要么在请求间隙插入3E 00(测试仪在线)保活。

3.2 27服务:安全解锁的Seed-Key机制

很多动作不是切了会话就能做的,比如写校准参数、擦Flash。这类操作前面还有一道门:27服务安全解锁。流程固定:

Tx 02 27 01 // 请求种子,01是第一级别 Rx 06 67 01 11 22 33 44 // 返回种子 11 22 33 44 Tx 06 27 02 45 56 67 78 // 发送Key,由种子按算法计算 Rx 02 67 02 // 解锁成功

为什么不能直接放一个密码进去?因为UDS的安全设计本质不是防黑客,而是防止误操作和保证流程规范。如果Key明文广播,产线上随便来个人抓个包就能刷ECU,售后合规就乱了。Seed-Key算法由每个OEM自己定义,ISO 14229只管服务结构,不规定算法,所以不同厂商ECU的解锁算法完全不同。

新手踩得最多的坑有两个。一是时序:请求种子和解锁之间往往有严格的时间窗口,超时后ECU会自动关闭解锁权限,或者要求重新拉种子。二是失败锁定:连续输错几次Key,ECU会返回0x36(尝试次数超限)或0x37(延时未到),锁定时间可能从几秒到几分钟不等。这时候不是算法算错了,而是时间惩罚还没结束,得等。

顺带提一句,有些ECU安全相关流程还会做内部自检,比如某些带Class B时钟自检的控制器,会先检查时钟精度再给种子,时钟偏差过大直接拒绝,这类属于OEM定制逻辑,不在标准正文里,但排查解锁失败时要想到。

3.3 19/14服务:故障码的读与清

19服务有几个子功能,入门阶段记住最重要的几个:

  • 01 按状态掩码统计DTC数量
  • 02 按状态掩码读DTC(最常用,比如19 02 FF)
  • 04 读DTC快照(冻结帧)
  • 06 读DTC扩展数据

请求19 02 FF的意思是“把当前所有状态的DTC都报给我”。响应格式:59 02 + 状态可用性掩码 + 若干组(DTC 3字节 + 状态1字节)。看一个例子:

Tx 03 19 02 FF Rx 08 59 02 FF 01 25 00 28

这里08是SF长度,59 02是服务响应,FF是ECU支持的状态位掩码,01 25 00是一个DTC编码,最后的28是状态字节。0x28换算成二进制是00101000,bit3=1表示confirmedDTC(已确认故障),bit5=1表示testFailedSinceLastClear(上次清除后测试失败过)。读到这个组合,说明这个故障是真实存在、已确认的,不是偶发。

19 02 FF响应如果DTC很多,就会变成第2章讲的多帧。所以你在Trace里看到FF、FC、CF一长串,别慌,拼接完以后再按格式解析。

清故障码用14服务:14 + DTC高位 + DTC中位 + DTC低位,FF FF FF表示清除所有DTC。响应是01 54,一句话的事。但实际工程里,清DTC往往要求先满足前置条件(扩展会话、整车静默、上电状态正确),否则返回7F 14 22(条件不满足)或7F 14 7F(当前会话不支持)。

3.4 22/2E服务:按标识符读写数据

22服务是按DID(Data Identifier)读数据。DID是2字节的“门牌号”,比如F190在很多车型里是VIN,F187是常见的软件版本DID(具体以OEM诊断规范为准)。请求22 F1 90,成功响应62 F1 90加数据。

Tx 03 22 F1 87 Rx 05 62 F1 87 01 0A // 版本号字面数据,具体含义看该ECU的DID表

2E服务按DID写数据,请求2E + DID + 数据,成功响应6E + DID。写操作一般都需要扩展会话加安全解锁,而且写完以后最好回读验证,这在产线标定和售后配置时非常常见。不要以为DID是标准规定的,F1开头有一部分被标准“建议保留”,但绝大多数DID含义是OEM在诊断规范里自己定的。拿到一份诊断调查表,第一件事就是查DID表和RID表。

3.5 31服务:例程控制

31服务的请求结构:31 + 子功能 + RID(2字节)+ 可选参数。子功能三个固定值:

  • 01 开始例程
  • 02 停止例程
  • 03 查询例程执行结果

例程的概念类似于“远程调用ECU内部一段程序”,客户端控制启停、查询结果,但具体逻辑在ECU内部。经典例子是刷写前擦除Flash:31 01 FF 00(不同OEM的RID不同,有些是0202/0203)。响应71 + 子功能 + RID + 结果数据,如果例程会返回状态参数,会一起带上。

为什么需要31而不是直接定义一个写Flash的服务?因为例程模式让OEM可以灵活封装各种复合操作:自检、擦除、计算校验值、读取Bootloader版本,全都通过同一套启停查询接口暴露出来,而不用为每个新需求发明一个新SID。

3.6 34/36/37服务:完整的UDS刷写链路

要说新手最大的实战需求,可能就是刷写流程。完整的典型序列是这样:

Tx 02 10 02 // 进编程会话 Rx 02 50 02 Tx 02 27 01 // 安全解锁 Rx 06 67 01 xx xx xx xx Tx 06 27 02 xx xx xx xx Rx 02 67 02 Tx 05 31 01 02 02 // 例程:擦除应用区Flash Rx 03 71 01 02 02 Tx 08 34 00 00 08 00 01 00 // 请求下载:地址0x000800,长度0x0100 Rx 03 64 00 01 // 64表示接受,带块长度信息 Tx 08 36 01 AA BB CC DD EE FF // 传输数据,块序列号01 Rx 02 76 01 Tx ... 36 02 36 03 ... // 循环发送 Tx 02 37 // 请求传输退出 Rx 01 77 Tx 05 31 01 02 03 // 例程:检查编程依赖/完整性 Rx 04 71 01 02 03 00 Tx 02 11 01 // 复位ECU Rx 01 51

这里几个字节要搞清楚。34服务请求里34后面两个字节是地址和长度格式标识符,入门阶段理解成“34告诉ECU我要往哪个地址写多少数据”就够了。36服务后面第一字节是块序列号(从1开始,到FF后回绕到01继续),ECU靠它检查帧顺序,目的很简单——防止丢帧或乱序导致数据写错位置。写错位置在Flash里就是灾难。

刷写链路里最常见的NRC是7F 34 70(上传下载不被接受)、7F 36 72(编程失败)、7F 36 70。碰到这些,优先检查:地址范围对不对、Flash擦过没有、安全解锁有没有失效、块序列号有没有回绕错误。

4. NRC负响应码:协议里最有信息量的部分

4.1 负响应格式与三类问题

负响应格式就三个字节:7F + SID + NRC。但NRC是调试时最值钱的信息,它把“ECU为什么不理你”分成了几类:

  • 服务本身的问题:0x11(不支持这个服务)、0x7F(当前会话不支持该服务)
  • 子功能或参数问题:0x12(子功能不支持)、0x13(报文长度错误)、0x31(请求超出范围)
  • 条件和时序问题:0x22(条件不满足)、0x24(请求序列错误)、0x33(安全访问拒绝)、0x35(Key错)、0x36/0x37(解锁次数超限/延时中)

定位思路很简单:先看NRC类型,再回看请求本身,最后检查当前ECU状态。绝大多数问题轮不到翻标准全文,把前端几个NRC背下来就够用了。

4.2 高频NRC速查表

NRC名称含义与常见场景
0x10generalReject一般拒绝,很少见,通常是内部错误
0x11serviceNotSupportedECU根本不认识这个SID
0x12subFunctionNotSupported服务认识,但这个子功能不支持
0x13incorrectMessageLengthOrInvalidFormat长度不对或格式错,多发/少发字节
0x22conditionsNotCorrect前置条件不满足,比如没切会话
0x24requestSequenceError时序错误,比如没请求种子就发Key
0x31requestOutOfRangeDID/RID/参数超出范围
0x33securityAccessDenied需要安全解锁,或安全级别不够
0x35invalidKeyKey算错了
0x36exceededNumberOfAttempts解锁尝试次数超限
0x37requiredTimeDelayNotExpired解锁失败惩罚时间未到
0x70uploadDownloadNotAccepted上传下载不被接受,地址或格式不对
0x72generalProgrammingFailure编程失败,Flash写入/擦除出问题
0x78requestCorrectlyReceivedResponsePending请求已收到,还在处理,稍后回最终响应
0x7EsubFunctionNotSupportedInActiveSession当前会话不支持该子功能
0x7FserviceNotSupportedInActiveSession当前会话不支持该服务

0x78值得单独说。它不是一个错误,而是“我在干活,别催”。ECU在P2时间内处理不完,先回一个0x78占位,然后继续在P2时间内完成并回最终响应。所以收到0x78以后千万不要直接判失败,要等最终响应。很多上位机误报超时,就是P2配置太短,或者收到0x78后没有正确处理。

4.3 一个真实的NRC排查过程

有一次我同事调试一个新项目的ECU,发22 F1 90读VIN,返回7F 22 22(条件不满足)。第一反应是查DID表,发现F190确实定义了;又查安全等级,发现读VIN不需要解锁;最后翻诊断规范,里面写了一条:F190只在扩展会话或编程会话下可读。把它切到10 03后,22 F1 90正常返回数据。

这类案例非常典型。NRC=0x22时,脑子里要立刻过一遍:会话对不对、安全解锁有没有、ECU有没有在跑特定状态、有没有别的服务占用资源。按这个顺序排查,十有八九能找到原因。

5. 把知识串起来:一次完整诊断会话逐字节解读

5.1 场景设计

假设一辆车在产线上报了一个已确认的故障码,我们需要按流程读取、确认,然后清除,再读一下ECU版本号,最后复位ECU。ECU上电后处于默认会话。我按最标准的路径走一遍,每一行都是你能在Trace里看到的真实帧:

Tx 02 10 03 // 进入扩展会话 Rx 06 50 03 00 32 13 88 13 88 // 成功;P2=50ms,P2*=5000ms,S3=5000ms Tx 02 27 01 // 请求种子 Rx 06 67 01 11 22 33 44 // 种子=11 22 33 44 Tx 06 27 02 45 56 67 78 // 计算后发送Key Rx 02 67 02 // 解锁成功 Tx 03 19 02 FF // 读所有状态的DTC Rx 10 1B 59 02 FF 01 25 00 // FF:总长27字节 Tx 30 00 00 // 流控:继续 Rx 21 28 10 02 00 61 03 05 // CF1 Rx 22 00 32 04 A1 00 15 05 // CF2 Rx 23 12 00 82 06 33 00 12 // CF3,拼接完整 Tx 04 14 FF FF FF // 清除所有DTC Rx 01 54 // 清除成功 Tx 03 22 F1 87 // 读ECU软件版本 Rx 05 62 F1 87 01 0A // 版本号字面数据 0x01 0x0A Tx 02 11 01 // 复位ECU Rx 01 51 // 复位成功,之后总线上ECU会离网或重启

5.2 逐层拆解这段对话

先看10 03这条。请求是02 10 03,SF的PCI=0x02,数据长度2字节:SID 0x10和子功能0x03。响应50 03后面的6个字节就是P2、P2*、S3三个时间参数。很多调试工具里能看到ECU的P2/P2*配置,就来自这里。

再看19 02 FF这一段多帧。FF帧的0x1B是总长,等于FF数据段6字节加后面三个CF各7字节,共27字节。拼接后的完整数据流是:

59 02 FF 01 25 00 28 10 02 00 61 03 05 00 32 04 A1 00 15 05 12 00 82 06 33 00 12

解析时先把整个多帧拼成一个连续字节流,再按“3字节DTC+1字节状态”切分。状态字节0x28、0x61、0x32这些反映了不同DTC的不同确认状态。0x28的confirmedDTC位为1,是需要关注的故障;0x61里bit0(testFailed)和bit5(testFailedSinceLastClear)都为1,说明这个故障还在持续发生。

后面的读版本号响应只有5个字节,SF一个帧就完成了。这里DID是F1 87,具体项目里要看该ECU的诊断规范确认含义。最后11 01复位,响应完以后ECU会重新初始化,所以不要马上发下一条请求,要等重启完成。

5.3 现实与教科书之间的差距

这种教科书式的序列在真车上很少一次走通。我见过太多情况:

  • 第一秒发10 03就超时,不是ECU坏了,是它上电后还在跑初始化,CAN还没ready,等两秒再发。
  • 解锁返回7F 27 37,说明上次失败的惩罚还没结束,先歇会儿。
  • 清DTC返回7F 14 22,说明清故障码必须在扩展会话,而你前面不小心被S3踢回默认会话了。
  • 读版本号返回7F 22 31,大概率DID写错了,或者这个ECU根本不支持这个DID。

遇到这些,先别急着怀疑协议栈,按NRC分类逐项查状态。工具只是放大了你的判断,真正定位问题的还是你对协议流程的理解。

6. 常用工具与新手高频坑

6.1 CANoe和TSMaster里怎么快速上手诊断功能

CANoe是Vector的当家产品,新手最常见的困惑是“诊断DLL文件怎么生成”。实际上,你日常调试根本不需要自己生成DLL。诊断DLL一般是OEM或工具链根据CDD/ODX数据库生成,供自动化测试或诊断仪使用。手动调试的路径是:新建工程 → 添加CAN通道 → 在Diagnostics/Diagnostic Console里加载CDD文件(如果能有CDD),加载后会看到树状的服务列表,点一下就能自动填充请求字节并发送。没有CDD也没关系,直接用CAN IG(Interaction Generator)或CAPL发原始CAN帧,自己拼字节。

TSMaster是国内用得越来越多的工具。它的诊断模块也支持加载CDD/ODX,也支持手动发诊断请求。自动触发方面,可以配置定时轮询,比如每500ms自动发一条22 F1 87读版本号;也可以配置成事件触发,比如DTC状态变化后自动发19 02 FF。不同版本菜单名称不同,思路都是“设置周期或事件 → 绑定诊断请求 → 启动自动发送”。

新手在工具上的第一个操作建议永远是:先学会过滤。Trace窗口里同时有几条ECU在刷网络管理报文、应用报文,你根本看不清7E0和7E8。用过滤器只保留请求/响应ID,整个报文链路立刻清晰。

6.2 新手常踩的坑:LIN诊断、可选数据、时间参数

热词里有个“发送可选诊断数据打不开”,大概率是诊断控制台的请求编辑器里,某个可选参数没有填完整,界面把发送按钮置灰了,或者当前选的报文模板需要先填DID/RID。解决办法:把必填参数填完,或者换用原始帧发送模式手动拼字节。这不是ECU的锅,是工具UI逻辑。

还有一个高频场景是“LIN诊断报文”。UDS到了LIN上,报文ID固定为主请求帧0x3C、从响应帧0x3D,数据场第一字节是NAD(节点地址),后面才是PCI和诊断数据。LIN诊断的传输层和CAN的ISO 15765-2不是一套,很多人用CAN多帧的思维去解析LIN从响应,直接把NAD当成了数据,越看越乱。入门阶段记住:LIN诊断一条主请求或从响应最大也就8字节,NAD决定了这条消息是发给哪个ECU、从哪个ECU回的。具体映射,参考ISO 17987和OEM的LIN诊断规范。

时间参数是另一个隐性大坑。很多工具默认P2是50ms、P2是5000ms,但OEM完全可以自定义。如果ECU在100ms才回响应,而工具P2还是50ms,要么工具会显示超时,要么你会看到一堆0x78。调工具超时配置前,先看ECU诊断规范里P2/P2的定义。调试早期就把这些参数配好,能省一整天的排查时间。

最后再分享一点个人经验。我带过不少新人,他们在UDS上卡壳,十有八九不是卡在协议本身,而是卡在“不知道这条报文的来龙去脉”。所以我一直建议:拿到任何一条诊断报文,先问自己三件事——这帧是请求还是响应?寻址的是哪个ECU?ECU现在处于什么会话、有没有解锁?这三件事捋清楚,再去看字节内容,绝大部分问题都能有方向。诊断协议这东西,背是背不完的,但把服务模型、寻址、会话、NRC这四根柱子立起来,后面翻标准、查OEM规范都是水到渠成的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询