zephyr/samples/modules/canopennode
Torsten Rasmussen 1cccc8a8fe cmake: increase minimal required version to 3.20.0
Move to CMake 3.20.0.

At the Toolchain WG it was decided to move to CMake 3.20.0.

The main reason for increasing CMake version is better toolchain
support.

Better toolchain support is added in the following CMake versions:
- armclang, CMake 3.15
- Intel oneAPI, CMake 3.20
- IAR, CMake 3.15 and 3.20

Signed-off-by: Torsten Rasmussen <Torsten.Rasmussen@nordicsemi.no>
2021-08-20 09:47:34 +02:00
..
boards
objdict
src
CMakeLists.txt cmake: increase minimal required version to 3.20.0 2021-08-20 09:47:34 +02:00
Kconfig
prj_no_storage.conf
prj.conf
README.rst
sample.yaml

.. _canopennode-sample:

CANopenNode
###########

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 and optionally program download over CANopen.

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.

Building and Running for boards without storage partition
=========================================================
The sample can be built for boards without a flash storage partition by using a different configuration file:

.. zephyr-app-commands::
   :zephyr-app: samples/subsys/canbus/canopen
   :board: <your_board_name>
   :conf: "prj_no_storage.conf"
   :goals: build flash
   :compact:

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 python-can

Next, configure python-can to use your CAN adapter through its
configuration file. On GNU/Linux, the configuration looks similar to
this:

.. code-block:: console

   cat << EOF > ~/.canrc
   [default]
   interface = socketcan
   channel = can0
   bitrate = 125000
   EOF

Please refer to the `python-can`_ documentation for further details
and instructions.

Finally, 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()

   network.connect()

   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()

   network.connect()

   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()

   network.connect()

   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()

   network.connect()

   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

Testing CANopen Program Download
********************************

Building and Running for FRDM-K64F
==================================
The sample can be rebuilt with MCUboot and program download support
for the FRDM-K64F as follows:

#. Build and flash MCUboot by following the instructions in the
   :ref:`mcuboot` documentation page.

#. Rebuild the CANopen sample with MCUboot support:

   .. zephyr-app-commands::
      :zephyr-app: samples/subsys/canbus/canopen
      :board: frdm_k64f
      :goals: build
      :gen-args: -DCONFIG_BOOTLOADER_MCUBOOT=y
      :compact:

#. Sign the newly rebuilt CANopen sample binary (using either the
   demonstration-only RSA key from MCUboot or any other key used when
   building MCUboot itself):

   .. code-block:: console

      west sign -t imgtool --bin --no-hex -- --key mcuboot/root-rsa-2048.pem \
              --version 1.0.0

#. Flash the newly signed CANopen sample binary using west:

   .. code-block:: console

      west flash --skip-rebuild --bin-file zephyr/zephyr.signed.bin

#. Confirm the newly flashed firmware image using west:

   .. code-block:: console

      west flash --skip-rebuild --runner canopen --confirm-only

#. Finally, resign the CANopen sample binary with a new version number
   and perform a program download over CANopen:

   .. code-block:: console

      west sign -t imgtool --bin --no-hex  -- --key mcuboot/root-rsa-2048.pem \
              --version 1.0.1
      west flash --skip-rebuild --bin-file zephyr/zephyr.signed.bin \
              --runner canopen

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

.. _python-can:
   https://python-can.readthedocs.io/

.. _can-utils:
   https://github.com/linux-can/can-utils

.. _libedssharp:
   https://github.com/robincornelius/libedssharp