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

大家好,我是老李。今天咱们聊聊UDS里最基础、也最绕不开的一个服务——0x10诊断会话控制。

说实话,我刚开始做UDS协议栈那会儿,觉得会话控制不就是发个指令切个模式嘛,有啥难的?结果第一次在实车上调试,就因为会话切换时序没处理好,导致ECU死锁了。嗯,从那以后我再也不敢小看这个服务了。

4.1 为什么需要会话控制?

你想想看,ECU在车上跑的时候,大部分时间都在干正事——控制发动机、管理电池、处理传感器数据。这时候如果诊断仪随便发个写内存的指令,那不就乱套了?

所以UDS设计了会话机制。说白了,就是给ECU设了几个「工作模式」:

  • 默认会话:安全模式,只能读读基本信息
  • 编程会话:刷写模式,可以擦写Flash
  • 扩展会话:调试模式,可以读写参数、做特殊功能

每个模式能干啥,不能干啥,都是提前定好的。这就好比工厂里的门禁卡——普通员工只能进办公区,工程师才能进实验室,管理员才能进机房。

4.2 三种会话的详细说明

4.2.1 默认会话(Default Session)

这是ECU上电后的默认状态。我个人习惯叫它「安全模式」。在这个模式下:

  • 只能执行0x22(读取数据)、0x19(读取DTC)这类只读服务
  • 不能执行0x2E(写入数据)、0x31(例程控制)等有风险的操作
  • 不需要安全访问(0x27)就能用

我记得有一次,客户反馈说ECU在默认会话下能写参数,我第一反应就是协议栈实现有问题。查了半天,发现是会话状态机没初始化好,默认会话的权限掩码配错了。

注意:默认会话下,如果收到非法的服务请求,ECU必须回复NRC 0x7F(服务不支持)或0x22(条件不满足)。千万别偷偷执行,这是违反ISO 14229的。

4.2.2 编程会话(Programming Session)

这个会话只有一个用途——刷写软件。说白了,就是给ECU升级固件用的。

进入编程会话后:

  • 可以执行0x34(请求下载)、0x36(传输数据)、0x37(请求退出传输)
  • 通常需要先做安全访问(0x27)
  • 很多ECU会在这个会话下关闭通信栈,只保留诊断通道

我遇到过最坑的事:某次刷写过程中,ECU突然进入了编程会话,但没来得及关闭CAN报文发送。结果刷写到一半,ECU还在往外发心跳报文,把Bootloader的Flash擦写操作给打断了。从那以后,我强制要求在进入编程会话时,必须暂停所有非诊断通信。

4.2.3 扩展会话(Extended Session)

扩展会话是给工程师调试用的。我个人觉得这是最灵活、也最容易出问题的会话。

在扩展会话下:

  • 可以执行0x2E(写入数据)、0x31(例程控制)
  • 通常需要安全访问(0x27)
  • 可以做IO控制(0x2F)、读写内存(0x3D)等

举个例子:你在产线上想校准一个传感器,就需要先切到扩展会话,然后做安全访问,最后才能写校准参数。顺序错了,ECU就会给你回个NRC 0x22。

4.3 会话切换条件与状态机

会话切换不是你想切就能切的。UDS定义了一个严格的状态机,我画个表格给你看:

当前会话 目标会话 切换条件 备注
默认 编程 发送0x10 02 直接切换
默认 扩展 发送0x10 03 直接切换
编程 默认 发送0x10 01 或超时自动回退
扩展 默认 发送0x10 01 或超时自动回退
编程 扩展 不允许直接切换 必须先回默认
扩展 编程 不允许直接切换 必须先回默认

看到没?编程和扩展之间不能直接切。为什么?我猜设计者是想让你「冷静一下」——回默认会话确认一下状态,再决定下一步干啥。

4.4 超时机制与S3定时器

这里有个关键点——S3定时器。说白了,就是ECU会监控诊断通信是否活跃。

  • 默认会话下,S3定时器通常不启用(或者设得很长)
  • 编程和扩展会话下,S3定时器一般设为5秒
  • 如果5秒内没收到任何诊断请求,ECU自动切回默认会话

我踩过这个坑:有一次调试,我切到扩展会话后去上了个厕所,回来发现ECU已经回默认了。我还以为是程序跑飞了,查了半天才发现是S3超时了。嗯,从那以后我养成了习惯——长时间调试时,每隔3秒发个0x3E(Tester Present)保持会话。

小技巧:0x3E服务就是用来「续命」的。发送0x3E 00(不响应模式),ECU会重置S3定时器,但不会回复任何数据。这样既保持了会话,又不会占用总线带宽。

4.5 状态机实现要点

如果你要自己写会话状态机,我建议注意这几点:

  1. 状态切换必须是原子操作:切换过程中不能处理其他请求
  2. 进入新会话时要重置权限:比如安全访问状态、S3定时器
  3. 退出会话时要清理资源:比如关闭打开的文件、释放锁
  4. 记录会话切换日志:方便调试和问题定位

给你看个简单的状态机伪代码:

// 会话状态枚举
typedef enum {
    SESSION_DEFAULT = 0x01,
    SESSION_PROGRAMMING = 0x02,
    SESSION_EXTENDED = 0x03
} SessionType;

// 会话切换处理
uint8_t HandleSessionControl(uint8_t targetSession) {
    // 检查是否允许直接切换
    if (!IsTransitionAllowed(currentSession, targetSession)) {
        return NRC_CONDITIONS_NOT_CORRECT; // 0x22
    }
    
    // 退出当前会话
    ExitSession(currentSession);
    
    // 进入新会话
    EnterSession(targetSession);
    
    // 更新状态
    currentSession = targetSession;
    
    // 重置S3定时器
    ResetS3Timer();
    
    return NRC_POSITIVE_RESPONSE;
}

4.6 避坑指南

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

  • 不要忽略S3超时:有些ECU在编程会话下超时后,会直接复位。如果你没处理好,刷写到一半就前功尽弃了。
  • 注意会话切换的时序:发送0x10请求后,要等ECU回复了肯定响应,才能发下一个请求。别急着连发。
  • 扩展会话下别乱写:有些参数写错了,ECU可能直接死机。我建议在写之前先读一下当前值,确认一下。
  • 测试要覆盖边界条件:比如在编程会话下请求切到扩展,ECU应该回复什么?答案是NRC 0x22。

好了,关于诊断会话控制,今天就聊到这儿。下一章咱们聊聊安全访问(0x27),那个更有意思——你会看到ECU是怎么「防小人」的。