1. 心跳机制概述:CANopenNode 心跳协议原理、报文结构与生产者/消费者模型

各位同学,咱们今天正式进入 CANopenNode 心跳机制的学习。心跳这个词,你想想看,就像人的脉搏一样——设备还活着吗?工作正常吗?这就是心跳要回答的问题。

我在做第一个 CANopen 项目时,调试阶段最头疼的就是设备突然“失联”。后来发现,心跳机制用好了,能省掉 80% 的排查时间。咱们今天就把这个基础打牢。

1.1 心跳协议原理:为什么需要心跳?

CANopen 网络里,多个节点共享一条总线。主站怎么知道从站还活着?从站怎么知道主站还在发指令?

心跳协议(Heartbeat Protocol)就是干这个的。它让每个节点定期发送一个“我还活着”的信号。如果某个节点在规定时间内没发心跳,其他节点就知道它出问题了。

核心概念:心跳不是数据交换,而是状态确认。它不传应用数据,只传“我还在”这个事实。

我个人习惯把心跳比作“设备间的握手”——不需要每次都握得很紧,但必须定期握一下。CANopenNode 的心跳机制遵循 CiA 301 和 CiA 302 标准,实现起来非常规范。

1.2 心跳报文结构:一个 COB-ID 就够了

心跳报文的结构非常简单。它只用一个 CAN 帧,数据域只有 1 个字节。嗯,你没看错,就 1 个字节。

字段 说明 值范围
COB-ID 0x700 + 节点 ID 0x701 ~ 0x7FF
DLC 数据长度,固定为 1 1
Data[0] 节点状态(NMT 状态) 0x00 ~ 0x7F

举个例子:节点 ID 为 5 的设备,它的心跳 COB-ID 就是 0x705。数据字节如果是 0x05,表示它处于 Operational 状态。

小技巧:我在调试时经常用 CAN 分析仪抓心跳报文。看到 0x700 + ID 的帧,基本就能确认心跳在跑。如果连续几个周期没看到,那就要查节点电源或总线了。

NMT 状态码是心跳报文的核心数据。常用的状态码如下:

状态码 NMT 状态 含义
0x00 Boot-up 节点启动完成,准备进入 Pre-operational
0x04 Stopped 节点停止,不参与通信
0x05 Operational 节点正常运行,可收发 PDO/SDO
0x7F Pre-operational 节点初始化完成,可配置但不可运行

我曾经遇到过一个坑:某个设备启动后一直发 0x00(Boot-up),我以为它没初始化完。后来发现是固件里忘了切换状态。所以看到持续 0x00 时,别傻等,直接查代码里的状态机。

1.3 生产者/消费者模型:谁发谁收?

心跳机制采用生产者/消费者模型。说白了就是:一个节点发,其他节点收。

  • 生产者(Producer):发送心跳报文的节点。每个节点都可以是生产者,只要它配置了心跳发送周期。
  • 消费者(Consumer):接收心跳报文的节点。消费者会监控生产者的心跳,如果超时未收到,就触发错误处理。

在 CANopenNode 中,生产者通过对象字典 0x1017 配置心跳周期,消费者通过 0x1016 配置监控时间。

注意:消费者配置的监控时间必须大于生产者的发送周期。我一般设成周期的 2~3 倍,避免总线抖动导致误报。

你想想看,如果生产者的周期是 100ms,消费者设了 80ms 超时,那基本每次都会报错。这不是心跳的问题,是配置的问题。

1.4 心跳机制流程图

下面这张图展示了心跳机制的核心流程。我画的时候特意把生产者和消费者的关系拆开了,方便你理解。

CANopenNode 心跳机制流程图 心跳机制流程图 生产者(Producer) 1. 配置 0x1017 心跳周期 2. 定时器触发心跳发送 3. 发送 CAN 帧(0x700+ID) 4. 数据字节 = NMT 状态 循环:等待下一个周期 消费者(Consumer) 1. 配置 0x1016 监控时间 2. 监听 0x700+ID 的 CAN 帧 3. 收到心跳? 4. 重置超时计时器 超时?触发 NMT 错误处理 CAN 总线 说明:生产者周期发送心跳,消费者监听并监控超时。超时后消费者可触发 NMT 错误或切换状态。

1.5 心跳周期与超时配置

在 CANopenNode 中,心跳相关的对象字典条目非常清晰。我整理了一张表,方便你对照配置:

对象字典索引 名称 作用 单位
0x1017 Producer Heartbeat Time 设置心跳发送周期 毫秒(ms)
0x1016 Consumer Heartbeat Time 设置心跳监控超时时间 毫秒(ms)

配置 0x1017 时,设为 0 表示不发送心跳。我建议生产环境至少设 100ms,调试时可以设 500ms 方便观察。

避坑指南:我曾经把 0x1017 设成 10ms,结果总线被心跳报文塞满了。CAN 总线带宽有限,心跳周期别太短。一般 100ms ~ 1000ms 比较合理。

消费者配置 0x1016 时,每个条目包含节点 ID 和超时时间。格式是:高 16 位为节点 ID,低 16 位为超时时间。例如 0x000501F4 表示监控节点 5,超时 500ms。

1.6 心跳在 CANopenNode 中的实现

CANopenNode 的心跳实现非常轻量。核心代码在 CO_driver.c 和 CO_NMT_Heartbeat.c 中。我简单说一下流程:

  1. 初始化时,读取 0x1017 的值,启动一个定时器。
  2. 定时器到期后,调用 CO_NMT_heartbeatSend() 发送心跳帧。
  3. 消费者端,每次收到心跳帧,重置超时计数器。
  4. 如果超时计数器溢出,触发 CO_NMT_heartbeatError() 回调。

代码层面,你只需要在对象字典里配好 0x1017 和 0x1016,剩下的 CANopenNode 帮你搞定。但要注意,心跳发送依赖系统定时器,如果定时器精度不够,心跳周期会漂移。

重要:心跳机制不保证数据完整性,只保证节点存活。如果你需要确认数据是否更新,还得靠 PDO 或 SDO。