跳到主要内容

07. CANopen 是什么

学习形式概念课
硬件要求不需要
配套 Demo
本课动作不写电机对象

第 06 课的 ESP32-C3 程序从总线上实际收到了:

RX CAN-ID 0x701 DLC 1 DATA 05

我们已经能看懂这是一帧 CAN 报文:

0x701 CAN-ID
1 数据长度为 1 字节
05 这 1 字节数据的值

但只知道这些,还不知道电机想表达什么。

这一课先不发送新报文,也不读写电机参数。我们就围绕这一行真实日志,把下面两个问题弄清楚:

电机为什么使用 CAN-ID 0x701?
数据 05 为什么可以表示电机当前的状态?

这两个问题的答案都来自 CANopen。

CAN 把报文送到,但不解释数据

收到 0x701 [1] 05 时,CAN 控制器已经做了很多事:

识别帧起始
-> 参与 CAN-ID 仲裁
-> 接收 DLC 和 DATA
-> 检查帧格式和 CRC
-> 正确接收时发出 ACK
-> 把 CAN-ID、DLC 和 DATA 交给程序

CAN 规定了电信号、报文格式、仲裁、校验和错误处理。它可以让不同节点正确收发一帧数据,却没有规定 0x70105 必须表示什么。

例如,另一套不使用 CANopen 的设备也可以发送:

CAN-ID 0x701 DLC 1 DATA 05

对那套设备来说,05 完全可能表示传感器编号或某个厂家自定义状态。它的 CAN 帧仍然正确,但业务含义与当前电机不同。

因此,收到正确的 CAN 帧,只能证明底层通信已经建立。要解释这帧数据,还要知道设备遵守哪种上层通信规则。

CANopen 让通信双方使用同一套规则

IG35EC020 的厂家资料说明该电机支持 CANopen。因此,我们不能随意猜测 0x701 [1] 05 的含义,而要按 CANopen 的规则解读它。

CANopen 建立在 Classical CAN 之上。它没有取代 CAN,也没有增加新的总线线缆。它主要约定:

  • 总线上的设备怎样编号;
  • 一帧报文用来报告状态、访问参数,还是传输过程数据;
  • 报文数据区中的字节应当怎样解释;
  • 通信双方应当按什么步骤交换数据。

同一帧报文,现在可以从两个角度来看:

查看角度能读出什么
Classical CANCAN-ID 是 0x701,DLC 是 1,DATA 是 05
CANopenNode-ID 为 1 的节点正在报告自己的运行状态

可以把这种分工写成:

CAN 负责把帧正确送到总线上
CANopen 负责规定这帧报文的用途和数据含义

先找出报文来自哪个节点

CANopen 把总线上的每台设备称为一个节点,英文是 Node。正常节点使用一个 1-127 范围内的编号,这个编号叫作 Node-ID

本教程实验平台:IG35EC020 厂家手册给出的默认 Node-ID 是 1。这个数值来自设备资料,不是根据串口日志猜出来的。

Node-ID 1 只表示“这是 1 号节点”,它还不是最终出现在报文里的 CAN-ID。

原因很直接:同一台设备不只会发送一种报文。它既要报告自己仍然在线,也要返回设备参数,以后还可能传输位置和速度。

所以,一条 CANopen 报文的默认 CAN-ID 通常要让接收方看出两件事:

这帧报文用来做什么
+
这帧报文属于哪个节点

再找出这帧报文用来做什么

CANopen 为常用的报文用途分配了默认编号区域。现在只看与后续实验有关的三类:

报文用途默认基础编号
节点启动或周期报告在线状态(Boot-up/Heartbeat)0x700
向节点请求访问一个参数(SDO 请求)0x600
节点返回参数访问结果(SDO 响应)0x580

这些基础编号来自 CANopen 通信规范。0x700 不是 ESP32 程序选择的,也不是电机厂家临时定义的。

可以直接在 CAN in Automation 发布的 CiA 301 CANopen 海报中核对。整张海报很大,现在不需要从头读到尾,只找底部的 Pre-defined CAN-IDs 表。下面截取的是这张表的右半部分。

CiA 301 海报中的 Pre-defined CAN-IDs 表

图片来源:CAN in Automation,CiA 301 CANopen poster(2024),第 1 页的 Pre-defined CAN-IDs 区域。

这里的 CiA 是 CAN in Automation 的缩写,301 是该组织分配给这套 CANopen 通用通信规范的文档编号,不是软件版本号。第 08 课会专门说明 CiA 301、CiA 401 和 CiA 402 的关系。

先看截图倒数第二行:

Object:NMT error control
Specification:CiA 301
CAN-ID:701h to 77Fh (700h + node-ID)

这里不能把 NMT error control 直接翻译成“Heartbeat”。它是一个范围更大的分类,包含几种用于确认节点启动或监视节点是否正常在线的通信:

NMT error control
|
+-- Boot-up 节点初始化完成时发送一次
+-- Heartbeat 节点按照设定周期主动发送
+-- Node/Life Guarding 较早使用的节点监视方式

Heartbeat 被归入“错误控制”,不是因为它每次都在报告错误,而是因为接收方可以利用它发现错误。

例如,某个节点约定每 1000 ms 发送一次 Heartbeat。负责监视它的设备会同时计时:

按时收到 Heartbeat -> 节点仍然在线,并且可以读出它的 NMT 状态
超过规定时间仍未收到 -> 节点掉线、断电或总线通信可能出现异常

所以,Heartbeat 是 NMT error control 中的一种协议,两者不是同一个概念。当前实测的 0x701 [1] 05 周期出现,因此它具体是一帧 Heartbeat;海报则把共享这段 CAN-ID 范围的几种协议归在更大的 NMT error control 分类下。

现在再关注这一行最右边的 CAN-ID。

海报使用数字右下角的 h 表示十六进制,C 代码和本教程则习惯在数字前写 0x。两种写法表示相同的数:

海报写法 700h + node-ID
代码写法 0x700 + Node-ID

表中的 701h to 77Fh 表示实际 CAN-ID 的范围。因为普通节点的 Node-ID 从 1 开始,所以这个范围的第一个编号是:

700h + 01h = 701h

换成本教程使用的写法就是:

0x700 + 0x01 = 0x701

对这些默认报文,把用途的基础编号和设备的 Node-ID 组合起来,就得到最终的 CAN-ID:

默认 CAN-ID = 报文用途的基础编号 + Node-ID

现在就能计算电机的状态报文:

报文用途:节点启动或周期报告状态
基础编号:0x700
电机 Node-ID:0x01

0x700 + 0x01 = 0x701

这就是串口日志中出现 CAN-ID 0x701 的原因。

Node-ID 为 1 的同一台电机,以后进行参数访问时,还会使用 0x6010x581。所以,“电机的 Node-ID 是 1”是正确的,“电机永远只使用 CAN-ID 0x701”则不正确。

0x700 怎样在 11 位 CAN-ID 中容纳 Node-ID

前面已经可以算出 0x701。如果还想知道为什么基础编号加上 Node-ID 后不会破坏原来的报文用途,可以继续看它的二进制结构。

很多 CANopen 默认节点报文使用下面的 11 位布局:

bit 10 ... bit 7 bit 6 ........ bit 0
+---------------+-----------------------+
| 报文用途 | 7 位 Node-ID |
+---------------+-----------------------+

Node-ID 的有效范围是 1-127,最多只需要低 7 位。因此,它不会覆盖高 4 位中表示的报文用途。

Heartbeat 所在区域的高 4 位是 1110。先把 Node-ID 的位置全部留为 0

报文用途 1110
Node-ID 位置 0000000
-----------
11 位 CAN-ID 1110 0000000 = 0x700

再把 Node-ID 1 放进低 7 位:

报文用途 1110
Node-ID 1 0000001
-----------
11 位 CAN-ID 1110 0000001 = 0x701

这就是 0x700 + Node-ID 在二进制中的实际样子。

现在再看数据 05

CAN-ID 0x701 已经告诉我们:这是 Node-ID 1 的节点发出的启动或周期状态报文。

这类周期状态报文在 CANopen 中叫作 Heartbeat,中文常译为心跳。它的数据区只有 1 字节,用来表示节点当前的 NMT 状态。

CANopen 规定:

DATA 05 = Operational(运行状态)

因此,可以把完整报文读成:

CAN-ID 0x701 -> Node-ID 1 的 Heartbeat
DLC 1 -> 状态数据占 1 字节
DATA 05 -> 该节点当前处于 Operational

第 09 课会从节点上电开始,连续观察 Boot-up、NMT 状态变化和 Heartbeat 周期。到那时再把 0004057F 等状态值放到完整过程中理解。

读 CANopen 资料时会看到 COB-ID

现在已经知道 0x701 是一帧报文的 CAN-ID。但查看 CANopen 手册或配置工具时,可能会看到这样的表述:

Heartbeat COB-ID = 0x701

这里没有出现第二个 ID,仍然是前面收到的 0x701

CANopen 把 Heartbeat、SDO 和 PDO 这些具有明确用途的通信称为 通信对象,英文是 Communication Object,缩写为 COB。某项通信所使用的 CAN-ID,就称为 COB-ID。

因此,下面两句话指向同一个数值:

查看 CAN 帧时: 这帧报文的 CAN-ID 是 0x701
查看 CANopen 通信时:该 Heartbeat 的 COB-ID 是 0x701

对当前课程来说,可以先把 COB-ID 理解成“某项 CANopen 通信使用的 CAN-ID”。

这帧 Heartbeat 只是 CANopen 的起点

现在读懂的 0x701 [1] 05 只在报告“1 号节点当前处于运行状态”。它没有携带电机型号、实际位置或报警代码。

后面需要解决的问题不同,CANopen 也准备了不同的通信方式:

我们想做什么后续会学什么
确认节点上电、在线以及当前状态NMT、Boot-up 和 Heartbeat
按统一编号找到设备参数对象字典
读取一个设备参数SDO
周期传输位置、速度等过程数据PDO
理解电机状态、模式和运动命令CiA 402

这里先不展开这些名词。这张表只说明:当我们从“节点是否在线”继续走到“读参数”和“控制电机”时,每一步都有对应的 CANopen 规则,不需要自己猜 CAN-ID 和数据含义。

这些规则在实验平台中位于哪里

下图从电线一直画到电机应用。看图时沿着中间的箭头从下向上看:越靠下,越接近真实电信号;越靠上,越接近“电机现在是什么状态”。

CAN、CANopen、CiA 402 与实验硬件的关系

在本教程的实验平台中:

CANH、CANL 和 TJA1050 负责总线上的差分电信号
ESP32-C3 内部 TWAI 收发 Classical CAN 帧
CANopen 解释节点、报文用途和数据含义
CiA 402 进一步规定驱动器的状态和运动控制

这也说明了为什么要先学 CAN,再学 CANopen。如果 CANH、CANL 接线或波特率错误,底层连一帧报文都收不到,上层规则也就没有可以解释的数据。

第 06 课的程序已经是 CANopen 程序了吗

还不是。

第 06 课的程序只使用 ESP-IDF TWAI 驱动:

收到 CAN 帧
-> 取出 CAN-ID、DLC 和 DATA
-> 打印到串口

程序本身不知道 0x701 是 Heartbeat,也不知道 05 表示 Operational。现在是我们根据 CANopen 规则,用人脑解读串口日志。

后面会使用一个名为 CANopenNode 的开源 CANopen 协议栈。这里的协议栈可以先理解成一套已经写好的协议程序:它负责执行节点状态、通信超时和参数访问等 CANopen 规则。先看懂原始报文,到时才能知道这套程序替应用代码处理了什么。

回顾:CANopen 是什么

现在先把海报、公式和二进制放在一边,回到这一课的标题。

CANopen 是建立在 Classical CAN 之上的设备通信规范。

CAN 负责一帧报文怎样在总线上传输,包括电信号、帧格式、仲裁、校验和 ACK。CANopen 接着规定这帧报文由哪个节点发送、用来做什么、数据怎样解释,以及通信双方按照什么步骤配合。

Classical CAN 解决“怎样把一帧数据正确送到总线上”
CANopen 解决“设备之间怎样理解和使用这帧数据”

CANopen 不是 ESP32-C3 内部的一块外设。ESP32-C3 中负责收发 Classical CAN 帧的外设叫 TWAI。CANopen 也不是另一种接线方式,它仍然通过 CANH 和 CANL 传输普通 CAN 帧。

CANopen 为设备通信提供了一套共同规则。现在已经接触到其中几项:

  • 使用 Node-ID 区分总线上的节点;
  • 根据报文用途和 Node-ID 得到默认 CAN-ID;
  • 使用 Heartbeat 周期报告节点在线状态;
  • 用规定的数据值表示当前 NMT 状态;
  • 在 CANopen 资料中,用 COB-ID 表示某项通信所使用的 CAN-ID。

第 06 课收到的原始报文是:

RX CAN-ID 0x701 DLC 1 DATA 05

只看 CAN,可以读出它的 CAN-ID、数据长度和数据。按照 CANopen 规则继续解释:

0x700 + 0x01 = 0x701

0x700 是 Boot-up/Heartbeat 使用的默认基础编号,0x01 是电机的 Node-ID。因此,0x701 表示 Node-ID 1 的节点所使用的 Boot-up/Heartbeat CAN-ID。

这帧报文周期出现,并且唯一的数据字节是 05,所以可以把它完整读成:

Node-ID 1 的电机正在通过 Heartbeat 报告:
我仍然在线,当前处于 Operational 状态。

如果只记住一句话,可以记成:

CAN 让设备能够传输报文,CANopen 让不同设备按照共同规则理解和使用报文。

自己算两次

一台 CANopen 设备的 Node-ID 是 5。它的默认 Heartbeat CAN-ID 是多少?

0x700 + 0x05 = ______

现在又收到一帧 Heartbeat:

CAN-ID 0x70A DLC 1 DATA 05

发送该报文的节点 Node-ID 是多少?

0x70A - 0x700 = ______

答案:

第一题:0x705
第二题:0x0A,也就是十进制 10

资料链接

CANopenNode 默认 CANopen 标识符:

CANopenNode 默认 CANopen 标识符

该页面列出了 Heartbeat、SDO、PDO 和 NMT 等通信使用的默认 CAN-ID 基础值。

CANopenNode NMT 与 Heartbeat:

CANopenNode NMT 与 Heartbeat

该页面列出了 Heartbeat 数据字节中的 NMT 状态值,例如 0x05 表示 Operational。

CAN in Automation:Error control protocols:

CiA:Error control protocols

该页面说明了 NMT error control 与 Boot-up、Heartbeat、Node/Life Guarding 的关系,以及这些协议为什么共同使用 0x700 + Node-ID

CAN in Automation:CiA 301 CANopen poster:

CANopen poster 2024

这份官方资料汇总了默认 CAN-ID、NMT、Heartbeat、SDO、PDO 和对象字典等内容。本课使用的是海报底部 Pre-defined CAN-IDs 表中的局部;打开完整海报后,可以用相同位置核对原文。

下一课先解决阅读 CANopen 资料时一定会遇到的编号问题:CiA 301、CiA 401 和 CiA 402 分别规定什么,它们为什么不是三个版本。