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请求后,会:

  1. 检查当前会话是否允许切换到目标会话
  2. 如果可以,回复肯定响应(0x50 + SubFunc)
  3. 然后内部切换状态

这里有个关键点:响应是在切换前发的,还是切换后发的? 答案是:先发响应,再切换。所以,你收到肯定响应后,不要立刻发下一个请求,等个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的“权限管理”。

  • 默认会话:只读,安全
  • 扩展会话:读写,需解锁
  • 编程会话:刷写,最高权限

切换时注意时序,注意安全策略。嗯,就这些。

最后一句: 我见过太多工程师在会话切换上翻车了。要么是没等响应就发下一个请求,要么是切了会话忘了重新解锁。记住:慢一点,稳一点,诊断通信急不得