| .. _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 | 
 |       :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/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 |