1. 0x28服务到底在干什么
做过几年车载诊断开发的工程师,对UDS协议栈里的0x28服务应该都不陌生。这个服务的中文名是CommunicationControl,翻译过来就是通信控制。它的核心作用就一句话:由诊断仪主动控制ECU内部通信报文的收发状态。
很多刚入行的朋友会把0x28和0x10(会话切换)、0x11(ECU复位)混在一起,觉得都是诊断流程里的“控制类服务”。实际上它们的侧重点完全不同。0x10和0x11管的是ECU的工作模式,而0x28管的是ECU的“对外通信权限”——也就是ECU什么时候允许往外发应用报文,什么时候允许接收网络管理报文,什么时候彻底闭嘴。
我举个例子你就明白了。假设你正在给一辆车做ECU刷写,刷写过程中如果ECU还在正常往外发CAN报文,比如车速、转速、扭矩这些,总线上其他节点收到后可能会误以为这个ECU工作异常,从而报出一堆故障码,甚至触发某些安全策略。更麻烦的是,刷写过程中Flash擦写操作本身对时序要求很严格,外部CAN中断频繁打断也会增加失败概率。这时候诊断仪发一个0x28服务,把ECU的应用报文和网络管理报文都关掉,让ECU进入“静默刷写”状态,整个刷写过程就干净利落多了。
0x28服务的适用场景远不止刷写。比如下线检测(EOL)时,产线设备需要独占ECU的通信资源,防止应用报文干扰检测结果;又比如某些特殊的故障复现场景,需要临时屏蔽掉某类报文来定位问题。可以说,只要你想让ECU在某个时间段内闭嘴或者张嘴,0x28就是标准答案。
这个服务的请求格式非常简洁,一共两个字节:子功能参数(SubFunction)+ 通信类型参数(CommunicationType)。诊断仪发过去,ECU处理成功后回一个肯定响应。但就是这么简单的格式,背后牵扯到的状态管理、时序要求、安全机制,远比表面看起来复杂。这篇文章我就把自己在实际项目中踩过的坑、总结的经验,完整地梳理一遍。
2. 报文格式与参数定义
2.1 请求报文结构
0x28服务的请求报文遵循UDS协议标准的单帧格式,应用层数据部分一共3个字节:
| Byte 0 | Byte 1 | Byte 2 | | 0x28 | ControlType | CommunicationType |Byte 0是服务ID,固定为0x28。Byte 1是控制类型,Byte 2是通信类型。在CAN物理层上,这3个字节会加上PCI(协议控制信息)打包成一帧,如果是单帧,PCI是0x03,表示后面有3个数据字节。
这里要特别提醒一点:UDS的寻址方式分为物理寻址和功能寻址。0x28服务从协议规范上看是允许功能寻址的,但实际工程中我强烈建议只用物理寻址。原因后面在讲NRC的时候会细说。
2.2 控制类型(ControlType)详解
控制类型占1个字节,ISO 14229-1标准里定义了3个值:
| 控制类型值 | 名称 | 定义 |
|---|---|---|
| 0x00 | normalCommunication | 恢复为正常通信状态 |
| 0x01 | disableCommunication | 禁止通信 |
| 0x02 | enableCommunication | 使能通信 |
看到这三个定义,很多人会有一个疑问:0x00和0x02有什么区别?都是让ECU恢复通信,为什么要有两个值?这个问题的答案恰恰是0x28服务最核心的设计逻辑。
我的理解是这样的:0x00是“复位到默认状态”,0x02是“显式使能”。在ISO 14229的语境下,normalCommunication意味着ECU回到它上电初始化后的那种通信行为——应用报文按照自身的调度逻辑正常发送,网络管理报文也按网络管理策略正常参与。而enableCommunication更强调“我允许你通信”,在行为表现上两者可能类似,但语义侧重不同。
关键的区别在于叠加会话状态和Security Access状态来看。如果你先执行了0x28 0x01(禁止通信),再执行0x28 0x00(恢复normal),ECU会回到正常通信。但如果ECU本身处于编程会话(Programming Session,0x10 0x02)下,应用报文本来就是停发的,这时候0x28 0x00是否能把应用报文拉起来?不同厂商的实现策略是有差异的。有些ECU严格按照“normal状态即默认会话下的normal通信行为”来理解,编程会话下应用报文始终不发;有些ECU则把0x00理解成“恢复该会话模式下本应存在的通信”。这个差异在跨品牌诊断适配时经常踩坑。
另外,ISO 14229-1:2020版本在0x28服务上增加了一个新的子功能:0x03,用于使能增强型通信(enableEnhancedCommunication)。这个功能在传统的CAN ECU上用得不多,但在以太网诊断(DoIP)和基于SOME/IP的服务发现场景下开始出现。它的设计意图是让ECU在正常通信被禁止的情况下,仍然保留某些特殊报文的收发能力,比如诊断报文本身。这个特性在处理“诊断通信与应用通信共用物理通道”的ECU上非常有用。
2.3 通信类型(CommunicationType)的位定义
通信类型参数是0x28服务里信息量最大的一个字节,它的每一位都代表一类通信。标准定义如下:
| Bit | 含义 |
|---|---|
| Bit 0 | 应用报文(Application报文)的收发 |
| Bit 1 | 网络管理报文(Network Management报文)的收发 |
| Bit 2 | 不使用(标准中保留) |
| Bit 5 | 不使用(标准中保留) |
| Bit 6 | 特定应用(Application Specific)报文的收发 |
| Bit 7 | 不使用 |
这里要特别说明一下Bit 0和Bit 6的区别。Bit 0的应用报文指的是ECU周期性发送的、包含实际物理信号(车速、转速、温度、状态等)的CAN报文,这些报文通常由一个叫“应用报文调度表”的机制来管理,每一帧报文分配一个固定的周期,比如10ms、20ms、100ms不等。而Bit 6的“特定应用”是2020版标准新增的,它允许诊断仪精确控制某一条特定的应用报文,而不是一刀切地把所有应用报文全关了。这个功能在报文级调试时非常好用,比如你想单独屏蔽掉某一帧引起总线冲突的报文,而不影响其他报文的正常发送。
通信类型最常用的组合值是0x01(只关应用报文)、0x02(只关网络管理报文)、0x03(应用和网络管理都关)。你可能会问,为什么标准里不把通信类型设计成枚举值而要用位掩码?我的理解是位掩码的可扩展性更好。未来如果新增一类通信,只需要占用一个新的保留位,而不用修改协议结构。另外,位掩码也允许诊断仪在一个请求里同时操作多个通信类型,减少交互次数。
2.4 肯定响应格式
当ECU成功处理0x28请求后,返回的肯定响应包含2个字节:
| Byte 0 | Byte 1 | | 0x68 | ControlType |响应中的ControlType与请求中的ControlType保持一致,用于让诊断仪确认ECU执行的是哪种子功能。这一点很多开发者会忽视,写解析代码时直接跳过响应里的ControlType,只判断服务ID。但实际上这是标准的闭环校验机制,建议解析时一定要比对响应中的ControlType与请求值是否一致,防止ECU因为某种原因处理了错误的子功能。
3. 子功能与通信类型的组合逻辑
0x28服务的核心理解难点,在于“控制类型”和“通信类型”是一个二维矩阵,不同组合会产生完全不同的效果。我把常见的组合整理成一张表:
| ControlType | CommunicationType | 效果 |
|---|---|---|
| 0x01(禁止) | 0x01(应用) | 停止所有应用报文发送,NM报文正常 |
| 0x01(禁止) | 0x02(网络管理) | 停止位置网络管理报文,应用报文正常 |
| 0x01(禁止) | 0x03(应用+网络管理) | 所有通信完全静默,仅诊断报文可通 |
| 0x00(恢复正常) | 0x01(应用) | 恢复应用报文发送 |
| 0x00(恢复正常) | 0x03(应用+网络管理) | 恢复所有通信 |
| 0x02(使能) | 0x01(应用) | 显式使能应用报文发送 |
| 0x03(使能增强) | 0x00(无特定类型) | 使能增强型通信模式 |
实际项目中,最常用的组合是0x28 0x01 0x03,也就是全部通信禁止,以及0x28 0x00 0x03,全部恢复正常。前者用于刷写前置阶段,后者用于刷写完成后恢复ECU通信。
还有一个组合容易被忽略:0x28 0x01 0x02,只禁止网络管理报文。这个组合在真实项目中扮演着重要角色。我举一个很典型的场景:整车在行驶过程中,如果某个ECU因为软件故障需要在线升级,但此时总线上的网络管理报文还在正常收发,其他ECU会认为这个节点状态正常,不会触发网络休眠。如果你在刷写过程中把NM报文停掉,其他ECU经过一段时间后会发现这个节点“失联”,从而触发网络管理超时错误,报故障码。这时候你可能会觉得,那我不停NM不就完事了?但问题是,如果不停NM,ECU可能会因为收到网络管理请求报文而触发本地唤醒事件,打断刷写流程。这个矛盾怎么解决?标准定义里其实留了一个口子:当ECU进入编程会话后,即使NM报文被禁止,ECU仍然会响应网络管理报文(取决于具体网络管理策略),但不会主动发送NM报文。这样就既保证了其他节点能感知到它的存在,又避免了主动通信对刷写过程的干扰。
再来看0x28 0x02(enableCommunication)的使用场景。这个子功能在刷写完成后的恢复阶段很常用。有些ECU的实现中,0x28 0x00只是把内部的通信标志位复位,但不会真正唤醒报文调度器,而0x28 0x02会强制唤醒调度器开始发报文。如果刷写完成后你发现ECU已经返回了肯定响应,但总线上迟迟看不到应用报文,很可能是ECU对0x00和0x02的实现差异导致的。这时候手动再发一帧0x28 0x02 0x03,往往能解决问题。
关于位掩码的作用,我再展开说一点。设计0x28服务时,ECU内部通常会维护一个“通信状态寄存器”,每一位对应一类通信的当前状态:
Bit 0: Application Communication Enabled Bit 1: Network Management Communication Enabled Bit 6: Specific Application Communication Enabled当收到0x28请求时,ECU根据ControlType把相应的位置1或清0。但这个操作不是简单的赋值——它需要同时考虑当前会话状态、安全状态、IO状态等其他因素。后面讲实现方案的时候我会详细展开。
4. 0x28与诊断流程的配合
4.1 刷写流程中的关键位置
0x28服务在整个UDS刷写流程中处于“承上启下”的位置。一个标准的刷写流程(以UDS on CAN为例)大致是:
1. 扩展会话(0x10 0x03) 2. 安全访问解锁(0x27 0x01/0x02) 3. 通信控制禁止(0x28 0x01 0x03) 4. 例程控制(0x31):预编程条件检查 5. 写入指纹信息(0x2E) 6. 请求下载(0x34) 7. 传输数据(0x36)循环 8. 请求退出传输(0x37) 9. 例程控制(0x31):编程完整性校验 10. ECU复位(0x11 0x01)0x28在这个流程里的作用有两层。第一层是刷写前的总线静默,这个很好理解,防止刷写过程中应用报文和诊断报文打架。第二层是刷写完成后的通信恢复。注意,步骤10的ECU复位后,ECU会回到默认会话,通信状态也会随之恢复为normalCommunication。也就是说,只要复位成功,ECU的通信自然就恢复了,不需要再发0x28 0x00。但有些刷写工具为了保证可靠性,复位后还会发一帧0x28 0x00 0x03作为兜底,防止ECU在复位过程中状态机没有正确复位。这里有个细节:复位后ECU回到默认会话,而0x28服务只能在非默认会话下执行。如果你复位后立刻发0x28 0x00,ECU会回NRC 0x7F(服务不支持当前会话),因为默认会话下0x28服务本来就不支持。正确的兜底逻辑应该是:先发0x10 0x03切到扩展会话,再发0x28 0x00。
我在第一版刷写工具里就犯过这个错误。刷写流程代码是这么写的:0x11复位 -> 延时500ms -> 0x28 0x00 0x03。结果每次刷完都报错,但看总线日志ECU的通信其实已经恢复了,就是多了一个NRC 0x7F。后来排查了很久才发现是会话状态的问题。从那以后我写刷写工具都会有一个铁律:凡是涉及会话状态的服务交互,必须先确认当前会话再发下一个请求。
4.2 与网络管理报文的交互
上一节讲了刷写场景,这里把网络管理报文再单独拎出来讲一下。在AUTOSAR网络管理(CAN NM)协议栈下,ECU的网络管理报文分为被动模式和主动模式。ECU处于被动模式时,只监听NM报文、不主动发送;处于主动模式时,周期发送NM报文,参与网络管理状态机。
0x28服务对NM报文的控制,在不同ECU上的实现差异非常大。有些ECU直接硬切:0x28禁止NM报文后,网络管理状态机冻结,NM报文立即停发。有些ECU则采用软切换:0x28禁止NM报文后,ECU内部网络管理状态机仍然运行,但在发送路径上加了一个开关,NM报文在出口处被丢弃。这两种实现方式在行为表现上没有本质差异,但在状态机内部的数据一致性上有区别。硬切方式可能会导致网络管理状态机感知到异常(比如发送队列堆积),软切换方式则避免了这个问题。
从诊断仪的角度看,在发送0x28 0x01 0x02(禁止NM)之后,如果需要恢复,一定要给ECU留出足够的网络管理状态机恢复时间。我的经验值是至少留200ms,最好500ms。因为网络管理状态机通常有定时器(如NM_TimeOut),如果恢复得太快,状态机可能还没有从“被动模式”切换回“主动模式”,NM报文依然不会发。这种情况在总线日志上看起来就是:诊断仪发了0x28 0x00 0x02,ECU回了肯定响应,但NM报文过了2秒才出现。很多工程师会误认为是ECU执行慢,其实是网络管理状态机的恢复周期在作怪。
4.3 与DTC状态的关系
0x28服务禁止通信后,DTC状态位的处理是另一个容易被忽略的点。当ECU把应用报文停了,某些传感器信号不再更新,可能导致信号无效或超时。如果ECU的DTC监控模块还在运行,它会因为“信号丢失”而报故障码,从而造成误诊断。
ISO 14229对这个问题没有明确的强制规定,但主流的AUTOSAR实现会在0x28禁止通信后,自动暂停与通信相关的DTC监控逻辑。具体来说,DTC状态位中与“确认故障”相关的位不会被置位,只有与“待处理故障”相关的位会记录。这样做的好处是,刷写完成后恢复通信,DTC状态不会因为刷写过程本身而产生误报。
诊断仪端如果有DTC读取的习惯(很多产线流程刷写后会自动读DTC),要注意区分正常故障码和“因通信禁止而产生的临时故障码”。临时故障码在通信恢复并运行几个循环后会自动清除,不需要人为介入。
5. 实际操作中的关键参数与注意事项
5.1 子功能中安全访问的前提条件
0x28服务是否需要安全访问(Security Access)解锁?这个问题在ISO标准里没有强制规定,属于ECU设计者的自由裁量权。但从行业实践看,绝大多数ECU对0x28 0x01(禁止通信)都要求安全访问解锁,而对0x28 0x00(恢复正常)不做要求。
这个设计逻辑很好理解:禁止通信是一个“危险”操作,如果任意诊断仪都能把ECU的通信关掉,那总线上的恶意节点可以轻松地让一个ECU“失联”,这可能引发安全问题。想象一下,如果你能通过OBD口向车辆的ABS ECU发送一个0x28 0x01 0x03,把ABS的全部通信都关掉,那车辆的安全制动功能就瘫痪了。所以,禁止通信必须要有安全访问这道门槛。
但在项目实践中,我发现有些ECU实现得不彻底:0x28 0x01要求安全访问,但0x28 0x00不要求。这看似问题不大,其实存在逻辑漏洞——攻击者可以先用0x28 0x00“重置”通信状态,再发0x28 0x01。如果ECU在0x28 0x00时把安全访问状态也重置了,那还好;但如果0x28 0x00不重置安全状态,攻击者就可以绕过安全访问限制。所以,如果是我来设计ECU,我会把0x28服务所有子功能都挂到安全访问后面。
还有一个容易踩的坑:安全访问超时。很多ECU在安全解锁后会启动一个超时定时器(通常约5到10秒),超时后安全状态自动失效。如果你在解锁后没有及时发送0x28请求,可能等真正发的时候已经超出安全窗口,ECU会回NRC 0x33(安全访问被拒绝)。这个问题的排查方法很简单:看时序日志,确认0x27解锁响应到0x28请求之间的间隔是否在安全窗口内。如果是,检查ECU的安全窗口配置是否合理,或者调整工具的发送时序。
5.2 会话模式要求与NRC处理
0x28服务只能在非默认会话(通常指扩展会话或编程会话)下执行,默认会话下会回NRC 0x7F(服务不支持)或0x7E(子功能不支持),具体看ECU实现。
这里要特别注意一个细节:同一个NRC在不同场景下的含义不同。比如NRC 0x12(子功能不支持),可能是ControlType不支持,也可能是CommunicationType的某些位不受支持。排查时需要结合发出去的具体字节来逐一定位。
我在调试中总结的NRC含义速查表:
| NRC | 含义(0x28服务上下文下) | 排查方向 |
|---|---|---|
| 0x12 | 子功能不支持 | 检查ControlType是否在0x00-0x03范围内,且ECU支持 |
| 0x13 | 报文长度或格式错误 | 检查请求是否恰好3个字节(含服务ID),CommunicationType是否为合法值 |
| 0x22 | 条件不满足 | 检查是否处于正确的会话模式、安全访问状态、生命周期状态等 |
| 0x31 | 请求超出范围 | 检查CommunicationType的位定义,ECU可能不支持Bit6 |
| 0x33 | 安全访问被拒绝 | 检查是否已解锁,安全窗口是否超时 |
| 0x7F | 服务不支持(当前会话下) | 切到扩展/编程会话后重试 |
| 0x7E | 子功能不支持(当前会话下) | 当前会话下该子功能不可用,检查会话切换 |
0x22(conditionsNotCorrect)是0x28服务里最常出现的NRC,也是最难排查的一个。因为它背后可能的原因太多了:ECU没有完全初始化完成、底层报文调度器还没有启动、某些故障状态下禁止通信被锁定、正在执行其他例程等等。我的排查建议是:先看ECU的日志,确认条件不满足的具体模块是什么;如果是通用协议栈,检查协议栈的API返回值;如果是问题定位不明确,先做一个“基本功能验证”——在ECU刚上电、处于扩展会话、安全解锁、无其他任务的状态下,只发0x28 0x01 0x03,看是否成功。如果基本场景都失败,那基本可以确定是ECU应用层的问题;如果基本场景成功,说明问题出在前置条件上。
5.3 时序要求:请求间隔与响应超时
0x28服务对时序的敏感度比一般服务更高,尤其是禁止通信后ECU内部的模块状态切换需要时间。经验值如下:
- 0x28 0x01(禁止):ECU收到请求后,需要在10ms到50ms内完成通信停止。这个过程中,ECU可能会把当前正在发送的报文发完,再停止发送队列。诊断仪不能因为超过P2默认值(50ms)就认为超时。
- 0x28 0x00/0x02(恢复):ECU恢复通信后,报文调度器启动需要时间。诊断仪看到肯定响应后,不要立刻检查总线上的报文,建议等待100ms以上再做报文收发验证。
如果你的诊断仪使用的是P2=50ms、P2*=5000ms的标准超时配置,0x28服务的响应一般不会超时。但要注意的是,在一些资源受限的ECU上,0x28禁止通信后,如果ECU正在执行Flash擦写等耗时操作,响应可能超过P2,进入P2的等待阶段。诊断仪一定要正确处理P2的扩展等待,不能一超时就把整个诊断会话终止。
5.4 功能寻址的禁用问题
前面提到0x28服务要慎用功能寻址,这里展开说说原因。如果你用功能寻址向所有ECU发送0x28 0x00 0x03(恢复正常通信),这个请求会被总线上所有支持0x28的ECU接收并执行。在多个ECU都响应的情况下,诊断仪的接收队列会堆积大量肯定响应,这本身就是一种带宽浪费。
更严重的情况是:如果在总线中有某个ECU正处于“禁止通信”状态,它的应用报文被停了。此时你用功能寻址发0x28 0x00想把它“叫醒”——但这个ECU已经关闭了非诊断报文的收发,功能寻址的诊断请求它能不能收到?这取决于ECU对诊断报文的处理策略。大部分ECU即使禁止了应用通信,仍然会处理诊断请求(因为诊断报文走的是独立通道),所以功能寻址通常能收到。但问题是,这个ECU恢复通信后,如果它和另一个ECU同时响应,两个响应帧的仲裁ID可能相同(功能寻址的响应通常也是功能地址),导致总线错误帧。
所以我的原则是:0x28服务一律使用物理寻址,逐个ECU操作。如果确实需要批量操作(比如产线下线全部ECU静默),用循环逐帧发送物理请求比一次功能寻址更可控。
6. 测试与验证:如何确认0x28生效了
6.1 应用报文验证方法
确认0x28禁止应用报文是否生效,最直观的方法是抓总线报文。用CANalyzer、PCAN或者国产的周立功CAN分析仪,在总线上抓取目标ECU的报文,观察对应周期报文的消失。
但这里有个容易踩的坑:发送节点是ECU,但报文的仲裁ID可能是别的节点也在发。比如动力总成CAN总线上,发动机ECU和变速箱ECU可能都在发转速信号,仲裁ID不同但信号内容相似。如果你只筛ID而不过滤源节点,可能会误判“报文还在发”。正确的做法是:在CAN Matrix里查清楚目标ECU发送的报文的源地址(Source Address),然后过滤这个源地址的所有报文,再观察周期变化。
报文验证还有一个细节:0x28禁止通信后,ECU的报文调度器停发报文,但报文在CAN控制器发送缓冲区里可能还有残留。这些残留报文会在禁止后的几十微秒内发完。所以验证时至少要观察5个以上完整周期,确认不是缓冲区尾帧干扰判断。
我用过的验证脚本是CAPL写的,大致逻辑是:发送0x28 0x01 0x03后,等待200ms,然后统计目标ECU应用报文是否出现。如果出现,判定失败;如果没出现,判定成功。这里有一个经验值:等待时间不能太短,因为ECU内部状态切换需要时间,但也别太长,否则测试周期变长,效率低。200ms到500ms是一个合理的窗口。
6.2 网络管理报文验证方法
NM报文的验证逻辑和应用报文类似,但有一点不同:NM报文是事件型的,不一定周期发送。所以你不能用“等待一个周期”的方式来判断。我推荐的做法是:先让ECU处于主动模式,确认NM报文在发;然后发送0x28 0x01 0x02;等待几个网络管理周期(NM cycle time通常是1s或2s);观察NM报文是否停止。如果NM报文停了,再发0x28 0x00 0x02;等待恢复周期;观察NM报文是否重新开始发送。
要注意的是,不同ECU的NM行为差异极大。有些ECU在收到0x28禁止NM后,会立即进入“被动模式”,表现为NM报文停止发送但保持监听;有些ECU则会直接退出网络,表现为不仅不发送NM报文,也不再响应网络管理请求。后者的行为对总线网络的一致性影响更大,其他ECU可能会因为这个节点的“退网”而重新计算网络拓扑。在测试时要特别关注这一点。
6.3 边界场景与异常恢复测试
除了基本的功能验证,0x28服务的测试还需要覆盖异常场景。我个人在测试中一定会覆盖的边界场景包括:
第一,通信禁止状态下收到其他诊断服务请求。比如ECU已经被0x28 0x01 0x03禁止通信了,此时诊断仪再发0x22(读取数据)是否正常?按标准,诊断通信不受影响,应该正常响应。如果ECU把诊断报文也停了,那说明实现有bug,会导致刷写流程完全卡死。
第二,通信禁止状态下ECU断电重启。重启后通信状态应该恢复到normal,但这需要通过测试验证。有些ECU的NVM(非易失存储)里保存了通信状态,重启后如果错误恢复了“禁止通信”状态,会导致ECU上车后整条总线静默。
第三,通信恢复瞬间与报文调度器的竞态。比如0x28允许恢复后,ECU的报文调度器从停止状态切回运行状态,这个切换过程如果处理不当,可能会发出一个“半帧”——即报文的前半部分用旧的DLC,后半部分用新的DLC,导致CRC错误或信号解析异常。这种问题很难复现,但一旦出现就是量产事故。测试时建议做100次以上的恢复/禁止循环,观察每帧报文的DLC和CRC是否稳定。
第四,通信禁止期间DTC状态是否变化。有些ECU在禁止通信后,由于内部信号不再更新,会触发“信号超时”类DTC。测试时要先记录禁止前的DTC快照,禁止通信一段时间后再次读取,确认没有新增的误报DTC。
7. 常见问题与排查技巧实录
7.1 问题:0x28成功后ECU仍然发送报文
这是我被问得最多的问题。现象是:诊断仪发出0x28 0x01 0x03,ECU返回了肯定响应,但总线日志里目标ECU的报文还在周期性出现。
排查思路分三步:
第一步,确认0x28请求的物理寻址正确。用CANalyzer检查请求帧的仲裁ID,确认是目标ECU的物理寻址请求ID(通常是0x7E0加上ECU的扩展地址偏移),而不是功能寻址ID。
第二步,确认ECU返回肯定响应的来源。有些时候你看到的肯定响应是网关ECU代答的,不是目标ECU。在复杂网络拓扑下(比如中央网关+多个域控制器),可能存在诊断路由,网关会把诊断请求转发到目标ECU,然后代回响应。这种情况下,目标ECU可能根本没收到0x28请求。
第三步,检查ECU的通信类型定义。上面说过,0x28只控制“应用报文”和“网络管理报文”,但一些ECU还有“扩展报文”(Extended Data)、“指令报文”(Command)等类别,这些报文不受0x28控制。如果目标ECU在禁止通信后仍发送的报文属于“指令报文”或“安全相关报文”(比如安全气囊的点火指令报文),那是正常行为,不算bug。
7.2 问题:恢复通信后部分报文不出现
0x28 0x00 0x03发送成功后,ECU的应用报文没有立即恢复,或者过了一段时间后总线上只有部分报文。
这个问题的常见原因有三个:
原因一,ECU报文调度器的启动延迟。很多ECU的报文调度器是分优先级的,高优先级报文(如安全相关报文、故障报文)先启动,低优先级报文后启动。所以你会看到部分报文恢复了,部分报文还在“沉睡”。这种情况不是故障,等待一段时间(通常几秒)就全部恢复了。
原因二,会话切换导致报文组变化。有些ECU在不同会话模式下发送的报文组不同。比如默认会话下发15帧报文,扩展会话下只发8帧(诊断会话下减少非必要报文)。如果你在扩展会话下发0x28 0x00 0x03,恢复的本来就是8帧报文,而非15帧。要完全恢复全部报文,需要切回默认会话或编程完成后的正常会话。
原因三,报文调度器的“丢失调度”问题。这是最隐蔽的坑。在禁止通信期间,ECU的报文调度器停止运行,内部的时间戳和计数器的状态是暂停的。恢复通信时,如果调度器没有正确更新这些状态,可能会导致报文的周期错乱(比如本应该10ms发一次的报文变成了15ms发一次)。这个过程通常持续几个周期后会自动收敛,但在这几个周期内,接收端可能会因为报文超时而报错。
解决办法是在0x28 0x00 0x03之后,先观察200ms以上的总线报文,确认所有报文周期恢复稳定后,再进行后续诊断操作。不要恢复通信后立即执行下一个服务。
7.3 问题:0x28返回NRC 0x22但条件都满足
前面提到0x22是最难排查的NRC。我遇到的问题里,80%的0x22都能归因到前置条件不满足,但20%是ECU内部的时序问题。
举一个实际案例:某ECU在扩展会话下,安全访问已经解锁,但发送0x28 0x01 0x03一直返回0x22。后来翻ECU的代码发现,ECU在接收0x28请求后,需要先向应用层报文调度模块申请一个“通信禁止令牌”。但那个模块正在处理一个异常唤醒事件,导致令牌申请超时。这种情况下,诊断仪无论怎么调整发送时序都没用,需要等ECU内部事件处理完毕。
排查这类问题的思路是:查看ECU的诊断日志(如果有的话),或者用调试器(Tracing)看一下协议栈内部的状态机。如果没有这些手段,就只能靠经验——先等一段时间(比如1秒)重试,如果重试成功,说明是瞬时状态导致的问题;如果始终失败,检查是否在某些特定事件(如CAN唤醒、KWP2000快速启动)后触发。
7.4 问题:刷写完成后通信恢复但应用层功能异常
这是最让人头大的问题,因为表面上0x28 0x00 0x03已经执行成功,ECU的报文也出现在总线上了,但ECU的应用功能就是不对,比如车窗升不上去、灯光不亮。
这类问题通常不是0x28服务本身造成的,而是调制过程中其他因素导致的连锁反应。最常见的两个原因:
一是DTC故障导致的功能降级。刷写前的“通信禁止”状态可能让ECU误判了某些信号超时,记录了故障码。刷写后恢复通信,但故障状态还没清除,ECU出于安全策略进入了“跛行模式”或“功能降级”。解决办法是刷写完成后执行0x14(清除DTC)服务,让ECU重新评估故障状态。
二是NVM数据的一致性被破坏。某些ECU在通信禁止期间,外部输入的校准参数(比如指纹信息、零部件号)没有正确写入NVM,导致ECU功能异常。这种问题的排查思路是检查刷写流程里是否遗漏了NVM写入步骤,特别是0x2E(写入数据)服务的写入时机是否在0x28禁止通信之后。
8. 工具链与脚本化操作建议
0x28服务的用法如果固化在诊断工具里,重复使用时能省很多事。我用过的工具链里,比较实用的方案有两种:一种是基于CANoe的CAPL脚本,另一种是基于Python的uds库(比如python-can + udsoncan)。
CAPL脚本的优势是跟CANoe的报文日志、数据分析功能深度集成,适合产线调试和测试验证。我之前写过一个0x28服务的测试脚本,核心逻辑是:
// 发送0x28 0x01 0x03:禁止通信 byte request[3] = {0x28, 0x01, 0x03}; DiagnosticRequest(0x7E0, request); // 等待肯定响应 if (TestWaitForDiagResponse(0x7E8, 500)) { Write("0x28 Disable Communication: OK"); } else { Write("0x28 Disable Communication: Timeout"); } // 延迟200ms后检测目标ECU报文是否停止 TestWaitForTimeout(200); int count = CheckReceivedMessageCount(0x123); // 0x123为目标报文ID if (count == 0) { Write("App Message Stopped: OK"); } else { Write("App Message Still Sending: FAIL"); }Python方案更适合批量自动化和持续集成场景。unsoncan库对0x28服务有原生支持,调用非常简单:
import udsoncan from udsoncan.client import Client from udsoncan.services import CommunicationControl with Client(conn, request_timeout=2) as client: client.change_session(udsoncan.services.SessionControl.Session.extendedSession) client.unlock_security_access(0x01, key_func) client.comm_control( control_type=CommunicationControl.ControlType.disableCommunication, communication_type=0x03 )注意Python方案里有两个关键点:一是先切换会话和安全解锁,顺序不能错;二是communication_type要明确传递0x03,有些库的默认值可能跟你的预期不一致。
如果做的是产线工具,我还建议在0x28服务外面包一层重试逻辑——当收到NRC 0x33(安全访问被拒绝)时,自动先执行安全访问解锁再重试;当收到NRC 0x22(条件不满足)时,先切换会话再重试。这样在产线上遇到兼容性问题时,工具不会直接报错退出,而是自动完成前置条件。
9. 我的一些经验总结
最后聊点我在实际项目中积累的体会。
0x28服务看着简单,但它恰恰是UDS协议栈里最能体现一个ECU“状态机设计功力”的地方。一个设计良好的0x28服务,不仅要正确执行禁止/恢复操作,还要考虑好与会话切换、安全访问、DTC监控、网络管理、报文调度器这些模块之间的交互。任何一处的状态没有同步,就会出现“看起来成功了,实际上功能不对”的隐蔽问题。
给ECU开发者的建议是:0x28服务的实现,一定要把“通信状态”做成一个显式的状态变量,而不是散落在各个模块里的隐式标志。这样排查问题时才能快速定位。给诊断仪开发者的建议是:0x28服务永远不要裸发,要把会话检查、安全解锁、响应超时这些前置逻辑封装好,让上层调用者只关心“禁止”还是“恢复”这个语义。
再补充一个小技巧。如果你在调试中发现0x28执行后总线行为不符合预期,建议把ECU的CAN控制器配置改成“只听模式”(Listen Only)测试一下。因为有时候你以为总线上的报文是目标ECU发的,实际上是另一个ECU在转发或者在模拟发送。只听模式下,可以清晰分辨出哪些报文是目标ECU真正发出的,哪些是总线上的其他收发节点。
0x28之外的扩展方向,就是和0x31(例程控制)配合。比如你可以用0x31启动一个“进入刷写模式”的例程,例程内部会自动完成0x28禁止通信的动作。这样比分两步走更可靠——因为例程内部的时序是ECU自己控制的,不会出现“0x28已发送,但ECU内部的报文调度模块还没准备好”这种半吊子状态。我自己在实际项目中更推荐这种封装方式,特别是对刷写安全性有严格要求的场景。