1.. zephyr:code-sample:: canopennode 2 :name: CANopenNode 3 4 Use the CANopenNode CANopen protocol stack in Zephyr. 5 6Overview 7******** 8This sample application shows how the `CANopenNode`_ CANopen protocol 9stack can be used in Zephyr. 10 11CANopen is an internationally standardized (`EN 50325-4`_, `CiA 301`_) 12communication protocol and device specification for embedded 13systems used in automation. CANopenNode is a 3rd party, open-source 14CANopen protocol stack. 15 16Apart from the CANopen protocol stack integration, this sample also 17demonstrates the use of non-volatile storage for the CANopen object 18dictionary and optionally program download over CANopen. 19 20Requirements 21************ 22 23* A board with CAN bus and flash support 24* Host PC with CAN bus support 25 26Building and Running 27******************** 28 29First, ensure the optional CANopenNode module is enabled and available: 30 31 .. code-block:: console 32 33 west config manifest.project-filter +canopennode 34 west update canopennode 35 36Building and Running for TWR-KE18F 37================================== 38The :zephyr:board:`twr_ke18f` board is equipped with an onboard CAN 39transceiver. This board supports CANopen LED indicators (red and green 40LEDs). The sample can be built and executed for the TWR-KE18F as 41follows: 42 43.. zephyr-app-commands:: 44 :zephyr-app: samples/modules/canopennode 45 :board: twr_ke18f 46 :goals: build flash 47 :compact: 48 49Pressing the button labelled ``SW3`` will increment the button press 50counter object at index ``0x2102`` in the object dictionary. 51 52Building and Running for FRDM-K64F 53================================== 54The :zephyr:board:`frdm_k64f` board does not come with an onboard CAN 55transceiver. In order to use the CAN bus on the FRDM-K64F board, an 56external CAN bus transceiver must be connected to ``PTB18`` 57(``CAN0_TX``) and ``PTB19`` (``CAN0_RX``). This board supports CANopen 58LED indicators (red and green LEDs) 59 60The sample can be built and executed for the FRDM-K64F as follows: 61 62.. zephyr-app-commands:: 63 :zephyr-app: samples/modules/canopennode 64 :board: frdm_k64f 65 :goals: build flash 66 :compact: 67 68Pressing the button labelled ``SW3`` will increment the button press 69counter object at index ``0x2102`` in the object dictionary. 70 71Building and Running for STM32F072RB Discovery 72============================================== 73The :zephyr:board:`stm32f072b_disco` board does not come with an onboard CAN 74transceiver. In order to use the CAN bus on the STM32F072RB Discovery board, an 75external CAN bus transceiver must be connected to ``PB8`` (``CAN_RX``) and 76``PB9`` (``CAN_TX``). This board supports CANopen LED indicators (red and green 77LEDs) 78 79The sample can be built and executed for the STM32F072RB Discovery as follows: 80 81.. zephyr-app-commands:: 82 :zephyr-app: samples/modules/canopennode 83 :board: stm32f072b_disco 84 :goals: build flash 85 :compact: 86 87Pressing the button labelled ``USER`` will increment the button press counter 88object at index ``0x2102`` in the object dictionary. 89 90Building and Running for STM32F3 Discovery 91========================================== 92The :zephyr:board:`stm32f3_disco` board does not come with an onboard CAN 93transceiver. In order to use the CAN bus on the STM32F3 Discovery board, an 94external CAN bus transceiver must be connected to ``PD1`` (``CAN_TX``) and 95``PD0`` (``CAN_RX``). This board supports CANopen LED indicators (red and green 96LEDs) 97 98The sample can be built and executed for the STM32F3 Discovery as follows: 99 100.. zephyr-app-commands:: 101 :zephyr-app: samples/modules/canopennode 102 :board: stm32f3_disco 103 :goals: build flash 104 :compact: 105 106Pressing the button labelled ``USER`` will increment the button press counter 107object at index ``0x2102`` in the object dictionary. 108 109Building and Running for other STM32 boards 110=========================================== 111The sample cannot run if the <erase-block-size> of the flash-controller exceeds 0x10000. 112Typically nucleo_h743zi with erase-block-size = <DT_SIZE_K(128)>; 113 114 115Building and Running for boards without storage partition 116========================================================= 117The sample can be built for boards without a flash storage partition by using a different configuration file: 118 119.. zephyr-app-commands:: 120 :zephyr-app: samples/modules/canopennode 121 :board: <your_board_name> 122 :conf: "prj_no_storage.conf" 123 :goals: build flash 124 :compact: 125 126Testing CANopen Communication 127***************************** 128CANopen communication between the host PC and Zephyr can be 129established using any CANopen compliant application on the host PC. 130The examples here uses `CANopen for Python`_ for communicating between 131the host PC and Zephyr. First, install python-canopen along with the 132python-can backend as follows: 133 134.. code-block:: console 135 136 pip3 install --user canopen python-can 137 138Next, configure python-can to use your CAN adapter through its 139configuration file. On GNU/Linux, the configuration looks similar to 140this: 141 142.. code-block:: console 143 144 cat << EOF > ~/.canrc 145 [default] 146 interface = socketcan 147 channel = can0 148 bitrate = 125000 149 EOF 150 151Please refer to the `python-can`_ documentation for further details 152and instructions. 153 154Finally, bring up the CAN interface on the test PC. On GNU/Linux, this 155can be done as follows: 156 157.. code-block:: console 158 159 sudo ip link set can0 type can bitrate 125000 restart-ms 100 160 sudo ip link set up can0 161 162To better understand the communication taking place in the following 163examples, you can monitor the CAN traffic from the host PC. On 164GNU/Linux, this can be accomplished using ``candump`` from the 165`can-utils`_ package as follows: 166 167.. code-block:: console 168 169 candump can0 170 171NMT State Changes 172================= 173Changing the Network Management (NMT) state of the node can be 174accomplished using the following Python code: 175 176.. code-block:: py 177 178 import canopen 179 import os 180 import time 181 182 ZEPHYR_BASE = os.environ['ZEPHYR_BASE'] 183 EDS = os.path.join(ZEPHYR_BASE, 'samples', 'modules', 'canopennode', 184 'objdict', 'objdict.eds') 185 186 NODEID = 10 187 188 network = canopen.Network() 189 190 network.connect() 191 192 node = network.add_node(NODEID, EDS) 193 194 # Green indicator LED will flash slowly 195 node.nmt.state = 'STOPPED' 196 time.sleep(5) 197 198 # Green indicator LED will flash faster 199 node.nmt.state = 'PRE-OPERATIONAL' 200 time.sleep(5) 201 202 # Green indicator LED will be steady on 203 node.nmt.state = 'OPERATIONAL' 204 time.sleep(5) 205 206 # Node will reset communication 207 node.nmt.state = 'RESET COMMUNICATION' 208 node.nmt.wait_for_heartbeat() 209 210 # Node will reset 211 node.nmt.state = 'RESET' 212 node.nmt.wait_for_heartbeat() 213 214 network.disconnect() 215 216Running the above Python code will update the NMT state of the node 217which is reflected on the indicator LEDs (if present). 218 219SDO Upload 220========== 221Reading a Service Data Object (SDO) at a given index of the CANopen 222object dictionary (here index ``0x1008``, the manufacturer device 223name) can be accomplished using the following Python code: 224 225.. code-block:: py 226 227 import canopen 228 import os 229 230 ZEPHYR_BASE = os.environ['ZEPHYR_BASE'] 231 EDS = os.path.join(ZEPHYR_BASE, 'samples', 'modules', 'canopennode', 232 'objdict', 'objdict.eds') 233 234 NODEID = 10 235 236 network = canopen.Network() 237 238 network.connect() 239 240 node = network.add_node(NODEID, EDS) 241 name = node.sdo['Manufacturer device name'] 242 243 print("Device name: '{}'".format(name.raw)) 244 245 network.disconnect() 246 247Running the above Python code should produce the following output: 248 249.. code-block:: console 250 251 Device name: 'Zephyr RTOS/CANopenNode' 252 253SDO Download 254============ 255Writing to a Service Data Object (SDO) at a given index of the CANopen 256object dictionary (here index ``0x1017``, the producer heartbeat time) 257can be accomplished using the following Python code: 258 259.. code-block:: py 260 261 import canopen 262 import os 263 264 ZEPHYR_BASE = os.environ['ZEPHYR_BASE'] 265 EDS = os.path.join(ZEPHYR_BASE, 'samples', 'modules', 'canopennode', 266 'objdict', 'objdict.eds') 267 268 NODEID = 10 269 270 network = canopen.Network() 271 272 network.connect() 273 274 node = network.add_node(NODEID, EDS) 275 heartbeat = node.sdo['Producer heartbeat time'] 276 reboots = node.sdo['Power-on counter'] 277 278 # Set heartbeat interval without saving to non-volatile storage 279 print("Initial heartbeat time: {} ms".format(heartbeat.raw)) 280 print("Power-on counter: {}".format(reboots.raw)) 281 heartbeat.raw = 5000 282 print("Updated heartbeat time: {} ms".format(heartbeat.raw)) 283 284 # Reset and read heartbeat interval again 285 node.nmt.state = 'RESET' 286 node.nmt.wait_for_heartbeat() 287 print("heartbeat time after reset: {} ms".format(heartbeat.raw)) 288 print("Power-on counter: {}".format(reboots.raw)) 289 290 # Set interval and store it to non-volatile storage 291 heartbeat.raw = 2000 292 print("Updated heartbeat time: {} ms".format(heartbeat.raw)) 293 node.store() 294 295 # Reset and read heartbeat interval again 296 node.nmt.state = 'RESET' 297 node.nmt.wait_for_heartbeat() 298 print("heartbeat time after store and reset: {} ms".format(heartbeat.raw)) 299 print("Power-on counter: {}".format(reboots.raw)) 300 301 # Restore default values, reset and read again 302 node.restore() 303 node.nmt.state = 'RESET' 304 node.nmt.wait_for_heartbeat() 305 print("heartbeat time after restore and reset: {} ms".format(heartbeat.raw)) 306 print("Power-on counter: {}".format(reboots.raw)) 307 308 network.disconnect() 309 310Running the above Python code should produce the following output: 311 312.. code-block:: console 313 314 Initial heartbeat time: 1000 ms 315 Power-on counter: 1 316 Updated heartbeat time: 5000 ms 317 heartbeat time after reset: 1000 ms 318 Power-on counter: 2 319 Updated heartbeat time: 2000 ms 320 heartbeat time after store and reset: 2000 ms 321 Power-on counter: 3 322 heartbeat time after restore and reset: 1000 ms 323 Power-on counter: 4 324 325Note that the power-on counter value may be different. 326 327PDO Mapping 328=========== 329Transmit Process Data Object (PDO) mapping for data at a given index 330of the CANopen object dictionary (here index ``0x2102``, the button 331press counter) can be accomplished using the following Python code: 332 333.. code-block:: py 334 335 import canopen 336 import os 337 338 ZEPHYR_BASE = os.environ['ZEPHYR_BASE'] 339 EDS = os.path.join(ZEPHYR_BASE, 'samples', 'modules', 'canopennode', 340 'objdict', 'objdict.eds') 341 342 NODEID = 10 343 344 network = canopen.Network() 345 346 network.connect() 347 348 node = network.add_node(NODEID, EDS) 349 button = node.sdo['Button press counter'] 350 351 # Read current TPDO mapping 352 node.tpdo.read() 353 354 # Enter pre-operational state to map TPDO 355 node.nmt.state = 'PRE-OPERATIONAL' 356 357 # Map TPDO 1 to transmit the button press counter on changes 358 node.tpdo[1].clear() 359 node.tpdo[1].add_variable('Button press counter') 360 node.tpdo[1].trans_type = 254 361 node.tpdo[1].enabled = True 362 363 # Save TPDO mapping 364 node.tpdo.save() 365 node.nmt.state = 'OPERATIONAL' 366 367 # Reset button press counter 368 button.raw = 0 369 370 print("Press the button 10 times") 371 while True: 372 node.tpdo[1].wait_for_reception() 373 print("Button press counter: {}".format(node.tpdo['Button press counter'].phys)) 374 if node.tpdo['Button press counter'].phys >= 10: 375 break 376 377 network.disconnect() 378 379Running the above Python code should produce the following output: 380 381.. code-block:: console 382 383 Press the button 10 times 384 Button press counter: 0 385 Button press counter: 1 386 Button press counter: 2 387 Button press counter: 3 388 Button press counter: 4 389 Button press counter: 5 390 Button press counter: 6 391 Button press counter: 7 392 Button press counter: 8 393 Button press counter: 9 394 Button press counter: 10 395 396Testing CANopen Program Download 397******************************** 398 399Building and Running for FRDM-K64F 400================================== 401The sample can be rebuilt with MCUboot and program download support 402for the FRDM-K64F as follows: 403 404#. Build the CANopenNode sample with MCUboot support: 405 406 .. zephyr-app-commands:: 407 :tool: west 408 :zephyr-app: samples/modules/canopennode 409 :board: frdm_k64f 410 :goals: build 411 :west-args: --sysbuild 412 :gen-args: -Dcanopennode_CONF_FILE=prj_img_mgmt.conf -DSB_CONFIG_BOOTLOADER_MCUBOOT=y 413 :compact: 414 415#. Flash the newly built MCUboot and CANopen sample binaries using west: 416 417 .. code-block:: console 418 419 west flash --skip-rebuild 420 421#. Confirm the newly flashed firmware image using west: 422 423 .. code-block:: console 424 425 west flash --skip-rebuild --domain canopennode --runner canopen --confirm-only 426 427#. Finally, perform a program download via CANopen: 428 429 .. code-block:: console 430 431 west flash --skip-rebuild --domain canopennode --runner canopen 432 433Modifying the Object Dictionary 434******************************* 435The CANopen object dictionary used in this sample application can be 436found under :zephyr_file:`samples/modules/canopennode/objdict` in 437the Zephyr tree. The object dictionary can be modified using any 438object dictionary editor supporting CANopenNode object dictionary code 439generation. 440 441A popular choice is the EDS editor from the `libedssharp`_ 442project. With that, the 443:zephyr_file:`samples/modules/canopennode/objdict/objdict.xml` 444project file can be opened and modified, and new implementation files 445(:zephyr_file:`samples/modules/canopennode/objdict/CO_OD.h` and 446:zephyr_file:`samples/modules/canopennode/objdict/CO_OD.c`) can be 447generated. The EDS editor can also export an updated Electronic Data 448Sheet (EDS) file 449(:zephyr_file:`samples/modules/canopennode/objdict/objdict.eds`). 450 451.. _CANopenNode: 452 https://github.com/CANopenNode/CANopenNode 453 454.. _EN 50325-4: 455 https://can-cia.org/cia-groups/international-standardization/ 456 457.. _CiA 301: 458 https://can-cia.org/cia-groups/technical-documents/ 459 460.. _CANopen for Python: 461 https://github.com/christiansandberg/canopen 462 463.. _python-can: 464 https://python-can.readthedocs.io/ 465 466.. _can-utils: 467 https://github.com/linux-can/can-utils 468 469.. _libedssharp: 470 https://github.com/robincornelius/libedssharp 471