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

至于这个函数到底有什么作用,后面再说。
未完待续。。。

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