CANOpen protocol details: CANfestival source code analysis

CANFestival-3 source code explanation I: important structure

There are several points to note:
1. Use the canfestival-3 source code downloaded from the official website. The downloaded compressed package file name is Use the object dictionary editor built into the source code to generate the object dictionary file;
2. It mainly analyzes the code related to DS 301 protocol in the source code. The reference document of DS 301 protocol is 301_v04000201.pdf;
3. Recommend a document called CANopen easy introduction, which introduces the basic knowledge of CANopen. It is produced by Guangzhou Zhiyuan electronics. It is very well written. A document CANopen high level protocol for CAN bus is also recommended.
4. This article assumes that the reader knows something about CANopen and does not involve the basic knowledge of CANopen.

1.CANOpen node structure

The core structure in canfestival is the node data structure, which contains all the data information needed by a CANOpen node. This structure is defined in the data.h file. The source code is as follows:

struct struct_CO_Data {
	/* Object dictionary */
	UNS8 *bDeviceNodeId;
	const indextable *objdict;
	s_PDO_status *PDO_status;
	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;
	/* LSS */
	lss_transfer_t lss_transfer;
	lss_StoreConfiguration_t lss_StoreConfiguration;

Let's take a look at the contents of this structure.

1.1 Object dictionary

1.1.1 Node-ID

UNS8 *bDeviceNodeId;
Each node has a unique ID called node ID. this variable is used to record node ID.
According to the source code, 301 official documents and CANopen high level protocol for CAN bus, the node ID occupies 7 bits, and the value range is: [1, 127].
The source code for judging whether the node ID is valid is as follows. You can see that the node ID range is [1, 127].

if(!(nodeId>0 && nodeId<=127)){
	  MSG_WAR(0x2D01, "Invalid NodeID",nodeId);

Some online tutorial documents write [0, 127], and even some write a maximum of 128, which is wrong. It should be noted that 0 cannot be used as a node ID.

1.1.2 Index table

const indextable *objdict;
This variable is used to store the index of each object. According to the index, we can find the real location where the object data is stored. It is a structure array type. The source code of this structure is as follows:

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; Save information about sub indexes. In CANopen, each object has 16 bit indexes and 8-bit sub indexes.
UNS8 bSubCount; Represents the number of sub indexes. An 8-bit sub index can represent up to 255 sub indexes.
UNS16 index; Represents an index.
The length of the array depends on the number of variables defined. For example, in the source code of the object dictionary generated by the object dictionary editor, the source code of the initialization of the array is as follows:

/* 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},

There are 16 objects defined. The meaning of these objects will be discussed later.
The source code of subindex of the above subindex structure is as follows:

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; There are three types of permissions: RW, wo and ro.
UNS8 bDataType; Represents the data type of data stored in this object. The source code of the data type is as follows, which is completely consistent with the definition in 301 document:

/** 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; Indicates the size of the data, that is, the number of bytes.
void* pObject; A pointer indicating that the object holds the data variable, that is, the location where the data is really stored.
ODCallback_t callback; The callback function when writing events. The function prototype of the callback function is as follows:

typedef UNS32 (*ODCallback_t)(CO_Data* d, const indextable *, UNS8 bSubindex);

1.1.3 PDO structure

s_PDO_status *PDO_status;
PDO_status is used to save the array of PDO communication parameters sent, and an s will be generated for each TPDO_ PDO_ For the status structure, even if TPDO is not defined, in order to avoid compilation errors, the length of this array is at least 1, which will be discussed later when the structure is initialized.
s_ PDO_ The source code of status is as follows:

/** The PDO structure */
struct struct_s_PDO_status {
  UNS8 transmit_type_parameter;
  TIMER_HANDLE event_timer;
  TIMER_HANDLE inhibit_timer;
  Message last_message;

It can be seen that this structure actually saves the communication parameters of PDO, only TPDO parameters, not RPDO parameters. In fact, the communication parameters of PDO are saved in the object index in 1.1.2. It is unclear why a structure should be saved separately in this place.

First, review the communication parameters of PDO

Both TPDO and RPDO have communication parameters and mapping parameters. There are six communication parameters as follows:

0x02Transmission TypeUNSIGNED8
0x03Inhibit TimeUNSIGNED16
0x04Compatibility EntryUNSIGNED8
0x05Event TimeUNSIGNED16
0x06SYNC start valueUNSIGNED8

Let's explain what these six parameters mean first.
1. There is no need to explain the COB ID too much. It is simply understood that the CAN node can know whether this frame message belongs to PDO, SDO, NMT, SYNC or others through the COB ID.
2.Transmission Type refers to the communication type of PDO, including synchronous, asynchronous, periodic, aperiodic, etc. see Table 3 of CANopen high level protocol for CAN bus for details. The value range is 0 ~ 255. The source code of value definition is as follows:

/** 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 refers to the minimum time interval of PDO transmission to avoid too fast transmission frequency and too large bus load. The unit is 100us.
4. I don't know what the compatibility entry is. It's not mentioned in the document. Let's ignore it first.
5.Event Time: if the PDO is sent regularly, this parameter indicates the timing time. If this parameter is 0, it indicates that the PDO is triggered by the event, and the unit is ms.
6.SYNC start value synchronization start value. When the PDO is sent synchronously, such as Transmission Type=10, the PDO is sent after receiving 10 synchronization packets. If SYNC start value=2, the PDO is sent after receiving 2 synchronization packets at the beginning, and then it is sent according to 10 synchronization packets.

struct_ s_ PDO_ The status structure saves the 2, 3 and 5 parameters in the TPDO parameters separately. The purpose of this is unknown for the time being. Another parameter is Message last_message; Save the latest TPDO message. You also don't know the purpose of doing so.

1.1.4 RxPDO_EventTimers

void (*RxPDO_EventTimers_Handler)(CO_Data*, UNS32);
When these two are put together, they are literally related to the receiving PDO time timer.

1.1.5 First and Last Index

const quick_index *firstIndex;
const quick_index *lastIndex;
These two variables are mainly used to store the index values of SDO and PDO in the object dictionary described in 1.1.2, mainly to make it easier to use SDO and PDO objects later. Specifically, the structure quick_ The source code of index is as follows:

typedef struct s_quick_index{

SDO_SVR represents the index of SDO server; SDO_CLT represents the index of SDO client; PDO_RCV represents the index of RPDO; PDO_RCV_MAP represents the index of RPDO mapping object; PDO_TRS represents the index of TPDO; PDO_TRS_MAP represents the index of the TPDO mapping object.
The values of these two variables are with testmaster in 1.1.2_ Objdict [] is automatically generated by the object dictionary editor. Take testmaster in 1.1.2_ For objdict [], the corresponding firstIndex and lastIndex are as follows:

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 */

Indicates testmaster_ In objdict [], indexes 4-5 belong to SDO server objects; 6-7 belong to SDO client object; 8-9 belong to RPDO object; 10-11 belong to RPDO mapping object; 12-13 belong to TPDO object; 14-15 belong to TPDO mapping object.

1.1.6 ObjdictSize

const UNS16 *ObjdictSize;
Represents the number of objects in the object dictionary. The initialized value is also automatically generated by the object dictionary editor:

const UNS16 TestMaster_ObjdictSize = sizeof(TestMaster_objdict)/sizeof(TestMaster_objdict[0]);

1.1.7 Slave or Master

const UNS8 *iam_a_slave;
The master or slave flag is also initialized in the file automatically generated by the object dictionary editor.

const UNS8 TestMaster_iam_a_slave = 0;

0 represents the master and 1 represents the slave.

1.1.8 valueRangeTest

valueRangeTest_t valueRangeTest;
This is a function pointer that detects whether the value is out of range. This function is also initialized in the file automatically generated by the dictionary editor:

/* 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;
  return 0;

As for what this function does, we'll talk about it later.

1.2 SDO

This structure holds all the information about SDO. SDO_ MAX_ SIMULTANEOUS_ The macro definition of transfers represents the maximum number of SDOs transmitted simultaneously. It is defined in config.h, and the default value is 1. The source code of this structure is as follows:

/* 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 is subject to ENDIANISATION
                              * (with respect to CANOPEN_BIG_ENDIAN)
  UNS8           data [SDO_MAX_LENGTH_TRANSFER];
  UNS8           *dynamicData;
  UNS32          dynamicDataSize;
  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;

As can be seen from the annotation of this structure, this structure is mainly used to save the SDO received before writing to the object dictionary; Data read from the object dictionary before SDO is sent. I won't go into the details. I'll talk about it later.

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*);

Save state machine related information. See the document CANopen high level protocol for CAN bus for the state machine defined by CANopen Node.

1.3.1 nodeState

nodeState indicates the node state. The source code is defined as follows:

/* 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;

The node status in CANopen high level protocol for CAN bus is described as follows:

1Disconnected *
2Connecting *
3Preparing *

The status with * is only available on the extended boot up node; State 0 cannot appear in the Node Guard reply, because the node will not reply to the Node Guard message in the initializing state.

1.3.2 CurrentCommunicationState

Save the flag of whether to open the corresponding function. The source code of this structure:

typedef struct
	INTEGER8 csBoot_Up;
	INTEGER8 csEmergency;
	INTEGER8 csLifeGuard;
} s_state_communication;

1 means to turn on the corresponding function, and 0 means to turn off the corresponding function. For example, in the initialization state, s_state_communication newCommunicationState = {1, 0, 0, 0, 0, 0, 0}; Indicates that only boot is enabled_ Up function, other functions are turned off. In the pre operational state, s_state_communication newCommunicationState = {0, 1, 1, 1, 1, 0, 1}; Indicates that SDO, Emergency, SYNC, LifeGuard and LSS functions are enabled.

1.3.3 status calling function initialisation

Initialization function, which will be called when the node is in the initialization state. This function has an implementation version that does nothing in state.c:

void _initialisation(CO_Data* d){(void)d;}

Users can also reconstruct this function in the application and do some initialization in the user application. preOperational

The function called by pre operational state also has an implementation in state.c:

void _preOperational(CO_Data* d){
    if (!(*(d->iam_a_slave)))
        masterSendNMTstateChange (d, 0, NMT_Reset_Node);

The implementation of this function indicates that if the current node is master, then masterSendNMTstateChange (d, 0, NMT_Reset_Node) will be executed;, This function is a function for the master node to send NMT messages. The function is defined in nmtMaster.c as follows:

** @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;[0] = cs;[1] = nodeId;

  return canSend(d->canHandle,&m);

Where, #define NMT_Reset_Node 0x81, nodeId=0x00 indicates control over all nodes, which indicates that when the master node enters pre operational, it will send a frame of such data to all nodes:


In another blog, it is said that this frame data 0x81 indicates that the node is placed in reset application mode. operational

The function used to call the Operational state also has an implementation in state.c:

void _operational(CO_Data* d){(void)d;}

I didn't do anything. stopped

The function called for Stopped status also has an implementation in state.c:

void _stopped(CO_Data* d){(void)d;}

1.3.4 callback function

void (*NMT_Slave_Node_Reset_Callback)(CO_Data*);
void (*NMT_Slave_Communications_Reset_Callback)(CO_Data*);

These two callback functions are called respectively when the node is reset and the communication is reset.

1.4 NMT-heartbeat

Data structure related to node 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]; 

The slave station can monitor the heartbeat from other creators (master station or other slave stations). The first three are variables related to the consumer monitoring the creator's heartbeat, which are set in the variable dictionary 0x1016.
UNS16 *ProducerHeartBeatTime; Indicates the time interval of heartbeat generation, which is set in 0x1017. For example, if you want to generate a heartbeat every second, set 0x3E8 in the sub index of 0x1017 to generate such a code:

/* index 0x1017 :   Producer Heartbeat Time. */
                    UNS16 TestMaster_obj1017 = 0x3E8;	/* 1000 */
                    subindex TestMaster_Index1017[] = 
                       { RW, uint16, sizeof (UNS16), (void*)&TestMaster_obj1017, NULL }


TIMER_HANDLE ProducerHeartBeatTimer; It is the producer's heartbeat clock. When the structure is initialized, it is a null value #define TIMER_NONE -1, which will be assigned in the initialization function.
heartbeatError_t heartbeatError; Is the error handling function pointer, which is initialized to an empty function:

void _heartbeatError(CO_Data* d, UNS8 heartbeatID){(void)d;(void)heartbeatID;}

e_nodeState NMTable[NMT_MAX_NODE_ID]; Is an array of all node states, holding the maximum #define NMT_MAX_NODE_ID 128128 node status, initialized as Unknown_state = 0x0F.

struct_ CO_ The other parts of the data structure, NMT nodeguarding, sync, general, DCF, reason, emcy, will be discussed in the next article.

Tags: can

Posted on Wed, 27 Oct 2021 21:11:50 -0400 by Wynder