i am completely confused and it feels like I end up with more memory and stuff to set up than I have. PDO's are dealt with already, I have a stucture for them and the message receiving code will just copy the data from the message to the correct jstructure location. What I really need to do with is sort out SDO communication so basically everything.
I'm sorry if I confused you
.
Put simply, my point was that what CAN and CANopen call an "object dictionary" is actually a structured collection of entities that are interrelated in ways defined in the device profiles. So, in my opinion, it would make sense to create functions that implement such ways, as accessor functions (get, set), and associate those with the "object dictionary". For example:
// SPDX-License-Identifier: CC0-1.0 (i.e. Do Whatever You Want, just don't blame me)
#include <stdint.h>
// Basic types using CANopen numbering.
#define TYPE_I8 0x02 /* int8_t */
#define TYPE_I16 0x03 /* int16_t */
#define TYPE_I32 0x04 /* int32_t */
#define TYPE_I64 0x15 /* int64_t */
#define TYPE_U8 0x05 /* uint8_t */
#define TYPE_U16 0x06 /* uint16_t */
#define TYPE_U32 0x07 /* uint32_t */
#define TYPE_U64 0x1B /* uint64_t */
#define TYPE_REAL32 0x08 /* float */
#define TYPE_REAL64 0x11 /* double */
#define TYPE_FLOAT TYPE_REAL32
#define TYPE_DOUBLE TYPE_REAL64
// Note: A different scheme would allow simpler conversion implementation
// between numerically identified types, as when copying objects
// to or from the object dict.
// Accessor functions need some kind of *context*. It is a constant, but could have many forms.
// This assumes __WORDSIZE == 32, i.e. sizeof (void *) == 4.
typedef const struct {
union {
void *ptr;
uint32_t u32;
int32_t i32;
uint16_t u16[2];
int16_t i16[2];
uint8_t u8[4];
int8_t i8[4];
};
} ctx;
// Object dictionary operations are stored in Flash, and describe accessor functions and their context.
typedef const struct {
ctx const context1; // This is usually a pointer.
ctx const context2; // This usually uses the integer components. Say, a size of the array.
int (*const get)(void *into, uint32_t index_intotype_subindex, ctx context1, ctx context2);
int (*const set)(void *from, uint32_t index_fromtype_subindex, ctx context1, ctx context2);
} object_dict_ops;
// Each object dict entry takes (2+sizeof(void *)) bytes of RAM.
#ifndef OBJECT_DICT_ENTRIES
#define OBJECT_DICT_ENTRIES 500
#endif
// Recursive mutex/spinlock/interrupt locking.
uint32_t object_dict_lock(void);
void object_dict_unlock(uint32_t);
// Object dictionary itself.
const object_dict_ops *object_dict_entry[OBJECT_DICT_ENTRIES]; // In same order as object_dict_index
uint16_t object_dict_index[OBJECT_DICT_ENTRIES]; // In sorted order, largest index first
int32_t object_dict_entries = 0;
const int32_t object_dict_entries_max = OBJECT_DICT_ENTRIES;
// Each object dict index is handled by a set of operations.
// These operations are added to the dictionary at run time, but defined at compile time.
int object_dict_attach(const object_dict_ops *ops, int index);
// Although only the index is needed for detaching operations,
// it may be useful to keep the other parameters for verification.
int object_dict_detach(const object_dict_ops *ops, int index);
// Individual values can be copied to and from the object dict, per the accessor functions.
int object_dict_get(void *into, uint_fast16_t index, uint8_t subindex, uint8_t into_type);
int object_dict_set(void *from, uint_fast16_t index, uint8_t subindex, uint8_t from_type);
Here is an example of accessor functions that can be used to access arrays of
int32_t values:
int get_i32(void *into, uint32_t index_intotype_subindex, ctx context1, ctx context2)
{
const uint8_t into_type = (index_intotype_subindex >> 8) & 0xFF;
const uint8_t subindex = index_intotype_subindex & 0xFF;
int32_t *const array = context1.ptr;
const uint8_t length = context2.u8[0];
// Length requested?
if (subindex == 0)
return copy_i32_into(into, length, into_type);
// Actual value?
if (subindex <= length)
return copy_i32_into(into, array[subindex-1], into_type);
return -1;
}
int set_i32(void *from, uint32_t index_fromtype_subindex, ctx context1, ctx context2)
{
const uint8_t from_type = (index_fromtype_subindex >> 8) & 0xFF;
const uint8_t subindex = index_fromtype_subindex & 0xFF;
int32_t *const array = context1.ptr;
const uint8_t length = context2.u16[0];
// Cannot set length.
if (subindex == 0)
return -1;
// Setting an actual value?
if (subindex <= length)
return copy_into_i32(array + subindex, from, from_type);
return -1;
}
They assume that function
int copy_i32_into(void *dst, int32_t src_val, uint8_t dst_type);copies the specified
src_val into
*dst of
dst_type type; and that function
int copy_into_i32(int32_t *dst, void *src, uint8_t src_type);copies the value of
*src_val of type
src_type into an
int32_t at
*dst.
They should also do any necessary type conversions.
(Note that for
copy_into_i32(),
dst is always aligned. The
void * can point to anything, so may be misaligned for the specified type.)
In general, you'd need different accessor functions for the complex structures (like the one specified in index 0x0022), for device-profile STRUCTs (if any!), for ARRAYs (separate ones for each type you use in RAM), and for VARs (separate ones for each type you use in RAM).
For example, to implement an array of 6 auxiliary variables for digital inputs at index 0x2050, you could do
int32_t aux_variables[6];
const object_dict_ops aux_variables_ops = {
.context1 = { .ptr = aux_variables },
.context2 = { .u8 = { sizeof aux_variables / sizeof aux_variables[0], }},
.get = get_i32,
.set = set_i32,
};
At runtime, you would need to associate the ops with the desired object dict index:
if (object_dict_attach(&aux_variables_ops, 0x2050)) { /* failed */ }Let's say you then have an
int16_t v. You can use
if (object_dict_get(&v, 0x2050, 0x01, TYPE_I16)) { /* failed */ }to get the first variable from 0x2050:0x01 to
v, or
if (object_dict_set(&v, 0x2050, 0x01, TYPE_I16)) { /* failed */ }to copy the value of
v to 0x2050:0x01.
The same functions can be used for any number of arrays of
int32_t, each array only needs their own
const object_dict_ops entry, to specify also the array location and size.
Note that
object_dict_attach() should keep the entries in
object_dict_index[] and
object_dict_entry[] ––
object_dict_entry[k] describes index
object_dict_index[k] –– in decreasing order of index, so that all four functions can use a very fast binary search to find the target entry (or insertion index for _attach). All four functions also must internally use
lockval = object_dict_lock(); and
object_dict_unlock(lockval); to protect the accesses to the object dictionary (via a mutex, spinlock, or interrupt mask), so that it is safe to get and set the object dict values from any context.
In your SDO receive and transmit functions, you'll use
object_dict_set() and
object_dict_get(), and if either returns nonzero/failure, you send the EMCY error message.
Because the indexes are compact in memory (a single array of
uint16_t), a binary search will find them in less than 17 iterations (9 if you have less than 512 entries in use), and each iteration only involves an addition, a substraction, divide-by-two (one bit shift right), loading a 16-bit word from memory, and two comparisons. It will be fast. (Not as fast as directly referencing variables in memory, of course; but fast enough to use even in an interrupt context.)
I am writing [a CANopen] master
Okay, that simplifies things. Slaves must be modifiable by the master, but the master only needs to support different kinds of slaves. The above certainly applies, but you are unlikely to need the setters and only a single getter (as all your memory variables are
int32_t's.)
Something like
// SPDX-License-Identifier: CC0-1.0
#ifndef OBJECT_DICT_H
#define OBJECT_DICT_H
// If you modify the state associated with an object dict entry non-atomically,
// you need to lock access to the object dictionary.
uint32_t object_dict_lock(void);
void object_dict_unlock(uint32_t);
// Supported CANopen types
#define TYPE_I8 0x02
#define TYPE_I16 0x03
#define TYPE_I24 0x10
#define TYPE_I32 0x04
#define TYPE_U8 0x05
#define TYPE_U16 0x06
#define TYPE_U24 0x16
#define TYPE_U32 0x07
typedef union {
uint32_t key;
struct {
uint16_t index;
uint8_t type; // CANopen type; entry is always int32_t
uint8_t length;
};
} index_type;
// Return the index:type:len corresponding to an index, or 0:0:0 if not found.
index_type object_dict_find(uint_fast16_t index);
// Copy a value from object dict into local storage, applying format conversions.
// Returns the number of bytes copied, or negative for error.
int object_dict_get_into(void *into, uint_fast16_t index, uint8_t subindex);
// Copy a value from local storage to object dict, applying format conversions.
// Returns the number of bytes copied from, or a negative error code.
int object_dict_set_from(void *from, uint_fast16_t index, uint8_t subindex);
// Associate an object dict index with a variable (len=0) or an array of variables (len=1..253).
// Returns 0 if success, a negative error code otherwise.
int object_dict_new(int32_t *mem, uint_fast16_t index, uint8_t length, uint8_t type);
// Error codes; must be negative
#define OBJECT_DICT_FULL -1
#define OBJECT_DICT_INDEX_IN_USE -2
#define OBJECT_DICT_INVALID_INDEX -3
#define OBJECT_DICT_INVALID_SUBINDEX -4
#define OBJECT_DICT_INVALID_TYPE -5
#define OBJECT_DICT_INVALID_VALUE -6 // Value exceeds type limits
#endif /* OBJECT_DICT_H */
and the corresponding implementation (with a fixed maximum number of object dict entries):
// SPDX-License-Identifier: CC0-1.0
#include <stdint.h>
#include "object_dict.h"
#ifndef OBJECT_DICT_ENTRIES
#define OBJECT_DICT_ENTRIES 512
#endif
static index_type object_dict_index[OBJECT_DICT_ENTRIES];
static int32_t *object_dict_entry[OBJECT_DICT_ENTRIES];
int object_dict_entries = 0;
const int object_dict_entries_max = OBJECT_DICT_ENTRIES;
uint32_t object_dict_lock(void)
{
// Recursive interrupt/spinlock.
return 0;
}
void object_dict_unlock(uint32_t lock)
{
// Recursive interrupt/spinlock.
(void)lock;
}
// Internal binary search lookup. Object dict must be locked. Returns -1 if not found.
static int index_at(uint_fast16_t index)
{
if (index < 1 || index > 65535)
return OBJECT_DICT_INVALID_INDEX;
if (object_dict_entries < 1)
return OBJECT_DICT_INVALID_INDEX;
if (object_dict_index[0].index < index)
return OBJECT_DICT_INVALID_INDEX;
if (object_dict_index[0].index == index)
return 0;
if (object_dict_index[object_dict_entries - 1].index > index)
return OBJECT_DICT_INVALID_INDEX;
if (object_dict_index[object_dict_entries - 1].index == index)
return object_dict_entries - 1;
int i = 0;
int imin = 1; // inclusive
int imax = object_dict_entries - 1; // exclusive
while (imin < imax) {
i = imin + (imax - imin) / 2;
if (object_dict_index[i].index > index) {
imin = i + 1;
} else
if (object_dict_index[i].index < index) {
imax = i;
} else {
return i;
}
}
return OBJECT_DICT_INVALID_INDEX;
}
// Internal: Copy an int32_t to (unaligned) CANopen data buffer using the specified CANopen type.
// Returns the number of bytes copied, or negative error code.
static int copy_i32_into(void *into, const uint8_t type, const int32_t value)
{
if ( (type == TYPE_U8 && (value < 0 || value > 255)) ||
(type == TYPE_U16 && (value < 0 || value > 65535)) ||
(type == TYPE_U24 && (value < 0 || value > 16777215)) ||
(type == TYPE_U32 && value < 0) ||
(type == TYPE_I8 && (value < -128 || value > 127)) ||
(type == TYPE_I16 && (value < -32768 || value > 32767)) ||
(type == TYPE_I24 && (value < -8388608 || value > 8388607)) )
return OBJECT_DICT_INVALID_VALUE;
unsigned char *dest = into;
unsigned char data[4] = { ((uint32_t)value),
((uint32_t)value) >> 8,
((uint32_t)value) >> 16,
((uint32_t)value) >> 24 };
if (type == TYPE_I32 || type == TYPE_U32) {
dest[0] = data[0];
dest[1] = data[1];
dest[2] = data[2];
dest[3] = data[3];
return 4;
}
if (type == TYPE_I24 || type == TYPE_U24) {
dest[0] = data[0];
dest[1] = data[1];
dest[2] = data[2];
return 3;
}
if (type == TYPE_I16 || type == TYPE_U16) {
dest[0] = data[0];
dest[1] = data[1];
return 2;
}
if (type == TYPE_I8 || type == TYPE_U8) {
dest[0] = data[0];
return 1;
}
return OBJECT_DICT_INVALID_TYPE;
}
// Internal: Copy a CANopen data buffer value of the specified CANopen type to an int32_t.
// Returns the number of source bytes copied, or negative error code.
static int copy_i32_from(void *from, const uint8_t type, int32_t *const to)
{
const unsigned char *const src = from;
uint32_t val;
int len;
switch (type) {
case TYPE_I32:
val = (uint32_t)(src[0])
+ ((uint32_t)(src[1]) << 8)
+ ((uint32_t)(src[2]) << 16)
+ ((uint32_t)(src[3]) << 24);
len = 4;
break;
case TYPE_U32:
val = (uint32_t)(src[0])
+ ((uint32_t)(src[1]) << 8)
+ ((uint32_t)(src[2]) << 16)
+ ((uint32_t)(src[3]) << 24);
if (val & UINT32_C(0x80000000))
return OBJECT_DICT_INVALID_VALUE;
len = 4;
break;
case TYPE_I24:
val = (uint32_t)(src[0])
+ ((uint32_t)(src[1]) << 8)
+ ((uint32_t)(src[2]) << 16);
if (val & UINT32_C(0x00800000))
val |= UINT32_C(0xFF000000);
len = 3;
break;
case TYPE_U24:
val = (uint32_t)(src[0])
+ ((uint32_t)(src[1]) << 8)
+ ((uint32_t)(src[2]) << 16);
len = 3;
break;
case TYPE_I16:
val = (uint32_t)(src[0])
+ ((uint32_t)(src[1]) << 8);
if (val & UINT32_C(0x8000))
val |= UINT32_C(0xFFFF0000);
len = 2;
break;
case TYPE_U16:
val = (uint32_t)(src[0])
+ ((uint32_t)(src[1]) << 8);
len = 2;
break;
case TYPE_I8:
val = src[0];
if (val & UINT32_C(0x80))
val |= UINT32_C(0xFFFFFF00);
len = 1;
break;
case TYPE_U8:
val = src[0];
len = 1;
break;
default:
return OBJECT_DICT_INVALID_TYPE;
}
// Compiler will optimize this to a simple assignment, but this way we're standards compliant.
if (val & UINT32_C(0x80000000))
*to = -(int32_t)(~val) - 1;
else
*to = val;
return len;
}
// Return the index:type:len corresponding to an index, or 0:0:0 if not found.
index_type object_dict_find(uint_fast16_t index)
{
index_type result = { .index = 0, .type = 0, .length = 0 };
if (index < 1 || index > 0xFFFF)
return result;
uint32_t lock = object_dict_lock();
int i = index_at(index);
if (i >= 0)
result = object_dict_index[i];
object_dict_unlock(lock);
return result;
}
// Copy a value from object dict into local storage, applying format conversions.
// Returns the number of bytes copied, or negative for error.
int object_dict_get_into(void *into, uint_fast16_t index, uint8_t subindex)
{
if (index < 1 || index > 0xFFFF)
return OBJECT_DICT_INVALID_INDEX;
uint32_t lock = object_dict_lock();
int i = index_at(index);
if (i < 0) {
object_dict_unlock(lock);
return OBJECT_DICT_INVALID_INDEX;
}
int32_t value;
uint8_t type = object_dict_index[i].type;
if (subindex == 0) {
if (object_dict_index[i].length == 0) {
// VAR access.
value = object_dict_entry[i][0];
} else {
// ARRAY length is always UNSIGNED16 (UNSIGNED8 in older versions of CANopen).
value = object_dict_index[i].length;
type = TYPE_U16;
}
} else
if (object_dict_index[i].length >= subindex) {
// Array value access.
value = object_dict_entry[i][subindex - 1];
} else {
object_dict_unlock(lock);
return OBJECT_DICT_INVALID_SUBINDEX;
}
object_dict_unlock(lock);
return copy_i32_into(into, type, value);
}
// Copy a value from local storage to object dict, applying format conversions.
// Returns the number of bytes copied from, or a negative error code.
int object_dict_set_from(void *from, uint_fast16_t index, uint8_t subindex)
{
if (index < 1 || index > 0xFFFF)
return OBJECT_DICT_INVALID_INDEX;
uint32_t lock = object_dict_lock();
int i = index_at(index);
if (i < 0) {
object_dict_unlock(lock);
return OBJECT_DICT_INVALID_INDEX;
}
int32_t value;
int result = copy_i32_from(from, object_dict_index[i].type, &value);
if (result <= 0) {
object_dict_unlock(lock);
return result;
}
if (subindex == 0) {
if (object_dict_index[i].length == 0) {
object_dict_entry[i][0] = value;
object_dict_unlock(lock);
return result;
} else {
// Setting array length is not supported.
object_dict_unlock(lock);
return OBJECT_DICT_INVALID_SUBINDEX;
}
}
if (subindex > object_dict_index[i].length) {
object_dict_unlock(lock);
return OBJECT_DICT_INVALID_SUBINDEX;
}
object_dict_entry[i][subindex-1] = value;
object_dict_unlock(lock);
return result;
}
int object_dict_new(int32_t *mem, uint_fast16_t index, uint8_t length, uint8_t type)
{
if (index < 1 || index > 65535)
return OBJECT_DICT_INVALID_INDEX;
uint32_t lock = object_dict_lock();
int i;
if (object_dict_entries >= object_dict_entries_max) {
object_dict_unlock(lock);
return OBJECT_DICT_FULL;
}
if (object_dict_entries < 1) {
i = 0;
} else
if (object_dict_index[0].index == index) {
object_dict_unlock(lock);
return OBJECT_DICT_INDEX_IN_USE;
} else
if (object_dict_index[0].index < index) {
i = 0;
} else
if (object_dict_index[object_dict_entries - 1].index == index) {
object_dict_unlock(lock);
return OBJECT_DICT_INDEX_IN_USE;
} else
if (object_dict_index[object_dict_entries - 1].index > index) {
i = object_dict_entries;
} else {
int imin = 1; // inclusive
int imax = object_dict_entries - 1; // exclusive
while (imin < imax) {
i = imin + (imax - imin) / 2;
if (object_dict_index[i].index > index) {
imin = i + 1;
} else
if (object_dict_index[i].index < index) {
imax = i;
} else {
object_dict_unlock(lock);
return OBJECT_DICT_INDEX_IN_USE;
}
}
if (object_dict_index[imin].index == index) {
object_dict_unlock(lock);
return OBJECT_DICT_INDEX_IN_USE;
} else {
i = imin;
}
}
if (i < object_dict_entries) {
for (int k = object_dict_entries; k > i; k--)
object_dict_index[k] = object_dict_index[k-1];
for (int k = object_dict_entries; k > i; k--)
object_dict_entry[k] = object_dict_entry[k-1];
}
object_dict_index[i] = (index_type){ .index = index, .type = type, .length = length };
object_dict_entry[i] = mem;
object_dict_entries++;
object_dict_unlock(lock);
return 0;
}
The idea here is that if you want to map an UNSIGNED16 VAR at index 0x6040 to
int32_t status_word;, you do
if (object_dict_new(&status_word, 0x6040, 0x00, TYPE_U16)) failed();Then, to copy the status word into two bytes at ptr, you do
if (object_dict_get_into(ptr, 0x6040, 0x00) != 2) failed();Note that the return value, 2, is the number of bytes copied into ptr, and applies to all integer types. (I've only tested
TYPE_I32, though.)
Similarly, to copy the two bytes at ptr to the status word, you do
if (object_dict_set_from(ptr, 0x6040, 0x00) != 2) failed();In the SDO receive/transmit functions, the return value will be negative if you reference an invalid index:subindex pair, and the number of bytes used from the buffer otherwise; this should make it rather easy to implement them.
If you want to know the type at index 0x6040, use
object_dict_find(0x6040). It returns an union of
index_type; the
.index is either
0x6040 or 0 if not found, and
.type specifies the CANopen type (TYPE_U16, above), and
.length is 0 for VARs, positive for arrays.
(That is,
(index_type){ .index > 0, .type != 0, .length = 0 } refers to a VAR, and
(index_type){ .index > 0, .type != 0, .length > 0 }refers to an ARRAY.
Because the indexes are sorted in decreasing order, binary search is used, and time complexity for object access is O(
N), where
N is the number of indexes registered in the object_dict. (The number of sub-indexes does not affect that at all.)
I did test VARs and ARRAYs (
length=2..253 only) of
TYPE_I32, i.e.
int32_t some_data[length]; with
if (object_dict_new(some_data, index, length, TYPE_I32)) failed();and that
object_dict_get_into(&len, index, 0x00) returns the length into an UNSIGNED16, and that
object_dict_get_into(&val, index, subindex) and
object_dict_set_from(&val, index, subindex) copy the 4-byte values correctly.
The
object_dict_set_from() and
object_dict_get_into() always use little-endian byte order, as defined in CiA 301. The code should follow ISO C11 (actually, ISO C99 with the addition of unnamed structs/unions).
If you have more than one thread, or the object_dict is accessed from interrupt context, then you should use
uint32_t lock = object_dict_lock(); and
object_dict_unlock(lock); around accesses to the variables added to the object dictionary, to ensure the accesses are atomic, except on architectures where (correctly aligned) accesses to
int32_t are always atomic.