4、UDS服务之诊断会话控制(0x10):会话切换(默认/扩展/编程)、会话状态机、超时处理

4.1 会话控制到底在控制什么?

诊断会话控制,服务ID是0x10。说白了,它就是ECU的「工作模式切换器」。

ECU平时在车上跑,不能一直开着诊断功能。你想想看,如果ECU一边控制发动机喷油,一边等着你发诊断指令,那不乱套了?所以UDS定义了三种会话状态,让ECU知道「现在该干嘛」。

我个人习惯把会话理解成「权限等级」:

  • 默认会话(Default Session):上电后的初始状态,只能做基本读取操作。
  • 扩展会话(Extended Session):允许一些特殊操作,比如读写参数、执行例程。
  • 编程会话(Programming Session):最高权限,用于刷写固件、更新软件。

我在项目中遇到过一件事:有个同事在默认会话下直接发0x2E写数据,结果ECU一直回复NRC 0x78(请求正确接收,但响应待定)。他查了半天,最后发现是会话没切过来。嗯,这种坑踩过一次就记住了。

4.2 三种会话的切换规则

会话切换不是你想切就能切的。UDS协议里有一套严格的状态机规则。我画了个简化的逻辑:

当前会话 目标会话 是否允许 备注
默认 扩展 ✅ 允许 最常见切换
默认 编程 ✅ 允许 需安全访问配合
扩展 默认 ✅ 允许 回到初始状态
扩展 编程 ✅ 允许 常见刷写流程
编程 默认 ✅ 允许 刷完必须切回
编程 扩展 ✅ 允许 部分ECU支持

这里有个关键点:从编程会话切回默认会话时,ECU通常会执行一次硬件复位。我刚开始做诊断时不知道这个,刷完固件直接断电,结果ECU变砖了。后来才知道,必须发0x10 01让ECU自己复位,才能正常启动新程序。

4.3 会话状态机:ECU的「大脑切换」

ECU内部维护着一个会话状态机。每次收到0x10请求,它都会检查当前状态和目标状态是否合法。

举个例子,你发了一条报文:

发送:02 10 03
解释:请求切换到编程会话(sub-function = 0x03)

ECU会这样处理:

  1. 检查当前会话是否允许切换到编程会话。
  2. 如果允许,回复肯定响应:06 50 03 00 32 01 F4(后面四个字节是P2和P2*定时参数)。
  3. 如果不允许,回复NRC 0x78(或0x22,条件不满足)。

我记得有一次调试,ECU一直回复0x78。我查了半天,发现是安全访问没通过。编程会话要求必须先解锁安全等级,否则直接拒绝。这个设计其实很合理——防止有人乱刷固件。

核心要点:会话切换不是简单的「发一条指令就完事」。ECU会检查前置条件,比如安全访问状态、点火状态、车速等。任何条件不满足,切换都会失败。

4.4 超时处理:ECU的「耐心」是有限的

ECU不会一直等你。每个会话都有两个超时参数:

  • P2(Server):ECU处理单个请求的最大时间。默认50ms。
  • P2*(Server):扩展超时,用于耗时操作(比如擦除Flash)。默认5000ms。

当ECU切换到某个会话后,如果长时间没有收到任何诊断请求,它会自动切回默认会话。这个时间叫S3 Server,通常是5秒。

为什么会这样?你想想看,如果诊断仪突然掉线了,ECU一直留在编程会话里,那车还能开吗?所以超时机制是一种安全保护。

我在CANoe里调试时,经常遇到这种情况:

// 发送会话切换请求
diagRequest ECU 0x10 03;

// 等待响应
// ... 5秒过去了,没收到任何请求

// ECU自动切回默认会话
// 下次发诊断指令时,发现会话已经变了

嗯,这里要注意:超时计时器是连续滚动的。每次收到有效诊断请求,计时器都会重置。所以如果你在编程会话里持续发指令,ECU会一直留在编程模式。

避坑指南:我曾经在刷写固件时,因为文件传输太慢,中间间隔超过了S3超时时间。结果ECU切回了默认会话,后续的刷写指令全部失败。后来我加了一个「心跳机制」,每2秒发一条TesterPresent(0x3E),问题就解决了。

4.5 实战:CANoe中如何测试会话切换

在CANoe里,我一般用CAPL脚本来模拟诊断仪的行为。下面是一个简单的例子:

// 切换到扩展会话
diagRequest ECU 0x10 02;

// 等待肯定响应
if (diagGetLastResponseCode() == 0x50)
{
  write("会话切换成功,当前为扩展会话");
  
  // 执行一些扩展会话下的操作
  diagRequest ECU 0x22 F1 90;  // 读取VIN
}
else
{
  write("会话切换失败,错误码:0x%x", diagGetLastResponseCode());
}

我个人习惯在测试脚本里加一个超时监控:

// 启动超时定时器
setTimer(S3_Timer, 4000);  // 4秒后触发

// 每次收到诊断响应,重置定时器
on diagResponse ECU
{
  write("收到响应,重置S3定时器");
  setTimer(S3_Timer, 4000);
}

// 超时处理
on timer S3_Timer
{
  write("警告:S3超时,ECU可能已切回默认会话");
  // 重新发送会话切换请求
  diagRequest ECU 0x10 01;
}

这个脚本在实际项目中帮我抓到了不少问题。有一次客户反馈ECU偶尔会「死机」,我加上这个监控后发现,其实是S3超时导致会话切换,而后续的指令还在用旧的会话ID发,自然就收不到正确响应了。

4.6 总结一下

诊断会话控制看似简单,但坑不少。我总结几个关键点:

  • 切换前检查前置条件:安全访问、点火状态、车速等。
  • 注意超时时间:S3超时会自动切回默认会话。
  • 编程会话结束后必须复位:发0x10 01让ECU自己重启。
  • 用TesterPresent保持会话:长时间操作时,每2秒发一次0x3E。

嗯,会话控制是UDS诊断的基础。搞懂了它,后面的0x27安全访问、0x34请求下载才能顺利展开。下一章我们聊聊安全访问——那个让无数工程师头疼的「种子与密钥」机制。