How to make an application
This chapter describes the design flow for a CANopen device by using the CANopen Library provided by port. The way to build a NMT master and a NMT slave application will be shown by the delivered examples s1 for the NMT slave and parts of m1 for the NMT master. Both examples are located in the example directory.
The design is separated into the following steps:
decision about the kind of device (master/slave)
pre-definition of the CANopen services (number, properties)
decision about the target system (hardware/operating system)
After that the user knows the device properties and can start with the coding of the communication part of his application. The necessary steps are listed below:
preparation
configuration of the hardware
building up the object dictionary
coding of the main routine
coding of the reset behavior
coding of the indication behavior
optimization
Make preparations
The easiest way to start programming of a new project is to use an existing example delivered by the CANopen Library. All examples are located in the directory examples. The Readme file there shows the main features for all available examples. Furthermore the directory examples/template contains skeletons for master and slave applications and for all indication functions. The user only has to fill these function bodies with his application code. The names of the needed files for your application are shown Table 44.
For the first step we suggest using a slave example s1. This example can be compiled within its directory with the delivered make and project files.
module | cat. | description |
main.c | m | main module |
usr_301.c | m | user’s service indication function for CiA-301 services |
usr_302.c | c | user’s service indication function for CiA-302 services |
usr_303.c | c | user’s service indication function for CiA-303 services |
usr_304.c | c | user’s service indication function for CiA-304 services |
usr_305.c | c | user’s service indication function for CiA-305 services |
nmtslave.c | m | network management behavior functions |
cal_conf.h | m | contains the configuration of the CANopen Library in general, this file is generated by the Industrial Communication Creator |
objects.[c,h] | m | contains the object dictionary, these files are generated by the Industrial Communication Creator |
co_init.c | m | contains the initialization of the CANopen Library, this file is generated by the Industrial Communication Creator |
Table 44: overview about user templates (cat. … category, m … mandatory, c … conditional – only required if associated CANopen services are used)
The next step is to build a Makefile or project file. Within this file all dependencies of your project files shall be set . The search paths for #include files of your Makefile or development environment have to be set to:
your working directory to ensure that the cal_conf.h is included first before all other include files
canopen/include
drivers/shar_inc
drivers/<target>
Configure hardware
The configuration of the hardware can be done by using the Industrial Communication Creator. All settings are saved in the header file cal_conf.h as compiler defines.
Build object dictionary
The object dictionary is also created by using the Industrial Communication Creator. The object dictionaries are saved in the files objects.h and objects.c.
1.1 Create the main routine
There is a template (template/main.c) for coding the main routine. It is recommended to use this template for rapid programming. The following steps are necessary for the design of an application:
· design of the boot-up behavior (initialization)
· design of the application
· design of the shutdown behavior
The boot-up behavior of an application requires the following:
· hardware initialization: iniDevice()
· initialization of CAN controller, timer and ISR’s: initCan()
· initialization of the CANopen Library: init_Library()
This function is generated by the Industrial Communication Creator. The initialization is saved in file co_init.c.
· modify communication parameter at the object dictionary if necessary
· initialize application
· activation of interrupts for CAN and timer
· starting of all nodes startRemoteNodeReq() (only master)
The difference between a NMT master and a NMT slave is that the NMT master controls all network participants. Therefore each NMT master application has to manage the network. In the CANopen Library the NMT master collects all communication relevant information in a node management list (network). With this list the NMT master is able to guard the nodes and to influence their communication states. Before all network participants changes into the state NMT/OPERATIONAL the NMT master or another configuration application can parameterize their communication variables i.e. COB-ID, PDO mapping.
int main(void)
{
RET_T coRetVal; /* CANopen Library return value */
UNSIGNED8 ret; /* return value for common purpose */
BOOL_T err = CO_FALSE; /* error flag */
/* initialization of hardware */
ret = iniDevice();
/* initialize CAN controller with CAN bit rate of 125 kbit/s */
ret = initCan(125);
/* initialize CANopen Library and execute CANopen minimum boot-up */
coRetVal = init_Library();
/* initialize CANopen timer */
initTimer();
/* start CAN controller */
Start_CAN();
/* enable interrupts */
ENABLE_CPU_INTERRUPTS();
}
Listing 35: example for initialization
The design of the application is the user’s task. He has to ensure that the CAN message buffer is read continously or event driven. The easiest way to perform this is an endless loop from which the buffer read function and the application functions are called cyclically, see Listing 36.
int main(void)
{
. . .
/* main loop */
while(1)
{
/* manage received CAN messages and
time-dependent CANopen services */
FlushMbox();
/* application function */
application();
}
}
Listing 36: endless loop reception process
But it is also possible to call FlushMbox() by a Real Time Operating System signal or other mechanisms. Then the user’s application runs within other tasks, see Listing 37.
while(1)
{
/* sleep while no CAN event
CANopen task should be woken up:
- if a new message is received
- if the CANopen timer is expired
in order to check all CANopen timers */
os_wait(event);
/* interpret received message or handle CANopen timers */
FlushMbox();
}
Listing 37: signal-driven process
The implementation of the shutdown behavior is only necessary if the CANopen Library can be left during run-time of the application i.e. if running on an operating system. The called functions will free the allocated system resources. The following steps are to be made for this:
stop all nodes stopRemoteNodeReq() (only master)
deactivate the ISRs for CAN and timer
de-initialize CAN controller, timer and their ISR’s
delete the node entries removeRemoteNodeReq() (only master)
delete the local node deleteNodeReq()
delete the node management list (network) deleteNetworkReq() (only master)
de-initialize the CANopen Library leaveCANopen()
main()
{
....
/* release all resources */
releaseTimer();
deleteNodeReq();
/* leave CANopen */
leaveCANopen();
return(0);
}
Listing 38: example for shutdown
The file co_init.c, generated by the Industrial Communication Creator contains the function deinit_Library() that does all necessary steps to shut down the CANopen Library.
Program the reset behavior
The reset behavior is a property only for slaves. Each NMT slave can receive a Reset Application or a Reset Communication command from the NMT master. In the module nmtslave.c the user will find the reset functions. Within these functions an individual reset of the application data and states can be implemented.
Furthermore there is the function newStateInd(). This function informs the user’s application about each transition of the communication state machine. This information can be important, because a few communication services are not available in certain states. For example, the application transmits error codes via PDOs and the master forces the node to NMT/PRE-OPERATIONAL. Then PDOs are no longer allowed.
Program indication behavior
For each received CANopen message an indication function is called. Templates for these functions can be found in the module template/usr_301.c. The task of these functions is to start an error handling or additional pre- or post-processing for object dictionary entries or to initiate a reaction.
There are three kinds of errors which can be handled with the indication functions. The first kind are errors in the CAN controller and CAN driver message queues. The function canErrorInd() informs the application about error of the CAN controller e.g. "bus-off", "error passive" and about the overflow of the receive and transmit buffers since the last call to canErrorInd(). The current state of the CAN controller can be queried by getCanDriverState().
If an emergency message is received, the function emcyInd() is called at the EMCY consumer. There the user can handle fatal errors of remote devices.
The second kind of errors are the Node Guarding errors. In the case of a lost connection between master and slave the function mGuardErrorInd() is called at the master and the function sGuardErrorInd() at the slave.
The other functions of the module are for the common data service handling. For each received PDO the function pdoInd() is called. Within this function the user can define reactions which should be initiated by certain PDOs.
The server SDO services are divided into read and write access functions. For every kind there is a function sdoWrInd() for write access and sdoRdInd() for read access. It is possible to initiate reactions or to manipulate values i.e. unit transformations with these functions.
Other service indications and confirmations are defined within usr_301.c.
The function getNodeId() is very important. This function returns the node-ID which was read i.e. by a DIP switch or from a nonvolatile memory. It is called implicitly during the CANopen Library initialization initCANopen() and before executing Reset Communication.
The function timeInd() indicates a received Time Stamp.
testSdoValue() is a filter function for values which should be written to the object dictionary by SDO. Using this filter the application can check values before they are written into the object dictionary.
In an SDO client the functions sdoWrCon() and sdoRdCon() are called if the confirmation message to a SDO read or write request was received. Within these functions the application can react to the successful service or to the errors resulting from an abort domain transfer.
For a convenient usage of nonvolatile memory to store data via object 1010h the functions saveParameterInd(), clearParameterInd() and loadParameterInd() have been introduced.
For detailed information about the functions mentioned above please have a look to the Reference Manual of the CANopen Library.
Optimizations
On completion of the programming of the application behavior, all communication needs are fixed. Therefore the communication part can be optimized. The optimization can be done in:
cal_conf.h
objects.h
driver modules
In the configuration header cal_conf.h all services which are not used by the application can be disabled. That is a typical code size optimization.
In the object dictionary implementation, all unused variables of the communication and the application shall be removed. Data memory space can be saved.
In the driver modules for CAN and the timer, all unnecessary functionalities can be removed in order to reduce code size. Furthermore the run-time behavior can be optimized, for example by removing the transmit queue for Full-CAN controller.