3. SDO通信机制与报文格式

SDO(Service Data Object)是 CoE 中用于访问对象字典“单个条目”的通信方式,与 PDO 的周期性过程数据不同,SDO 是请求—应答式的非周期通信,适合读写参数、配置、诊断信息等。理解 SDO,关键是抓住两条主线:一是访问流程(初始化、分段、完整访问),二是报文格式(命令字、索引、子索引、数据区)。把这两条主线打通,就能自己实现一个 SDO 读取工具。

一、SDO 的两种访问方式

对象字典条目的数据长度决定了 SDO 采用哪种访问方式。若数据长度不超过 4 字节,可用快速 SDO(Expedited SDO),数据直接塞进报文的数据区,一次请求—应答即可完成;若数据长度超过 4 字节,则用分段 SDO(Segmented SDO),先交换长度信息,再按段传输,每段最多 7 字节有效数据。此外还有完整访问(Complete Access),它允许一次访问某个对象下的全部子索引,常用于读取整个数组或记录型对象。

判断依据:数据长度 ≤ 4 字节走快速访问;> 4 字节走分段访问;需要整体读写多子索引对象时用完整访问。

二、SDO 报文结构

一个 SDO 报文由 8 字节 CANopen 数据帧承载,结构如下:

字节字段说明
0命令字(CCS/SCS)区分上传/下载、快速/分段、请求/应答
1–2索引(Index)对象字典主索引,小端
3子索引(SubIndex)对象下的子项编号
4–7数据区快速访问时存放数据或长度信息

命令字是 SDO 的“语义核心”。以客户端(主站)发起为例,上传(读)请求命令字为 0x40;下载(写)请求则根据数据长度使用 0x23(4 字节)、0x27(3 字节)、0x2B(2 字节)、0x2F(1 字节)。服务器(从站)应答时,命令字会带上“快速/分段”和“数据长度”标志,例如上传应答 0x43 表示快速访问且数据 4 字节,0x4B 表示 2 字节,0x4F 表示 1 字节。分段传输时,命令字中的 bit0 表示“还有后续段”,bit4 表示“切换位(toggle)”,用于保证段序正确。

三、上传(读取)流程

以读取 0x1018:01(厂商 ID,4 字节)为例,主站发送上传请求:

请求: 40 18 10 01 00 00 00 00
      ↑  ↑     ↑
      |  |     └─ 子索引 01
      |  └─ 索引 0x1018(小端 18 10)
      └─ 命令字 0x40(上传请求)

应答: 43 18 10 01 78 56 34 12
      ↑           ↑
      |           └─ 数据区:0x12345678(厂商 ID)
      └─ 命令字 0x43(快速上传应答,4 字节)

若对象数据超过 4 字节,应答命令字会变为 0x41(分段上传初始化应答),数据区给出总长度;随后主站发送段请求 0x60,从站以 0x00/0x10 等命令字返回每段 7 字节数据,直到最后一段命令字 bit0 为 0 表示结束。

四、下载(写入)流程

写入 0x1018:01 时,主站发送下载请求,命令字按数据长度选择:

请求: 23 18 10 01 78 56 34 12
      ↑           ↑
      |           └─ 待写入数据
      └─ 命令字 0x23(下载请求,4 字节)

应答: 60 18 10 01 00 00 00 00
      ↑
      └─ 命令字 0x60(下载应答,成功)

若写入失败,应答命令字为 0x80,数据区返回 4 字节中止码(Abort Code),例如 0x06010002 表示“试图写只读对象”,0x06070010 表示“数据类型不匹配”。中止码是排障时最重要的线索。

五、完整访问(Complete Access)

完整访问通过命令字中的 bit4(CA 位)标识。上传时命令字为 0x40 | 0x10 = 0x50,下载时为 0x23 | 0x10 = 0x33。它一次读取对象下所有子索引,数据按子索引顺序拼接,长度由对象定义决定。完整访问适合批量读取 PDO 映射、同步管理器配置等结构化对象,能显著减少报文往返。

SDO 上传/下载流程与报文格式 上传(读取)流程 主站:上传请求 0x40 从站:应答 0x43/0x41 数据 ≤4B:直接返回 数据 >4B:分段请求 0x60 分段:每段 7 字节,toggle 位交替 下载(写入)流程 主站:下载请求 0x23 从站:应答 0x60 失败:0x80 + 中止码 中止码示例:0x06010002 只读对象 SDO 报文 8 字节结构 命令字 Byte 0 索引 Index Byte 1–2(小端) 子索引 Byte 3 数据区 Data Byte 4–7(快速访问数据 / 分段长度) 命令字关键位: bit4 = CA(完整访问) bit0 = 后续段标志 bit4 = toggle 切换位 bit1–3 = 数据长度编码

六、实现一个 SDO 读取工具

下面给出一个基于 Python 的 SDO 读取工具核心逻辑,假设底层已有 EtherCAT 主站库可发送/接收 CANopen 帧。工具接收索引、子索引,自动判断快速或分段访问,并解析应答。

def sdo_upload(index, subindex):
    # 构造上传请求:命令字 0x40 + 索引小端 + 子索引
    req = bytes([0x40, index & 0xFF, (index >> 8) & 0xFF, subindex, 0, 0, 0, 0])
    resp = send_sdo(req)          # 底层发送并等待应答
    cmd = resp[0]

    if cmd == 0x43:               # 快速上传,4 字节
        return int.from_bytes(resp[4:8], 'little')
    elif cmd == 0x4B:             # 快速上传,2 字节
        return int.from_bytes(resp[4:6], 'little')
    elif cmd == 0x4F:             # 快速上传,1 字节
        return resp[4]
    elif cmd == 0x41:             # 分段上传初始化
        total = int.from_bytes(resp[4:8], 'little')
        data = b''
        toggle = 0
        while True:
            seg_req = bytes([0x60 | (toggle << 4), 0, 0, 0, 0, 0, 0, 0])
            seg = send_sdo(seg_req)
            data += seg[1:8]      # 每段 7 字节有效数据
            if not (seg[0] & 0x01):   # bit0=0 表示最后一段
                break
            toggle ^= 1
        return int.from_bytes(data[:total], 'little')
    elif cmd == 0x80:             # 中止
        abort = int.from_bytes(resp[4:8], 'little')
        raise Exception(f"SDO Abort: 0x{abort:08X}")
    else:
        raise Exception(f"未知命令字: 0x{cmd:02X}")

使用示例:读取 0x1018:01 厂商 ID,调用 sdo_upload(0x1018, 0x01),若返回 0x12345678,说明读取成功。若抛出中止码 0x06010002,则说明该对象只读,需检查是否误用了下载操作。

七、关键判断与排障要点

  • 命令字不匹配:请求与应答命令字必须成对出现,若收到 0x80 应立即解析中止码。
  • 长度判断:快速访问数据区有效字节数由命令字 bit2–3 决定,解析时不能固定取 4 字节。
  • 分段 toggle:每段 toggle 位必须交替,若从站返回 toggle 错误,说明段序丢失,需重传。
  • 完整访问:CA 位置 1 后,数据区长度可能超过 7 字节,需按对象定义的总长度循环接收。
  • 字节序:索引、子索引、数据均按小端解析,跨平台实现时务必统一。
注意:SDO 是请求—应答式通信,同一时刻一个 SDO 通道只能有一个未完成的事务。若在分段传输中途插入其他 SDO 请求,会导致 toggle 错乱或中止码 0x05040000(SDO 协议超时)。

掌握以上流程与报文格式后,SDO 读取工具的核心就是“构造请求—发送—解析命令字—按长度取数据—处理分段与中止”。实际工程中,建议把命令字、索引、子索引、数据区封装成结构体,并记录每次请求的原始报文,便于对照从站手册排查问题。