zephyr/samples/subsys/canbus/canopen
Kumar Gala 2d7cc8f774 samples: subsys: canbus: canopen: Move dts overlays under boards
Move the dts overlay under a boards/ dir to make it match conf files.

Signed-off-by: Kumar Gala <kumar.gala@linaro.org>
2020-04-01 10:44:31 -04:00
..
boards samples: subsys: canbus: canopen: Move dts overlays under boards 2020-04-01 10:44:31 -04:00
objdict
src kernel: deprecate k_uptime_delta_32 2020-03-23 10:26:05 -04:00
CMakeLists.txt cmake: use find_package to locate Zephyr 2020-03-27 16:23:46 +01:00
Kconfig
README.rst samples: canbus: canopen: fix python package install instructions 2020-02-11 17:49:20 +02:00
prj.conf
sample.yaml

README.rst

.. _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 python-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