Embedded Office Article

How to setup a dynamic CANopen object dictionary

This article is the first of two writings to achieve a flexible and comfortable way of managing CANopen device software with dynamically generated object dictionaries during runtime.

The first part describes tool functions for managing the object directory, while the second part illustrates the usage of the introduced tool functions.

Resource Estimation

Embedded Office has optimized the CANopen stack for minimal resource requirements. The default examples and descriptions assume that you want to minimize the usage of RAM. For this reason, the manuals and templates declare the object dictionary storage as a const array. The keyword const instructs the linker to place the object dictionary in nonvolatile memory (e.g., FLASH).

For an estimation of how big the difference is, we have a quick look into the reference manual. The reference manual explains that each object dictionary entry needs 3 x 32bit values. Therefore, at an object dictionary with 256 object entries, we talk about: 256 * 12 Byte = 3 kByte

CANopen Dictionary Memory Estimation

In case of a static object dictionary, the CANopen stack stores the entire dictionary (including pointers to the changing data) in non-volatile memory. On the dynamic side, the CANopen stack stores the object dictionary in RAM, along with the changing data. The use of pointers to the changing data is optional.

Note, the term "dynamic" means that we can create and change an object entry within the object dictionary during runtime up to a defined amount of object entries.

The allocation of the required memory is still "static" - we don't need (and want) 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 dictionary.

Module: app_dict

The object dictionary is an array of object entries. We must store the object entries ascending by the index/sub-index information. This requirement ensures that the CANopen stack can perform fast and efficient access to a specific entry.

Object Definition

Let's start with introducing a new module, called app_dict (application dictionary). This module is responsible for the 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  Length;
    CPU_INT32U  Used;
} OD_DYN;

The member Root will hold the start address of the object dictionary. The member Length stores the maximal number of object entries in the object dictionary, and 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 dictionary management functions as readable as possible.

The service 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 service ObjCpy() will copy 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 service ObjSwap() will exchange the content 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 service ObjCmp() will compare the index and subindex information and return the result of the comparison similar to strcmp()

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 node, we have to specify the object dictionary as an argument for this node.

The next service function will provide us a clean and empty object dictionary.

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

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

    self->Root = root;
    self->Length  = length - 1;  /* <-- */
    self->Used = 0;
}

Note the marked line for setting the maximal number of object entries. The CANopen stack requires a CO_OBJ_DIR_ENDMARK at the end of the used object dictionary area. For this reason, we reduce the maximal number of (user) object entries by 1.

Add & Update Function

The function for adding a new object entry needs three arguments besides the object handle reference self:

  • First argument is the key which includes the information of index, subindex, type and some additional flags to a single 32bit value. We should create this value with the macro CO_KEY(..).

  • The second argument, type is a reference to a structure of type-functions. For primitive object types, this argument is 0. The CANopen reference manual explains the system data types. Some selected system data types are described in later examples, too.

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

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->Length)) {
        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 generation of the object dictionary executes 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 principle, we want to walk through the (sorted) list one time and insert the new object entry in the right position. All used object entries behind the insert position must be shifted by one place to the end.

Set Object Dictionary Specification

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

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

Conclusion

That's it. Now we own a set of functions for managing the CANopen object dictionary. This set includes services for:

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

We are now ready to go for using these functions in the next article: How to use the dynamic CANopen object dictionary.

Create Your Free Account
Create an account to get access to free Embedded Office services
Access free Embedded Office services
Related Links
Read details for the CANopen stack
How to use the dynamic CANopen object dictionary
© Copyright 2019. Embedded Office GmbH & Co. KG. All rights reserved. (Version: 0f9b52c)