4. 对象字典(OD)基础:对象字典的概念、索引与子索引、对象字典的数据类型
说实话,刚接触CANopen那会儿,我对着“对象字典”这三个字琢磨了半天。听着挺玄乎,其实说白了,它就是CANopen设备的一个“数据仓库”。
你想想看,一个电机驱动器,它需要知道自己的额定电流、当前转速、故障状态。这些数据放哪儿?总得有个规矩的地方存着。对象字典就是干这个的。它把设备里所有能访问的数据,都按一套标准格式组织起来。
我习惯把它理解成一本“说明书”。别人想读你的数据,或者想改你的参数,都得通过这本说明书来找位置。
4.1 对象字典到底是什么?
对象字典,英文叫Object Dictionary,简称OD。它是一个有序的数据结构集合。每个CANopen设备都必须有一个OD。
OD的核心作用就两个:
- 标准化数据访问:不管你是哪家公司的驱动器,只要支持CANopen,读电流参数都走同一个索引。
- 设备描述:OD里存着设备类型、制造商信息、通信参数、应用数据。别人一看OD,就知道你这设备能干什么。
关键认知:OD不是代码里的一个数组,它是一个逻辑概念。在STM32上,它可能是一段RAM区域,也可能是Flash里的常量表。CANopenNode的实现里,OD就是一个结构体数组。
4.2 索引与子索引——怎么找到数据?
OD里的每个数据项,都用两个编号定位:索引和子索引。
索引是一个16位的数字,范围0x0000到0xFFFF。它就像一本书的章号。比如0x1000是设备类型,0x1017是心跳生产者时间。
子索引是一个8位的数字,范围0x00到0xFF。它就像章下面的小节号。有些对象是数组或记录,就得靠子索引来区分具体元素。
举个例子,0x2000这个索引,子索引0x01存速度比例系数,子索引0x02存加速度时间。你读0x2000的0x01,拿到的是比例系数;读0x2000的0x02,拿到的是加速度。同一个索引,不同子索引,意义完全不同。
| 索引 | 子索引 | 参数名称 | 数据类型 |
|---|---|---|---|
| 0x1000 | 0x00 | 设备类型 | UNSIGNED32 |
| 0x1017 | 0x00 | 心跳生产者时间 | UNSIGNED16 |
| 0x2000 | 0x01 | 速度比例系数 | UNSIGNED32 |
| 0x2000 | 0x02 | 加速度时间 | UNSIGNED16 |
我的习惯:在项目初期,我会先画一张OD映射表。把每个索引、子索引、数据类型、默认值、读写属性都列清楚。这张表就是你和上层协议栈沟通的桥梁,省得后面改来改去。
4.3 对象字典的数据类型
OD里存的数据,不是随便什么格式都行。CANopen标准定义了一套基础数据类型。我挑几个常用的说说。
4.3.1 基本整数类型
- UNSIGNED8/16/32:无符号整数,长度分别是1、2、4字节。存状态、标志位、计数器,用UNSIGNED8就够了。
- INTEGER8/16/32:有符号整数。存温度、速度偏差这些可能为负的值。
4.3.2 浮点类型
- REAL32:32位浮点数,符合IEEE 754标准。我做的伺服驱动器,位置环和速度环的PID系数都用REAL32存。精度够用,传输也快。
4.3.3 可见字符串
- VISIBLE_STRING:ASCII字符串。设备名称、软件版本号,都用这个类型。注意长度要提前定好,别超了。
4.3.4 域类型
- DOMAIN:不限定格式的原始数据块。适合存固件升级包、配置文件。我在做OTA升级时,就用DOMAIN类型来传输固件二进制流。
| 数据类型 | 长度 | 取值范围 | 典型用途 |
|---|---|---|---|
| UNSIGNED8 | 1字节 | 0 ~ 255 | 状态码、子索引数量 |
| UNSIGNED16 | 2字节 | 0 ~ 65535 | 心跳时间、PDO映射参数 |
| UNSIGNED32 | 4字节 | 0 ~ 4294967295 | 设备类型、错误寄存器 |
| INTEGER16 | 2字节 | -32768 ~ 32767 | 温度值、速度偏差 |
| REAL32 | 4字节 | ±1.18e-38 ~ ±3.4e38 | PID系数、位置值 |
| VISIBLE_STRING | 可变 | ASCII字符 | 设备名称、版本号 |
避坑提醒:我曾经在一个项目里,把UNSIGNED32的数据用UNSIGNED16来定义。结果上位机读到的值全是截断的,排查了半天才发现是数据类型定义错了。数据类型一定要和实际存储长度严格对应,差一个字节都不行。
4.4 对象字典的结构图
下面这张图,是我画的对象字典结构关系。你可以看到,索引和子索引如何定位到具体的数据项,以及数据类型如何决定存储方式。
4.5 在CANopenNode中定义OD
CANopenNode里,OD是用一个结构体数组定义的。每个结构体描述一个对象条目。我贴一段实际代码,你看看就明白了。
// CANopenNode中对象字典条目的结构体定义
typedef struct {
uint16_t index; // 索引
uint8_t subIndex; // 子索引
uint8_t attribute; // 读写属性
uint8_t dataType; // 数据类型
uint32_t minValue; // 最小值
uint32_t maxValue; // 最大值
void* dataPtr; // 指向实际存储的指针
uint32_t dataLength; // 数据长度
} OD_entry_t;
实际定义OD时,会像下面这样。每个条目都指定了索引、子索引、数据类型和存储位置。
// 对象字典定义示例
const OD_entry_t OD_Entries[] = {
// 设备类型
{0x1000, 0x00, ODA_SDO_R, ODT_UNSIGNED32, 0, 0xFFFFFFFF, &DeviceType, 4},
// 心跳生产者时间
{0x1017, 0x00, ODA_SDO_RW, ODT_UNSIGNED16, 0, 0xFFFF, &HeartbeatTime, 2},
// 自定义参数:速度比例系数
{0x2000, 0x01, ODA_SDO_RW, ODT_UNSIGNED32, 0, 0xFFFFFFFF, &SpeedRatio, 4},
// 自定义参数:加速度时间
{0x2000, 0x02, ODA_SDO_RW, ODT_UNSIGNED16, 0, 0xFFFF, &AccelTime, 2},
};
我的经验:定义OD时,dataPtr一定要指向一个全局变量或者静态变量。我见过有人直接指向局部变量的地址,结果SDO一读,数据全乱套了。另外,dataLength必须和数据类型严格匹配,UNSIGNED32就是4字节,别写成2。
4.6 对象字典的通信访问方式
OD里的数据,主要通过两种方式访问:
- SDO(服务数据对象):点对点访问,可以读写任意索引和子索引。适合配置参数、读取诊断信息。速度慢,但可靠。
- PDO(过程数据对象):广播式访问,只能映射到OD里特定的条目。适合实时传输控制指令和状态数据。速度快,开销小。
这两种方式,后面章节会详细讲。你现在只需要记住:OD是数据仓库,SDO和PDO是搬运工。
注意:不是所有OD条目都能通过PDO访问。只有那些标记为“可映射”的条目才行。我在一个项目里,想把设备序列号映射到PDO里,结果发现它不支持映射。查了标准才知道,VISIBLE_STRING类型默认不能做PDO映射。所以设计OD时,提前想好哪些数据要走PDO,哪些走SDO。
4.7 小结
对象字典是CANopen设备的灵魂。没有OD,设备就是一堆散乱的数据。有了OD,数据就有了标准化的访问路径。
这一章,我们讲了:
- OD的概念:标准化的数据仓库
- 索引和子索引:16位索引 + 8位子索引,精确定位数据
- 数据类型:UNSIGNED、INTEGER、REAL、VISIBLE_STRING、DOMAIN
- 在CANopenNode中如何定义OD条目
- SDO和PDO的访问方式