1、概述

        CANopen协议是在20世纪90年代末,由总部位于德国纽伦堡的CiA(CAN in Automation)组织,在CAL(CAN Application Layer)的基础上发展而来。

        经过对CANopen协议规范文本的多次修改,使得CANopen协议的稳定性、实时性、抗干扰性都得到了进一步的提高。并且CiA在CANopen的基础协议——CiA 301之上,对各个行业不断推出设备子协议,使CANopen协议在各个行业得到更快的发展与推广。

1.1 CANopen协议

        关于CANopen的协议的介绍,详细信息请参见CiA 301(CANopen应用层和通讯描述协议)或访问官网:https://www.can-cia.org/

1.2 CANopenNode协议栈

        CANopenNode是免费开源的CANopen协议栈。CANopen是建立在CAN基础上的用于嵌入式控制系统的国际标准化(EN 50325-4) (CiA301)高层协议。CANopenNode是用ANSI C语言以面向对象的方式编写的。它运行在不同的微控制器上,作为裸机应用或带RTOS应用。通信变量、设备变量、自定义变量收集在CANopen对象字典中,可以从C代码和CANopen网络访问。CANopenNode的主页为GitHub - CANopenNode/CANopenNode: CANopen protocol stack

源码文件说明如下所示:

①301/ - CANopen 应用层和通信配置文件

CO_config.h - CANopenNode 的配置宏。
CO_driver.h - CAN 硬件和 CANopenNode 之间的接口。
CO_ODinterface.h/.c - CANopen 对象字典接口。
CO_Emergency.h/.c - CANopen 紧急协议。
CO_HBconsumer.h/.c - CANopen 心跳消费者协议。
CO_NMT_Heartbeat.h/.c - CANopen 网络管理和心跳生产者协议。
CO_PDO.h/.c - CANopen 过程数据对象协议。
CO_SDOclient.h/.c - CANopen 服务数据对象 - 客户端协议(主站功能)。
CO_SDOserver.h/.c - CANopen 服务数据对象 - 服务器协议。
CO_SYNC.h/.c - CANopen 同步协议(生产者和消费者)。
CO_TIME.h/.c - CANopen 时间戳协议。
CO_fifo.h/.c - 用于 SDO 和网关数据传输的 Fifo 缓冲区。
crc16-ccitt.h/.c - CRC 16 CCITT 多项式的计算。

②303/ - CANopen 推荐

CO_LEDs.h/.c - CANopen LED 指示灯

③304/ - CANopen 安全

CO_SRDO.h/.c - CANopen 安全相关数据对象协议。

CO_GFC.h/.c - CANopen 全局故障安全命令(生产者和消费者)。

④305/ - CANopen 层设置服务 (LSS) 和协议

CO_LSS.h - CANopen 层设置服务协议(通用)。

CO_LSSmaster.h/.c - CANopen 层设置服务 - 主协议。

CO_LSSslave.h/.c - CANopen 层设置服务 - 从站协议。

⑤309/ - 从其他网络访问 CANopen

CO_gateway_ascii.h/.c - Ascii 映射:NMT 主站、LSS 主站、SDO 客户端。

⑥storage/ - 存储

CO_storage.h/.c - CANopen 数据存储基础对象。

CO_storageEeprom.h/.c - CANopen 数据存储对象,用于将数据存储到块设备 (eeprom) 中。

CO_eeprom.h - 与 CO_storageEeprom 一起使用的 eeprom 接口,功能是特定于目标系统的。

⑦extra/ - 附加项

CO_trace.h/.c - 用于随时间记录变量的 CANopen 跟踪对象。

⑧ example/ - 包含基本示例的目录,应在任何系统上编译

CO_driver_target.h - CANopenNode 的示例硬件定义。
CO_driver_blank.c - CANopenNode 的示例空白接口。
main_blank.c - 主线和其他线程 - 示例模板。
CO_storageBlank.h/.c - 数据存储到非易失性存储器的示例空白演示。
Makefile – Makefile for example。
DS301_profile.xpd - DS301 的 CANopen 设备描述文件。 它还包括 CANopenNode 的特定属性。 该文件在对象字典编辑器的配置文件中也可用。
DS301_profile.eds、DS301_profile.md - 标准 CANopen EDS 文件和降价文档文件,从DS301_profile.xpd 自动生成。
OD.h/.c - CANopen 对象字典源文件,从 DS301_profile.xpd 自动生成。

⑨doc/ - 文档目录

CHANGELOG.md - 更改日志文件。
deviceSupport.md - 有关受支持设备的信息。
objectDictionary.md - CANopen 对象字典接口的描述。

⑩ CANopen.h/.c 

CANopen 对象的初始化和处理,适用于通用配置。

1.3 对象字典

        对象字典是 CANopen 最重要的部分之一,每个开源的CANopne协议栈一般都会提供对应的软件编辑对象字典。CANopenEditor是CANopenNode提供的软件,链接为GitHub - CANopenNode/CANopenEditor: CANopen Object Dictionary Editor。只需提取 zip 文件并运行 EDSEditor.exe。CANopen对象字典编辑器主要有以下3点特征:

        1、 可导入:CANopen的电子数据文档(EDS或XDD的文件格式)。

        2、 可导出:CANopen的电子数据文档(EDS或XDD的文件格式)、文件资料、CANopenNode协议栈的C源文件。

        3、 为CANopen的对象字典、设备信息等提供图形化的编辑界面。

      开始新项目或打开现有的项目文件。支持许多项目文件类型,EDS、XDD v1.0、XDD v1.1、旧的自定义 XML 格式。 然后可以将生成的项目文件保存为 XDD v1.1 文件格式 (xmlns=“http://www.canopen.org/xml/1.1”)。项目文件也可以导出为其他格式,可用于生成对象字典的文档(EDS文件)和CANopenNode源文件(OD.c/.h文件)。如果启动了新项目,则可能会插入 DS301_profile.xpd。如果编辑现有(旧)项目,则可能会删除现有的通信特定参数,然后可能会插入新的 DS301_profile.xpd。要克隆、添加或删除,请选择对象并使用右键单击。正确设置自定义对象字典需要一些 CANopen知识。也可以从另一个项目中插入单独的对象。

        CANopenNode 在标准项目文件中包含一些自定义属性。本应用指南中将下载CANopenEditor在PC机上运行EDSEditor.exe,来编辑CANopen demo的对象字典,并由同样的参数配置生成“EDS文件”和“OD.c/.h文件”,分别供“上位机调试软件”和“CANopen协议栈”使用。

2、代码移植

        整个过程分为两个部分,首先使用STM32CubeMX软件建立基础的外设初始化工程,其后再对CANopenNode源码进行添加至工程中。

2.1 STM32CubeMX创建工程

        本示例使用的STM32F103RCT6,启动STM32CubeMX软件,创建工程。

1、CAN波特率配置为250K,开启相关中断。

2、定时器配置:仅需要超时中断功能,因此使用基础定时器TIM6,开启更新中断。

3、串口配置:协议栈运行过程中有打印函数,因为我们初始化串口,重定义printf就可以查看对应的log信息。

4、重新分配堆区的大小,修改为0x2000(因为CANopneNode协议栈申请4062byte大小字节)。

5、生成MDK5工程代码,在Keil中勾选Use MicroLIB。

6、重定向函数添加

int fputc(int ch, FILE *f)
{
      HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
      return ch;
}

自此工程软件配置完毕,可自行添加代码验证打印函数和程序是否正常执行。

2.3 CANopenNode源码添加

1、从CANopenNode (CANopenNode) / Repositories · GitHub下载源码。

CANopenEditor:是字典文件编辑软件,下载的代码中无exe文件(可能需要用户自己编译生成,了解的同学可以告知一下);可以我的网盘链接中自取。

CANopenNode:协议栈源码,上文有介绍。

CanOpenSTM32:基于STM32 HAL库驱动文件。

通过网盘分享的文件:CANopenEditor

链接: 百度网盘 请输入提取码 提取码: qmea

通过网盘分享的文件:CANopenNode

链接: 百度网盘 请输入提取码 提取码: 7nsf

2、在Keil工程目录下建立CANopneNode文件夹,文件夹中再创建一个CANopneNode文件夹,github下载部分文件直接复制到新建文件夹下(如下图红色框里所示将)。

3、在第一级的CANopenNode文件夹中创建名为STM32文件下,然后将源码中的部分文件夹直接拷贝到其中。

4、在keil创建对应的文件夹,并将对应的.c文件添加到Keil工程中。

5、主要代码如下所示:

#include "app_main.h"
#include "config.h"
#incldue "CO_app_STM32.h"
#incldue "OD.h"

CANopenNodeSTM32 CANopenNode;

void system_init(void)
{
    /* 初始化CANopen */
    canOpenNodeSTM32.CANHandle = &hcan;              /* 使用CAN接口 */
    canOpenNodeSTM32.HWInitFunction = MX_CAN_Init;     /* 初始化CAN */
    canOpenNodeSTM32.timerHandle = &htim6;             /* 使用的定时器句柄 */
    canOpenNodeSTM32.desiredNodeID = 1;                /* Node-ID */
    canOpenNodeSTM32.baudrate = 250;                   /* 波特率,单位KHz */
    canopen_app_init(&CANopenNode);
}
void app_main(void)
{
    system_init();
    while(1)
    {
        canopen_app_process(); /* CAN处理 */
    }
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) 
{
    if (htim == CANopenNode->timerHandle) 
    {
        canopen_app_interrupt();
    }
}

6、将代码烧录到控制板中,使用USB转CAN监视数据,设备上电时会发送boot up。

7、后续设备不会发出其他数据,因为源码中的心跳时间设置为0,我们可以尝试修改0x1017的对应的数值,从而开启定时心跳。可以从下图看出,通过SDO修改心跳包的数据由0x00变为0x05(运行态)。

00 00 06 01        //从站地址为1所以是601
2B 17 10 00 E8 03 00 00 
// 2B是写两个字节;17 10是0x1017;00:是0x1017的sub id;E8 03:1000ms,其他补0 

3、字典文件创建

        上述协议栈跑的是默认提供的字典文件,下面我们详细说一说EDSEditor如何从零开始创建属于自己的设备字典文件。

        1、运行EDSEditor软件,点击工具栏“File”-> “New”-> 在“Product name”中修改为“New Product”为“CANopenNodeSlave”-> 将该工程保存为“CANopenNodeSlave.xdd”。

2、单击“Insert Profile”->选择“DS301_profile.xpd”-> 弹出“Insert OD Objects”对话框中 ->“Target”中选择“CANopenNodeSlave”工程 -> 单击“Insert”。

3、0x1017修改为2000ms(用户可自行修改其他时间)。

4、将0x1400 ~ 0x1403和0x1800 ~ 0x1803对应PDO的参数配置为异步(0xFE),触发时间设置为1000ms。

5、左击“Manufacturer Specific Parameters”中的“Index”或“Name”选择“Add…”来新建自定义的对象。作者提供的字典文件中的变量类型为ARRAY(数组),所以可以可以添加多个子成员。TPDO和RPDO需要设置对应的权限。

TPDO:

RPDO:

6、在“TX PDO Mapping” 窗口中去配置TPDO的映射参数所寻址的对象,不勾选Invalid(启动对应的TPDO)。

7、在“RX PDO Mapping” 窗口中去配置RPDO的映射参数所寻址的对象,具体细节类似上述TPDO。

8、单击“File”-> “Export CanOpenNode…”-> 在“另存为”对话框中选择路径为STM32工程下的../CANopenNodeSlave\CANopenNode\STM32 文件夹 -> 将生成的OD源文件替换原有文件。若上述操作过程出现弹窗错误警告,请检查OD.h文件中30 - 50行是否缺少宏定义,若缺少请手动添加

9、烧录程序,上电运行控制板,USB转CAN监视数据如下图所示。

10、修改0x1018的厂商ID和产品ID(默认为0会导致在PLC上无法运行,CiA301的为0的ID为保留ID),导出新的OD文件,然后单击“File”-> “Export…”-> 在“另存为”对话框中选择“保存类型”为(*.eds)-> 单击“保存”将该文件名默认为“CANopenNodeSlave.eds”。

11、将对应的eds导入PLC,配置对应的主站信息,连接对应的硬件接口,可实现CANopen主从站的通讯。

4、补充说明

4.1 PDO变量读写

        通过阅读对应的OD文件,发现PDO变量可以通过此变量进行访问。

4.2、资料分享

通过网盘分享的文件:CANopenEditor

链接: https://pan.baidu.com/s/1JpmBz2-LLZuNBYMNofTi-Q 提取码: qmea

通过网盘分享的文件:CANopenNode

链接: https://pan.baidu.com/s/1LlkQEUFeVDZdr7T8z9Ir2Q 提取码: 7nsf

4.3、示例创建的字典文件

链接: https://pan.baidu.com/s/1JpmBz2-LLZuNBYMNofTi-Q 提取码: qmea

4.4 主要代码

#include "app_main.h"
#include "config.h"
#incldue "CO_app_STM32.h"
#incldue "OD.h"

CANopenNodeSTM32 CANopenNode;

void system_init(void)
{
        /* 初始化CANopen */
    canOpenNodeSTM32.CANHandle = &hcan;              /* 使用CAN接口 */
    canOpenNodeSTM32.HWInitFunction = MX_CAN_Init;     /* 初始化CAN */
    canOpenNodeSTM32.timerHandle = &htim6;             /* 使用的定时器句柄 */
    canOpenNodeSTM32.desiredNodeID = 1;                /* Node-ID */
    canOpenNodeSTM32.baudrate = 250;                   /* 波特率,单位KHz */
    canopen_app_init(&CANopenNode);
}
void app_main(void)
{
    static uint32_t system_log_tick = 0;
    static uint32_t tpdo_data_count = 0;
    system_init();
    while(1)
    {
    
        canopen_app_process(); /* CAN处理 */
        if((HAL_GetTick() - system_log_tick) >= 3000 )
        {
            //接收PDO打印
            printf("OD_RAM.x2001_rdpo_data[0] = %d\n",OD_RAM.x2001_rdpo_data[0]);
            printf("OD_RAM.x2001_rdpo_data[1] = %d\n",OD_RAM.x2001_rdpo_data[1]);
            printf("OD_RAM.x2001_rdpo_data[2] = %d\n",OD_RAM.x2001_rdpo_data[2]);
            printf("OD_RAM.x2001_rdpo_data[3] = %d\n",OD_RAM.x2001_rdpo_data[3]);
            //发送PDO
            OD_RAM.x2001_tdpo_data[0] = tpdo_data_count++; 
            OD_RAM.x2001_tdpo_data[1] = tpdo_data_count++; 
            OD_RAM.x2001_tdpo_data[2] = tpdo_data_count++;
            OD_RAM.x2001_tdpo_data[3] = tpdo_data_count++;                  
        }
    }
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) 
{
    if (htim == CANopenNode->timerHandle) 
    {
        canopen_app_interrupt();
    }
}

5、总结

        此博客为学习记录,撰写过程可能存在部分引用,如有侵权可联系作者删除。其次,本文仅以读者自己的应用为例,使用过程可能会忽略部分说明,欢迎一起交流学习,指出读者文中的错误描述以便让广大攻城狮得到更优质的文章学习。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐