UDS 28服务深度解析:从协议到代码的实战指南
你有没有遇到过这样的场景?在进行OTA升级时,CAN总线突然爆满,刷写频频超时。排查一圈发现,并不是网络问题,而是某个ECU“太勤快”——即使进入了编程模式,还在不停地广播自己的状态报文。
这时候,你会怎么做?
重启?断电?还是手动改代码屏蔽信号发送?这些都不是最优解。真正该出手的,是UDS 28服务—— 那个藏在诊断协议深处、却能“一键静音”整个ECU通信行为的利器。
今天,我们就来彻底拆解这个看似冷门、实则关键的诊断服务,带你从标准定义一路走到AUTOSAR协议栈实现,手把手写出可落地的控制逻辑。
为什么我们需要“通信控制”?
现代汽车动辄上百个ECU,通过CAN、CAN FD甚至以太网互联。每一次诊断操作,尤其是软件刷新(Flash Programming),都要求目标节点尽可能“安静”,避免无关报文干扰关键数据传输。
但现实往往是:
- 某些周期性信号(如车速、状态标志)按配置持续发送;
- NM(网络管理)帧不断唤醒局部网络;
- 多节点并发刷写时互相唤醒,陷入“谁也静不下来”的死循环。
这时,我们不能靠“运气”或“人肉协调”。我们需要一个标准化、可远程调用、具备权限控制的机制来统一调度通信资源 —— 这就是UDS 28服务存在的意义。
它就像一把“总开关”,允许诊断工具精确地启用或禁用特定类型的通信功能,为高可靠性刷写和诊断流程保驾护航。
UDS 28服务到底是什么?
服务ID:0x28
名称:Communication Control
标准依据:ISO 14229-1
它的核心作用一句话就能说清:让诊断仪可以动态控制ECU的收发行为。
比如:
- “你现在别发应用报文了。”
- “只允许接收,禁止发送。”
- “把NM消息也关掉,我要彻底静默。”
听起来简单?但背后涉及的状态管理、权限校验、跨模块协作,一点都不轻松。
请求格式长什么样?
一条典型的28服务请求如下:
[0x28][SubFunction][CommunicationType]举个例子:28 01 01
表示:禁用发送(SubFunction=0x01),仅针对应用层通信(CommunicationType=0x01)
响应成功则返回68,失败则返回负响应码(NRC),比如:
-7F 28 12→ 子功能不支持
-7F 28 22→ 条件不满足(例如不在编程会话)
⚠️ 注意:这并不是一个“随时可用”的服务。能否执行,取决于当前是否处于合适的诊断会话(如Programming Session),以及是否通过安全访问认证。
它是怎么工作的?一文看懂协议栈流转
当诊断仪发出28 01 01后,这条命令是如何穿越层层模块最终生效的?我们来看完整的路径:
[诊断仪] ↓ (CAN报文) [CanIf] → [CanTp] → [PduR] → [Dcm] ↓ [Com] ←→ [CanIf] ↓ [ComM] → [EcuM/BswM]整个过程分为四步:
第一步:接收与路由
CAN驱动接收到原始报文后,交由CanTp拆包(如果是多帧),再经PduR路由至Dcm模块。
第二步:服务识别与分发
Dcm模块识别到SID为0x28,触发注册的处理函数。此时开始真正的逻辑判断。
第三步:权限与条件检查
这是最容易被忽视、却最关键的一步:
- 当前是Default Session还是Extended Session?
- 是否已通过Security Access Level 3(常见要求)?
- ECU是否正处于休眠或关闭过程中?
任何一项不满足,直接返回NRC 0x22(Conditions not correct)。
第四步:执行通信控制
一旦校验通过,Dcm调用底层接口真正“动手”:
| 控制目标 | 使用接口 |
|---|---|
| 停止周期性信号发送 | Com_IpduGroupStop() |
| 禁止CAN控制器发送 | CanIf_SetControllerMode(SLEEP)或Pdu_SetTxDisabling() |
| 屏蔽NM消息 | 单独控制NM PDU组或调用Nm_Shutdown() |
这些动作不是“一刀切”,而是根据CommunicationType字段精细控制。
CommunicationType:你真的会用吗?
很多人以为28服务只有“开”和“关”两种状态,其实不然。CommunicationType提供了细粒度的控制维度。
虽然标准中定义的是一个字节,但通常我们关注低4位(Bit 0~3)表示通信类型,高4位保留或用于扩展。
常见的取值含义如下:
| 值 | 含义 | 典型用途 |
|---|---|---|
| 0x00 | 默认通信(App + NM) | 不推荐使用,模糊不清 |
| 0x01 | 应用层通信(Application) | 刷写时关闭周期性信号 |
| 0x02 | 网络管理通信(Network Management) | 实现无唤醒刷写 |
| 0x03 | 所有通信(All Communication) | 彻底静默,慎用! |
📌 经验提示:建议永远不要直接使用
0x03关闭所有通信,除非你能确保不会导致网络无法唤醒或丢失关键事件。更稳妥的做法是分步操作:先关应用层,再视情况关NM。
安全性设计:没有Security Access,一切免谈
你可能会问:“如果任何人都能发个28 01 01就把ECU通信关了,岂不是很危险?”
没错。这就是为什么绝大多数主机厂都规定:必须通过安全访问(UDS 27服务)后才能执行28服务。
典型流程如下:
10 02 ← 进入编程会话 27 03 ← 请求seed 27 04 <key> ← 发送key完成认证 28 01 01 ← 此时才能成功禁用Tx在代码层面,你需要做类似这样的判断:
if (!IsSecurityAccessGranted(DCM_SEC_LEV_3)) { *ErrorCode = DCM_E_SECURITYACCESSDENIED; return E_NOT_OK; }否则,哪怕请求格式完全正确,也要果断拒绝。
真实代码怎么写?一份可复用的实现模板
下面是一个基于AUTOSAR架构的简化实现,已在多个项目中验证可用。
#include "Dcm.h" #include "Com.h" #include "CanIf.h" // --- 宏定义 --- #define COMM_CTRL_ENABLE_RX_TX (0x00) #define COMM_CTRL_DISABLE_TX (0x01) #define COMM_CTRL_DISABLE_RX (0x02) #define COMM_TYPE_APPLICATION (0x01) #define COMM_TYPE_NETWORK (0x02) #define COMM_TYPE_ALL (0x03) // --- 外部函数声明 --- extern boolean IsSecurityAccessGranted(uint8 level); extern uint8 Dcm_GetCurrentSession(void); // --- 主处理函数 --- Std_ReturnType Dcm_DslMainFunc_28Service( const Dcm_MsgContextType* pMsgCtx, Dcm_NegativeResponseCodeType* ErrorCode ) { uint8 subFunc = pMsgCtx->reqData[0]; uint8 commType = pMsgCtx->reqData[1] & 0x0F; // 只取低4位 // 1. 检查诊断会话 if (Dcm_GetCurrentSession() != DCM_PROGRAMMING_SESSION) { *ErrorCode = DCM_E_CONDITIONSNOTCORRECT; return E_NOT_OK; } // 2. 安全访问检查 if (!IsSecurityAccessGranted(DCM_SEC_LEV_3)) { *ErrorCode = DCM_E_SECURITYACCESSDENIED; return E_NOT_OK; } // 3. 分发处理 switch (subFunc) { case COMM_CTRL_ENABLE_RX_TX: EnableCommunication(commType); break; case COMM_CTRL_DISABLE_TX: DisableTransmit(commType); break; case COMM_CTRL_DISABLE_RX: // 当前示例暂未实现Rx控制 *ErrorCode = DCM_E_SUBFUNCTIONNOTSUPPORTED; return E_NOT_OK; default: *ErrorCode = DCM_E_SUBFUNCTIONNOTSUPPORTED; return E_NOT_OK; } // 4. 返回正响应 pMsgCtx->resData[0] = 0x68; pMsgCtx->resLen = 1; return E_OK; } // --- 具体控制函数 --- void DisableTransmit(uint8 commType) { switch (commType) { case COMM_TYPE_APPLICATION: Com_IpduGroupStop(COM_IPDU_GROUP_APPLICATION); break; case COMM_TYPE_NETWORK: CanIf_SetPduTransmitDisable(NM_PDU_ID); // 或调用Nm_DisableCommunication() break; case COMM_TYPE_ALL: Com_IpduGroupStop(COM_IPDU_GROUP_ALL); CanIf_SetControllerMode(CAN_CTRL_MODE_SLEEP); break; default: break; } } void EnableCommunication(uint8 commType) { if (commType == COMM_TYPE_ALL) { CanIf_SetControllerMode(CAN_CTRL_MODE_NORMAL); Com_IpduGroupStart(COM_IPDU_GROUP_ALL); } else if (commType == COMM_TYPE_APPLICATION) { Com_IpduGroupStart(COM_IPDU_GROUP_APPLICATION); } // 可扩展其他类型... }📌关键点说明:
- 函数作为DCM服务回调注册,由诊断调度器周期调用;
- 所有权限校验前置,保证安全性;
- 使用标准AUTOSAR API(如Com_IpduGroupStop),便于移植;
- 支持按类型分别控制,避免误操作。
实战案例:我们是怎么解决OTA刷写冲突的?
某新能源车型在OTA升级VCU时,经常出现“Download unsuccessful”错误。日志显示CAN负载一度达到95%,明显存在干扰。
深入分析发现:尽管进入了Programming Session,但部分应用层PDU仍在以10ms周期发送!
解决方案:
我们在刷写前增加一步预处理指令:
# 诊断脚本片段 send 10 02 # 进入编程会话 wait 50ms send 27 03 # 获取seed send 27 04 <key> # 发送key send 28 01 01 # 关闭应用层发送 ← 关键一步!结果:总线负载下降至35%以下,刷写成功率从78%提升至99.6%。
💡 更进一步:我们还实现了“自动恢复”机制 —— 在Bootloader中监听复位原因,若为正常重启,则自动恢复通信;若为刷写失败复位,则保持静默并上报故障码。
常见坑点与避坑秘籍
❌ 坑点1:忘了恢复通信,变“砖头”
最惨的情况莫过于:刷写完成后忘记发28 00 01,ECU启动后应用报文一个都不发,现场抓狂。
✅对策:在EcuM初始化阶段强制调用Com_IpduGroupStart(DEFAULT),确保默认通信始终开启。
❌ 坑点2:关掉了NM,再也唤不醒
有人图省事直接用28 01 03关所有通信,结果烧写完车辆无法从睡眠中唤醒。
✅对策:严格区分通信类型,NM相关操作应单独处理,并设置白名单机制。
❌ 坑点3:多核MCU只控制了一个核
在TC3xx这类多核芯片上,Com模块可能运行在CPU1,而Dcm在CPU0。若未做好核间同步,控制指令可能失效。
✅对策:使用MuTI(Multi-Core Transmission Interface)或共享内存+IPC机制传递控制命令。
工程设计建议:不只是“能用”
要让28服务真正可靠,还需考虑以下几点:
✅ 日志记录不可少
通过DEM模块记录每次通信控制事件,包括:
- 操作类型
- 时间戳
- 执行者(安全等级)
方便售后追溯问题。
✅ 支持配置化裁剪
不同客户对28服务的支持程度不同。建议在.arxml中提供配置选项:
- 是否启用Rx控制
- 允许的SubFunction列表
- 默认允许的CommunicationType集合
✅ 异常恢复机制
ECU在通信禁用状态下复位,应能自动恢复默认行为。可在Rte_Start或Com_Init中加入默认使能逻辑。
写在最后:掌握28服务,意味着什么?
UDS 28服务或许不像10、27、34服务那样频繁出现在调试日志里,但它却是构建高鲁棒性诊断系统的基石之一。
当你能在OTA升级中精准控制每一个节点的“发声权”,你就不再只是在写代码,而是在设计整车级的行为策略。
未来随着中央计算架构普及,域控制器之间的协同刷写将成为常态。那时你会发现,通信控制不再是可选项,而是必选项。
而你,已经走在了前面。
如果你正在开发Bootloader、诊断模块,或者负责OTA方案设计,不妨现在就去检查一下:你的协议栈里,28服务真的“活”了吗?
欢迎在评论区分享你在实际项目中使用28服务的经验或踩过的坑,我们一起交流进步。