CANOpen协议详解(一):CANfestival源码分析

本文详细解析CANFestival-3中CANOpen节点的数据结构,涵盖Node-ID、对象字典、PDO结构、状态机及NMT心跳等关键内容。介绍了Node-ID的有效范围、索引表、PDO通信参数、SDO信息及状态机状态转换函数,为理解CANOpen协议提供深入见解。
摘要由CSDN通过智能技术生成

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都有通信参数和映射参数两种参数,通信参数有如下六种:

subindexnametype
0x01COB IDUNSIGNED32
0x02Transmission TypeUNSIGNED8
0x03Inhibit TimeUNSIGNED16
0x04Compatibility EntryUNSIGNED8
0x05Event TimeUNSIGNED16
0x06SYNC start valueUNSIGNED8

这里先解释一下这六个参数是什么意思吧。
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;
}

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

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》中关于节点状态的描述如下:

ValueState
0Initialising
1Disconnected *
2Connecting *
3Preparing *
4Stopped
5Operational
127Pre-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的时候,会给所有节点发送一帧这样的数据:

000081nodeId

在另一篇博客里面有讲,这一帧数据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 128128个节点的状态,初始化为Unknown_state = 0x0F

struct_CO_Data结构体里面的其他部分NMT-nodeguarding, SYNC, General, DCF concise, EMCY,在下一篇讲。

CANopen是一个用于控制设备的通信协议,它基于CAN总线,是一种开放的、标准化的、高度灵活的通信协议。 CANopen协议包含了一系列的标准化对象和通信服务,用于实现设备之间的通信和控制。这些对象和服务可以被用于控制各种不同类型的设备,例如电机、传感器、执行器等等。 CANopen协议的主要特点包括: 1. 灵活性:CANopen协议可以适用于各种不同类型的设备,并且可以根据应用的需求进行定制。 2. 可扩展性:CANopen协议可以通过添加新的对象和服务来扩展功能,同时也可以支持多个CAN总线之间的通信。 3. 实时性:CANopen协议可以实现实时控制和通信,可以在高速和高负载的环境下保持稳定性。 4. 可靠性:CANopen协议支持错误检测和纠正,可以保证通信的可靠性和稳定性。 CANopen协议主要包含以下几个方面: 1. CANopen对象字典:CANopen对象字典包含了所有的对象和服务。每个对象都有一个独立的索引号,可以通过索引号进行访问。 2. CANopen通信服务:CANopen通信服务包括了一系列的命令和数据传输方式,用于实现设备之间的通信。 3. CANopen网络管理:CANopen网络管理用于管理CAN总线上的设备,包括设备的配置、识别、诊断等。 4. CANopen应用层:CANopen应用层定义了一系列的应用程序接口(API),用于实现设备之间的交互和控制。 总之,CANopen协议是一种开放的、标准化的、高度灵活的通信协议,可以应用于各种不同类型的设备之间的通信和控制。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巴普蒂斯塔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值