第2章:诊断协议基础:ISO 14229 (UDS) 协议栈详解、ISO 15765 (CAN诊断) 传输层、诊断会话与安全访问
好,我们直接进入正题。诊断协议这块,说白了就是车上的ECU怎么跟外部的诊断仪“说话”。你想想看,一辆车几十个ECU,每个ECU都有自己的脾气,如果没有一套统一的语言,那诊断仪根本没法干活。这套语言,就是UDS——统一诊断服务。
2.1 ISO 14229 (UDS) 协议栈详解
UDS,全称是 Unified Diagnostic Services。它定义了一套标准化的服务,比如读故障码、写数据、执行例程等等。我个人习惯把UDS协议栈想象成一个“七层汉堡”,虽然实际应用中我们通常只关心应用层和一部分网络层。
UDS的核心服务分类:
- 诊断和通信管理类: 比如 0x10(诊断会话控制)、0x11(ECU复位)、0x3E(测试仪在线)。这些是“握手”和“保活”的服务。
- 数据传输类: 比如 0x22(通过ID读取数据)、0x2E(通过ID写入数据)。这是最常用的,读传感器值、写配置参数都靠它。
- 存储数据传输类: 比如 0x14(清除诊断信息)、0x19(读取故障码信息)。专门对付DTC的。
- 输入输出控制类: 比如 0x2F(输入输出控制)。这个很实用,可以强制让某个执行器动作,比如喷油嘴、风扇。
- 远程激活例程类: 比如 0x31(例程控制)。用来执行一段预定义的程序,比如“自学习”、“刷写”等。
避坑指南: 我曾经在一个项目中,发现0x22服务读取某个数据时,ECU总是返回否定响应。查了半天,原来是该数据只在特定会话下才可读。嗯,这里要注意,UDS的每个服务都有其“生效条件”,不是你想读就能读的。
UDS报文结构:
一个典型的UDS请求报文,包含三个部分:
- 服务标识符(SID): 比如 0x22,表示“读取数据”。
- 子功能(Sub-function): 可选,比如 0x10服务的子功能 0x01 表示“默认会话”,0x02 表示“编程会话”。
- 数据参数(Data): 具体的请求数据,比如要读取的数据ID。
举个例子,读取数据ID为 0xF190 的请求报文:
请求: 22 F1 90
响应: 62 F1 90 [数据字节]
你看,请求是 0x22 + 2字节数据ID。响应是 0x62(SID+0x40表示成功响应)+ 数据ID + 实际数据。
2.2 ISO 15765 (CAN诊断) 传输层
UDS是应用层的协议,它本身不关心数据怎么在CAN总线上传输。这个脏活累活,交给了ISO 15765。说白了,ISO 15765就是UDS在CAN总线上的“快递员”。
为什么需要传输层?
CAN报文一帧最多只能带8个字节数据。但UDS的请求或响应可能很长,比如刷写一个固件,数据量可能几百KB。这时候就需要把长数据拆成多个CAN帧发送,接收端再拼回去。ISO 15765就是干这个的。
四种传输机制:
| 类型 | 描述 | 典型场景 |
|---|---|---|
| 单帧(SF) | 数据长度 ≤ 7字节(CAN 2.0标准帧) | 读一个短数据,比如读VIN码 |
| 首帧(FF) | 数据长度 > 7字节,发送第一个帧,包含总长度 | 长数据发送的开始 |
| 连续帧(CF) | 后续的数据帧,按顺序编号 | 长数据的中间部分 |
| 流控帧(FC) | 接收端告诉发送端:“慢点发”或“继续发” | 防止接收端缓冲区溢出 |
个人经验: 我记得有一次调试刷写功能,ECU总是刷到一半就超时。后来抓CAN报文发现,是发送端连续帧发得太快,ECU的接收缓冲区满了,流控帧没来得及处理。解决方案很简单:在发送端增加一个“发送间隔”,或者严格遵循流控帧的“块大小”和“最小间隔时间”。
ISO 15765的寻址方式:
CAN诊断有两种寻址模式:
- 物理寻址(点对点): 诊断仪直接跟某个特定ECU通信。CAN ID通常是 0x7DF(请求)和 0x7E8(响应)之类的。
- 功能寻址(广播): 诊断仪发一个报文,所有支持该服务的ECU都要响应。CAN ID通常是 0x7DF。这个在“唤醒”所有ECU时很有用。
2.3 诊断会话与安全访问
ECU不是“裸奔”的。它有不同的“会话模式”,以及一把“锁”。
诊断会话:
ECU通常支持三种会话:
- 默认会话(Default Session): 上电后的初始状态。只能执行一些“安全”的服务,比如读VIN、读故障码。不能写数据或刷写。
- 编程会话(Programming Session): 用于刷写固件。在这个会话下,可以执行0x31例程控制(比如擦除、写入Flash)。
- 扩展会话(Extended Session): 用于更高级的诊断,比如读写校准参数、执行特殊例程。比默认会话权限高,但比编程会话低。
切换会话通过 0x10 服务实现:
请求: 10 01 // 切换到默认会话
响应: 50 01 // 成功
请求: 10 03 // 切换到扩展会话
响应: 50 03 // 成功
注意: 会话切换是有“超时”的。如果ECU在扩展会话下长时间(比如5秒)没有收到任何诊断请求,它会自动跳回默认会话。这是为了防止诊断仪断开后,ECU一直处于高权限状态。
安全访问(Security Access):
有些关键操作,比如刷写、写校准参数,需要先“解锁”ECU。这个解锁过程就是安全访问,通过 0x27 服务实现。
流程一般是这样的:
- 请求种子(Seed): 诊断仪发 0x27 01(请求种子)。ECU返回一个随机数(种子)。
- 发送密钥(Key): 诊断仪根据种子,通过一个算法计算出密钥,发 0x27 02(发送密钥)。
- 验证: ECU用同样的算法计算密钥,如果匹配,则解锁成功,返回 0x67 02。
你想想看,这个算法是保密的。不同车企、不同ECU可能用不同的算法。我曾经见过一个项目,安全算法是用一个查表法加上CRC校验,简单但有效。
// 伪代码示例:安全访问算法
uint32_t CalculateKey(uint32_t seed) {
// 简单的异或+加法,实际项目会复杂得多
uint32_t key = seed ^ 0xA5A5A5A5;
key += 0x12345678;
return key;
}
避坑指南: 我曾经遇到一个ECU,安全访问总是失败。后来发现,ECU在收到错误的密钥后,会进入一个“延迟锁定”状态,必须等待一段时间才能再次请求种子。这个机制是为了防止暴力破解。所以,如果你的诊断仪连续失败,别急着重试,先等一等。
好了,这一章的内容就到这里。诊断协议是汽车软件的“通用语言”,理解UDS和ISO 15765,你就能跟ECU“对话”了。下一章,我们聊聊诊断栈的软件架构设计。