Lely core libraries 1.9.2
CANopen library overview

Contrary to most other CANopen stacks, this implementation is completely passive; the library does not perform any I/O (besides may reading some files from disk), it does not create threads nor does it access the system clock. Instead, it relies on the user to send and receive CAN frames and update the clock. This allows the library to be easily embedded in a wide variety of applications.

The library is also asynchronous. Issuing a request is always a non-blocking operation. If the request is confirmed, the API accepts a callback function which is invoked once the request completes (with success or failure). This allows the stack to run in a single thread, even when processing dozens of simultaneous requests (not uncommon for an NMT master).

CAN network object

The interface between the CANopen stack and the CAN bus (and system clock) is provided by the CAN network object (can_net_t/CANNet) from the Lely CAN library (liblely-can). When the CANopen stack needs to send a CAN frame, it hands it over to a CAN network object, which in turn invokes a user-defined callback function to write the frame to the bus. Similarly, when a user reads a CAN frame from the bus, he gives it to a CAN network object, which in turn distributes it to the registered receivers in the CANopen stack. Additionally, the user periodically checks the current time and tells the CAN network object, which then executes the actions for timers that have elapsed.

Sometimes a CANopen application runs on a device which does not have direct access to a CAN bus. CiA 315 defines a protocol for tunneling CAN frames over wired or Wireless Transmission Media (WTM). The interface for this protocol can be found in lely/co/wtm.h (co_wtm_t) and lely/co/wtm.hpp (lely::COWTM). Additionally, the can2udp tool can be run as a daemon or service to act as a proxy between a CAN bus and UDP.

The first task when writing a CANopen application is connecting a CAN network object to the CAN bus. The following example shows how to do this using the Lely I/O library (liblely-io), which provides a platform-independent interface to the CAN bus.

C example:

#include <lely/util/time.h>
#include <lely/can/net.h>
#include <lely/io/can.h>
#include <lely/io/poll.h>
struct my_can {
io_handle_t handle;
int st;
io_poll_t *poll;
can_net_t *net;
struct timespec next;
};
int my_can_init(struct my_can *can, const char *ifname);
void my_can_fini(struct my_can *can);
// This function can be called to perform a single step in an event loop.
void my_can_step(struct my_can *can, int timeout);
int my_can_on_next(const struct timespec *tp, void *data);
int my_can_on_send(const struct can_msg *msg, void *data);
int
my_can_init(struct my_can *can, const char *ifname)
{
int errc = 0;
// Initialize the I/O library.
if (lely_io_init() == -1) {
errc = get_errc();
goto error_init_io;
}
// Open a handle to the CAN bus.
can->handle = io_open_can(ifname);
if (can->handle == IO_HANDLE_ERROR) {
errc = get_errc();
goto error_open_can;
}
can->st = io_can_get_state(can->handle);
// Create a new I/O polling interface.
can->poll = io_poll_create();
if (!can->poll) {
errc = get_errc();
goto error_create_poll;
}
// Create a CAN network object.
can->net = can_net_create();
if (!can->net) {
errc = get_errc();
goto error_create_net;
}
// Do not block when reading or writing CAN frames and make sure we
// receive the frames we sent.
// Watch the CAN bus for incoming frames.
struct io_event event = {
.u.handle = can->handle
};
io_poll_watch(can->poll, can->handle, &event, 1);
struct timespec now = { 0, 0 };
// Obtain the current time. This is equivalent to
// clock_gettime(CLOCK_REALTIME, &now) from POSIX. To use a steady,
// monotonic clock instead, replace all occurrences of the following
// line with:
// clock_gettime(CLOCK_MONOTONIC, &now);
// Initialize the CAN network clock.
can_net_set_time(can->net, &now);
can->next = now;
// Register my_can_on_next as the function to be invoked when the time
// at which the next CAN timer triggers is updated.
can_net_set_next_func(can->net, &my_can_on_next, can);
// Register my_can_on_send() as the function to be invoked when a CAN
// frame needs to be sent.
can_net_set_send_func(can->net, &my_can_on_send, can);
return 0;
can->net = NULL;
error_create_net:
can->poll = NULL;
error_create_poll:
can->handle = IO_HANDLE_ERROR;
error_open_can:
error_init_io:
set_errc(errc);
return -1;
}
void
my_can_fini(struct my_can *can)
{
can->net = NULL;
can->poll = NULL;
can->handle = IO_HANDLE_ERROR;
}
void
my_can_step(struct my_can *can, int timeout)
{
struct timespec now = { 0, 0 };
// Check if a CAN timer is set to expire before the specified timeout.
// Ignore if the expiration time is in the past, as that probably
// indicates an old timer.
int64_t msec = timespec_diff_msec(&can->next, &now);
if (msec > 0 && msec < timeout)
timeout = msec;
// Wait at most `timeout` milliseconds for an I/O event to occur.
struct io_event event = IO_EVENT_INIT;
int n = io_poll_wait(can->poll, 1, &event, timeout);
// Update the CAN network clock.
can_net_set_time(can->net, &now);
if (n != 1 || event.u.handle != can->handle)
return;
// If the CAN bus is ready for reading, process all waiting frames.
if (event.events & IO_EVENT_READ) {
struct can_msg msg = CAN_MSG_INIT;
int result = 0;
while ((result = io_can_read(can->handle, &msg)) == 1)
can_net_recv(can->net, &msg);
// Treat the reception of an error frame, or any error other
// than an empty receive buffer, as an error event.
if (!result || (result == -1 && get_errnum() != ERRNUM_AGAIN
event.events |= IO_EVENT_ERROR;
}
// If an error occurred, update the state of the CAN device.
if (can->st == CAN_STATE_BUSOFF || (event.events & IO_EVENT_ERROR)) {
int st = io_can_get_state(can->handle);
if (st != can->st) {
if (can->st == CAN_STATE_BUSOFF) {
// Recovered from bus off. This is typically
// handled by invoking:
// co_nmt_on_err(nmt, 0x8140, 0x10, NULL);
} else if (st == CAN_STATE_PASSIVE) {
// CAN in error passive mode. This is typically
// handled by invoking:
// co_nmt_on_err(nmt, 0x8120, 0x10, NULL);
}
can->st = st;
}
}
}
int
my_can_on_next(const struct timespec *tp, void *data)
{
struct my_can *can = data;
can->next = *tp;
return 0;
}
int
my_can_on_send(const struct can_msg *msg, void *data)
{
struct my_can *can = data;
// Send a single frame to the CAN bus.
return io_can_write(can->handle, msg) == 1 ? 0 : -1;
}
This header file is part of the utilities library; it contains the native and platform-independent er...
@ ERRNUM_WOULDBLOCK
Operation would block.
Definition: errnum.h:230
@ ERRNUM_AGAIN
Resource unavailable, try again.
Definition: errnum.h:86
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
Definition: errnum.c:947
void set_errc(int errc)
Sets the current (thread-specific) native error code to errc.
Definition: errnum.c:957
errnum_t get_errnum(void)
Returns the last (thread-specific) platform-independent error number set by a system call or library ...
Definition: errnum.h:369
@ CAN_STATE_BUSOFF
The bus off state (TX/RX error count >= 256).
Definition: can.h:33
@ CAN_STATE_PASSIVE
The error passive state (TX/RX error count < 256).
Definition: can.h:31
This header file is part of the I/O library; it contains the Controller Area Network (CAN) declaratio...
int io_can_write(io_handle_t handle, const struct can_msg *msg)
Writes a single CAN or CAN FD frame.
Definition: can.c:271
io_handle_t io_open_can(const char *path)
Opens a CAN device.
Definition: can.c:104
int io_can_read(io_handle_t handle, struct can_msg *msg)
Reads a single CAN or CAN FD frame.
Definition: can.c:211
int io_can_get_state(io_handle_t handle)
Obtains the state of a CAN device.
Definition: can.c:383
void lely_io_fini(void)
Finalizes the I/O library and terminates the availability of the I/O functions.
Definition: io.c:59
int lely_io_init(void)
Initializes the I/O library and makes the I/O functions available for use.
Definition: io.c:33
int io_set_flags(io_handle_t handle, int flags)
Sets the flags of an I/O device.
Definition: io.c:124
#define IO_HANDLE_ERROR
The value of an invalid I/O device handle.
Definition: io.h:34
@ IO_FLAG_NONBLOCK
Perform I/O operations in non-blocking mode.
Definition: io.h:62
@ IO_FLAG_LOOPBACK
Receive own messages (i.e., sent by the same device).
Definition: io.h:64
void io_handle_release(io_handle_t handle)
Decrements the reference count of an I/O device handle.
Definition: handle.c:47
int timespec_get(struct timespec *ts, int base)
Sets the interval at ts to hold the current calendar time based on the specified time base.
Definition: time.c:32
#define TIME_UTC
An integer constant greater than 0 that designates the UTC time base.
Definition: time.h:202
#define CAN_MSG_INIT
The static initializer for can_msg.
Definition: msg.h:114
This header file is part of the CAN library; it contains the CAN network interface declarations.
void can_net_set_send_func(can_net_t *net, can_send_func_t *func, void *data)
Sets the callback function used to send CAN frames from a network interface.
Definition: net.c:334
void can_net_destroy(can_net_t *net)
Destroys a CAN network interface.
Definition: net.c:195
can_net_t * can_net_create(void)
Creates a new CAN network interface.
Definition: net.c:170
void can_net_set_next_func(can_net_t *net, can_timer_func_t *func, void *data)
Sets the callback function invoked when the time at which the next CAN timer triggers is updated.
Definition: net.c:269
int can_net_recv(can_net_t *net, const struct can_msg *msg)
Receives a CAN frame with a network interface and processes it with the corresponding receiver(s).
Definition: net.c:278
int can_net_set_time(can_net_t *net, const struct timespec *tp)
Sets the current time of a CAN network interface.
Definition: net.c:213
This header file is part of the I/O library; it contains I/O polling interface declarations.
int io_poll_watch(io_poll_t *poll, io_handle_t handle, struct io_event *event, int keep)
Registers an I/O device with an I/O polling interface and instructs it to watch for certain events.
Definition: poll.c:253
void io_poll_destroy(io_poll_t *poll)
Destroys an I/O polling interface.
Definition: poll.c:244
@ IO_EVENT_READ
An event signaling that a file descriptor is ready for reading normal-priority (non-OOB) data.
Definition: poll.h:41
@ IO_EVENT_ERROR
An event signaling that an error has occurred for a file descriptor.
Definition: poll.h:36
io_poll_t * io_poll_create(void)
Creates a new I/O polling interface.
Definition: poll.c:219
int io_poll_wait(io_poll_t *poll, int maxevents, struct io_event *events, int timeout)
Waits at most timeout milliseconds for at most maxevents I/O events to occur for any of the I/O devic...
Definition: poll.c:356
#define IO_EVENT_INIT
The static initializer for struct io_event.
Definition: poll.h:72
A CAN network interface.
Definition: net.c:37
An I/O polling interface.
Definition: poll.c:48
A CAN or CAN FD format frame.
Definition: msg.h:88
uint_least8_t data[CAN_MSG_MAX_LEN]
The frame payload (in case of a data frame).
Definition: msg.h:103
A CAN device.
Definition: can.c:53
An I/O event.
Definition: poll.h:50
io_handle_t handle
An I/O device handle (if events != IO_EVENT_SIGNAL).
Definition: poll.h:67
int events
The events that should be watched or have been triggered (either IO_EVENT_SIGNAL, or any combination ...
Definition: poll.h:56
union io_event::@10 u
Signal attributes depending on the value of events.
An I/O device handle.
Definition: handle.h:41
A time type with nanosecond resolution.
Definition: time.h:83
This header file is part of the utilities library; it contains the time function declarations.
int_least64_t timespec_diff_msec(const struct timespec *t1, const struct timespec *t2)
Returns the time difference (in milliseconds) between *t1 and *t2.
Definition: time.h:203

C++11 example:

#include <lely/util/time.h>
#include <lely/can/net.hpp>
#include <lely/io/can.hpp>
#include <lely/io/poll.hpp>
using namespace lely;
class MyCAN {
public:
explicit MyCAN(const char* ifname)
: m_handle(ifname) // Open a handle to the CAN bus.
, m_st(m_handle.getState()) // Get the state of the CAN device.
, m_poll(new IOPoll) // Create a new I/O polling interface.
, m_net(new CANNet) // Create a CAN network object.
{
// Do not block when reading or writing CAN frames and make sure
// we receive the frames we sent.
m_handle.setFlags(IO_FLAG_NONBLOCK | IO_FLAG_LOOPBACK);
// Watch the CAN bus for incoming frames.
io_event event = { IO_EVENT_READ, { 0 } };
event.u.handle = m_handle;
m_poll->watch(m_handle, &event, true);
timespec now = { 0, 0 };
// Obtain the current time. This is equivalent to
// clock_gettime(CLOCK_REALTIME, &now) from POSIX. To use a
// steady, monotonic clock instead, replace all occurrences of
// the following line with:
// clock_gettime(CLOCK_MONOTONIC, &now);
// Initialize the CAN network clock.
m_net->setTime(now);
m_next = now;
// Register the onNext() member function as the function to be
// invoked when the time at which the next CAN timer triggers is
// updated.
m_net->setNextFunc<MyCAN, &MyCAN::onNext>(this);
// Register the onSend() member function as the function to be
// invoked when a CAN frame needs to be sent.
m_net->setSendFunc<MyCAN, &MyCAN::onSend>(this);
}
// Disable copying.
MyCAN(const MyCAN&) = delete;
MyCAN& operator=(const MyCAN&) = delete;
CANNet* getNet() const noexcept { return m_net.get(); }
// This function can be called to perform a single step in an event
// loop.
void
step(int timeout = 0) noexcept
{
timespec now = { 0, 0 };
// Check if a CAN timer is set to expire before the specified
// timeout. Ignore if the expiration time is in the past, as
// that probably indicates an old timer.
int64_t msec = timespec_diff_msec(&m_next, &now);
if (msec > 0 && msec < timeout)
timeout = msec;
// Wait at most `timeout` milliseconds for an I/O event to
// occur.
int n = m_poll->wait(1, &event, timeout);
// Update the CAN network clock.
m_net->setTime(now);
if (n != 1 || event.u.handle != m_handle)
return;
// If the CAN bus is ready for reading, process all waiting
// frames.
if (event.events & IO_EVENT_READ) {
int result = 0;
while ((result = m_handle.read(msg)) == 1)
m_net->recv(msg);
// Treat the reception of an error frame, or any error
// other than an empty receive buffer, as an error
// event.
if (!result || (result == -1
event.events |= IO_EVENT_ERROR;
}
// If an error occurred, update the state of the CAN device.
if (m_st == CAN_STATE_BUSOFF
|| (event.events & IO_EVENT_ERROR)) {
int st = m_handle.getState();
if (st != m_st) {
if (m_st == CAN_STATE_BUSOFF) {
// Recovered from bus off. This is
// typically handled by invoking:
// nmt->onErr(0x8140, 0x10);
} else if (st == CAN_STATE_PASSIVE) {
// CAN in error passive mode. This is
// typically handled by invoking:
// nmt->onErr(0x8120, 0x10);
}
m_st = st;
}
}
}
private:
int
onNext(const timespec* tp) noexcept
{
m_next = *tp;
return 0;
}
int
onSend(const can_msg* msg) noexcept
{
// Send a single frame to the CAN bus.
return m_handle.write(*msg) == 1 ? 0 : -1;
}
IOCAN m_handle;
int m_st;
timespec m_next;
};
This header file is part of the I/O library; it contains the C++ interface of the Controller Area Net...
An opaque CAN network interface type.
Definition: net.hpp:83
A Controller Area Network (CAN) device handle.
Definition: can.hpp:38
An opaque I/O polling interface type.
Definition: poll.hpp:70
Global namespace for the Lely Industries N.V. libraries.
Definition: buf.hpp:32
::std::unique_ptr< T, delete_c_type< T > > unique_c_ptr
A specialization of std::unique_ptr for trivial, standard layout or incomplete C types,...
Definition: c_type.hpp:103
This header file is part of the CAN library; it contains the C++ interface of the CAN network interfa...
This header file is part of the I/O library; it contains the C++ interface of the I/O polling interfa...

Object dictionary

The object dictionary is the central concept of the CANopen protocol. It contains almost the entire state of a CANopen device, including process data and communication parameters. Communication between nodes consists primarily of reading from and writing to each others object dictionary. Even writing CANopen applications is mostly a matter of configuring the object dictionary.

Together with the Node-ID, the object dictionary is managed by a CANopen device object (co_dev_t/lely::CODev). Although it is possible to construct the object dictionary from scratch with the C and C++ API, it is more convenient to read it from an Electronic Data Sheet (EDS) or Device Configuration File (DCF) (see CiA 306). Functions to parse EDS/DCF files can be found in lely/co/dcf.h and lely/co/dcf.hpp.

Embedded devices often do not have the resources to parse an EDS/DCF file at runtime. Using the dcf2c tool it is possible to create a C file containing a static object dictionary, which can be compiled with the application. The static object dictionary can then be converted to a dynamic one at runtime (see lely/co/sdev.h and lely/co/sdev.hpp).

Service objects

The CANopen stack implements the following services:

While it is possible to create these services by hand, it is almost always more convenient to let them be managed by a single Network Management (NMT) service object (co_nmt_t/lely::CONMT). An NMT object manages the state of a CANopen device and creates and destroys the other services as needed.

Like all CANopen services, the NMT service gets its configuration from the object dictionary. A typical CANopen application therefore consists of

  • creating the CAN network object, as we have seen above;
  • loading the object dictionary form an EDS/DCF file;
  • creating an NMT service;
  • and, finally, processing CAN frames in an event loop.

Note, however, that a newly created NMT service starts out in the 'Initialisation' state and does not create any services or perform any communication. This allows the application to register callback functions before the node becomes operational. The NMT service, including the entire boot-up sequence, can be started by giving it the RESET NODE command.

The following example is a minimal CANopen application. Depending on the EDS/DCF file, it can be a simple slave that does nothing besides producing a heartbeat message, or a full-fledged master managing a network of slaves (including automatic firmware upgrades!).

C example:

#include <lely/util/diag.h>
#include <lely/co/dcf.h>
#include <lely/co/nmt.h>
#include <stdlib.h>
int
main(void)
{
// Initialize the CAN network.
struct my_can can;
if (my_can_init(&can, "can0") == -1) {
diag(DIAG_ERROR, get_errc(), "unable to initialize CAN network");
goto error_init_can;
}
// Load the object dictionary from a DCF file.
if (!dev) {
diag(DIAG_ERROR, get_errc(), "unable to load object dictionary");
goto error_create_dev;
}
// Create the NMT service.
co_nmt_t *nmt = co_nmt_create(can.net, dev);
if (!nmt) {
diag(DIAG_ERROR, get_errc(), "unable to create NMT service");
goto error_create_nmt;
}
// Start the NMT service. We do this by pretending to receive a RESET
// NODE command from the master.
// The main event loop.
for (;;) {
// Process CAN frames in steps of 10 milliseconds.
my_can_step(&can, 10);
// TODO: do other useful stuff.
}
my_can_fini(&can);
return EXIT_SUCCESS;
error_create_nmt:
error_create_dev:
my_can_fini(&can);
error_init_can:
return EXIT_FAILURE;
}
This header file is part of the CANopen library; it contains the Electronic Data Sheet (EDS) and Devi...
co_dev_t * co_dev_create_from_dcf_file(const char *filename)
Creates a CANopen device from an EDS or DCF file.
Definition: dcf.c:96
void co_dev_destroy(co_dev_t *dev)
Destroys a CANopen device, including all objects in its object dictionary.
Definition: dev.c:166
This header file is part of the utilities library; it contains the diagnostic declarations.
@ DIAG_ERROR
An error.
Definition: diag.h:49
void diag(enum diag_severity severity, int errc, const char *format,...)
Emits a diagnostic message.
Definition: diag.c:156
This header file is part of the CANopen library; it contains the network management (NMT) declaration...
void co_nmt_destroy(co_nmt_t *nmt)
Destroys a CANopen NMT master/slave service.
Definition: nmt.c:1027
int co_nmt_cs_ind(co_nmt_t *nmt, co_unsigned8_t cs)
Processes an NMT command from the master or the application.
Definition: nmt.c:1710
#define CO_NMT_CS_RESET_NODE
The NMT command specifier 'reset node'.
Definition: nmt.h:49
co_nmt_t * co_nmt_create(can_net_t *net, co_dev_t *dev)
Creates a new CANopen NMT master/slave service.
Definition: nmt.c:1002
This header file is part of the C11 and POSIX compatibility library; it includes <stdlib....
A CANopen device.
Definition: dev.c:38
A CANopen NMT master/slave service.
Definition: nmt.c:104

C++11 example:

#include <lely/co/dcf.hpp>
#include <lely/co/nmt.hpp>
int
main()
{
// Initialize the I/O library.
// Initialize the CAN network.
MyCAN can("can0");
// Load the object dictionary from a DCF file.
auto dev = make_unique_c<CODev>("test.dcf");
// Create the NMT service.
auto nmt = make_unique_c<CONMT>(can.getNet(), dev.get());
// Start the NMT service. We do this by pretending to receive a RESET
// NODE command from the master.
nmt->csInd(CO_NMT_CS_RESET_NODE);
// The main event loop.
for (;;) {
// Process CAN frames in steps of 10 milliseconds.
can.step(10);
// TODO: do other useful stuff.
}
return 0;
}
This header file is part of the CANopen library; it contains the C++ interface of the Electronic Data...
This header file is part of the CANopen library; it contains the C++ interface of the network managem...

CANopen requests

As mentioned before, the CANopen stack is asynchronous. Requests return immediately, but take a callback function which is invoked once the request succeeds (or fails). See the documentation of the Lely utilities library (liblely-util) for a description of the general callback interface.

As an example, define the following callback function for reading a 32-bit unsigned integer from a remote object dictionary:

#include <lely/util/diag.h>
#include <lely/co/csdo.hpp>
void
onUp(COCSDO* sdo, co_unsigned16_t idx, co_unsigned8_t subidx,
co_unsigned32_t ac, co_unsigned32_t val, void* data)
{
(void)sdo;
(void)data;
if (ac)
// Print an error message if an abort code was received.
diag(DIAG_ERROR, 0, "received SDO abort code %08X (%s) when reading object %04X:%02X",
ac, co_sdo_ac2str(ac), idx, subidx);
else
// Print the received value on success.
diag(DIAG_INFO, 0, "received 0x%08X when reading object %04X:%02X",
val, idx, subidx);
}
An opaque CANopen Client-SDO service type.
Definition: csdo.hpp:156
This header file is part of the CANopen library; it contains the C++ interface of the Client-SDO decl...
@ DIAG_INFO
An informational message.
Definition: diag.h:45
const char * co_sdo_ac2str(co_unsigned32_t ac)
Returns a string describing an SDO abort code.
Definition: sdo.c:57

Before the main event loop we obtain a Client-SDO service object from the NMT service and issue the request:

// Wait for the node to be operational.
while (nmt->getSt() != CO_NMT_ST_START)
can.step(10);
// Obtain the first preconfigured Client-SDO connection.
auto csdo = nmt->getCSDO(1);
if (csdo)
// Read the device type (object 1000) from the remote object dictionary.
csdo->upReq<co_unsigned32_t, &onUp>(0x1000, 0x00, nullptr);
#define CO_NMT_ST_START
The NMT state 'operational'.
Definition: nmt.h:61

The upReq() function returns immediately; onUp() is called from the main event loop when the request succeeds (or fails).

This is an example for a Client-SDO requests, but all services follow this pattern. Note that by the time the callback function is called, the request has been completed. It is therefore possible to issue a new request directly from the callback function. This pattern is used extensively by the master when booting slaves.