4. UDS诊断协议:ISO 14229-1基础、诊断会话控制、安全访问、例程控制

好,咱们进入第四章。这一章要啃的是UDS诊断协议,也就是ISO 14229-1。说实话,搞Bootloader升级,UDS就是你的“通用语言”。没有它,ECU和诊断仪之间就是鸡同鸭讲。

我最早接触UDS是在一个商用车项目上。那时候刚入行,看着一堆0x10、0x27、0x31的十六进制数,头都大了。后来摸透了才发现,UDS的套路其实很固定。说白了,它就是一套“请求-响应”的对话规则。

4.1 UDS协议栈与OSI模型

先看一张图,把UDS在CAN总线里的位置搞清楚。

UDS在OSI模型中的位置 应用层 (Layer 7) UDS诊断服务 (ISO 14229-1) 表示层 (Layer 6) 数据传输格式定义 会话层 (Layer 5) 诊断会话控制 (0x10) 传输层 (Layer 4) ISO 15765-2 (TP层) 网络层 (Layer 3) CAN 2.0 / CAN FD 关键说明 • UDS是应用层协议 • 底层依赖CAN传输 • 会话层控制状态机 • 传输层处理分包 • 网络层定义帧格式 Bootloader关注点: 1. 0x10 会话控制 2. 0x27 安全访问 3. 0x31 例程控制 4. 0x34 请求下载 5. 0x36 数据传输 6. 0x37 退出传输

从图里能看出来,UDS在第七层,也就是应用层。它不关心底层是CAN 2.0还是CAN FD,也不管数据怎么分包。这些脏活累活,都交给下面的传输层和网络层去干。

我个人习惯把UDS理解成“ECU的API”。你调用不同的服务ID(SID),ECU就执行对应的操作。比如0x10是切换会话,0x27是解锁安全等级,0x31是运行一段程序。

4.2 诊断会话控制 (0x10)

ECU上电后,默认处于一个“默认会话”。在这个会话里,很多诊断服务是被禁止的。你想刷写程序?不行。你想读内存?也不行。必须先切换到正确的会话模式。

会话控制服务0x10,就是用来干这个的。它的请求格式很简单:

请求: 0x10 + 子功能
响应: 0x50 + 子功能 + 附加参数

常见的子功能有这些:

子功能 会话名称 典型用途 我见过的坑
0x01 默认会话 上电后的初始状态 很多服务在这里不可用
0x02 编程会话 Bootloader升级专用 必须切到这个会话才能刷写
0x03 扩展诊断会话 读故障码、标定等 有些ECU不支持这个会话
0x04 安全系统诊断会话 安全相关功能 很少用,但遇到就头疼
我的经验: 编程会话(0x02)是Bootloader升级的“入场券”。如果ECU不响应0x10 02,或者响应了负响应码0x7F,那后面的所有操作都白搭。我曾经在一个项目上,因为CAN总线负载率太高,ECU没收到会话切换请求,排查了整整一天。后来加了重试机制,问题才解决。

4.3 安全访问 (0x27)

你想想看,如果谁都能随便刷写ECU,那车还不得乱套?所以UDS设计了安全访问机制。说白了,就是一把锁。你得先证明你有钥匙,ECU才让你碰它的内存。

安全访问的流程是一个“挑战-应答”模式:

  1. 请求种子: 诊断仪发0x27 + 01(请求种子),ECU返回一个随机数(种子)。
  2. 计算密钥: 诊断仪用种子和算法算出密钥。
  3. 发送密钥: 诊断仪发0x27 + 02 + 密钥,ECU验证。
  4. 验证结果: 密钥正确,ECU返回0x67 + 02;错误,返回0x7F。

这里有个关键点:种子和密钥的算法,是OEM自己定义的。ISO 14229只规定了流程,没规定算法。所以不同车企的算法千奇百怪。

// 伪代码示例:安全访问流程
// 步骤1: 请求种子
Tx: 0x27 0x01
Rx: 0x67 0x01 [Seed_Byte0] [Seed_Byte1] ... [Seed_Byte3]

// 步骤2: 计算密钥 (算法由OEM定义)
Key = CalculateKey(Seed);

// 步骤3: 发送密钥
Tx: 0x27 0x02 [Key_Byte0] [Key_Byte1] ... [Key_Byte3]
Rx: 0x67 0x02  // 成功
// 或
Rx: 0x7F 0x27 0x35  // 失败,NRC=0x35 (无效密钥)
避坑指南: 我曾经遇到过一个ECU,它的安全访问算法里有个隐藏的“时间窗口”。从收到种子到发送密钥,必须在100ms内完成。超过这个时间,ECU会认为你在重放攻击,直接拒绝。后来我们在上位机里加了定时器,确保密钥计算和发送在80ms内搞定。

4.4 例程控制 (0x31)

例程控制,说白了就是让ECU执行一段预置好的程序。在Bootloader升级里,这个服务特别有用。比如擦除Flash、检查CRC、复位ECU,都可以通过例程控制来实现。

例程控制有三个子功能:

子功能 含义 典型例程ID 说明
0x01 启动例程 0xFF00 擦除Flash扇区
0x02 停止例程 0xFF01 停止正在运行的例程
0x03 请求例程结果 0xFF02 检查例程执行状态

举个例子,擦除Flash的流程:

// 启动擦除例程
Tx: 0x31 0x01 0xFF 0x00 [StartAddress_H] [StartAddress_L] [Length_H] [Length_L]
Rx: 0x71 0x01 0xFF 0x00  // 例程已启动

// 等待一段时间后,请求结果
Tx: 0x31 0x03 0xFF 0x00
Rx: 0x71 0x03 0xFF 0x00 [Status]  // Status=0x00 表示成功
核心要点: 例程控制是Bootloader升级的“执行器”。你通过0x34请求下载、0x36传输数据,但真正把数据写入Flash的,是0x31启动的“编程例程”。我建议你把例程ID和对应的功能整理成一张表,放在代码注释里,方便后期维护。

4.5 负响应码 (NRC)

UDS里,不是所有请求都能成功。当ECU无法执行某个服务时,它会返回一个负响应。格式是0x7F + 请求SID + NRC。

常见的NRC码:

NRC 含义 常见原因
0x10 一般拒绝 请求格式错误
0x11 不支持 ECU不支持这个服务
0x12 子功能不支持 会话不对,或子功能无效
0x22 条件不满足 比如没切到编程会话
0x31 请求超出范围 地址或长度参数错误
0x35 无效密钥 安全访问密钥错误
0x36 尝试次数超限 安全访问失败次数太多
0x37 超时 操作时间窗口已过
0x78 请求正确-响应待定 ECU正在处理,稍后重试
调试技巧: 当收到0x7F时,别慌。先看NRC码,再查当前会话状态。我遇到过最坑的情况是,ECU返回0x78(响应待定),上位机没处理这个NRC,直接超时报错。后来加了“等待-重试”逻辑,问题就解决了。

4.6 本章小结

UDS诊断协议是Bootloader升级的“骨架”。0x10控制会话状态,0x27保障安全访问,0x31执行具体操作。这三板斧用好了,升级流程就顺了。

嗯,这里要注意:不同OEM对UDS的实现有差异。比如有些ECU要求先切到扩展会话,再切到编程会话;有些则可以直接切。所以拿到一个项目,第一件事就是看诊断规范文档,别想当然。