Add sample demonstrating the integration of CANopenNode in Zephyr to support the CANopen protocol. This fixes #15278. Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
353 lines
10 KiB
ReStructuredText
353 lines
10 KiB
ReStructuredText
.. _canopen-sample:
|
|
|
|
CANopen
|
|
#######
|
|
|
|
Overview
|
|
********
|
|
This sample application shows how the `CANopenNode`_ CANopen protocol
|
|
stack can be used in Zephyr.
|
|
|
|
CANopen is an internationally standardized (`EN 50325-4`_, `CiA 301`_)
|
|
communication protocol and device specification for embedded
|
|
systems used in automation. CANopenNode is a 3rd party, open-source
|
|
CANopen protocol stack.
|
|
|
|
Apart from the CANopen protocol stack integration, this sample also
|
|
demonstrates the use of non-volatile storage for the CANopen object
|
|
dictionary.
|
|
|
|
Requirements
|
|
************
|
|
|
|
* A board with CAN bus and flash support
|
|
* Host PC with CAN bus support
|
|
|
|
Building and Running
|
|
********************
|
|
|
|
Building and Running for TWR-KE18F
|
|
==================================
|
|
The :ref:`twr_ke18f` board is equipped with an onboard CAN
|
|
transceiver. This board supports CANopen LED indicators (red and green
|
|
LEDs). The sample can be built and executed for the TWR-KE18F as
|
|
follows:
|
|
|
|
.. zephyr-app-commands::
|
|
:zephyr-app: samples/subsys/canbus/canopen
|
|
:board: twr_ke18f
|
|
:goals: build flash
|
|
:compact:
|
|
|
|
Pressing the button labelled ``SW3`` will increment the button press
|
|
counter object at index ``0x2102`` in the object dictionary.
|
|
|
|
Building and Running for FRDM-K64F
|
|
==================================
|
|
The :ref:`frdm_k64f` board does not come with an onboard CAN
|
|
transceiver. In order to use the CAN bus on the FRDM-K64F board, an
|
|
external CAN bus tranceiver must be connected to ``PTB18``
|
|
(``CAN0_TX``) and ``PTB19`` (``CAN0_RX``). This board supports CANopen
|
|
LED indicators (red and green LEDs)
|
|
|
|
The sample can be built and executed for the FRDM-K64F as follows:
|
|
|
|
.. zephyr-app-commands::
|
|
:zephyr-app: samples/subsys/canbus/canopen
|
|
:board: frdm_k64f
|
|
:goals: build flash
|
|
:compact:
|
|
|
|
Pressing the button labelled ``SW3`` will increment the button press
|
|
counter object at index ``0x2102`` in the object dictionary.
|
|
|
|
Testing CANopen Communication
|
|
*****************************
|
|
CANopen communication between the host PC and Zephyr can be
|
|
established using any CANopen compliant application on the host PC.
|
|
The examples here uses `CANopen for Python`_ for communicating between
|
|
the host PC and Zephyr. First, install python-canopen along with the
|
|
python-can backend as follows:
|
|
|
|
.. code-block:: console
|
|
|
|
pip3 install --user canopen can
|
|
|
|
Next, bring up the CAN interface on the test PC. On GNU/Linux, this
|
|
can be done as follows:
|
|
|
|
.. code-block:: console
|
|
|
|
sudo ip link set can0 type can bitrate 125000 restart-ms 100
|
|
sudo ip link set up can0
|
|
|
|
|
|
To better understand the communication taking place in the following
|
|
examples, you can monitor the CAN traffic from the host PC. On
|
|
GNU/Linux, this can be accomplished using ``candump`` from the
|
|
`can-utils`_ package as follows:
|
|
|
|
.. code-block:: console
|
|
|
|
candump can0
|
|
|
|
NMT State Changes
|
|
=================
|
|
Changing the Network Management (NMT) state of the node can be
|
|
accomplished using the following Python code:
|
|
|
|
.. code-block:: py
|
|
|
|
import canopen
|
|
import os
|
|
import time
|
|
|
|
ZEPHYR_BASE = os.environ['ZEPHYR_BASE']
|
|
EDS = os.path.join(ZEPHYR_BASE, 'samples', 'subsys', 'canbus', 'canopen',
|
|
'objdict', 'objdict.eds')
|
|
NODEID = 10
|
|
|
|
network = canopen.Network()
|
|
|
|
# 'can0' and 'socketcan' is for GNU/Linux, please see the python-can documentation for other platforms:
|
|
network.connect(channel='can0', bustype='socketcan')
|
|
|
|
node = network.add_node(NODEID, EDS)
|
|
|
|
# Green indicator LED will flash slowly
|
|
node.nmt.state = 'STOPPED'
|
|
time.sleep(5)
|
|
|
|
# Green indicator LED will flash faster
|
|
node.nmt.state = 'PRE-OPERATIONAL'
|
|
time.sleep(5)
|
|
|
|
# Green indicator LED will be steady on
|
|
node.nmt.state = 'OPERATIONAL'
|
|
time.sleep(5)
|
|
|
|
# Node will reset communication
|
|
node.nmt.state = 'RESET COMMUNICATION'
|
|
node.nmt.wait_for_heartbeat()
|
|
|
|
# Node will reset
|
|
node.nmt.state = 'RESET'
|
|
node.nmt.wait_for_heartbeat()
|
|
|
|
network.disconnect()
|
|
|
|
Running the above Python code will update the NMT state of the node
|
|
which is reflected on the indicator LEDs (if present).
|
|
|
|
SDO Upload
|
|
==========
|
|
Reading a Service Data Object (SDO) at a given index of the CANopen
|
|
object dictionary (here index ``0x1008``, the manufacturer device
|
|
name) can be accomplished using the following Python code:
|
|
|
|
.. code-block:: py
|
|
|
|
import canopen
|
|
import os
|
|
|
|
ZEPHYR_BASE = os.environ['ZEPHYR_BASE']
|
|
EDS = os.path.join(ZEPHYR_BASE, 'samples', 'subsys', 'canbus', 'canopen',
|
|
'objdict', 'objdict.eds')
|
|
NODEID = 10
|
|
|
|
network = canopen.Network()
|
|
|
|
# 'can0' and 'socketcan' is for GNU/Linux, please see the python-can documentation for other platforms:
|
|
network.connect(channel='can0', bustype='socketcan')
|
|
|
|
node = network.add_node(NODEID, EDS)
|
|
name = node.sdo['Manufacturer device name']
|
|
|
|
print("Device name: '{}'".format(name.raw))
|
|
|
|
network.disconnect()
|
|
|
|
Running the above Python code should produce the following output:
|
|
|
|
.. code-block:: console
|
|
|
|
Device name: 'Zephyr RTOS/CANopenNode'
|
|
|
|
SDO Download
|
|
============
|
|
Writing to a Service Data Object (SDO) at a given index of the CANopen
|
|
object dictionary (here index ``0x1017``, the producer heartbeat time)
|
|
can be accomplished using the following Python code:
|
|
|
|
.. code-block:: py
|
|
|
|
import canopen
|
|
import os
|
|
|
|
ZEPHYR_BASE = os.environ['ZEPHYR_BASE']
|
|
EDS = os.path.join(ZEPHYR_BASE, 'samples', 'subsys', 'canbus', 'canopen',
|
|
'objdict', 'objdict.eds')
|
|
NODEID = 10
|
|
|
|
network = canopen.Network()
|
|
|
|
# 'can0' and 'socketcan' is for GNU/Linux, please see the python-can documentation for other platforms:
|
|
network.connect(channel='can0', bustype='socketcan')
|
|
|
|
node = network.add_node(NODEID, EDS)
|
|
heartbeat = node.sdo['Producer heartbeat time']
|
|
reboots = node.sdo['Power-on counter']
|
|
|
|
# Set heartbeat interval without saving to non-volatile storage
|
|
print("Initial heartbeat time: {} ms".format(heartbeat.raw))
|
|
print("Power-on counter: {}".format(reboots.raw))
|
|
heartbeat.raw = 5000
|
|
print("Updated heartbeat time: {} ms".format(heartbeat.raw))
|
|
|
|
# Reset and read heartbeat interval again
|
|
node.nmt.state = 'RESET'
|
|
node.nmt.wait_for_heartbeat()
|
|
print("heartbeat time after reset: {} ms".format(heartbeat.raw))
|
|
print("Power-on counter: {}".format(reboots.raw))
|
|
|
|
# Set interval and store it to non-volatile storage
|
|
heartbeat.raw = 2000
|
|
print("Updated heartbeat time: {} ms".format(heartbeat.raw))
|
|
node.store()
|
|
|
|
# Reset and read heartbeat interval again
|
|
node.nmt.state = 'RESET'
|
|
node.nmt.wait_for_heartbeat()
|
|
print("heartbeat time after store and reset: {} ms".format(heartbeat.raw))
|
|
print("Power-on counter: {}".format(reboots.raw))
|
|
|
|
# Restore default values, reset and read again
|
|
node.restore()
|
|
node.nmt.state = 'RESET'
|
|
node.nmt.wait_for_heartbeat()
|
|
print("heartbeat time after restore and reset: {} ms".format(heartbeat.raw))
|
|
print("Power-on counter: {}".format(reboots.raw))
|
|
|
|
network.disconnect()
|
|
|
|
Running the above Python code should produce the following output:
|
|
|
|
.. code-block:: console
|
|
|
|
Initial heartbeat time: 1000 ms
|
|
Power-on counter: 1
|
|
Updated heartbeat time: 5000 ms
|
|
heartbeat time after reset: 1000 ms
|
|
Power-on counter: 2
|
|
Updated heartbeat time: 2000 ms
|
|
heartbeat time after store and reset: 2000 ms
|
|
Power-on counter: 3
|
|
heartbeat time after restore and reset: 1000 ms
|
|
Power-on counter: 4
|
|
|
|
Note that the power-on counter value may be different.
|
|
|
|
PDO Mapping
|
|
===========
|
|
Transmit Process Data Object (PDO) mapping for data at a given index
|
|
of the CANopen object dictionary (here index ``0x2102``, the button
|
|
press counter) can be accomplished using the following Python code:
|
|
|
|
.. code-block:: py
|
|
|
|
import canopen
|
|
import os
|
|
|
|
ZEPHYR_BASE = os.environ['ZEPHYR_BASE']
|
|
EDS = os.path.join(ZEPHYR_BASE, 'samples', 'subsys', 'canbus', 'canopen',
|
|
'objdict', 'objdict.eds')
|
|
NODEID = 10
|
|
|
|
network = canopen.Network()
|
|
|
|
# 'can0' and 'socketcan' is for GNU/Linux, please see the python-can documentation for other platforms:
|
|
network.connect(channel='can0', bustype='socketcan')
|
|
|
|
node = network.add_node(NODEID, EDS)
|
|
button = node.sdo['Button press counter']
|
|
|
|
# Read current TPDO mapping
|
|
node.tpdo.read()
|
|
|
|
# Enter pre-operational state to map TPDO
|
|
node.nmt.state = 'PRE-OPERATIONAL'
|
|
|
|
# Map TPDO 1 to transmit the button press counter on changes
|
|
node.tpdo[1].clear()
|
|
node.tpdo[1].add_variable('Button press counter')
|
|
node.tpdo[1].trans_type = 254
|
|
node.tpdo[1].enabled = True
|
|
|
|
# Save TPDO mapping
|
|
node.tpdo.save()
|
|
node.nmt.state = 'OPERATIONAL'
|
|
|
|
# Reset button press counter
|
|
button.raw = 0
|
|
|
|
print("Press the button 10 times")
|
|
while True:
|
|
node.tpdo[1].wait_for_reception()
|
|
print("Button press counter: {}".format(node.tpdo['Button press counter'].phys))
|
|
if node.tpdo['Button press counter'].phys >= 10:
|
|
break
|
|
|
|
network.disconnect()
|
|
|
|
Running the above Python code should produce the following output:
|
|
|
|
.. code-block:: console
|
|
|
|
Press the button 10 times
|
|
Button press counter: 0
|
|
Button press counter: 1
|
|
Button press counter: 2
|
|
Button press counter: 3
|
|
Button press counter: 4
|
|
Button press counter: 5
|
|
Button press counter: 6
|
|
Button press counter: 7
|
|
Button press counter: 8
|
|
Button press counter: 9
|
|
Button press counter: 10
|
|
|
|
Modifying the Object Dictionary
|
|
*******************************
|
|
The CANopen object dictionary used in this sample application can be
|
|
found under :zephyr_file:`samples/subsys/canbus/canopen/objdict` in
|
|
the Zephyr tree. The object dictionary can be modified using any
|
|
object dictionary editor supporting CANopenNode object dictionary code
|
|
generation.
|
|
|
|
A popular choice is the EDS editor from the `libedssharp`_
|
|
project. With that, the
|
|
:zephyr_file:`samples/subsys/canbus/canopen/objdict/objdicts.xml`
|
|
project file can be opened and modified, and new implementation files
|
|
(:zephyr_file:`samples/subsys/canbus/canopen/objdict/CO_OD.h` and
|
|
:zephyr_file:`samples/subsys/canbus/canopen/objdict/CO_OD.c`) can be
|
|
generated. The EDS editor can also export an updated Electronic Data
|
|
Sheet (EDS) file
|
|
(:zephyr_file:`samples/subsys/canbus/canopen/objdict/objdicts.eds`).
|
|
|
|
.. _CANopenNode:
|
|
https://github.com/CANopenNode/CANopenNode
|
|
|
|
.. _EN 50325-4:
|
|
https://can-cia.org/groups/international-standardization/
|
|
|
|
.. _CiA 301:
|
|
https://can-cia.org/groups/specifications/
|
|
|
|
.. _CANopen for Python:
|
|
https://github.com/christiansandberg/canopen
|
|
|
|
.. _can-utils:
|
|
https://github.com/linux-can/can-utils
|
|
|
|
.. _libedssharp:
|
|
https://github.com/robincornelius/libedssharp
|