.. _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 ******************** First, ensure the optional CANopenNode module is enabled and available: .. code-block:: console west config manifest.project-filter +canopennode west update canopennode 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 of the flash-controller exceeds 0x10000. Typically nucleo_h743zi with erase-block-size = ; 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: :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 :gen-args: -Dcanopennode_CONF_FILE=prj_img_mgmt.conf :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/objdict.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/objdict.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