干了这些年车载诊断,给ECU刷写、做总线测试、处理售后问题的时候,基本都躲不开一个服务——UDS的CommunicationControl,也就是0x28。很多刚入门的朋友看到协议栈里“0x28”这个Service ID,第一反应是“哦,通信控制”,然后就过去了。但真到了项目里,你会发现这个服务藏着不少细节:子功能怎么选、通信类型填几、为什么发完0x28之后整个网络突然安静了、ECU是不是死机了?这篇文章我就把这个服务彻底掰开揉碎,结合刷写、测试、排查的实际场景,把报文格式、参数逻辑、实操步骤和踩坑经验一次讲清楚。
1. 先搞明白0x28是干嘛的:刷写前为什么要让ECU“闭嘴”
1.1 刷写场景里的真实痛点
你拿诊断仪连上整车,准备给某个ECU刷新固件。正常情况下,ECU在运行状态下会不停地往总线上发应用报文,比如车速、转速、扭矩、温度这些周期性消息。这些消息是给仪表、其他控制器用的,本身没问题,但一旦你开始通过诊断总线给ECU下载程序,问题就来了。
第一,带宽冲突。CAN总线是共享的,刷写时诊断仪和ECU之间要走大量的多帧传输、流控帧、下载数据帧,这些帧的优先级未必比得上某些周期报文。万一关键应用报文发了,刷写请求只能等总线空闲,整个刷写流程就会变得很慢,甚至超时失败。第二,状态干扰。刷写的过程中ECU的Flash正在被擦除、写入,应用层逻辑实际上已经暂停了。如果这个时候ECU还在尝试往外发应用报文,轻则违反上位机的时序要求,重则导致程序跑飞、Flash数据损坏。
这时候就需要0x28服务出场。它的作用很简单:按照诊断仪的指令,把ECU对应用报文和网络管理报文的接收、发送能力临时关闭或恢复,让ECU在刷写期间“闭嘴”,只保留诊断通道。注意,这里说的是“临时”,因为0x28不是永久配置,ECU复位或者重新上电之后,默认就会恢复通信功能。
1.2 0x28到底能管哪些报文、管不了哪些
这个点特别容易误解。0x28的“通信控制”不是把所有CAN报文都一刀切断,它管的是三种类型的通信:
- 应用报文(Application messages):也就是ECU正常运行时的功能性报文,比如周期性发送的状态帧、事件触发的故障帧。
- 网络管理报文(Network management messages):在OSEK NM、AUTOSAR NM这种网络管理机制下,节点之间互相“报活”的报文。
- 诊断报文(Diagnostic messages):走0x7DF/0x7E0/0x7E8这类诊断ID的请求和响应帧。
关键来了:0x28真正能控制的,只有前两种。诊断报文本身是不能通过0x28关掉的,否则你自己就把诊断通道给封了,后面还怎么继续刷写?这一点协议标准里解释得很清楚,CommunicationControl控制的是“application messages”和“network management messages”,诊断报文的收发始终是允许的。但在实际项目里,我见过不少刚接触诊断开发的同事,以为0x28能把ECU完全静默,结果发了0x28之后发现诊断请求还能通,就一脸懵——这不是ECU没执行,而是诊断报文本来就是例外。
1.3 除了刷写,0x28还能用在哪
很多人觉得0x28就是个“刷写前奏曲”,其实它的应用场景比想象中多:
- 整车静默模式测试。某些测试用例需要验证总线某个节点“消失”之后,其他节点能否正常工作。通过0x28控制目标ECU禁止发送应用报文,比拔插头、剪线要干净得多,而且还能用诊断请求恢复。
- 防盗认证与配对过程。有些模块在防盗匹配流程里要先切断对外通信,避免错误状态广播出去,完成认证后再恢复。
- 网关路由调试。网关ECU在配置路由表或者升级自身固件时,需要临时停发网络管理报文,防止进入奇怪的状态。
- 下线检测和EOL配置。生产线上的功能测试,会通过0x28禁止某个ECU发NM报文,让网络快速进入预休眠状态,缩短节拍时间。
这些场景里,0x28的价值不只是“关”,更重要的是“可控”。你能精确控制关哪个方向(收/发)、关哪类报文(应用/NM/全部),以及通过一条请求随时恢复。
2. 请求与响应格式拆解:一个字节都不能错
2.1 请求报文的三个字节怎么填
0x28服务的请求帧结构非常短,SI多两个字节。第一字节固定是0x28,第二字节是子功能(Sub-function),第三字节是通信类型(Communication type)。我把协议标准里定义的格式整理成了一张表,方便对照:
| 字节 | 名称 | 值范围 | 说明 |
|---|---|---|---|
| 0 | Service ID | 0x28 | CommunicationControl |
| 1 | Sub-function | 0x00-0x03(可加0x80抑制正响应) | 控制类型,决定使能/禁止RX/TX |
| 2 | Communication type | 0x01-0x06 | 指定控制哪类通信 |
先看子功能。标准定义了下面这几种控制类型:
- 0x00:enableRxAndTx(使能接收和发送)。把之前被禁止的通信恢复,相当于“解禁”。
- 0x01:enableRxAndDisableTx(使能接收,禁止发送)。ECU还能收,但不能发应用报文。
- 0x02:disableRxAndEnableTx(禁止接收,使能发送)。ECU还能发,但不接收。
- 0x03:disableRxAndTx(禁止接收和发送)。最严格的状态,ECU把应用通信完全切断。
然后看通信类型。这个字段定义了上面这些子功能要作用在什么报文上:
- 0x01:所有通信(application messages和network management messages都算)。
- 0x02:仅应用报文。
- 0x03:仅网络管理报文。
- 0x04:应用报文和网络管理报文。注意,这个组合在协议标准里是不建议使用的,很多ECU会直接拒绝或者不做区分,我自己也基本没见过有效实现,项目中尽量避开。
- 0x05:仅诊断报文。标准里提到这个值,但实际绝大多数ECU都会回复NRC 0x31(请求超出范围),因为诊断报文不能被控制。
- 0x06:诊断报文和网络管理报文。也是标准里的定义,但同样基本不会真正实现。
于是,一条最常见的刷写前关闭应用通信的请求就是:
02 28 03 02前面是CAN诊断帧的PCI字节,0x02表示后续2个数据字节:0x28是服务ID,0x03表示禁止RX和TX,0x02表示只针对应用报文。这条请求发出去之后,ECU就会停止发送和接收应用报文,开始安安静静地等你刷写。
2.2 子功能为什么把RX和TX拆开
刚接触的人可能会疑惑:0x28控制通信,干嘛要把接收和发送分开?直接一个“开/关”不就行了?
这其实是基于实际需求的考虑。举个很现实的例子:在某些整车测试中,你希望ECU不要发应用报文,避免干扰其他模块的判断,但你又希望它还能接收某些应用指令,用于执行特定的动作。这时候单独禁止TX、保留RX就非常重要。反过来,有些场景只需要让ECU“听不见”总线上其他节点发来的应用报文,但允许它继续对外广播自己的状态,那就用0x02。还有一个潜在用途是配合复位管理:禁止RX可以让ECU暂时忽略应用层唤醒信号,防止总线消息把它从特定状态拉走。
再往深一点说,0x28的控制粒度是“报文类型”和“收发方向”的二维组合,这背后对应的其实是ECU通信栈底层的PDU收发使能开关。在AUTOSAR架构里,收到0x28请求后,COM模块或者PduR模块会按通信类型和方向去关闭对应PDU组的发送通道或接收通道。这样一来,应用层模块还能正常调用接口,但数据根本不会真正走到CAN Controller,从物理层面把通信“捂住”了。
2.3 正响应与负响应的坑
按UDS标准,0x28的正响应格式是SID+0x40,即0x68,后面跟着回显的子功能和通信类型:
03 68 03 0203是响应长度,0x68是正响应SID,0x03是子功能回显(禁止RX和TX),0x02是通信类型回显(应用报文)。但这里有个容易踩的坑:很多ECU的协议栈实现并没有把通信类型回显出来,只回显了子功能,响应帧是:
02 68 03这两种实现都不算错,标准允许正响应中包含请求参数,但具体到某个ECU,解析时最好以“服务ID正确+子功能正确”为准来判断成功,不要严格要求第三个字节必须和请求一致。我在实际做自动化测试的时候就吃过这个亏——脚本里严格比对了解析结果,结果在某个供应商的ECU上一直报响应格式错误,最后发现是对方只回显了子功能,没有回显通信类型。
负响应形式是:
03 7F 28 [NRC]常见NRC包括0x12(子功能不支持)、0x13(报文长度错误)、0x22(条件不满足)、0x31(请求超出范围)等。每种NRC对应的实际原因和排查方向,我在后面第4部分专门展开说。
3. 实操演示:刷写流程里0x28的标准用法
3.1 一次性看懂报文时序
纸上谈兵没有用,我直接放一段典型的刷写前诊断时序,大家照着抓报文比对就能理解。
假设场景是通过CAN物理寻址方式给一个ECU刷写,地址是0x7E0(请求)/0x7E8(响应),刷写前需要先做安全访问、关闭DTC记录、关闭应用通信。完整的日志大致长这样:
Tester -> ECU: 02 27 05 00 // 请求安全访问种子 ECU -> Tester: 06 67 05 12 34 56 78 // 返回种子 Tester -> ECU: 06 27 06 [密钥] // 发送解密钥 ECU -> Tester: 02 67 06 // 安全访问通过 Tester -> ECU: 02 85 02 // 关闭DTC记录(0x85服务) ECU -> Tester: 02 C5 02 // 正响应 Tester -> ECU: 02 28 03 02 // 禁止应用报文的发送和接收 ECU -> Tester: 03 68 03 02 // 正响应,应用通信已关闭 Tester -> ECU: 02 31 01 [刷写准备例程] // 进入编程会话或擦除前准备 ...注意我上面把0x85(ControlDTCSetting)放在0x28前面,这是常规操作顺序:先进入扩展会话、做安全解锁、关闭故障码记录、再关闭应用通信,最后才做刷写相关的例程和传输服务。这个顺序不是固定的,但基本逻辑是:在动Flash之前,先把所有可能干扰刷写的“正常功能”全部关掉。
0x28出现在这个位置的时间点很讲究。太早关闭应用通信,比如刚进入扩展会话就关,可能会让ECU来不及发送某些关键的运行状态报文,导致其他节点误判;太晚关闭,比如已经开始刷写Flash了,应用报文还在总线上抢带宽,就可能拖慢传输速度甚至引发超时。最稳妥的做法是在安全访问完成后、刷写例程开始前,执行0x28。
3.2 ECU收到0x28之后内部发生了什么
从功能逻辑上看,0x28是一条指令,但从实现层面看,它触发的是一连串内部状态切换。这里我以常见的AUTOSAR协议栈为例说明:
收到0x28请求后,Dcm模块先解析子功能和通信类型,校验通过后正响应发出,同时调用通信栈的接口去关闭对应类型的PDU。在Com层,管理器会把应用报文的发送标志置为不可用,周期任务里调度到这个PDU时会直接跳过发送,而不是“发了但内容无效”。同样,接收路径上的PDU会被设置成不接收,物理上总线上虽然还有帧,但报文不会进入应用层。
这里有个和底层硬件相关的细节:0x28禁止发送应用报文,不等于ECU完全不再往总线上发任何东西了。CAN控制器还是会正常发送ACK应答总线上的帧,因为ACK属于CAN数据链路层的行为,不归COM层管。所以在示波器或总线分析软件里,你仍然能看到该节点对每一帧都回了ACK,但你看不到它发出应用报文。对于这一点,测试人员要心里有数,别误以为“禁止发送没生效”。
还有一个值得注意的点:0x28禁止的是“通信”,不是“会话”。ECU的诊断会话状态、安全解锁状态都不会因为0x28而被重置。所以刷写完成后,你不需要重新做安全访问,直接发0x28恢复通信即可。当然,如果中间的刷写过程导致ECU复位了,那会话、解锁状态会全部清空,流程要重来。
3.3 恢复通信的两种方式
刷写完成后,当然要恢复ECU的应用通信。有两种方式:
第一种,通过诊断请求恢复:
Tester -> ECU: 02 28 00 02 ECU -> Tester: 03 68 00 02子功能0x00表示使能接收和发送,通信类型0x02表示针对应用报文。这条请求发完,ECU立刻恢复应用报文的收发,不需要等整车下电。
第二种,ECU复位或断电重启。0x28的控制效果是“非保持”的,ECU一旦发生复位、重新上电,通信栈会按照默认配置初始化,也就是所有通信全使能。这也是为什么刷写流程的末尾通常会有一个ECU复位指令(0x11服务),复位之后不用发恢复通信的请求,ECU自己就活了。
这里要特别提醒一下:如果刷写过程中没有发过0x28,而你在刷完后又发了0x28 00 02恢复通信,问题也不大,ECU会把“恢复”当成一条正常请求处理,不会报错。但如果ECU当前已经处于全使能状态,你发0x28 03 02(禁止)再立刻发0x28 00 02(恢复)这种“空转”操作,个别严格的ECU可能会在中间状态处理上出问题,比如计数器、状态机奇奇怪怪地跳变,所以尽量不要做无意义的开关操作。
3.4 实操心得:时序和超时要提前设计
我做了这么多次刷写和诊断开发,最大的感触是0x28本身不复杂,复杂的是它对测试时序的影响。一旦你把ECU的通信给关了,总线上就少了很多信息,测试设备、监控工具、甚至整车控制器都可能“不习惯”。
举个例子:我用CANoe做自动化测试时,脚本里会启动一个周期统计窗口监控ECU的应用报文。发0x28之前,每10ms能收到一条状态帧;发0x28之后,这条状态帧就消失了,如果监控窗口没有做“允许缺失”的处理,测试脚本会立刻判定通信超时、直接fail。所以在我自己搭测试环境时,0x28操作前后会加一个“通信静默期”的标识,测试用例里明确告诉脚本:这段时间内应用报文不报错。
还要注意诊断仪本身的请求超时设置。0x28的正响应一般几十毫秒内就返回,问题不大。但如果你想测试ECU在关闭应用通信后、是否还能正常响应诊断请求,一定要记得:诊断报文没有关,诊断响应会正常回来。如果ECU连诊断响应都不回了,那问题多半不在0x28,而在ECU本身的处理逻辑或硬件故障。
4. NRC错误排查:我踩过的那些坑
4.1 常见NRC速查表
0x28的负响应虽然格式固定,但具体NRC的含义和触发条件,不同ECU厂商的实现差异很大。我整理了项目里最常遇到的几种,做成速查表:
| NRC | 名称 | 触发原因 | 排查方向 |
|---|---|---|---|
| 0x12 | Sub-function Not Supported | 子功能不是0x00-0x03或未按标准实现 | 确认请求字节,核对ECU诊断规范 |
| 0x13 | Incorrect Message Length Or Invalid Format | 请求长度不是两数据字节 | 数一下PCI字节,是不是把抑制位多算了 |
| 0x22 | Conditions Not Correct | 当前条件下不支持该控制组合 | 确认是否处于正确会话、安全访问状态 |
| 0x31 | Request Out Of Range | 通信类型超出支持范围 | 比如发0x05控制诊断报文、发0x06等 |
| 0x7E | Sub-Function Not Supported In Active Session | 当前会话不支持该子功能 | 检查是否在默认会话里发了扩展会话才允许的请求 |
| 0x7F | Service Not Supported In Active Session | 0x28服务在当前会话不可用 | 检查会话类型 |
4.2 三个高频问题实录
问题一:发了0x28 03 01(禁止所有通信)之后,ECU连诊断请求都不回了。
这个现象我在测试现场遇到过。第一反应是ECU死机了,但用示波器一看,诊断请求帧有ACK,ECU硬件没死。最后定位到的原因是:ECU的协议栈实现没有按标准处理“所有通信”这个类型,内部把诊断报文的接收路径也顺带关了。这类问题在非AUTOSAR实现的ECU上比较常见,因为协议栈是供应商针对特定硬件裁剪过的,对0x01这个值理解有偏差。解决办法:除非ECU规范明确支持,否则尽量避免使用0x01(所有通信),而是用0x02(仅应用报文)或0x03(仅网络管理报文)这种精确控制。
问题二:发了0x28 03 02,ECU返回NRC 0x31。
这个通常是ECU诊断规范里定义的通信类型和标准不一致。有些ECU只支持0x01(所有通信),对0x02(应用报文)反而认为超出范围。我在某个项目里就碰到过:对方规范里明确写着“本ECU仅支持communication type = 01”,因为该ECU没有网络管理功能,所有报文都是应用报文,0x01和0x02效果一样。所以遇到0x31,第一时间去翻该ECU的诊断规范,看看它支持哪些通信类型,别上来就怀疑协议栈坏了。
问题三:刷写流程中,0x28正响应已经收到了,但总线上还能看到该ECU发应用报文。
这个问题我先查了ECU的应用层逻辑,发现应用报文确实没有通过COM层发送,但通过另一个独立通道(比如扩展数据、网络管理帧)发出来了。原因在于:ECU同时使用了多路CAN通道,0x28只管了主诊断通道上的应用报文,另一个通道上的报文不受影响。这类ECU通常会在规范里写明“0x28仅控制诊断通道对应的PDU”。如果你遇到这种问题,要结合ECU的通道架构来看,先确认是哪条物理通道还在发报文,再看对应PDU是否在控制范围内。
4.3 与其他诊断服务的配合细节
0x28很少单独使用,它总是和刷写、测试流程里其他服务协同工作。这里说几个我实际总结出来的配合细节:
先做安全访问再做0x28。很多ECU把0x28定义成非安全服务,默认会话就能发,但也有部分ECU要求必须进入扩展会话或完成安全解锁后才能执行0x28。稳妥的顺序是:进入扩展会话(0x10 03)→ 安全访问(0x27)→ 关闭DTC记录(0x85 02)→ 关闭应用通信(0x28 03 02)。这样能最大程度避免NRC 0x22。
0x28和0x85不是一回事。0x85关的是故障码记录,ECU还是会正常通信、正常工作,只是不往故障内存里写故障。0x28关的是通信本身,故障码记录和通信是两个维度。刷写时两者都要关,但千万别混用:只关0x85不够,应用报文还在;只关0x28不够,刷写过程中产生的误码会被存下来。
0x28和0x31例程控制的关系也很关键。在刷写流程里,0x28通常放在例程控制(0x31)之前。前面提到,有些例程执行时,ECU内部会主动做一些资源清理、状态切换,如果这时候应用通信还在,例程执行可能被意外报文打断。所以遵守这个顺序,能让刷写例程在“安静”的环境里执行,成功率更高。
4.4 实现细节心得:关于“什么时候关闭”的硬件问题
最后分享一个比较少有人提到的硬件层面细节。0x28请求发出后,ECU的正响应是先发出的,然后才去真正关闭通信。也就是说,正响应和“通信真正被关闭”之间有一个时间窗口。这个窗口虽然很短暂(微秒到毫秒级),但在对时序敏感的场景里,测试设备如果紧跟着下一帧就发其他诊断请求,有一定概率撞上ECU正在关闭通信的瞬间,导致那一帧被丢弃、请求超时。
所以你在写测试脚本时,0x28的正响应返回之后,最好加一个小的延时(比如10-20毫秒),再发后面的刷写请求,给ECU留足内部状态切换的时间。不要一收到0x68就立刻咔咔往下发,不然在高速报文环境下容易偶发超时,还非常难复现。
5. 关于0x28的扩展思考:从“关通信”到“管通信”
我最初接触0x28时,只觉得它是刷写工具链里一个“不起眼但必须调用的服务”,跟0x27、0x34、0x36这些“大佬”比起来,存在感很低。做多了才发现,0x28的价值恰恰在于它定义了ECU通信行为的一个“可控开关”,而开关背后,是整车电子架构对总线资源管理的诉求。
从协议设计的角度看,UDS把“应用通信控制”独立成一个服务,而不是让上位机直接去操作底层CAN寄存器的使能位,这本身就是一种抽象。诊断仪不需要知道ECU内部有几个PDU、哪个PDU对应哪个报文、发到哪个物理通道,只需要告诉ECU“我要你停止发送应用报文”或者“恢复所有通信”,剩下的事情由ECU内部完成。这种抽象让上层诊断逻辑和底层硬件解耦,也让不同ECU之间的诊断表现趋于一致。
从整车应用的角度看,0x28还承载了更多可能性。比如部分新架构车型支持通过0x28实现远程诊断时的局部网络管理——在某次OTA刷写中,网关可以单独让目标ECU静默更新,而不影响其他节点的正常工作。再比如配合功能安全需求,某条关键链路的ECU在诊断模式下被要求必须停止发送周期报文,以免干扰安全校验逻辑,这都用得上0x28。
如果你在写自己的诊断工具,我建议把0x28封装成一个带参数校验的独立接口:
def communication_control(session, sub_function, comm_type, suppress_positive=False): if sub_function not in [0x00, 0x01, 0x02, 0x03]: raise ValueError("invalid sub-function") if comm_type not in [0x01, 0x02, 0x03]: raise ValueError("invalid communication type") if suppress_positive: sub_function |= 0x80 request = [0x02, 0x28, sub_function, comm_type] return send_diag_request(session, request)这样做的好处是,测试脚本里调用时不容易因为手工拼字节出错。比如我早期踩过直接发0x28 83 02(把0x80抑制位和0x03混在一起变成0x83)的坑,值时发出去ECU一直不回正响应,查了半天才发现是我自己在测试工具里把抑制位加错了位置。0x80抑制正响应这个位,在0x28里是允许使用的,但很多测试人员会忽略它的存在。加上0x80后,ECU只执行、不回复正响应,这在刷写流程中可以减少一帧总线开销,但对测试脚本的同步逻辑有要求,你要确保脚本不会等一个永远不会来的正响应。
我个人的习惯是:普通调试和开发阶段,不用0x80抑制位,保持正响应,方便抓包验证;量产刷写工具里,才根据协议规范决定是否启用0x80,减少总线上无效消息,提升刷写效率。
最后再提醒一句关于诊断规范的事。网上讲0x28的文档很多,但真正到项目里,第一参考永远是手上这份ECU的诊断规范(Diagnostic Specification)。同一个0x28,在不同供应商、不同平台上支持的子功能范围、通信类型定义都可能不一样。你按ISO 14229标准写的通用逻辑,到了某个ECU上可能就返回NRC了。所以动手写工具之前,把规范翻一遍,确认这个ECU支持哪些子功能、哪些通信类型、有没有额外的前置条件,这比什么都重要。
这些经验都是我在一次次刷写失败、报文超时、NRC抓狂之后换来的。做车载诊断开发就是这样,技术本身不难,难的是把每个细节的“为什么”弄明白,然后在自己的工具和流程里把这些细节都设计到位。希望这篇文章能让你少踩几个我踩过的坑。