canfestival-3源碼詳解一:重要結構體

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[]來說,對應的firstIndexlastIndex如下:

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;
}

至於這個函數到底有什麼作用,後面再說。
未完待續。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章