首页 / 驱动算法 / CANopen专题

CANopen 从站嵌入式实现与总线负载率

把前两课的概念落成固件:STM32 上怎么起一个 CANopen 从站?CANopenNode 框架怎么移植?PDO/SDO/心跳/节点 ID 怎么配?最后用总线负载率计算器,把"这条总线能挂多少轴"算得明明白白。

1. 从站架构总览

一个 CANopen 从站的固件骨架就四层:

  1. CAN 驱动:收发报文、错误处理、波特率配置
  2. 协议核心:对象字典 + NMT/SDO/PDO/心跳/EMCY 处理
  3. 应用层:你的电机控制、IO、传感器逻辑,读写 OD
  4. 实时循环:SYNC 驱动的控制循环 / PDO 收发

通信协议部分尽量别手写——直接用成熟开源栈 CANopenNode,把精力放在应用层。

2. 硬件:MCU + 收发器

⚠️ 波特率与采样点

波特率要全网络一致,采样点(典型 75%~87.5%)影响长线缆下的鲁棒性。1Mbps 总线长度建议 ≤ 40m,125k 可到 500m 左右。位时间 = 同步段 + 传播段 + 相位段1 + 相位段2,改采样点要重算各段。

3. CANopenNode:别重复造轮子

CANopenNode 是 C 语言写的开源 CANopen 协议栈(含 CiA 301 + 部分 402),结构清晰,适合 STM32 移植。核心文件:

文件职责
CO_driver / CO_driver_target底层 CAN 收发封装(各平台移植点)
CO_OD.c / CO_OD.h对象字典(生成器产出,配 EDS)
CO_NMT_HeartbeatNMT 状态机 + 心跳 + 节点守护
CO_SDOserverSDO 服务端(单帧/分段/块传输)
CO_PDOPDO 通信/映射参数与收发
CO_EMCY紧急报文
CO_SYNC同步
CO_GFC / CO_LSS网关 / 节点ID 配置(可选)

开发流程通常是:用 EDS 编辑器(如 EDS Editor / 文本)写对象字典 → 生成 CO_OD.c/h → 移植 CO_driver_target → 在主循环/中断里周期调用 CO_CANopen_process 和 CO_... 各模块。

4. 节点初始化流程

  1. 读节点 ID 与波特率(拨码/EEPROM/OD)
  2. 初始化 CAN 外设、进入复位/预操作
  3. 配置 PDO 通信参数(COB-ID、传输类型)与映射(先禁后改再启)
  4. 配置心跳周期(OD 1017h,0=关闭)并启动心跳发送
  5. 启动 TPDO 触发(周期定时器或事件)
  6. 等待主站 NMT 命令进入 Operational

5. 交互式演示:总线负载率计算器

调整节点数、PDO 周期、心跳周期等,实时估算总线负载率。工程上建议把稳态负载控制在 30%~50% 以下,给 EMCY/突发 SDO 留裕量。

CANopen 总线负载率计算器
总线负载率 ≈ 7.3% · TPDO 占 6.5% · 心跳 0.4% · SYNC 0.4%

单帧位时间估算:标准帧 8 字节 ≈ 47 + 8·DLC + 填充裕量 ≈ 121 位。负载率 = Σ(每秒帧数 × 每帧位数) / 波特率。试想:8 个从站各 1ms 发一帧 8 字节,500k 下负载已到 19%——这就是为什么高节拍系统要用 1Mbps 或缩短数据。

6. 代码实现(C语言)

6.1 CANopenNode 初始化骨架

/* 简化示意:对象字典、NMT、心跳、PDO、SDO 实例化 */ static CO_t CO; /* 协议栈主结构 */ static uint8_t OD[2048]; /* 对象字典存储区 */ void canopen_init(uint8_t node_id, uint32_t baud) { can_init(baud); /* 底层 CAN 波特率 */ CO_ReturnError_t err = CO_init(&CO, &CO_CANmodule, &CO_CANrx, &CO_CANtx, OD, sizeof(OD), node_id); if (err != CO_ERROR_NO) { fault_handler(err); } /* 心跳周期:OD 1017h 写 100(单位 ms,0=关闭) */ CO_OD_setU16(0x1017, 0, 100); /* 预操作状态,等待主站 NMT */ CO_NMT_setNMTState(&CO.NMT, NMT_PRE_OPERATIONAL); }

6.2 主循环:协议处理 + 周期任务

/* 主循环:越短越好,协议栈处理 + 周期/事件任务 */ void main_loop(void) { while (1) { CO_CANopen_process(&CO); /* 处理接收队列(RX) */ CO_NMT_process(&CO.NMT, 0, CO_Time()); CO_EMCY_process(&CO.EMCY); CO_Heartbeat_process(&CO.Heartbeat, 0); if (CO_NMT_getInternalState(&CO.NMT) == NMT_OPERATIONAL) { run_control(); /* 应用:FOC/IO 控制 */ CO_PDO_process(&CO.PDO, CO_Time()); /* 周期 PDO 发送 */ } /* 周期 TPDO 触发:每 5ms */ if (CO_Time() - last_tpdo > 5) { CO_PDO_sendTPDO(&CO.PDO, 1); last_tpdo = CO_Time(); } } } /* RX/TX 中断:把报文交给协议栈(平台相关) */ void CAN_RX_IRQHandler(void) { CO_CANmodule_process(&CO.CANmodule); CO_CANrxMsg_t *msg = CO_CANrxMsgRead(&CO.CANmodule); if (msg) CO_CANrxBufferRead(&CO.CANmodule, msg); CO_CANsendProcess(&CO.CANmodule); }

6.3 总线负载率估算

/* 标准帧 8 字节位时间 ≈ 121 位(含填充裕量) */ float bus_load(uint32_t baud, uint32_t nodes, uint32_t tpdo_period_ms, uint32_t hb_period_ms, uint32_t sync_period_ms) { const float BITS = 121.0f; /* 8 字节标准帧 */ float fps = 0; fps += nodes * (1000.0f / tpdo_period_ms); /* TPDO */ fps += nodes * (1000.0f / hb_period_ms); /* 心跳 */ if (sync_period_ms > 0) fps += 1000.0f / sync_period_ms; /* SYNC */ return (fps * BITS) / (float)baud; /* 负载率 0~1 */ }

7. 工程要点与常见误区

要点 1:负载率留裕量

稳态周期报文控制在 30%~50% 以下,剩下的给 EMCY、SDO 配置和主站突发请求。CAN 满负载会大幅增加仲裁延迟和错误重发概率。

要点 2:采样点与线缆

高速率 + 长线缆要调采样点(75%~87.5%)、加终端电阻、注意地电位。CAN 是差分但不是隔离的,长距离或跨设备建议加隔离收发器。

要点 3:PDO 映射先禁后改

改映射前把 1F80h(节点保护)/通信参数有效位清 0,改完置 1,再进 Operational。现场"改了没生效"多半是没走这套流程。

要点 4:心跳是维护的命脉

主站通过心跳检测从站掉线;从站也要监控 SYNC 是否还在(同步超时),丢失同步要按配置安全停机,不能继续瞎跑。

误区 1:手写协议栈

CANopen 状态机+SDO 分段+块传输的坑远超想象。用成熟栈(CANopenNode、Canfestival)成熟可靠,把时间花在应用层。

误区 2:所有数据都走 SDO

SDO 只用于配置/诊断。周期性实时数据必须 PDO,否则延迟和总线占用不可控。

误区 3:波特率整网不一致也能凑合

CAN 波特率不一致整网无法通信;换从站、改线后要先核对波特率与采样点。

误区 4:只加 PDO 不算负载

每加一个周期 PDO 就占带宽。设计网络先算负载率(用上面的公式),再定节拍,别等现场堵车才回头。

8. 小结

  1. 从站 = CAN 驱动 + 协议核心 + 应用层 + 实时循环;协议栈用 CANopenNode 这类成熟开源实现
  2. 硬件三件套:CAN 控制器 + 收发器 + 终端电阻,波特率整网一致、采样点按线缆调
  3. 初始化流程:节点 ID/波特率 → 协议栈实例化 → PDO 配置 → 心跳 → 等 NMT 进 Operational
  4. 主循环里周期调 CO_CANopen_process + 各模块,Operational 后才跑应用
  5. 总线负载率要预算:8 字节帧 ≈121 位,稳态控制在 30%~50% 以下

至此 CANopen 专题三篇闭环:协议基础(字典与通信对象)→ CiA 402(状态机与模式)→ 从站实现与负载预算。回到 CANopen 专题目录,或继续 FOC 主线的其余高级篇。