资讯动态

UDS 0x22服务全解析:从DID规划到ISO-TP传输的实战指南

发布时间:2026/10/19 2:19:51 来源:尧图企业网站定制
做了几年车载诊断相关的开发我最大的感受是UDS协议本身并不复杂二十四五个服务里真正天天打交道的就那么几个而0x22服务ReadDataByIdentifier按标识符读取数据绝对算得上是最常用的一个。不管是产线下线检测、售后故障排查还是研发阶段的ECU标定验证你都得靠它去把ECU内部的数据“掏”出来看。但越是常用越容易踩坑。很多人对着协议文档看了一遍觉得“不就是发个0x22加两个字节的DID嘛”结果一到真车上就蒙了要么响应一直报0x33要么收到的数据长度对不上要么报文超过8个字节以后不知道该怎么组帧。这篇文章我就从协议设计初衷、报文结构、DID规划、ISO-TP传输层、会话与安全访问这几个维度把22服务彻底拆开讲清楚最后再附一个完整的实读VIN案例和排查思路。适合刚开始接触UDS的测试工程师也适合被各种“玄学DID”折磨过、想系统理一遍的嵌入式开发。1. 22服务在UDS协议栈中的位置与设计初衷1.1 诊断服务的“读”与“写”分工UDSUnified Diagnostic Services是ISO 14229定义的一套应用层诊断协议它面向的是车辆上所有ECU电子控制单元。在整套服务清单里有负责会话控制的0x10、有负责安全解锁的0x27、有负责读写DTC故障码的0x19、0x14还有外围设备控制0x2F、例程控制0x31等等。而0x22和0x2E这两兄弟负责的是一个几乎贯穿ECU全生命周期的基本操作——数据的读出与写入。从命名就能看出来ReadDataByIdentifier关键在于“Identifier”这个词。它跟我们平时嵌入式开发里“直接读某块内存地址”的思路完全不同。UDS不要求诊断仪知道ECU内部的物理地址它只要求你告诉ECU“我要读ID是0xF190的那个数据”剩下的事情ECU自己根据内部映射关系去完成。这是一种非常典型的“逻辑化、标准化”接口设计。这样设计的第一个好处是安全诊断仪不需要关心ECU的Flash/RAM布局也就减少了因为地址错误导致ECU异常的概率。第二个好处是跨平台同一套诊断仪逻辑可以适配不同硬件平台的ECU只要它们对外暴露的DID接口一致。第三个好处是灵活DID背后可以是一个寄存器、一段RAM、一组标定参数甚至可以是实时计算出来的值——ECU完全可以“现算现卖”在响应当中填充最新数据。1.2 为什么用DID而不是直接给地址有人可能会问协议里不也有0x23ReadMemoryByAddress吗那个才是直接按地址读的为什么还要保留0x22确实0x23在看bootloader、标定开发时很有用但它有两个明显问题一是要求诊断仪预先知道ECU的内存映射这在售后场景下几乎不现实二是0x23涉及的能力太底层很多ECU厂商出于安全考虑根本不开放或者只在扩展会话安全解锁后开放有限范围。反过来0x22暴露的DID接口是经过ECU软件层“加工”过的。比如一个DID代表“当前车速单位km/h”ECU收到请求后去读取轮速传感器、组合导航信息或者总线上的车速信号经过格式转换后再返回。诊断仪拿到的是带语义的工程值而不是裸的寄存器位。这种封装层次的区别让0x22成为整车故障诊断、下线配置、软件刷新后验证的主干通道。1.3 22服务的适用边界22服务能读什么从实践看大致可以归纳为几类静态标识类VIN码、软件版本号、硬件版本号、零部件号、生产日期、序列号。这类数据内容固定适合做整车身份识别和配置校验。动态状态类当前电压、当前温度、发动机转速、车速、油门开度、电池SOC等实时参数。这类数据是“读一次变一次”经常用于OBD仪表和在线诊断。快照记录类特定条件下记录的故障环境数据比如冻结帧数据、事件记录。虽然读DTC快照更常用0x19但很多厂商也会把关键运行参数暴露成DID供诊断仪直接抓取。标定/标识存档类校码、防盗码、配置字、售后刷写标记。正因为覆盖面广22服务几乎在所有诊断场景里都会出现。但也正因为它是“万能读接口”协议实现方在设计DID表时必须特别小心哪些DID可以在默认会话直接读哪些必须切扩展会话哪些还得先过安全解锁——这些访问控制策略直接决定了后面我在第5部分要讲的排查难点。2. 22服务报文逐字节拆解从请求到响应2.1 请求帧到底长什么样22服务没有子功能SubFunction这一点跟0x10、0x27不一样。它的请求格式非常简洁字节位置长度内容示例Byte 01SID 0x220x22Byte 11DID高字节0xF1Byte 21DID低字节0x90所以一条“读取VIN码”的典型请求就是22 F1 90总共3个字节。如果某些DID编号扩展到了3字节有些OEM定义DID为三字节请求就会变成22 3字节DID总共4个字节。ISO 14229里对DID长度的描述没有限定死必须两字节但市场上绝大多数实现都是两字节三字节属于厂商自定义玩法。可能你会注意到请求里只有DID没有“要读多长”的字段。这意味着数据长度完全由ECU决定。ECU内部维护着一张DID信息表里面记录了每个DID对应的数据长度、访问权限、数据来源。收到0x22后ECU查表、取数、打包响应。从这个角度理解22服务更像是一种“按键存取”你按下键DID机器吐出对应的结果至于结果有多长不是你需要关心的。2.2 正响应结构0x62带你飞如果ECU对请求处理成功会返回正响应字节位置长度内容示例Byte 01正响应SID 0x620x220x400x62Byte 11DID高字节0xF1Byte 21DID低字节0x90Byte 3..NN-3DID对应的数据17字节VIN ASCII码正响应SID的规律全UDS通用请求SID加0x40。0x22 0x40 0x62不用死记看到0x62就知道这是对22服务的正响应。数据部分按DID的定义逐字节拼接。最常见的是ASCII字符串VIN、版本号也有大小端整数转速、电压、位段组合状态字、BCD码日期时间几种。具体怎么解析取决于ECU的诊断规范。这里我特别强调一点拿到响应数据后不要急着按“看起来像什么”去解读先确认字节序和单位。同一份数据有的厂商用大端有的用Intel小端解析错了会把5000rpm读成20000rpm。2.3 负响应0x7F 0x22 NRC当ECU无法完成请求时不会静默丢弃而是返回负响应格式固定为0x7F 请求SID0x22 NRCNegative Response Code比如7F 22 33的意思是对不起0x22服务请求被拒绝了原因是安全访问被拒绝0x33。NRC是整个UDS调试里最需要熟悉的内容因为排查问题基本就是靠它对号入座。22服务最常见的NRC有这么几个NRC含义常见触发场景0x13请求消息长度错误DID后面还多了垃圾字节或者请求长度与协议规定不符0x22条件不满足车辆当前状态不允许读该DID比如点火开关未ON0x31请求超出范围DID本身没定义或当前ECU不支持该DID0x33安全访问被拒绝该DID受保护必须先做0x27安全解锁0x7E当前会话不支持该DID只能在扩展会话/编程会话下访问0x7F服务不支持ECU根表里就没有0x22服务少数情况我遇到过很多初学者把7F 22 31当成“DID错了”其实只对了一半0x31的确是DID超出范围但“超出范围”也可能是DID没在这个会话里开放。如果你在默认会话里读一个只能在编程会话读的DID某些ECU会回0x7E但也有实现直接回0x31。所以看到NRC别急着改DID先把会话状态和安全状态一起捋一遍。3. DID规划一整套数据标识符的命名学问3.1 DID是怎么编号的DID是两个字节理论上范围从0x0000到0xFFFF总共65536个编号。但协议并不会傻到让厂商乱用实际上ISO 14229推荐了一些标准区间和公共DID各大OEM再在这个基础上定义自己的私有区。典型的划分方式是这样的DID范围用途归属0x0000-0x00FF保留/制造商特定0x0100-0x01FF通用数据标识很多ECU用它做基础运行参数0xF190-0xF1FF车辆/ECU标识类不只是VIN很多VIN都在这块0xF2xx网络配置、通讯参数0xF3xx排放相关尤其OBD系统0xF4xx-0xFExxOEM自定义扩展区当然这只是“惯例”不是强制标准。在实际项目中看DID规范比猜DID含义靠谱得多。OEM的诊断规范文档里通常每个DID都会给到DID值、数据类型、数据长度、字节序、取值范围、单位、支持的会话、安全访问等级、读/写权限甚至更新条件。拿到这种表22服务才算有了“完整上下文”。3.2 公共DID里的几个熟面孔我平时用得最多的几个公共DID给大家列一下0xF190VIN码Vehicle Identification Number17字节ASCII包含厂商代码、车型代码、生产年份、序列号等信息整车身份识别的“身份证号”。0xF187软件版本号Software Version常见为ASCII字符串可能有16字节、20字节不等。做软件刷新和版本校验必读。0xF18C硬件版本号Hardware Version类似0xF187但标识的是硬件PCB版本。0xF189ECU生产序列号。0xF191整车零标定序列号或车辆特征码按厂商定义。0x0100..0x010F常用于通用模拟量/开关量数据块比如发动机转速、车速、冷却液温度等具体分配还看规范。这里有个小坑不少ECU的VIN DID虽然统一写0xF190但数据长度不一定是17字节。有些商用车会读20字节带厂商扩展信息有些ECU响应是ASCII但中间可能有空格填充。解析VIN时一定要先看规范里给的长度和填充规则否则读出来的字符串会带奇怪的尾部乱码。3.3 厂商自定义DID的设计原则如果你负责ECU端的诊断协议实现那设计DID表就是一件需要抠细节的工作。我的建议是第一尽量往标准区间靠。车辆身份相关的放0xF1区间OBD排放相关的放0xF3区间不要自己随手定义0xABCD之类的“天马行空号”。标准区间的意义在于工具链和诊断仪层面通常已经有了预设处理减少集成工作量。第二内部映射要明确。DID到内部信号源的映射表必须可追溯。我在某项目里就见过DID定义与软件代码分支对不上的情况导致读出来的数据跟需求文档差一个字节测试和开发互相甩锅。所以建一张“DID-长度-单位-来源信号-权限-换算公式”六要素表格比什么好记性都强。第三权限要分级。默认会话可读的DID只留非敏感信息比如版本号、供应商ID涉及车型配置、防盗、安全相关的DID一律放到扩展会话安全解锁之后。别嫌麻烦这是车联网背景下最基本的防渗透要求。4. 传输层的秘密8个字节CAN帧怎么装下大块数据4.1 为什么单帧读不了长数据很多人第一次实测22服务时会发现一个现象请求是22 F1 90三个字节CAN传统帧一帧就能发完但响应可能一帧回不完。因为经典CAN帧的数据场最多8字节而22服务的正响应一旦带上数据比如VIN的17字节整体就是20字节0x62 2字节DID 17字节数据根本不是8字节装得下的。这时候就要靠传输层协议ISO-TPISO 15765-2来处理分段。ISO-TP的逻辑很简单把应用层的长报文拆成一段段CAN帧再在接收端拼回来发送端遇到超过8字节的数据就要用多帧方式先发一个首帧再连续发若干连续帧接收端则用流控帧来协调发送节奏防止缓冲溢出。这里要区分四个角色帧类型标识作用单帧SF首字节高四位0x0报文总长≤7CAN时8-1时一帧搞定首帧FF首字节高四位0x1第2-3字节为总长度总长7时发出让接收端知道总长并准备接收连续帧CF首字节高四位0x2后续为序号首帧之后的数据块每帧带递增序号流控帧FC首字节高四位0x3接收端回复告诉发送端“可以发”“每次发几帧”“帧间隔多少”4.2 多帧传输里的时序细节以CAN 2.0标准帧数据场8字节为例假设我们要发送20字节的应用层报文ISO-TP多帧过程大致是发送端发首帧首字节0x10扩展长度20字节用两字节表示即字节2-3存0x0014第一帧剩下的6字节放应用层数据的前6个字节。接收端发流控帧通常0x30 0x0A 0x00意思是可以继续发最大连续帧数10帧间隔0ms具体值要看ECU实现。发送端发连续帧10x21接下来7字节数据。发送端发连续帧20x22剩下7字节数据。接收端把所有数据按顺序拼起来去掉ISO-TP头还原出0x62 F1 90 17字节VIN再交给应用层解析。这个过程中最容易出问题的是“帧间隔”。Flow Control里的STminSeparation Time minimum指定了连续帧之间的最小间隔有的ECU要求5ms、10ms甚至20ms。如果测试工具不遵守就会出现报文丢失、接收端缓冲区溢出表现为“响应超时”或“数据错乱”。我调试某个ECU时就是因为它要求连续帧间隔5ms而某第三方工具默认发0ms结果十条里有三条读不到完整VIN。这类问题在报文看板上还不太明显因为CAN数据都发出来了但ECU就是“没收齐”。4.3 大数据量DID怎么读更稳如果DID对应的数据量特别大比如几十上百字节的配置存档、故障冻结数据多帧传输的可靠性和效率就变得很关键。我的经验是优先让ECU端把大数据DID拆分到合理长度不要动辄一个DID几百字节。ISO-TP虽然在CAN-FD里能支持到4095字节但经典CAN的4095字节上限实际上会拆成几百帧通讯效率很低出错概率也高。如果必须读大块数据考虑分成多个逻辑DID每个DID承载一段语义模块。比如“系统配置A”“系统配置B”既方便读也方便诊断仪按需获取。确认接收端工具支持多帧拼接。大部分正规CAN工具都支持ISO-TP协议栈但一些裸串口转CAN的简易板卡需要自己实现拆包组包这种情况下读到半截就按“长度不符”处理。5. 会话、安全访问与时序三个最容易卡住22服务的隐形门5.1 会话状态决定了你能看到多少DIDUDS协议里有三种标准会话默认会话0x01Default Session、扩展会话0x03Extended Session、编程会话0x02Programming Session。大多数ECU开机后会停留在默认会话在这个状态下22服务往往只能读公共标识VIN、版本号。涉及内部参数、标定值、生产信息的DID默认会话根本不允许读。我遇到过这样一个场景某测试员在整车下线工位上要给ECU写入配置先想读一下ECU当前配置确认版本结果发22 F1 87回来一个7F 22 0x7E。他以为ECU没烧录好折腾半天。其实原因只是工具没有先发10 03切换到扩展会话。所以如果你的22请求被拒先试试按顺序走一遍10 03切扩展会话→ 如果需要再27 01/02安全解锁→ 再发22。5.2 安全访问0x27读个数据还要“过门禁”很多受保护的DID不仅需要扩展会话还需要安全访问解锁。0x27服务的流程是诊断仪发27 01带一个种子请求ECU返回种子Seed诊断仪用算法计算出密钥Key再发27 02带上KeyECU校验通过后后续一段时间内允许访问受保护DID。这里有个关键点安全访问的保护对象往往不是“整个22服务”而是“特定DID”。也就是说同一个22服务里有的DID不用解锁就能读有的必须解锁后才能读。ECU端实现时通常在DID表里给每个DID配一个“访问权限等级”0级公共、1级需要扩展会话、2级需要安全解锁以此类推。种子-密钥算法的安全等级直接影响破解难度但这不在本文展开。我只提醒两点其一不要随便把某个DID设成“安全解锁后任意读”容易被扫描攻击其二调试时如果总是0x33失败多半不是算法算错了而是种子有效期过了或者你用的Key是上个Session的要对同步机制做检查。5.3 P2/P2*时序为什么你的请求像在“射空气”UDS的时序参数也是很多问题的来源。ISO 14229定义了P2和P2*两个时间参数P2ECU从接收请求到开始响应允许的最长时间默认通常50ms。超过P2没响应诊断仪会开始等待P2*。P2*增强型等待时间ECU在需要更长时间处理请求比如硬件读取慢时可以在这个窗口内发一个待响应通知0x78告诉诊断仪“我在处理别急”。P2*默认可以到5000ms。22服务读取的数据如果是缓存好的常量通常几十毫秒内就能响应但如果DID背后是实时传感器采样、外部总线信号采集可能就需要打标、转换、组包几毫秒到几百毫秒都可能。一旦ECU端处理时间超过P2就必须发0x7F 22 0x78来维持传输否则诊断仪会判定超时直接报“通讯失败”。我看到不少自定义诊断工具把P2超时设成10ms、20ms读响应稍慢的DID时频频超时甚至归咎于“CAN总线质量差”。实际上先看工具配置里的P2参数再决定要不要优化ECU端处理逻辑才是正确的排错顺序。6. 实战复盘用22服务完整读一次VIN码6.1 发送链路搭建现在我们把整套流程串起来假设测试环境是一块通过CAN总线连接到PC的诊断卡目标ECU是某个模拟ECU节点也可以用真实ECU台架。我们要读VINDID定为0xF190。第一步确保通讯链路正常先发一条会话控制请求试试水发送02 10 03 00 00 00 00 00这是ISO-TP单帧02表示应用层数据长度2字节10 03是“切换到扩展会话”。这里注意CAN原始字节和UDS应用层字节之间隔了一层ISO-TP头工具如果已经内置ISO-TP协议栈你直接写10 03就行工具会自动加头如果是裸CAN收发那你自己得先组好ISO-TP帧再发。第二步确认会话切换成功后发22服务发送03 22 F1 90 00 00 00 0003代表后面应用层3个字节22 F1 90就是我们的请求。正常情况下你能收到类似接收10 14 62 F1 90 4C 53 56 4710是首帧标志14十六进制即十进制20表示后面应用层总长20字节接下来是62 F1 90开头说明响应正常。紧接着会有连续帧接收21 4C 45 54 30 30 30 31 接收22 32 33 34 35 36 37 38把所有ISO-TP头去掉按顺序拼接应用层数据62 F1 90 4C 53 56 47 4C 45 54 30 30 30 31 32 33 34 35 36 37 38去掉62 F1 90剩下的17字节就是VIN4C 53 56 47 4C 45 54 30 30 30 31 32 33 34 35 36 37 38十六进制按ASCII解码为字符串假设是LSVGLET00012345678。这个例子里的数据用的是虚构的编码仅作演示但整个拼包和解析流程和真实台架上一模一样。6.2 调试工具的选择与配置要点市面上常见的CAN分析工具基本都支持诊断发送和ISO-TP自动分包但我自己的经验是即便用带协议栈的工具也要把“显示原始CAN帧”这一层打开。原因在于自动解析有时候会“美化”掉ISO-TP头的细节比如流控帧丢了、连续帧序号重复等这些恰恰是排查多帧问题最需要的线索。工具选型上我习惯先在低成本的开源/评估板方案上验证基本帧收发逻辑再上商用的整车诊断工具做完整测试不要一上来就直接依赖高级封装否则底层出了问题你很那定位。另外诊断报文的地址格式不要搞错。物理寻址Physical Addressing一般用0x7E0/0x7E8或0x7DF广播和功能寻址Functional Addressing0x7DF在22服务里都有人用但功能寻址通常只用于不需要明确响应对象的场合比如广播“谁支持VIN读取请响应”。实际读ECU数据时最好用物理寻址避免多个ECU同时响应造成总线冲突。CAN ID和ISO-TP的N_PCI头要区分开来CAN ID是网络层的“门牌号”ISO-TP头是传输层的“包装箱”。很多初学者把这两个概念搞混把0x10当成CAN ID去设置结果根本发不出去。6.3 实测中常见的意外情况在我自己的一次测试中遇到过一个很典型的“假故障”读VIN时偶发失败抓包发现请求发出后ECU回了7F 22 78但工具直接把78当成了真的负响应显示“服务失败”。实际上0x78是“响应待定”ECU还在处理。这种误判对排查干扰极大。正确的做法是看到0x78后继续等待几毫秒或几十毫秒后ECU会补发真正的正响应。还有一次A同学调试某个ECU的22服务读取请求长度写错了把22 F1 90 AA发出去多了一个尾巴字节ECU直接回7F 22 13长度错误。很多人这时候会怀疑DID写错了其实0x13明确指向的是“报文长度格式不对”而不是DID本身无效。这时候回头检查请求字节数量比瞎猜DID高效得多。7. 排查22服务异常的完整思路按层拆解7.1 先看请求有没有送到ECU排查顺序我通常是从底向上先看CAN物理层和链路层再说应用层。如果你的诊断工具显示“发送完成”但ECU毫无反应你需要先确认终端电阻是否到位CAN总线必须有120欧终端否则信号质量差。CAN波特率是否一致最常见的是500k/250k但不少ECU是125k对不上直接全丢。CAN ID是否匹配物理寻址请求ID和响应ID都必须在诊断规范里核对。ISO-TP首字节是否被正确解释比如你发的是单帧但长度字段超过7就会因为非法格式被ECU抛弃。许多“22服务无响应”的问题其实在CAN层就已经死掉了。报文没到达ECU或者到达但ISO-TP组包不完整自然不会有应用层响应。7.2 再查NRC对应的“权限状态”如果ECU确实响应了只是回的是负响应那就从应用层状态去推7F 22 31先确认DID在规范里存在再确认当前会话是否允许。部分ECU在本会话禁止时会回0x31而不是0x7E。7F 22 0x33大概率是安全访问未解锁。按顺序执行0x27流程再回来读。7F 22 0x7E当前会话不支持切扩展会话或编程会话后再试。7F 22 0x13请求长度格式有问题回看自己发出去的字节数。如果ECU回的是7F 22 78记住这不是失败而是延后响应继续等确认P2*超时时间内会不会收到正响应。如果一直只有0x78没有后续那才是ECU端处理逻辑堵死了。7.3 拿到数据之后也未必万事大吉数据长度对不对、解析值合不合理也是排查重点。常见问题有拼接顺序错乱多帧接收时不要把首帧和连续帧的数据顺序倒过来拼。先首帧数据、再CF1、再CF2顺序是严格的。大小端理解反了比如车速DID定义是Motorola格式大端你按小端算数字会变成完全离谱的值。排查时和ECU标定值对一下立刻能定位。ASCII填充问题VIN长度不够时有些ECU用空格0x20填充有些用0x00填充。如果解析时只Trim掉空格遇到0x00就会变成带空字符的字符串。规范化解析要按规范来不要想当然。单位换算没做DID数据常常是原始AD值或百分比需要按公式换算成工程值。比如温度传感器数据0x012C对应十进制300换算系数0.1℃/bit实际就是30.0℃。直接拿300当温度用数据自然“离谱”。7.4 我对22服务测试工具链的整体建议最后给一点工具链层面的经验。一条成熟的22服务诊断链至少应该包含以下几部分支持ISO-TP协议栈的CAN接口工具并且能同时展示原始CAN帧和解析后应用层报文。可编辑的DID数据库把DID值、长度、单位、权限等级、换算公式、ASCII/整数格式都配置进去这样测试时自动解析不用每次手工查表。自动化脚本能力能写循环读、条件读、时序测试比如在解锁前后分别读某个DID验证权限或者每隔10ms连续读10次看响应稳定性。日志回溯能力抓取完整总线报文方便事后复盘“到底是请求没发对还是响应没组对”。理想状态下你敲一条22 F1 90工具里立即就能看到完整的VIN。如果这个链路还处于需要手工拼接十六进制数组、手工查规范换算的状态那说明测试侧的工具链还有优化空间这也是提高诊断效率最直接的方向。从我接触过的项目来看认真把22服务吃透的人再去折腾0x2E、0x23、0x31都会顺很多因为它打通的是“DID映射—会话权限—ISO-TP传输—数据格式”这一整条诊断主链路。以后你拿到任何一份新ECU的诊断规范先找它的DID清单看看哪些是默认会话可读的公共数据哪些是解锁后才开放的内部数据心里基本就有数了哪些场景能快速上手测试哪些场景需要先陪ECU走一遍“聊天流程”。这就是22服务教给你的第一课。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑