对象字典内存模型
目录
什么是对象字典
对象字典(Object Dictionary,简称 OD)是 CANOpen 设备的"数据中心"。它是一个结构化的数据集合,包含了设备的所有参数、配置、状态和通信参数。
关键特征:
- 每个条目由 16 位索引(Index)+ 8 位子索引(Sub-index) 唯一标识
- 每个条目有明确的数据类型、访问权限、默认值
- 对象字典是 SDO 和 PDO 通信的基础
┌─────────────────────────────────────────────────┐
│ 对象字典(Object Dictionary) │
├──────────┬──────────┬──────────┬────────────────┤
│ 索引 │ 子索引 │ 数据类型 │ 访问类型 │
├──────────┼──────────┼──────────┼────────────────┤
│ 0x1000 │ 0x00 │ UNSIGNED32│ ro (只读) │ 设备类型
│ 0x1001 │ 0x00 │ UNSIGNED8 │ ro │ 错误寄存器
│ 0x1003 │ 0x00 │ UNSIGNED8 │ ro │ 错误数量
│ 0x1003 │ 0x01 │ UNSIGNED32│ ro │ 错误码 1
│ 0x1003 │ 0x02 │ UNSIGNED32│ ro │ 错误码 2
│ 0x2000 │ 0x00 │ INTEGER16 │ rw │ 关节目标位置
└──────────┴──────────┴──────────┴────────────────┘索引区域分配
CANOpen 规范定义了对象字典的索引区域:
| 索引范围 | 区域名称 | 说明 | 示例 |
|---|---|---|---|
| 0x0000 | 未使用 | 保留 | – |
| 0x1000-0x1FFF | 通信区 | 通信参数、设备信息 | 0x1000 设备类型 |
| 0x2000-0x5FFF | 制造商区 | 制造商特定参数 | 0x2000 关节位置 |
| 0x6000-0x6FFF | 输入区 | 数字输入映射 | 0x6000 数字输入 |
| 0x7000-0x7FFF | 输出区 | 数字输出映射 | 0x7000 数字输出 |
| 0x8000-0x8FFF | 配置区 | 配置参数 | 0x8000 节点 ID |
| 0x9000-0x9FFF | 信息区 | 状态信息 | 0x9000 运行时间 |
| 0xA000-0xFFFF | 设备行规区 | 特定行规参数 | CiA 402: 0x6040 控制字 |
通信区详解(0x1000-0x1FFF)
| 索引 | 名称 | 类型 | 访问 | 说明 |
|---|---|---|---|---|
| 0x1000 | Device type | UNSIGNED32 | ro | 设备类型标识 |
| 0x1001 | Error register | UNSIGNED8 | ro | 错误状态(可 PDO 映射) |
| 0x1002 | Manufacturer status | UNSIGNED32 | ro | 制造商状态 |
| 0x1003 | Pre-defined error field | ARRAY | ro | 标准错误记录 |
| 0x1005 | COB-ID SYNC | UNSIGNED32 | rw | SYNC 消息 COB-ID |
| 0x1006 | Communication cycle period | UNSIGNED32 | rw | 通信周期(微秒) |
| 0x1007 | Synchronous window length | UNSIGNED32 | rw | 同步窗口长度 |
| 0x1008 | Manufacturer device name | VISIBLE_STRING | const | 设备名称 |
| 0x1009 | Manufacturer hardware version | VISIBLE_STRING | const | 硬件版本 |
| 0x100A | Manufacturer software version | VISIBLE_STRING | const | 软件版本 |
| 0x1010 | Store parameters | ARRAY | rw | 保存参数到非易失存储 |
| 0x1011 | Restore default parameters | ARRAY | rw | 恢复默认参数 |
| 0x1012 | COB-ID Time | UNSIGNED32 | rw | TIME 消息 COB-ID |
| 0x1014 | COB-ID EMCY | UNSIGNED32 | rw | EMCY 消息 COB-ID |
| 0x1015 | Inhibit time EMCY | UNSIGNED16 | rw | EMCY 抑制时间 |
| 0x1016 | Consumer heartbeat | ARRAY | rw | 心跳消费者配置 |
| 0x1017 | Producer heartbeat | UNSIGNED16 | rw | 心跳生产者周期 |
| 0x1018 | Identity object | RECORD | ro | 制造商信息(厂商 ID、产品码等) |
三种对象类型
1. VAR(变量)
单一值,最简单的对象类型。
[1000]
ParameterName=Device type
ObjectType=0x07 ; VAR
DataType=0x0007 ; UNSIGNED32
AccessType=ro
DefaultValue=0x00000000C 语言内存表示:
typedef struct {
uint16_t index;
uint8_t sub_index;
uint16_t data_type;
uint8_t access_type;
void *data; /* 指向实际数据的指针 */
size_t data_size; /* 数据大小(字节) */
} od_var_t;
/* 实例:设备类型 */
uint32_t device_type = 0x00010192;
od_var_t od_1000 = {
.index = 0x1000,
.sub_index = 0x00,
.data_type = 0x0007, /* UNSIGNED32 */
.access_type = OD_ACCESS_RO,
.data = &device_type,
.data_size = sizeof(uint32_t)
};2. ARRAY(数组)
多个相同类型的值,sub-index 0 表示元素个数。
[1003]
ParameterName=Pre-defined error field
ObjectType=0x08 ; ARRAY
DataType=0x0007 ; UNSIGNED32
AccessType=ro
SubNumber=9 ; sub0 ~ sub8
[1003sub0]
ParameterName=Number of errors
ObjectType=0x07
DataType=0x0005 ; UNSIGNED8
AccessType=ro
DefaultValue=0
[1003sub1]
ParameterName=Standard error field 1
ObjectType=0x07
DataType=0x0007 ; UNSIGNED32
AccessType=ro
DefaultValue=0C 语言内存表示:
typedef struct {
uint16_t index;
uint16_t data_type; /* 所有元素的类型相同 */
uint8_t access_type;
uint8_t max_sub; /* 最大子索引 */
void *data; /* 数组指针(data[0] = count, data[1..N] = 元素) */
size_t element_size;
} od_array_t;
/* 实例:错误记录 */
uint32_t error_field[9] = {0}; /* [0]=count, [1..8]=errors */
od_array_t od_1003 = {
.index = 0x1003,
.data_type = 0x0007, /* UNSIGNED32 */
.access_type = OD_ACCESS_RO,
.max_sub = 8,
.data = error_field,
.element_size = sizeof(uint32_t)
};
/* 访问:读取错误数量 */
uint8_t error_count = ((uint32_t*)od_1003.data)[0];3. RECORD(记录)
多个不同类型的字段,sub-index 0 表示字段个数。
[1018]
ParameterName=Identity object
ObjectType=0x09 ; RECORD
AccessType=ro
SubNumber=5
[1018sub0]
ParameterName=Highest sub-index supported
ObjectType=0x07
DataType=0x0005 ; UNSIGNED8
AccessType=ro
DefaultValue=0x04
[1018sub1]
ParameterName=Vendor-ID
ObjectType=0x07
DataType=0x0007 ; UNSIGNED32
AccessType=ro
DefaultValue=0x00000001
[1018sub2]
ParameterName=Product code
ObjectType=0x07
DataType=0x0007
AccessType=ro
DefaultValue=0x00000001
[1018sub3]
ParameterName=Revision number
ObjectType=0x07
DataType=0x0007
AccessType=ro
DefaultValue=0x00000001
[1018sub4]
ParameterName=Serial number
ObjectType=0x07
DataType=0x0007
AccessType=ro
DefaultValue=0x00000001C 语言内存表示:
typedef struct {
uint16_t index;
uint8_t access_type;
uint8_t max_sub;
struct {
uint16_t data_type;
void *data;
size_t data_size;
} fields[]; /* 每个字段类型不同 */
} od_record_t;
/* 实例:制造商信息 */
uint32_t vendor_id = 0x00000001;
uint32_t product_code = 0x00000001;
uint32_t revision_number = 0x00000001;
uint32_t serial_number = 0x00000001;
od_record_t od_1018 = {
.index = 0x1018,
.access_type = OD_ACCESS_RO,
.max_sub = 4,
.fields = {
{0x0005, &vendor_id, sizeof(uint32_t)},
{0x0007, &product_code, sizeof(uint32_t)},
{0x0007, &revision_number, sizeof(uint32_t)},
{0x0007, &serial_number, sizeof(uint32_t)}
}
};统一对象字典抽象
在实际实现中,我们通常用一个统一的结构体来表示所有三种类型:
typedef enum {
OD_TYPE_VAR,
OD_TYPE_ARRAY,
OD_TYPE_RECORD
} od_type_t;
typedef struct {
uint16_t index;
uint8_t max_sub;
od_type_t type;
union {
/* VAR */
struct {
uint16_t data_type;
uint8_t access;
void *data;
size_t size;
} var;
/* ARRAY */
struct {
uint16_t data_type;
uint8_t access;
void *data;
size_t element_size;
} array;
/* RECORD */
struct {
uint8_t access;
struct {
uint16_t data_type;
void *data;
size_t size;
} *fields;
} record;
};
} od_entry_t;优势:
- 统一的查找接口:
od_find(index, sub_index) - 统一的访问接口:
od_read()/od_write() - 内部根据
type分支处理不同的内存布局
SDO 访问对象字典的流程
SDO Upload Request Object Dictionary SDO Upload Response
│ │ │
│───[0x600+N]──────────────>│ │
│ CCS=10, Index, SubIdx │ │
│ │── find(0x1000, 0) ──> │
│ │ 读取 data 指针 │
│ │ 拷贝 4 字节到响应帧 │
│ │ │
│<──[0x580+N]───────────────│ │
│ SCS=01, Data(4 bytes) │ │关键步骤:
- 服务端收到 SDO 请求后,根据索引和子索引查找对象字典条目
- 根据条目类型(VAR/ARRAY/RECORD)获取数据指针
- 检查访问权限(ro/rw/wo)
- 将数据拷贝到 SDO 响应帧(Expedited 最多 4 字节,Segmented 更多)
- 返回响应
内存对齐与大小端
大小端问题:
- CAN 帧使用 小端模式(Little-Endian) 传输多字节数据
- 如果 MCU 是大端模式(如某些 ARM 配置),需要在 SDO 传输时进行字节序转换
- Linux x86/ARM 通常是小端模式,可以直接拷贝
/* 小端模式:可以直接内存拷贝 */
/* 大端模式:需要字节序转换 */
uint32_t to_le32(uint32_t value) {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
return __builtin_bswap32(value);
#else
return value;
#endif
}内存对齐:
- 对象字典中的数据应按其自然大小对齐(32 位值按 4 字节对齐)
- 避免非对齐访问导致的性能下降或硬件异常
动态对象字典
某些设备需要在运行时添加或删除对象字典条目。实现策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 静态数组 | 编译时固定大小,运行时查找 | 资源受限的 MCU |
| 哈希表 | O(1) 查找,支持动态添加 | Linux 上位机 |
| 二叉搜索树 | O(log N) 查找,内存紧凑 | 中等规模设备 |
| 链表 | 简单但 O(N) 查找 | 小型设备(条目 < 50) |
总结
| 维度 | 说明 |
|---|---|
| 对象类型 | VAR(单值)、ARRAY(同类型数组)、RECORD(不同类型结构体) |
| 索引区域 | 0x1000-0x1FFF 通信区,0x2000-0x5FFF 制造商区 |
| C 表示 | void* 指针 + data_type + access_type |
| 访问流程 | 查找条目 -> 检查权限 -> 拷贝数据 -> 返回 |
| 大小端 | CAN 使用小端模式,大端 MCU 需转换 |
如果这篇文章对你有帮助,欢迎请我喝杯咖啡 ☕
支付宝
微信