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会这样处理:
- 检查当前会话是否允许切换到编程会话。
- 如果允许,回复肯定响应:
06 50 03 00 32 01 F4(后面四个字节是P2和P2*定时参数)。 - 如果不允许,回复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请求下载才能顺利展开。下一章我们聊聊安全访问——那个让无数工程师头疼的「种子与密钥」机制。