Code Monkey home page Code Monkey logo

canopen-stack's Introduction

Free CANopen Stack

This project is a free implementation of the CANopen protocol according to the free specification CiA 301. You need to register at Can in Automation (CiA) to retrieve your copy of the specification.

The source code is compliant to the C99 standard and you must cross-compile the source files as part of your project with the cross-compiler of your choice.

Note: The source code of this project is independent from the CAN controller and microcontroller hardware. The hardware specific parts are called drivers. For a full featured CANopen stack, we need the drivers for hardware timer, CAN controller and a non-volatile storage media.

Features

General

  • Usable with or without a real-time operating system (RTOS)
  • Software timer management

CiA 301 - CANopen application layer and communication profile

  • Unlimited number of SDO servers which supports:
    • Expedited transfers
    • Segmented transfers
    • Block transfers
  • Unlimited number of SDO clients which supports:
    • Expedited transfers
    • Segmented transfers
  • Unlimited number of TPDO and RPDOs which supports:
    • Synchronized operation
    • Asynchronous operation
    • Manufacturer specific operation
  • Unlimited number of entries in object dictionary
    • Static or dynamic object dictionary
    • Data types: signed/unsigned 8/16/32-Bit integer, string, domain and user-types
  • Unlimited number of parameter groups for parameter storage
  • Emergency producer which supports:
    • Manufacturer specific extensions
    • Unlimited error history
  • Unlimited Emergency consumers
  • SYNC producer
  • Unlimited number of SYNC consumers
  • Heartbeat producer
  • Unlimited number of Heartbeat consumers
  • Network Management consumer

CiA 305 - Layer Setting Services (LSS)

  • Baudrate configuration
  • NodeId configuration

Note: the term 'unlimited' means, that the implementation introduces no additional limit. There are technical limits, described in the specification (for example: up to 511 possible TPDOs)

Usage

With version 4.4, the CANopen Stack project introduces an ecosystem that tries to support you in your project management. This support uses multiple repositories for essential aspects of an embedded software project setup:

  • cmake-scripts - this repository is responsible for the embedded toolchains and the component package management.
  • canopen-stack - this repository represents the platform independent CANopen stack component.
  • canopen-stm32f7xx - this repository contains a complete Quickstart example setup for the device STM32F769. The adaption to other devices out of the STM32F7 series are small.
  • STM32CubeF7 - this fork of the ST Microelectronics HAL package is integrated into the CMake build system and packaged with minimal required source files to get the ST HAL/LL drivers working (No middleware and documentation).

Add Component in CMake (recommended)

The build system is realized with CMake and the CPM.cmake package management. See cmake-scripts for details. Adding the CANopen Stack into your project is done during the configuration phase of the build environment. During this phase, the CANopen Stack is fetched in the defined version and is available for usage.

set(CO_TARGET   "canopen-stack")
set(CO_PROJECT  "embedded-office/canopen-stack")
set(CO_VERSION  "4.4.0")
CPMAddPackage(
  NAME    ${CO_TARGET}
  URL     https://github.com/${CO_PROJECT}/releases/download/v${CO_VERSION}/${CO_TARGET}-src.zip
  VERSION ${CO_VERSION}
)

History

The first release of this CANopen stack is back in 2005. It is still used in many CANopen nodes from small startup companies up to big players in the automation market. Since Embedded Office sells an OEM license to Micrium to provide the CANopen stack as a part of the uC/ product line, we maintain the CANopen stack for the Flexible Safety RTOS and bare metal usage in parallel.

Some years later, now in 2020, we think it is time for a new way of software development of components where no product specific know-how is neccessary. This project is the try with the hope, that this way of software development is good for existing customers, for Embedded Offic and for all potential new users.

To avoid confusion, it is the best to continue with the release version numbering. The first stable release of the open-source variant of the CANopen Stack is: V4.0.0

License

The Apache 2.0 license is suitable for commercial usage, so we think this is the best for this free component. If you have problems or concerns with this license, just contact us at Embedded Ofice. We will help you to get the legal approvals within your company.

Code of Conduct

As everywhere in the world (especially in the internet) and at every time, we think a respectful and open minded communication is essential for peaceful and innovative developments. Please have a look in our Code of Conduct and think about your writing before submitting.

Contribution

Issues and Questions

Feel free to write bug reports, questions or and feedback as issue within this github repository.

Development Environment

The development environment for the CANopen stack takes place on the host machine. In release 4.4.0, we have decided to switch from the Windows-only MSVC compiler to the LLVM compiler. The main reason for this decision is that the same compiler is available for Windows, Linux, and Mac OS users.

Development Tools

Download and install these free tools for your system:

  • Install the build tools Cmake
  • Install the build system Ninja
  • Install the static checker cppcheck
  • Install the compiler LLVM

Note: on my Windows machine, I use the Visual Studio 2019 Build Tools which inlcudes the LLVM compiler and the required Windows SDK libraries.

Run the Test Applications

The application is build with CMake using the provided presets:

# configure the project build environment for your host system
$ cmake --preset llvm-host
# build the library for your host and all test applications
$ cmake --build --preset debug

# execute all test applications
$ ctest --preset all

canopen-stack's People

Contributors

cesarvandevelde avatar cjardin112 avatar domen2242 avatar dozack avatar jernejskrabec avatar michael-hillmann avatar tonbo777 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

canopen-stack's Issues

Interface Receive API

We wanted to know more about Interface Receive COIfCanReceive() API.
As we understand, Will this API be triggered for valid CAN packets which are not intended for the device node id.
For example, if a device with canopen stack and node id 2 are setup. Using this API, can we get packets for SDO transfers between node 3 and node 4?

SDO block download after a SDO segmented/block upload

Doing a SDO block download after a SDO block upload writes the previously read data into OD, instead of what was received during the block download.

Steps to reproduce the behavior:
In this example all transfers occur on the same index/subindex, which initially contains 10 bytes of data: 0xFFFFFFFFFFFFFFFFFFFF

  1. Do a SDO block upload, data uploaded by the SDO server is: 0xFFFFFFFFFFFFFFFFFFFF
  2. Do a SDO block download, data downloaded to the SDO server is: 0x00000000000000000000
  3. Do a SDO block upload, data uploaded by the SDO server is: 0xFFFFFFFFFFFFFFFFFFFF

Expected behavior:

  1. Do a SDO block upload, data uploaded by the SDO server is: 0xFFFFFFFFFFFFFFFFFFFF
  2. Do a SDO block download, data downloaded to the SDO server is: 0x00000000000000000000
  3. Do a SDO block upload, data uploaded by the SDO server is: 0x00000000000000000000

The issue appears to be caused by the SDO server buffer position pointer not being reset between a block upload and download.
This causes the downloaded data to be written into the buffer after data from previous upload, instead of overwriting it.
Segmented upload and download don't have this issue, because they both reset the buffer position pointer during transfer initialization (in functions COSdoInitUploadSegmented and COSdoInitDownloadSegmented).

    ...
    srv->Buf.Cur  = srv->Buf.Start;
    srv->Buf.Num  = 0;
    ...

SDO block download, however, only resets the buffer position pointer at the end of a sub-block or transfer.
Resetting the buffer position pointer in COSdoInitDownloadBlock appears to fix the issue

CO_ERR COSdoInitDownloadBlock(CO_SDO *srv)
{
    CO_ERR   result = CO_ERR_SDO_ABORT;
    uint32_t width  =  0;
    uint32_t size;
    uint8_t  cmd;

    cmd = CO_GET_BYTE(srv->Frm, 0);
    if ((cmd & 0x02) != 0) {
        width = CO_GET_LONG(srv->Frm, 4);
    }
    size = COSdoGetSize(srv, width);
    if (size == 0) {
        COSdoAbort(srv, CO_SDO_ERR_TOS);
        return (result);
    }
    if (width <= size) {
        result = COObjWrBufStart(srv->Obj, srv->Node, srv->Buf.Cur, 0);
        if (result != CO_ERR_NONE) {
            srv->Node->Error = CO_ERR_SDO_WRITE;
            result           = CO_ERR_NONE;
        }

        srv->Blk.SegCnt = 0;
        srv->Blk.State  = BLK_DOWNLOAD;
        srv->Blk.SegNum = CO_SDO_BUF_SEG;
        srv->Buf.Cur  = srv->Buf.Start;   //added
        srv->Buf.Num  = 0;                //added

        CO_SET_BYTE(srv->Frm, 0xA0, 0);
        CO_SET_LONG(srv->Frm, (uint32_t)CO_SDO_BUF_SEG, 4);
    } else {
        COSdoAbort(srv, CO_SDO_ERR_LEN_HIGH);
    }

    return (result);
}

The same issue also occurs when doing a SDO block download after a SDO segmented upload.

Block transfer issue with basic data types

Attempting to do a block upload/download on a OD entry containing a basic data type doesn't work.
Download gets aborted, while an upload sends some random data, instead of what's in the OD.
Issue seems to be caused by block transfers calling COObjWrBuf(Start/Cont) and COObjRdBuf(Start/Cont) functions, which don't work when an object's type is 0.

This is probably a minor issue; Doing a block transfer on small objects should rarely, if ever, happen.
However, it should probably still work and shouldn't respond with random data during a block upload.

Recommended clarification / improvement to quickstart example, and some suggested macro-foo

I've now managed to bring up a working implementation of this CANopen stack on my STM32F405 development hardware. Thank you very much for this software stack, and for doing additional work on the documentation in response to user requests. It is greatly appreciated and extremely helpful.

I've used the quickstart example as a pattern for development, and I have several observations and suggestions from the perspective of a novice user.

I had significant trouble properly setting up TPDOs in the object dictionary; the example contains an error there, and there are also opportunities to make this setup easier. (Some of my difficulties came from not really understanding how the PDO configuration mechanisms are intended to work, of course. The only real cure for that was reading and research.)

The example object dictionary contains the following TPDO communications entries for TPDO #0:

    {CO_KEY(0x1800, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)2},
    {CO_KEY(0x1800, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, (uintptr_t)0x40000180},
    {CO_KEY(0x1800, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)254},

The entry at subindex 1 encodes the COBID 0x180 that is used for TPDO 0 frames, but there are problems with it:

  • The value here is plugged in as a "magic number" that is quite confusing to the newcomer; it could easily be generated by a macro instead (see below), reducing the possibility of user error and improving readability
  • The COBID 0x180 needs to be indexed by the node ID to get the correct COBID value for a real node; I think that CO_OBJ_D__R_ should be replaced by CO_OBJ_DN_R_ to accomplish this. (In my application, node IDs are generated at run time by hardware straps, so hard-coding the node ID into the magic number is not possible.)

I wrote these macros to simplify the process:

// PDO communication entries encode the node number and a service flag with the basic TPDO COBID.
// These macros generate the right COBID for TPDO x or RPDO x, and the dictionary does the node-ID indexing.
#define PDO_NON_RTR_FLAG 0x40000000
#define PDO_COBID_INCREMENT 0x100
#define TPDO_COBID_BASE 0x180
#define RPDO_COBID_BASE 0x200
#define TPDO_COBID(x) (uintptr_t)(PDO_NON_RTR_FLAG | (TPDO_COBID_BASE + (x * PDO_COBID_INCREMENT)))
#define RPDO_COBID(x) (uintptr_t)(PDO_NON_RTR_FLAG | (RPDO_COBID_BASE + (x * PDO_COBID_INCREMENT)))

Using them, the TPDO comms entries now look like this:

    // TPDO #0 communications entries
    {CO_KEY(0x1800, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)2},
    {CO_KEY(0x1800, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, TPDO_COBID(0)},
    {CO_KEY(0x1800, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)254},

The example object dictionary contains the following TPDO mapping entries for TPDO #0:

    {CO_KEY(0x1A00, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)3},
    {CO_KEY(0x1A00, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, (uintptr_t)0x21000120},
    {CO_KEY(0x1A00, 2, CO_UNSIGNED32|CO_OBJ_D__R_), 0, (uintptr_t)0x21000208},
    {CO_KEY(0x1A00, 3, CO_UNSIGNED32|CO_OBJ_D__R_), 0, (uintptr_t)0x21000308},

Sub-indices 1..3 simply encode the index/subindex and size of the data that this TPDO will ship out; here, too, these mysterious magic numbers can be made easier to understand with some simple macro magic:

// PDO mapping entries have a data field composed of index, subindex, and type
// This handy macro creates that 32-bit entity for us
#define PDO_MAP_ENTRY(x, y, z) (uintptr_t)((x << 16) | (y << 8) | (z))

Which gives us the following:

    // TPDO #0 mapping entries
    {CO_KEY(0x1A00, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)3},
    {CO_KEY(0x1A00, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, PDO_MAP_ENTRY(0x2100, 1, CO_UNSIGNED32},
    {CO_KEY(0x1A00, 2, CO_UNSIGNED32|CO_OBJ_D__R_), 0, PDO_MAP_ENTRY(0x2100, 2, CO_UNSIGNED8}},
    {CO_KEY(0x1A00, 3, CO_UNSIGNED32|CO_OBJ_D__R_), 0, PDO_MAP_ENTRY(0x2100, 3, CO_UNSIGNED8}},

I believe this is a bit easier to follow, and makes implementing new entries by hand much easier. (This may be an uncommon use case, and most users may use tools or device profiles to autogenerate the object dictionary, but I don't have convenient access to those tools, and my object dictionary is fairly small.)

As a side note, there are a number of #defines in co_obj.h (lines 38-41 and 46-48) that have the "PDO map" flag, but I have been unable to determine how to use them, or indeed, to determine if they matter in this context. (My code appears to work without them, but I may be fooling myself.)

I'll also sneak a last question in here, as I haven't been able to figure out the answer on my own: I'm also implementing a relatively basic CANopen master node on an STM32F4 processor; is this stack suitable for use in that role? (If so, I will have to figure out how it should handle incoming TPDO frames from the various remote nodes. A problem for tomorrow...)

Once again, thanks for all the fish!

Synchronous PDOs

How do I configure a synchronous PDO Transfer? I couldn't find any relevant info in the docs.

Question about conformance of this stack

Hi,

I'm currently using your stack for a project on a STM32F0. Works like a charm.

I would like to know if anyone has tested this stack against the CiA conformance test tool ?

Thanks.

Br;

Alternative NVM driver interface for using with existing database

Is your feature request related to a problem? Please describe.
Currently, the stacks implements it's own "database" module in order to store objects to NVM. NVM abstraction is possible thanks to NVM port driver. Let's say for example a "database" module already exists with his features, capabilities, etc: it would be possible to implement NVM port driver to read/write from/to this "database"? Right now I think it's not, since NVM driver interface is really basic: read/write starting from an offset for a specific size. It's not possible to redirect these calls to existing database since they are raw data: no interpretation of these data is possible.

Describe the solution you'd like
I'd like to have an alternative NVM driver interface, where user can decide to redirect read/write calls to its own database.

Describe alternatives you've considered
The solution I thought is to change CO_IF_NVM_READ_FUNC and CO_IF_NVM_WRITE_FUNC to something like:

typedef uint32_t (*CO_IF_NVM_READ_FUNC )(uint32_t objectId, uint32_t subIndex, uint8_t* ptrBuffer, uint32_t size);
typedef uint32_t (*CO_IF_NVM_WRITE_FUNC)(uint32_t objectId, uint32_t subIndex, uint8_t* ptrBuffer, uint32_t size);

Maybe the NVM driver interface (and implementation of internal code) may be choose at compile time with a specific compilation define in co_cfg.h (e.g. #define CO_USE_INTERNAL_DATABASE_IMPLEMENTATION -> use the current implementation, #define CO_USE_EXTERNAL_DATABASE_IMPLEMENTATION -> use different NVM driver interface.
Additional context
/

SDO Client functionality

We can see in documentation that SDO Client functionality is present in roadmap for the canopen-stack.
We require this functionality in our use case. Can you please share the tentative timeline for the same?

SDO block download issue

I have a SDO server node (based on this stack) and a SDO client node (based on canfestival) .
Attempting to do a SDO block download between the devices fails, because the server keeps replying to every sub-block frame with an identical frame - except for ID.
The client rejects these responses and aborts the transfer

Steps to reproduce the behavior:

  1. the client initializes a SDO block download (example size 39 bytes, but shouldn't really matter)
  2. the server responds successfully
  3. the client sends 6 frames of data, setting the 'c' bit on the last frame
  4. the server sends 6 response frames. First 5 frames are the same frames received by the server (except for id), while the last one is the proper sub-block acknowledgment frame
  5. the client responds to each frame with a abort request, cancelling the transaction before having a chance to parse the correct acknowledgment frame
  6. the server responds to each abort frame with an identical frame (except for id)

Expected behavior:

  1. the client initializes a SDO block download - 39 bytes
  2. the server responds successfully
  3. the client sends 6 frames of data, setting the 'c' bit on the last frame
  4. the server responds to the last frame with a sub-block acknowledgment frame
  5. the client sends a block download end frame
  6. the server responds with a block download end response frame, ending the transaction

Modifying the CONodeProcess process function as follows appears to fix the issue.
Replacing

    if ((allowed & CO_SDO_ALLOWED) != 0) {
        srv = COSdoCheck(node->Sdo, &frm);
        if (srv != 0) {
            (void)COSdoResponse(srv);
            (void)COIfCanSend(&node->If, &frm);
            allowed = 0;
        }
    }

with

    if ((allowed & CO_SDO_ALLOWED) != 0) {
        srv = COSdoCheck(node->Sdo, &frm);
        if (srv != 0) {
            CO_ERR status = COSdoResponse(srv);
            if (status != CO_ERR_SDO_ABORT)
                (void)COIfCanSend(&node->If, &frm);
            allowed = 0;
        }
    }

yields the expected result. However I'm not sure if this might have any other negative consequences.

Calling COTmrService from timer ISR is causing page fault

Hi thanks for the wonderful project,

I am trying to use the stack in one of my project.

As per the document had implemented the driver for NVM, CAN Interface and Timer.

Though had confusion about which function to call to update timer then from the clock example added the

COTmrService(&Node.Tmr);

in my timer ISR which is called every 1msec

But when I enable it, is causing page fault at the below line in co_tmr.c

    /* setup next timer event */
    if (tn != 0) {                       
        COIfTimerReload(cif, tn->Delta);       //line causing page fault
    } else {
        COIfTimerStop(cif);
    }
    result = 1;

Am I doing it right?

Environment

STM32
STM32CubeIDE
HAL
FreeRTOS

Compilation of testsuite, running on a 64bit target is not possible

Multiple warnings in the dynamic object directory setup, due to casting the data pointer to a 32bit value. This is not suitable on a 64bit machine. We must use (uintptr_t) in these castings.

  • Target: Testsuite on Win10
  • Cross-Compiler: Visual Studio Build Tool v2019
  • CANopen Stack: v4.0.0

Broken RPDO connection

Hi,
I managed to get my TPDO working. But unfortunately, my RPDO seems broken..

From what I found, I think the comparison
if ((id & CO_RPDO_COBID_REMOTE) == 0) { pdo->Node->Error = CO_ERR_RPDO_COM_OBJ; return (CO_ERR_RPDO_COM_OBJ); }
is not relevant in the RPDO initialization process because we do not need the RTR flag like TPDO. (CORPdoReset() co_pdo.c line 549)

As you don't take care of RTR flag in CO_COBID_RPDO_DEFAULT macro, this comparison is always true.
Pull request : #34

Br;

Custom SDO abort

Hello,

Thanks for the stack.

Is it possible to create a custom SDO abort, by calling the function COSdoAbort with specific code error?

I would like to have more granularity on SDO errors,that the error corresponds to the application.

Julien

CO_COBID_SDO_STD macro appears to truncate standard CAN ID incorrectly

The macro definition for CO_COBID_SDO_STD at line 359 of co_obj.h appears to truncate the CAN ID for an SDO server incorrectly. The macro is defined as:

#define CO_COBID_SDO_STD(valid, dynamic, id)       \
    (((uint32_t)(id) & 0x3ffuL)                  | \
     (((uint32_t)(dynamic) & 0x1u) << 30u)       | \
     ((uint32_t)(1uL - ((valid) & 0x1u)) << 31u))

The line (((uint32_t)(id) & 0x3ffuL) truncates a CAN ID of 0x600 to 0x200, which cannot be correct.

As a result, the macros CO_COBID_SDO_REQUEST() and CO_COBID_SDO_RESPONSE() resolve to incorrect COBIDs of 0x200 and 0x180 (plus node ID) instead of 0x600 and 0x580.

Replacing the mask value of 0x3fful with 0x7fful corrects the problem.

How to get out of a sticky situation

I have a SDO block upload issue I've ran into a couple of times while testing.

The SDO server(based on this stack) is in a BLK_UPLOAD state from a previous unfinished transfer SDO block upload (ex. SDO client got reset during transfer).
SDO client (based on canfestival) now tries to initiate a new block upload, which gets aborted by the SDO server because it doesn't match expected frame format for an existing block upload transfer.

} else if (srv->Blk.State == BLK_UPLOAD) {
    if (cmd == 0xA1) {
        result = COSdoEndUploadBlock(srv);
    } else if ((cmd & 0xE3) == 0xA2) {
        result = COSdoAckUploadBlock(srv);
    } else {
        COSdoAbort(srv, CO_SDO_ERR_CMD); // this is what gets executed
    }
    return (result);
}

Every subsequent attempt by the SDO client to start a new block upload gets aborted, because the SDO server remains in a BLK_UPLOAD state.

How is such a situation supposed to get resolved?
The CiA 301 standard is very clear on what should happen in such a situation during a segmented SDO upload; the new SDO upload initiate frame would terminate the existing transfer start a new one.
However, I cannot find any such clause regarding SDO block upload in the standard.

from looking at canfestival's source code for SDO server, it seems to terminate the existing transfer when receiving a new SDO block upload initiate frame, as well as aborting the new request (but a subsequent request should then work).

How do you think such a situation is supposed to get resolved? I can't find a clear answer in the CiA standard.

Fatal error on NMT reset when CANopen node is not using EMCY module

Describe the bug
Fatal error on NMT reset when CANopen node is not using EMCY module

To Reproduce
Steps to reproduce :
-> No EMCY table in specs (NULL)
-> NMT master sends reset

Reason :
Even if we don't have EMCY table, CONmtReset function will call COEmcyReset which will point on a NULL pointer.

Expected behavior
Either: explain, that EMCY is mandatory. We need to adjust Quickstart example to eliminate confusion.
Or: enhance the implementation of EMCY module to allow a node without EMCY

CANopen Stack (please complete the following information):

  • Device: any
  • OS: any
  • Version 4.1.4

Additional context
n/a

Small documentation issue in TPDO timer example

Hi,

I believe there is a small error in the documentation example for the TPDO timer trigger on this page: https://canopen-stack.org/docs/usecase/tpdo

The timer trigger example shows this as the dict snippet:

{CO_KEY(0x1800, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)5},
{CO_KEY(0x1800, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, CO_COBID_TPDO_DEFAULT(0)},
{CO_KEY(0x1800, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)0xfe},
{CO_KEY(0x1800, 3, CO_UNSIGNED32|CO_OBJ_D__R_), 0, (uintptr_t)0},
{CO_KEY(0x1800, 5, CO_UNSIGNED32|CO_OBJ_D__R_), CO_TEVENT, (uintptr_t)60},

I tried this and it did not work because COTPdoReset() reads words specifically for subindex 3 and 5, which fail with CO_ERR_OBJ_SIZE. Changing sub-index 3 and 5 to type CO_UNSIGNED16 instead of 32 causes it to work correctly.

-Thomas

PDO Timer Config when entering the OPERATE state.

Hi,

Device: [S32K116]
OS: [none]
Version [v4.1.5]

I think there's a possible issue when moving to the OPERATE state. If this is done when already in the operational state a new PDO timer is reconfigured on top of the old one, such that the period of the PDO transmission is then incorrect (doubled, tripled or more). This can be reset by transissioning back to pre-operational, then back to operate, but thought it worth flagging up.

Many thanks, and as I've stated perviously, great work!

All the best,

Stu

Size Preservation During SDO Download

With an expedited SDO download, the primary success path from COSdoDownloadExpedited( ) calls COObjWrValue( ), but passes a width of CO_LONG.

Why is the transfer size not preserved?

err = COObjWrValue(srv->Obj, srv->Node, (void*)&data, CO_LONG, nodeid);

A similar question applies to COObjWrValue( ), why are all calls to COObjWrType( ) or COObjWrDirect( ) hard-coded as CO_LONG?

This makes a custom CO_TYPE rather problematic, since 1, 2, or 3-byte transfers from a well-formed expedited SDO download are being misreported to the custom type's Write( ) function as 4-bytes long.

Make headers compatible with C++

Description

As of yet, the headers of this library are incompatible with C++.
The underlying issues are twofold, but should be easy to fix.

  • The function prototypes are not declared with C linkage when included in a C++ file.
  • The co_types.h header attempts to overwrite C++ keywords such as bool.

Steps to Reproduce

  1. Include co_core.h inside a C++ file and compile:
#include "co_core.h"
canopen-stack/canopen/include/co_types.h:41:26: error: redeclaration of C++ built-in type 'bool' [-fpermissive]
canopen-stack/canopen/include/co_types.h:38: warning: "NULL" redefined
co_callbacks.cpp:64:9: error: conflicting declaration of 'int16_t COLssStore(uint32_t, uint8_t)' with 'C' linkage
...
  1. Include using an extern "C" block:
extern "C" {
    #include "co_core.h"
}
canopen-stack/canopen/include/co_types.h:41:26: error: redeclaration of C++ built-in type 'bool' [-fpermissive]
canopen-stack/canopen/include/co_types.h:38: warning: "NULL" redefined
  1. Force co_types.h to stop redefining bool and NULL by manually defining C-specific preprocessor symbols.
extern "C" {
    #pragma push_macro("__STD_C__")
    #pragma push_macro("__STDC_VERSION__")

    #define __STD_C__        1
    #define __STDC_VERSION__ 201112L
    #include "co_core.h"

    #pragma pop_macro("__STD_C__")
    #pragma pop_macro("__STDC_VERSION__")
}

Expected behavior

Ideally, one should be able to include co_core.h inside a C++ file as-is, without special preprocessor manoeuvres.

The C-linkage issue can be solved by wrapping all headers in the following construct:

#ifdef __cplusplus
extern "C" {
#endif

// Header contents

#ifdef __cplusplus
}
#endif

The issue with co_types.h could be solved by adding another defined check:

#if defined(__STDC__) && (__STDC_VERSION__ >= 199901L) ||       /* ANSI C99 */\
    defined(_MSC_VER) || \
    defined(__cplusplus)

Environment (If Necessary)

  • Target: STM32F103
  • Cross-Compiler: GNU Arm embedded toolchain 9-2020-q2-update
  • CANopen Stack: 4.0.1

Explain TPDO and RPDO CAN addresses

Hi,
i am trying to communicate two modules, in one I set TPDO at the default address 0 (0x180 + node_id) and 1 (0x280 + node_id, and in the second RPDO also at the default addresses 0 (0x200 + node_id) and 1 (0x300 + node_id).
Mappings and objects are also configured.

TPDO0 is sent after changing the object value (via CO_TASYNC) variable increment in the timer, and TPDO1 after pressing the button.

I was able to receive the mapped RPDO, but only if I manually set the COBID as the TPDO address from the first node (0x181 and 0x281).
When debugging the code in the CO_RPDO * CORPdoCheck (CO_RPDO * pdo, CO_IF_FRM * frm) function, the incoming TPDO message identifier from NODE_1 (0x181 and 0x281) is compared to the RPDO in NODE_2 which has default addresses (0x201 and 0x301) and therefore does not process the frame.

NODE 1 (TPDO CONFIGURATION)

    /* PDO PARAMETERS */
    {CO_KEY(0x1800, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)2},
    {CO_KEY(0x1800, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, CO_COBID_TPDO_DEFAULT(0)},
    {CO_KEY(0x1800, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)254},

    {CO_KEY(0x1801, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)2},
    {CO_KEY(0x1801, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, CO_COBID_TPDO_DEFAULT(1)},
    {CO_KEY(0x1801, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)254},

    /* PDO MAPPING */
    {CO_KEY(0x1A00, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)1},
    {CO_KEY(0x1A00, 1, CO_UNSIGNED32 |CO_OBJ_D__R_), 0, CO_LINK(0x2100, 0x01, 16)},

    {CO_KEY(0x1A01, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)1},
    {CO_KEY(0x1A01, 1, CO_UNSIGNED32 |CO_OBJ_D__R_), 0, CO_LINK(0x2100, 0x02, 8)},

    /* OBJECTS */
    {CO_KEY(0x2100, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)2},
    {CO_KEY(0x2100, 1, CO_UNSIGNED16 |CO_OBJ___PR_), CO_TASYNC, (uintptr_t)&Obj2100_01_10},
    {CO_KEY(0x2100, 2, CO_UNSIGNED8 |CO_OBJ___PR_), 0, (uintptr_t)&Obj2100_02_08},

NODE_2 (RPDO CONFIGURATION)

    /* PDO PARAMETERS */
    {CO_KEY(0x1400, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)2},
    {CO_KEY(0x1400, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, CO_COBID_RPDO_DEFAULT(0)},
    {CO_KEY(0x1400, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)254},

    {CO_KEY(0x1401, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)2},
    {CO_KEY(0x1401, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, CO_COBID_RPDO_DEFAULT(1)},
    {CO_KEY(0x1401, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)254},

	/* PDO MAPPING */
    {CO_KEY(0x1600, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)1},
    {CO_KEY(0x1600, 1, CO_UNSIGNED32 |CO_OBJ_D__R_), 0, CO_LINK(0x2100, 0x01, 16)},

    {CO_KEY(0x1601, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)1},
    {CO_KEY(0x1601, 1, CO_UNSIGNED32 |CO_OBJ_D__R_), 0, CO_LINK(0x2101, 0x01, 8)},    

	/* OBJECTS */
    {CO_KEY(0x2100, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)1},
    {CO_KEY(0x2100, 1, CO_UNSIGNED16 |CO_OBJ___PR_), 0, (uintptr_t)&counter},
    
    {CO_KEY(0x2101, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, (uintptr_t)1},
    {CO_KEY(0x2101, 1, CO_UNSIGNED8 |CO_OBJ___PR_), 0, (uintptr_t)&button},

Do I understand the CANOpen stack correctly and the reception of TPDO0 (e.g. 0x181) via NODE_2 should compare the configured RPDO0 (0x201)? @michael-hillmann

RTR Frames

Hi,

Are there any plans to support RTR frames?

Best regards,

Stuart

[Question]: Sync received application callbacks?

Description

This is not a bug, rather a question about using the stack, I didnt find anywhere else to post questions so I hope this is ok. I'm trying to determine if its possible to have an application callback occur once a sync message has been received by the stack and processed? The goal would be to allow the application to be notified when a sync has been received so it can read updated object dictionary values/PDO's/etc.

I see there is an application hook for COPdoReceive(), but this includes the bare CO_IF_FRM * received by the stack, and doesn't include sync information.

Thank you for the nice stack btw, its very easy to use and the documentation is nice.

Environment (If Necessary)

  • CANopen Stack: 3.3.0

Add existing documentation to project pages

There is documentation existing in form of documents. For improving the usability and collaborations, lets convert them into github pages. The automatic jekyll build is configured for the directory /docs. Structure of website is pending; first idea:

Home
+-- Introduction (descibe the overall architecture and philosophy)
+-- Hardware Interfacing (describe how to connect to specific targets)
+-- Configuration (describe how to configure and start the CANopen node)
+-- N x Modules (SDO Server, TPDO, RPDO, etc..)
    +-- M x Services per Module (describe the use case of each function)

ballpark numbers for ROM+RAM requirements

Hi,
this is a promising stack and I am seriously considering it for a small project.

I'm very curious to see some typical figures for ROM + RAM requirements for this stack. Of course I understand these will vary a lot depending on enabled features and OD contents, and also on target architecture, but I think even just having a few data points would be valuable to give a rough estimate of what to expect. I understand @shersey-locus has some code running on a stm32F405 from reading some discussions here a while back? That would be one good example.
Another one that would be nice to list is the smallest known, minimal device implementation - just to set a lower limit to adjust expectations.

One can of course do the exercise of configuring + compiling for their target, but I think some basic data could avoid some time loss and frustration.

Explain possible usecases for transmission of PDO

Is your feature request related to a problem? Please describe.
Multiple questions regarding the topic of PDO transmissions. This should be better described in the usage area of the documentation.

Describe the solution you'd like
Improve the usage description section: Triggering TPDO

should include the use cases:

  • timer based sending as defined in the standards
  • transmit on change of object entry as described in the quickstart
  • transmit with the application API function calls

Describe alternatives you've considered
n/a

Additional context
n/a

Running on 16bit architectures

I have been trying to run canopen-stack on AVR 16 Bit boards. After hours trying to understand why SPO were not being set, I found out that the Dictionary assumes uintptr_t is 32-bit. I was seeing some warning but did not pay attention. In this case it looses the bit 30 and the SPO is not configured.

Now if I change the CO_OBJ_T to use uint32 on the Data field, I cant map object to the dictionary. I keep getting warning: large integer implicitly truncated to unsigned type [-Woverflow] and I cast twice then I get error: initializer element is not constant.

I would ask:

  1. It there a way to make this with with 16 bit pointers?
  2. If this is not supported, show the build fail with an error?

I have to say I really enjoyed the library and the documentation. A couple of things could be improved but I'm overall very impressed. I know these boards may be on the low end, but I'm doing this on a hobby budget.

I managed to fit it on the 2K RAM board by cutting SPO buffers down (CO_SDO_BUF_SEG = 15), but I have yet to test using SPOs and have no reference on how tight this can be. I would love to be able to run this stack on Arduino Nanos and MCP2515 cheap boards. Have to say I'm new to CanOpen, but wanted to not reinvent the wheel.

Object Parameter Validity

Hi,

  • Device: [S32K116]
  • OS: [none]
  • Version [v4.1.5]

Great work on the stack! We have it implemented on a NXP S32K116, and it works really well. During the implementation though we've noticed a few discrepencies (possible bugs). So I thought I'd post what we've observed.

'Object Parameter Validity'

Some CANopen objects seem to accept invalid values. This is true of objects such as COBID SYNC (0x1005), and COBID EMCY (0x1014) when '0' is set. It's not accepted as a valid value by the stack, but it is accepted i.e. an 'invalid' respose is not transmitted back to the master.

Also, and I think this is more in the realm of a feature request, but it is related. Would it be possible to create a mechanism that would allow limits on object parameters to be easily configured such that if they were exceeded, an 'invalid' CANopen response was transmitted back to the master?

Many thanks,

Stu

Enable/Disable features of stack

Hi,

Is it possible to enable/disable some of the features of canopen-stack. I am considering not to use NMT, LSS slave. Can i configure them not to build in stack. Please suggest.

Thanks

Small error in quickstart mandatory objects documentation

Within the mandatory object entries of the quickstart documentation, the table row for identity object 1018h subindex 0h holds a wrong value (1). This value should be 4, because we provide entries for subindex 1 to 4 (Note: source code is correct).

Missing object dictionary entries in quickstart example

Description

First of all, thank you for releasing this high-quality stack under an open source license! I've worked with the CanFestival stack in the past, but it is not actively maintained and has some strange quirks.

While experimenting with this stack, I bumped into a couple of documentation issues. The steps described in the quickstart guide do not lead to a minimal working example of the CANopen stack. In the example, only the first subindex of the identity object (1018h) is given, but the LSS module requires all 4 subindices. Similarly, the Sync COB-ID (1005h) is missing from the example, but is required by the sync subsystem. Finally, I think it would be a good idea to show explicitly how object dictionary variables (e.g. Obj1001_00_08) need to be defined.

Steps to Reproduce

  1. Create a node with an object dictionary as given by the quickstart guide
#include "co_core.h"

/* define the static object dictionary */
const CO_OBJ ClockOD[] = {
    {CO_KEY(0x1000, 0, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0},
    {CO_KEY(0x1001, 0, CO_UNSIGNED8 |CO_OBJ____R_), 0, &Obj1001_00_08},
    {CO_KEY(0x1017, 0, CO_UNSIGNED16|CO_OBJ_D__R_), 0, 0},
    {CO_KEY(0x1018, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 1},
    {CO_KEY(0x1018, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0},

    {CO_KEY(0x1200, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 2},
    {CO_KEY(0x1200, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, 0x600},
    {CO_KEY(0x1200, 2, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, 0x580},

    {CO_KEY(0x1800, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 2},
    {CO_KEY(0x1800, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x40000180},
    {CO_KEY(0x1800, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 254},

    {CO_KEY(0x1A00, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 3},
    {CO_KEY(0x1A00, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x21000120},
    {CO_KEY(0x1A00, 2, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x21000208},
    {CO_KEY(0x1A00, 3, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x21000308},

    {CO_KEY(0x2100, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 3},
    {CO_KEY(0x2100, 1, CO_UNSIGNED32|CO_OBJ___PR_), 0, &Obj2100_01_20},
    {CO_KEY(0x2100, 2, CO_UNSIGNED8 |CO_OBJ___PR_), 0, &Obj2100_02_08},
    {CO_KEY(0x2100, 3, CO_UNSIGNED8 |CO_OBJ___PR_), 0, &Obj2100_03_08},  
};
/* set number of object entries */
const uint16_t ClockODLen = sizeof(ClockOD)/sizeof(CO_OBJ);
  1. Compile and start the example. Observe that the application crashes and Clk->Error is set to CO_ERR_CFG_1018 inside COLssInit().

  2. Add extra subindices for product code, revision ID, and serial:

    {CO_KEY(0x1018, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 4},
    {CO_KEY(0x1018, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0},
    {CO_KEY(0x1018, 2, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0},
    {CO_KEY(0x1018, 3, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0},
    {CO_KEY(0x1018, 4, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0},
  1. Observe that the application still crashes. Clk->Error is now set to CO_ERR_CFG_1005_0 inside COSyncInit().

  2. Add the missing index:

   {CO_KEY(0x1005, 0, CO_UNSIGNED32|CO_OBJ____RW), 0, (uintptr_t)&Obj1005_00_32},
  1. The example should now run.

Expected behavior

Ideally, the quickstart guide should build into a working example application.

Environment (If Necessary)

  • Target: STM32F103
  • Cross-Compiler: GNU Arm embedded toolchain 9-2020-q2-update
  • CANopen Stack: 4.0.1

Design choices question (driver interface)

Hello, why don't you make a timer, mem, bus interfaces based on callbacks? it would let the rest of the code build and run perfectly in a "dummy" mode; there are just simple vtables (struct) handling n-delegated-operations? indeed you do that later on, instead of having a proper timer, memory and bus standalone interfaces for vendor to feed with callbacks? would cut half of the code base.

How to set up object dictionary entry for a multibyte data buffer?

I'm adding a firmware update capability to my CANopen devic, and I have a few conceptual questions that I still haven't puzzled out.

My approach here is to define a 256-byte data buffer object in the device, fill it with data via SDO block writes, and then trigger a flash write of the buffer to the external flash by an SDO write to a separate control variable that has a custom CO_OBJ_TYPE.

The custom CO_OBJ_TYPE for the control variable seems quite straightforward; I mainly need to implement a CO_OBJ_WRITE_FUNC for that object type that uses the new object value to start the flash write operation.

The part that is giving me trouble here is knowing how to correctly define a block of N bytes in the object dictionary so that I can write to it using SDO block write operations. What is the best way to do this?

Thanks again for this software stack and for the help you've provided in using it.

Steve

Compile-time warnings: declared inside parameter list, incompatible-pointer-types, unused-but-set-variable

Description

The compiler emits 3 distinct types of warnings when compiling the stack:

  1. declared inside parameter list:
    In file included from ../dependencies/canopen-stack/canopen/include/co_nmt.h:28,
                     from ../dependencies/canopen-stack/canopen/source/co_nmt.c:21:
    ../dependencies/canopen-stack/canopen/include/co_obj.h:276:56: warning: 'struct CO_NODE_T' declared inside parameter list will not be visible outside of this definition or declaration
      276 | typedef uint32_t (*CO_OBJ_SIZE_FUNC) (CO_OBJ *, struct CO_NODE_T *, uint32_t);
          |                                                        ^~~~~~~~~
    
  2. -Wincompatible-pointer-types:
    ../dependencies/canopen-stack/canopen/source/co_nmt.c:72:42: warning: initialization of 'int16_t (*)(CO_OBJ *, struct CO_NODE_T *, void *, uint32_t)' {aka 'short int (*)(struct CO_OBJ_T *, struct CO_NODE_T *, void *, long unsigned int)'} from incompatible pointer type 'int16_t (*)(CO_OBJ *, struct CO_NODE_T *, void *, uint32_t)' {aka 'short int (*)(struct CO_OBJ_T *, struct CO_NODE_T *, void *, long unsigned int)'} [-Wincompatible-pointer-types]
       72 | const CO_OBJ_TYPE COTNmtHbCons = { 0, 0, COTypeNmtHbConsRead, COTypeNmtHbConsWrite };
          |                                          ^~~~~~~~~~~~~~~~~~~
    
  3. -Wunused-but-set-variable:
    ../dependencies/canopen-stack/canopen/source/co_pdo.c:908:16: warning: variable 'cod' set but not used [-Wunused-but-set-variable]
      908 |     CO_DICT   *cod;
          |                ^~~
    

As I understand it, issues 1 and 2 are caused by the order of definitions: the function prototypes that take CO structs as arguments are declared before the definition of those structs. To solve these issues, the header files will need to be restructured.

Issue 3 is much simpler to solve: variable cod should be removed from COTypePdoComIdWrite and inside COTypeNmtHbConsWrite.

Steps to Reproduce

Below is a verbose build log. The log file includes the GCC invocations used to compile the CANopen stack:

build.log

Expected behavior

The stack should compile without warnings. Compiler warnings usually point to underlying issues that can unexpectedly become real problems.

Environment

  • Target: STM32F103
  • Cross-Compiler: GNU Arm embedded toolchain 9-2020-q2-update
  • CANopen Stack: 4.0.2

Multi-frame Buffer Issue

Hi,

Device: [S32K116]
OS: [none]
Version [v4.1.5]

In respect to multi-frame CANopen SDO write requests, the buffer pointer (COObjWrBufCont) always only contains the last frame (7 bytes), yet the buffer should contain 127 x 7 bytes. As such it is only possible to access the most recent 7 bytes transmitted. Could you confirm this issue please, and advise if it a planned to be fixed?

Many thanks,

Stu

Need help to understand correct way to use the stack

Description

Hi. Firstly, thanks for the nice stack. This is by far the best I found. While I am able to get the stack partially working, I am not sure if what I am doing is right. I am using a linux machine and using the virtual can (socket can) before I move it to a micro-controller. I am able to get the quickstart example to send out the time every one second as can messages but I had to do the following to get there.

Steps to Reproduce

  1. Implemented the timer (co_timer_dummy.c) using <time.h>
  2. Implemented the can (co_can_dummy.c) using <linux/can.h>, <sys/socket.h>, <poll.h> (read is non-blocking)
  3. Implemented the nvm (co_nvm_dummy.c) using a simple uint8_t buffer and memcpy methods
  4. Added COTmrService(&Clk.Tmr); to the while(1) loop in clock_app.c
  5. Initialized a can frame using CO_IF_FRM time_data; inside the static void AppClock(void *p_arg) method, populated it and sent using COIfCanSend(&Clk.If, &time_data);

Expected behavior

I was expecting the node to now send the time over can each second and if I send a NMT command to switch it's state to stopped (2) using cansend vcan0 000#0201 or pre-operational (128) using cansend vcan0 000#8001 , I was expecting it to stop.

Seen behavior

The node runs but does not send any messages automatically every 1 second. But if I comment out my method to read can messages in the static int16_t DrvCanRead (CO_IF_FRM *frm) method and send any message eg: using cansend vcan0 000#0101 , it starts sending messages but I cant stop it. Problem with this method is that it works only with CONmtSetMode(&Clk.Nmt, CO_OPERATIONAL); present before the loop starts. I would not be able switch the state dynamically. When I do read using DrvCanRead, I can see using debug that the state actually changes but in this condition, no messages are sent.

I am unsure of the correct way to get things working. Thanks in advance for your help.

Environment (If Necessary)

  • Linux (Ubuntu 18.04)
  • CANopen Stack

PDO Mapping is Modifiable

Hi,

Device: [S32K116]
OS: [none]
Version [v4.1.5]

PDO mapping shouldn’t be able to be modified, but the CANopen Stack seems to allow access / changes to the mapping outside of the proper process. I thought it worth mentioning as it leads to CCTT errors.

Kind regards,

Stu

Introduce a flexible interface for timer drivers

Description

CO_TMR_TICKS_PER_SEC is currently hardcoded to a value of 100 inside co_tmr.h. It might be useful to make this value configurable, seeing that many CANopen communication parameters are more fine-grained than 10ms. Examples include cycle period (in us), TPDO inhibit time (in multiples of 100us), and producer heartbeat time (in ms).

Proposed solution

The simplest solution would be to move CO_TMR_TICKS_PER_SEC from co_tmr.h to co_cfg.h.

Optionally, the original definition inside co_tmr.h could be wrapped so that a fall-back value is used if CO_TMR_TICKS_PER_SEC is missing, e.g.:

/* co_tmr.h */
#ifndef CO_TMR_TICKS_PER_SEC
#define CO_TMR_TICKS_PER_SEC     100
#endif

Optionally, a static_assert could be used to guarantee that 1000 / CO_TMR_TICKS_PER_SEC evenly:

_Static_assert(1000 % CO_TMR_TICKS_PER_SEC == 0, "Bad configuration for CO_TMR_TICKS_PER_SEC");

Note that _Static_assert is part of the C11 standard.
I do not know which range of compilers / language features you wish to support in this project, but that might be something to take into consideration.

Finally, this change could introduce potential issues inside the definition inside this macro:

#define CO_TMR_TICKS(ms)         ((ms)/(1000/CO_TMR_TICKS_PER_SEC))

If CO_TMR_TICKS_PER_SEC is set to 10000 in order to allow a time granularity of 100us, the expression 1000/CO_TMR_TICKS_PER_SEC would evaluate to 0, leading to a divide-by-zero bug. FreeRTOS has a macro called pdMS_TO_TICKS() with similar issues; this thread discusses some of the design implications. In the case of this project, it might be useful to add a second macro CO_TMR_US_TO_TICKS for situations where fine-grained timing is possible.

Defining more than 4 TPDOs

The feature description in the top-level README states that the stack supports an unlimited number of TPDOs and RPDOs; however, the code that defines the CAN-IDs for PDOs increments the ID by 0x100 for each PDO, so if I define more than the default 4 TPDOs in this way, the 5th will collide with the CAN-ID of the default server-to-client SDO. I seem to be failing to understand something here.

What am I missing here? Can I in fact define additional TPDOs without doing major surgery on the stack, and if so, how should I do that?

Thanks,
Steve

Documentation Bug on StringObject

Describe the bug

In the example in docs/docs/usecase/dictionary.md the type is incorrect:

  const CO_STRING {
      0,                  /* variable for read position     */
      &DemoString[0]      /* start address of string memory */
  } DemoStringObj;

It should be CO_OBJ_STR also on Mac using const will result in bus error when reading since the Offset has to be updated on a a read operation. Updating an object like this on a read seems odd (more below).

I also think the Domain example is incorrect.

Additional context

This made me look at the code and question why this object is needed and why the offset is this object. It is not thread-safe, and if two SDO server interleave the read the offset cannot be relied upon. So my reading is that :

  1. Some protection exist to avoid concurrent and inter-leafed reading of large objects.
  2. OR users of the large object read functions have to reset the offset before each read, which again creates are temporal coupling and potential risk.

I'm not fully sure how these object should be used, but my intuition is that a CODictRdBuffer call should should include some sort of descriptor by reference (like the FD in files). The "FD" would allows you to control that reader offset, indicate if end of buffer was reached, etc. It also make each reader responsible for keeping its own offset. The API is kind of strange, if the buffer I use is too small I don't know how many bytes were copied? Also from string the \0 is not added.

Again this last comment is based on just writing tests and getting unexpected behaviour. I could be missing the large picture.

Callback function for reception of data in SDOs

My application uses a good half-dozen configurable parameters to control a sensor. I've configured them as SDO variables because of the limited number of default RPDOs, and because it is desirable to be able to set the parameters individually in pre-operational mode as well as operational mode.

However, there's no efficient way to determine when a new parameter value has been received - there is no callback mechanism for SDO transactions as there is for PDOs. As a result, my main loop has to read every parameter from the object dictionary at every sensor update, which I find computationally inefficient. The lack of a callback for SDO operations also makes it difficult to efficiently implement one-time command or parameter-change events that don't involve NMT mode changes. The limited supply of PDOs restricts my ability to just add more RPDOs to handle action-on-value-change events.

My current method for this is to constantly re-read a command code from the object dictionary and compare it to a previous-command-code variable to detect a change, a process I find inelegant at best.

For these reasons, it would be useful to have a callback mechanism analogous to that used for TPDOs and RPDOs that would allow me to trigger internal operations when an SDO transaction has occurred. Is there a reason that such a mechanism would be a bad idea, or architecturally inconsistent with the CANopen device model?

Thanks,
Steve

Requesting a fully-populated source example

I'm an experienced embedded developer, but entirely new to using CAN and CANopen, and I'm trying to use this CANopen stack to implement my first CANopen-based device. It's turning out to be a steep learning curve.
Because I'm new to CANopen, there are many assumptions baked into the various support stacks that I haven't yet learned (for example, are all of the services supported by the stack required, or are some optional? Can I build an application without a heartbeat or LSS? How to determine how many timers, SDO buffers are needed? How do I correctly report an error with the emergency table, respond to incoming RPDOs, trigger outgoing TPDOs, safely update variables that the object dictionary refers to?)

One of the resources I generally look for is a fully-worked-out sample application (such as the clock application listed in the documents for this stack). A complete set of source code, even for an MCU architecture different from those I use, always provides insight into how to successfully use the software stack in question, and generally reveals things I didn't learn from the documentation.

Unfortunately, I'm unable to find any complete source code examples whatever for this CANopen stack. There are various sets of documentation on the 'Net that relate to this stack, but they vary in quality and in how well they actually match the API in this repository, and none of them provide the integrated snapshot that a simple full source example delivers.

I'm acutely aware that questions always outnumber answers, and I always hesitate to ask others to do unpaid work on my behalf, so I shall limit myself to suggesting that including a full source example such as the clock would be of great help to those new to the stack.

What is the expected behavior of a dual SDO Request on a single server?

Hello, I am quite new to CanOpen and learning a lot from your implementation.

I was wondering if the behavior of a double access on a single server is defined in the 301 standard.

Example:

  1. Node 1 requests a segmented upload from the Node 2 via the 0x602 request.
  2. The requests arrive to let's say 5/10 segments
  3. The Node 3 requests an expedited request via the 0x602 request.

What should happen here? From what I understand of your implementation, the Server 0x602 will accept the request without changing the Active Object because of the Node 1 request uses the same RxID. Will the Node 2 abort the request via a 0x582 answer and therefore kill both Node 1 and Node 3 requests when it realizes the CCS doesn't fit?

My workaround was to declare 2 servers on the Node 2 and make Node 1 communicate via 0x612 and Node 3 via 0x632.
But this solutions seems wrong to me...

Could you guide me toward the proper standard solution or even better did I misunderstand the implementation and this exception is already handled somehow?

Thank you and sorry if I was unclear on some points or lacking in my understanding.

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.