Giter Club home page Giter Club logo

c-open's Introduction

c-open: CANopen stack

Build Status CodeQL

This repository contains a CANopen stack for both master and slaves. The stack implements most of CiA 301 and 305 (LSS). The stack is written to an OS abstraction layer and can also be used in a bare metal application. Using the abstraction layer, the stack can run on Linux, Windows or on an RTOS.

A simple slave is included to serve as an example of how to use the stack. The slave can also be used to run the CiA Conformance Test Tool.

Also included is a simple master example that lists all slaves on the bus and a comprehensive set of unit-tests.

Cloning

Clone the source:

$ git clone --recurse-submodules https://github.com/rtlabs-com/c-open.git

This will clone the repository with submodules. If you already cloned the repository without the --recurse-submodules flag then run this in the c-open folder:

$ git submodule update --init --recursive

Prerequisites for all platforms

  • CMake 3.14 or later

Windows

  • Visual Studio 2017 or later
  • Kvaser CANlib SDK

You can use a windows or unix shell as preferred. The following instructions are for a unix shell. CMake is assumed to be in your path.

The windows build supports Kvaser devices and requires the Kvaser CANlib SDK. CMake should find the SDK but if not a hint can be given by setting -DCANLIB_ROOT_DIR="C:\Program Files (x86)\Kvaser\Canlib" or similar during configuration.

$ cmake -B build.win32 -A Win32
$ cmake --build build.win32 --config Release
$ cmake --build build.win32 --config Release --target check

This builds the project and runs the unit tests.

Linux

  • GCC 4.6 or later
$ cmake -B build
$ cmake --build build --target all check

This builds the project and runs the unit tests.

rt-kernel

  • Workbench 2020.1 or later

You should use a bash shell, such as for instance the Command Line in your Toolbox installation. Set the BSP variable to the name of the BSP you wish to build for. Set the RTK variable to the path of your rt-kernel tree.

Standalone project

This creates standalone makefiles.

$ RTK=/path/to/rt-kernel BSP=xmc48relax cmake \
   -B build.xmc48relax \
   -DCMAKE_TOOLCHAIN_FILE=cmake/tools/toolchain/rt-kernel.cmake \
   -G "Unix Makefiles"
$ cmake --build build.xmc48relax

Workbench project

This creates a Makefile project that can be imported to Workbench. The project will be created in the build directory. The build directory should be located outside of the source tree.

$ RTK=/path/to/rt-kernel BSP=xmc48relax cmake \
   -B build.xmc48relax -S /path/to/c-open \
   -DCMAKE_TOOLCHAIN_FILE=cmake/tools/toolchain/rt-kernel.cmake \
   -DCMAKE_ECLIPSE_EXECUTABLE=/opt/rt-tools/workbench/Workbench \
   -DCMAKE_ECLIPSE_GENERATE_SOURCE_PROJECT=TRUE \
   -G "Eclipse CDT4 - Unix Makefiles"

A source project will also be created in the c-open tree. This project can also be imported to Workbench. After importing, right-click on the project and choose New -> Convert to a C/C++ project. This will setup the project so that the indexer works correctly and the Workbench revision control tools can be used.

The library and the unit tests will be built. Note that the tests require a stack of at least 6 kB. You may have to increase CFG_MAIN_STACK_SIZE in your bsp include/config.h file.

Contributions

Contributions are welcome. If you want to contribute you will need to sign a Contributor License Agreement and send it to us either by e-mail or by physical mail. More information is available here.

c-open's People

Contributors

elupus avatar hefloryd avatar nattgris avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

c-open's Issues

Dead code in co_handle_rx

In co_handle_rx will different functions be called, depending on the received function code.

However, the function co_emcy_rx will never be called since the value CO_FUNCTION_EMCY is the same as the value CO_FUNCTION_SYNC, which already has been checked.

The correct condition to call co_pdo_sync should be:

else if ((function == CO_FUNCTION_SYNC) && (node == 0))

SDO abort codes not exported

The public co_api header defines the co_access_fn function type. But when implementing such an access function, you probably need one or more of the CO_SDO_ABORT codes which are only defined in the private co_sdo header.

CiA 417 uses invalid PDO COB-ID

While implementing CiA 417 object dictionary I noticed that RPDO 261 is specified (CiA 417:3 §A.1) as using COB-ID 0x180 but the stack is not accepting this. And indeed, looking at CiA 301 §7.3.5 that CAN-ID is listed as restricted, not for use by PDOs etc.

Now I wonder, is that a mistake/typo in the CiA 301 document and, by extension, in the stack? Or is it just a temporary loss of brain functionality when they picked COB-IDs for CiA 417?

Regardless, can the check in co_validate_cob_id() be relaxed to support the CiA 417 requirements?

More efficient timeout implementation

It's a bit inefficient to poll the stack for its timeout handling at 1 kHz.

Suggestion from #3:

It would be nice if the clients of the periodic job feature could say when they are expiring, so that the timer can be armed to the time required instead of periodically. Since all callees in co_handle_periodic() seems to end up using co_is_expired() to check now against a timestamp and fixed period, this should be easily implemented by returning the time left from co_is_expired() and propagating the minimum value up the call chain to co_main() which can re-arm the timer.

Then also the CO_JOB_PERIODIC message must be force sent whenever any event causes a timer to be set, to recalculate the timeout. Maybe co_handle_periodic() can be called every iteration of the co_main() loop to cover all cases where the event passes trough the main thread? Are there others?

The main loop could probably even use the timeout feature of os_mbox_fetch() to wake up at the right time instead of keeping a separate timer around.

test_sdo_server.cpp:207:30: error: storing the address of local variable ‘obj’ in ‘mock_co_obj_find_result’

hpz420/~/canopen/c-open/c-open 14 cmake --build build --target all check
[ 5%] Built target osal
[ 35%] Built target canopen
[ 41%] Built target slave
[ 47%] Built target slaveinfo
[ 50%] Built target gtest
[ 52%] Building CXX object CMakeFiles/co_test.dir/test/test_sdo_server.o
/home/john/canopen/c-open/c-open/test/test_sdo_server.cpp: In member function ‘virtual void SdoServerTest_BadSubIndex_Test::TestBody()’:
/home/john/canopen/c-open/c-open/test/test_sdo_server.cpp:207:30: error: storing the address of local variable ‘obj’ in ‘mock_co_obj_find_result’ [-Werror=dangling-pointer=]
207 | mock_co_obj_find_result = &obj;
| ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
/home/john/canopen/c-open/c-open/test/test_sdo_server.cpp:199:18: note: ‘obj’ declared here
199 | const co_obj_t obj = {0, OTYPE_NULL, 0, NULL, NULL};
| ^~~
In file included from /home/john/canopen/c-open/c-open/test/test_sdo_server.cpp:22:
/home/john/canopen/c-open/c-open/test/mocks.h:75:25: note: ‘mock_co_obj_find_result’ declared here
75 | extern const co_obj_t * mock_co_obj_find_result;
| ^~~~~~~~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors
gmake[2]: *** [CMakeFiles/co_test.dir/build.make:76: CMakeFiles/co_test.dir/test/test_sdo_server.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:1087: CMakeFiles/co_test.dir/all] Error 2
gmake: *** [Makefile:166: all] Error 2

Fixed by adding "static" at line 199 in test_sdo_server.cpp.

Automatic transition to NMT Pre-operational for non-critical errors

The 1029h Error behavior object allows to configure whether the device should autonomously enter Pre-operational, Stopped or to remain in the current NMT state, after a "serious CANopen device failure". The default behavior, and the only behavior if 1029h is not implemented, is to transition to Pre-operational.

This is implemented correctly by the stack, however the transition (if not disabled) is taken regardless of which error has occurred, for any EMCY that is sent by the application. CiA301 lists the following conditions as mandatory for being "CANopen device failures":

  • Bus-off
  • Life guarding or Heartbeat time-out

"Severe CANopen device errors also may be caused by CANopen device internal failures". But there is no way to tell that a specific EMCY is not considered severe by the application.

How to support PDO COB-ID based on node ID after it has been changed by LSS?

I have a pre-defined PDO with a COB-ID that is defined to be based on the node ID. i.e. something like the following in the od_defaults array (much like the example slave):

	{0x1881, 1, 0x40000480 + DEFAULT_NODE_ID }, /* Enable TPDO 130 */

However, when the node ID is changed via LSS, the COB-ID of course still matches the default node ID, not the new one. I see no possible way to fix this.

I have considered patching the defaults array with the new node ID, however the new ID is not available until after the defaults have been applied.

I have also considered using my PR #46 to update the COB-ID later, in the NMT callback when going Pre-operational and the active node ID has been established. However, since this is (at least during startup) still inside the co_init() call, no client has been possible to create to be able to submit jobs to the co_main thread.

High cpu-usage on linux

On linux we check for incoming can frames by creating a thread that blocks in select(). When select() unblocks, the thread calls a callback to inform the main thread that there are messages to read, by posting in a mailbox. The thread will then attempt the select() again, which will succeed until the main thread has read the can message. If the main thread is not allowed to run this will cause high cpu-usage and fill the mailbox.

Find a better method to create a callback on incoming messages on linux, or rework the abstraction layer so that incoming messages can be posted to the main thread.

UNSIGNED48 is not working

Unfortunately it seems there is no way to use objects of datatype UNSIGNED48, or any other INTEGER or UNSIGNED of size between 32 and 64.

Even for 64 bit integers, there's no way to programmatically handle the access via a co_access_fn.

UNSIGNED48 is widely used for all virtual I/O in e.g. CiA 417, where programmatic access is also needed to take care of the somewhat complicated behaviour of those objects.

PDOs sent in Pre-operational

If the application triggers PDO transmission using co_pdo_event() or co_pdo_obj_event(), the stack transmits the PDOs regardless of NMT state. It should not send PDOs unless the NMT state is Operational.

PDO size mismatch EMCY

In some setups, a TPDO on another node can have slightly different setups depending on the manufacturer/type of the particular device. For example, a device profile may mandate that PDO X contains object A, while some devices are mapping both object A and B in that PDO. Even if we only want to use object A (which is guaranteed to be available) and only have it mapped in our RPDO, the stack throws an EMCY with code 0x8210 just because it receives a PDO with "additional junk" on the end.

This is covered by §7.5.2.36 in CiA 301 which states (emphasis mine):

If a CANopen device receives a PDO that is having less data bytes than the number of mapped data bytes (length), then the CANopen device shall initiate the EMCY write service, if supported, with the error code 8210 h.

And, a little earlier in the paragraph:

If the CANopen device receives a PDO that is having more data bytes than the number of mapped data bytes is (length), then the CANopen device shall use the first data bytes up to the length and may be initiate the EMCY write service, if supported.

So the case with extra data in the PDO is not necessary to write an EMCY for (it does not even state the code to use) and in cases like this also not very helpful. Can the check be disabled for this case, or if it makes sense to have it in other cases, at least be able to disable it by configuration?

Standard RPDO padding not possible

It seems standard RPDO padding between mappings as per CiA 301 §7.4.7.1 is not implemented.

For static mappings I guess you could work around it by defining objects like

	{0x0005, OTYPE_DEFTYPE,0,               OD0005, NULL},

with

static uint8_t dummy;

/* Entry descriptor for UNSIGNED8 (0005h) */
static const co_entry_t OD0005[] = {
	{0x00, OD_WO | OD_TRANSIENT | OD_RPDO, DTYPE_UNSIGNED8, 8, 0, &dummy},
};

However to support dynamic mappings you'd basically have to do this for all integer types for all slaves, because you don't know what mapping the master may set up. Seems better if that was built into the stack.

MPDO support

It would be nice if the stack supported MPDO, as required by some CANopen profiles (such as CiA 417).

Unable to override default TPDO CAN-ID

An updated CAN-ID for a PDO that is setup in the defaults array does not stick even if the communication parameters are stored. This can be demonstrated using the example slave, with the addition of a long delay before exiting main, and the following test script:

# Start the node to see the TPDO, then reset again
cansend vcan0 000#0101
sleep 1
cansend vcan0 000#8101
sleep 1

# Reconfigure the TPDO with CAN-ID 0x456 and duplicate the mapping
sdowrite vcan0 1 0x1800 1 4 0x80000456
sdowrite vcan0 1 0x1A00 0 1 0
sdowrite vcan0 1 0x1A00 1 4 0x20010010
sdowrite vcan0 1 0x1A00 2 4 0x20010010
sdowrite vcan0 1 0x1A00 0 1 2
sdowrite vcan0 1 0x1800 1 4 0x00000456

# Store the communication parameters
sdowrite vcan0 1 0x1010 2 4 0x65766173

# Start the node to see the TPDO with updated CAN-ID and mapping
cansend vcan0 000#0101
sleep 1

# Reset the node, then start again and expect the PDO to be identical
cansend vcan0 000#8101
sleep 1
cansend vcan0 000#0101

candump shows the following output:

  vcan0  701   [1]  00
  vcan0  081   [8]  00 10 81 00 00 00 00 00
  vcan0  081   [8]  00 00 00 00 00 00 00 00
  vcan0  000   [2]  01 01
  vcan0  181   [2]  AA 55
  vcan0  000   [2]  81 01
  vcan0  701   [1]  00
  vcan0  601   [8]  23 00 18 01 56 04 00 80
  vcan0  581   [8]  60 00 18 01 00 00 00 00
  vcan0  601   [8]  2F 00 1A 00 00 00 00 00
  vcan0  581   [8]  60 00 1A 00 00 00 00 00
  vcan0  601   [8]  23 00 1A 01 10 00 01 20
  vcan0  581   [8]  60 00 1A 01 00 00 00 00
  vcan0  601   [8]  23 00 1A 02 10 00 01 20
  vcan0  581   [8]  60 00 1A 02 00 00 00 00
  vcan0  601   [8]  2F 00 1A 00 02 00 00 00
  vcan0  581   [8]  60 00 1A 00 00 00 00 00
  vcan0  601   [8]  23 00 18 01 56 04 00 00
  vcan0  581   [8]  60 00 18 01 00 00 00 00
  vcan0  601   [8]  23 10 10 02 73 61 76 65
  vcan0  581   [8]  60 10 10 02 00 00 00 00
  vcan0  000   [2]  01 01
  vcan0  456   [4]  AA 55 AA 55
  vcan0  000   [2]  81 01
  vcan0  701   [1]  00
  vcan0  000   [2]  01 01
  vcan0  181   [4]  AA 55 AA 55

The last line shows that the TPDO COB-ID reverted to the default after an NMT reset. However, the updated mapping was correctly remembered.

Event-based communication error handling

Currently, communication errors are polled for in the CO_JOB_PERIODIC context by calling co_emcy_handle_can_state(). To avoid the need for periodic polling (see #6), the drivers could send events on errors, just like RX is handled.

For embedded controllers they most likely provide the ability to interrupt on error flags.

SocketCAN on linux can be told to send error events through the regular RX path, see https://www.kernel.org/doc/html/latest/networking/can.html#raw-socket-option-can-raw-err-filter for details.

If some platform doesn't support it, there's always the option to fall back to a separate polling thread.

Using high PDO numbers is very inefficient

I can't find a way to use high PDO numbers without setting MAX_TX_PDO and MAX_RX_PDO to those high numbers, which is wasting huge amounts of RAM due to the large arrays. I want to implement RPDO 259, 261 and 263 and TPDO 260, 262 and 385, as mandated by the 417 application profile.

Finding a matching PDO is also inefficient because it's doing a linear search through the whole, almost entirely empty, array.

Is there a way around it or would the PDO code need to use a data structure that scales better?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.