4. 刷写流程核心服务续:请求下载(0x34)、数据传输(0x36)、请求退出传输(0x37)、ECU复位(0x11)、读取数据(0x22)

好,咱们接着往下聊。上一章我们把刷写的前置服务讲透了,这一章进入真正的数据传输阶段。说白了,就是怎么把二进制文件塞进ECU的Flash里,以及塞完之后怎么收尾。

我个人习惯把这几个服务称为「刷写三兄弟」——0x34负责开门,0x36负责搬砖,0x37负责关门。再加上0x11复位和0x22读取,一套完整的刷写流程就闭环了。

4.1 请求下载(0x34)——告诉ECU:我要写数据了

0x34这个服务,是刷写的起点。它的作用就是告诉ECU:「嘿,我要开始写数据了,你准备好接收。」

请求报文格式很简单:

请求:34 [数据格式] [地址参数] [长度参数]
响应:74 [最大数据块长度]

这里有几个关键参数,我一个个说:

  • 数据格式:0x00表示数据是二进制,0x01表示带校验。我一般用0x00,省事。
  • 地址参数:你要写的起始地址。比如0x08000000,这是STM32的Flash起始地址。
  • 长度参数:你要写的总字节数。注意,这个长度是整段数据的长度,不是单包长度。

ECU收到0x34后,会返回一个0x74响应。响应里最重要的字段是最大数据块长度(MaxNumberOfBlockLength)。这个值决定了你每次0x36能发多少字节。

重点:最大数据块长度不是随便定的。它受限于ECU的RAM大小、Flash页大小、以及CAN帧的DLC限制。我见过有些ECU只给64字节,有些能给到4096字节。你想想看,如果ECU只支持64字节,你一次发1KB,那肯定报错。

避坑指南:我曾经遇到过一个项目,ECU返回的最大数据块长度是1024,但我实际测试发现超过512就丢包。后来查了半天,发现是CAN驱动层的DMA缓冲区没配够。所以,不要完全相信文档,实际测试一下最靠谱。

4.2 数据传输(0x36)——真正的搬砖工

0x36是刷写过程中调用最频繁的服务。它的任务很简单:把数据一块一块地发给ECU。

请求报文:

36 [块序列号] [数据...]

块序列号从1开始递增。ECU收到后,会返回一个0x76响应,里面包含当前块的序列号,表示确认收到。

这里有个细节:块序列号是循环的。0xFF之后是0x00,然后0x01。为什么?因为有些ECU的计数器只有8位。我刚开始做的时候没注意这个,结果序列号溢出后ECU直接拒绝接收,搞得我一脸懵。

实际开发中,0x36的调用频率很高。比如你要刷一个256KB的固件,每包发256字节,那就要发1024次0x36。这中间任何一个包丢了或者超时了,整个刷写就失败了。

注意:0x36的响应超时时间一般设为50ms~200ms。太短了容易误判丢包,太长了影响刷写速度。我一般设100ms,稳得很。

还有一个坑:数据对齐。有些ECU要求每包数据长度必须是4字节对齐,或者必须是Flash页大小的整数倍。如果不满足,ECU会直接报错。所以,发送前最好检查一下数据长度。

4.3 请求退出传输(0x37)——关门,收工

数据发完了,就该0x37出场了。它的作用就是告诉ECU:「数据发完了,你可以开始处理了。」

请求报文:

37

ECU收到后,会返回0x77。但注意,0x77不代表刷写成功,只代表ECU收到了退出请求。真正的刷写结果,需要后续通过0x22或者0x19来确认。

为什么要有0x37?你想想看,如果没有这个服务,ECU怎么知道数据发完了?它只能一直等,等到超时。那多尴尬。

我的习惯:发送0x37之后,不要立即发0x11复位。给ECU一点时间处理内部状态。我一般等200ms再发复位请求。曾经有个项目,0x37刚发完就复位,结果Flash写入还没完成,直接变砖。嗯,从那以后我就记住了这个教训。

4.4 ECU复位(0x11)——让新固件跑起来

0x11是UDS里最常用的服务之一。它的作用就是让ECU复位,重新启动。

请求报文:

11 [复位类型]

复位类型有三种:

复位类型 说明
硬复位 0x01 相当于断电重启,所有寄存器复位
软复位 0x03 只复位CPU,外设保持状态
快速复位 0x04 部分ECU支持,用于快速启动

刷写完成后,我一般用硬复位(0x01)。因为软复位可能残留一些状态,影响新固件的运行。

注意:0x11的响应是在复位前发送的。也就是说,ECU先回0x51,然后才复位。所以,不要指望0x11的响应能告诉你复位是否成功。复位后,你需要重新建立诊断连接。

还有一个细节:复位后的等待时间。不同ECU的启动时间不一样,有的几十毫秒,有的好几秒。我一般等3秒再发诊断请求,确保ECU已经跑起来了。

4.5 读取数据(0x22)——刷写后的体检

0x22是UDS里最灵活的服务之一。它可以读取任意DID(数据标识符)的值。在刷写场景中,我常用它来验证刷写结果。

请求报文:

22 [DID高字节] [DID低字节]

比如读取ECU的软件版本号:

请求:22 F1 00
响应:62 F1 00 [版本数据...]

刷写完成后,我一般会读取以下几个DID:

  • 软件版本号:确认新固件已经刷进去了
  • Flash校验和:确认数据完整性
  • ECU状态:确认ECU正常运行

我的经验:0x22不仅可以用来验证刷写结果,还可以用来诊断问题。比如刷写失败后,读取ECU的错误状态DID,往往能直接定位问题原因。我曾经遇到一个刷写失败案例,查了半天没头绪,最后用0x22读了一个DID,发现是Flash写保护没关。嗯,有时候问题就是这么简单。

4.6 刷写流程总结

好了,把这几个服务串起来,一个完整的刷写流程就是:

  1. 0x10 03(扩展会话)
  2. 0x27(安全访问)
  3. 0x31(例程控制,擦除Flash)
  4. 0x34(请求下载)
  5. 0x36(数据传输,重复多次)
  6. 0x37(请求退出传输)
  7. 0x11(ECU复位)
  8. 0x22(读取数据,验证结果)

每一步都有它的意义,少一个都不行。你想想看,如果没有0x34,ECU不知道你要写哪里;如果没有0x36,数据传不过去;如果没有0x37,ECU不知道数据发完了;如果没有0x11,新固件跑不起来;如果没有0x22,你都不知道刷没刷成功。

下一章,我会讲刷写过程中的错误处理和超时机制。这部分内容,说白了就是「出问题了怎么办」。相信我,这部分内容在实际项目中比正常流程更重要。因为正常流程谁都会写,但能把异常处理做好,才是真功夫。