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会做三件事:

  1. 长度检查:报文长度是否在合理范围内?太短或太长,直接拒绝。
  2. 格式检查:服务ID是否支持?子功能是否合法?
  3. 会话检查:当前会话是否允许这个服务?

如果以上任何一项不通过,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才是真正干活的。