Command-line tutorial

9 minute read

The first tutorial does not require any programming. We will use the CANopen control tool to create CANopen master and slave processes that communicate over a virtual CAN bus. No CAN hardware is required; instead, we will use the virtual CAN interface provided by SocketCAN.

Note: Using SocketCAN means this tutorial only works on Linux.

You may want to monitor traffic on the virtual CAN bus, to better understand what is going on. In this tutorial we will assume the use of candump from can-utils, but Wireshark works just as well.

Virtual CAN interface

Make sure the virtual CAN driver is loaded:

sudo modprobe vcan

and create a virtual CAN network interface with the name vcan:

sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

You can check that the interface was created by running

ip link show vcan0

This should output something like

3: vcan0: <NOARP,UP,LOWER_UP> mtu 72 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000

To monitor the CAN traffic in this tutorial, run

candump vcan0

in a terminal window and keep it open. You can also capture the traffic from vcan0 with Wireshark.

A single device

Every CANopen device, including the mock master and slave in this tutorial, has an object dictionary. The layout and content of this object dictionary is described in an electronic data sheet (EDS) or device configuration file (DCF).

“EDS” and “DCF” tend to be used interchangeably. Technically speaking, an EDS describes a certain type of device, while a DCF describes an individual device, including unique values such as a serial number. But most tools and parsers do not make a distinction.

Open a terminal and create a virtual CANopen device with the default DCF for the CANopen control tool:

coctl vcan0 /etc/coctl.dcf

Note: If you installed Lely CANopen from source, coctl.dcf might be in a different directory, such as /usr/local/etc.

Initialize the device (input is shown beginning with >, output with <):

> [1] set network 1     # Set the default network-ID, so it can be omitted.
< [1] OK
> [2] set id 1          # Set the node-ID of this device to 1.
< [2] OK
> [3] init 0            # Initialize the device, and configure the CAN bus with
> [3]                   # with a bit rate of 1000 kbit/s.
< coctl: error: unable to set bitrate of vcan0 to 1000000 bit/s
< coctl: NMT: entering reset application state
< coctl: NMT: entering reset communication state
< coctl: NMT: running as master
< coctl: NMT: entering pre-operational state
< coctl: NMT: entering operational state
< [3] OK

The vcan driver does not support changing the bitrate. This is an error you can safely ignore.

The terminal running candump shows two new lines:

vcan0  701   [1]  00
vcan0  000   [2]  82 00

The first line is the boot-up message from our device. Heartbeat and boot-up messages always have CAN-ID 0x700+$NODEID and a single-byte payload with the current state. 00 indicates the device just rebooted.

The second line is there because our device is configured as a CANopen master; it sent the network management (NMT) command (CAN-ID 000) “reset communication” (82) to all nodes (00) the moment it went operational. If other nodes where present, this command would be followed by their boot-up messages.

Adding a slave

Download cmd-slave.dcf, open a terminal in the directory where you stored the file and run

coctl vcan0 ./cmd-slave.dcf

Initialize the slave with node-ID 2:

> [1] set network 1
< [1] OK
> [2] set id 2
< [2] OK
> [3] init 0
< coctl: error: unable to set bitrate of vcan0 to 1000000 bit/s
< coctl: NMT: entering reset application state
< coctl: NMT: entering reset communication state
< coctl: NMT: running as slave
< coctl: NMT: entering pre-operational state
< coctl: NMT: entering operational state
< [3] OK

candump shows a new line with the boot-up message:

vcan0  702   [1]  00

The boot-up message is detected by the master, which you can see by pressing <Enter> in the terminal of the first device:

> [4]
< 1 2 BOOT_UP
< 1 2 USER BOOT A (The CANopen device is not listed in object 1F81.)

Sending heartbeats

Neither the master or the slave are configured to produce heartbeat messages. From the master we can configure the heartbeat producer on the slave (node-ID 2) by using a service data object (SDO) request to write the number of milliseconds (as a 16-bit unsigned integer) to object 1017 sub-index 0 (Producer heartbeat time):

> [4] 2 write 0x1017 0 u16 1000
< [4] OK

The SDO request shows up in the candump output as two lines:

vcan0  602   [8]  2B 17 10 00 E8 03 00 00
vcan0  582   [8]  60 17 10 00 00 00 00 00

The first frame is the expedited SDO download (= write) request from the master. Because the value (0x03e8 = 1000) is smaller than 4 bytes, the entire request fits in a single frame. The second frame is the response from the slave, acknowledging that the value was written to the object dictionary.

From now on, candump shows the following frame every second:

vcan0  702   [1]  05

05 means the slave is in the NMT state “operational”.

We can also read the value back from the master:

> [5] 2 read 0x1017 0 u16
< [5] 0x03e8

candump shows the request and reponse:

vcan0  602   [8]  40 17 10 00 00 00 00 00
vcan0  582   [8]  4B 17 10 00 E8 03 00 00


CANopen nodes are uniquely identified by a combination of four 32-bit numbers: the vendor-ID, product code, revision number and serial number. The identity values are stored in object 1018 sub-index 1-4, respectively:

> [6] 2 r 0x1018 1 u32
< [6] 0x00000360
> [7] 2 r 0x1018 2 u32
< [7] 0x00000000
> [8] 2 r 0x1018 3 u32
< [8] 0x00000000
> [9] 2 r 0x1018 4 u32
< [9] 0x00000000

The vendor-ID is 0x360 (registered by Lely Industries N.V.) and the product code, revision number and serial number are all 0.

Detecting nodes

If we don’t know the node-ID of a slave (or it is unconfigured), we can discover it by using the layer setting services (LSS).

The following (Lely-specific) command scans the network for nodes:

> [10] _lss_fastscan 0 0 0 0 0 0 0 0

The eight zeros are our initial guesses for the four identity values and the masks specifying which bits are already known (in this case none).

For every one of the 128 bits, the master sends a request (CAN-ID 7E5) asking if there is a node which matches the current (masked) value, first trying 0, then 1. It waits for 100 ms for one or more nodes to reply (CAN-ID 7E4). Since most of the bits are zero for our slave, the scan takes a little over 13 seconds.

The result is

< [10] 0x00000360 0x00000000 0x00000000 0x00000000

which matches the values obtained earlier.

There also exists an _lss_slowscan method for devices that don’t support the LSS Fastscan service. It requires the user to provide the vendor-ID and product code, and a range of revision numbers and serial numbers. It then uses a binary search to narrow down those last two numbers.

Changing the node-ID

The node we just identified is now in the “configuration” state and will respond to LSS configuration requests. Any other node in the network will ignore those requests since it is still in the “waiting” state.

We can ask for the node-ID:

> [11] lss_get_node
< [11] 2

and even change it:

> [12] lss_set_node 3
> [12] OK

But this does not yet have any effect:

> [13] lss_get_node
< [13] 2

The new node-ID is still “pending”. First, switch the node back to the “waiting” state:

> [14] lss_switch_glob 0
< [14] OK

Further LSS configuration requests will now fail:

> [15] lss_get_node
> [15] ERROR: 103 (Time-out)

Normally we would first use lss_store to request the node to store the new node-ID to non-volatile memory so it survives a reboot, but the virtual slave created by the CANopen control tool does not support this.

To activate the new node-ID, we issue the NMT “reset communication” command to node 2:

> [16] 2 reset comm
< [16] OK
< 1 3 BOOT_UP
< 1 3 USER BOOT A (The CANopen device is not listed in object 1F81.)

and receive a boot-up message from node 3:

vcan0  000   [2]  82 02
vcan0  703   [1]  00

Note that the slave is no longer sending heartbeat messages. The “reset communication” command caused objects 1000..1FFF, which includes 1017 (Producer heartbeat time), to be reset to their default values.

Sending and receiving PDOs

During normal operation, real-time data transfer between nodes is performed by means of the process data object (PDO) service. The transmission of a PDO can be event-driven or synchronous. In the latter case, the master periodically sends a synchronization object (SYNC) to trigger the transmission, reception and processing of PDOs.

To start transmitting SYNC messages, write the period between messages (in µs) to object 1006 sub-index 0 (Communication cycle period) in the local object dictionary of the master:

> [17] 1 w 0x1006 0 u32 1000000
< [17] OK

From now on, the following message shows up every second:

vcan0  080   [0]

No PDOs are sent after the SYNC, because they have not been configured yet.

Go to the terminal running the CANopen control tool for the slave and configure a Transmit-PDO (TPDO) with

> [4] set tpdo 1 0x183 sync1 1 0x2007 0 u32
< [4] OK

This tells the slave to create the first TPDO (1), with the default CAN-ID (0x180+$NODEID), that is sent on every SYNC (sync1) and contains a single object (1). The object mapped into the PDO is 0x2007 sub-index 0, which is a 32-bit unsigned integer (u32).

Once the TPDO is configured, it is sent after every SYNC:

vcan0  080   [0]
vcan0  183   [4]  00 00 00 00

To see that it really contains the mapped object, change the value of object 2007 in the local object dictionary:

> [5] 3 w 0x2007 0 u32 0x12345678
< [5] OK

Starting with the next SYNC, the new value appears in the payload of the CAN frame (in little-endian):

vcan0  080   [0]
vcan0  183   [4]  78 56 34 12

Although a PDO is now sent on every SYNC, it is not yet received, because no node has configured a corresponding Receive-PDO (RPDO).

Go back to the terminal running the CANopen control tool for the master and configure the first RPDO with

> [17] set rpdo 1 0x183 sync1 1 0x2007 0 u32
< [17] OK

The parameters are the same as for the TPDO on the slave. The CAN-ID and transmission type must be equal, but the object mapping only has to be compatible. However, in this case the master also happens to have an object in the local object dictionary with index 2007, sub-index 0 and type u32, so the mapping is equal as well.

You can check that the master is now receiving the PDOs by pressing <Enter>:

> [18]
< 1 pdo 1 1 0x12345678
< ...

and that it stores the value of the PDO in the local object dictionary:

> [18] 1 r 0x2007 0 u32
< 1 pdo 1 1 0x12345678
< ...
< [18] 0x12345678

That concludes the command-line tutorial.