- 嵌入式
- 通信
【免费下载链接】CANopenNode
CANopen protocol stack
CANopenNode 是一套用 ANSI C 编写、面向对象方式实现的 CANopen 协议栈(CiA 301 / DS-301),但它刻意将"协议栈核心"与"具体硬件驱动"彻底分离:主仓库不包含任何针对某款芯片的驱动代码,所有硬件相关文件都由外部独立工程提供。本文以仓库内的 doc/deviceSupport.md 为主体,结合 301/CO_driver.h 与 example 目录下的空白驱动模板,系统讲解 CANopenNode 的设备接口规范、最小可编译工程的用法、驱动开发者推荐的移植工作流程,以及当前社区已确认支持的 Linux、STM32、PIC、Analog Devices、Zephyr 等各类平台清单,帮助读者快速完成自有硬件的移植接入。
一、设计理念:协议栈与硬件驱动彻底分离
CANopenNode 可以运行在数量众多的设备上:从简单的 16 位微控制器到 PC 计算机,存在大量不同硬件、不同开发工具、不同开发者维护的实现。正如 doc/deviceSupport.md 开头所述,单个项目维护者不可能持续跟进所有硬件接口的更新,因此所有硬件相关文件都不属于 CANopenNode 主项目。
这种设计带来的直接好处是协议栈核心与硬件无关:301目录下的 NMT、PDO、SDO、Emergency、SYNC、TIME、LSS 等协议模块,以及storage、303(LED)、304(SRDO/GFC)、305(LSS)、309(ASCII gateway)等扩展模块,都只依赖一个统一的驱动抽象层。
从源码结构看,每个设备移植工程只需要向 CANopenNode 提供三个接口文件(见 301/CO_driver.h 的 "Hardware interface of CANopenNode" 一节):
| 文件 | 职责 | 是否属于主仓库 |
|---|---|---|
CO_driver.h | 声明通用驱动函数与类型,被 CANopenNode 的每个 .c 文件包含 | 是(位于 301/CO_driver.h) |
CO_driver_target.h | 定义微控制器特定的类型声明与必要宏 | 否,由各设备工程提供(本仓库提供空模板) |
CO_driver.c | 实现CO_driver.h中声明的函数 | 否,由各设备工程提供(本仓库提供空模板) |
本仓库仅在example目录中提供了两个"基本为空"的模板文件,保证CANopenNode/example能在任何系统上编译通过(但编译出的程序没有连接真实 CAN 硬件,不可直接使用)。完整的设备接口文档集中在 301/CO_driver.h 中。
二、设备接口规范:驱动层到底要实现什么
以 301/CO_driver.h 为权威依据,一个完整的 CAN 驱动必须实现以下核心数据结构与函数。
2.1 三个核心数据结构
CO_CANrx_t(接收帧配置对象):包含标准 CAN 标识符ident(bit 0..10 为 ID,bit 11 为 RTR)、掩码mask、绑定的 CANopen 对象指针object,以及回调函数指针pCANrx_callback。CO_CANtx_t(发送帧配置对象):包含按 CAN 模块对齐的标识符ident、帧长度DLC、8 字节数据data[8],以及两个关键标志bufferFull(上一帧是否仍在缓冲中)与syncFlag(同步 PDO 标志,防止同步帧在同步窗口外发出)。CO_CANmodule_t(完整 CAN 模块对象):聚合了rxArray/txArray及其大小、CANerrorStatus错误状态位域、CANnormal运行模式标志、useCANrxFilters硬件过滤器使用标志、bufferInhibitFlag、firstCANtxMessage、CANtxCount等。
2.2 必须实现的函数清单
301/CO_driver.h 声明了以下关键驱动函数,空白实现可参考 example/CO_driver_blank.c:
| 函数 | 作用与要点 |
|---|---|
CO_CANsetConfigurationMode(CANptr) | 请求 CAN 进入配置(停止)模式并等待完成 |
CO_CANsetNormalMode(CANmodule) | 请求 CAN 进入正常运行模式,置CANnormal = true |
CO_CANmodule_init(...) | 初始化 CAN 模块对象与硬件寄存器,必须在通信复位阶段、配置模式下调用。合法位速率(kbps)为 10、20、50、125、250、500、800、1000,非法值默认回落为 125 |
CO_CANmodule_disable(CANmodule) | 程序退出时关闭 CAN 模块 |
CO_CANrxBufferInit(...) | 配置接收缓冲:设置标识符与掩码并绑定对象与回调。同一标识符下索引最小者优先 |
CO_CANtxBufferInit(...) | 配置发送缓冲:设置标识符、RTR、帧长与syncFlag,返回待填充数据的缓冲指针 |
CO_CANsend(CANmodule, buffer) | 发送 CAN 帧:若硬件发送缓冲空闲则直接复制,否则置bufferFull等待 TX 中断发送 |
CO_CANclearPendingSyncPDOs(...) | 同步窗口超时后清空所有挂起的同步 TPDO |
CO_CANmodule_process(CANmodule) | 周期性调用,计算CANerrorStatus错误位域 |
CO_CANinterrupt(CANmodule) | 中断入口:接收中断做标识符匹配并调用CANrx_callback快速预处理,发送中断按索引顺序发送排队帧 |
接收侧的回调机制尤其重要:CAN 帧先由硬件或软件过滤,中断/实时线程根据 11 位 CAN-ID 与掩码判定归属的 CANopen 对象,然后调用对应的CANrx_callback()快速提取数据,供普通线程稍后处理。CO_CANrxMsg_readIdent()、CO_CANrxMsg_readDLC()、CO_CANrxMsg_readData()是目标平台需以内联函数或宏方式提供的帧读取辅助函数。
2.3 错误状态与返回值约定
驱动层的CO_CANmodule_process()需要按 301/CO_driver.h 的位域约定上报错误:收发错误计数器 ≥ 96 进入 warning 级,≥ 128 进入 passive 级,发送计数器 ≥ 256 进入 bus off。位域包括CO_CAN_ERRTX_WARNING、CO_CAN_ERRTX_PASSIVE、CO_CAN_ERRTX_BUS_OFF、CO_CAN_ERRTX_OVERFLOW、CO_CAN_ERRTX_PDO_LATE、CO_CAN_ERRRX_WARNING、CO_CAN_ERRRX_PASSIVE、CO_CAN_ERRRX_OVERFLOW等。
函数统一返回CO_ReturnError_t枚举(定义见 301/CO_driver.h),包括CO_ERROR_NO、CO_ERROR_ILLEGAL_ARGUMENT、CO_ERROR_ILLEGAL_BAUDRATE、CO_ERROR_TX_OVERFLOW、CO_ERROR_TX_PDO_WINDOW等 20 余种,方便上层协议模块与应用程序统一处理。
2.4 多线程与 RTOS 移植的关键宏
CANopenNode 原生支持多线程(裸机下表现为不同优先级的中断)。驱动模板必须在CO_driver_target.h中定义以下同步宏:
CO_LOCK_CAN_SEND/CO_UNLOCK_CAN_SEND:保护CO_CANsend()临界区;CO_LOCK_EMCY/CO_UNLOCK_EMCY:保护CO_errorReport()/CO_errorReset();CO_LOCK_OD/CO_UNLOCK_OD:保护对象字典(OD)读写,防止实时线程与主线程并发访问;CO_FLAG_READ/CO_FLAG_SET/CO_FLAG_CLEAR:接收新帧标志的读写,配合内存屏障CO_MemoryBarrier()防止编译器优化乱序。
example/CO_driver_target.h提供了全部为空实现的模板;Zephyr、FreeRTOS、Mbed-os 等 RTOS 移植则需用互斥量、信号量或关调度实现这些宏,这通常是移植工作量最大的部分之一。
三、空白模板:example 目录——任何系统上都能编译的最小工程
example 目录不包含任何特定设备接口,它本身就是"驱动模板"与"最小应用示例"的结合体,可作为新移植工程的起点。目录内的文件职责如下:
| 文件 | 职责 |
|---|---|
| example/CO_driver_blank.c | 空白驱动实现:每个函数只有骨架与参数校验,寄存器操作留空 |
| example/CO_driver_target.h | 空白目标定义:CO_LITTLE_ENDIAN、基础类型、三个数据结构、空同步宏 |
| example/main_blank.c | 完整的主程序流程模板(见下文) |
| example/OD.c / example/OD.h | 由 CANopenEditor 等外部工具生成的对象字典 |
example/CO_storageBlank.c /.h | 非易失存储的空白实现 |
| example/Makefile | 使用 gcc 编译的 Makefile,目标产物canopennode_blank |
| example/DS301_profile.eds | DS301 设备描述文件(EDS),供组态工具使用 |
编译验证非常简单(需要 gcc 环境,在仓库根目录执行):
cd example && makeexample/Makefile 将CO_driver_blank.c、CO_storageBlank.c与301、303、305、storage目录下的协议模块以及CANopen.c、OD.c、main_blank.c一并编译链接,-Wall开告警、-g开调试信息,还预留了CO_USE_GLOBALS与CO_MULTIPLE_OD两个可选宏的开关位。
example/main_blank.c 演示了标准初始化与运行流程,这是每个真实移植工程主程序的骨架:
CO_new()分配并创建 CANopen 对象(也可用CO_USE_GLOBALS改用全局变量);- 进入通信复位循环:
CO_CANsetConfigurationMode()→CO_CANmodule_disable()→CO_CANinit()(CAN 与位速率)→CO_LSSinit()(LSS 从站地址与待定 node-ID/位速率)→CO_CANopenInit()(NMT、SDO server/client、Emergency 等)→CO_CANopenInitPDO()(PDO 映射)→CO_CANsetNormalMode(); - 普通线程循环调用
CO_process()处理所有异步对象,实时线程(tmrTask_thread,典型 1 ms 周期)依次调用CO_process_SYNC()、CO_process_RPDO()、CO_process_TPDO(),并在CO_LOCK_OD/CO_UNLOCK_OD保护下执行; CO_CAN1InterruptHandler()是 CAN 接收/发送中断入口的占位。
Node-ID 与位速率在模板中以pendingNodeId(默认 10)与pendingBitRate(默认 125 kbps)体现,注释明确说明真实设备应"从拨码开关或非易失存储读取,并可由 LSS 从站配置"——这正是把 LSS(CiA 305)用于在线改节点地址与波特率的接入点,相关实现见 305/CO_LSSslave.c。
四、驱动开发者推荐工作流程(官方七步法)
doc/deviceSupport.md 专门为希望共享与协作的驱动开发者给出了推荐流程,这是社区长期实践沉淀的移植方法论:
- 建立独立的设备 demo 工程仓库:在 Git 托管平台创建你自己的设备专用仓库;
- 引入 CANopenNode 核心:将 CANopenNode 作为 git 子模块加入工程,例如:
git submodule add https://gitcode.com/gh_mirrors/ca/CANopenNode(原始文档建议添加上游仓库地址,使用镜像地址同样可以完成子模块引入;引入后 CANopenNode 核心代码保持不动,便于后续升级。)
- 添加具体驱动与工程文件:编写
CO_driver_target.h与CO_driver.c,并加入应用代码、对象字典、构建脚本; - 撰写良好的 README.md:描述你的工程、所用 demo 板、开发工具与用法;
- (可选)准备 CANopenDemo 设备并运行测试:在 CANopenDemo 仓库中提供 demoDevice,利用其教程与测试工具验证移植正确性;
- 分享成果:在 doc/deviceSupport.md 中新增设备条目并提交 Pull Request;或在 CANopenNode 的 issues 中为新设备创建 issue;
- 申请成为项目开发者:将你的工作贡献并入 CANopenNode 项目,成为其开发者,共同提升代码质量与功能完整性。
文档同时指出:目前最新、最完整的参考实现是 CANopenLinux 与 CANopenPIC(面向 PIC32 裸机),移植时可优先参考这两个工程;example目录则是最小化的通用模板。
五、社区已确认的设备支持清单
以下是 doc/deviceSupport.md 记录的设备/平台支持现状(信息以文档记录为准,工程托管于各自独立仓库,此处仅列出关键事实)。
5.1 主流活跃端口
| 平台 | 说明 | CANopenNode 版本 | 状态 | 特性 | 工具 / 硬件 |
|---|---|---|---|---|---|
| Linux(socketCAN) | 与 Linux 内核 socketCAN 集成,带主站命令接口,文档明确说明其接口属于本项目体系 | v4.0 | 活跃参考实现 | OD 存储、错误计数器、主站功能(SDO client、LSS master、NMT master) | Linux PC、树莓派等 |
| STM32 | STM32 微控制器集成 | v4.0 | — | — | — |
| PIC32 / dsPIC30 / dsPIC33 | Microchip 16/32 位 PIC 系列,最完整参考实现之一 | v4.0 | 活跃参考实现 | PIC32 OD 存储、PIC32 SDO client demo、错误计数器 | MPLAB X;Explorer 16、Max32 板;已知最小资源示例:dsPIC30F4011(16 位,RAM 不足 2KB,4 TPDO + 4 RPDO) |
| Analog Devices MAX32662 / MAX32690 | ADI MAX32xx 系列集成 | v4.0 | 看似稳定 | LED 指示灯、错误计数器 | Maxim Micros SDK;MAX32662-EVKIT、MAX32690-EVKIT(信息更新于 2023-02-17) |
| Zephyr RTOS | Zephyr 官方模块级集成,含示例工程 | v1.3 | 稳定 | OD 存储、LED 指示灯、Program Download、Zephyr SDO server demo | Zephyr SDK;任意带 CAN 接口且支持 Zephyr 的开发板(信息更新于 2025-11-13) |
| Mbed-os RTOS + STM32(F091RC、L496ZG) | Mbed-os 集成 | — | 稳定 | OD 存储、LED 指示灯、GPIO | Mbed CLI(gcc 7);STM32 Nucleo F091RC 等带 CAN 的开发板(信息更新于 2020-01-21) |
5.2 社区贡献端口
| 平台 | 说明 | CANopenNode 版本 | 状态 | 特性 |
|---|---|---|---|---|
| Kinetis K20(NXP Arm,Teensy3) | 面向 Teensy3 的驱动,配套 readme 文档 | v1.0(2017-08-01) | 看似稳定 | 错误计数器(信息更新于 2020-03-02) |
| S32DS(NXP S32 Design Studio,Arm / PowerPC) | 支持 S32K144EVB-Q100、DEVKIT-MPC5748G 等评估板,文档随驱动附送 | v1.0(2017-08-01) | 看似稳定 | 错误计数器(信息更新于 2020-03-02) |
| ESP32 | 社区多次讨论(2023-03、2020-07);CANopenNode_ESP32作为 ESP-IDF 组件,为便于维护直接使用未修改的 CANopenNode 核心,另有配套示例工程 | — | 社区维护 | 基于 ESP-IDF 框架 |
| FreeRTOS(Neuberger 版) | 基于 v1.3-master 分支的 FreeRTOS 移植(2020-06-23) | v1.3 | 社区维护 | 多线程移植参考 |
| STM32CubeMX HAL | Atollic Studio 的 demo 工程,在 Nucleo STM32L452xx 板上测试通过(2019-05-03) | — | 社区维护 | 基于 STM32CubeMX HAL 库 |
| K64F FreeRTOS(Kinetis SDK) | Kinetis SDK 平台的 FreeRTOS 移植(2018-02-13) | — | 社区维护 | — |
| LPC1768(MBED) | 2016 年发布的 CANopenNode v1.0 时代示例 | v1.0 | 历史 | 最早一批 MBED 移植 |
5.3 历史端口(v1.0 时代)
文档还记录了已不再活跃的历史实现,供参考与追溯:RTOS eCos(2013)、Atmel SAM3X、ST STM32、NXP LPC177x_8x、Freescale MCF5282(均为 CANopenNode v1.0 时代)、BECK IPC Embedded Web-Controller SC243(2015 年最后一次发布)、ADI ADSP-CM408 混合信号控制器(2014 年贡献)、Microchip PIC18F(2006 年最后一次发布),以及最初属于 CANopenNode 的旧版 Object Dictionary Editor(依赖极老版本的 Firefox 运行)。这些条目印证了 CANopenNode 自 2006 年以来在多代硬件上的持续演进。
六、设备特性与仓库源码的对应关系
文档中反复出现的设备特性,都能在当前仓库中找到对应的协议实现模块,这为移植后的功能验收提供了明确抓手:
| 文档提到的特性 | 仓库中的实现位置 |
|---|---|
| OD 存储(自动或由标准 CANopen 命令触发) | storage/CO_storage.c 与 example/CO_storageBlank.c,通过 OD 对象 1010h/1011h 子索引配合 CRC 校验实现非易失保存/恢复 |
| 错误计数器 / Emergency | 301/CO_Emergency.c 配合驱动层CO_CANmodule_process()的CANerrorStatus位域 |
| 主站功能:SDO client、LSS master、NMT master | 301/CO_SDOclient.c、305/CO_LSSmaster.c、301/CO_NMT_Heartbeat.c |
| LED 指示灯(CiA 303-3) | 303/CO_LEDs.c(CANopen 状态红绿双色指示) |
| 节点地址/波特率在线配置 | 305/CO_LSSslave.c 与 main_blank.c 中的pendingNodeId/pendingBitRate |
| 网关 / 外部访问 | 309/CO_gateway_ascii.c(CiA 309-3 ASCII 命令接口) |
所有模块的全局协调入口在 CANopen.h:CO_new()、CO_CANinit()、CO_CANopenInit()、CO_CANopenInitPDO()、CO_process()等构成了与平台无关的"初始化—运行"契约,而 301/CO_config.h 则负责各模块的编译期裁剪(如是否启用 SDO client、LSS、LED 等),设备移植时可按需裁剪以节省资源——这也是 PIC 端能做到"RAM 不足 2KB"的重要原因。
七、移植新设备的关键检查点
综合 doc/deviceSupport.md、301/CO_driver.h 与 example 模板,移植一个新平台时建议按以下顺序自检:
- 类型与字节序:在
CO_driver_target.h中正确声明CO_LITTLE_ENDIAN或CO_BIG_ENDIAN(CANopen 本身为小端),定义bool_t、CO_CANrx_t、CO_CANtx_t、CO_CANmodule_t; - CAN 初始化与位速率:实现
CO_CANmodule_init(),支持文档规定的 8 种位速率,非法值回落 125; - 收发路径:实现
CO_CANrxBufferInit()/CO_CANtxBufferInit()/CO_CANsend(),接收侧提供CO_CANrxMsg_readIdent/DLC/Data并实现CO_CANinterrupt()的匹配分发与发送队列; - 同步与临界区:按目标平台(裸机中断优先级或 RTOS 互斥量)落实
CO_LOCK_*与CO_FLAG_*宏; - 周期处理:保证
CO_CANmodule_process()、CO_process()及 1 ms 级实时线程(SYNC/RPDO/TPDO)按文档要求的调用节奏运行; - 工程闭环:以
example为起点编写应用与 OD,先在任意系统上通过make编译,再移植驱动接入真实总线,最后参照第五节特性清单逐项验证。
按照文档的定位,CANopenNode 是一个"开放移植生态"而非封闭实现:官方维护 Linux 接口,其他控制器接口由独立工程承载,并通过 doc/deviceSupport.md 持续登记新设备。对开发者而言,最务实的路径是——以 example 空白模板为本、以 CANopenLinux 与 CANopenPIC 为参照、按官方七步法提交自己的移植成果,让 CANopen 协议栈在你的硬件上稳定运行,并回馈到整个社区。
- 嵌入式
- 通信
【免费下载链接】CANopenNode
CANopen protocol stack
相关推荐
GsonFactory最佳实践:避免这些坑让你的解析更高效
GsonFactory最佳实践:避免这些坑让你的解析更高效 在Android开发中,Json解析是每个开发者都会遇到的基础任务,但你是否经常遇到因为后台数据格式
移动开发序列化F´ 新平台移植指南:Toolchain、平台文件、Fw::Types、硬件驱动与 OSAL 五步移植法
F´ 新平台移植指南:Toolchain、平台文件、Fw::Types、硬件驱动与 OSAL 五步移植法 F´(F Prime)是一个面向飞行软件与嵌入式系统的
嵌入式系统编程Dogecoin 内嵌 LevelDB 的 port 平台抽象层解析:接口设计、默认实现与跨平台移植指南
Dogecoin 内嵌 LevelDB 的 port 平台抽象层解析:接口设计、默认实现与跨平台移植指南 本文围绕 src/leveldb/port/READM
区块链
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考