CANFestival-3源碼詳解一:重要結構體
有幾點需要說明:
1.使用的是官網下載的canfestival-3源代碼,下載的壓縮包文件名是:Mongo-canfestival-3-asc-1a25f5151a8d.zip,使用的是源碼裏面自帶的對象字典編輯器來生成對象字典文件;
2.主要解析源碼裏面與DS 301協議有關的代碼,DS 301協議參考文檔爲301_v04000201.pdf;
3.推薦一個文檔叫《CANopen輕鬆入門》,介紹了CANopen的基礎知識,廣州致遠電子出品,寫的非常好。還推薦一個文檔《CANopen high-level protocol for CAN-bus》。
4.本文默認讀者對CANopen有所瞭解,不涉及CANopen的基礎知識。
1.CANOpen node structure
canfestival裏面最核心的一個結構體,就是這個節點數據結構體,這個結構體包含了一個CANOpen節點(node)需要用到的所有數據信息。這個結構體定義在data.h文件中,源碼如下:
struct struct_CO_Data {
/* Object dictionary */
UNS8 *bDeviceNodeId;
const indextable *objdict;
s_PDO_status *PDO_status;
TIMER_HANDLE *RxPDO_EventTimers;
void (*RxPDO_EventTimers_Handler)(CO_Data*, UNS32);
const quick_index *firstIndex;
const quick_index *lastIndex;
const UNS16 *ObjdictSize;
const UNS8 *iam_a_slave;
valueRangeTest_t valueRangeTest;
/* SDO */
s_transfer transfers[SDO_MAX_SIMULTANEOUS_TRANSFERS];
/* s_sdo_parameter *sdo_parameters; */
/* State machine */
e_nodeState nodeState;
s_state_communication CurrentCommunicationState;
initialisation_t initialisation;
preOperational_t preOperational;
operational_t operational;
stopped_t stopped;
void (*NMT_Slave_Node_Reset_Callback)(CO_Data*);
void (*NMT_Slave_Communications_Reset_Callback)(CO_Data*);
/* NMT-heartbeat */
UNS8 *ConsumerHeartbeatCount;
UNS32 *ConsumerHeartbeatEntries;
TIMER_HANDLE *ConsumerHeartBeatTimers;
UNS16 *ProducerHeartBeatTime;
TIMER_HANDLE ProducerHeartBeatTimer;
heartbeatError_t heartbeatError;
e_nodeState NMTable[NMT_MAX_NODE_ID];
/* NMT-nodeguarding */
TIMER_HANDLE GuardTimeTimer;
TIMER_HANDLE LifeTimeTimer;
nodeguardError_t nodeguardError;
UNS16 *GuardTime;
UNS8 *LifeTimeFactor;
UNS8 nodeGuardStatus[NMT_MAX_NODE_ID];
/* SYNC */
TIMER_HANDLE syncTimer;
UNS32 *COB_ID_Sync;
UNS32 *Sync_Cycle_Period;
/*UNS32 *Sync_window_length;;*/
post_sync_t post_sync;
post_TPDO_t post_TPDO;
post_SlaveBootup_t post_SlaveBootup;
post_SlaveStateChange_t post_SlaveStateChange;
/* General */
UNS8 toggle;
CAN_PORT canHandle;
scanIndexOD_t scanIndexOD;
storeODSubIndex_t storeODSubIndex;
/* DCF concise */
const indextable* dcf_odentry;
UNS8* dcf_cursor;
UNS32 dcf_entries_count;
UNS8 dcf_status;
UNS32 dcf_size;
UNS8* dcf_data;
/* EMCY */
e_errorState error_state;
UNS8 error_history_size;
UNS8* error_number;
UNS32* error_first_element;
UNS8* error_register;
UNS32* error_cobid;
s_errors error_data[EMCY_MAX_ERRORS];
post_emcy_t post_emcy;
#ifdef CO_ENABLE_LSS
/* LSS */
lss_transfer_t lss_transfer;
lss_StoreConfiguration_t lss_StoreConfiguration;
#endif
};
先看一下這個結構體裏面包含哪些內容。
1.1 Object dictionary
1.1.1 Node-ID
UNS8 *bDeviceNodeId;
每個節點都有一個唯一的標識叫Node-ID,這個變量就是用來記錄Node-ID的。
根據源碼和301官方文檔以及《CANopen high-level protocol for CAN-bus》文檔來看,這個Node-ID佔7位,取值的範圍是:[1, 127]。
判斷Node-ID是否有效的源碼如下,可以看到Node-ID範圍是[1, 127]。
if(!(nodeId>0 && nodeId<=127)){
MSG_WAR(0x2D01, "Invalid NodeID",nodeId);
return;
}
而有些網上的教程文檔寫的是[0, 127],甚至有些寫的是最大128都是不對的,需要注意0是不能作爲Node-ID的。
1.1.2 Index table
const indextable *objdict;
這個變量用來存放每個對象的索引,根據索引就能找到真正存放對象數據的位置。它是一個結構體數組類型,這個結構體源碼如下:
struct td_indextable
{
subindex* pSubindex; /* Pointer to the subindex */
UNS8 bSubCount; /* the count of valid entries for this subindex
* This count here defines how many memory has been
* allocated. this memory does not have to be used.
*/
UNS16 index;
};
其中
subindex* pSubindex;
保存子索引的相關信息,在CANopen中,每個對象都有16位索引和8位子索引。
UNS8 bSubCount;
表示子索引的個數,8位子索引最多可以表示255個子索引。
UNS16 index;
表示索引。
定義了多少個變量,這個數組長度就是多少,例如通過對象字典編輯器生成的對象字典源碼中,這個數組初始化的源碼如下:
/**************************************************************************/
/* Declaration of pointed variables */
/**************************************************************************/
const indextable TestMaster_objdict[] =
{
{ (subindex*)TestMaster_Index1000,sizeof(TestMaster_Index1000)/sizeof(TestMaster_Index1000[0]), 0x1000},
{ (subindex*)TestMaster_Index1001,sizeof(TestMaster_Index1001)/sizeof(TestMaster_Index1001[0]), 0x1001},
{ (subindex*)TestMaster_Index1017,sizeof(TestMaster_Index1017)/sizeof(TestMaster_Index1017[0]), 0x1017},
{ (subindex*)TestMaster_Index1018,sizeof(TestMaster_Index1018)/sizeof(TestMaster_Index1018[0]), 0x1018},
{ (subindex*)TestMaster_Index1200,sizeof(TestMaster_Index1200)/sizeof(TestMaster_Index1200[0]), 0x1200},
{ (subindex*)TestMaster_Index1201,sizeof(TestMaster_Index1201)/sizeof(TestMaster_Index1201[0]), 0x1201},
{ (subindex*)TestMaster_Index1280,sizeof(TestMaster_Index1280)/sizeof(TestMaster_Index1280[0]), 0x1280},
{ (subindex*)TestMaster_Index1281,sizeof(TestMaster_Index1281)/sizeof(TestMaster_Index1281[0]), 0x1281},
{ (subindex*)TestMaster_Index1400,sizeof(TestMaster_Index1400)/sizeof(TestMaster_Index1400[0]), 0x1400},
{ (subindex*)TestMaster_Index1401,sizeof(TestMaster_Index1401)/sizeof(TestMaster_Index1401[0]), 0x1401},
{ (subindex*)TestMaster_Index1600,sizeof(TestMaster_Index1600)/sizeof(TestMaster_Index1600[0]), 0x1600},
{ (subindex*)TestMaster_Index1601,sizeof(TestMaster_Index1601)/sizeof(TestMaster_Index1601[0]), 0x1601},
{ (subindex*)TestMaster_Index1800,sizeof(TestMaster_Index1800)/sizeof(TestMaster_Index1800[0]), 0x1800},
{ (subindex*)TestMaster_Index1801,sizeof(TestMaster_Index1801)/sizeof(TestMaster_Index1801[0]), 0x1801},
{ (subindex*)TestMaster_Index1A00,sizeof(TestMaster_Index1A00)/sizeof(TestMaster_Index1A00[0]), 0x1A00},
{ (subindex*)TestMaster_Index1A01,sizeof(TestMaster_Index1A01)/sizeof(TestMaster_Index1A01[0]), 0x1A01},
};
其中定義了16個對象,這些對象的含義後文會講到。
上述子索引結構體subindex
的源碼如下:
typedef struct td_subindex
{
UNS8 bAccessType;
UNS8 bDataType; /* Defines of what datatype the entry is */
UNS32 size; /* The size (in Byte) of the variable */
void* pObject; /* This is the pointer of the Variable */
ODCallback_t callback; /* Callback function on write event */
} subindex;
其中
UNS8 bAccessType;
表示權限,有三種:RW, WO, RO。
UNS8 bDataType;
表示這個對象裏面存儲數據的數據類型,數據類型的源碼如下,與301文檔中的定義完全一致:
/** this are static defined datatypes taken fCODE the canopen standard. They
* are located at index 0x0001 to 0x001B. As described in the standard, they
* are in the object dictionary for definition purpose only. a device does not
* to support all of this datatypes.
*/
#define boolean 0x01
#define int8 0x02
#define int16 0x03
#define int32 0x04
#define uint8 0x05
#define uint16 0x06
#define uint32 0x07
#define real32 0x08
#define visible_string 0x09
#define octet_string 0x0A
#define unicode_string 0x0B
#define time_of_day 0x0C
#define time_difference 0x0D
#define domain 0x0F
#define int24 0x10
#define real64 0x11
#define int40 0x12
#define int48 0x13
#define int56 0x14
#define int64 0x15
#define uint24 0x16
#define uint40 0x18
#define uint48 0x19
#define uint56 0x1A
#define uint64 0x1B
#define pdo_communication_parameter 0x20
#define pdo_mapping 0x21
#define sdo_parameter 0x22
#define identity 0x23
/* CanFestival is using 0x24 to 0xFF to define some types containing a
value range (See how it works in objdict.c)
*/
UNS32 size;
表示數據的大小,即佔多少字節。
void* pObject;
表示這個對象保存數據變量的指針,即數據真正存儲的位置。
ODCallback_t callback;
寫入事件時的回調函數,回調函數的函數原型如下:
typedef UNS32 (*ODCallback_t)(CO_Data* d, const indextable *, UNS8 bSubindex);
1.1.3 PDO structure
s_PDO_status *PDO_status;
PDO_status
用來保存發送PDO通信參數的數組,對每一個TPDO都會產生一個s_PDO_status
結構體,就算沒有定義TPDO,爲避免編譯錯誤,這個數組長度也至少是1,這一點在後面結構體初始化的時候再說。
s_PDO_status
的源碼如下:
/** The PDO structure */
struct struct_s_PDO_status {
UNS8 transmit_type_parameter;
TIMER_HANDLE event_timer;
TIMER_HANDLE inhibit_timer;
Message last_message;
};
可以看出這個結構體實際上保存了PDO的通信參數,只會保存TPDO的參數,不會保存RPDO的參數。其實PDO的通信參數在1.1.2講的對象索引裏面都會保存,爲什麼在這個地方要單獨用一個結構體來保存,暫時不清楚。
首先回顧一下PDO的通信參數
不論是TPDO還是RPDO都有通信參數和映射參數兩種參數,通信參數有如下六種:
subindex | name | type |
---|---|---|
0x01 | COB ID | UNSIGNED32 |
0x02 | Transmission Type | UNSIGNED8 |
0x03 | Inhibit Time | UNSIGNED16 |
0x04 | Compatibility Entry | UNSIGNED8 |
0x05 | Event Time | UNSIGNED16 |
0x06 | SYNC start value | UNSIGNED8 |
這裏先解釋一下這六個參數是什麼意思吧。
1.COB ID就不用過多解釋了,簡單來理解就是,通過COB ID可以讓CAN節點知道,這一幀報文屬於PDO、SDO、NMT、SYNC還是其他。
2.Transmission Type表示PDO的通信類型,有同步、異步、週期、非週期等,具體見《CANopen high-level protocol for CAN-bus》的Table3。取值範圍是0~255,取值定義的源碼如下:
/** definitions of the different types of PDOs' transmission
*
* SYNCHRO(n) means that the PDO will be transmited every n SYNC signal.
*/
#define TRANS_EVERY_N_SYNC(n) (n) /*n = 1 to 240 */
#define TRANS_SYNC_ACYCLIC 0 /* Trans after reception of n SYNC. n = 1 to 240 */
#define TRANS_SYNC_MIN 1 /* Trans after reception of n SYNC. n = 1 to 240 */
#define TRANS_SYNC_MAX 240 /* Trans after reception of n SYNC. n = 1 to 240 */
#define TRANS_RTR_SYNC 252 /* Transmission on request */
#define TRANS_RTR 253 /* Transmission on request */
#define TRANS_EVENT_SPECIFIC 254 /* Transmission on event */
#define TRANS_EVENT_PROFILE 255 /* Transmission on event */
3.Inhibit Time表示PDO發送的最小時間間隔,避免發送頻率太快,總線負載太大,單位是100us。
4.Compatibility Entry這個不知道是啥,文檔裏面都沒提到,先不管。
5.Event Time如果是定時發送PDO,那麼這個參數表示的定時時間,如果這個參數爲0,那麼表示事件觸發發送PDO,單位是ms。
6.SYNC start value同步起始值。當PDO爲同步發送時,比如Transmission Type=10,那麼收到10個同步包後才發送PDO,如果SYNC start value=2,那麼剛開始時收到2個同步包就開始發送PDO,之後就按10個同步包發送。
struct_s_PDO_status
結構體將TPDO的參數裏的2、3、5參數單獨拿出來保存,這樣做的目的是啥,暫時不知道。另外一個參數是Message last_message;
保存最新的TPDO message,同樣不知道這樣做的目的是啥。
1.1.4 RxPDO_EventTimers
TIMER_HANDLE *RxPDO_EventTimers;
void (*RxPDO_EventTimers_Handler)(CO_Data*, UNS32);
這兩個放在一起,從字面上看都與接收PDO時間定時器有關。
1.1.5 First and Last Index
const quick_index *firstIndex;
const quick_index *lastIndex;
這兩個變量主要用來存儲SDO、PDO在1.1.2中所述的對象字典中的索引值,主要是爲了讓後面使用SDO、PDO對象更方便。具體來說,結構體quick_index
的源碼如下:
typedef struct s_quick_index{
UNS16 SDO_SVR;
UNS16 SDO_CLT;
UNS16 PDO_RCV;
UNS16 PDO_RCV_MAP;
UNS16 PDO_TRS;
UNS16 PDO_TRS_MAP;
}quick_index;
SDO_SVR
表示SDO server的索引;SDO_CLT
表示SDO client的索引;PDO_RCV
表示RPDO的索引;PDO_RCV_MAP
表示RPDO映射對象的索引;PDO_TRS
表示TPDO的索引;PDO_TRS_MAP
表示TPDO映射對象的索引。
這兩個變量的值是隨1.1.2中TestMaster_objdict[]
一起由對象字典編輯器自動產生的,就拿1.1.2中的TestMaster_objdict[]
來說,對應的firstIndex
和lastIndex
如下:
const quick_index TestMaster_firstIndex = {
4, /* SDO_SVR */
6, /* SDO_CLT */
8, /* PDO_RCV */
10, /* PDO_RCV_MAP */
12, /* PDO_TRS */
14 /* PDO_TRS_MAP */
};
const quick_index TestMaster_lastIndex = {
5, /* SDO_SVR */
7, /* SDO_CLT */
9, /* PDO_RCV */
11, /* PDO_RCV_MAP */
13, /* PDO_TRS */
15 /* PDO_TRS_MAP */
};
表明TestMaster_objdict[]
中,索引4-5屬於SDO server對象;6-7屬於SDO client對象;8-9屬於RPDO對象;10-11屬於RPDO映射對象;12-13屬於TPDO對象;14-15屬於TPDO映射對象。
1.1.6 ObjdictSize
const UNS16 *ObjdictSize;
表示對象字典裏面對象的個數。初始化的值也是由對象字典編輯器自動產生的:
const UNS16 TestMaster_ObjdictSize = sizeof(TestMaster_objdict)/sizeof(TestMaster_objdict[0]);
1.1.7 Slave or Master
const UNS8 *iam_a_slave;
主機或從機標誌,也是在對象字典編輯器自動生成的文件中初始化。
const UNS8 TestMaster_iam_a_slave = 0;
0表示主機,1表示從機。
1.1.8 valueRangeTest
valueRangeTest_t valueRangeTest;
這是一個函數指針,用於檢測值是否的超出範圍。這個函數初始化也是在字典編輯器自動生成的文件中進行:
/**************************************************************************/
/* Declaration of value range types */
/**************************************************************************/
#define valueRange_EMC 0x9F /* Type for index 0x1003 subindex 0x00 (only set of value 0 is possible) */
UNS32 TestMaster_valueRangeTest (UNS8 typeValue, void * value)
{
switch (typeValue) {
case valueRange_EMC:
if (*(UNS8*)value != (UNS8)0) return OD_VALUE_RANGE_EXCEEDED;
break;
}
return 0;
}
至於這個函數到底有什麼作用,後面再說。
未完待續。。。