1. 线还插着0x31例程却不认你了先分清物理连接和诊断会话在UDS诊断协议的项目现场有一种现象最磨人CAN线和诊断设备都连得好好的总线上一片绿色万用表量下来通路也正常可刚才还能跑起来的0x31例程控制服务再发一次就只回一个NRC。干这行时间长了就会发现这类“线还插着操作却不能用”的问题十有八九不是线缆断了、接触不良而是ECU这一侧的诊断会话状态已经悄悄变了。今天就把这个坑拆开讲清楚顺便把0x31例程控制服务的前因后果捋一遍。标题里这个“#31”我倾向于把它理解成两层意思第一层是服务ID 0x31也就是UDS里非常常用的例程控制服务第二层是字面上的“31号线还插着”提醒你就算物理连接看起来一切正常UDS诊断也不能只信物理层。UDS诊断原理上其实就是一个“请求—响应—状态管理”的闭环ECU内部有一个会话状态机这个状态机决定了你现在能调用哪些服务、哪些服务会被拒绝。线插着只代表电平信号能跑通不代表诊断会话还停留在你上次离开的位置。很多朋友一遇到“刚才还能用现在不能用”的问题第一反应就是查线、换工具、重启软件结果折腾半天发现硬件一点问题没有。真正的问题往往在ECU的逻辑层默认会话、扩展会话、编程会话之间的切换S3Server超时定时器安全访问的解锁状态这些才是决定“操作能不能用”的关键。这篇尽量控制在三分钟能读完的体量但信息密度会比较高做过诊断或者正在写UDS自动化测试脚本的朋友可以直接跳到第三节和第五节的速查表。2. 三个状态陷阱让0x31例程突然失效2.1 S3Server超时汽车CAN UDS诊断超时里最隐蔽的元凶先说一个最常见的场景。你用0x10 03进入了扩展会话0x27解锁也做完了0x31例程跑得好好的。然后你停下来看了几行日志、调了一下面板参数、或者接了电话再回头发0x31结果NRC直接砸过来。这个时候先别骂ECU大概率是S3Server定时器超时了。S3Server是会话保持定时器的名字ISO 14229-1里它的默认值通常是5000ms也就是5秒。ECU只要在5秒内没有收到任何诊断请求就会自动从当前会话退回默认会话。这里说的“任何诊断请求”包括0x3E TesterPresent也包括其他任何合法的诊断报文。所以当你停下手里的操作超过5秒会话就已经悄悄掉了但总线上的物理连接是完好的软件界面也不会提示你“会话已丢失”于是下一次0x31请求就会撞上一扇关着的门。这个机制用生活化一点的说法门禁卡有效期过了门本身没坏电也通着但你就是刷不进去。ECU的默认会话就像门禁系统的初始状态权限最少很多例程压根不开放。回到默认会话之后你发0x31如果RID本身要求扩展会话或编程会话常见的反馈就是NRC 0x7F服务不支持或者NRC 0x22条件不满足。这两种响应都提示你会话状态不对。我在实际项目里见过最典型的一次是台架测试时软件界面显示“已连接”但上一个诊断请求和下一个诊断请求之间的时间戳差了将近9秒ECU早就自己退回默认会话了。问题不在线不在工具就在于S3Server这个看不见的定时器。2.2 安全访问状态被清空0x31收到0x33或0x22第二个陷阱比S3Server还要隐蔽一点安全访问状态被清空。很多0x31例程是受安全保护的比如擦除Flash、校验CRC、复位某些外设这些RID在执行之前都要求先通过0x27服务完成SeedKey解锁。解锁之后ECU会记住“当前会话已经通过安全验证”但这记忆是有条件的。按ISO 14229的语义安全访问状态是跟着会话走的。一旦ECU退回默认会话或者发生了一次ECU复位或者你主动用0x10 01切回默认会话解锁状态都会被清零。就算你马上重新进入扩展会话解锁状态也不会自动恢复必须重新走一遍0x27的SeedKey流程。这个坑在调试的时候特别容易让人抓狂。因为你可能开着CANoe的诊断控制窗口会话显示为“扩展会话”看起来一切正常但ECU内部的安全状态其实已经被上次的会话切换冲掉了。此时发0x31如果例程要求安全解锁ECU会返回NRC 0x33安全访问被拒绝或者在某些实现里返回0x22条件不满足。单看NRC只能判断“它不让我跑”查半天都想不到是解锁状态丢了。我自己的习惯是只要诊断请求的间隔出现过Gap或者发生过任何一次会话切换就默认“安全状态不可靠”在发0x31之前重新执行一遍0x27解锁。多花几十毫秒但能省下几个小时排查NRC的时间。2.3 例程还在跑重复启动Routine的时序问题第三个陷阱和0x31服务本身的时序有关。0x31例程控制服务有三个子功能0x01启动例程、0x02停止例程、0x03读取例程结果。某些例程是长时执行的比如擦除整个Flash区域可能要几百毫秒甚至更久某些例程是周期性执行的启动一次之后就会按周期跑还有一些例程执行期间不允许被再次启动。如果你在例程还没跑完的时候又发了一次0x31 01RID一样子功能一样ECU可能返回NRC 0x22条件不满足。注意这里的“没跑完”不是指你没有收到响应而是指例程的实际执行状态还停留在“运行中”。0x31的启动响应0x71只是告诉诊断仪“我已经开始执行这个例程了”不代表这事已经办完了。你得用0x31 03去轮询例程结果确认状态位变成“完成”之后才能安全地再次启动同ID的例程。这个陷阱在自动化测试里尤其突出。脚本里如果只发了0x31 01就往下走下一个用例又对同一个RID发0x31 01时序上还没跨过例程执行窗口结果就是一片NRC。后面第四节会展开讲自动化测试时序怎么设计这里先记住一个结论RoutineControl是有执行状态机的不是简单的“发出去就有结果”。3. 一次排查实录3分钟定位汽车CAN UDS诊断超时根因3.1 第一步解析NRC判断是协议层拒绝还是物理层超时真到了现场不要急着拔线重插。拿起抓包日志第一步先看ECU到底有没有回响应。如果请求发出去之后总线上一片死寂等完P2Server又等P2StarServer最后工具提示“超时”那是物理层或者传输层的问题包括CAN收发器故障、波特率不匹配、终端电阻异常等等。这个时候才轮到查线、查连接、查接插件。但如果请求发出去之后ECU快速回了一条0x7F开头的报文那问题就在协议层。0x7F是UDS否定响应的固定前缀结构是第一个字节0x7F第二个字节是你请求的服务ID第三个字节是NRC。比如你发0x31 01ECU回7F 31 22意思就是“服务0x31的请求被拒绝原因是0x22条件不满足”。这里有一个新手必晕的点NRC 0x31和SID 0x31重名。ECU如果回7F 31 31第一个31是服务ID第二个31是NRC码“请求超出范围”意思是RID或者参数不合法不是说ECU不支持0x31这整个服务。别被两个31绕进去这个误解我见过不止一次。收到NRC之后具体往哪个方向查看第三节第五节的速查表就行。以最常见的0x22为例优先怀疑三点当前会话对不对、安全解锁有没有丢、例程是不是还在跑。这三条全都排查完再去看其他冷门前置条件。3.2 第二步用带时间戳的抓包验证S3Server超时要验证是不是S3Server超时方法非常简单把抓包工具里的时间戳列出来对比“上一次ECU收到诊断请求”和“这一次发出0x31请求”之间的间隔。间隔超过S3Server配置值基本就能定罪。下面是一段典型的抓包片段我用CANoe的Trace窗口导出后简化成这个样子10:00:01.101 7E0 02 10 03 00 00 00 00 00 # 请求进入扩展会话 10:00:01.124 7E8 08 50 03 00 32 13 88 13 88 # 响应返回P2/P2*/S3参数 10:00:01.158 7E0 02 27 01 00 00 00 00 00 # 请求seed 10:00:01.174 7E8 06 67 01 12 34 56 78 00 # 响应seed 10:00:01.190 7E0 06 27 02 9A 8F 3B 1D 00 # 发送key 10:00:01.205 7E8 02 67 02 00 00 00 00 00 # 解锁成功 10:00:01.220 7E0 05 31 01 FF 00 00 00 00 # 启动Routine ID 0xFF00 10:00:01.240 7E8 03 71 01 FF 00 00 00 00 # 例程启动成功 ... 10:00:09.988 7E0 05 31 01 FF 00 00 00 00 # 隔了很久再次启动 10:00:10.005 7E8 03 7F 31 22 00 00 00 00 # 返回NRC 0x22注意看两个关键报文的时间戳10:00:01.240 和 10:00:09.988中间隔了差不多8.7秒。ECU最后一次收到诊断请求之后超过5秒没有再收到任何东西S3Server触发会话退回默认。而0xFF00这个RID在默认会话下不做安全解锁和会话要求条件不满足所以NRC 0x22顺理成章。顺便说一句ECU响应0x10 03时返回的会话参数记录里P2Server、P2StarServer、S3Server的值都能直接读出来。上面示例里08 50 03 00 32 13 88 13 88后面六个字节就是这三个定时器的值00 32是50ms13 88是5000ms。不同OEM可能改过这些参数所以排查前最好先看ECU实际下发的配置不要想当然认为S3Server一定是5秒。3.3 第三步按正确顺序恢复会话和安全解锁确认是S3Server超时之后恢复操作要按UDS的规矩来顺序错了还是会碰壁。最稳的一套流程是重新进入扩展会话、重新安全解锁、再启动例程、再轮询结果。所谓最稳就是把前置条件全刷新一遍# 伪代码一个稳定的RoutineStart流程 send(0x10, 0x03) # 重新进入扩展会话 wait_response() send(0x27, 0x01) # 请求seed seed wait_response() key generate_key(seed) # 具体SeedKey算法以ECU文档为准 send(0x27, 0x02, key) wait_response() send(0x31, 0x01, 0xFF00) # 启动目标Routine wait_response() send(0x31, 0x03, 0xFF00) # 读取例程结果为什么每一步都要等待响应再发下一条因为UDS请求是按顺序处理的诊断仪侧虽然可以连续发送但ECU侧会按接收顺序处理而且有些服务在ECU忙的时候会触发P2Star扩展等待。诊断仪如果不等响应就连发收到的NRC很可能不是逻辑问题而是纯粹的时序碰撞。这也解释了很多测试脚本为什么跑着跑着就开始报错——不是写错了服务ID是节奏不对。还有个细节值得强调重新进入扩展会话之后之前解锁的钥匙已经作废了必须把0x27 01/02整套流程重新走一遍。如果种子算法里包含随机数每次seed都不一样不要拿上一次的key硬套否则ECU会认为key错误解锁失败。4. 手动测试与自动化测试都在这里踩坑4.1 自动化测试脚本没保活UDS测试报告就会一片黄如果你用UDS自动化测试框架批量跑用例想输出一份干净的测试报告S3Server超时就是头号“脏数据”来源。很多测试用例在设计时压根没考虑会话保活上一个用例结束之后停了6秒才进入下一个用例或者用例内部有一段sleep等待比如等待上电稳定、等待继电器动作一停就是七八秒。ECU早就退回默认会话了脚本还按“扩展会话已激活”的状态往下跑后面一连串0x31和0x27的结果全是NRC。这类问题跑到测试报告里表现就是成片的黄色NRC条目而且不是偶发是每次跑到固定位置都必现。不懂的人以为ECU有bug其实脚本在会话管理上就是裸奔的。解决方案不复杂核心思路就是“保活”。在每个用例的入口强制走一遍会话激活在一切可能超过S3Server的等待位置之前插入TesterPresent也就是0x3E 00。TesterPresent的发送间隔建议设成2秒左右小于默认的5秒S3Server就行。注意0x3E 00会触发ECU回一条0x7E 00响应这条响应同样会刷新会话定时器。如果你不想让响应干扰测试日志也可以发0x3E 80这个子功能表示“不需要响应”但ECU收到后依然会刷新S3Server。我实际测试过的ECU都认这个逻辑可以放心用。自动化测试框架里还容易忽略的一点是不要在测试报告里只记录NRC码最好把解析后的文本也打出来。比如0x22直接显示“conditionsNotCorrect”0x33显示“securityAccessDenied”0x7F服务不支持单独归类。你写报告的时候会发现NRC码分布一眼就能定位是会话问题、安全问题还是参数问题省得每一条都翻协议标准去查。4.2 手动操作和面板测试同样会被会话超时坑不要以为只有自动化脚本才会踩S3Server手动测试踩得也不少。用CANoe的Diagnostic Console发送窗口或者用自己写的面板按钮点一下“Start Routine”之后停了20秒再点另一个按钮ECU早就掉回默认会话了。如果工具界面没有隐藏的TesterPresent周期发送那看起来就是“刚才还能用”的灵异现象。我的建议是在手动测试环境里直接开一个周期发送TesterPresent的定时任务500ms或者1000ms发一次都行让ECU始终停留在你想要的会话。这个和自动化的保活思路一样只是实现工具不同。有些诊断仪自带“会话保持”开关有些需要自己在CAPL或者Python脚本里循环发送不管哪种方式先确认它在工作再去谈后面的Routine测试。手动测试还有一个人为因素等待时间不可控。你翻文档、看波形、接电话时间就过去了。所以我的习惯是所有关键操作前面都设一个“重新激活会话”的习惯动作宁可多发一条0x10 03也不要赌ECU的会话还在。这个习惯在写测试用例、写自动化脚本、手动刷写标定流程中全都适用。5. NRC速查表与三条实战经验5.1 NRC速查表看到哪个码就往哪个方向查把常用NRC整理成一张表贴在工位上比翻标准快得多。这里针对0x31例程控制场景列几个最常碰到的NRC含义最可能的场景优先检查0x7F服务不支持当前会话不支持该服务或ECU未实现0x31当前诊断会话0x22条件不满足没解锁、例程还在跑、前置条件缺失重新解锁、查例程状态0x33安全访问被拒绝例程受保护但未解锁或解锁状态已丢失重新走0x27流程0x31请求超出范围RID写错、子功能不支持、参数越界查诊断调查表里的RID0x24请求序列错误前序服务未完成顺序不对检查服务调用顺序0x10服务暂时不可用ECU正忙于其他处理或会话切换中等待P2Star后重发看到0x22先别急着怪ECU逻辑有问题八成是会话或者安全状态的问题。看到0x33直接去查解锁流程别在RID上浪费时间。看到0x31反而要去查诊断调查表和数据字典看看RID是不是压根不在支持列表里。5.2 三条实战经验能省下半天排查时间经验一先看时间戳再看NRC。遇到任何“刚才还能用”的问题第一件事就是打开带时间戳的抓包日志看上一次诊断请求和这一次失败请求之间的时间间隔。如果间隔明显大于S3Server不要再查线查软件了会话超时实锤。经验二如果工具不支持直接读当前会话可以用一个已知只在扩展会话下有效的RID去探路。发一条0x31 01如果ECU回0x22或者0x7F说明当前不在扩展会话如果回0x71说明会话还在。用服务本身的响应来判断会话状态比猜要靠谱得多。经验三物理层问题和逻辑层问题的表象不一样。物理层问题通常是“发出去没有响应”最后工具报超时逻辑层问题通常是“快速回了NRC”。如果收到了NRC物理连接大概率没问题至少在CAN收发这个层面是通的。这个区分能帮你少走很多弯路。有一次我在现场排查同事坚持认为是线束干扰导致0x31失败结果一查抓包日志ECU在100ms内就回了NRC这显然不是干扰的表现。后来发现问题就出在测试用例的前一步做了ECU复位复位之后安全状态清零Routine自然跑不起来。线束没动过一下。最后再分享一个小习惯这个习惯我现在已经固化到所有项目里了凡是涉及0x31例程控制的测试在脚本开头固定写一段“进会话、解锁、确认状态”的初始化代码在脚本任何可能超过3秒的空档里插入TesterPresent。这套动作看起来有点啰嗦但对多ECU、多配置的车型项目来说能过滤掉一大批虚假失败让你输出的测试报告真的可以拿去分析问题而不是先花一小时洗数据。也建议你把0x31的所有子功能、RID清单、前置条件整理成一张自己的速查表踩坑的时候翻自己的表比什么都快。
阅读完成 · 觉得有帮助?