lely-canopen-rtt:为什么推荐在 RT-Thread 上使用 Lely CANopen 构建主站
摘要:面向 RT-Thread MCU 的 CANopen 主站选型,比较 Lely、CANopenNode、CanFestival 与商业栈,并说明本软件包的架构、静态对象字典生成和使用流程。
@[toc]
推荐判断
如果项目的目标是在 RT-Thread MCU 上实现一个真正承担网络管理职责的 CANopen 主站,而不是只做几条 NMT 命令和简单 SDO 读写,我更推荐以 Lely CANopen 为协议栈核心,再通过 lely-canopen-rtt 这一层完成 RT-Thread 适配。
推荐它的原因并不是“Lely 支持 CANopen,所以可以用”,而是它的设计方式和主站需求比较匹配:Lely 本身把 NMT Master、远端节点 boot、配置请求、SDO Client、PDO、SYNC、EMCY、TIME 等能力组织成完整的 CANopen 服务对象;dcfgen 又能够从网络 YAML 和各个从站的 EDS/DCF 生成 Master DCF,把节点身份、Heartbeat、启动策略、PDO 映射和配置数据放到网络配置模型中;dcf2c 进一步把对象字典转换成 C 静态描述,使 MCU 不需要在运行时解析文本 DCF。
lely-canopen-rtt 在这个基础上解决了另一个实际问题:Lely 上游的 EV/IO2/CANopen 模型如何安全地运行在 RT-Thread 的 CAN 驱动、定时器、线程和 ISR/callback 环境中。软件包没有把上游协议栈改造成一套新的 CANopen,而是保留 Lely 的异步模型,并通过 single-owner runtime 把所有 Lely 对象收口到一个专用线程中执行。
因此,它比较适合下面这类项目:
- MCU 是 CANopen 网络中的控制器、Manager 或传统意义上的主站;
- 需要管理多个伺服、I/O 模块、编码器或其他 CANopen 从站;
- 启动阶段需要检查节点、配置参数、监控 Heartbeat,并决定何时进入 Operational;
- 运行阶段既有周期 PDO,又有 SDO 参数访问、EMCY 故障处理和 NMT 控制;
- 希望把网络拓扑和对象字典尽量放在 Host 侧生成,而不是在 MCU 代码里手写大量索引、COB-ID 和启动状态机;
- 项目使用 RT-Thread,希望协议栈和 CAN 驱动、定时器、应用线程之间有明确的并发边界。
如果只是做一个小型 CANopen 从站,或者主站仅需要“发 NMT + 偶尔做一次 SDO”,CANopenNode 往往更直接;如果项目已经使用 CanFestival 并且现有设备、对象字典和 DS402 示例都围绕它建立,也没有必要为了换协议栈而重写;如果项目要求商业技术支持、CANopen FD、Safety 或明确的认证交付,则商业 CANopen Master Stack 可能比开源方案更合适。
所以这篇文章的结论不是“Lely 在任何场景都比其他协议栈好”,而是:当 RT-Thread 设备要承担较完整的 CANopen 主站职责时,Lely 的网络管理能力、Host 配置生成能力和异步架构,与 lely-canopen-rtt 的 single-owner RTOS 适配组合在一起,形成了一条比较完整的工程路径。
为什么 CANopen 主站不能只看“支持 NMT、SDO、PDO”
很多 CANopen 协议栈的功能列表看起来都差不多:NMT、SDO、PDO、Heartbeat、EMCY、SYNC 基本都有。仅按功能名称做选型,很容易得到“几个协议栈都差不多”的结论。
但主站和从站真正的复杂度并不在同一个位置。
对于一个普通 CANopen 从站,核心任务通常是维护自己的对象字典,响应 SDO,收发 PDO,处理 NMT 状态和 Heartbeat。对象字典主要描述“我这个设备有什么数据和通信参数”。
主站面对的是整个网络。除了自己也是一个 CANopen 节点之外,它还需要知道:
- 网络里应该有哪些节点;
- 哪些节点必须存在,哪些节点允许缺席;
- 节点启动后是否需要 Reset Communication;
- 是否需要检查 Vendor-ID、Product Code、Revision、Serial Number;
- 哪些参数需要在 boot 阶段通过 SDO 下载;
- Heartbeat producer/consumer 如何配置;
- 主站什么时候启动从站进入 Operational;
- 从站 TPDO 应映射到主站哪个 RPDO;
- 某个节点掉线、重启或重新 Boot-up 后,应用侧状态如何恢复;
- 运行阶段的 SDO 请求如何与自动 NMT boot/configuration 共用默认 SDO 通道。
这就是为什么主站选型时,我更关注“有没有网络管理模型”和“网络配置如何进入固件”,而不是只看有没有 sendNMT()、SDO_Read() 之类的 API。
CiA 301 本身也不是一个单一的传统主从协议。NMT 使用 Master/Slave 模型,SDO 使用 Client/Server 模型,PDO 使用 Producer/Consumer 模型。一个主站程序实际上需要同时组织这些不同通信模型,并把它们统一到节点生命周期和应用控制流程中。
对主站来说,一个好用的协议栈应该尽量减少应用层重复实现这些网络管理状态,而不是只提供报文级 API。
为什么 Lely CANopen 比较适合作为主站核心
NMT Master、Boot 和 Configuration 是协议栈的一等能力
Lely 的 C CANopen library 本身提供 NMT Master 能力,并且把 NMT boot slave 和 NMT configuration request 作为可裁剪功能。上游构建配置中可以分别关闭 master、nmt-boot 和 nmt-cfg,这说明这些能力不是应用示例里临时拼出来的辅助代码,而是协议栈内部正式维护的功能路径。
Lely 对 NMT Master boot 还定义了专门的 SDO timeout、boot wait timeout、SDO retry、reset timeout 等参数。换句话说,它并不是只负责发送一个 NMT Start,而是已经考虑“一个主站如何等待、检查和配置远端节点”这类网络管理问题。
这对多节点主站非常重要。项目不再需要自己从零设计:
1 | 收到 Boot-up |
Lely 的 NMT Master/boot/configuration 已经承担了其中相当一部分标准协议状态机。应用层更应该关注产品策略,而不是重新实现通用 CANopen boot 过程。
需要注意,Lely 并不是完整实现 CiA 302 的所有扩展。官方 standards 页面明确列出了 flying master、network redundancy 等未实现或 application-specific 的部分。因此这里推荐的是它当前已实现的 CANopen Manager/NMT boot/configuration 路径,而不是把它描述成“完整 CiA 302 实现”。
dcfgen 把“主站代码”提升成“网络配置”
我认为 Lely 对主站最有价值的部分之一并不只在 runtime,而是 dcfgen。
普通从站的对象字典主要描述本机。主站对象字典却天然与整个网络有关:远端节点是谁、Heartbeat 怎么监控、哪些节点需要 boot、PDO 怎么对接、启动阶段要写哪些参数,这些本来就更像网络配置,而不是业务 C 代码。
Lely 的 dcfgen 使用 YAML 描述 Master 和各个 slave,并读取 slave EDS/DCF。它可以生成 Master DCF,还可以在启用 remote PDO mapping 时,根据从站 PDO 自动构造主站对应 PDO 映射。涉及启动配置时,它还能生成用于远端配置的 concise DCF 数据。
这意味着产品可以把很多本来散落在源码中的配置:
1 | Node-ID |
集中到网络描述文件中。
对主站项目来说,这种模式比在 main.c 里维护几十个 0x1400、0x1600、0x1800、0x1A00 写操作更容易审查,也更容易随从站 EDS/DCF 变化重新生成。
Lely 本身是 passive + asynchronous,适合被 RTOS 接管调度
Lely 官方对底层 C CANopen library 的一个重要描述是 passive:协议库本身不强制创建线程、不直接接管系统时钟,也不绑定某个具体 CAN 驱动。CAN 帧输入、CAN 帧输出和时间推进由外部环境提供。
同时,它的请求模型是异步的。发起请求本身不要求阻塞整个协议栈,完成结果通过 callback/future/executor 路径继续处理。
这对 Linux 很方便,对 RTOS 其实更重要。因为 MCU 项目最不希望发生的事情之一,就是第三方协议栈内部偷偷创建线程、使用 pthread/TLS,或者把某个 SDO transaction 做成阻塞式等待,最终导致 BSP、libc 和调度模型被协议栈反向绑定。
lely-canopen-rtt 利用的正是这一点:RT-Thread 负责“什么时候运行、CAN 从哪里来、timer 怎么触发”,Lely 负责“收到这个帧和这个时间后 CANopen 状态机应该怎么推进”。
dcf2c 让对象字典在 Host 生成、在 MCU 静态使用
Lely 常见的桌面用法可以直接加载 EDS/DCF,但 MCU 没必要保留这套运行时文本解析路径。
lely-canopen-rtt 的 target policy 固定 LELY_NO_CO_DCF=1、LELY_NO_STDIO=1、LELY_NO_CO_OBJ_FILE=1,目标端不会运行时读取 DCF 文件。Host 使用 dcf2c 把 DCF 生成 const struct co_sdev 静态描述,再编译进固件。
这个设计有几个直接好处:
- MCU 不依赖文件系统;
- 不需要在启动时解析 INI/DCF 文本;
- 对象字典结构在构建阶段就确定;
- 生成产物可以进入代码审查和版本管理;
- 网络配置变化可以表现为可审查的生成 diff;
- target 只保留实际运行所需的数据结构和协议路径。
它并不等于“完全不用 heap”。当前 runtime 仍选择 RT-Thread heap,并由 Lely 根据静态 co_sdev 创建运行时对象。这里的“静态”指的是对象字典描述和生成输入不需要 target 解析文本,而不是宣称整个协议栈全部静态分配。
Apache-2.0 对嵌入式产品集成比较友好
Lely core 使用 Apache License 2.0,lely-canopen-rtt 仓库本身也使用 Apache-2.0。相较于 LGPL runtime 的方案,产品集成时许可证边界更直接。当然,最终发布仍然应该保留仓库 LICENSE、NOTICE 和适用的 upstream 版权信息。
lely-canopen-rtt 解决的不是 CANopen 协议,而是 RT-Thread 集成问题
直接把 Lely 源码加入 RT-Thread 工程并不等于完成了移植。真正困难的是 I/O、时间、线程和生命周期。
这个软件包最核心的设计是 single-owner:RT-Thread 可以有很多应用线程、中断和驱动 callback,但所有 Lely EV/IO2/CANopen 对象只允许一个 owner thread 访问。
target 中固定:
1 |
这些宏的含义不是“RT-Thread 不能多线程”,而是“Lely 内部不承担线程同步”。跨线程工作必须先进入 RT-Thread 的 event/message queue,再由 owner 串行执行。
这条边界非常适合 MCU:
- CAN RX callback 不直接执行复杂 CANopen 状态机;
- timer callback 不直接进入 Lely;
- MSH 或业务线程不直接持有
co_nmt_t、co_csdo_t、PDO/SYNC/EMCY/TIME service pointer; - 所有异步协议状态都在 owner 中推进;
- 应用通过 queue 提交命令,通过 snapshot 读取状态;
- shutdown 先关闭 callback/command admission,再等待已进入 callback 退出,最后释放 CANopen 对象。
这比“给所有 Lely API 外面加 mutex”更容易建立明确的 ownership 规则。
和其他 CANopen 协议栈怎么比较
下面的比较重点放在“嵌入式主站”而不是单纯比较 CANopen 协议覆盖率。
| 维度 | Lely + lely-canopen-rtt |
CANopenNode | CanFestival / canfestival-rtt |
MicroControl CANopen Master |
|---|---|---|---|---|
| 许可证 | Apache-2.0 | Apache-2.0 | runtime LGPL-2.1,工具 GPL | 商业授权 |
| 主要语言 | 上游 C/C++,本软件包 target 使用 C core | ANSI C | ANSI C | C99 源码交付 |
| 主站定位 | NMT Master、boot、configuration、SDO Client 等是正式协议栈能力 | 官方功能表称为 Simple NMT master,并提供 SDO Client/LSS Master/Gateway |
可构建 Master/Slave,具备 NMT、SDO Client、PDO、EMCY、LSS 等 | 面向复杂控制器的完整商业 Master 产品 |
| 网络配置方式 | dcfgen 从 Master YAML + slave EDS/DCF 生成 Master DCF/配置数据 |
以 OD 和应用初始化为核心,CANopenEditor 可生成 OD C 文件 | Objdict editor/generator,节点对象字典模式较传统 | 商业工具/API,支持运行时参数化 |
| 主站 boot/config | Lely 提供 NMT boot/configuration state machine;软件包可发布 boot snapshot 和 manual cfg | 能做 NMT/SDO/LSS Commander,但复杂网络启动策略通常更多落在应用组织层 | 支持主站和 concise DCF,已有传统 Master 示例 | 官方宣称支持 CiA 301/302/305,适合复杂网络管理 |
| PDO/SYNC | 支持;软件包提供静态 PDO event 和 transmission type 控制 | 支持,动态 PDO mapping 能力成熟 | 支持 | 支持 |
| RT-Thread 集成 | 本软件包直接使用 RT-Thread CAN/event/timer/message queue | 栈本身跨 MCU/RTOS,需要目标 port | 已有 canfestival-rtt,使用 RT-Thread CAN + hwtimer,并带 Master402 示例 |
需要按商业栈 driver/API 接入 |
| 并发模型 | passive async + single-owner,非 owner 通过 queue/snapshot | 非阻塞 process 模型,可单线程或多线程组织 | 传统 timer/CAN callback + port 模型,RTT 移植中有 CAN RX/timer 线程 | 由商业实现和接口约束决定 |
| 主站配置生成 | 强,dcfgen -r 很适合把网络拓扑、Heartbeat、boot 和 PDO 放到生成流程 |
更偏设备 OD 与应用组合 | 有 OD generator,但工具链和工程模式较传统 | 通常有配套商业工具 |
| 目标端 DCF | 本软件包使用 dcf2c 静态化,不运行时解析 DCF |
常用生成 OD.c/OD.h |
常用 generator 生成节点对象字典 C | 取决于产品方案 |
| 工程透明度 | 源码、生成链、Kconfig、source allowlist 都可审查 | 源码成熟、社区大、MISRA C:2012 有说明 | 源码开放,但历史分支较多 | 商业支持强,但内部实现受供应商产品约束 |
| 更推荐的场景 | RT-Thread MCU 多节点主站、网络 boot/config/PDO 管理 | 从站、通用节点、轻量主站、需要成熟 MCU 社区时 | 已有 CanFestival 项目、需要沿用现有 DS402/OD 资产 | Safety、CANopen FD、商业支持、明确交付责任 |
与 CANopenNode:不是谁“更强”,而是主站抽象不同
CANopenNode 是非常成熟的开源 CANopen 协议栈,而且对 MCU 很友好。它支持 SDO Client、NMT Master、LSS Master、Heartbeat consumer、PDO、SYNC、TIME、EMCY,并且官方还提供 CANopenDemo、CANopenLinux/CANopenSocket 和 CANopenEditor。它的社区和设备移植数量也明显更大。
如果开发的是一个 CANopen 从站,我通常不会因为 Lely 有 dcfgen 就否定 CANopenNode。从站的主要问题是本地 OD、PDO、SDO Server 和硬件驱动,CANopenNode 在这类场景非常直接。
但它自己的官方 README 对 NMT Master 的描述就是 Simple NMT master。它的 commander 能力更多通过 NMT Master + SDO Client + LSS Master/Gateway 组合出来。对于一个需要“按网络配置自动 boot 多个节点、检查 identity、生成远端 PDO mapping、形成配置数据”的主站,Lely 的 dcfgen + NMT boot/configuration 更像是围绕网络 Manager 设计的一套完整路径。
所以选择差异可以概括为:
- CANopenNode 更像一个非常成熟、可自由拼装的 CANopen node toolbox;
- Lely 在主站场景下更强调异步服务、Network Management 和 DCF 驱动的网络配置。
如果主站逻辑很简单,CANopenNode 的简洁反而是优势;当网络启动和配置变复杂时,Lely 的生成式网络配置价值会逐渐体现出来。
与 CanFestival:Lely 更适合新项目建立现代化生成与并发边界
CanFestival 并不是不能做主站。官方资料明确支持 NMT Master/Slave、Heartbeat、SDO clients/servers、PDO、EMCY、SYNC 和 LSS,也支持 concise DCF。RT-Thread 社区还有现成的 canfestival-rtt 移植,甚至提供了 CiA 402 Master 示例。
因此,如果一个项目已经基于 CanFestival 工作多年,或者目标就是快速复用现有 Master402 示例,继续使用 CanFestival 是合理选择。
但对新项目来说,Lely 有几个更吸引我的点:
dcfgen的输入天然是整个网络,而不是只围绕某个节点对象字典;- Lely C core 明确采用 passive/asynchronous 思路,适合由 RTOS 接管 I/O 和调度;
lely-canopen-rtt把线程所有权收口为 single-owner,而不是让业务代码直接穿过多个 timer/CAN callback;- Apache-2.0 的集成边界更直接;
- 本软件包把上游源文件选择、Host 生成和 target 运行时拆成了可审查的层。
CanFestival 官方站点也明确显示当前源码存在多个分支/镜像,主 repo 更新比较松散。这并不代表它不能稳定工作,而是新项目在长期维护时需要先确定自己要跟踪哪个 fork 和哪套工具链。
与商业 Master Stack:开源灵活性不等于商业交付能力
以 MicroControl CANopen Master 为例,官方产品直接面向复杂控制网络,明确覆盖 CiA 301、CiA 302、CiA 305,并提供 CANopen FD、可选 Safety、商业技术支持和 C99 源码交付。
如果项目的关键要求是:
- CANopen FD/CiA 1301;
- Safety;
- 厂商 SLA 和技术支持;
- 审核时需要明确的软件供应商责任;
- 希望购买现成的标准覆盖而不是自己维护开源集成;
那么商业栈可能是更稳妥的选择。
lely-canopen-rtt 的优势是源码完全可控、RT-Thread 集成透明、网络生成链可修改,而且没有商业 runtime 费用;它的代价是产品团队自己承担 target 验证、上游冻结版本维护以及尚未覆盖功能的扩展责任。
整体框架:Host 负责“生成网络”,MCU 负责“运行网络”
理解这个软件包最重要的一点,是不要把 master.yml、remote DCF、master_sdev.c 和运行时 CANopen 对象混在一起。
它把工作明确分成 Host 和 Target 两部分。
1 | flowchart LR |
远端设备的 EDS/DCF 是“网络里这个从站长什么样”的 Host 输入;master.yml 是“主站准备怎么管理这个网络”的策略;生成后的 master_sdev.c 才是 MCU 本地 Master 对象字典描述。
仓库里的 examples/node1/node1.dcf 绝不能被理解成“MCU 本地 Node1”。它描述的是远端 Node1。真正被 MCU 绑定的是 examples/master_node1/master_sdev.c。
软件包还增加了 compact_master_dcf.py。原因是 Lely dcfgen 的 Manager 模板面向通用网络,某些 node-indexed array 会有较大的 CompactSubObj 范围。对于只有少数节点的 MCU,如果直接全部展开,会生成大量不使用的 sub-object。compactor 根据当前网络缩小这些范围,再交给 dcf2c,更符合 MCU 的资源模型。
这就是这个软件包与“直接把 Lely 编译进 RT-Thread”的差别:它不仅解决 runtime port,还把 Host 生成路径也纳入工程。
Target Runtime:所有 CANopen 状态只在一个 owner thread 内推进
目标端的主路径如下:
1 | flowchart LR |
这张图里最重要的不是 ev_loop,而是 owner 边界。
CAN RX callback 收到帧时不会直接调用 co_nmt_on_*() 或 SDO/PDO 处理函数。它只取得一个短生命周期 pin、设置 event,然后退出。owner 被唤醒后再批量读取 RT-Thread CAN software FIFO,把帧送入 Lely。
应用线程也不直接调用底层 Lely service。比如应用想发送 NMT command,会把命令复制到 runtime message queue;想看远端 NMT 状态,则读取 owner 已发布的 atomic snapshot。
这样可以避免一个常见问题:CAN RX 在驱动 callback 中更新协议状态、业务线程同时访问对象字典、timer 又在另一个上下文触发超时,最后不得不在 Lely 对象外面铺很多锁。
在这里,规则非常简单:只有 owner 可以碰 Lely。
主站从启动到运行的流程
当使用静态 Master 时,启动过程大致可以理解为:
1 | sequenceDiagram |
这里有两个很重要的工程语义。
第一,lely_rtt_runtime_start() 返回成功之前,owner 会完成基础 CAN/IO2/runtime 初始化,并创建本地 Master。启动失败不会把一个半初始化 runtime 当成可用对象发布。
第二,应用看到的 post 成功和远端节点真正完成动作是两回事。例如 lely_rtt_runtime_post_nmt() 返回 RT_EOK 只表示命令已经进入 owner queue;节点是否真的进入 Operational,要继续看 remote NMT snapshot。
这类语义看似麻烦,但对工业通信更安全,因为 API 不会把“本地已排队”伪装成“远端已经完成”。
Master 控制面已经覆盖哪些常用能力
当前 runtime.h 提供的 owner-safe 主站能力主要包括:
| 能力 | 软件包提供的接口语义 |
|---|---|
| Local NMT | 读取本地主站 NMT state snapshot |
| Remote NMT | 读取远端 state、Heartbeat timeout 后暂时标记 unavailable |
| Boot | 读取远端 NMT boot completion result |
| NMT command | 异步 post start/stop/pre-op/reset-node/reset-comm |
| Client-SDO | request object 形式的 upload/download |
| Block SDO | block upload/download |
| SDO cancel | 显式取消 queued/active application SDO |
| Manual NMT config | 通过 owner 调用 NMT configuration request,并返回 terminal result/diagnostic |
| Local OD | 访问本地 0x2000..0x5FFF manufacturer range |
| TPDO | 更新本地 OD 后触发已配置好的 static TPDO event |
| SYNC/PDO | 控制 0x1006 period、PDO transmission type,读取 processed-SYNC snapshot |
| EMCY | 保留远端 EMCY history,并支持本地主站 EMCY producer 操作 |
| TIME | 收取 TIME snapshot,或显式发送 absolute TIME |
| MSH | 使用 co 根命令进行示例调试和联调 |
这里最有价值的是“owner-safe”。应用不需要知道 co_nmt_t、co_csdo_t 到底在哪个线程创建,也不会拿到这些对象后在错误线程中直接调用。
SDO 也不是一个简单的同步 read()。软件包用 request object 表达它的生命周期:
1 | create request |
当前实现每个远端 Node-ID 同时最多一个 application SDO transaction,并且 application-owned Client-SDO 不借用 NMT boot 使用的 Client-SDO。这样做的目的就是避免应用 SDO 和自动 boot/configuration 同时抢 CiA 301 预定义 SDO 通道。
Kconfig 和源码选择为什么也是这个软件包的优势
协议栈移植到 MCU 后,最怕“为了一个 SDO Client 把桌面平台后端、pthread、filesystem、gateway 全部编进来”。
lely-canopen-rtt 的 SConscript 不是扫描整个 upstream src/,而是从 metadata/RTTHREAD_SOURCE_ALLOWLIST.txt 读取允许进入 target 的源文件,并明确拒绝 Linux/POSIX/Win32 I/O backend、thread loop、fiber executor 等不属于当前 RT-Thread 架构的源文件。
Kconfig 再对 CANopen feature 做第二层裁剪。例如:
1 | PKG_LELY_USING_CO_CSDO |
Master application bridge 也是按需开启:
1 | PKG_LELY_USING_MASTER_COMMAND |
这使得“协议栈支持什么”和“产品这次实际启用了什么”是两个独立层次。对于资源受限 MCU,这比默认把所有功能打开更容易控制 ROM/RAM 和依赖关系。
怎么使用:从一个远端 Node1 到真正的产品主站
第一步:把软件包接入 RT-Thread 工程
上层 BSP 需要让本仓库的 Kconfig 和 SConscript 进入构建。
基础开关:
1 | PKG_USING_LELY=y |
它会选择 heap、device、CAN 和 event 等 runtime 需要的 RT-Thread 组件。
如果使用自动初始化:
1 | PKG_LELY_APP_AUTO_INIT=y |
然后根据实际 CAN 网络选择 bitrate、owner thread stack/priority/timeslice、RX batch 和 start/stop timeout。
这些值都是产品/BSP 参数,仓库默认值不是所有平台的推荐值。
第二步:准备每个远端设备的 EDS/DCF
CANopen 主站最好不要先手写远端对象表。正常路径是从设备厂商获取 EDS,或者在 CANopenEditor 中建立并导出具体 DCF。
例如伺服驱动可能提供 CiA 402 对象,I/O 模块可能按 CiA 401,编码器可能按 CiA 406。协议栈并不需要把这些 profile 全部硬编码成 C 结构;它首先把设备对象字典当作标准 CANopen OD 处理。
第三步:用 master.yml 描述网络策略
仓库示例:
1 | master: |
这里最值得注意的不是 YAML 语法,而是职责位置。
Node-ID、Heartbeat multiplier、mandatory、automatic NMT Start、Reset Communication 这些应该是网络/产品策略,所以留在生成输入里。runtime.c 不应该根据某个项目随便猜出一套默认主站策略。
仓库中的 node_id: 127、revision/serial、Node1 是否 mandatory 等都只是 fixture,需要在产品使用前替换。
第四步:生成静态 Master Object Dictionary
仓库的 Windows Host 统一入口如下:
1 | .\tools\gen_sdev.ps1 ` |
这条命令背后的核心链路是:
1 | remote DCF + master.yml |
产品真正需要版本管理的是:生成输入、生成工具版本和最终生成产物之间的关系。
第五步:开启需要的主站控制面
最小 Master 示例会选择基础 NMT Master/CSDO/NMT boot。需要应用主动控制时,再逐项打开:
1 | PKG_LELY_USING_MASTER_COMMAND=y |
不要因为“以后可能会用”就全部启用。CANopen feature 和 application bridge 都会增加代码、状态和测试面。
第六步:先用 MSH 把网络跑通
仓库内 Master + Node1 示例启用 PKG_LELY_USING_MSH 后,会导出 co 根命令。
常用联调命令例如:
1 | co status |
这一步适合验证:CAN 驱动是否正常、Node1 是否 Boot-up、Heartbeat 是否稳定、SDO 是否可访问、NMT command 是否真正改变远端状态。
需要注意,当前 MSH Kconfig 依赖仓库内 Master + Node1 示例。真实产品不应该把这个 fixture 当成最终应用接口,产品代码应使用公开 runtime API。
第七步:应用通过 owner-safe API 使用主站
自动初始化成功后,应用可以取得默认 runtime:
1 | lely_rtt_runtime_t *runtime = lely_rtt_runtime_get_default(); |
查看远端状态:
1 | rt_uint8_t state; |
发送 NMT Start:
1 | lely_rtt_runtime_post_nmt(runtime, |
做一次远端 SDO upload 时,使用 request object:
1 | lely_rtt_sdo_request_t *req = lely_rtt_sdo_request_create(); |
重点仍然是结果语义。post 成功只代表请求被接受;真正的 remote/protocol completion 要看 result.status 和 abort_code。
用在伺服、I/O 和编码器上时应该怎么理解
这个软件包是 CANopen 通信与 Master runtime,不是某个设备 profile 的高级业务库。
例如连接一个 CiA 402 伺服驱动时,Lely 可以完成:
- NMT 管理;
- SDO 访问
0x6040、0x6041、0x6060、0x607A等对象; - 根据 DCF 配置 PDO;
- 通过 RPDO/TPDO 交换实时控制和状态数据;
- 处理 Heartbeat、EMCY、SYNC。
但“CiA 402 Power Drive System 状态机应该何时写 0x0006、0x0007、0x000F”“Homing/Profile Position/CSP 的产品控制策略”仍属于应用或单独的 device-profile driver。当前软件包没有把这些业务语义伪装成通用 CANopen Master API。
同样,连接 CiA 401 I/O 模块或 CiA 406 编码器时,软件包提供的是 OD/SDO/PDO/NMT 等通信基础;具体 I/O channel、position scaling、preset 等 profile 语义仍由上层理解。
这样的边界反而比较健康:CANopen 栈负责标准通信和网络生命周期,设备驱动负责 profile,产品逻辑负责机器行为。
当前必须说明的能力边界
推荐一个软件包时,把边界说清楚比只列优点更重要。
1. CAN FD transport 不等于 CANopen FD
软件包可以选择 CAN FD frame support,并明确适配 RT-Thread BSP 中 rt_can_msg.len 是 payload bytes 还是 raw DLC;但这不能自动推导成“已经实现 CiA 1301 CANopen FD Master”。当前文章讨论的核心仍然是经典 CANopen/CiA 301 路径。
如果产品明确需要 CANopen FD,应单独做标准和协议栈能力确认。
2. 当前 public Master bridge 不提供通用 dynamic PDO remapping
软件包可以触发已经配置好的 static TPDO,并可以调整支持的 PDO transmission type;但通用 dynamic PDO remapping 不在当前 owner-safe bridge 范围。
这与它的整体思路一致:优先在 Host 生成网络映射,而不是在运行时任意重构 PDO。
3. LSS core 可选,但当前没有对应的 owner-safe Master application bridge
Kconfig 可以启用 Lely LSS core,但当前公开 Master control plane 主要覆盖 NMT、SDO、OD、PDO/SYNC、EMCY、TIME,没有提供与这些模块同级的 RT-Thread LSS Master API。
如果产品需要在现场通过 LSS 动态分配 Node-ID/bitrate,需要继续扩展 owner-safe bridge,不能因为 upstream 有 LSS 就直接宣称当前软件包已经提供完整 LSS 产品接口。
这也是 CANopenNode 当前的一个实际优势:它已有明确的 LSS Master/Gateway 能力。
4. 静态网络配置更适合拓扑相对稳定的设备
dcfgen -> dcf2c 很适合工业设备、车辆控制器、机器人控制器这类“网络拓扑在固件构建时基本已知”的产品。
如果产品要求运行时发现任意第三方设备、动态创建大量对象和 PDO 映射,这套 Host-first 方法会显得更严格,需要额外设计动态配置层。
5. Host CI 不等于目标板验证
仓库已经把 Host regression tests 放入 GitHub Actions workflow,能够检查不少源码级和生成器契约;但这不等于真实 BSP 的 CAN driver、ISR 时序、bus-off recovery、queue pressure、CAN FD length convention 和多节点 HIL 已经在所有硬件上验证。
最终产品仍然需要用自己的 MCU、CAN controller、transceiver 和真实从站完成 target build 与总线测试。
最终应该怎么选
如果让我针对一个新的 RT-Thread CANopen 项目做选型,我会按下面的顺序判断。
优先选择 lely-canopen-rtt 的场景:
- 本机是 CANopen 主站/Manager,而不是简单从站;
- 网络里有多个远端节点;
- 需要比较完整的 NMT boot/configuration、Heartbeat、SDO、PDO/SYNC 管理;
- 希望根据 EDS/DCF 自动生成 Master 网络配置;
- 希望 MCU 运行时不解析 DCF;
- 项目是 RT-Thread,希望明确控制 Lely 与 ISR/driver/application thread 的并发关系;
- 可以接受对网络拓扑采用 Host-first、静态生成的工程模式。
更适合 CANopenNode 的场景:
- 主要开发 CANopen 从站;
- 主站只需要简单 NMT/SDO/LSS commander;
- 更看重成熟的 MCU port 社区、CANopenDemo、CANopenEditor 和现有 CANopenNode 生态;
- 需要其已经提供的 LSS Master 或其他特定功能。
更适合 CanFestival 的场景:
- 已有大量 CanFestival 代码和对象字典;
- 现有 RT-Thread 产品已经使用
canfestival-rtt; - 希望直接复用其现有 Master402 示例,不准备更换主站框架。
更适合商业协议栈的场景:
- 明确要求 CANopen FD、Safety、商业技术支持或标准覆盖承诺;
- 项目更看重供应商交付责任,而不是开源代码的完全可控性;
- 有对应的软件预算。
对于“RT-Thread + MCU + 多节点经典 CANopen 主站”这个具体组合,我更倾向于 Lely。真正让我推荐它的不是某一个 API,而是从网络配置到运行时的完整链条:
1 | 设备 EDS/DCF |
这条链把 CANopen 主站最容易散落到业务代码里的网络拓扑、启动配置、对象字典、异步协议状态和线程边界重新组织到各自应该在的位置。
作为软件包分享时,可以重点强调的价值
- 不是简单移植 Lely,而是为 RT-Thread 建立完整的 CANopen Master runtime。
- Lely 的 NMT Master/boot/configuration 能力比“简单 NMT master”更贴近多节点网络管理。
dcfgen让主站配置从手写 C 逻辑转为可审查的网络 YAML + EDS/DCF。dcf2c把 Master OD 静态化,MCU 不需要文件系统和运行时 DCF parser。- single-owner 把 Lely 对象与 RT-Thread ISR、driver callback、业务线程之间的并发边界说清楚。
- 应用通过 command queue、request object 和 snapshot 使用主站,不直接跨线程操作 Lely service。
- Kconfig + source allowlist 控制 target 实际编译的协议能力和上游源码边界。
- 对 CAN FD length、CAN status、hardware filter 等 BSP 差异采用显式契约,不在通用层猜测。
- 与 CANopenNode、CanFestival 相比,优势集中在“复杂主站 + 网络生成 + RT-Thread 并发模型”,而不是宣称所有维度都更强。
- 与商业栈相比,优势是 Apache-2.0、源码可控和可定制,代价是产品团队自己承担 target/HIL 验证和功能扩展。
如果一个团队已经开始为主站自己维护远端节点表、Heartbeat 表、boot state、SDO 配置数组、PDO mapping 数组、多个线程之间的 CANopen 锁,并且这些逻辑越来越难维护,那么这个软件包的价值就已经不只是“换一个协议栈”,而是把主站重新整理成一套可生成、可裁剪、可审查的工程架构。
参考资料
wdfk-prog/lely-canopen-rtt:https://github.com/wdfk-prog/lely-canopen-rtt- Lely CANopen - Library overview:https://opensource.lely.com/canopen/docs/overview/
- Lely CANopen - Build configuration:https://opensource.lely.com/canopen/docs/configuration/
- Lely CANopen - EDS/DCF tools:https://opensource.lely.com/canopen/docs/dcf-tools/
- Lely CANopen - C++ tutorial / Master DCF:https://opensource.lely.com/canopen/docs/cpp-tutorial/
- Lely CANopen - Standards support:https://opensource.lely.com/canopen/docs/standards/
- CANopenNode repository:https://github.com/CANopenNode/CANopenNode
- CANopenNode documentation:https://canopennode.github.io/CANopenNode/
- CanFestival:https://canfestival.org/
- RT-Thread CanFestival port:https://github.com/gbcwbz/canfestival-rtt
- MicroControl CANopen / CANopen FD Master:https://www.microcontrol.net/en/portfolio/protocol-stacks/canopen/canopen-master-stack/
- CiA 301 CANopen application layer and communication profile。本文协议术语同时参考项目提供的 CiA 301 V4.2.0 中文注释资料。
说明:本文按
lely-canopen-rtt2026-09-11 可见main(head09fd73e980cb2dc2f832a09ea6d41b3bbb00061d)及对应公开文档整理。Host/源码级证据不等同于具体 BSP、目标板和多节点 HIL 验证结果。








