4、对象字典条目结构:条目组成与属性定义
好,咱们今天来聊聊对象字典里最核心的东西——条目结构。
你想想看,对象字典就像一本厚厚的设备手册。每个条目就是手册里的一条记录。那这条记录长什么样?包含哪些字段?这就是我们要搞清楚的事。
4.1 条目组成:六个关键字段
每个对象字典条目,说白了由六个部分组成。我习惯把它们记成「一索二名三类型,属性和默认值不能忘」。
| 字段 | 说明 | 示例 |
|---|---|---|
| 索引(Index) | 16位地址,唯一标识一个对象 | 0x1000(设备类型) |
| 子索引(Subindex) | 8位编号,用于访问数组或记录内的元素 | 0x00、0x01、0x02 |
| 名称(Name) | 描述性字符串,让人看懂这是啥 | "Device Type" |
| 数据类型(DataType) | 决定存储大小和解释方式 | UNSIGNED32 |
| 属性(Access) | 读写权限控制 | ro、rw、wo、const |
| 默认值(Default Value) | 上电复位后的初始值 | 0x00000000 |
嗯,这里要注意:索引和子索引是必须的,其他字段可以省略或由实现决定。但实际项目中,我建议你把这六个字段都写清楚。为什么?因为后面调试的时候,少一个字段都可能让你抓狂。
4.2 索引:对象的身份证
索引是一个16位的无符号整数,范围从0x0000到0xFFFF。CANopen标准把索引空间划分成了几个区域:
- 0x0000-0x0FFF:数据类型定义区,一般不用
- 0x1000-0x1FFF:通信对象区(COB-ID、同步、心跳等)
- 0x2000-0x5FFF:制造商特定区,随便你用
- 0x6000-0x9FFF:标准设备行规区(比如驱动器的对象)
- 0xA000-0xFFFF:保留或网络管理用
我记得刚做第一个CANopen项目时,把自定义对象放在了0x1000附近,结果跟标准通信对象冲突了。嗯,从那以后我再也不敢乱用索引范围了。
4.3 子索引:数组里的位置
子索引是8位的,范围0x00-0xFF。它用来访问复合对象里的具体元素。
举个例子,对象0x1001(错误寄存器)只有一个值,子索引固定为0x00。但对象0x1017(生产者心跳时间)也只有一个值,同样子索引0x00。
那什么时候用多个子索引?
- 数组类型:比如0x6007(驱动器状态),子索引0x00表示元素个数,0x01开始才是实际数据
- 记录类型:比如0x1018(标识对象),子索引0x00是记录长度,0x01是厂商ID,0x02是产品代码...
我个人习惯:子索引0x00永远用来表示「有多少个有效子索引」。这是CANopen的约定,别打破它。
4.4 名称:让人看懂你在干啥
名称就是个字符串,比如"Device Type"、"Error Register"。它不影响功能,但影响可读性。
你想想看,如果你在EDS文件里看到一堆0x2000、0x2001,鬼知道是啥。但写成"Motor Current"、"Motor Voltage",一眼就明白了。
我建议:名称要遵循CANopen标准命名,不要自己瞎编。比如标准里叫"Producer Heartbeat Time",你就别写成"Heartbeat Send Time"。否则别人拿到你的EDS文件,还得猜半天。
4.5 数据类型:决定存储方式
数据类型告诉系统:这个对象占几个字节,怎么解释这些字节。
CANopen里常用的数据类型有:
| 类型名 | 大小 | 范围 |
|---|---|---|
| BOOLEAN | 1字节 | 0或1 |
| UNSIGNED8 | 1字节 | 0-255 |
| UNSIGNED16 | 2字节 | 0-65535 |
| UNSIGNED32 | 4字节 | 0-4294967295 |
| INTEGER8 | 1字节 | -128到127 |
| INTEGER16 | 2字节 | -32768到32767 |
| INTEGER32 | 4字节 | -2147483648到2147483647 |
| VISIBLE_STRING | 可变 | ASCII字符串 |
| OCTET_STRING | 可变 | 原始字节 |
这里有个坑:数据类型选错了,SDO读写就会出问题。我曾经把一个UNSIGNED16的变量配成了UNSIGNED32,结果主站读回来的值全是错的。排查了半天才发现是类型不匹配。
4.6 属性定义:rw、ro、wo、const
属性决定了外部设备(比如主站)怎么访问这个对象。这是对象字典里最容易搞混的地方。
CANopen定义了四种基本属性:
- rw(读写):既能读也能写。比如配置参数、控制字。
- ro(只读):只能读,不能写。比如状态字、错误码。
- wo(只写):只能写,不能读。这个用得少,但确实存在。比如某些命令寄存器。
- const(常量):固定值,不能改。比如设备类型、版本号。
重要提醒:属性是从外部视角定义的。对设备内部来说,它自己当然可以读写自己的对象。但对外部主站来说,属性就是访问权限。
举个例子:对象0x1000(设备类型)的属性是const。主站可以读它,但不能写它。如果你试图用SDO写0x1000,设备会返回异常响应。
嗯,这里有个细节:wo属性在CANopen里其实很少用。我做了这么多年项目,几乎没见过wo的对象。大部分情况下,要么是rw,要么是ro。const主要用于标识信息。
4.7 默认值:上电后的起点
默认值就是设备上电或复位后,这个对象的值。它很重要,因为:
- 如果默认值设错了,设备可能一开始就处于错误状态
- 有些主站会读取默认值来判断设备是否正常
- 存储/恢复功能(对象0x1010、0x1011)会用到默认值
我建议:所有对象都要有合理的默认值。别偷懒设为0。比如心跳时间,默认值设为0意味着心跳禁用。如果你想让设备默认发送心跳,就设成100ms或1000ms。
避坑指南:我曾经遇到一个项目,某个对象的默认值设成了0xFFFFFFFF,结果主站一读就报溢出。后来发现是数据类型是UNSIGNED16,但默认值写成了32位的最大值。这种低级错误,查起来真的很浪费时间。
4.8 实战:一个完整的条目定义
说了这么多,咱们看个完整的例子。假设我们要定义一个「电机目标转速」对象:
索引:0x2000
子索引:0x00(表示只有一个值)
名称:"Target Motor Speed"
数据类型:INTEGER16(转速范围-32768到32767 RPM)
属性:rw(主站可以读写)
默认值:0(上电后电机不转)
在EDS文件里,它长这样:
[2000]
SubNumber=1
ParameterName=Target Motor Speed
ObjectType=0x7
DataType=0x0003
AccessType=rw
DefaultValue=0
你看,每个字段都对应上了。ObjectType=0x7表示这是一个变量(VAR),DataType=0x0003对应INTEGER16。
我个人习惯:写EDS文件时,把注释也加上。比如:
; 电机目标转速,单位RPM,范围-32768到32767
[2000]
SubNumber=1
ParameterName=Target Motor Speed
ObjectType=0x7
DataType=0x0003
AccessType=rw
DefaultValue=0
这样过几个月再看,自己也能快速想起来当初为啥这么设。
4.9 小结
对象字典的条目结构,说白了就是六个字段:索引、子索引、名称、数据类型、属性、默认值。每个字段都有它的作用和约束。
属性定义里,rw和ro最常见,const用于固定信息,wo几乎不用。选对数据类型和默认值,能省去后面很多调试的麻烦。
嗯,下一章咱们聊聊对象字典的存储机制——怎么把配置保存到非易失性存储器里。这个在实际项目中特别重要,因为掉电后参数不能丢。