zephyr/samples/modules/canopennode
Henrik Brix Andersen 55d0ffa874 drivers: can: remove CAN_HAS_CANFD Kconfig helper
Remove the CAN_HAS_CANFD Kconfig helper symbol in order to allow enabling
CAN-FD support in the API regardless of driver support.

Change default to CAN-FD support being disabled and have samples and tests
that require CAN-FD support turn it on. This aligns the default
configuration across CAN controller drivers regardless of their
capabilities.

The rationale behind this is that we are starting to see MCUs with multiple
CAN controllers, some CAN-FD compatible, some not (e.g. NXP i.MX RT1060 and
FPGAs). Automatically enabling CAN-FD support based on the presence of a
CAN-FD capable CAN controller leads to different application default
settings based on the CAN controller(s) in use.

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
2023-01-25 15:00:39 +00:00
..
boards
objdict
src samples: canopennode: Fix typo 2023-01-04 10:34:06 +01:00
CMakeLists.txt samples: modules: canopennode: convert to sysbuild 2023-01-13 14:05:40 +01:00
Kconfig
prj_no_storage.conf drivers: can: remove CAN_HAS_CANFD Kconfig helper 2023-01-25 15:00:39 +00:00
prj.conf drivers: can: remove CAN_HAS_CANFD Kconfig helper 2023-01-25 15:00:39 +00:00
README.rst samples: modules: canopennode: convert to sysbuild 2023-01-13 14:05:40 +01:00
sample.yaml samples: modules: canopennode: convert to sysbuild 2023-01-13 14:05:40 +01:00
sysbuild.conf samples: modules: canopennode: convert to sysbuild 2023-01-13 14:05:40 +01:00

.. _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/modules/canopennode
   :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 transceiver 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/modules/canopennode
   :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 STM32F072RB Discovery
==============================================
The :ref:`stm32f072b_disco_board` board does not come with an onboard CAN
transceiver. In order to use the CAN bus on the STM32F072RB Discovery board, an
external CAN bus transceiver must be connected to ``PB8`` (``CAN_RX``) and
``PB9`` (``CAN_TX``). This board supports CANopen LED indicators (red and green
LEDs)

The sample can be built and executed for the STM32F072RB Discovery as follows:

.. zephyr-app-commands::
   :zephyr-app: samples/modules/canopennode
   :board: stm32f072b_disco
   :goals: build flash
   :compact:

Pressing the button labelled ``USER`` will increment the button press counter
object at index ``0x2102`` in the object dictionary.

Building and Running for STM32F3 Discovery
==========================================
The :ref:`stm32f3_disco_board` board does not come with an onboard CAN
transceiver. In order to use the CAN bus on the STM32F3 Discovery board, an
external CAN bus transceiver must be connected to ``PD1`` (``CAN_TX``) and
``PD0`` (``CAN_RX``). This board supports CANopen LED indicators (red and green
LEDs)

The sample can be built and executed for the STM32F3 Discovery as follows:

.. zephyr-app-commands::
   :zephyr-app: samples/modules/canopennode
   :board: stm32f3_disco
   :goals: build flash
   :compact:

Pressing the button labelled ``USER`` will increment the button press counter
object at index ``0x2102`` in the object dictionary.

Building and Running for other STM32 boards
===========================================
The sample cannot run if the <erase-block-size> of the flash-controller exceeds 0x10000.
Typically nucleo_h743zi with erase-block-size = <DT_SIZE_K(128)>;


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/modules/canopennode
   :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', 'modules', 'canopennode',
                   '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', 'modules', 'canopennode',
                   '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', 'modules', 'canopennode',
                   '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', 'modules', 'canopennode',
                   '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 the CANopenNode sample with MCUboot support:

   .. zephyr-app-commands::
      :tool: west
      :app: samples/modules/canopennode
      :board: frdm_k64f
      :goals: build
      :west-args: --sysbuild
      :compact:

#. Flash the newly built MCUboot and CANopen sample binaries using west:

   .. code-block:: console

      west flash --skip-rebuild

#. Confirm the newly flashed firmware image using west:

   .. code-block:: console

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

#. Finally, perform a program download via CANopen:

   .. code-block:: console

      west flash --skip-rebuild --domain canopennode --runner canopen

Modifying the Object Dictionary
*******************************
The CANopen object dictionary used in this sample application can be
found under :zephyr_file:`samples/modules/canopennode/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/modules/canopennode/objdict/objdicts.xml`
project file can be opened and modified, and new implementation files
(:zephyr_file:`samples/modules/canopennode/objdict/CO_OD.h` and
:zephyr_file:`samples/modules/canopennode/objdict/CO_OD.c`) can be
generated. The EDS editor can also export an updated Electronic Data
Sheet (EDS) file
(:zephyr_file:`samples/modules/canopennode/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