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
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 :ref:`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 :ref:`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 :ref:`stm32f072b_disco_board` 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 :ref:`stm32f3_disco_board` 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 :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