2. CANopenNode协议栈速览
对象字典,说白了就是CANopen设备的“灵魂”。每个CANopen节点能做什么、怎么配置、状态如何,全都记录在这张表里。我刚开始接触CANopen时,觉得OD就是个配置文件,后来踩了不少坑才明白——不理解OD,就别想真正掌握CANopen。
2.1 对象字典的结构
OD本质上是一个标准化的数据结构表。CANopen协议规定,每个对象由索引(Index)和子索引(SubIndex)唯一标识。索引是16位,子索引是8位。
| 索引范围 | 用途 | 我见过的典型对象 |
|---|---|---|
| 0x1000 - 0x1FFF | 通信对象 | 设备类型、COB-ID、同步周期 |
| 0x2000 - 0x5FFF | 制造商特定对象 | 电机参数、PID系数 |
| 0x6000 - 0x9FFF | 标准化设备对象 | DS402驱动状态机 |
每个对象条目包含以下属性:
- 数据类型:UNSIGNED8、INTEGER32、OCTET_STRING等
- 访问权限:ro(只读)、rw(读写)、wo(只写)、const(常量)
- 存储方式:RAM(掉电丢失)、EEPROM(持久保存)
- PDO映射能力:能否被映射到PDO中
关键点:OD不是简单的键值对,它定义了数据的元信息。你想想看,如果只存一个数值,你怎么知道它能不能被SDO写入?能不能被PDO映射?这些信息都在OD描述里。
2.2 EDS/DCF文件格式
EDS(Electronic Data Sheet)是OD的“身份证”。它用纯文本描述OD结构,方便工具解析。DCF(Digital Configuration File)则是EDS的“定制版”,包含了具体配置值。
我习惯用十六进制编辑器直接看EDS文件,但更推荐用CANopenEDS Editor这类工具。一个典型的EDS片段长这样:
[FileInfo]
FileName=MyMotorDrive.eds
FileVersion=1.0
FileRevision=1
[DeviceInfo]
VendorName=MyCompany
ProductNumber=0x1234
BaudRate_10=1
BaudRate_20=1
BaudRate_50=1
BaudRate_125=1
BaudRate_250=1
BaudRate_500=1
BaudRate_800=1
BaudRate_1000=1
[1000]
ParameterName=Device Type
ObjectType=0x07
DataType=0x0007
AccessType=ro
DefaultValue=0x00000000
注意看[1000]这个段,它对应索引0x1000。每个段里定义了对象类型、数据类型、访问权限和默认值。我曾经在项目中遇到一个坑:EDS里AccessType写成了rw,但实际硬件只支持读,结果配置工具写入时直接报错。
避坑指南:EDS文件中的ObjectType字段容易搞混。0x07表示普通变量,0x08表示数组,0x09表示记录。我见过有人把数组类型写成0x07,导致PDO映射时数据长度计算错误。
2.3 CO_OD.c/h生成机制
在CANopenNode中,OD不是手写的,而是通过OD生成器自动生成的。你只需要提供EDS文件,工具就会生成CO_OD.c和CO_OD.h。
生成流程大致如下:
- 编写或获取EDS文件
- 运行
objdictgen或canopennode-od-generator - 工具解析EDS,生成C代码
- 将生成的
CO_OD.c/h加入工程
生成的CO_OD.h里会定义类似这样的宏:
/* 索引0x1000,子索引0x00 */
#define OD_1000_00_DEVICE_TYPE 0x1000
#define OD_1000_00_DEVICE_TYPE_SUB 0x00
/* 索引0x1017,子索引0x00 - 心跳生产者时间 */
#define OD_1017_00_HEARTBEAT_TIME 0x1017
#define OD_1017_00_HEARTBEAT_TIME_SUB 0x00
而CO_OD.c里则是一个巨大的结构体数组:
const CO_OD_entry_t CO_OD_array[] = {
/* 索引0x1000 */
{
.index = 0x1000,
.subEntriesCount = 1,
.subEntries = (const CO_OD_subEntry_t[]){
{
.subIndex = 0x00,
.dataType = CO_UNSIGNED32,
.attribute = CO_ODA_READ,
.pData = (void*)&OD_1000_00_DeviceType,
.dataSize = 4
}
}
},
/* ... 更多条目 */
};
我的习惯:每次修改EDS后,我会先对比生成的CO_OD.h差异,确保宏定义没变。有一次我改了EDS里的索引号,忘了更新代码里的宏引用,结果运行时SDO读写全乱套了。
2.4 运行时OD访问
运行时访问OD,说白了就是通过SDO或PDO读写OD里的数据。CANopenNode提供了统一的API:
/* 读取OD条目 */
CO_OD_getEntry(od, 0x1000, 0x00, &value, &size);
/* 写入OD条目 */
CO_OD_setEntry(od, 0x1017, 0x00, &value, size);
/* 检查OD条目是否存在 */
CO_OD_checkEntry(od, 0x2000, 0x01);
这些API内部会遍历CO_OD_array,找到对应的索引和子索引,然后执行读写操作。注意,写操作会触发回调函数,比如:
CO_ODF_1000:索引0x1000的写回调CO_ODF_1017:索引0x1017的写回调
回调函数里可以做一些额外处理,比如更新硬件寄存器。我在电机控制项目中,就把PID参数更新放在回调里,这样SDO写入后立即生效。
性能提示:运行时频繁通过SDO读写OD会有延迟。如果数据需要实时更新(比如电机转速),建议用PDO映射。OD只是“数据库”,PDO才是“高速公路”。
2.5 OD与电机控制的关联
在电机控制中,OD里最关键的几个对象:
| 索引 | 名称 | 作用 |
|---|---|---|
| 0x6040 | 控制字 | 启动/停止/使能电机 |
| 0x6041 | 状态字 | 读取电机当前状态 |
| 0x607A | 目标位置 | 位置模式下的目标值 |
| 0x6083 | 加速度 | 加减速时间 |
这些对象在OD中定义好后,主站通过SDO或PDO访问它们,就能控制电机了。我做过一个项目,主站每10ms通过PDO发送目标位置,电机跟着跑,效果很流畅。
实战建议:在电机控制项目中,我建议把所有需要实时更新的参数(如目标位置、速度)都做成PDO可映射。而配置类参数(如PID系数、加速度)用SDO访问。这样既保证了实时性,又方便调试。
嗯,OD这块内容确实不少,但它是CANopen的基石。理解了OD,后面学PDO、SDO、同步机制都会轻松很多。记住:OD是“数据库”,EDS是“数据库定义”,CO_OD.c/h是“数据库实现”,运行时API是“数据库接口”。把这四层关系理清了,CANopen就算入门了。