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:
struct my_can {
int st;
};
int my_can_init(
struct my_can *
can,
const char *ifname);
void my_can_fini(
struct my_can *
can);
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;
goto error_init_io;
}
goto error_open_can;
}
goto error_create_poll;
}
goto error_create_net;
}
};
return 0;
error_create_net:
error_create_poll:
error_open_can:
error_init_io:
return -1;
}
void
my_can_fini(
struct my_can *
can)
{
}
void
my_can_step(
struct my_can *
can,
int timeout)
{
if (msec > 0 && msec < timeout)
timeout = msec;
return;
int result = 0;
}
}
}
}
}
int
{
struct my_can *
can = data;
return 0;
}
int
my_can_on_send(
const struct can_msg *msg,
void *data)
{
struct my_can *
can = data;
}
This header file is part of the utilities library; it contains the native and platform-independent er...
@ ERRNUM_WOULDBLOCK
Operation would block.
@ ERRNUM_AGAIN
Resource unavailable, try again.
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
void set_errc(int errc)
Sets the current (thread-specific) native error code to errc.
errnum_t get_errnum(void)
Returns the last (thread-specific) platform-independent error number set by a system call or library ...
@ CAN_STATE_BUSOFF
The bus off state (TX/RX error count >= 256).
@ CAN_STATE_PASSIVE
The error passive state (TX/RX error count < 256).
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.
io_handle_t io_open_can(const char *path)
Opens a CAN device.
int io_can_read(io_handle_t handle, struct can_msg *msg)
Reads a single CAN or CAN FD frame.
int io_can_get_state(io_handle_t handle)
Obtains the state of a CAN device.
void lely_io_fini(void)
Finalizes the I/O library and terminates the availability of the I/O functions.
int lely_io_init(void)
Initializes the I/O library and makes the I/O functions available for use.
int io_set_flags(io_handle_t handle, int flags)
Sets the flags of an I/O device.
#define IO_HANDLE_ERROR
The value of an invalid I/O device handle.
@ IO_FLAG_NONBLOCK
Perform I/O operations in non-blocking mode.
@ IO_FLAG_LOOPBACK
Receive own messages (i.e., sent by the same device).
void io_handle_release(io_handle_t handle)
Decrements the reference count of an I/O device handle.
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.
#define TIME_UTC
An integer constant greater than 0 that designates the UTC time base.
#define CAN_MSG_INIT
The static initializer for can_msg.
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.
void can_net_destroy(can_net_t *net)
Destroys a CAN network interface.
can_net_t * can_net_create(void)
Creates a new CAN network interface.
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.
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).
int can_net_set_time(can_net_t *net, const struct timespec *tp)
Sets the current time of a CAN network interface.
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.
void io_poll_destroy(io_poll_t *poll)
Destroys an I/O polling interface.
@ IO_EVENT_READ
An event signaling that a file descriptor is ready for reading normal-priority (non-OOB) data.
@ IO_EVENT_ERROR
An event signaling that an error has occurred for a file descriptor.
io_poll_t * io_poll_create(void)
Creates a new I/O polling interface.
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...
#define IO_EVENT_INIT
The static initializer for struct io_event.
An I/O polling interface.
A CAN or CAN FD format frame.
uint_least8_t data[CAN_MSG_MAX_LEN]
The frame payload (in case of a data frame).
io_handle_t handle
An I/O device handle (if events != IO_EVENT_SIGNAL).
int events
The events that should be watched or have been triggered (either IO_EVENT_SIGNAL, or any combination ...
union io_event::@10 u
Signal attributes depending on the value of events.
A time type with nanosecond resolution.
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.
C++11 example:
class MyCAN {
public:
explicit MyCAN(const char* ifname)
: m_handle(ifname)
, m_st(m_handle.getState())
{
event.u.handle = m_handle;
m_poll->watch(m_handle, &event, true);
m_net->setTime(now);
m_next = now;
m_net->setNextFunc<MyCAN, &MyCAN::onNext>(this);
m_net->setSendFunc<MyCAN, &MyCAN::onSend>(this);
}
MyCAN(const MyCAN&) = delete;
MyCAN& operator=(const MyCAN&) = delete;
CANNet* getNet() const noexcept {
return m_net.get(); }
void
step(int timeout = 0) noexcept
{
if (msec > 0 && msec < timeout)
timeout = msec;
int n = m_poll->wait(1, &event, timeout);
m_net->setTime(now);
if (n != 1 || event.u.handle != m_handle)
return;
int result = 0;
while ((result = m_handle.read(msg)) == 1)
m_net->recv(msg);
if (!result || (result == -1
}
int st = m_handle.getState();
if (st != m_st) {
}
m_st = st;
}
}
}
private:
int
{
m_next = *tp;
return 0;
}
int
onSend(
const can_msg* msg)
noexcept
{
return m_handle.write(*msg) == 1 ? 0 : -1;
}
int m_st;
};
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.
A Controller Area Network (CAN) device handle.
An opaque I/O polling interface type.
Global namespace for the Lely Industries N.V. libraries.
::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,...
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:
int
main(void)
{
if (my_can_init(&
can,
"can0") == -1) {
goto error_init_can;
}
if (!dev) {
goto error_create_dev;
}
if (!nmt) {
goto error_create_nmt;
}
for (;;) {
}
return EXIT_SUCCESS;
error_create_nmt:
error_create_dev:
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.
void co_dev_destroy(co_dev_t *dev)
Destroys a CANopen device, including all objects in its object dictionary.
This header file is part of the utilities library; it contains the diagnostic declarations.
void diag(enum diag_severity severity, int errc, const char *format,...)
Emits a diagnostic message.
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.
int co_nmt_cs_ind(co_nmt_t *nmt, co_unsigned8_t cs)
Processes an NMT command from the master or the application.
#define CO_NMT_CS_RESET_NODE
The NMT command specifier 'reset node'.
co_nmt_t * co_nmt_create(can_net_t *net, co_dev_t *dev)
Creates a new CANopen NMT master/slave service.
This header file is part of the C11 and POSIX compatibility library; it includes <stdlib....
A CANopen NMT master/slave service.
C++11 example:
int
main()
{
auto dev = make_unique_c<CODev>("test.dcf");
auto nmt = make_unique_c<CONMT>(
can.getNet(), dev.get());
for (;;) {
}
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:
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)
diag(
DIAG_ERROR, 0,
"received SDO abort code %08X (%s) when reading object %04X:%02X",
else
diag(
DIAG_INFO, 0,
"received 0x%08X when reading object %04X:%02X",
val, idx, subidx);
}
An opaque CANopen Client-SDO service type.
This header file is part of the CANopen library; it contains the C++ interface of the Client-SDO decl...
@ DIAG_INFO
An informational message.
const char * co_sdo_ac2str(co_unsigned32_t ac)
Returns a string describing an SDO abort code.
Before the main event loop we obtain a Client-SDO service object from the NMT service and issue the request:
auto csdo = nmt->getCSDO(1);
if (csdo)
csdo->upReq<co_unsigned32_t, &onUp>(0x1000, 0x00, nullptr);
#define CO_NMT_ST_START
The NMT state 'operational'.
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.