4. UDS协议在刷写中的应用:核心服务与机制

好,我们进入第四章。这一章可以说是整个刷写流程的“交通规则”——UDS协议。很多刚入行的工程师觉得UDS就是一堆服务ID,背下来就行了。其实不然。我个人习惯是把UDS看作一套“医生和病人的对话协议”,ECU是病人,诊断仪是医生,你想想看,医生要做什么?先问诊(读数据),再诊断(诊断故障),最后开刀(刷写)。

我在项目里见过太多人把34、36、37服务背得滚瓜烂熟,结果一上实车就报错——为什么?因为没搞懂会话状态和安全锁。这一章,我们就把这些核心服务拆开揉碎了讲清楚。

4.1 诊断会话控制(0x10服务)

ECU上电后默认处于默认会话(Default Session)。在这个状态下,很多刷写相关的服务是不开放的。你需要先切换到扩展会话(Extended Session)或编程会话(Programming Session)。

会话类型 子功能ID 典型用途 我遇到的坑
默认会话 0x01 上电后默认,只允许基本诊断 直接发34服务会收到NRC 0x78(请求正确但当前会话不支持)
扩展会话 0x03 读写参数、例程控制 有些ECU需要先切扩展才能解锁安全访问
编程会话 0x02 刷写专用,允许34/36/37等 切到编程会话后,很多非刷写服务会被禁用
注意: 会话切换不是瞬时的。ECU内部需要释放资源、重置状态机。我曾经遇到过连续发两次10 02导致ECU死锁的情况——因为第一次还没完成,第二次又来了。建议每次切换后等待至少50ms再发下一条。

4.2 安全访问机制(0x27服务)

刷写不是谁都能干的。安全访问机制就是ECU的“门锁”。你发一个seed请求,ECU给你一串随机数,你用算法算出key,再发回去。对上了,门就开了。

流程很简单:

  1. 发送 0x27 01(请求种子)
  2. ECU回复种子(4字节或8字节)
  3. 本地用算法计算key
  4. 发送 0x27 02(发送key)
  5. ECU验证通过,解锁

关键点: 安全算法通常是OEM自定义的。有的用AES,有的用CRC+异或,有的干脆就是个查表。我在一个项目里遇到过算法里藏了个时间因子——同一组seed在不同时间算出的key不一样,调试了三天才发现。

解锁后,ECU会保持解锁状态直到你切会话、下电或超时。超时时间一般是5秒,但有些ECU只有2秒。嗯,这里要注意:如果你刷写过程中耗时太长,需要定期发一些“保活”报文,比如0x3E 0x80(Tester Present)。

4.3 读取数据(0x22服务)与写入数据(0x2E服务)

这两个服务是“读写”搭档。22用来读,2E用来写。在刷写场景中,我们常用它们来:

  • 读取ECU的软件版本号(DID 0xF190等)
  • 读取VIN码
  • 写入刷写标志位
  • 写入配置信息

举个例子,读版本号:

请求:22 F1 90
响应:62 F1 90 01 02 03 04  // 版本号 01.02.03.04

写入配置:

请求:2E F1 90 01 02 03 04
响应:6E F1 90 01 02 03 04
小技巧: 2E服务在刷写前可以用来“通知”ECU即将进入刷写模式。比如写一个特定的DID值为0xAA,ECU收到后就知道要准备擦除Flash了。这个做法在博世和大陆的平台上很常见。

4.4 例程控制(0x31服务)

31服务是用来执行一段“小程序”的。在刷写中,它通常用来:

  • 擦除Flash(0xFF00)
  • 检查编程条件(0xFF01)
  • 复位ECU(0xFF02)

我个人习惯把31服务看作“ECU的API调用”。你传参数,它执行,返回结果。比如擦除Flash:

请求:31 01 FF 00  // 开始擦除
响应:71 01 FF 00  // 擦除成功
警告: 擦除操作不可逆。我曾经在测试时忘了检查擦除地址范围,结果把Bootloader区也擦掉了——ECU直接变砖。后来只能用BDM工具重新烧录。所以,31服务一定要加地址范围校验。

4.5 刷写核心三件套:34、36、37

这三个服务是刷写的核心。说白了就是:

  • 34(请求下载): 告诉ECU我要写数据了,这是数据的起始地址和长度
  • 36(传输数据): 一包一包地发数据
  • 37(请求退出传输): 告诉ECU数据发完了,你校验一下

标准流程:

  1. 34 00 44 00 08 00 00 10 00 // 请求下载,地址0x00080000,长度0x1000
  2. ECU回复 74 00 44 00 20 // 同意下载,块大小0x20(32字节)
  3. 36 01 00 [32字节数据] // 第一块
  4. 36 02 00 [32字节数据] // 第二块
  5. ... 重复直到发完
  6. 37 00 // 请求退出
  7. ECU回复 77 00 // 传输完成

注意块大小: 34服务的响应中会返回一个“块大小”(Block Size)。这个值决定了你每包36服务能发多少字节。CAN 2.0一帧最多8字节,但UDS允许连续帧(CF),所以块大小可以大于8。我见过最大块大小是4096字节(用CAN FD)。但别贪心——块越大,出错重传的成本越高。

4.6 知识体系总览

下面这张图把本章的核心服务串起来了。你可以看到,刷写流程就是一条流水线:先切会话,再解锁,然后读写配置,执行例程,最后用34/36/37完成数据传输。

UDS刷写服务流程总览 UDS刷写服务流程总览 诊断会话控制 0x10 → 编程会话 安全访问 0x27 → 解锁 读写数据 0x22 / 0x2E 先切会话 再解锁 例程控制 0x31 → 擦除/校验 请求下载 0x34 → 地址+长度 传输数据 0x36 → 分块发送 准备擦除 擦除完成 开始发送 请求退出传输 0x37 → 结束 数据发完 校验 → 复位(0x11) 刷写完成 ⚠ 常见错误 • 未切会话就发34 • 未解锁就发36 • 块大小超过ECU限制

4.7 避坑指南

最后,我把自己这些年踩过的坑总结一下:

  • 会话超时: 编程会话有超时时间,一般是5秒。如果你在刷写过程中不发任何报文,ECU会自动切回默认会话。我曾经在刷一个大文件时,中间计算校验花了3秒,结果ECU切回默认会话了,下一包36直接报错。
  • 安全访问重试: 大多数ECU限制安全访问重试次数。连续输错3次key,ECU会锁定几分钟甚至几小时。所以,测试时一定要先确认算法正确。
  • 34服务的地址对齐: 有些ECU要求起始地址必须按4字节或8字节对齐。不对齐的话,ECU会回复NRC 0x13(长度或格式错误)。
  • 36服务的序列号: 36服务的第一个字节是序列号(0x01~0xFF),循环使用。但有些ECU要求序列号必须连续,不能跳号。我见过一个ECU,序列号从0x01跳到0x03就直接报错——中间缺了0x02都不行。
我的建议: 在开发刷写工具时,一定要把UDS的响应超时、NRC处理、重试机制都做进去。不要假设ECU永远会正确响应。实际项目中,CAN总线上的干扰、ECU负载过高都可能导致响应延迟或丢失。一个健壮的刷写工具,应该能处理这些异常情况。