4. 诊断会话控制(0x10):会话状态机、默认会话、扩展会话、编程会话、会话切换条件与超时处理

好,咱们今天聊聊诊断会话控制。这个服务,UDS 协议里编号是 0x10。

说实话,这是整个诊断栈里最基础、也最容易踩坑的地方。我见过不少新手,上来就发诊断请求,结果 ECU 不理他。为什么?因为会话没切对。

4.1 会话状态机:ECU 的“工作模式”

你可以把会话理解成 ECU 的“工作模式”。不同模式下,ECU 能干的事不一样。

比如默认会话下,你只能读读故障码。想刷写程序?不行,得先切到编程会话。

UDS 标准定义了三种会话:

  • 默认会话 (Default Session):上电后的初始状态
  • 扩展会话 (Extended Session):允许一些特殊诊断服务
  • 编程会话 (Programming Session):用于刷写软件

我个人习惯,把状态机画在纸上。每个会话是一个节点,箭头代表切换条件。这样逻辑特别清楚。

核心要点:ECU 永远处于且仅处于一个会话中。切换会话必须通过 0x10 服务。

4.2 默认会话:ECU 的“待机模式”

ECU 上电后,自动进入默认会话。这是最安全的模式。

在这个模式下,能用的诊断服务很有限。一般只允许:

  • 0x10(会话控制本身)
  • 0x14(读取故障码)
  • 0x19(读取故障码信息)
  • 0x22(读取数据)

为什么限制这么多?说白了,是为了安全。你想想看,车在路上跑着,突然有人通过诊断口把 ECU 的标定参数改了,那多危险。

我记得有一次,客户反馈说 ECU 在默认会话下无法执行 0x2E(写入数据)。我一看,这不是设计如此吗?默认会话本来就不该允许写操作。后来我们改了一下需求文档,把这个问题明确了。

4.3 扩展会话:调试与标定模式

扩展会话,我习惯叫它“调试模式”。

在这个模式下,ECU 开放了更多权限。比如:

  • 0x2E(写入数据)
  • 0x31(例程控制)
  • 0x34/0x36(请求下载/传输数据)

扩展会话通常用于产线标定、售后调试。它比默认会话权限高,但比编程会话低。

这里有个坑:扩展会话是有超时时间的。如果一段时间内没有收到任何诊断请求,ECU 会自动切回默认会话。

注意:扩展会话的超时时间,标准建议是 5 秒。但很多 OEM 会改成 10 秒或 30 秒。具体看需求。

4.4 编程会话:刷写模式

编程会话,权限最高。主要用于:

  • 刷写应用程序
  • 更新 Bootloader
  • 修改标定数据

进入编程会话前,通常需要先做安全访问(0x27 服务)。这是为了防止非法刷写。

我曾经遇到过一个案例:某款 ECU 在编程会话下,不小心把 Bootloader 刷坏了。结果 ECU 变砖,只能拆下来用编程器恢复。从那以后,我特别强调:编程会话里,一定要做完整性校验

4.5 会话切换条件与超时处理

会话切换不是随便切的。标准规定了几个条件:

源会话 目标会话 切换条件
默认 扩展 发送 0x10 03
默认 编程 发送 0x10 02(通常需先安全访问)
扩展 默认 超时或发送 0x10 01
编程 默认 刷写完成或超时

超时处理这块,我建议你重点关注。标准规定:

  • 默认会话:无超时
  • 扩展会话:S3 超时(通常 5 秒)
  • 编程会话:S3 超时(通常 5 秒,但刷写期间可延长)

超时后,ECU 会做什么?

  1. 自动切回默认会话
  2. 终止当前正在执行的诊断服务
  3. 释放所有资源(比如锁定的内存区域)

我的经验:在实现超时处理时,建议用硬件定时器。软件定时器容易受任务调度影响,导致超时不准。我曾经用软件定时器,结果在刷写大文件时,定时器被阻塞了,ECU 误判超时,直接切回了默认会话。那叫一个惨。

4.6 代码示例:会话切换处理

下面是一个简化的会话切换处理逻辑。实际项目中会更复杂,但核心思路不变。

// 伪代码:会话切换处理
uint8_t handle_session_control(uint8_t target_session) {
    switch (current_session) {
        case DEFAULT_SESSION:
            if (target_session == EXTENDED_SESSION) {
                current_session = EXTENDED_SESSION;
                start_timer(S3_TIMER, 5000); // 5秒超时
                return POSITIVE_RESPONSE;
            } else if (target_session == PROGRAMMING_SESSION) {
                // 检查安全访问状态
                if (security_access_granted) {
                    current_session = PROGRAMMING_SESSION;
                    start_timer(S3_TIMER, 5000);
                    return POSITIVE_RESPONSE;
                } else {
                    return NRC_SECURITY_ACCESS_DENIED;
                }
            }
            break;
        case EXTENDED_SESSION:
            if (target_session == DEFAULT_SESSION) {
                current_session = DEFAULT_SESSION;
                stop_timer(S3_TIMER);
                return POSITIVE_RESPONSE;
            }
            break;
        // ... 其他会话处理
    }
    return NRC_INVALID_SESSION;
}

嗯,这里要注意:超时定时器必须在每次收到诊断请求时重置。否则,你发完一个请求,等了几秒再发下一个,ECU 可能已经超时切回默认了。

4.7 避坑指南

最后,分享几个我踩过的坑:

  • 坑一:编程会话下,不要随意切回默认。刷写过程中切会话,会导致刷写失败。我见过有人把刷写流程写错了,刷到一半切回默认,结果 ECU 变砖。
  • 坑二:超时时间不要设太短。5 秒对于大多数场景够用,但如果你的诊断工具响应慢,建议设到 10 秒。
  • 坑三:多 ECU 系统中,会话状态要同步。比如网关切到编程会话,其他 ECU 也要跟着切。否则会出现通信混乱。

好了,会话控制就聊这么多。下一章咱们讲安全访问(0x27 服务),那个更有意思。