1. CANopen协议概述:从CAN总线到CANopen

各位同学,咱们今天正式开课。做嵌入式实时控制,CANopen 是绕不开的一关。我最早接触它是在一个伺服驱动器项目上,当时被对象字典搞得一头雾水——后来才发现,这东西说白了就是个“通信协议+设备描述”的标准化方案。

先别急着看代码,咱们把地基打牢。

1.1 从CAN总线到CANopen

CAN 总线(Controller Area Network)是底层硬件,它只负责把数据帧发出去、收回来。但问题来了——你发了一帧数据,里面 8 个字节到底代表什么?是速度值还是温度值?谁该响应?谁该忽略?

CANopen 就是在 CAN 总线之上,定义了一套“大家怎么说话”的规则。它把 CAN 帧的 11 位 ID 重新组织,分成功能码和节点 ID,还规定了数据内容的含义。

核心区别一句话:CAN 是物理层+数据链路层,CANopen 是应用层协议。没有 CANopen,你只能看到一堆 0 和 1;有了 CANopen,你才知道“0x41 0x00”是“电机目标转速 1000 rpm”。

我刚开始做项目时,直接用 CAN 裸帧通信,结果每次换设备都要重新对协议,累得够呛。后来切到 CANopen,才发现标准化有多香。

1.2 对象字典(OD)的概念

对象字典是 CANopen 的灵魂。你可以把它想象成一个“设备参数大表格”。每个参数都有一个 16 位索引(Index)和 8 位子索引(Subindex)。

举个例子:

索引 子索引 参数名称 数据类型 说明
0x6007 0x00 Abort Connection Option UNSIGNED8 节点掉线后的行为
0x6040 0x00 Controlword UNSIGNED16 控制字,启停电机
0x6041 0x00 Statusword UNSIGNED16 状态字,读取状态

对象字典里不光存数据,还存了数据的属性——比如读写权限、数据类型、默认值。我见过不少新手直接往 0x6040 写 0x0006,结果电机没反应。为什么?因为控制字要先写 0x06 进入“准备就绪”状态,再写 0x0F 才能启动。这些状态机逻辑,都写在对象字典对应的设备配置文件里。

避坑指南:我曾经在调试时发现对象字典里某个参数怎么都写不进去,查了半天才发现是子索引写错了。记住:子索引 0x00 通常代表“整个对象”或“默认值”,具体要看 DS-301 或设备子协议的定义。

1.3 PDO与SDO的区别

这是 CANopen 里最核心的两个通信机制。我直接说人话:

  • SDO(Service Data Object):像快递,点对点,确认送达。你发一个请求,对方回一个响应。适合配置参数、读写对象字典。
  • PDO(Process Data Object):像广播,一对多,不确认。适合实时传输控制数据,比如速度、位置、电流。

为什么要有两种?你想想看:

  • SDO 有握手、有确认,速度慢但可靠。适合上电初始化时配置参数。
  • PDO 没有握手,一帧最多 8 字节数据,速度快。适合 1ms 甚至更快的控制周期。

关键区别表:

特性 SDO PDO
通信方式 点对点,请求-响应 生产者-消费者,无确认
数据长度 最多 4 字节(分段可更多) 最多 8 字节
用途 配置、诊断、读写 OD 实时控制、状态反馈
实时性 低(ms 级) 高(μs 级)
CAN ID 分配 固定(0x580+节点ID 等) 可映射,可配置

我记得有个项目,客户要求 500μs 内完成 4 个轴的位置同步。用 SDO 肯定不行,一帧来回就 200μs 了。最后全部切到 PDO,配合同步帧,才把抖动压下去。

1.4 CANopenNode软件架构简介

CANopenNode 是一个开源的 CANopen 协议栈实现,用 C 语言写的。我选它做课程平台,原因有三:

  1. 代码干净:分层清晰,移植方便。我在 STM32、AT32、GD32 上都跑过。
  2. 文档齐全:虽然英文为主,但例程很全。
  3. 社区活跃:遇到问题,GitHub Issues 里基本能找到答案。

它的架构大致分三层:

  • CAN 驱动层:负责收发 CAN 帧,跟硬件打交道。
  • 协议栈核心层:处理 NMT、SDO、PDO、心跳、同步等协议逻辑。
  • 应用层:你的业务代码,比如电机控制算法、传感器采集。

下面这张图,是我自己画的一个简化架构,你看一眼就明白了:

CANopenNode 软件架构 应用层(Application) 电机控制 / 传感器采集 / 用户逻辑 协议栈核心层(CANopenNode Core) NMT SDO PDO 心跳 对象字典(OD)管理 CAN 驱动层(CAN Driver) CAN 控制器初始化 / 帧收发 / 中断处理 硬件层(CAN Transceiver + MCU) 用户只需修改 应用层和 OD 配置 驱动层需根据 具体 MCU 移植

从图上你能看到,应用层在最上面,你写业务逻辑;核心层在中间,处理所有 CANopen 协议细节;驱动层在最下面,跟硬件打交道。我一般移植时,只改驱动层和应用层,核心层基本不动。

注意:CANopenNode 默认使用定时器中断来驱动协议栈的定时事件(比如心跳、同步)。如果你用的 MCU 定时器资源紧张,记得提前规划好。我曾经在 GD32F103 上因为定时器冲突,导致心跳丢帧,排查了两天才发现是定时器优先级没配好。

好了,第一章就到这里。对象字典、PDO、SDO 这三个概念,你最好能用自己的话讲清楚。后面几章,咱们会一步步搭建一个真实的 CANopen 从站,从 OD 配置到 PDO 映射,再到实时控制,全走一遍。