...
Example of the usage of a timer:
Code Block | ||
---|---|---|
| ||
TIMER_EVENT_T myTimer; /* define timer struct */ |
...
/* add a cyclic timer for 1 sec */ |
...
addTimerEvent(&myTimer, 10000, \ |
...
(CO_TIMER_TYPE_USERSPEC | CO_TIMER_TYPE_CYCLIC)); |
...
... |
...
void userTimerEvent(TIMER_EVENT_T *pTimer) |
...
{ |
...
if (pTimer == &myTimer) |
...
{ |
...
/* start my reaction */ |
...
} |
...
} |
Listing 1: example for timer usage
...
Except the first server SDO, all SDOs are marked as invalid after the initialization. Only the COB-IDs for the first server SDO are initialized with the default COB-ID on the basis of the node-ID (see pre-defined connection set). The COB-IDs for the other SDOs shall be set and validated using the function setCobId(), see Listing 2.
Code Block | ||
---|---|---|
| ||
/* create SDO server service 1, |
...
this service has to use a standardized COB-ID */ |
...
defineSdo(1, SERVER); |
...
/* create SDO client service 1 to device with the node-ID 2 */ |
...
defineSdo(1, CLIENT); |
...
/* configure COB-IDs for the SDO client service 1 */ |
...
/* for SDO request from SDO client to SDO server */ |
...
cobId = 0x600 + 2; |
...
setCobId(0x1280, 1, cobId); |
...
/* for SDO response from SDO server to SDO client */ |
...
cobId = 0x580 + 2; |
...
setCobId(0x1280, 2, cobId); |
Listing 2: example for defining SDOs and setup COB-IDs
...
Error codes are generated automatically by the CANopen Library, see appendix.
Code Block | ||
---|---|---|
| ||
/*************************************************************** |
...
sdoRdInd - indicates the occurrence of an SDO read access |
...
* |
...
\retval CO_OK success |
...
\retval CO_E_xxx error |
...
*/ |
...
RET_T sdoRdInd( |
...
UNSIGNED16 index, /**< [in] index of object */ |
...
UNSIGNED8 subIndex /**< [in] sub-index of object */ |
...
CO_COMMA_LINE_PARA_DECL /**< [in] additional parameter */ |
...
) |
...
{
{ RET_T coRetVal; /* CANopen Library return value */ |
...
coRetVal = CO_OK; |
...
if (0x2000 == index) |
...
{ |
...
/* increment application-specific counter */ |
...
actual_u32++; |
...
} |
...
return (coRetVal); |
...
} |
Listing 3: example for SDO read indication
...
First the write permission flag and the value limits are tested by the CANopen Library. If the value is within the limit range the user can run additional tests on the value by the function testSdoValue(), before it is written into the object dictionary. These tests can be necessary if the application uses the corresponding variable for a second task or an interrupt service routine or for data with size greater than 4 bytes. For variables with size of max. 4 bytes (e.g. UNSIGNED8-UNSIGNED32, INTEGER8-INTEGER32, REAL32 values) the old value is stored before the new value is written into the object dictionary and the function sdoWriteInd() is called. In the case of an error return value from sdoWriteInd() the old value is restored.
Code Block | ||
---|---|---|
| ||
/***************************************************************** |
...
sdoWrInd - indicates the occurrence of an SDO write access |
...
* |
...
\retval CO_OK success |
...
\retval CO_E_xxx error |
...
*/ |
...
RET_T sdoWrInd( |
...
UNSIGNED16 index, /**< [in] index of object */ |
...
UNSIGNED8 subIndex /**< [in] sub-index of object |
...
CO_COMMA_LINE_PARA_DECL /**< [in] additional parameter */ |
...
) |
...
{ |
...
if ((0x2000 == index) && (0 == subIndex)) |
...
{ |
...
actual_u32 = setpoint_u32; |
...
} |
...
else |
...
{ |
...
actual_u32++; |
...
} |
...
return(CO_OK); |
...
} |
Listing 4: example for SDO write indication
...
An error free return value from the request functions does not necessarily mean a successful transmission. It means only that the transfer was successfully initiated by saving the first data into the transmit message buffer. The application is informed about the termination of the transfer through the functions sdoWrCon() and sdoRdCon(). If an error occurs these functions can evaluate the error reason.
Code Block | ||
---|---|---|
| ||
void sdoWrCon( |
...
UNSIGNED8 sdoNum, /**< [in] number of SDO service */ |
...
UNSIGNED32 errorFlag /**< [in] reason for the function call */ |
...
CO_COMMA_LINE_PARA_DECL /**< [in] additional parameter */ |
...
) |
...
{ |
...
if (E_SDO_TIMEOUT == errorFlag) |
...
{ |
...
printf("SDO timeout occurred"); |
...
return; |
...
} |
...
switch (errorFlag & 0xFF000000UL) |
...
{ |
...
/* successful confirmation */ |
...
case E_SDO_NO_ERROR: |
...
break; |
...
/* SDO service error */ |
...
case E_SDO_SERVICE: |
...
switch(errorFlag & 0x00FF0000UL |
...
{
...
) { case E_SDO_INCONS_PARA: |
...
printf(" - Inconsistent parameter"); |
...
break; |
...
case E_SDO_ILLEG_PARA: |
...
printf(" - Illegal parameter"); |
...
break; |
...
} |
...
break;
break; ... |
...
default:
...
default: printf("Error: abort transfer reason %lX", errorFlag); |
...
break; |
...
} |
...
} |
Listing 5: example for SDO write confirmation
...
the Industrial Communication Creator by the Default Value and Size of the object or
the application by calling the functions setDomainAddr() and setDomainSize() during runtime, see Listing 6
Code Block | ||
---|---|---|
| ||
UNSIGNED8 programDownloadArea[MAX_DOMAIN_BUF_SIZE]; |
...
int main() |
...
{
{ BOOL_T bRetVal; /* return value of data type BOOL_T */ |
...
. . . |
...
bRetVal = setDomainAddr(DOMAIN_INDEX, DOMAIN_SUB, \ |
...
&programDownloadArea[0] CO_COMMA_LINE_PARA); |
...
if (CO_TRUE == bRetVal) |
...
{ |
...
bRetVal = setDomainSize(DOMAIN_INDEX, DOMAIN_SUB, \ |
...
MAX_DOMAIN_BUF_SIZE CO_COMMA_LINE_PARA); |
...
} |
...
. . . |
...
) |
Listing 6: domain initialization
...
All SDO transfers are initialized by the SDO client, so the SDO server is always the passive part. Therefore the indication size for the SDO server must be setup at compile time. It can be done by the Industrial Communication Creator. If the configured data size is elapsed, the indication function sdoDomainInd() is called. Here the application can save or flash the received data. After that, the receive buffer will be cleared and the next data will be received until the next border of the configured data size is reached. Then the indication function is called again.
Code Block | ||
---|---|---|
| ||
/********************************************************************/ |
...
/** |
...
\brief sdoDomainInd - domain size border reached |
...
* |
...
This function is called for SDO Domain transfers |
...
after the receipt of CONFIG_DOMAIN_INDICATION_SIZE bytes. |
...
The application gets the possibilty to save the received data |
...
for instance in the flash memory. |
...
After leaving this function is buffer will be overwritten |
...
with new received data. |
...
* |
...
The buffer size CONFIG_DOMAIN_INDICATION_SIZE will not be |
...
divisible by the data length of the SDO messages, |
...
i.e. divisible by 7. |
...
This function is called when CONFIG_DOMAIN_INDICATION_SIZE |
...
and more data are received. |
...
The application is responsible to save the oversized bytes |
...
temporarily. |
...
Therefore the function gets as parameter: |
...
- actSize: number of bytes at the buffer to process by the |
...
application, including the number of overSize bytes |
...
from the last cycle |
...
- overSize: number of oversized bytes received with last SDO |
...
The CANopen Library controls the byte counting. |
...
* |
...
At the end of SDO transfer the indication function sdoWrInd() |
...
is called. |
...
* |
...
\return |
...
CANopen Library return value |
...
*/ |
...
RET_T sdoDomainInd( |
...
UNSIGNED16 index, /**< [in] index if current SDO access */ |
...
UNSIGNED8 subIndex, /**< [in] sub-index of current SDO access */ |
...
UNSIGNED8 *pData, /**< [in] pointer to domain buffer */ |
...
UNSIGNED32 actSize, /**< [in] number of bytes to flash */ |
...
UNSIGNED8 overSize /**< [in] byte number to store temporarily */ |
...
CO_COMMA_LINE_PARA_DECL /**< [in] additional parameter */ |
...
) |
...
{
{ /* temporary flash buffer */ |
...
UNSIGNED8 flashBuffer[CONFIG_DOMAIN_INDICATION_SIZE]; |
...
static UNSIGNED8 savedBuffer[7]; /* static save buffer */ |
...
static UNSIGNED8 savedBufferSize = 0; /* count of saved data */ |
...
...
/* first copy the rest bytes from the previous flash cycle |
...
to the flash buffer */ |
...
memcpy(&flashBuffer[0], &savedBuffer[0], savedBufferSize); |
...
/* now copy new received bytes to flash buffer */ |
...
memcpy(&flashBuffer[savedBufferSize], pData, actSize); |
...
/* save oversize data for next flash cycle */ |
...
memcpy(&savedBuffer[0], pData + actSize, overSize); |
...
savedBufferSize = overSize; |
...
/* flash data */ |
...
return(CO_OK); |
...
} |
...
#endif /* CONFIG_SDO_SERVER && CONFIG_DOMAIN_INDICATION_SIZE */ |
Listing 7: example for sdoDomainInd()
...
Before usage all PDOs must be defined. A maximum of 512 Receive PDOs and 512 Transmit PDOs with a maximum PDO mapping of 64 entries for each direction are possible. Initialization is done with the function definePdo().
Code Block | ||
---|---|---|
| ||
/* define RPDO5 */ |
...
definePdo(5, CONSUMER, CO_FALSE CO_COMMA_LINE_PARA); |
...
/* setup COB-ID is necessary for PDOs 5..512 */ |
...
setCobId(0x1404, 1, 0x67F CO_COMMA_LINE_PARA); |
Listing 8: example for PDO definition
...
Only for the COB-ID (sub-index 1 of the PDO communication parameter object) and the inhibit time (sub-index 3 of the PDO communication parameter object) it is required that the PDO must be disabled for configuration.
Code Block | ||
---|---|---|
| ||
/* 1. disable TPDO1: */ |
...
coRetVal = setCobId(0x1800, 1, (PDO_NO_VALID_BIT | cobId) \ |
...
CO_COMMA_LINE_PARA); |
...
/* 2. configure COB-ID of TPDO1 and enable TPDO1: */ |
...
coRetVal = setCobId(0x1800, 1, cobId CO_COMMA_LINE_PARA); Listing 9: example for COB-ID configuration of TPDO1 |
Listing 9: example for COB-ID configuration of TPDO1
Code Block | ||
---|---|---|
| ||
/* 1. disable PDO: */ |
...
cobId = PDO_NO_VALID_BIT | cobId; |
...
coRetVal = putObj(0x1800, 1, (UNSIGNED8*)&cobId, 4, \ |
...
CO_TRUE CO_COMMA_LINE_PARA); |
...
coRetVal = setCommPar(0x1800, 1 CO_COMMA_LINE_PARA); |
...
/* 2. configure PDO inhibit time: */ |
...
inhibitTime = 1000; |
...
coRetVal = putObj(0x1800, 3, (UNSIGNED8*)&inhibitTime, 2, \ |
...
CO_TRUE CO_COMMA_LINE_PARA); |
...
coRetVal = setCommPar(0x1800, 3 CO_COMMA_LINE_PARA); |
...
/* 3. enable PDO: */ |
...
cobId = (~PDO_NO_VALID_BIT) & cobId; |
...
coRetVal = putObj(0x1800, 1, (UNSIGNED8*)&cobId, 4, \ |
...
CO_TRUE CO_COMMA_LINE_PARA); |
...
coRetVal = setCommPar(0x1800, 1 CO_COMMA_LINE_PARA); |
Listing 10: example for the configuration of PDO inhibit time local for TPDO1
For all local modifications of the communication parameters in the object dictionary the function setCommPar() has to be called in order to update the internal structures. Changes via the CANopen network using SDO and the function setCobId() automatically update the internal structures.
Code Block | ||
---|---|---|
| ||
/* 1. disable PDO: */ |
...
setCobId(0x1400, 1, PDO_NO_VALID_BIT CO_COMMA_LINE_PARA); |
...
/* 2. disable PDO mapping: */ |
...
mapCnt = 0; |
...
putObj(0x1600, 0, &mapCnt, 1, CO_TRUE CO_COMMA_LINE_PARA); |
...
setCommPar(0x1600, 0 CO_COMMA_LINE_PARA); |
...
/* 3. configure PDO mapping entry 1: map object 2000h/1 of data type UNSIGNED32 */ |
...
mapEntry = 0x20000120; |
...
putObj(0x1600, 1, &mapEntry, 4, CO_TRUE CO_COMMA_LINE_PARA); |
...
/* 4. configure PDO mapping entry 2: map object 2100h/0 of data type INTEGER8 */ |
...
mapEntry = 0x21000108; |
...
putObj(0x1600, 2, &mapEntry, 4, CO_TRUE CO_COMMA_LINE_PARA); |
...
/* 5. enable PDO mapping: 2 PDO mapping entries are valid */ |
...
mapCnt = 2; |
...
putObj(0x1600, 0, &mapCnt, 1, CO_TRUE CO_COMMA_LINE_PARA); |
...
setCommPar(0x1600, 0 CO_COMMA_LINE_PARA); |
...
/* 6. enable PDO: and set COB-ID to 220h */ |
...
setCobId(0x1400, 1, 0x220 CO_COMMA_LINE_PARA); |
Listing 11: example for configuration of the PDO mapping for RPDO1
...
Transmitting asynchronous PDOs is done using the function writePdoReq(). Only the PDO number is given as function argument. The CANopen Library automatically composes the transmit buffer by saving the mapped data at the transmit buffer. Asynchronous PDOs are transmitted immediately, synchronous PDOs are stored and transmitted after the next applicable SYNC message, RTR-only PDOs are also stored and transmitted after the next RTR request.
Code Block | ||
---|---|---|
| ||
/* transmit TPDO1 */ |
...
writePdoReq(1 CO_COMMA_LINE_PARA); |
...
/* transmit TPDO3 */ |
...
writePdoReq(3 CO_COMMA_LINE_PARA); |
Listing 12: example for PDO transmission request
...
PDOs are transmitted with high priority. To avoid blocking of the CAN communication by high priority PDOs a PDO inhibit time parameter can be defined. The PDO inhibit time is a minimum time between two consecutive transmissions of this PDO. If the PDO inhibit time has not elapsed yet, the function writePdoReq() returns an error code. The application can try to send it later if the PDO inhibit time is elapsed.
Code Block | ||
---|---|---|
| ||
/* main loop */ |
...
while(1) |
...
{ |
...
if (1 == transmitPdoFlag) |
...
{ |
...
coRetVal = writePdoReq(1); |
...
/* repeat the transmission of the TPDO1 if TPDO1 was not |
...
transmitted because the PDO inhibit time is still running */ |
...
if (CO_E_INHIBITED != coRetVal) |
...
{ |
...
transmitPdoFlag = 0; |
...
} |
...
FlushMBox(); |
...
. . . |
...
} |
Listing 13: example for waiting on ending of PDO inhibit time
...
The call of an application function can depend on the mapped objects. The current address of the mapped object can be queried by the function getMapObjAddr(). This is useful for dynamic PDO mapping. An example is given in Listing 14.
Code Block | ||
---|---|---|
| ||
/****************************************************************/ |
...
/* pdoInd - PDO indication function |
...
sends PDO 2 on line 1 if PDO 1 has been received on CAN line 0 |
...
* |
...
\returns |
...
nothing |
...
*/ |
...
void pdoInd( |
...
UNSIGNED16 pdoNum /**< [in] number of PDO service */ |
...
CO_COMMA_LINE_PARA_DECL /**< [in] additional parameter */ |
...
) |
...
{ |
...
/* application function is fixed assigned to an object |
...
and the object is static mapped into RPDO1 */ |
...
if (1 == pdoNum) |
...
{ |
...
/* received data already stored at object(s) */ |
...
user_action(); |
...
} |
...
/* application function is fixed assigned to an object |
...
and this object is dynamic mapped into RPDO1 */ |
...
else if (2 == pdoNum) |
...
{ |
...
/* check, if object |
...
into RPDO1 on mapping entry 1 */
...
with the name setpoint is mapped into RPDO1 on mapping entry 1 */ if (getMapObjAddr(pdoNum, 1) == &setpoint) |
...
{ |
...
/* yes, object is mapped, |
...
execute object-specific application function */ |
...
user_action2(); |
...
} |
...
} |
...
} |
Listing 14: example for PDO indication
...
Emergency (EMCY) messages serve for transmitting and receiving error messages. One EMCY producer and up to 127 EMCY consumers can be created in a device. The function defineEmcy() with the appropriate parameter initializes the emergency service for producer or consumer. If the EMCY consumer list in the object dictionary at index 1028hexists then all entries with a valid COB-ID are initialized automatically. If it does not exist the EMCY consumers can be added by the function setEmcyConsumerCobId().
Code Block | ||
---|---|---|
| ||
/* producer */ |
...
coRetVal = defineEmcy(PRODUCER CO_COMMA_LINE_PARA); |
...
/* consumer */ |
...
coRetVal = defineEmcy(CONSUMER CO_COMMA_LINE_PARA); |
...
/* if EMCY consumer list does not exist, add node 5 */ |
...
coRetVal = setEmcyConsumerCobId(5, 0x185 CO_COMMA_LINE_PARA); |
...
/* add node 35 */ |
...
coRetVal = setEmcyConsumerCobId(35, 0x1A3 CO_COMMA_LINE_PARA); |
Listing 15: example for EMCY initialization
EMCY messages on the CAN-bus are generated by the function writeEmcyReq(). The function writeEmcyReq() sets the general error bit in object 1001h automatically. If more than the general error bit in object 1001h shall be supported the application has to set the bits before writeEmcyReq() is called.
Code Block | ||
---|---|---|
| ||
UNSIGNED8 addInfo[5]; |
...
RET_T coRetVal; |
...
addInfo[0] = 0x11; |
...
addInfo[1] = 0x22; |
...
addInfo[2] = 0x33; |
...
addInfo[3] = 0x44; |
...
addInfo[4] = 0x55; |
...
coRetVal = writeEmcyReq(0xFF00, &manuErr[0] CO_COMMA_LINE_PARA); |
...
if (CO_OK != coRetVal) |
...
{ |
...
printf(“error EMCY 0xFF00 %d", (int)coRetVal); |
...
} |
Listing 16: example for EMCY request
...
The reception of EMCY messages from other nodes in the network is indicated by the function emcyInd(). This function makes all data contained in the EMCY message available to the application. Storage of the data does not take place.
Code Block | ||
---|---|---|
| ||
/*******************************************************************/ |
...
/* emcyInd - indicates the occurrence of an emergency object |
...
* |
...
In this function the user has to define his application-specific |
...
error handling. The function must send a message to the server |
...
in order to repair the error. |
...
* |
...
\returns |
...
nothing |
...
*/ |
...
void emcyInd( |
...
UNSIGNED8 emcyNode, /**< [in] emergency number */ |
...
EMERGENCY_T *pEmcy /**< [in] data of the emergency message */ |
...
CO_COMMA_LINE_PARA_DECL /**< [in] additionl parameter */ |
...
) |
...
{ |
...
switch (pEmcy->errCode & 0xFF00) |
...
{ |
...
case 0x4000: |
...
printf("Temperature\n"); |
...
break; |
...
case 0x5000: |
...
printf("Device Hardware\n"); |
...
break; |
...
default: |
...
printf("emergency ind %x\n", pEmcy->errCode); |
...
break; |
...
} |
...
printf("errReg: %x, Manu: %x %x %x %x %x\n", pEmcy->errReg, \ |
...
pEmcy‑>manu[0], pEmcy->manu[1], pEmcy->manu[2], \ |
...
pEmcy->manu[3], pEmcy->manu[4]); |
...
} |
Listing 17: EMCY indication
...
The SYNC message serves for synchronous transfer of PDOs and synchronous execution of internal procedures in different nodes of the network. A node can either be the SYNC producer or the SYNC consumer. The type of service must be determined by the initialization of the function defineSync() or by setting the appropriate bit at index 1005h. Additionally, the SYNC communication cycle period has to be set for the SYNC producer in the object dictionary and the internal structures have to be updated with the function setCommPar().
Code Block | ||
---|---|---|
| ||
/* define SYNC producer */ |
...
coRetVal = defineSync(PRODUCER CO_COMMA_LINE_PARA); |
...
/* set new communication cycle period in µs */ |
...
cycleTime = 1000; |
...
/* write value to object dictionary */ |
...
coRetVal = putObj(0x1006, 0, &cycleTime, 4, CO_TRUE CO_COMMA_LINE_PARA); |
...
/* update internal values */ |
...
coRetVal = setCommPar(0x1006, 0 CO_COMMA_LINE_PARA); |
...
/* start cyclic transmission of SYNC */ |
...
startSyncReq(CO_LINE_PARA); |
Listing 18: initialization, configuration and start of SYNC service
...
Each node has to provide at least one service. The Heartbeat service is preferable. Even if both services are implemented guarding has to be done with only one service. Only services that have been initialized can be used, see Listing 19. The initialization is included in the function init_Library() generated by the Industrial Communication Creator.
Code Block | ||
---|---|---|
| ||
/* initialize node only for Heartbeat usage*/ |
...
createNodeReq(CO_FALSE, CO_TRUE CO_COMMA_LINE_PARA); |
...
/* initialize node only for Node Guarding usage */ |
...
createNodeReq(CO_TRUE, CO_FALSE CO_COMMA_LINE_PARA); |
...
/* initialize node for Heartbeat and Node Guarding usage */ |
...
createNodeReq(CO_TRUE, CO_TRUE CO_COMMA_LINE_PARA); |
Listing 19: initialization of error control
...
The initialization of the Heartbeat consumer is carried out with the function defineHeartbeatConsumer(). All nodes of the Heartbeat consumer list in object 1016h (consumer heartbeat time) are initialized for Heartbeat guarding. Changes to Heartbeat parameters can be performed directly in the object dictionary. After each change the internal variables have to be updated with the function setCommPar().
Code Block | ||
---|---|---|
| ||
UNSIGNED32 hbConsTime; |
...
RET_T coRetVal; |
...
/* build new value for the third Heartbeat consumer |
...
with HEARTBEAT consumer time of 0x123 ms |
...
for guarding of node 0x44 */ |
...
hbConsTime = (UNSIGNED32) (0x44 << 16) | 0x0123; |
...
/* write new value to the object dictionary */ |
...
coRetVal = putObj(0x1016, 3, &hbConsTime, 4, CO_TRUE CO_COMMA_LINE_PARA); |
...
/* update internal values */ |
...
coRetVal = setCommPar(0x1016, 3 CO_COMMA_LINE_PARA); |
...
. . . |
...
/* change Heartbeat consumer time to 0x222 ms */ |
...
hbConsTime = (UNSIGNED32) (0x44 << 16) | 0x0222; |
...
/* write new value to object dictionary */ |
...
coRetVal = putObj(0x1016, 3, &cycleTime, 4, CO_TRUE CO_COMMA_LINE_PARA); |
...
/* update internal values */ |
...
coRetVal = setCommPar(0x1016, 3 CO_COMMA_LINE_PARA); |
Listing 20: configuration of Heartbeat consumer
...