CANopen Know How - Using Dynamic Object Directory

by Michael Hillmann (comments: 0)

In the first part of this article, we have seen how to write some helpful management functions for dynamic Object Directories.

Now we want to see the usage of our brand new dynamic Object Directory. For this reason, we will build up the simple demo "Quickstart" with a dynamic Object Directory. For details of the object entries, please have a look to the Reference Manual (see chapter "Object Directory Table").

Overview

We should have a short look into the Quickstart Guide (see chapter "Overview") as well. This chapter explains the demo. The following list summarizes the needed Object Directory entries.

Object Entries, mandatory in CiA DS301:

  • Device Type (Index 1000h)
  • Error Register (Index 1001h)
  • Heartbeat Time (Index 1017h)
  • Identity Object (Index 1018h)
  • SDO Server (Index 1200h)

Object Entries, optional in CiA DS301 but needed by our demo:

  • Transmit PDO Communication (Index 1800h)
  • Transmit PDO Mapping (Index 1A00h)

Object Entries, manufacturer specific for this demo:

  • Clock Data (Index 2100h)

Create Object Entries

First we will take a look on all CiA DS301 object entries one by one. After getting these building blocks, we will combine them into a function, which creates our complete Object Directory.

Device Type

The device type is a basic type 32bit value, which identifies the CANopen device. Due to the fact, that our demo is not a real CANopen device, we store a constant 32bit dummy value (0x00000000) in this entry.

:
ODAddUpdate(self, CO_KEY(0x1000, 0, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x00000000);
:

We choose the additional flags "CO_OBJ_D__R_" to store the data directly (D) in the object entry and allow read access (R) from the communication interface.

Error Register

The error register is a basic type 8bit value, which holds the error register flags. The CANopen EMGY module manages the single error register flags.

:
ODAddUpdate(self, CO_KEY(0x1001, 0, CO_UNSIGNED32|CO_OBJ___PR_), 0, &ErrReg);
:

For this entry, we choose the additional flags "CO_OBJ___PR_" to store a pointer to the data in the object entry (_ instead of D), allow the PDO mapping (P) and allow read access (R) from the communication interface.

Heartbeat Time

The heartbeat producer is a system type 16bit value, which holds the time in ms between two heartbeats. The CANopen NMT module manages the system reactions on read or write accesses.

:
ODAddUpdate(self, CO_KEY(0x1017, 0, CO_UNSIGNED32|CO_OBJ____RW), CO_THEARTBEAT, &HbTime);
:

For the system type "CO_THEARTBEAT", we must choose the storage of a data pointer in the object directory (_ instead of D) according to the Reference Manual (see chapter 6.3.4.3 Object Data Reference). To get a CiA DS301 conform system, we should disallow the PDO mapping (_ instead of P) and allow read (R) and write (W) access from the communication interface for the heartbeat time. This leads to the additional flags: "CO_OBJ____RW".

Identity Object

The identity object is a more detailed structure of general CANopen node information. Due to the fact, that our demo is not a real CANopen device, we store a constant 32bit dummy value (0x00000000) in the mandatory entry at Sub-index 01h.

:
ODAddUpdate(self, CO_KEY(0x1018, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 0x01);
ODAddUpdate(self, CO_KEY(0x1018, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x00000000);
:

The Sub-index 00h holds the highest supported Sub-index in this index array as a constant 8bit value. Note: we can remember this as a general convention for index arrays, defined by the CiA DS301.

SDO Server Function

We will encapsulate the SDO server communication object entry in a separate function. This allows us to add multiple SDO servers with a smile. The Sub-index 01h is the CAN identifier (called COB-ID in CANopen) for the SDO request, the Sub-index 02h holds the CAN identifier for the SDO response. The CiA DS301 specifies for SDO server #0 the values for this mandatory

SDO server #0:

  • SDO request : COB-ID = 600h + NodeID
  • SDO response : COB-ID = 580h + NodeID

For all additional SDO servers (#1 .. #127) the identifiers must be selected by the manufacturer. To get full flexibility and highest possible guidance by the function, we define the following function:

void CreateSDOServer(CPU_INT08U srv,
                     CPU_INT32U request,
                     CPU_INT32U response)
{
    if (srv == 0) {
        request  = 0x600;
        response = 0x580;
    }
    ODAddUpdate(self, CO_KEY(0x1200+srv, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 0x02);
    ODAddUpdate(self, CO_KEY(0x1200+srv, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, request);
    ODAddUpdate(self, CO_KEY(0x1200+srv, 2, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, response);
}

We choose the additional flags for the request and response identifiers to allow the storage of the identifiers independent of the used CANopen NodeID. The NodeID may change over time and we don't want to update all depending object entries. Well, we can realize this by enabling the NodeID flag "N" in the additional flags to remove the current NodeID while writing and adding the current NodeID while reading the object entry through the CANopen communication library API. Due to this feature, I highly recommend the usage of the provided API.

TPDO Communication Function

Now we want to setup the transmit PDO (TPDO) communication object entry with a similar service function. This static PDO communication index array contains powerful settings. For details on the influence on the transmission behavior of each single Sub-index please have a look into the CiA DS301 specification.

void CreateTPDOCom(CPU_INT08U  num,
                   CPU_INT32U  id,
                   CPU_INT08U  type,
                   CPU_INT16U  inhibit,
                   CPU_INT16U  evtimer)
{
    ODAddUpdate(self, CO_KEY(0x1800+num, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 0x05);
    ODAddUpdate(self, CO_KEY(0x1800+num, 1, CO_UNSIGNED32|CO_OBJ_DN_R_), 0, id);
    ODAddUpdate(self, CO_KEY(0x1800+num, 2, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, type);
    ODAddUpdate(self, CO_KEY(0x1800+num, 3, CO_UNSIGNED16|CO_OBJ_D__RW), 0, inhibit);
    ODAddUpdate(self, CO_KEY(0x1800+num, 5, CO_UNSIGNED16|CO_OBJ_D__RW), CO_TEVENT, evtimer);
}

Note, that the Sub-index 04h is missing in this array. This is specified in the CiA DS301 for backward compatibility reasons. Furthermore, the Sub-index 00h states the highest accessible Sub-index (05h) and NOT the number of entries. This is a small pitfall when building this object entry.

TPDO Mapping Function

The PDO mapping for the TPDOs needs an array with the mapping information as argument to our service function. The mapping information is specified in CiA DS301 and is very similar to the key information of a object entry:

  • Index : 16bit value (0000h to FFFFh)
  • Sub-index : 8bit value (0 to 255)
  • Width : 8bit value with number of bits (e.g. 8, 16 or 32)

For building this mapping information, we can use the well known CO_KEY(..) macro where we use the 3rd argument with the number of bits.

void CreateTPdoMap(CPU_INT08U  num,
                   CPU_INT32U *map,
                   CPU_INT08U  len)
{
    CPU_INT08U n;

    ODAddUpdate(self, CO_KEY(0x1A00+num, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, len);
    for (n = 0; n < len; n++) {
        ODAddUpdate(self, CO_KEY(0x1A00+num, 1+n, CO_UNSIGNED32|CO_OBJ_D__R_), 0, map[n]);
    }
}

This function will work correctly but could be improved with some guidance checks. The CANopen communication library supports the number of bits 8, 16 and 32. Other values are not supported. Furthermore, the sum of all bits could be checked to be lower or equal to 64 bits. These guidance checks are left for the reader as a small exercise.

Create Directory Function

Now we are ready for building our dynamic object directory for the quickstart project in a very smart and efficient way.

static CPU_INT08U ErrReg;
static CPU_INT16U HbTime;
static CPU_INT08U Second;

void CreateDir(OD_DYN *self)
{
    CPU_INT32U map[3];

    ErrReg = 0;
    HbTime = 0;

    ODAddUpdate(self, CO_KEY(0x1000, 0, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x00000000);
    ODAddUpdate(self, CO_KEY(0x1001, 0, CO_UNSIGNED8 |CO_OBJ___PR_), 0, &ErrReg);
    ODAddUpdate(self, CO_KEY(0x1017, 0, CO_UNSIGNED16|CO_OBJ____RW), CO_THEARTBEAT, &HbTime);
    ODAddUpdate(self, CO_KEY(0x1018, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 0x01);
    ODAddUpdate(self, CO_KEY(0x1018, 1, CO_UNSIGNED32|CO_OBJ_D__R_), 0, 0x00000000);

    CreateSdoServer(0, 0x600, 0x580);

    CreateTPdoCom(0, 0x40000180, 254, 0, 0);

    map[0] = CO_KEY(0x2100, 3, 32);
    map[1] = CO_KEY(0x2100, 2,  8);
    map[2] = CO_KEY(0x2100, 1,  8);
    CreateTPdoMap(0, map, 3);

    ODAddUpdate(self, CO_KEY(0x2100, 0, CO_UNSIGNED8 |CO_OBJ_D__R_), 0, 0x03);
    ODAddUpdate(self, CO_KEY(0x2100, 1, CO_UNSIGNED8 |CO_OBJ___PR_), CO_TASYNC, &Second); /* <-- */
    ODAddUpdate(self, CO_KEY(0x2100, 2, CO_UNSIGNED8 |CO_OBJ_D_PR_), 0, 0x00);
    ODAddUpdate(self, CO_KEY(0x2100, 3, CO_UNSIGNED32|CO_OBJ_D_PR_), 0, 0x00000000);
}

Note, in the marked line we use the system type "CO_TASYNC" to specify the manufacturer specific trigger event "on change" for this object entry. This results in transmission of any TPDO which holds a communication type 254 and a mapping entry for this signal, if the node is in operational mode. For our demo, this results in transmission of the TPDO with the clock values every second.

Starting the Node

For setup and starting a CANopen node, we can now integrate the functions to a smart CANopen startup sequence:

#define OD_OBJ_N    32

static  OD_DYN      DynDir;
static  CO_OBJ      DynList[OD_OBJ_N];
static  CO_TMR_MEM  TmrMem[CO_TMR_N];

void StartNode(CO_NODE *node, CANBUS_PARA *can, CPU_INT08U node_id)
{
    CO_NODE_SPEC spec;

    spec.NodeId   = node_id;
    spec.EmcyCode = 0;
    spec.TmrMem   = &TmrMem[0];
    spec.TmrNum   = CO_TMR_N;
    spec.CanBus   = can;
    spec.SdoBuf   = 0;

    ODInit   (&DynDir, &DirList[0], OD_OBJ_N);
    ODSetSpec(&DynDir, &spec);

    CreateDir(&DynDir);

    CONodeInit(node, &spec);
}

This function will generate a (temporary) node specification, initializes our new dynamic object directory and initializes the CANopen node with this specification. To integrate this in the delivered quick start demo, the only line we must change is the initialization of the specified CANopen node:

void main(int atgc, char *argv[])
{ 
    :
    /* CONodeInit(&Clk, (CO_NODE_SPEC *)&AppSpec);  OLD */
    StartNode(&Clk, AppSpec.CanBus, 1);          /* NEW */
    :
}

Well, that's it.

Enjoy the new flexibility with our dynamic generated object directories.

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.