3、诊断协议基础(UDS on CAN):UDS服务概述、诊断会话控制(0x10)、安全访问(0x27)、例程控制(0x31)
各位同学,欢迎来到第三章。
前面我们聊了CAN通信的基础,也搭好了CANoe的仿真环境。但说实话,光能收发报文,那只是车联网的“皮毛”。真正让ECU“听话”的,是诊断协议。今天我们就来啃一啃UDS on CAN这块硬骨头。
UDS,全称是Unified Diagnostic Services,统一诊断服务。它定义了一套标准化的“指令集”,让测试设备(比如我们的CANoe)能和ECU进行深度对话。说白了,就是ECU的“管理后台”。
我个人习惯把UDS服务分成三类:控制类(比如切换模式、复位)、读写类(读写数据、内存)、执行类(运行例程)。今天我们先挑三个最核心、也最常用的服务来解剖:0x10、0x27、0x31。
3.1 UDS服务概述:一个请求,一个响应
UDS的通信模型很简单,就是“请求-响应”。
测试器(Tester)发一个请求报文,ECU收到后,必须回复一个响应报文。如果请求成功,就回肯定响应;如果失败,就回否定响应(NRC,Negative Response Code)。
这里有个关键点:UDS报文是承载在CAN报文的数据场里的。通常一个UDS服务请求,会占用CAN报文的8个字节中的前几个字节。
一个典型的UDS请求报文格式是这样的:
| 字节0 | 字节1 | 字节2 | ... | 字节7 |
| PCI (协议控制信息) | SID (服务ID) | 子功能/数据 | ... |
举个例子,请求诊断会话控制(0x10)到默认会话(0x01):
CAN ID: 0x7E0 // 物理请求ID
数据: 02 10 01 00 00 00 00 00
// 02: 表示后续有2个有效字节
// 10: 服务ID
// 01: 子功能,表示切换到默认会话
ECU的响应报文格式类似:
CAN ID: 0x7E8 // 物理响应ID
数据: 06 50 01 00 32 01 F4 00
// 06: 表示后续有6个有效字节
// 50: 肯定响应,服务ID + 0x40
// 01: 子功能,确认切换到默认会话
// 00 32 01 F4: 会话参数(比如P2超时时间等)
嗯,这里要注意:肯定响应的SID = 请求SID + 0x40。比如请求0x10,肯定响应就是0x50。如果ECU回复0x7F,那就是否定响应,后面会跟一个字节的NRC。
3.2 诊断会话控制(0x10):ECU的“工作模式”
ECU不是永远都在“全功能”状态的。为了安全、省电,它设计了不同的会话模式。0x10服务就是用来切换这些模式的。
常见的会话模式有:
| 子功能 | 会话名称 | 说明 |
|---|---|---|
| 0x01 | 默认会话 (Default Session) | 上电后的初始模式,功能受限,通常只允许读一些基本信息 |
| 0x02 | 编程会话 (Programming Session) | 用于刷写软件,通信速率可能改变,安全等级最高 |
| 0x03 | 扩展会话 (Extended Session) | 用于诊断、标定,比默认会话开放更多服务 |
你想想看,为什么要有这些模式?
我在项目中遇到过一件事:有一次,一个ECU在默认会话下,死活不肯执行0x31例程。我查了半天手册,才发现——0x31服务在默认会话下是被禁用的。必须先切到扩展会话(0x03),才能执行。从那以后,我养成了一个习惯:做任何诊断操作前,先确认当前会话模式。
切换会话的流程很简单:
- Tester发送0x10 + 子功能(比如0x03)
- ECU回复0x50 + 子功能 + 会话参数(P2时间、P2*时间等)
- 如果ECU不支持该会话,会回复NRC 0x12(子功能不支持)或0x22(条件不满足)
3.3 安全访问(0x27):ECU的“门锁”
有些关键操作,比如刷写软件、解锁安全相关的配置,ECU不会让你随便碰。这时候就需要0x27服务——安全访问。
它的原理是“挑战-应答”。
流程是这样的:
- 请求种子: Tester发送0x27 + 子功能(比如0x01表示请求种子)
- ECU返回种子: ECU回复0x67 + 子功能 + 种子数据(通常是4个字节的随机数)
- 计算密钥: Tester根据种子,用约定的算法计算出密钥
- 发送密钥: Tester发送0x27 + 子功能+1(比如0x02)+ 密钥数据
- ECU验证: ECU内部用同样的算法计算密钥,比对。如果一致,回复0x67 + 子功能+1;否则回复NRC 0x35(无效密钥)
说白了,就是ECU给你出了一道题,你必须用正确的“公式”算出答案,才能开门。
这里有个坑:种子和密钥的算法是OEM(主机厂)保密的。不同项目、不同ECU,算法可能完全不同。有的用简单的异或,有的用AES加密,有的甚至用查表法。
// 伪代码示例
byte CalculateKey(byte seed[4]) {
// 假设算法是:密钥 = 种子 ^ 0x5A5A5A5A
dword key = (seed[0] << 24) | (seed[1] << 16) | (seed[2] << 8) | seed[3];
key = key ^ 0x5A5A5A5A;
// 返回密钥
return key;
}
当然,实际项目中算法要复杂得多。但思路是一样的:先拿到种子,再算密钥,最后发回去。
3.4 例程控制(0x31):让ECU“干活”
0x31服务,是让ECU执行一段预定义的“程序”。比如:擦除内存、检查软件完整性、执行自检等。
它有三个子功能:
| 子功能 | 名称 | 说明 |
|---|---|---|
| 0x01 | 启动例程 (Start Routine) | 让ECU开始执行某个例程 |
| 0x02 | 停止例程 (Stop Routine) | 强制停止正在执行的例程 |
| 0x03 | 请求例程结果 (Request Routine Results) | 查询例程的执行结果(比如成功/失败,以及返回数据) |
举个例子,启动一个“擦除内存”的例程:
请求: 02 31 01 FF 00 // 启动例程,例程ID = 0xFF00
响应: 06 71 01 FF 00 00 00 00 // 肯定响应,例程启动成功,状态为0x00(未完成?)
注意,例程ID(RID)是2个字节的,范围从0x0000到0xFFFF。具体每个ID代表什么功能,由OEM定义。
我遇到过最头疼的情况是:例程执行时间很长。比如擦除一个大的Flash块,可能需要几秒钟。这时候,ECU会在响应里告诉你“我还在忙”。
CANoe里怎么处理?我一般会用定时器轮询的方式:
- 发送0x31 01启动例程
- 收到肯定响应后,启动一个定时器,每隔100ms发送一次0x31 03(请求结果)
- 直到ECU返回“例程执行完成”的状态
好了,今天的内容就到这里。我们讲了UDS的三大核心服务:0x10(会话控制)、0x27(安全访问)、0x31(例程控制)。
下一章,我们会继续深入,聊聊UDS的读写服务(0x22、0x2E)和传输层(ISO 15765-2)。到时候,我们会用CANoe实际搭建一个完整的诊断仿真环境。
记住:诊断是ECU的灵魂,而CANoe是你解剖灵魂的手术刀。好好练,别怕犯错。