• CANopen笔记4 -- CanFestival在树莓派3B+上使用


      CanFestival是开源的CANOpen协议库(其它开源库还有CANOpenNodeLely CANopenCANopen Stack,以及ROS下的ros_canopen,Python实现的canopen等)。CanFestival具有许多优势:

    1. CanFestival为开发者提供了许多工具,以提高开发的便利性。例如用于生成节点对象字典源代码的对象字典编辑器objdictedit,以及便于开发者自由配置编译选项的配置脚本。

    2. CanFestival能够运行于多种类型的平台。CanFestival源代码由ANSI-C编写,驱动和例程的编译情况仅取决于具体的编译工具。在目前最新的版本中,官方提供了对于多种硬件平台的驱动。此外,CanFestival可以在任意类Unix系统下编译和运行,如Linux和FreeBSD。

    3. CanFestival协议功能完整,完全符合CANopen标准。CanFestival完全支持2002年2月发布的CIA DS-301 V4.02标准,并支持CiA DS302中的简明DFC协议。

    一、树莓派3B+上编译安装CanFestival

      CanFestival的源代码可以在https://hg.beremiz.org/CanFestival-3下载,解压后进入CanFestival-3的目录,在树莓派3B+上通过如下命令来配置编译选项

    ./configure --arch=armv7 --target=unix --can=socket

      其中arch选项用于设置处理器架构,target选项设置运行的目标系统,can选项设置can底层驱动,这里选socket设置为socket-can。其它选项通过./configure --help查询,如果想要在CanFestival运行时输出调试信息,可以通过选项--debug=MSG,WAR来实现。

      在 Linux 系统中,CAN 总线接口设备作为网络设备被系统进行统一管理。在 CAN 总线应用开发方面,Linux 提供了SocketCAN接口,使得CAN总线通信近似于以太网的通信(具体使用SocketCAN编程可参考Linux CAN编程详解)。使用树莓派3B+发送CAN消息可以通过其SPI接口外接CAN扩展板实现,比如微雪电子的RS485 CAN HAT 或双通道CAN的2-CH CAN FD HAT

       配置完成后,通过make命令编译,以及sudo make install进行安装。在安装对象字典编辑器时要保证安装了Python(2.4版本以上)及wxPython(2.6.3.2版本以上),对象字典编辑器的界面基于wxPython构建,可通过sudo apt-get install python-wxtools命令进行安装。CanFestival安装成功后其头文件会安装到/usr/local/include/canfestival目录下,库文件会安装到/usr/local/lib中。

      通过下面的命令配置CAN,设置波特率,并开启can0

    sudo ip link set can0 type can bitrate 500000
    sudo ifconfig can0 up

      在CanFestival-3的example文件夹中CANOpenShell示例程序可以用来在终端中执行一些CANOpen指令,比如扫描网络,发送SDO读写其它节点的对象字典,进行NMT控制等,开启CANOpenShell的用法如下:

    CANOpenShell load#CanLibraryPath,channel,baudrate,nodeid,type (0:slave, 1:master)

      比如通过socket-can发送can消息,通道为can0,波特率500k,节点id为1,类型为主节点,开启CANOpenShell的命令为:

      CANOpenShell load#libcanfestival_can_socket.so,can0,500k,1,1

      将树莓派CAN扩展版的CAN-H与CAN-L接到笔记本的USB-CAN卡上,打开CANPro软件,可以看到CANOpenShell节点启动后会发送bootup消息,bootup消息的报文格式为:

    COB-ID Data(1 byte)
    0x700+Node id 00

     

    二、开发新的CANOpen节点

      CanFestival自带的文档不够详细,初学者需要仔细阅读CanFestival v3.0 Manual、源代码doc文件夹下的CANOpen_memento.pdf以及doxygen根据代码注释生成的文档,除此之外主要就是参考example里面的几个示例程序,如SillySlave、TestMasterSlave、CANOpenShell等。SillySlave示例程序展示了从节点接收SYNC消息给主节点发送TPDO数据,TestMasterSlave展示了主从节点之间的相互通信(涉及NMT、SDO、PDO、SYNC等内容)。用objdictedit打开SillySlave的对象字典SillySlave.od文件,在TPDO1通信参数中可看到子索引02h设置为1,即收到1个SYNC同步消息发送一次PDO数据。

      在使用CanFestival时要明确设计的节点是要当master(如运动控制中的上位机控制节点)对其它节点进行管理,还是要当slave(如伺服驱动器节点)响应master进行操作。任意时刻CANopen网络中都仅存在一个设备以主设备身份执行特定功能,CANopen 网络中所有其他的设备均为从设备。使用CanFestival不管是开发主节点还是从节点均要设计对象字典,对象字典定义了节点的各种参数和内容。

      如下图所示,CanFestival通过固定的索引/子索引来访问对象列表中的条目。对象列表提供一个指向存储器中某个变量的指针。应用程序可直接通过变量名称访问所需的条目。对象字典列表就构成了索引/子索引与对应变量名称之间的接口。子索引条目采用subindex结构体描述(参考CanFestival源代码include文件夹下的objdictdef.h文件,定义了对象字典相关的参数)

    typedef struct td_subindex
    {
        UNS8                    bAccessType; /* 访问类型 */
        UNS8                    bDataType;   /* 数据类型 */
        UNS32                   size;        /* 数据大小 */
        void*                   pObject;     /* 指针指向变量 */
    } subindex;

      由于对象字典内容较多,CanFestival提供objdictedit程序通过图形化界面快速设计并生成所需要的对象字典。如下图,objdictedit中新建一个节点时选择节点类型(主节点/主站master或从节点/从站slave),输入节点名称(如MasterNode,后续将生成以节点名称为名字的MasterNode.c文件和对应的MasterNode.h的头文件),Profile中可选择不同的子协议类型(如运动控制子协议DS-402)。

      对象字典的配置涉及对CANOpen协议的理解,可参考DS-301协议文档、CANOpen_memento.pdf以及《现场总线CANopen设计与应用》一书。使用对象字典编辑器完成节点的配置后,点击Build Dictionary可生成对应的对象字典C语言源代码。

      CanFestival中最重要的数据类型为结构体CO_Data,不需要用户进行初始化,在对象字典编辑工具生成C文件的时候会对其进行初始化赋值(如果不初始化会出现段错误segmentation fault)。

      生成对象字典C文件后,程序流程可参考SillySlave或TestMasterSlave代码。最后在用gcc或g++编译时注意canfestival头文件和相关库的路径,以及链接的库(-lrt :provides POSIX realtime extensions;如果程序中使用dlopen、dlsym、dlclose、dlerror 显示加载动态库,需要设置链接选项 -ldl)

    g++ -O2 main.c master.c MasterNode.c -I /usr/local/include/canfestival/ -L /usr/local/lib/ -lcanfestival -lcanfestival_unix -lpthread -lrt -ldl -o test

      以SillySlave示例程序为模板将其改为最简单的主节点程序,main.c代码如下:

     1 #include "master.h"
     2 
     3 
     4 int main(int argc,char **argv)
     5 {
     6     char* LibraryPath = (char*)"/usr/local/lib/libcanfestival_can_socket.so"; 
     7 
     8     LoadCanDriver(LibraryPath);
     9 
    10     if(InitCANdevice((char*)"vcan0" , 500000,  0x0A) < 0)
    11     {
    12         printf("
    InitCANdevice() failed, exiting.
    ");
    13         return -1;
    14     }
    15 
    16     return 0;
    17 }
    View Code

      主节点相关的代码如下(主节点代码中用到了前一步对象字典编辑器生成的主节点对象字典MasterNode.c代码),master.h:

     1 #include "canfestival.h"
     2 #include "data.h"
     3 #include <unistd.h>
     4 #include <stdio.h>
     5 
     6 
     7 INTEGER8 InitCANdevice( char* bus, UNS32 baudrate, UNS8 node );
     8 
     9 void MasterNode_heartbeatError(CO_Data* d, UNS8);
    10 
    11 UNS8 MasterNode_canSend(Message *);
    12 
    13 void MasterNode_initialisation(CO_Data* d);
    14 void MasterNode_preOperational(CO_Data* d);
    15 void MasterNode_operational(CO_Data* d);
    16 void MasterNode_stopped(CO_Data* d);
    17 
    18 void MasterNode_post_sync(CO_Data* d);
    19 void MasterNode_post_TPDO(CO_Data* d);
    20 void MasterNode_storeODSubIndex(CO_Data* d, UNS16 wIndex, UNS8 bSubindex);
    21 void MasterNode_post_emcy(CO_Data* d, UNS8 nodeID, UNS16 errCode, UNS8 errReg);
    View Code

      master.c代码如下,在主节点进入预操作状态Pre-operational后,在对应的回调函数中通过setState函数改变主节点状态,进入操作状态operational。

      1 #include "MasterNode.h"
      2 #include "master.h"
      3 
      4 
      5 static UNS8 masterNodeID = 0;  
      6 
      7 void InitNode(CO_Data* d, UNS32 id)
      8 {
      9     /* Defining the node Id */
     10     setNodeId(&MasterNode_Data, masterNodeID);
     11     /* CAN init */
     12     setState(&MasterNode_Data, Initialisation);
     13 }
     14 
     15 void Exit(CO_Data* d, UNS32 id)
     16 {
     17     setState(&MasterNode_Data, Stopped);
     18 }
     19 
     20 INTEGER8 InitCANdevice(char * bus, UNS32 baudrate, UNS8 node )
     21 { 
     22     char busName[2];
     23     char baudRate[7];
     24     s_BOARD board;
     25 
     26 
     27     sprintf(baudRate, "%uK", baudrate);
     28     board.busname = bus;
     29     board.baudrate = baudRate;
     30 
     31     masterNodeID = node;
     32 
     33     MasterNode_Data.heartbeatError = MasterNode_heartbeatError;
     34     MasterNode_Data.initialisation = MasterNode_initialisation;
     35     MasterNode_Data.preOperational = MasterNode_preOperational;
     36     MasterNode_Data.operational = MasterNode_operational;
     37     MasterNode_Data.stopped = MasterNode_stopped;
     38     MasterNode_Data.post_sync = MasterNode_post_sync;
     39     MasterNode_Data.post_TPDO = MasterNode_post_TPDO;
     40     MasterNode_Data.storeODSubIndex = MasterNode_storeODSubIndex;
     41     MasterNode_Data.post_emcy = MasterNode_post_emcy;
     42     
     43     TimerInit();
     44 
     45     if(!canOpen(&board, &MasterNode_Data))
     46     {
     47         printf("
    aInitCANdevice() CAN bus %s opening error, baudrate=%s
    ",board.busname, board.baudrate);
     48         return -1;
     49     }
     50 
     51 
     52     printf("
    InitCANdevice(), canOpen() OK, starting timer loop...
    ");
     53 
     54     /* Start timer thread */
     55     StartTimerLoop(&InitNode); 
     56     
     57     /* wait Ctrl-C */
     58     pause();
     59     printf("
    Finishing.
    ");
     60     
     61     /* Stop timer thread */
     62     StopTimerLoop(&Exit);
     63     return 0;
     64 }
     65 
     66 void MasterNode_heartbeatError(CO_Data* d, UNS8 heartbeatID)
     67 {
     68     printf("MasterNode_heartbeatError %d
    ", heartbeatID);
     69 }
     70 
     71 void MasterNode_initialisation(CO_Data* d )
     72 {
     73     printf("MasterNode_initialisation
    ");
     74 }
     75 
     76 
     77 void MasterNode_preOperational(CO_Data* d)
     78 {
     79     printf("MasterNode_preOperational
    ");
     80 
     81     setState(d, Operational);
     82 }
     83 
     84 void MasterNode_operational(CO_Data* d)
     85 {
     86     printf("MasterNode_operational
    ");
     87 }
     88 
     89 void MasterNode_stopped(CO_Data* d)
     90 {
     91     printf("MasterNode_stopped
    ");
     92 }
     93 
     94 void MasterNode_post_sync(CO_Data* d)
     95 {
     96     printf("MasterNode_post_sync
    ");
     97 }
     98 
     99 void MasterNode_post_TPDO(CO_Data* d)
    100 {
    101     printf("MasterNode_post_TPDO
    ");
    102 }
    103 
    104 void MasterNode_storeODSubIndex(CO_Data* d, UNS16 wIndex, UNS8 bSubindex)
    105 {
    106     /*TODO : 
    107      * - call getODEntry for index and subindex, 
    108      * - save content to file, database, flash, nvram, ...
    109      * 
    110      * To ease flash organisation, index of variable to store
    111      * can be established by scanning d->objdict[d->ObjdictSize]
    112      * for variables to store.
    113      * 
    114      * */
    115     printf("MasterNode_storeODSubIndex : %4.4x %2.2xh
    ", wIndex,  bSubindex);
    116 }
    117 
    118 void MasterNode_post_emcy(CO_Data* d, UNS8 nodeID, UNS16 errCode, UNS8 errReg)
    119 {
    120     printf("Slave received EMCY message. Node: %2.2xh  ErrorCode: %4.4x  ErrorRegister: %2.2xh
    ", nodeID, errCode, errReg);
    121 }
    View Code

      为了方便观察输出信息,也可通过创建虚拟vcan,使用candump命令(sudo apt-get install can-utils)查看can报文。 

    sudo modprobe vcan
    sudo ip link add dev vcan0 type vcan
    sudo ip link set up vcan0

      从程序输出的信息中可以看到主节点在初始化状态后发送Boot-up message,然后自动进入Pre-operational预操作状态,随后按照程序中的setState函数设定进入operational操作状态。

    参考

    1. CanFestival-3

    2.现场总线CANopen设计与应用

    3. CANopen DS301协议中文翻译

    4. 基于ZYNQ的开源CANopen协议栈CANFestival移植

    5. CanOpen on a Raspberry PI using CanFestival

    6. MX6ULL-canfestival移植

    7. canFestival移植

  • 相关阅读:
    Java实现HadoopHA集群的hdfs控制
    Hadoop-2.8.5的HA集群搭建
    Python实现bing必应壁纸下载
    使用Python3将Markdown(.md)文本转换成 html、pdf
    使用GitHub作Free图床
    JavaMail实践--实现邮件发送
    Python3实现图片转字符画
    Java编写的Java编辑器_JavaIDE
    Java实现简易版计算器
    Java实现Excel表格操作--API:jxl
  • 原文地址:https://www.cnblogs.com/21207-iHome/p/15109710.html
Copyright © 2020-2023  润新知