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;
}
至于这个函数到底有什么作用,后面再说。
1.2 SDO
s_transfer transfers[SDO_MAX_SIMULTANEOUS_TRANSFERS];
这个结构体保存有关SDO的所有信息。SDO_MAX_SIMULTANEOUS_TRANSFERS
这个宏定义表示最大同时传输的SDO数目,在config.h中定义,默认值是1。这个结构体的源码如下:
/* The Transfer structure
Used to store the different segments of
- a SDO received before writing in the dictionary
- the reading of the dictionary to put on a SDO to transmit
WARNING : after a change in this structure check the macro s_transfer_Initializer in data.h
*/
struct struct_s_transfer {
UNS8 CliServNbr; /**< The index of the SDO client / server in our OD minus 0x1280 / 0x1200 */
UNS8 whoami; /**< Takes the values SDO_CLIENT or SDO_SERVER */
UNS8 state; /**< state of the transmission : Takes the values SDO_... */
UNS8 toggle;
UNS32 abortCode; /**< Sent or received */
/**< index and subindex of the dictionary where to store */
/**< (for a received SDO) or to read (for a transmit SDO) */
UNS16 index;
UNS8 subIndex;
UNS32 count; /**< Number of data received or to be sent. */
UNS32 offset; /**< stack pointer of data[]
* Used only to tranfer part of a line to or from a SDO.
* offset is always pointing on the next free cell of data[].
* WARNING s_transfer.data is subject to ENDIANISATION
* (with respect to CANOPEN_BIG_ENDIAN)
*/
UNS8 data [SDO_MAX_LENGTH_TRANSFER];
#ifdef SDO_DYNAMIC_BUFFER_ALLOCATION
UNS8 *dynamicData;
UNS32 dynamicDataSize;
#endif //SDO_DYNAMIC_BUFFER_ALLOCATION
UNS8 peerCRCsupport; /**< True if peer supports CRC */
UNS8 blksize; /**< Number of segments per block with 0 < blksize < 128 */
UNS8 ackseq; /**< sequence number of last segment that was received successfully */
UNS32 objsize; /**< Size in bytes of the object provided by data producer */
UNS32 lastblockoffset; /**< Value of offset before last block */
UNS8 seqno; /**< Last sequence number received OK or transmitted */
UNS8 endfield; /**< nbr of bytes in last segment of last block that do not contain data */
rxStep_t rxstep; /**< data consumer receive step - set to true when last segment of a block received */
UNS8 tmpData[8]; /**< temporary segment storage */
UNS8 dataType; /**< Defined in objdictdef.h Value is visible_string
* if it is a string, any other value if it is not a string,
* like 0. In fact, it is used only if client.
*/
TIMER_HANDLE timer; /**< Time counter to implement a timeout in milliseconds.
* It is automatically incremented whenever
* the line state is in SDO_DOWNLOAD_IN_PROGRESS or
* SDO_UPLOAD_IN_PROGRESS, and reseted to 0
* when the response SDO have been received.
*/
SDOCallback_t Callback; /**< The user callback func to be called at SDO transaction end */
};
typedef struct struct_s_transfer s_transfer;
看这个结构体的注释可以看出,这个结构体主要用于保存:在写入对象字典之前,接收到的SDO;在SDO发送之前,从对象字典读取的数据。具体细节就不深入了,后面用到再说。
1.3 State machine
/* 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*);
保存状态机相关的信息。CANopen Node定义的状态机见文档:《CANopen high-level protocol for CAN-bus》。
1.3.1 nodeState
nodeState表示节点状态,源码定义如下:
/* The nodes states
* -----------------
* values are choosen so, that they can be sent directly
* for heartbeat messages...
* Must be coded on 7 bits only
* */
/* Should not be modified */
enum enum_nodeState {
Initialisation = 0x00,
Disconnected = 0x01,
Connecting = 0x02,
Preparing = 0x02,
Stopped = 0x04,
Operational = 0x05,
Pre_operational = 0x7F,
Unknown_state = 0x0F
};
typedef enum enum_nodeState e_nodeState;
文档《CANopen high-level protocol for CAN-bus》中关于节点状态的描述如下:
Value | State |
---|---|
0 | Initialising |
1 | Disconnected * |
2 | Connecting * |
3 | Preparing * |
4 | Stopped |
5 | Operational |
127 | Pre-operational |
带*的状态只在支持extended boot-up节点才有;在Node Guard回复中不可能出现状态0,因为在Initialising状态下节点不会回复Node Guard消息。
1.3.2 CurrentCommunicationState
保存是否开启对应功能的标志,这个结构体源码:
typedef struct
{
INTEGER8 csBoot_Up;
INTEGER8 csSDO;
INTEGER8 csEmergency;
INTEGER8 csSYNC;
INTEGER8 csLifeGuard;
INTEGER8 csPDO;
INTEGER8 csLSS;
} s_state_communication;
1表示开启对应功能,0表示关闭对应功能。比如:在初始化状态的时候,s_state_communication newCommunicationState = {1, 0, 0, 0, 0, 0, 0};
表示只开启boot_up功能,其他功能关闭。在Pre-operational状态的时候,s_state_communication newCommunicationState = {0, 1, 1, 1, 1, 0, 1};
表示开启SDO, Emergency, SYNC, LifeGuard和LSS功能。
1.3.3 状态调用函数
1.3.3.1 initialisation
初始化函数,在节点的Initialisation状态时会调用这个函数。这个函数在state.c中有一个什么都没做的实现版本:
void _initialisation(CO_Data* d){(void)d;}
用户也可以在应用程序中重构这个函数,在用户应用程序中做一些初始化工作。
1.3.3.2 preOperational
Pre-operational状态下调用的函数,同样在state.c里有一个实现:
void _preOperational(CO_Data* d){
if (!(*(d->iam_a_slave)))
{
masterSendNMTstateChange (d, 0, NMT_Reset_Node);
}
}
这个函数的实现表明,如果当前节点是master,那么就会执行masterSendNMTstateChange (d, 0, NMT_Reset_Node);
,这个函数就是一个master节点发送NMT消息的函数,函数在nmtMaster.c中定义如下:
/*!
**
**
** @param d
** @param nodeId
** @param cs
**
** @return
**/
UNS8 masterSendNMTstateChange(CO_Data* d, UNS8 nodeId, UNS8 cs)
{
Message m;
MSG_WAR(0x3501, "Send_NMT cs : ", cs);
MSG_WAR(0x3502, " to node : ", nodeId);
/* message configuration */
m.cob_id = 0x0000; /*(NMT) << 7*/
m.rtr = NOT_A_REQUEST;
m.len = 2;
m.data[0] = cs;
m.data[1] = nodeId;
return canSend(d->canHandle,&m);
}
其中,#define NMT_Reset_Node 0x81
,nodeId=0x00表示对所有节点的控制,这就表明在主节点进入Pre-operational的时候,会给所有节点发送一帧这样的数据:
000 | 0 | 81 | nodeId |
---|
在另一篇博客里面有讲,这一帧数据0x81表示将节点置于reset-application模式。
1.3.3.3 operational
Operational状态下调用的函数,同样在state.c里有一个实现:
void _operational(CO_Data* d){(void)d;}
也是什么都没做。
1.3.3.4 stopped
Stopped状态下调用的函数,同样在state.c里有一个实现:
void _stopped(CO_Data* d){(void)d;}
1.3.4 回调函数
void (*NMT_Slave_Node_Reset_Callback)(CO_Data*);
void (*NMT_Slave_Communications_Reset_Callback)(CO_Data*);
这两个回调函数分别在节点复位和通信复位时调用。
1.4 NMT-heartbeat
与节点心跳有关的数据结构。
/* NMT-heartbeat */
UNS8 *ConsumerHeartbeatCount;
UNS32 *ConsumerHeartbeatEntries;
TIMER_HANDLE *ConsumerHeartBeatTimers;
UNS16 *ProducerHeartBeatTime;
TIMER_HANDLE ProducerHeartBeatTimer;
heartbeatError_t heartbeatError;
e_nodeState NMTable[NMT_MAX_NODE_ID];
从站可以从其他创建者(主站或其他从站)监测心跳,前三个就是与消费者监测创建者心跳相关的变量,在变量字典0x1016中设置。
UNS16 *ProducerHeartBeatTime;
表示心跳产生的时间间隔,在0x1017中设置。例如想要一秒产生一个心跳,那么在0x1017的子索引中设置成0x3E8,就会产生这样的代码:
/* index 0x1017 : Producer Heartbeat Time. */
UNS16 TestMaster_obj1017 = 0x3E8; /* 1000 */
subindex TestMaster_Index1017[] =
{
{ RW, uint16, sizeof (UNS16), (void*)&TestMaster_obj1017, NULL }
};
TIMER_HANDLE ProducerHeartBeatTimer;
是生产者心跳时钟,结构体初始化的时候是空值#define TIMER_NONE -1
,在初始化函数里面会赋值。
heartbeatError_t heartbeatError;
是错误处理函数指针,初始化成了一个空函数:
void _heartbeatError(CO_Data* d, UNS8 heartbeatID){(void)d;(void)heartbeatID;}
e_nodeState NMTable[NMT_MAX_NODE_ID];
是所有节点状态数组,保存了最大#define NMT_MAX_NODE_ID 128
128个节点的状态,初始化为Unknown_state = 0x0F
。
struct_CO_Data结构体里面的其他部分NMT-nodeguarding, SYNC, General, DCF concise, EMCY,在下一篇讲。