2、SDO对象字典基础:对象字典的结构、索引与子索引的概念、常用对象字典条目解析
好,咱们进入正题。上一章聊了SDO的报文格式,但SDO到底在读写什么?说白了,就是对象字典。对象字典是CANopen设备的灵魂,没有它,SDO就是个空壳子。
我刚开始接触CANopen时,觉得对象字典这名字挺唬人。后来发现,它其实就是一张大表格,把设备里所有能访问的参数都列了出来。每个参数都有个门牌号,SDO就是快递员,按门牌号送货取货。
2.1 对象字典的结构
对象字典在CANopen标准里叫Object Dictionary,缩写OD。它本质上是一个线性数组,每个元素叫一个对象。每个对象用16位的索引来定位,范围从0x0000到0xFFFF。
但别被这个范围吓到。实际用到的索引,主要集中在几个区域:
| 索引范围 | 用途 | 我常用的 |
|---|---|---|
| 0x0000 | 保留,不用 | — |
| 0x0001 – 0x001F | 静态数据类型定义 | 基本不碰 |
| 0x0020 – 0x003F | 复杂数据类型定义 | 偶尔看 |
| 0x1000 – 0x1FFF | 通信对象 | 天天打交道 |
| 0x2000 – 0x5FFF | 制造商特定对象 | 自己定义参数放这里 |
| 0x6000 – 0x9FFF | 标准化设备子协议 | 看设备类型 |
| 0xA000 – 0xFFFF | 保留或网络专用 | 几乎不用 |
你看,真正干活的就是0x1000到0x5FFF这块。通信参数、设备信息、你自己的应用数据,全塞在这里面。
核心要点:对象字典不是硬盘,不是存文件的。它是运行时的参数映射表,每个条目对应一个变量或一组变量。SDO读写对象字典,本质上就是在读写这些变量。
2.2 索引与子索引的概念
索引是16位的,够用吗?对于简单设备,一个索引对应一个参数,没问题。但有些参数是数组或结构体,比如一个电机驱动器的电流环PID参数,有P、I、D三个值。如果每个值占一个索引,太浪费了。
所以CANopen引入了子索引。子索引是8位的,范围0x00到0xFF。索引+子索引,就能精确定位到对象字典里的任何一个条目。
举个例子,我调试过一个伺服驱动器,它的位置环参数放在索引0x2000下:
- 子索引0x00:表示这个对象有多少个子条目(这里是3个)
- 子索引0x01:位置环比例增益Kp
- 子索引0x02:位置环积分时间Ti
- 子索引0x03:位置环微分时间Td
SDO读0x2000子索引0x01,拿到的就是Kp值。读0x2000子索引0x00,拿到的是3,告诉你下面有三个参数。
我的习惯:设计对象字典时,子索引0x00一定要填对。很多从站调试工具会先读子索引0x00来校验参数个数。填错了,工具直接报错,排查起来很头疼。
子索引的编号规则,标准里其实有建议:
- 0x00:强制存在,表示子条目数量(对于数组或记录)
- 0x01 – 0xFE:实际数据条目
- 0xFF:扩展或特殊用途,很少用
对于单个变量(比如一个32位整数),子索引通常只有0x00,而且它的值就是变量本身。但标准规定,单变量对象的子索引0x00必须返回0x01,表示只有一个子条目。嗯,这里要注意,有些初学者会忽略这个细节。
2.3 常用对象字典条目解析
咱们做从站调试,有几个索引是绕不开的。我把它们列出来,你调试时心里有个数。
2.3.1 设备信息区(0x1000 – 0x1029)
这一块是CANopen标准强制或推荐实现的,用来描述设备本身。
| 索引 | 名称 | 说明 |
|---|---|---|
| 0x1000 | 设备类型 | 32位,高16位是设备子协议号,低16位是附加信息 |
| 0x1001 | 错误寄存器 | 8位,每一位代表一类错误,调试时经常看 |
| 0x1005 | COB-ID同步报文 | 定义SYNC报文的ID,默认0x80 |
| 0x1017 | 心跳生产者时间 | 单位毫秒,0表示不发送心跳 |
| 0x1018 | 设备标识 | 包含厂商ID、产品代码、版本号等,子索引0x01-0x04 |
我记得有一次,现场设备死活连不上主站。我读0x1001一看,错误寄存器是0x10,查手册发现是通信错误。再读0x1005,发现COB-ID被改成了0x00。原来是有人误操作改了SYNC ID。这种问题,不看对象字典根本找不到原因。
2.3.2 通信参数区(0x1200 – 0x13FF)
SDO通信相关的参数,主要在这里。
- 0x1200:服务器SDO参数(从站用)
- 0x1280 – 0x12FF:额外的服务器SDO(如果你需要多个SDO通道)
- 0x1300 – 0x13FF:客户端SDO参数(主站用,从站一般不需要)
0x1200的子索引结构是这样的:
| 子索引 | 名称 | 说明 |
|---|---|---|
| 0x00 | 最大子索引 | 固定为0x03 |
| 0x01 | COB-ID客户端→服务器 | 默认0x600 + 节点ID |
| 0x02 | COB-ID服务器→客户端 | 默认0x580 + 节点ID |
| 0x03 | 节点ID | 可选,有些实现不填 |
你想想看,如果SDO通信不通,第一件事就是读0x1200,确认COB-ID对不对。我曾经遇到一个从站,0x1201子索引0x01的值被写成了0x700,结果SDO请求发过去石沉大海。查了半天,才发现是COB-ID被改了。
2.3.3 制造商特定区(0x2000 – 0x5FFF)
这一块是留给咱们自己发挥的。你的传感器、执行器、控制参数,全放这里。
设计时我建议:
- 0x2000 – 0x2FFF:放配置参数(上电后一般不常改)
- 0x3000 – 0x3FFF:放运行参数(运行时频繁读写)
- 0x4000 – 0x4FFF:放诊断信息(只读)
举个例子,一个温度传感器从站,我可能会这样设计:
| 索引 | 子索引 | 名称 | 类型 | 访问 |
|---|---|---|---|---|
| 0x2000 | 0x01 | 温度报警上限 | INT16 | 读写 |
| 0x2000 | 0x02 | 温度报警下限 | INT16 | 读写 |
| 0x3000 | 0x01 | 当前温度值 | INT16 | 只读 |
| 0x4000 | 0x01 | 最大温度记录 | INT16 | 只读 |
| 0x4000 | 0x02 | 最小温度记录 | INT16 | 只读 |
避坑指南:我曾经在0x2000里放了一个32位浮点数,但忘了对齐。结果主站用SDO读回来,数值总是错的。后来发现,对象字典里的数据对齐很重要。CANopen标准建议4字节数据按4字节对齐,2字节按2字节对齐。别偷懒,对齐做好,少很多麻烦。
2.4 对象字典的访问属性
每个对象字典条目,除了索引、子索引、数据类型,还有访问属性。这个属性决定了SDO能不能读写它。
- ro:只读。比如设备版本号,只能读不能写。
- wo:只写。很少见,但有些命令参数是只写的。
- rw:读写。大部分配置参数都是这个。
- const:常量。读出来固定不变,比如0x1018子索引0x01的厂商ID。
调试时,如果你写一个参数写不进去,先看看它的访问属性是不是ro。我见过有人对着只读参数写了一天,还以为是SDO协议有问题。
2.5 对象字典与SDO的关系
到这里,你应该明白了:SDO就是对象字典的读写工具。SDO请求里带的索引和子索引,就是对象字典的门牌号。主站发一个SDO写请求,把数据送到指定索引子索引的位置;从站收到后,更新对应的变量,再回复确认。
反过来,主站发SDO读请求,从站从对象字典里取出数据,打包成SDO响应发回去。
所以,调试SDO通信,本质上就是调试对象字典的配置。对象字典配对了,SDO通信就成功了一半。
一句话总结:对象字典是CANopen设备的“记忆体”,SDO是访问这个记忆体的“手”。你把手伸对了地方,才能拿到想要的东西。
这张图把关系画得很清楚。主站发SDO请求,从站收到后去对象字典里找对应的索引子索引,然后返回数据。整个过程,对象字典就是那个“数据库”。
调试小技巧:如果你手头有CAN分析仪,抓一下SDO报文,然后对照对象字典表,一条一条对。你会发现,SDO报文里的索引和子索引,就是对象字典里的门牌号。对上了,通信就通了。