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