4. SDO块传输协议详解:块下载与块上传流程

SDO块传输,说白了就是CANopen里用来搬大块数据的一种特殊机制。普通SDO一次只能传4个字节,你要是想更新个几百KB的固件,那得发几千次请求,效率低得让人抓狂。块传输就是为解决这个问题而生的。

我记得第一次接触块传输是在一个OTA升级项目里。当时客户要求通过CAN总线升级固件,我一开始用的普通SDO分段传输,结果升级一次要40多分钟。后来换成块传输,直接压缩到5分钟以内。嗯,差距就是这么大。

4.1 为什么需要块传输?

先看个对比,你就明白了:

特性 普通SDO SDO块传输
单次有效数据 4字节 最多7字节
协议开销 高(每个包都要应答) 低(整块应答)
适合场景 小数据量配置 大数据量传输
典型应用 读写对象字典条目 固件升级、批量参数下载

你想想看,普通SDO每发一个请求,就得等对方回一个应答。一来一回,总线利用率其实很低。块传输的思路是:我把数据攒成一整块,一口气发出去,最后统一确认。这样总线利用率就上去了。

4.2 块下载流程详解

块下载,就是主机往从机写数据。流程分三个阶段:

核心流程: 初始化 → 数据传输 → 结束确认

4.2.1 初始化阶段

主机发送块下载初始化请求,告诉从机:我要开始写数据了。这个请求里包含:

  • 目标对象字典索引和子索引
  • 总数据长度(可选)
  • 建议的块大小(Blksize)

从机收到后,回复一个块下载初始化应答,里面带上它实际能接受的块大小。这里有个坑——我遇到过从机说它能收32块,结果实际跑起来丢包严重。后来发现是硬件缓冲区不够大,建议你根据实际RAM大小来定Blksize。

4.2.2 数据传输阶段

这个阶段是核心。主机按块发送数据,每块包含多个CAN帧。每个帧的结构如下:

CAN ID: 0x600 + NodeID (发送)
数据域:
  Byte 0: 控制字 (0x00 ~ 0x7F 表示数据帧)
  Byte 1~7: 有效数据 (最多7字节)

块内帧的序号从0开始,到Blksize-1结束。主机发完一整块后,会等待从机的应答。从机回复一个块应答,里面包含:

  • 应答序列号(ACKSeq)
  • 请求的下一块大小(Blksize)
  • 错误状态(如果有)

我的经验: 实际项目中,我习惯把Blksize设成16或32。太小了协议开销大,太大了出错重传成本高。16是个不错的折中值。

4.2.3 结束阶段

主机发完所有数据后,发送块下载结束请求。从机回复块下载结束应答,整个流程就完成了。

如果中间有错误,从机会在块应答里报告。主机收到错误后,可以选择重传当前块或者终止传输。

4.3 块上传流程详解

块上传是反过来的——主机从从机读数据。流程类似,也是三个阶段:

4.3.1 初始化阶段

主机发送块上传初始化请求,指定要读的对象字典条目。从机回复时,会带上:

  • 数据总长度(如果知道的话)
  • 建议的块大小

这里有个细节:从机可能不知道数据总长度。比如你读一个动态生成的日志,长度是运行时才确定的。这种情况下,从机在初始化应答里把长度字段设为0,主机就得靠结束帧来判断传输是否完成。

4.3.2 数据传输阶段

从机主动发送数据帧,主机负责接收和确认。每发完一块,从机会等待主机的块应答。主机在应答里告诉从机:

  • 我收到了哪些帧(通过ACKSeq)
  • 下一块你发多大(Blksize协商)

我个人习惯在主机端实现一个超时机制。如果从机发完一块后,主机没及时应答,从机会一直等。我曾经遇到过从机死等导致总线卡死的情况,后来加了看门狗才解决。

4.3.3 结束阶段

从机发完所有数据后,发送块上传结束请求。主机确认后,回复块上传结束应答。

4.4 块大小协商机制

Blksize协商是块传输里最容易出问题的地方。说白了,就是主机和从机商量:一次发多少帧算一块?

协商过程是这样的:

  1. 主机在初始化请求里提出一个建议值
  2. 从机根据自己的能力回复一个实际值
  3. 传输过程中,任何一方都可以在应答里调整Blksize

注意: Blksize的取值范围是1~127。0是保留值,表示不支持块传输。128~255是特殊用途,普通应用不要用。

为什么要动态调整?我举个例子:

假设从机一开始说能收32块。但传输过程中,主机发现总线错误率很高,连续重传了好几次。这时候主机可以在下一个块应答里把Blksize降到8,减少每块的数据量,降低重传成本。

反过来,如果总线质量很好,从机也可以主动提高Blksize,提高传输效率。

4.5 块传输状态机

下面这张图是我自己画的块传输状态机,涵盖了块下载和块上传的完整流程:

SDO块传输状态机 空闲 (Idle) 初始化 (Init) 数据传输 (Data) 等待应答 (WaitAck) 结束 (End) 主机请求 从机应答 发完一块 收到应答 重传/调整Blksize 说明: 块下载和块上传共用同一套状态机,区别在于数据传输方向不同。 Blksize协商可以在初始化阶段和传输阶段动态进行。

4.6 实际项目中的避坑指南

做块传输的时候,有几个坑我踩过,分享给你:

坑1:缓冲区溢出

我曾经在一个项目里,从机说能收32块,但实际硬件FIFO只有16帧深。结果主机发到第17帧时,从机直接丢帧。后来我强制在初始化阶段把Blksize限制在硬件缓冲区大小以内。

坑2:超时处理

块传输里,等待应答的时间不能太长也不能太短。我一般设50ms,如果总线负载高可以适当放宽到100ms。太短了容易误判丢包,太长了影响效率。

建议: 在从机端实现一个块传输超时计数器。如果超过3秒没收到任何主机请求,自动退出块传输状态,回到空闲状态。这样可以防止总线卡死。

4.7 块传输与普通SDO的切换策略

实际项目中,我建议这样用:

  • 小于4字节的数据: 用普通SDO,简单直接
  • 4字节到1KB的数据: 用普通SDO分段传输,或者块传输都行
  • 大于1KB的数据: 必须用块传输,否则效率太低

你想想看,如果每次读写对象字典都用块传输,反而会增加不必要的协议开销。所以我的做法是:在应用层根据数据长度自动选择传输方式。

嗯,块传输的内容就这些。核心就是记住三个字:初始化、传输、结束。Blksize协商是灵魂,一定要根据硬件能力和总线质量动态调整。别死脑筋设个固定值就不管了。