3、诊断通信管理(DCM):DCM模块职责、诊断请求/响应处理流程、会话状态管理
好,我们进入诊断栈里最核心的一个模块——DCM。说实话,很多刚入行的工程师会把DCM和CAN总线上的报文收发混为一谈。其实不是的。DCM不负责物理层,它是个“翻译官”和“调度员”。
我个人习惯把DCM比作一个前台接待。客户(诊断仪)来了,它先判断客户有没有权限(会话状态),再判断客户要办什么事(服务ID),最后把任务分给对应的后台部门(比如DSP、CDD)。
3.1 DCM模块到底管什么?
DCM的全称是Diagnostic Communication Manager。它的职责很明确:管理诊断请求的接收、解析、路由,以及响应的组装和发送。
具体来说,它干这几件事:
- 接收原始数据:从PDU Router拿到一包字节流,比如“10 03”。
- 解析服务ID和子功能:判断这是不是个合法的请求。
- 检查会话状态:当前是默认会话?还是扩展会话?编程会话?不同会话下,能执行的服务不一样。
- 检查安全等级:有些服务需要解锁才能用,比如写入ECU内部参数。
- 路由到具体处理模块:比如诊断读写服务交给DSP,刷写服务交给FIM。
- 组装响应报文:把处理结果打包成“肯定响应”或“否定响应”,再发回去。
核心要点:DCM不关心你读的是哪个DID,也不关心你写的数据对不对。它只负责“流程合规性”。
我在项目中遇到过一种情况:开发人员把DCM和DSP的代码写在一起,结果每次改一个DID的读写逻辑,都要重新编译整个诊断模块。嗯,这其实是个架构上的坏味道。DCM应该保持“无业务逻辑”的状态。
3.2 诊断请求/响应处理流程
这个流程,说白了就是“一问一答”。但中间有很多细节。我画个简单的流程给你看:
诊断仪 --> [PDUR] --> [DCM_RequestIndication] --> [DCM解析] --> [DSP处理] --> [DCM_ResponseConfirmation] --> [PDUR] --> 诊断仪
每一步都有回调函数。你想想看,如果某个环节卡住了,整个诊断链路就断了。
3.2.1 请求进入阶段
当PDUR把数据交给DCM时,会调用一个接口,比如 Dcm_RequestIndication()。这个函数里,DCM会做三件事:
- 长度检查:报文长度是否在合理范围内?太短或太长,直接拒绝。
- 格式检查:服务ID是否支持?子功能是否合法?
- 会话检查:当前会话是否允许这个服务?
如果以上任何一项不通过,DCM会直接组装一个否定响应码(NRC),比如0x22(条件不满足)或0x11(服务不支持)。
注意:否定响应的优先级是有顺序的。我曾经见过一个项目,把“服务不支持”和“会话不允许”搞反了,结果诊断仪报的故障码完全不对。排查了整整两天。
3.2.2 请求处理阶段
如果检查通过,DCM会把请求路由到对应的处理模块。比如:
- 0x22(读取DID)→ 路由到DSP
- 0x2E(写入DID)→ 路由到DSP
- 0x31(例程控制)→ 路由到FIM
- 0x34/0x36(下载/传输数据)→ 路由到刷写模块
处理模块完成后,会返回一个结果给DCM。结果有两种:
- 肯定响应:包含服务ID+0x40,以及数据。
- 否定响应:包含0x7F,服务ID,以及NRC码。
3.2.3 响应发送阶段
DCM拿到结果后,调用 Dcm_ResponseConfirmation() 把数据交给PDUR。PDUR再根据路由表,把报文发到对应的CAN ID上。
这里有个小细节:响应报文的目标地址。物理寻址时,响应发回给请求方;功能寻址时,响应可能不发,或者只发一次。这个逻辑也是在DCM里控制的。
我的经验:调试诊断通信时,我建议你在DCM的入口和出口各加一个日志打印。这样能快速定位问题是出在“请求没进来”还是“响应没出去”。
3.3 会话状态管理
会话状态,是DCM里最容易让人迷糊的地方。为什么要有会话?说白了,就是为了安全。
ECU不能允许你在任何状态下都执行所有操作。比如,你正在开车,诊断仪突然发个“写入标定参数”的请求,那不就乱套了?
3.3.1 三种标准会话
UDS标准定义了三种会话:
| 会话类型 | 会话ID | 典型用途 | 允许的操作 |
|---|---|---|---|
| 默认会话 | 0x01 | 上电后的初始状态 | 只读操作(如读取DID、读取故障码) |
| 扩展会话 | 0x03 | 诊断/标定模式 | 读写操作、例程控制 |
| 编程会话 | 0x02 | 刷写/升级模式 | 内存写入、刷写相关服务 |
你想想看,如果ECU一直在默认会话里,那诊断仪连个DID都写不进去。所以,诊断仪必须先发一个 10 03 切换到扩展会话,才能执行写操作。
3.3.2 会话切换的规则
会话切换不是随意的。有几个关键规则:
- 只能从当前会话切换到目标会话:不能跳级。比如从默认会话可以直接切到编程会话,但有些实现会要求先切到扩展会话。
- 会话超时:如果一段时间内没有收到任何诊断请求,ECU会自动回到默认会话。这个超时时间通常是5秒左右,但可以配置。
- 会话切换时,安全锁会重置:也就是说,你从扩展会话切到编程会话后,需要重新解锁。
避坑指南:我曾经遇到一个项目,测试人员发现刷写过程中总是失败。排查到最后,发现是会话超时时间设得太短,刷写一个大文件时,中间间隔超过了5秒,ECU自动切回了默认会话。嗯,从那以后,我每次都会检查S3Server超时参数。
3.3.3 会话状态机
在代码层面,会话管理通常是一个简单的状态机。我一般这样实现:
typedef enum {
SESSION_DEFAULT = 0x01,
SESSION_PROGRAMMING = 0x02,
SESSION_EXTENDED = 0x03
} Dcm_SessionType;
static Dcm_SessionType currentSession = SESSION_DEFAULT;
Std_ReturnType Dcm_ChangeSession(Dcm_SessionType targetSession) {
// 检查是否允许切换
if (IsSessionTransitionAllowed(currentSession, targetSession) == FALSE) {
return E_NOT_OK;
}
// 重置安全锁
Dcm_ResetSecurityLock();
// 更新当前会话
currentSession = targetSession;
// 启动超时定时器
Dcm_StartSessionTimer(S3SERVER_TIMEOUT);
return E_OK;
}
这个状态机看起来简单,但实际项目中,你还要考虑“会话嵌套”的问题。比如,在编程会话里执行例程控制,例程执行过程中,会话超时了怎么办?
我的做法是:在例程执行期间,暂停会话超时计时器。等例程执行完再恢复。否则,刷写到一半,会话切回默认了,那可就麻烦了。
3.4 小结
DCM这个模块,说难不难,说简单也不简单。它的核心就是“流程控制”。你只要记住三点:
- 请求进来先检查,不合规的直接拒
- 响应组装要规范,肯定否定分清楚
- 会话状态是门禁,不同等级不同权
下一章,我们会深入DSP模块,看看那些读写DID、执行例程的服务到底是怎么实现的。到时候你会发现,DCM把流程走完了,DSP才是真正干活的。