Skip to content

Latest commit

 

History

History
358 lines (275 loc) · 20 KB

README.md

File metadata and controls

358 lines (275 loc) · 20 KB

CANopen basicDevice Example

Example of basic CANopen device, with quite rich functionality. It is based on CANopenNode, which is free and open source CANopen Stack, based on (CiA301) standard.

Example contains object dictionary with most common communication parameters and some additional manufacturer specific and device profile parameters. See picture below or complete OD documentation in basicDevice.md. Note also project file basicDevice.xdd, which can be opened and edited with EDSEditor.exe (Linux or Windows application). EDSEditor can export other files, including OD.h and OD.c source files for this example. basicDevice.xdd and basicDevice.eds are standard CANopen device description files and can be opened also with other CANopen tools.

EDSEditor

Example program

Makefile

It includes most common CANopen files and options for compilation.

It also generates CO_version.h file with information about git version number of CANopenNode and CANopenSocket, for example:

#define CO_VERSION_CANOPENNODE "v4.0-12-g09bf926-dirty"
#define CO_VERSION_APPLICATION "v1.2-20-g94add15-dirty"

This information is available from OD entry 0x2105, 01:

./cocomm "4 r 0x2105 1 vs"
[1] "v4.0-12-g09bf926-dirty"

"v4.0" is latest tag name, "12" is number of commits above the tag, "g" is git, "09bf926" is commit number and optional "dirty" means, git repository has uncommited changes.

Makefile also defines macro CO_DRIVER_CUSTOM, which includes CO_driver_custom.h into the foundation of all source files.

CANopenNode program flow

CANopenNode driver for Linux is written in a way, that program execution is triggered by different events, like reception of CAN message, one of the many timers expiration, other file descriptor based events, etc. All CANopenNode functions are non-blocking. After event occurs, all mainline and/or realtime functions are processed. In simpler, microcontroller based applications, mainline functions are executing in infinite loop and realtime functions are executed precisely each timer interval, 1ms typically.

CANopenNode driver for Linux offers two options, configurable with makefile:

  1. Whole program runs in single thread. This is default for canopend. This uses less system resources.
  2. Mainline functions are executing in primary thread and realtime functions are executed in own thread, triggered by precise timer. This way it is easier to achieve realtime operation and may be easier to implement a controller with access to peripherals, like digital input/output pins. This is default for basicDevice.

CO_application.h and CO_application.c

Those files contains five functions, which are called from main() function inside CANopenNode/socketCAN/CO_main_basic.c file. They are central part of our basicDevice application program:

CO_ReturnError_t app_programStart(bool_t CANopenConfigured, uint16_t *errinfo);
void app_communicationReset(bool_t CANopenConfigured);
void app_programEnd();
void app_programAsync(bool_t CANopenConfigured, uint32_t timer1usDiff);
void app_program1ms(bool_t CANopenConfigured, uint32_t timer1usDiff);

testingVariables.h and testingVariables.c

Those files contains example usage of OD entry "Testing variables" with multiple different sub entries.

Entry has IO extension enabled. This means that some entries are accessed with custom read/write functions. See source files and examples below for details.

Testing with Linux

You should successfully finish CANopenNode/doc/gettingStarted.md, before continuing with this example. Here more advanced and more usable command line application, cocomm, will be used.

Some background about communication paths, when using cocomm

cocomm is a small command line program, which establishes socket connection with canopend. It sends standardized CANopen commands (CiA309-3) to gateway and prints the responses to stdout and stderr.

  1. canopend serves a socket connection on /tmp/CO_command_socket address. This is local Unix socket, TCP socket can be used also. canopend is pure CANopen device with commander functionalities and gateway. It listens for socket connections.
  2. When run, cocomm tries to connect to /tmp/CO_command_socket address (this is default setting). canopend accepts the connection.
  3. cocomm writes the specified ascii command to established socket connection, for example [1] 4 read 0x1017 0 u16.
  4. Gateway in canopend receives the command and decodes it. read commands goes internally into CO_SDOclientUploadInitiate() and then command is processed with multiple CO_SDOclientUpload() function calls.
  5. CO_SDOclientUpload() now sends a CAN message to our basicDevice. (CAN interface in Linux is implemented with CAN sockets. This is the third type of sockets mentioned here.) However, in our example basicDevice receives SDO request, asking the value of the variable, located in Object Dictionary at index 0x1017, subindex 0.
  6. Our basicDevice receives CAN message with CAN ID=0x604. It determines SDO request, so CO_SDOserver_process() function gets the message. Function gets the value from internal Object Dictionary and sends the CAN response with CAN ID=0x584. Those messages can be seen in candump terminal. And it is not necessary to understand the details of SDO communication, it may be quite complex.
  7. canopend receives the CAN message, CO_SDOclientUpload() decodes it and sends binary value to gateway.
  8. Gateway in canopend translates binary value to asciiValue, unsigned16 in our example. It prepares the response, in our case [1] + asciiValue + \r\n. Then writes the response text back to /tmp/CO_command_socket.
  9. cocomm reads the response text from local socket and prints it partly to stderr ([1] \r\n) and partly to stdout (asciiValue).
  10. If there are more commands, step 3 is repeated. Otherwise cocomm closes the socket connection and exits.

Testing variables

Prepare the environment

We will use four Linux terminals:

# First terminal: candump
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0
candump -td -a vcan0

# second terminal: basicDevice
cd examples/basicDevice
make
./basicDevice -i4 vcan0

# third terminal: canopend (gateway)
cd CANopenNode
make
./canopend vcan0 -i1 -c "local-/tmp/CO_command_socket"

# fourth terminal: cocomm (connection with gateway)
cd cocomm
make

basic cocomm usage

It is very similar as in CANopenNode/doc/gettingStarted.md, but actually easier.

./cocomm --help
./cocomm "help"
./cocomm "help datatype"
./cocomm "4 r 0x1017 0 u16"
./cocomm "1 r 0x1017 0 u16" "4 r 0x1017 0 u16"

cocomm program can be copied to standard location of executables, so we can run it from any directory, without leading ./:

sudo cp cocomm /usr/bin/cocomm

Parameters to program can be set by program arguments, as described in cocomm --help, and can also be changed by environmental variables. For example, to change default socket path in for all next cocomm commands in current terminal, type:

export cocomm_socket="some other path than /tmp/CO_command_socket"

Commands can be also written into a file, for example create a commands.txt file, and for its content enter the commands:

[1] 1 start
[2] 4 start

Then make cocomm use that file:

$ cocomm -f commands.txt
[1] OK
[2] OK

Advanced variables and strings

Type commands and see responses in cocomm terminal. Optionally watch CAN communication in candump terminal.

./cocomm "4 r 0x2120 1 i64" "4 r 0x2120 2 x64" "4 r 0x2120 2 u64" "4 r 0x2120 2"
[1] -1234567890123456789
[2] 0x1234567890ABCDEF
[3] 1311768467294899695
[4] EF CD AB 90 78 56 34 12

./cocomm "4 r 0x2120 3 r32" "4 r 0x2120 4 r64"
[1] 12.345
[2] 456.789

./cocomm "4 r 0x2120 5 r64 # Custom read function calculates the average."
[1] 1.93001e+16

./cocomm "4 w 0x2120 3 r32 -7.720058e+16" "4 r 0x2120 5 r64"
[1] OK
[2] 7.8681e+07

./cocomm "4 w 0x2120 6 vs 12" "4 r 0x2120 6 vs" \
         "4 w 0x2120 6 vs 1234" "4 r 0x2120 6 vs" \
         "4 w 0x2120 6 vs \"1 2\"" "4 r 0x2120 6 vs"
[1] OK
[2] "12"
[3] ERROR:0x06070012 #Data type does not match, length of service parameter too high.
[4] "12"
[5] OK
[6] "1 2"

./cocomm "4 r 0x2120 7 vs"
[1] "1000 bytes long string buffer. Visible string may contain UTF-8 characters, like this 3 bytes long '€' Euro symbol. It may contain also control characters like tabs '	' or newLines '
'."

./cocomm "4 w 0x2120 7 vs \"Writing newLines is not possible as visible string, but exotic \"\"ß\"\" characters works.\""
[1] OK

./cocomm "4 r 0x2120 7 os" | base64 -d
[1]
Writing newLines is not possible as visible string, but exotic "ß" characters works.

./cocomm "4 r 0x2120 7 os" | base64 -d | hexdump -C
[1]...

echo -ne "We can encode anything to base64\n\nand transfer data as octet string or domain." | base64 -w0 | ./cocomm -i "4 w 0x2120 7 os"
[1] OK

./cocomm "4 r 0x2120 8 hex" "4 r 0x2120 8 os" "4 r 0x2120 8 vs" "4 r 0x2120 8 d" "4 r 0x2120 8"
[1] C8 3D BB 00 00 00 00 00 00 00
[2] yD27AAAAAAAAAA==
[3] "�=�"
[4] yD27AAAAAAAAAA==
[5] C8 3D BB 00 00 00 00 00 00 00

./cocomm "4 w 0x2120 8 hex 01 02 03"
[1] ERROR:0x06070013 #Data type does not match, length of service parameter too short.

./cocomm "4 w 0x2120 8 hex 01 2 30 456789 0A b0 0 ff"
[1] OK

Parameter with default value

Value is read from Object Dictionary on program initialization and stored to internal variable. Each SDO read will read this internal variable, incremented by 1, but value in OD location will stay unchanged. SDO write will write to internal variable and to OD location. And subsequent SDO write to index 0x1010 (store parameters) will update the default value, valid at startup. Experiment with cocomm and see testingVariables.c file for implementation of this behaviour.

Domain

Domain enables transfer an arbitrary large block of data in one SDO communication cycle. In our example size is limited only by free space of file system, if we have a 64-bit computer. RAM usage is low. SDO block transfer is used for highest efficiency over CAN. In our example internal virtual CAN network is used, which is very fast. On real CAN networks data transfer rates are significantly lower. However, SDO communication does not disturb higher priority real time data transfer, which is likely present on CAN bus. SDO block transfer is also resistant to disturbances to some degree. If necessary, it re-transmits corrupted data.

See testingVariables.c file for implementation of large data transfer. Our basicDevice reads data from file, referenced by Object 0x2120, sub index 11. This is a text file in the same directory as our basicDevice. We can safely read the file as visible string. If necessary, transfer can be interrupted by Ctrl+C. SDO communication will be closed correctly by abort message.

./cocomm "set sdo_block 1"
[1] OK

./cocomm "4 r 0x2120 11 vs"
[1] "basicDevice.md"

./cocomm "4 r 0x2120 10 vs"
[1] "CANopen documentation
=====================
**Basic device**
....

Above works, but is not very practical. This is better:

./cocomm "4 r 0x2120 10 d" | base64 -d | pv > file1
[1]
...success
56,7KiB 0:00:00 [ 600KiB/s] [<=>                                      ]

cmp file1 ../examples/basicDevice/basicDevice.md
rm file1

If there is no response from cmp command, files are identical. pv is nice Linux command, which displays progress. Object 0x2120, sub index 11 can be changed to point to any other accessible file.

When we send data to our basicDevice, it stores them to file, referenced by Object 0x2120, sub index 12.

./cocomm "4 r 0x2120 12 vs"
[1] "fileWrittenByDomain"

pv cocomm.c | base64 -w0 | ./cocomm -i "4 w 0x2120 10 d"
18,2KiB 0:00:00 [ 323MiB/s] [========================================>] 100%
[1] OK

cmp cocomm.c ../examples/basicDevice/fileWrittenByDomain

It is time to transfer something larger, like 100Mb random binary file:

openssl rand 100000000 > binaryFile
pv binaryFile | base64 -w0 | ./cocomm -i "4 w 0x2120 10 d"
95,4MiB 0:01:02 [1,52MiB/s] [========================================>] 100%
[1] OK

cmp binaryFile ../examples/basicDevice/fileWrittenByDomain
rm binaryFile ../examples/basicDevice/fileWrittenByDomain

Here is experimental candump output with two independent pairs of canopend-basicDevice communicating over one virtual CAN interface (two SDO block transfers interlaced):

(000.000003)  vcan0  604   [8]  6F C3 A9 6E AF 5C 81 32   'o..n.\.2'
(000.000010)  vcan0  604   [8]  70 B7 32 8A B2 B0 62 27   'p.2...b''
(000.000006)  vcan0  604   [8]  71 4F 5F 0B F0 40 0B 53   '[email protected]'
(000.000003)  vcan0  604   [8]  72 CE B9 49 D2 80 BD 55   'r..I...U'
(000.000010)  vcan0  603   [8]  A2 7F 7F 00 00 00 00 00   '........' < response
(000.000002)  vcan0  604   [8]  73 94 A2 9E E2 23 8C 5A   's....#.Z'
(000.000005)  vcan0  604   [8]  74 13 90 A2 59 C3 9B 1D   't...Y...'
(000.000003)  vcan0  604   [8]  75 5A F4 85 3A 3E B9 10   'uZ..:>..'
(000.000011)  vcan0  604   [8]  76 30 81 CC 7D 2E 7E 0F   'v0..}.~.'
(000.000000)  vcan0  583   [8]  01 FC 80 1C D4 8F 67 A2   '......g.'
(000.000006)  vcan0  604   [8]  77 9B FA 36 21 0F 09 2E   'w..6!...'
(000.000003)  vcan0  604   [8]  78 C2 6F 97 DC 1D 97 9E   'x.o.....'
(000.000001)  vcan0  583   [8]  02 E0 B9 97 47 8D 37 E6   '....G.7.'
(000.000008)  vcan0  583   [8]  03 07 B6 88 68 0B 6A 8C   '....h.j.'
(000.000002)  vcan0  604   [8]  79 4E 6B 8D 2B 02 F5 9E   'yNk.+...'
(000.000005)  vcan0  583   [8]  04 AD AC A3 B5 59 4C 30   '.....YL0'
(000.000001)  vcan0  604   [8]  7A 23 97 5B D6 2A 02 F4   'z#.[.*..'
(000.000007)  vcan0  583   [8]  05 67 6F 4C 1E AF 18 F3   '.goL....'
(000.000007)  vcan0  604   [8]  7C 6E 6A 12 FE 5E 0B 5B   '|nj..^.['
(000.000001)  vcan0  583   [8]  06 E0 43 D4 2E D2 98 BA   '..C.....'
(000.000005)  vcan0  604   [8]  7D 4B BF 69 25 43 98 FF   '}K.i%C..'
(000.000010)  vcan0  583   [8]  08 52 16 57 0D 11 72 AE   '.R.W..r.'
(000.000001)  vcan0  604   [8]  7F 63 0C B1 2B 19 72 89   '.c..+.r.'
(000.000007)  vcan0  583   [8]  09 67 3D EF B9 C1 32 D3   '.g=...2.'
(000.000012)  vcan0  583   [8]  0A 3D 22 7E 79 5B D5 D4   '.="~y[..'
(000.000010)  vcan0  584   [8]  A2 7F 7F 00 00 00 00 00   '........' < response
(000.000001)  vcan0  583   [8]  0B EC 91 5C FF 04 0A D7   '...\....'
(000.000011)  vcan0  583   [8]  0C 25 86 30 DC 44 6B 59   '.%.0.DkY'
(000.000006)  vcan0  583   [8]  0D 56 8F B0 23 81 41 FF   '.V..#.A.'
(000.000000)  vcan0  604   [8]  01 EF 91 CC EE E8 0D 9C   '........'
(000.000006)  vcan0  604   [8]  02 EA EB 00 A8 34 71 3F   '.....4q?'
(000.000002)  vcan0  583   [8]  0E 03 79 87 32 C6 96 15   '..y.2...'
(000.000006)  vcan0  604   [8]  03 C2 A9 61 8E 25 3D 0A   '...a.%=.'
...
(000.000003)  vcan0  604   [8]  79 16 F1 A6 12 9D 3D B2   'y.....=.'
(000.000005)  vcan0  604   [8]  7A E2 75 5A 16 18 66 37   'z.uZ..f7'
(000.000003)  vcan0  583   [8]  6B 72 30 0A 0F C2 2C F9   'kr0...,.'
(000.000000)  vcan0  604   [8]  7B 82 AE B2 D8 F0 71 32   '{.....q2'
(000.000010)  vcan0  604   [8]  7C 2A 04 DD BE 30 A4 6E   '|*...0.n'
(000.000001)  vcan0  583   [8]  6C EA 98 F4 F2 DA C6 93   'l.......'
(000.000005)  vcan0  604   [8]  7D 68 15 72 4D D0 B5 48   '}h.rM..H'
(000.000005)  vcan0  583   [8]  6D 7B 24 4D 6E 6A C0 71   'm{$Mnj.q'
(000.000008)  vcan0  604   [8]  7F E3 B8 4A CA BE 20 F1   '...J.. .'
(000.000003)  vcan0  583   [8]  6E 11 7B 1F DA 9D 2B B3   'n.{...+.'
(000.000010)  vcan0  583   [8]  6F 0A 1F 8B C4 56 F9 37   'o....V.7'
(000.000012)  vcan0  583   [8]  70 67 91 36 4F 59 04 C9   'pg.6OY..'
(000.000012)  vcan0  583   [8]  71 D4 B8 AC D1 06 7F 63   'q......c'
(000.000001)  vcan0  584   [8]  A2 7F 7F 00 00 00 00 00   '........' < response
(000.000008)  vcan0  583   [8]  72 F3 5A E9 06 B2 A7 42   'r.Z....B'
(000.000009)  vcan0  583   [8]  73 3D 99 69 35 9D B9 43   's=.i5..C'
(000.000000)  vcan0  604   [8]  01 3B CB D9 00 E5 6A B7   '.;....j.'
(000.000006)  vcan0  604   [8]  02 A6 0F 34 3D 07 C1 6A   '...4=..j'
(000.000002)  vcan0  583   [8]  74 72 CE 65 1B 82 0F FC   'tr.e....'
(000.000001)  vcan0  604   [8]  03 0D 35 70 C2 34 C4 7D   '..5p.4.}'
(000.000007)  vcan0  583   [8]  75 9C 7A 1D 19 FB A0 92   'u.z.....'
(000.000007)  vcan0  583   [8]  76 3D 9E B8 2F FF 9A 1B   'v=../...'
(000.000008)  vcan0  583   [8]  77 ED 4E 6D D5 BA E2 D6   'w.Nm....'
(000.000008)  vcan0  583   [8]  78 BA E2 83 1E 03 03 9B   'x.......'
(000.000005)  vcan0  604   [8]  04 DD 6B BB FA 9C 67 BF   '..k...g.'
(000.000002)  vcan0  583   [8]  79 B7 2E A5 37 28 1E F6   'y...7(..'
(000.000004)  vcan0  604   [8]  05 C6 A3 C7 EE 6D 24 C0   '.....m$.'
(000.000004)  vcan0  604   [8]  06 5F 4E 41 25 BB D9 44   '._NA%..D'
(000.000000)  vcan0  583   [8]  7A DB 34 D2 46 33 33 AE   'z.4.F33.'
(000.000008)  vcan0  583   [8]  7B 30 76 56 31 1A A6 5C   '{0vV1..\'
(000.000007)  vcan0  583   [8]  7C 20 42 44 A5 60 AC 77   '| BD.`.w'
(000.000004)  vcan0  604   [8]  07 42 7F 87 4B A5 ED 0E   '.B..K...'
(000.000004)  vcan0  583   [8]  7D 5D 36 93 F5 27 08 CF   '}]6..'..'
(000.000002)  vcan0  604   [8]  08 C1 17 36 99 9E 5F 17   '...6.._.'
(000.000003)  vcan0  604   [8]  09 C0 3A 4F AF 7F 92 71   '..:O...q'
(000.000006)  vcan0  583   [8]  7E 77 05 8E 00 43 29 D3   '~w...C).'
(000.000005)  vcan0  604   [8]  0A CF 24 64 25 4D 81 30   '..$d%M.0'
(000.000004)  vcan0  583   [8]  7F 98 C3 03 A3 2E AE E7   '........'
(000.000002)  vcan0  604   [8]  0B 60 F0 5B 29 3F 69 4B   '.`.[)?iK'
(000.000007)  vcan0  604   [8]  0C E1 02 DA CF 08 34 0E   '......4.'
(000.000007)  vcan0  604   [8]  0D 7F 13 89 DE AB AB 16   '........'
(000.000006)  vcan0  604   [8]  0E 98 AF 47 B3 30 56 DF   '...G.0V.'
(000.000003)  vcan0  604   [8]  0F 83 FB 95 9F EB E9 12   '........'
(000.000010)  vcan0  604   [8]  10 F8 09 E9 39 10 CE 0F   '....9...'
(000.000007)  vcan0  604   [8]  11 4C 72 0B DB 34 92 14   '.Lr..4..'
(000.000003)  vcan0  604   [8]  12 9D 83 1C 07 66 DC 7E   '.....f.~'
(000.000000)  vcan0  603   [8]  A2 7F 7F 00 00 00 00 00   '........' < response
(000.000008)  vcan0  604   [8]  13 DB E4 73 B0 6F 23 CB   '...s.o#.'
(000.000006)  vcan0  604   [8]  14 CE 1C 70 EC C3 57 99   '...p..W.'
(000.000006)  vcan0  604   [8]  15 7C 33 D5 C7 FC 80 9B   '.|3.....'
(000.000000)  vcan0  583   [8]  01 2D 89 31 D6 1F 36 88   '.-.1..6.'
(000.000010)  vcan0  583   [8]  02 B6 56 16 06 64 21 69   '..V..d!i'

Create project in your favourite IDE

BasicDevice example uses simple Makefile to properly compile necessary source files and set correct compile options. This file can be examined, to see, how to configure project files for your favourite IDE. There is also gateway.Makefile, which compiles the project with included gateway objects, similar as canopend.

Create project with KDevelop

  • sudo apt install kdevelop breeze
  • Run KDevelop, select: Project -> open project
  • Navigate to directory examples/basicDevice and click open.
  • KDevelop will recognize Makefile and will just use it. Click Finish.
  • Open project settings (right click on project on left panel)
    • Make: set to 1 thread operation.
    • Language support, Includes, add paths to directories:
      • CANopenNode/socketCAN
      • CANopenNode
      • examples/basicDevice
    • Language support, Defines, add:
      • CO_DRIVER_CUSTOM
  • Run -> Setup launches -> basicDevice:
    • Add Executable file
    • Name it basicDevice_i4_vcan0, for example.
    • Executable file: examples/basicDevice/basicDevice
    • Arguments: -i 4 vcan0
  • Build, then Execute or Debug