2. CANopen核心概念:对象字典(OD)、PDO、SDO、NMT、心跳报文
好,咱们直接切入正题。这一章要聊的,是CANopen协议栈里最核心的几个概念。说实话,我刚接触CANopen那会儿,也被这些缩写搞得头晕。什么OD、PDO、SDO、NMT……感觉像在背密码表。但后来我发现,只要搞懂它们各自是干什么的,整个协议栈的骨架就清晰了。
我个人习惯,把CANopen想象成一个工厂车间。对象字典就是仓库里的货架,PDO是传送带,SDO是维修工,NMT是车间主任,心跳报文就是设备上的指示灯。这么一比喻,是不是好理解多了?
2.1 对象字典(Object Dictionary, OD)
对象字典,说白了就是设备的“户口本”。每个CANopen设备都有一个OD,里面记录了设备的所有参数、配置、状态和数据。你想想看,一个驱动器要告诉主站“我的转速是多少”、“我的电流限值是多少”,这些信息都存在OD里。
OD的索引范围是16位的,从0x0000到0xFFFF。但实际常用的就几个区段:
| 索引范围 | 用途 | 我踩过的坑 |
|---|---|---|
| 0x1000 - 0x1FFF | 通信对象(设备类型、错误寄存器等) | 这里千万别乱改,尤其是0x1000 |
| 0x2000 - 0x5FFF | 制造商特定参数 | 我最喜欢在这里放自定义参数 |
| 0x6000 - 0x9FFF | 标准设备行规(如DS402驱动) | 做伺服驱动时,这里必须对齐 |
每个对象还有一个子索引(Subindex),用来访问数组或结构体里的具体元素。比如一个“电机参数”对象,子索引0可能存参数个数,子索引1存额定电流,子索引2存额定转速。
重点来了:移植协议栈时,OD的实现方式决定了你的内存占用和访问速度。我见过有人用线性数组存OD,查找时一个个遍历,结果在100个对象时性能就崩了。我个人推荐用哈希表或者二分查找,尤其是对象数量超过50个时。
我的小技巧:在OD里给每个对象加一个“访问权限”字段。比如只读、只写、可读写。这样在SDO访问时可以直接做权限校验,省得后面出问题再回头查。
2.2 过程数据对象(PDO)
PDO,就是那条“传送带”。它用来传输实时性要求高的数据,比如编码器位置、电流值、控制字。PDO的特点是:一包数据里可以塞多个对象,而且不需要应答,发出去就不管了。
PDO分两种:
- TPDO(发送PDO):设备主动往外发数据。比如伺服驱动器每1ms发一次当前位置。
- RPDO(接收PDO):设备接收来自主站或其他节点的数据。比如主站发一个目标位置给驱动器。
PDO的触发方式有几种:
- 事件触发:数据变化了就发。我做过一个项目,编码器值每变化1个脉冲就发一次PDO,结果总线直接被撑爆了……后来加了死区阈值才解决。
- 定时触发:固定周期发送。比如每10ms发一次,适合周期性数据。
- 远程帧触发:收到远程帧才发。这种方式用得少,但某些场景下很实用。
注意:PDO的映射(Mapping)决定了哪几个对象被塞进同一包数据里。映射关系写在OD的0x1A00-0x1BFF区域。我曾经犯过一个低级错误:把两个8位的数据映射到一个16位的槽里,结果数据全错位了。记住,映射时数据长度必须严格匹配。
2.3 服务数据对象(SDO)
SDO就是那个“维修工”。它用来访问OD里的任意对象,而且有应答机制,保证数据准确到达。SDO的传输速度比PDO慢,但胜在可靠。
SDO的通信机制是“客户端-服务器”模式:
- 客户端(通常是主站)发起请求:我要读0x6040这个对象。
- 服务器(从站)回复:0x6040的值是0x0006。
SDO的报文格式有点复杂,我直接给个例子:
// 读请求:索引0x6040,子索引0x00
// 发送:0x40 + 0x6040 + 0x00 + 0x00000000
// 回复:0x42 + 0x6040 + 0x00 + 0x00000006
// 0x40表示读请求,0x42表示读成功且数据长度为2字节
嗯,这里要注意:SDO的传输有分段机制。如果数据超过4字节,就要分成多段发送。我记得有一次调试一个固件升级功能,要写一个256字节的配置文件到OD里,结果SDO分段传输的时序没处理好,老是丢包。后来加了超时重传机制才搞定。
避坑指南:我曾经在SDO的回复里忘记设置“紧急标志”,导致主站一直收不到完成信号。记住,SDO回复的最后一个字节(控制字)一定要正确设置e位和s位。
2.4 网络管理(NMT)
NMT就是车间主任,负责管理所有设备的状态机。每个CANopen设备都有固定的状态机:
- 初始化(Initialization):上电后自动进入,完成硬件初始化和OD加载。
- 预操作(Pre-Operational):可以走SDO,但不能走PDO。适合配置参数。
- 操作(Operational):正常工作时,PDO和SDO都能用。
- 停止(Stopped):除了NMT,其他通信都停了。
NMT的命令很简单,就一个报文:
// 让节点1进入Operational状态
// COB-ID: 0x0000
// 数据: 0x01 0x01
// 第一个字节是命令(0x01=启动),第二个字节是节点ID
我个人习惯,在系统启动时先让所有节点进入Pre-Operational,然后逐个配置参数,最后统一发NMT命令切换到Operational。这样能避免配置过程中PDO乱发数据。
2.5 心跳报文(Heartbeat)
心跳报文,就是设备上的“指示灯”。它定期发送,告诉主站“我还活着”。如果主站超过一定时间没收到心跳,就知道这个设备挂了,可以采取相应措施。
心跳的配置在OD的0x1017对象里:
- 写入0:关闭心跳
- 写入非0值:心跳周期(单位毫秒)。比如写入100,就是每100ms发一次。
主站那边还有一个“心跳消费时间”(0x1016),用来设置超时阈值。比如消费时间设为300ms,那如果300ms内没收到心跳,就判定设备离线。
实战经验:我在一个自动化产线项目里,把心跳周期设成了1000ms,消费时间设成了3000ms。结果有一次总线负载高了,心跳报文延迟了2.8秒,差点触发误报警。后来我把周期改成500ms,消费时间改成1500ms,问题就解决了。记住,心跳周期和消费时间之间要留够余量,别卡得太死。
2.6 核心概念关系图
下面这张图,是我自己画的,把今天讲的几个概念串起来了。你看一眼,应该就能明白它们之间的关系:
你看,OD是核心,所有数据都存里面。PDO和SDO是两条数据通道,一个快一个稳。NMT管着设备的状态切换,心跳则负责监控设备是否在线。这五个概念,就是CANopen的骨架。
好了,这一章的内容就到这儿。记住,搞懂这些概念,你移植协议栈时就不会迷路。下一章咱们直接上手,讲怎么把CANopen协议栈移植到STM32上。