This chapter describes the driver interface of the CANopen Library. It shows how to build a custom driver.
The CANopen Library Target Driver Interface consists of two modules. These are a CAN driver (can<x>.c) and a CPU- or RTOS driver (cpu<y>.c). A driver module is necessary for using the CANopen Library. The user is responsible for these modules and thus they are always part of the user application. They have to be adapted for the application hardware. port provides many drivers for different targets. These drivers cover a wide range of systems, but they are not always the optimal solution for your application.
The prepared drivers work together with the examples of the CANopen Library. In order to ensure this fact, the engineers of port have inserted a layer between the actual driver and the CANopen Library.
Figure 49: HAL principle
The hardware abstraction layer (HAL) realization is shown in Figure 49. The example module main.c calls the HAL functions e.g. initCAN(). These functions are defined in the target-specific initialization file i.e. init_stm32.c. The functions from this file call the functions from the driver modules i.e. can_bxcan.c. The driver module uses a general include file i.e. can_bxcan.h, which contains all CAN controller specific constants. The module examples.h which is delivered with the CANopen Library, is only necessary to compile the examples from the delivery. It is not necessary for the end user project, although some parts may be useful.
If a prepared driver module is not available for purchase from port, the user has the option to design it himself. A generic driver is available for a quick start. In this way the user can tailor the functions to his specific hardware properties.
Figure 50: HAL realization
The HAL is not necessary when only one target is supported by the user application. In this case the driver functions can be called directly from the user’s main routine. The driver modules are deliverd with the following directory structure shown in Figure 51.
Figure 51: directory structure of CANopen drivers
A directory structure like this ensures that your driver will not be overwritten if a CANopen Library update is installed. Additional the user is able to make adaptations according to his needs, e.g. removal of unused functionality.
The following points give an overview of the driver requirements.
Figure 52: driver resources
The driver has to process receive, transmit and error events on the CAN bus. These events can be initiated by the CAN controller or system messages from an operating system driver. The timer interrupt is responsible for updating the internal time base. It is used for all time-based services, like Heartbeat, Node Guarding, synchronous services, for inhibit time and timeout checks.
The reaction to the events mentioned above are shown in Figure 52. Each event sets a global flag when it is triggered which are evaluated by the CANopen Library. No CANopen Library function is called directly by the driver. This means the CANopen Library has to be called cyclically in the main loop.
All driver functions are part of the user application. Only a few functions are called directly by the CANopen Library. The interface between the CANopen Library and the driver cannot be changed. The interface functions are described in the following paragraphs.
The driver is composed of the following parts:
compiler adaptations/ CPU dependent functions
CAN controller specific functions
application dependent adaptations
CAN driver
Structure
The structure of a CAN driver made by port is shown in Figure 53. It consists of a hardware independent and a hardware dependent part. Hardware independent are the receive (RX) and transmit (TX) buffers and their access mechanism. All of these functions are coded in drivers/shar_src/cdriver.c.
Figure 53: structure of CAN driver
CAN driver API
Initialization
Init_CAN() |
The parameters of the function Init_CAN() are depending on the CAN controller and its hardware access. The CAN controller is initialized but left in the state stopped. |
getBitTiming() |
Returns a pointer to an entry of the bit-timing table for the bit rate that was passed as argument. |
Set_Baudrate() |
Initializes the CAN controller for the given bit rate. The CAN controller is left the state stopped. |
CAN bus status
Start_CAN() |
Turns the CAN controller from state stopped into state running and enabled the CAN interrupt. |
Stop_CAN() |
Turns the CAN controller into state stopped. |
Clear_busoff() |
After a bus-off this function turns the CAN controller into state running. If this is successful is not sure. The state change can last some time (128x11 recessive Bits). |
Communication objects (COBs)
Communication objects are used for administration of static information of CAN messages. They are of data type COB_T (see chapter "CAN Driver Basics").
Define_COB() |
A new communication object is created. In the TwinCAN driver the helper function createCob() takes over the biggest part of the functionality. For Full-CAN controller like the TwinCAN hardware message objects are initialized, too. This is mainly done in the function initChannel(). |
Set_COB_ID() |
Sets a new COB-ID for an existing communication object. The type (RX, TX) of the communication object can change. It might as well be deactivated. Setting the COB‑ID in a hardware message object in the TwinCAN driver is mostly done in the function initChannel(). |
Transmit_COB() |
Sends a communication object. In drivers for embedded devices the message is written into the software buffer in the function Insert_TX_Request(). The function GetNext_TX_Request() activates sending the message. |
CAN driver state
getCanDriverState() |
|
|
Returns the current state of the CAN driver. CANFLAG_BUSOFF). | (e.g. | CANFLAG_ACTIVE, |
Interrupts
CAN_int() |
This is the interrupt function. In many drivers all interrupts are handled in just one function. |
Please have a look at the Reference Manual for detailed function descriptions.
CAN driver basics
The CAN driver is the interface for reception and transmission of messages. In general message queues are used for all messages received (pRX_Buffer[]) and transmitted (pTX_Buffer[]).
For error messages and timer events a group of global flags (coLibFlags, coCanFlags) are defined. The flags can be set by the CAN and the timer interrupt. In the function flagIdentification() the flags are evaluated. It is called within the function FlushMbox() located in the module cdriver.c or in a CPU specific module.
Received messages are processed by the function msgIdentification(). The argument passed to this function is of the type CAN_MSG_T. It is defined in the module co_stru.h and contains the CAN message. Assembling the CAN message into a variable of type CAN_MSG_T and the call to msgIdentification() is done in the function FlushMbox() from the module cdriver.c.
typedef struct {
COB_KIND_T cobType; /* COB type */
COB_IDENT_T cobId; /* COB-ID */
UNSIGNED8 pData[8]; /* CAN message data */
UNSIGNED8 length; /* if bit CO_RTR_REQ is set -> RTR */
} CAN_MSG_T;
Listing 28: structure of CAN message
Transmission of CAN messages is done in the function Transmit_COB(). This function is defined in the module can_xxx.c of the CAN driver. The parameters of this function are a pointer to the COB type description structure (COB_T), a pointer to the data, and the CAN line number for the multi-line version. The most important elements of the structure COB_T are shown in Table 39.
data type | name of element | description |
COB_IDENT_T | cobId | COB-ID of CAN message |
COB_KIND_T | eType | COB type of CAN message |
UNSIGNED8 | bChannel | channel number (CAN driver internal) |
Table 39: elements of data type COB_T
The type COB_KIND_T stores the information about the use of the message object, i.e. receiving, sending, receiving RTR or sending RTR messages. This is the main task. In addition the CANopen service like PDO, SDO can be specified too.
Figure 54: structure of COB kind
Note: The concrete position of the bits are subject to change. Only use the symbolic names of the CANopen Library.
COB type | description |
CO_COB_RX | receive message |
CO_COB_TX | transmit message |
CO_COB_RX_RTR | receive message, which can be requested by the local device (a receive message that can send an RTR request) |
CO_COB_TX_RTR | transmit message, which can be requested by remote devices (a transmit message, which can receive an RTR request) |
Table 40: types of CAN messages
CAN error handling covers hardware and software errors, e.g. CAN passive or buffer overflow. All CAN errors are reported by setting a flag in the global variable coLibFlags. The concrete error cause is set in the global variable coCanFlags. The function flagIdentification() checks the variables coLibFlags and coCanFlags and calls the appropriate CANopen Library function or a user indication function.
The definition of the CAN error flags and the timer flags is shown in Table 41 and Table 42.
flag name (coLibFlags) | description |
COFLAG_SYNC_RECEIVED | SYNC message was received |
COFLAG_TIMER_PULSED | timer interval has pulsed |
COFLAG_CAN_EVENT | CAN event |
Table 41: CAN event and timer flags
flag name (coCanFlags) | description |
CANFLAG_INIT | CAN is in INIT state |
CANFLAG_ACTIVE | CAN is active |
CANFLAG_BUSOFF | CAN controller is in bus-off |
CANFLAG_PASSIVE | CAN controller is in error passive |
CANFLAG_OVERFLOW | CAN controller has detected an overflow |
CANFLAG_TXBUFFER_OVERFLOW | TX Buffer overflow at the CAN driver |
CANFLAG_RXBUFFER_OVERFLOW | RX Buffer overflow at the CAN driver |
Table 42: CAN error flags
Adaption of flag handling
The default implementation of the flag set/reset macros are located in co_flag.h and co_drv.h.
#define SET_COLIB_FLAG(FLAG) (GL_ARRAY(coLibFlags) |= (FLAG))
#define RESET_COLIB_FLAG(FLAG) (GL_ARRAY(coLibFlags) &=˜(FLAG))
#define SET_CAN_FLAG(FLAG) GL_ARRAY(coCanFlags) |= (FLAG)
#define RESET_CAN_FLAG(FLAG) GL_ARRAY(coCanFlags) &= ˜(FLAG)
Changes are possible by a user specific setting in the cal_conf.h. A good working change is an additional atomic command or anything alike. An example is shown in Listing 29.
#define SET_CAN_FLAG(FLAG) do{ \
DISABLE_CPU_INTERRUPTS(); \
GL_ARRAY(coCanFlags) |= (FLAG); \
RESTORE_CPU_INTERRUPTS(); \
}while(0)
Listing 29: example for atomic flag access
Adaption of the function FlushMbox()
The default implementation of FlushMbox() is located in cdriver.c. For an own implementation this function can disabled by removing the compiler-define CONFIG_COLIB_FLUSHMBOX. With the Industrial Communication Creator this is done by deselecting the hardware setting: Driver uses Library function FlushMbox().
Buffer handling in embedded drivers
Embedded CAN drivers use separate buffers for sending and receiving CAN messages. Sending and receiving is carried out interrupt-driven. For drivers used in an operating system environment like Windows or Linux buffer handling is done by the layer 2 driver of the system. This driver is provided by the CAN interface manufacturer.
The default buffer handling is activated in the Industrial Communication Creator with the option Driver uses Library buffer. The size of the send and receive buffer can be set independently of each other. The Industrial Communication Creator generates the following settings in the header file cal_conf.h:
#define CONFIG_COLIB_BUFFER 1
#define CONFIG_TX_BUFFER_SIZE 10
#define CONFIG_RX_BUFFER_SIZE 10
This example uses 10 entries separately in the send and receive buffer. The data in the buffers are of type BUFFER_ENTRY_T.
typedef enum {EMPTY, FULL} MEM_STAT_T;
typedef struct {
VOLATILE MEM_STAT_T eStat;
COB_KIND_T eType;
COB_IDENT_T cobId;
UNSIGNED8 bLength;
UNSIGNED8 pData[8];
UNSIGNED8 bChannel;
} BUFFER_ENTRY_T;
Listing 30: structure of an entry in the CAN buffer
Buffer handling for embedded drivers uses macros for read/write access. The macros are defined in the header file cdriver.h. The parameter for these macros are:
parameter | value | description |
direction | TX | transmit buffer |
RX | receive buffer | |
action | Read | read from the buffer |
Write | write to the buffer | |
source |
| BUFFER_ENTRY_T member |
destination |
| BUFFER_ENTRY_T member |
Table 43: parameters for buffer access macros
For all macros it is assumed that a local pointer variable
BUFFER_ENTRY_T * pBuffer;
exists.
BUFFER_INIT_PTR(direction, action) |
Initialize access to the buffer. |
BUFFER_READ(direction, source) |
Read from buffer |
BUFFER_WRITE(direction, destination, data) |
Write data to the buffer |
BUFFER_ENTRY_INCR (direction, action, status) |
Switch to next entry of the buffer. After a call to this macro the previous BUFFER_INIT_PTR() is invalid. To receive access to a buffer BUFFER_INIT_PTR() has to be called again. |
CHECK_BUFFER_READ (direction) |
Checks if the buffer contains a "full" entry and if a message can be read from this buffer. If the check is true the following code block is executed. CHECK_BUFFER_READ(RX) { /* read from the Buffer */ ... } |
CHECK_BUFFER_WRITE (direction, error) |
Checks if the buffer contains an "empty" entry and if a message can be written to this buffer. If the check is true the following code block is executed. Otherwise an error condition is signaled and the following code block is ignored. CHECK_BUFFER_WRITE( TX, CANFLAG_TXBUFFER_OVERFLOW) { /* write to the Buffer */ ... } |
Process flow of a buffer read cycle
BUFFER_ENTRY_T * pBuffer;
/* allow buffer access */
BUFFER_INIT_PTR(TX, Read);
/* buffer full? */
CHECK_BUFFER_READ(RX)
{
/* read from the buffer */
length = BUFFER_READ(RX, bLength);
...
/* release buffer */
BUFFER_ENTRY_INCR(RX, Read, EMPTY);
}
Listing 31: buffer handling
In cdriver.c there are more functions that support this buffer handling.
void clearTxBuffer(void) void clearRxBuffer(void) |
Marks all entries in one buffer RX/TX as empty. Messages that are in buffers of the CAN controller remain unchanged. |
BUFFER_INDEX_T getNumberOfTxMessages(void) BUFFER_INDEX_T getNumberOfRxMessages(void) |
Returns number of RX/TX messages in the buffer. |
RET_T Insert_TX_Request(COB_T * pCOB, UNSIGNED8 * pData) |
Inserts the next transmission request into the queue. |
Please have a look at the Reference Manual for detailed function descriptions.
Interrupt handling
Figure 55: CAN interrupt handling
For systems which do not use a CAN interrupt e.g. PCs using active CAN cards or systems which get the CAN message via operating system drivers, it is not necessary to put the incoming messages into a queue. All messages are typically buffered in the operating system driver or in the memory of active CAN cards. The interpretation can be done directly via msgIdentification().
The reception flow checks two kinds of messages. The first is the CANopen SYNC telegram. The classification of SYNC has to be the first in order to guarantee a minimum of jitter. All other messages are put into a queue which decouples the CAN interrupt service routine from the CANopen Library.
CAN-ISR management
SetIntMask()
ResetIntMask()
Init_CAN_Interrupts()
Enable_CAN_Interrupts()
Disable_CAN_Interrupts()
Restore_CAN_Interrupts()
For the CAN-ISR management the functions for enabling/disabling CAN interrupts are mandatory. These functions ensure that the values of the message queue read and write pointers remain consistent. For active CAN modules these functions are empty. The functions for the setting and resetting of the CAN-ISR to/from interrupt vector tables are not implemented for active modules.
Special about using Remote Frames (RTR)
Between CAN controllers the implementation of the RTR support differs. This is one reason that the CiA recommends to implement devices without RTR support. On the CiA web site a document is available on this subject (see Application note 802).
In general a device shall not answer a Remote Request in every case. The CANopen Library needs to have the complete control about the messages a device sends. With the new CANopen Library version Remote Requests are only answered by software. For CAN controllers that can only answer by hardware, RTR support is not possible.
The CANopen Library uses the RTR settings from the object dictionary of the delivered device. It is possible to set the RTR not allowed bits to disable the RTR support.
CPU/RTOS driver
The CPU/RTOS driver is responsible for memory management, timer functionalities, CAN-ISR management and the CAN controller access. It is coded in target specific modules like drivers/<target-name>/cpu.c.
Functions that are valid for a complete CPU family are in drivers/shar_src/cpu_<cpu-family>.c. Functions used by all drivers are placed in drivers/shar_src/<module>.c.
Timer Functions:
InitTimer()
ReleaseTimer()
Timer_int()
The functions ensure the initialization / de-initialization of a hardware or software timer and the time triggered CANopen Library functionality. The CANopen function Timer_int() is called by an ISR or is driven by an operating system timer event. It only increments the counter coTimerTicks and sets a global flag.
Figure 56: time-triggered functionality
The functionality of the Timer_int() can also be integrated into a user defined timer function which is called cyclically.
Customer-specific timer implementation
Within many implementations no timer resource is free, especially the timer used in the default implementation. The CANopen Library needs only a function that is called with a constant period. It is also possible to use an already used timer interrupt. Within this periodic called function the variable coTimerTicks must be incremented and the CANopen Library must be informed by calling
coTimerTicks++;
SET_COLIB_FLAG(COFLAG_TIMER_PULSED);
Note: Please have a look in the delivered default implementation of the timer interrupt or the code fragment within the generic driver implementation.
For using a custom-specific implementation the default implementation must be deactivated in the hardware settings. Within the Industrial Communication Creator the setting Use pre-configured timer must be deactivate. This will disable the define CONFIG_COLIB_TIMER in the file cal_conf.h. The Timer period must be set to the new period.
ISR management
Init_CPU_Interrupts()
Enable_CPU_Interrupts()
Disable_CPU_Interrupts()
Restore_CPU_Interrupts()
These functions influence the CPU interrupt.
Note: Locking of CPU interrupts and CAN interrupts can be carried out nested.
Compiler adaptions
Compilers support different memory models. For the CANopen Library a memory model has to be chosen that allows access of a generic data pointer (unsigned char *) similarly to
global variables (e.g. canMsg),
object directory (possibly in Flash memory),
object directory description structures (possibly in Flash memory),
The CAN controller of the XC164CS is outside of the addressable area of the often used memory model LARGE. In order not to use a bigger memory model access to the CAN controller is carried out with a far pointer. For this purpose compiler dependent definitions are used in the header file drivers/shar_inc/co_keil.h.
#define FAR far
#define NEAR near
Some compilers require special constructs when a constant has to be linked into flash memory, but this is not necessary for the XC164. It is sufficient to define
#define CO_CONST const.
Application dependent adaptions
Note: Additional hints about the default implementation of different drivers are placed in the README files within the drivers directories.
The hardware dependent adjustments to the driver are located in the directory drivers/xc164. This makes it possible to overwrite the actual driver due to an update in the directory drivers/shar_inc and drivers/shar_src without carrying out the adjustments again.
The file init_xc164.c contains the hardware initialization function iniDevice() and the wrapper function initCan(). This wrapper function is needed only by some hardware architectures. For external CAN controller the access macros have to be adapted and the hardware connection like chip selects, timings have to be initialized.
The file cpu.c contains the locking and releasing of CAN interrupts. It is important to note that functions that lock the interrupt with the macro DISABLE_CAN_INTERRUPTS() can be called nested. Therefore, releasing of CAN interrupts typically should be done with the macro RESTORE_CAN_INTERRUPTS(). This ensures that the CAN interrupt is released in the top most function that initially locked the interrupt.
Initial operation
Hints for the initial operation of the prepared CAN driver. It is assumed that
the hardware has been initialized
the bit-timing table has been adapted,
access macros have been selected correctly or adapted respectively.
The emphasis of this chapter is on checking these adjustments.
The Industrial Communication Creator provides the possibility to enable debug settings for testing the adjustments. Debug settings are activated by the option Debug Settings. This enables the option Send a test message after Init. If this option is activated the following definitions are generated in the header file cal_conf.h:
#define CONFIG_EXPERIMENTAL 1
#define CONFIG_CAN_TX_TEST 1
This option causes that within the function Init_CAN() a message is sent with the COBID 100 and one data byte that has the value AAh. It is sent with the chosen CAN bit rate. No interrupts are used.
Note: After this test the options has to be deactivated again.