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