3、刷写核心服务:34h/36h/37h服务详解、数据传输流程、块大小与时序控制

好,咱们进入刷写最核心的部分了。34h、36h、37h这三个服务,说白了就是UDS刷写的三驾马车。你搞懂了它们,远程刷写就通了八成。我个人习惯把这仨服务叫做“开门、搬货、关门”。

3.1 34h服务:请求下载(开门)

34h服务,全称“Request Download”。它的任务就是告诉ECU:“嘿,我要开始给你传数据了,你准备好没?”

这个服务的请求报文长这样:

| Byte | 参数名          | 描述                          |
|------|-----------------|-------------------------------|
| 0    | 34h             | 服务ID                        |
| 1    | 数据格式         | bit0-3: 压缩方法, bit4-7: 加密方法 |
| 2-3  | 地址和长度格式信息 | 指定后续地址和长度的字节数        |
| 4-n  | 内存地址         | 刷写起始地址                    |
| n+1-m| 内存大小         | 要刷写的数据总长度               |

嗯,这里要注意一个坑。地址和长度格式信息这个字节,很多人会搞错。它决定了后面地址和长度各占几个字节。比如0x24,表示地址占2字节,长度占4字节。

我曾经踩过的坑: 有一次我传了0x24,但实际地址只用了1个字节。ECU直接给我回了NRC 0x13(消息长度错误)。后来我养成了习惯,每次都在上位机里硬编码检查这个字节。

ECU收到34h后,如果一切正常,会回复一个肯定响应:

| Byte | 参数名          | 描述                          |
|------|-----------------|-------------------------------|
| 0    | 74h             | 肯定响应ID                     |
| 1    | 最大块大小       | ECU一次能接收的最大数据长度       |

这个“最大块大小”非常关键。它决定了你后面36h服务每次能传多少数据。说白了,ECU告诉你:“我胃口就这么大,你一次别喂太多。”

3.2 36h服务:传输数据(搬货)

36h服务,全称“Transfer Data”。这就是真正搬砖的活了。你拿着34h拿到的“最大块大小”,把数据切成一块一块,用36h服务发过去。

请求报文结构:

| Byte | 参数名          | 描述                          |
|------|-----------------|-------------------------------|
| 0    | 36h             | 服务ID                        |
| 1    | 块序列号         | 从1开始递增                    |
| 2-n  | 数据             | 实际要写入的数据                |

块序列号从1开始,每次加1。ECU会检查这个序列号,防止丢包或乱序。我见过有些工程师图省事,序列号随便填,结果ECU直接拒绝写入。

我的经验: 块序列号最好用uint16,别用uint8。为什么?因为有些大文件,块数可能超过255。我遇到过项目里用uint8,结果刷写到一半序列号溢出,ECU直接罢工。

ECU的肯定响应很简单:

| Byte | 参数名          | 描述                          |
|------|-----------------|-------------------------------|
| 0    | 76h             | 肯定响应ID                     |
| 1    | 块序列号         | 当前成功接收的块序列号           |

这里有个小细节。ECU返回的序列号,是你发过去的那个。如果ECU返回的序列号和你预期的不一样,说明出问题了。我建议你在上位机里加个校验,每次收到响应后比对一下序列号。

3.3 37h服务:请求退出传输(关门)

37h服务,全称“Request Transfer Exit”。数据传完了,你得告诉ECU一声:“活干完了,你收尾吧。”

请求报文:

| Byte | 参数名          | 描述                          |
|------|-----------------|-------------------------------|
| 0    | 37h             | 服务ID                        |

就这么简单,没有额外参数。ECU收到后,会做两件事:

  • 检查接收到的数据完整性(比如CRC校验)
  • 把数据从临时缓冲区写入真正的Flash区域

肯定响应:

| Byte | 参数名          | 描述                          |
|------|-----------------|-------------------------------|
| 0    | 77h             | 肯定响应ID                     |
注意: 37h服务不是可选的。有些工程师觉得数据传完了就行,不发送37h。结果ECU一直处于传输状态,下次刷写直接失败。我刚开始做刷写时也犯过这个错,后来被测试同事追着骂了一周。

3.4 数据传输流程:完整链路

好了,三个服务都讲完了。咱们串起来看看完整流程:

  1. 诊断会话控制:先切到扩展会话(10 03)
  2. 安全访问:解锁ECU(27 01/27 02)
  3. 请求下载:发送34h,拿到最大块大小
  4. 传输数据:循环发送36h,每次不超过最大块大小
  5. 请求退出传输:发送37h,结束传输
  6. 检查状态:用22h服务读取刷写状态

你想想看,这个流程其实和咱们平时下载文件很像。先建立连接(34h),然后分块下载(36h),最后关闭连接(37h)。

3.5 块大小与时序控制

块大小,就是34h响应里那个“最大块大小”。它受什么影响?

  • ECU的RAM大小:缓冲区越大,块大小可以越大
  • CAN总线负载:块太大,会占用总线太久
  • Flash写入速度:ECU写入Flash需要时间,块太大可能超时

我建议的块大小范围:

总线类型 建议块大小 说明
CAN 2.0 4-8字节 CAN帧数据场最大8字节
CAN FD 64字节 CAN FD数据场最大64字节
以太网 512-4096字节 取决于ECU的TCP/IP栈

时序控制这块,说白了就是“别太快,也别太慢”。

  • 发送间隔:我一般设10-20ms。太快了ECU处理不过来,太慢了刷写时间太长
  • 超时时间:ECU处理36h服务,一般要求在50ms内响应。超过这个时间,上位机应该重发
  • 重试次数:我习惯设3次。超过3次还失败,直接报错,别死循环
避坑指南: 我曾经在一个项目里,块大小设成了64字节,但ECU的Flash写入时间需要100ms。结果每次发完36h,ECU还在写Flash,下一包就来了,直接丢包。后来我把发送间隔改成了150ms,问题解决。

嗯,这里还要提一下流控制。在CAN总线上,如果ECU处理不过来,它会发一个流控制帧(FC),告诉上位机“慢点”。上位机必须遵守这个流控制,否则ECU会丢包。

我个人习惯在代码里加一个动态调整机制。如果连续收到3次流控制,就自动降低发送速率。如果连续10次没有流控制,就适当提高速率。这样既能保证稳定性,又能提高刷写效率。

好了,34h/36h/37h这三个服务,加上块大小和时序控制,就是刷写的核心。你把这些搞明白了,剩下的就是具体实现细节了。下一章咱们聊聊刷写中的错误处理和诊断,那才是真正考验工程师水平的地方。