CANopen 工业协议
工业自动化与部分车载的标准化上层协议:以对象字典为核心,用 PDO/SDO/NMT/SYNC/EMCY/心跳等通信对象组织节点,COB-ID 预定义连接集与 NMT 状态机让即插即用成为可能。
工业自动化与部分车载的标准化上层协议:以对象字典为核心,用 PDO/SDO/NMT/SYNC/EMCY/心跳等通信对象组织节点,COB-ID 预定义连接集与 NMT 状态机让即插即用成为可能。
Index(16 位,如 0x1000)+ Subindex(8 位)定位。地址区段:0x1000–0x1FFF 通信参数、0x2000–0x5FFF 厂商自定义、0x6000–0x9FFF 设备 profile 标准对象。外界通过读写 OD 条目来配置和控制节点。0x600+NodeID、响应 0x580+NodeID。SDO 有确认机制、支持分段传输大数据,但开销大、速度慢,不适合实时。TPDO(发送 PDO)由节点主动发出,RPDO(接收 PDO)由节点接收。默认 COB-ID:TPDO1 0x180+NodeID、RPDO1 0x200+NodeID 等。COB-ID 0x000,2 字节:命令字 + 目标 NodeID(0 = 广播全部)。命令:0x01 启动(进 Operational)、0x02 停止、0x80 进预操作、0x81 复位节点、0x82 复位通信。CANopen 把 11 位 CAN ID 拆成「4 位功能码 + 7 位 NodeID」,功能码决定优先级与对象类型:
| 通信对象 | 功能码 | COB-ID 公式 | 方向 |
|---|---|---|---|
| NMT 控制 | 0000 | 0x000(广播) | 主→从 |
| SYNC | 0001 | 0x080(广播) | 主→全网 |
| EMCY | 0001 | 0x080 + NodeID | 从→主 |
| TPDO1 | 0011 | 0x180 + NodeID | 从→主 |
| RPDO1 | 0100 | 0x200 + NodeID | 主→从 |
| TPDO2 | 0101 | 0x280 + NodeID | 从→主 |
| RPDO2 | 0110 | 0x300 + NodeID | 主→从 |
| SDO 响应 | 1011 | 0x580 + NodeID | 从→主 |
| SDO 请求 | 1100 | 0x600 + NodeID | 主→从 |
| 心跳/Boot-up | 1110 | 0x700 + NodeID | 从→主 |
预定义连接集用 CAN 11 位 ID 的低 7 位作为 NodeID,7 位最大 127,NodeID=0 保留给广播。功能码占高 4 位,天然决定了优先级:功能码越小 ID 越小,仲裁时越优先。所以 NMT(0x000) 优先级最高、SYNC 次之、PDO 高于 SDO,恰好符合实时性需求。
OD 是节点参数的树状表格,每个 Index 可含多个 Subindex。下面是一个典型节点 OD 的骨架:
每个 CANopen 节点上电后按固定状态机运行,主站用 NMT 命令切换状态:
节点在 Pre-operational 态可以用 SDO 配置(读写 OD),但不会收发 PDO 过程数据。因此典型启动流程是:等待所有从站发出 Boot-up 报文 → 用 SDO 配置各节点 PDO 映射 → 主站广播 NMT 0x01 启动 → 节点进入 Operational 开始实时数据交换。若忘记发启动命令,PDO 永远不动是常见新手问题。
SDO 读(上传 Upload)和写(下载 Download)用固定的命令字格式。「快速传输 Expedited」适合 ≤4 字节数据,一来一回即完成。
Python 的 canopen 库封装了 SDO/PDO/NMT,导入 EDS 即可用符号名访问对象字典。
# canopen_demo.py —— SDO 读写 + PDO 配置 + NMT 启动 import canopen network = canopen.Network() network.connect(channel="can0", interface="socketcan") # 添加 NodeID=5 的从站,导入其 EDS node = network.add_node(5, "drive.eds") # ① SDO 读:读设备类型 0x1000(一来一回,可靠) dev_type = node.sdo[0x1000].raw print(f"设备类型: 0x{dev_type:08X}") # ② SDO 写:写控制字 0x6040:00 = 0x000F(使能运行) node.sdo[0x6040].raw = 0x000F # ③ 配置 TPDO1:映射实际位置 0x6064,事件触发 node.tpdo.read() # 读现有配置 node.tpdo[1].clear() node.tpdo[1].add_variable(0x6064) # 打包"实际位置" node.tpdo[1].trans_type = 255 # 255=异步事件触发 node.tpdo[1].event_timer = 100 # 每100ms发一次 node.tpdo[1].enabled = True node.tpdo.save() # 写回节点 OD # ④ NMT 启动:进入 Operational,PDO 开始收发 node.nmt.state = "OPERATIONAL" # ⑤ 接收 TPDO 回调 def on_position(msg): pos = node.tpdo[1]["Position actual value"].phys print(f"实际位置: {pos}") node.tpdo[1].add_callback(on_position) import time; time.sleep(5) network.disconnect()
配置阶段(改参数、设 PDO 映射)用 SDO——可靠有确认但慢;运行阶段(周期读位置、写目标速度)用 PDO——无开销无确认但快。一个正确的 CANopen 应用应该在 Pre-operational 阶段用 SDO 把一切配置妥当,进入 Operational 后只跑 PDO,避免运行时频繁 SDO 拖慢总线。
节点周期发心跳(0x700+ID,1 字节 = 当前 NMT 状态),主站超时未收到即判定掉线。写入 OD 0x1017 生产者心跳时间(ms)即启用。
# 启用节点心跳并监测在线状态 node.sdo[0x1017].raw = 500 # 每 500ms 发一次心跳 def on_heartbeat(state): names = {0: "Boot-up", 4: "Stopped", 5: "Operational", 127: "Pre-op"} print("节点状态:", names.get(state, state)) node.nmt.add_heartbeat_callback(on_heartbeat)
CANopen(CiA 301)是工业与部分车用的高层协议,核心是对象字典 OD——用 Index(16 位)+ Subindex(8 位)定位节点所有参数,分通信区(0x1000–0x1FFF)、厂商区(0x2000+)、profile 区(0x6000+)。通信对象各司其职:SDO(0x600/0x580+ID)点对点可靠读写 OD,配置用,慢;PDO(TPDO 0x180+ID / RPDO 0x200+ID)无开销实时传过程数据,其内容由映射参数(0x1600/0x1A00)灵活组合,传输类型决定同步/周期/事件触发;NMT(0x000)集中管理节点状态机(初始化→预操作→操作/停止),只有 Operational 才收发 PDO;SYNC(0x080)同步、EMCY(0x080+ID)报错、心跳(0x700+ID)监测在线。COB-ID = 功能码 + NodeID,NodeID 1–127。EDS 电子数据表描述设备 OD,工程工具导入即用。典型流程:等 Boot-up → SDO 配置 → NMT 启动 → PDO 实时运行。