2、刷写协议详解:UDS协议中的34/36/37服务
各位同学,今天我们聊点硬核的——UDS刷写三剑客:34、36、37服务。
说实话,我刚入行那会儿,看到这三个服务号就头大。34、36、37,长得跟双胞胎似的,搞不清楚谁先谁后。后来被师傅骂了几次,才真正搞明白它们的逻辑关系。
说白了,这三个服务就是刷写的标准三步走:请求下载 → 传输数据 → 结束传输。你想想看,这不就跟我们下载文件一样吗?先跟服务器打个招呼说我要下载了,然后一段一段收数据,最后说声拜拜。
2.1 34服务:请求下载(Request Download)
34服务是刷写的第一步。它的作用就是告诉ECU:「嘿,我要开始写数据了,你准备好了吗?」
我个人习惯把这个服务叫做「握手服务」。为什么?因为它确实是在握手——客户端和服务端互相确认一下,看看能不能开始干活。
请求格式:
字节0: 34 (服务ID)
字节1: 数据格式 (bit7-4: 压缩方法, bit3-0: 加密方法)
字节2-3: 地址和长度格式信息
字节4-7: 内存起始地址 (根据格式信息长度可变)
字节8-11: 内存大小 (根据格式信息长度可变)
这里有个坑,我必须要提醒大家。字节1的数据格式,很多人容易搞混。0x00表示不压缩不加密,0x01表示只压缩不加密,0x11表示既压缩又加密。嗯,这里要注意,实际项目中绝大多数情况都是0x00,因为刷写文件本身就已经是压缩好的了。
响应格式:
字节0: 74 (肯定响应)
字节1: 最大数据块长度 (MaxNumberOfBlockLength)
这个最大数据块长度很关键。它告诉客户端:你每次最多能发多少字节。我在项目中遇到过,有的ECU只支持256字节,有的能支持4096字节。如果你发的数据块超过了这个限制,ECU会直接给你个否定响应。
2.2 36服务:数据传输(Transfer Data)
36服务是刷写的主体。说白了,就是真正干活的部分。
它的格式很简单:
字节0: 36 (服务ID)
字节1: 块序列号 (BlockSequenceCounter)
字节2-N: 数据块内容
块序列号从1开始,每发一包就加1。为什么要这个序列号?你想想看,如果网络不好,数据包丢了怎么办?ECU可以通过序列号判断有没有丢包。
我记得有一次调试,发现ECU刷写到一半就停了。查了半天,原来是序列号从0x01跳到了0x03,中间丢了0x02。ECU检测到序列号不连续,直接罢工了。
36服务的响应:
字节0: 76 (肯定响应)
字节1: 块序列号 (与请求中的序列号相同)
这里有个小技巧:每次收到76响应后,再发下一包。不要一口气发太多,否则ECU处理不过来。我建议的做法是:发一包,等响应,再发下一包。虽然慢一点,但稳定。
2.3 37服务:请求退出传输(Request Transfer Exit)
37服务是刷写的收尾工作。它的作用就是告诉ECU:「数据发完了,你可以开始处理了。」
格式超级简单:
字节0: 37 (服务ID)
没错,就一个字节。不需要任何参数。
响应格式:
字节0: 77 (肯定响应)
收到77响应后,ECU就会开始处理接收到的数据。比如校验完整性、写入Flash等。这个过程可能需要几秒钟,具体看数据量大小。
2.4 刷写流程与状态机
好了,三个服务都讲完了。现在我们把它们串起来,看看完整的刷写流程。
标准刷写状态机:
IDLE → 发送34服务 → WAIT_DOWNLOAD
WAIT_DOWNLOAD → 收到74响应 → 发送36服务 → DATA_TRANSFER
DATA_TRANSFER → 收到76响应 → 继续发送36服务 → DATA_TRANSFER
DATA_TRANSFER → 数据发送完毕 → 发送37服务 → WAIT_EXIT
WAIT_EXIT → 收到77响应 → IDLE (刷写完成)
这个状态机看起来简单,但实际项目中会有各种异常情况。比如:
- 发送34服务后,ECU返回否定响应怎么办?
- 数据传输过程中,ECU超时了怎么办?
- 发送37服务后,ECU一直不响应怎么办?
我个人的做法是:每个状态都设置超时时间。比如34服务超时设为5秒,36服务超时设为2秒,37服务超时设为10秒。超时后重试3次,如果还不行,就报错退出。
- 34服务:建立连接,获取最大数据块长度
- 36服务:分段传输数据,注意序列号连续性
- 37服务:结束传输,等待ECU处理完成
- 状态机:严格按顺序执行,处理异常情况
最后说一句,刷写协议看着简单,但实际调试起来坑很多。我建议大家在开发阶段,多用CANoe或者PCAN的日志功能,把每次交互的数据都记录下来。出了问题,翻翻日志,基本都能找到原因。
好了,这一章就到这里。下一章我们聊聊刷写中的安全机制——安全访问和校验和。这些东西在量产项目中特别重要,搞不好就会出大问题。