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("\nInitCANdevice() failed, exiting.\n");
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("\n\aInitCANdevice() CAN bus %s opening error, baudrate=%s\n",board.busname, board.baudrate);
 48         return -1;
 49     }
 50 
 51 
 52     printf("\nInitCANdevice(), canOpen() OK, starting timer loop...\n");
 53 
 54     /* Start timer thread */
 55     StartTimerLoop(&InitNode); 
 56     
 57     /* wait Ctrl-C */
 58     pause();
 59     printf("\nFinishing.\n");
 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\n", heartbeatID);
 69 }
 70 
 71 void MasterNode_initialisation(CO_Data* d )
 72 {
 73     printf("MasterNode_initialisation\n");
 74 }
 75 
 76 
 77 void MasterNode_preOperational(CO_Data* d)
 78 {
 79     printf("MasterNode_preOperational\n");
 80 
 81     setState(d, Operational);
 82 }
 83 
 84 void MasterNode_operational(CO_Data* d)
 85 {
 86     printf("MasterNode_operational\n");
 87 }
 88 
 89 void MasterNode_stopped(CO_Data* d)
 90 {
 91     printf("MasterNode_stopped\n");
 92 }
 93 
 94 void MasterNode_post_sync(CO_Data* d)
 95 {
 96     printf("MasterNode_post_sync\n");
 97 }
 98 
 99 void MasterNode_post_TPDO(CO_Data* d)
100 {
101     printf("MasterNode_post_TPDO\n");
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\n", 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\n", 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移植


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM