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.
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 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:
- Whole program runs in single thread. This is default for
canopend
. This uses less system resources. - 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
.
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);
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.
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.
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.
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.- When run,
cocomm
tries to connect to/tmp/CO_command_socket
address (this is default setting).canopend
accepts the connection. cocomm
writes the specified ascii command to established socket connection, for example[1] 4 read 0x1017 0 u16
.- Gateway in
canopend
receives the command and decodes it.read
commands goes internally intoCO_SDOclientUploadInitiate()
and then command is processed with multipleCO_SDOclientUpload()
function calls. CO_SDOclientUpload()
now sends a CAN message to ourbasicDevice
. (CAN interface in Linux is implemented with CAN sockets. This is the third type of sockets mentioned here.) However, in our examplebasicDevice
receives SDO request, asking the value of the variable, located in Object Dictionary at index 0x1017, subindex 0.- Our
basicDevice
receives CAN message with CAN ID=0x604. It determines SDO request, soCO_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. canopend
receives the CAN message,CO_SDOclientUpload()
decodes it and sends binary value to gateway.- 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
. cocomm
reads the response text from local socket and prints it partly to stderr ([1] \r\n
) and partly to stdout (asciiValue).- If there are more commands, step 3 is repeated. Otherwise
cocomm
closes the socket connection and exits.
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
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
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
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 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'
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