CANopen Know How - Dynamic Object Directory

by Michael Hillmann (comments: 0)

In the following two articles I want to provide a flexible and comfortable way in managing CANopen device software with dynamically generated object directories.

The first part describes tool functions for managing the object directory. The second part concentrates on usage of the newly introduced helper functions.

Resource Estimation

The CANopen communication library is optimized for minimal resource requirements. The default usage and descriptions assume, that you want to minimize the usage of RAM. For this reason the manuals and templates declare the object directory storage as constant. Most of the object directory is then stored in nonvolatile memory (e.g. FLASH).

For a guess on how big the difference is, we have a quick look into the reference manual (see chapter "6.3.4 Object Directory Table"). The reference manual explains, that each object directory entry needs 3 x 32Bit values.

Constant Object Directory Dynamic Object Directory
The complete Object Directory (including mandatory pointers to global variables) is stored in non-volatile memory. The changing data is stored in RAM. 100% of Object Directory is stored in RAM. The use of pointers to global variables is optional.
Example: Object directory with 256 object entries result in: 256 * 12 Byte = 3 kByte FLASH and some global variables in RAM Example: Object directory with 256 object entries result in: 256 * 12 Byte = 3 kByte RAM and optional some global variables in RAM

Note, the term "dynamic" means that we are able to create and change object entries within the object directory during run time up to a defined amount of object entries.

The allocation of the required memory is still "static" - we don't need to use dynamic memory allocation in embedded systems with all the well known drawbacks.

Finally: We can improve the flexibility of our CANopen application software when working with a dynamic object directory.

Module: app_dir

The CANopen stack requires, that the object directory is a sorted array of object entries of the type "CO_OBJ". The object entries need to be sorted ascending by the index/Sub-index information. This allows the CANopen communication library a fast and efficient search of a specific entry.

Object Definition

Lets start with our application module "app_dir", which is responsible for our dynamic object directory management. First we define a structure for holding the variable data of the dynamic object directory:

#include "co_core.h"

typedef struct {
    CO_OBJ     *Root;
    CPU_INT32U  Len;
    CPU_INT32U  Used;
} OD_DYN;

The member "Root" will hold the start address of the object directory. The member "Len" will hold the maximal number of object entries in the object directory. The member "Used" will hold the current number of used object entries.

Local Helper Functions

Some small local functions will help us to get the following implementations of the object directory management functions as readable as possible. The function ObjSet() allows us to set the values of a single object entry to the given values.

static void ObjSet(CO_OBJ      *obj,
                   CPU_INT32U   key,
                   CO_OBJ_TYPE *type,
                   CPU_INT32U   data)
{
    obj->Key  = key;
    obj->Type = type;
    obj->Data = data;
}

The function ObjCpy() copies all values from the second given object entry to the first given object entry.

static void ObjCpy(CO_OBJ *a, CO_OBJ *b)
{
    a->Key  = b->Key;
    a->Type = b->Type;
    a->Data = b->Data;
}

The function ObjSwap() exchanges all values of the given object entries.

static void ObjSwap(CO_OBJ *a, CO_OBJ *b)
{
    CO_OBJ x;

    ObjCpy(&x,  a);
    ObjCpy( a,  b);
    ObjCpy( b, &x);
}

The function ObjCmp() masks the relevant bits out of the member "key" for comparisons. We must consider only the index and Sub-index infor-mation. The type information and the additional flags must be ignored during the sort comparisons.

static CPU_INT16S ObjCmp(CO_OBJ *a, CO_OBJ *b)
{
    CPU_INT16S result = 1;

    if (CO_GET_DEV(a->Key) ==
        CO_GET_DEV(b->Key)) {
        result = 0;

    } else if (CO_GET_DEV(a->Key) <
               CO_GET_DEV(b->Key)) {
        result = -1;
    }

    return (result);
}

Initialization Function

When starting the CANopen application, we have to specify the object directory for this node. Therefore we must define the object directory content before starting the CANopen application. The next simple service function will provide us a clean and empty object directory.

void ODInit (OD_DYNC *self, CO_OBJ *root, CPU_INT32U len)
{
    CPU_INT32U  idx;
    CO_OBJ      end = CO_OBJ_DIR_ENDMARK;
    CO_OBJ     *od;

    idx = 0;
    od  = root;
    while (idx < len) {
        ObjCpy(od, &end);
        od++;
        idx++;
    }

    self->Root = root;
    self->Len  = len - 1; /* <-- */
    self->Used = 0;
}

Note the marked line for setting the maximal number of object entries. The CANopen reference manual (see chapter "6.3.4 Object Directory Table") requires a "CO_OBJ_DIR_ENDMARK" at the end of the used object directory area. For this reason, we reduce the maximal number of object entries by 1.

Add & Update Function

The function for adding a new object entry needs 3 arguments besides of the object handle reference "self", derived from the CANopen reference manual (see chapter "6.3.4 Object Directory Table"):

  • First argument is the "key", which includes the information of index, Sub-index, type and some additional flags to a single 32 bit value. This value should be created with the macro CO_KEY(..).

  • The second argument "type" is a reference to a structure of type-functions. For basic object entries, this argument is 0. The system data types are explained in the CANopen reference manual (see chapter "6.3.4 Object Directory Table"). Some selected system data types are explained in later examples, too.

  • The last argument "data" holds the data of this object entry. The interpretation of this data depends on the additional flags of the key. We have a look at this relationship later in the examples.

This leads us to the following function:

void ODAddUpdate(OD_DYN      *self,
                 CPU_INT32U   key,
                 CO_OBJ_TYPE *type,
                 CPU_INT32U   data)
{
    CO_OBJ  temp;
    CO_OBJ *od;

    if ((key == 0) ||
        (self->Used == self->Len)) {
        return;
    }

    od = self->Root;
    while ((od->Key != 0) &&
           (ObjCmp(od->Key, key) < 0)) {
        od++;
    }

    if (ObjCmp(od->Key, key) == 0) {
        ObjSet(od, key, type, data);        /* Change existing entry */

    } else if (od->Key == 0) {
        ObjSet(od, key, type, data);        /* Add at End */
        self->Used++;

    } else {
        ObjSet(&temp, key, type, data);     /* Insert */
        do {
            ObjSwap(od, &temp);
            od++;
        } while (od->Key != 0);
        ObjCpy(od, &temp);
        self->Used++;
    }
}

The setup of the object directory is performed during the initialization phase of the CANopen node. Therefore the performance is not the most critical part. Nevertheless, we want to have an algorithm, which is linear ( O(n) ) when raising the number of object entries.

In principal, we want to walk through the (sorted) list one time and insert the new object entry at the right position. All used object entries behind the insert position must be shifted by 1 place to the end.

Set Object Directory Specification

This function is needed to set our new dynamic object directory start address and the number of possible object entries (including the end mark) in the CO_SPEC structure. This is useful during the initializing of the CANopen communication library.

void ODSetSpec(OD_DYN *self, CO_NODE_SPEC *spec)
{
    spec->Dir    = self->Root;
    spec->DirLen = self->Len + 1;
}

Fazit

That's it. Now we own a set of functions for managing the CANopen Object Directory. This includes functions for:

  • Object Handling (Set, Copy, Swap Compare)
  • Object Directory Handling (Init, Add & Update)
  • Device Specification Handling

We are now ready to go for using these functions in the next article.

Any questions or remarks before we start using these functions in my next article?

Go back

Add a comment

Update Notification

For an automatic notification on new blog articles, just register your EMail address.

Subscription

We are the Blogger:

Andrea Dorn

After my study of industrial engineering I worked at an engineering service provider. As team leader and sales representative, I was responsible for customers from aviation and mechanical engineering. I am part of the Embedded Office team since 2010. Here I am responsible for the Sales and Marketing activities. I love being outside for hiking, riding or walking no matter the weather.

Fridolin Kolb

I have more than 20 years experience in developing safety critical software as developer and project manager in medical, aerospace and automotive industries. I am always keen on finding a solution for any problem. The statement “This won’t never work”, will never work for me. In my spare time You can find me playing the traverse flute in our local music association, spending time with my family, or in a session as member of our local council and member of the local church council. So obviously I am lacking the ability to say “No” to any challenge ;-).

Michael Hillmann

I have been working for 20 years in safety critical software development. Discussing and solving challenges with customers and colleagues excites me again and again. In my spare time I can be found while hiking, spending time with my family, having a sauna with friends - or simply reading a good book.

Wolfgang Engelhard

I’m a functional safety engineer with over 10 years of experience in software development. I’m most concerned with creating accurate documentation for safety critical software, but lately found joy in destruction of software (meaning I’m testing). Spare time activities range from biking to mentoring a local robotics group of young kids.

Matthias Riegel

Since finishing my master in computer science (focus on Embedded Systems and IT-Security), I’ve been working at Embedded Office. Before that, I worked with databases, and learned many unusual languages (like lisp, clojure, smalltalk, io, prolog, …). In my spare time I’m often on my bike, at the lathe or watching my bees.