基于STM32的CANOpenNode从站实现
此博客为学习记录,撰写过程可能存在部分引用,如有侵权可联系作者删除。其次,本文仅以读者自己的应用为例,使用过程可能会忽略部分说明,欢迎一起交流学习,指出读者文中的错误描述以便让广大攻城狮得到更优质的文章学习。
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、总结
此博客为学习记录,撰写过程可能存在部分引用,如有侵权可联系作者删除。其次,本文仅以读者自己的应用为例,使用过程可能会忽略部分说明,欢迎一起交流学习,指出读者文中的错误描述以便让广大攻城狮得到更优质的文章学习。
更多推荐



所有评论(0)