3、对象字典数据类型:基本数据类型(布尔、整数、浮点)、复合数据类型(数组、记录)、域数据类型

好,咱们今天聊聊对象字典里的数据类型。说实话,这块内容看起来有点枯燥,但它是整个CANopen协议的基石。你想想看,如果连数据怎么存、怎么读都搞不清楚,后面那些复杂的PDO、SDO配置根本玩不转。

我刚开始接触CANopen时,就被这些数据类型绕晕过。后来踩了几个坑,才慢慢摸清楚门道。今天我把这些经验梳理出来,希望能帮你少走弯路。

3.1 基本数据类型

基本数据类型,说白了就是最基础的数据单元。CANopenNode里支持的类型,跟C语言的标准类型基本一一对应。

3.1.1 布尔型(BOOLEAN)

布尔型最简单,就两个值:TRUE(1)或FALSE(0)。占用1个字节。我个人习惯,在配置设备参数时,凡是只有两种状态的,比如使能/禁止、启动/停止,都用布尔型。

小提示: 布尔型虽然只占1个字节,但在对象字典里索引时,读写操作还是按字节对齐的。别想着省空间去搞位操作,CANopen协议栈不认这个。

3.1.2 整数型

整数型分有符号和无符号两大类。具体看下表:

数据类型 长度(字节) 取值范围 CANopenNode中的宏定义
UNSIGNED8 1 0 ~ 255 ODT_UNSIGNED8
UNSIGNED16 2 0 ~ 65535 ODT_UNSIGNED16
UNSIGNED32 4 0 ~ 4294967295 ODT_UNSIGNED32
INTEGER8 1 -128 ~ 127 ODT_INTEGER8
INTEGER16 2 -32768 ~ 32767 ODT_INTEGER16
INTEGER32 4 -2147483648 ~ 2147483647 ODT_INTEGER32

嗯,这里要注意。CANopen标准里还有UNSIGNED64和INTEGER64,但CANopenNode默认不支持。我遇到过有人硬要往里塞64位数据,结果协议栈直接报错。如果你真需要,得自己扩展协议栈,但说实话,大部分工业现场用32位就够了。

3.1.3 浮点型

浮点型就两种:REAL32(单精度)和REAL64(双精度)。

  • REAL32:4字节,对应C语言的float。精度约7位有效数字。
  • REAL64:8字节,对应C语言的double。精度约15位有效数字。

我在项目中遇到过一个问题:用REAL32传温度值,结果发现小数点后第三位开始抖动。后来查了半天,发现是传感器精度本身只有0.1℃,但上位机非要显示到0.001℃。其实这种情况,用INTEGER16加个比例因子更靠谱。

避坑指南: 我曾经在电机驱动项目里,用REAL32传速度给定值。结果SDO通信时,上位机写进去的值和读回来的值差了0.01。后来发现是字节序问题——CANopen默认小端序,但上位机是大端序。所以用浮点型时,一定要确认主从站的字节序一致。

3.2 复合数据类型

复合数据类型,就是把基本数据类型组合起来用。CANopen里主要有两种:数组(ARRAY)和记录(RECORD)。

3.2.1 数组(ARRAY)

数组就是同一类型数据的集合。每个元素通过子索引(SubIndex)访问。子索引0通常存储数组长度,子索引1开始存实际数据。

举个例子,一个长度为5的UNSIGNED16数组:

// 对象字典定义示例
OD_ENTRY_HWP_ARRAY16(0x2001, 5, myArray)

// 访问方式
// 子索引0:读取数组长度(返回5)
// 子索引1:读取第1个元素
// 子索引2:读取第2个元素
// ...以此类推

我个人习惯,用数组来存多通道的采集数据。比如8通道的模拟量输入,每个通道一个UNSIGNED16,数组长度就是8。这样上位机通过SDO读子索引1~8,就能拿到所有通道的值。

3.2.2 记录(RECORD)

记录就灵活多了。它允许不同数据类型的成员组合在一起。每个成员也有自己的子索引。

典型的例子是设备配置参数:

子索引 参数名称 数据类型 说明
0 记录长度 UNSIGNED8 固定为3
1 设备ID UNSIGNED16 1~255
2 采样周期 UNSIGNED32 单位:微秒
3 使能标志 BOOLEAN TRUE/FALSE

你看,记录里可以混搭不同类型。这在配置复杂设备时特别有用。我做过一个伺服驱动器项目,对象字典里一个记录包含了位置环PID参数、速度环PID参数、电流限幅值……总共十几个成员,类型五花八门。用记录封装起来,管理起来清爽多了。

重点: 数组和记录的区别在于——数组所有元素类型必须相同,记录可以不同。但它们的访问方式一样,都是通过主索引+子索引。

3.3 域数据类型

域(DOMAIN)数据类型,是CANopen里比较特殊的存在。它不限定具体的数据类型,可以存放任意长度的数据。说白了,就是一个「万能口袋」。

域类型的特点:

  • 长度可变:不像基本类型那样固定字节数。
  • 类型灵活:可以存字符串、二进制数据、甚至自定义结构体。
  • 访问受限:域类型不支持PDO映射,只能通过SDO分段传输。

我举个例子。有一次客户要求通过CANopen升级固件。固件文件大小可能从几十KB到几百KB不等。用数组?不行,长度不确定。用记录?也不行,类型太复杂。最后就是用域类型,把固件数据包一股脑塞进去,上位机通过SDO分段读写,完美解决。

域类型在CANopenNode中的定义方式:

// 对象字典中定义域类型
OD_ENTRY_HWP_DOMAIN(0x1F50, firmwareData)

// 访问时,需要通过SDO分段传输
// 每次传输最多7字节(CAN标准帧数据场)
注意: 域类型虽然灵活,但性能开销大。每次读写都要走SDO分段传输,不适合实时性要求高的场合。我见过有人拿域类型传实时波形数据,结果CAN总线被SDO报文塞爆了,PDO通信全乱套。所以,域类型只适合配置参数、固件升级这类非实时场景。

3.4 实际应用中的选择建议

说了这么多,到底该怎么选?我总结几条经验:

  1. 状态开关类:用布尔型。简单直接,别整花活。
  2. 数值测量类:优先用整数型。精度不够就加比例因子,比浮点型靠谱。
  3. 高精度计算类:用REAL32或REAL64。但记得确认字节序。
  4. 多通道同类数据:用数组。子索引访问方便。
  5. 混合配置参数:用记录。结构清晰,扩展性好。
  6. 大块不定长数据:用域类型。但别滥用,小心总线负载。

嗯,数据类型这块就聊到这儿。说白了,选对数据类型,你的对象字典就成功了一半。下一章咱们聊聊对象字典的索引分配和子索引设计,那才是真正考验架构能力的地方。