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几乎不用。选对数据类型和默认值,能省去后面很多调试的麻烦。

嗯,下一章咱们聊聊对象字典的存储机制——怎么把配置保存到非易失性存储器里。这个在实际项目中特别重要,因为掉电后参数不能丢。