4、诊断会话控制(0x10):默认会话、编程会话、扩展会话
诊断会话控制,服务ID是0x10。说白了,它就是ECU的“工作模式切换器”。
你想想看,ECU平时在车上跑,不可能一直开着“写程序”的权限吧?那多危险。所以UDS协议定义了不同的会话状态,每个状态开放不同的权限。
我个人习惯把0x10服务比作“门禁卡”。不同等级的卡,能进不同的房间。
4.1 三种核心会话状态
UDS里定义了三种最常用的会话。我直接给你列出来:
| 会话类型 | SID + SubFunc | 典型用途 | 我见过的坑 |
|---|---|---|---|
| 默认会话 (Default) | 0x10 0x01 | 正常通信,只读诊断 | 很多ECU上电后默认就是它 |
| 编程会话 (Programming) | 0x10 0x02 | 刷写Bootloader/APP | 刷写时千万别断电 |
| 扩展会话 (Extended) | 0x10 0x03 | 读写参数、执行例程 | 有些ECU需要先切扩展才能写数据 |
核心要点: 默认会话是“安全模式”,编程会话是“工厂模式”,扩展会话是“维修模式”。
4.2 默认会话(Default Session)
这是ECU上电后的初始状态。说白了,就是“啥也干不了,只能看看”。
在默认会话下,你能做的诊断服务非常有限。比如读取故障码(0x19)、读取数据(0x22),但写数据(0x2E)、执行例程(0x31)这些“危险操作”是被禁止的。
嗯,这里要注意:默认会话是唯一一个不能主动切换回去的会话。什么意思?ECU内部有定时器,如果一段时间没收到诊断请求,它会自动跳回默认会话。这叫“会话超时”。
我的经验: 我在调试一个T-Box项目时,发现ECU总是莫名其妙跳回默认会话。查了半天,原来是测试脚本里忘了发“保持激活”的报文。从那以后,我习惯在循环里加一个0x3E服务(Tester Present),防止会话超时。
4.3 编程会话(Programming Session)
这个会话权限最高。主要用于刷写固件。
进入编程会话后,ECU会关闭一些非必要的通信,比如CAN报文收发可能会暂停。为什么?因为刷写时CPU要全力处理Flash写入,没空管别的。
我记得有一次,客户反馈说刷写时总线报错。我一看日志,原来是编程会话下ECU没回NM报文,导致网络管理认为节点离线了。解决方案?在刷写前先发一条“抑制NM报文”的指令。
警告: 编程会话下,ECU的看门狗(Watchdog)可能会被关闭或延长。如果刷写失败,ECU可能“死机”。所以,刷写流程里一定要有“失败回退”机制。我曾经见过一个项目,刷写中途断电,ECU直接变砖,最后只能拆下来用编程器救。
4.4 扩展会话(Extended Session)
扩展会话是我用得最多的一个。它介于默认和编程之间,权限适中。
在扩展会话下,你可以做大部分“非刷写”的操作。比如:
- 写参数(0x2E)
- 执行例程(0x31)
- 安全访问(0x27)
- 通信控制(0x28)
为什么需要扩展会话?因为有些操作太危险,不能在默认会话下做。比如你正在开车,突然有人通过诊断仪把ESP的参数改了,那不出事了吗?
避坑指南: 我曾经遇到一个ECU,它要求必须先进入扩展会话,然后才能进行安全访问(0x27)。如果你在默认会话下直接发0x27,ECU会回复NRC 0x78(请求正确接收,但响应待定),然后就没下文了。所以,先切会话,再解锁,最后操作,这个顺序别搞反了。
4.5 会话切换时序
会话切换不是瞬间完成的。ECU需要时间处理。
一般来说,ECU收到0x10请求后,会:
- 检查当前会话是否允许切换到目标会话
- 如果可以,回复肯定响应(0x50 + SubFunc)
- 然后内部切换状态
这里有个关键点:响应是在切换前发的,还是切换后发的? 答案是:先发响应,再切换。所以,你收到肯定响应后,不要立刻发下一个请求,等个10ms左右,等ECU内部状态稳定。
我的习惯: 在CANoe的CAPL脚本里,我一般会在发送0x10后,加一个TestWaitForDiagResponse,然后等100ms再发后续报文。别问为什么是100ms,问就是经验。有些ECU切换慢,50ms不够用。
4.6 安全策略
会话控制本身不涉及安全,但它和安全访问(0x27)紧密相关。
简单说:先切会话,再解锁。你可以在默认会话下解锁,但解锁后如果切到其他会话,解锁状态可能会丢失。为什么?因为有些ECU设计成“每个会话独立管理解锁状态”。
举个例子:你在默认会话下解锁了,然后切到扩展会话。扩展会话的解锁状态是“未解锁”。你需要重新发0x27。这个设计很坑,但确实存在。
注意: 有些ECU在编程会话下,会强制要求“安全解锁”才能刷写。而且,编程会话的解锁种子(Seed)和默认会话不一样。我遇到过最变态的一个ECU,编程会话的种子是动态的,每次都不一样,需要算一个复杂的算法才能得到Key。
4.7 CANoe中的实现示例
在CANoe里,用CAPL实现会话切换很简单。我给你一个模板:
// 切换到扩展会话
diagRequest ECU_DiagObject.SessionControl_Extended req;
diagResponse ECU_DiagObject.SessionControl_Extended resp;
// 发送请求
req.Send();
// 等待响应
if (TestWaitForDiagResponse(resp, 1000) == 1) {
if (resp.ServiceResult == 0x50) {
write("扩展会话切换成功");
} else {
write("切换失败,NRC: 0x%02x", resp.ServiceResult);
}
} else {
write("超时,ECU没响应");
}
这段代码很简单。但实际项目中,我建议你封装成一个函数。比如:
void SwitchToSession(byte sessionType) {
// 构造请求
// 发送
// 等待
// 判断结果
}
这样,你在测试脚本里直接调用就行,不用每次都写一遍。
4.8 总结
诊断会话控制,说白了就是ECU的“权限管理”。
- 默认会话:只读,安全
- 扩展会话:读写,需解锁
- 编程会话:刷写,最高权限
切换时注意时序,注意安全策略。嗯,就这些。
最后一句: 我见过太多工程师在会话切换上翻车了。要么是没等响应就发下一个请求,要么是切了会话忘了重新解锁。记住:慢一点,稳一点,诊断通信急不得。