CAN多节点通信实战:基于系统RTC及系统时钟Tick的心跳风暴抑制方案

方案定义

本方案采用两级时基:

  • 系统RTC:用于计算统一周期起点 periodStart
  • 系统时钟Tick:用于保证发送窗口精度

本方案支持两种通信模式:

  • 主从通信:由 Master 周期发布系统 RTC
  • 对等通信:由 1号节点 周期发布系统 RTC

系统 RTC 发布节点只负责授时,不直接参与其他节点的心跳调度计算。


全局参数

  • Thb:节点心跳周期
  • N:系统容量
  • NodeId:节点号
  • rtcNow:当前节点本地 RTC 时间
  • localTickNow:当前节点本地系统 Tick

当前参数示例:

  • Thb = 30s
  • N = 200
  • 系统 RTC 发布周期 = 10分钟
  • 本地 Tick 精度 = 10ms

系统RTC来源

主从通信模式

系统 RTC 由 Master 周期发布。

Slave 接收后同步本地 RTC。

对等通信模式

系统 RTC 由 1号节点 周期发布, 其他节点接收后同步本地 RTC。

对等通信模式下,1号节点 发送,所有节点仍按统一规则计算心跳周期与发送窗口。


周期起点计算

当前心跳周期起点由节点本地 RTC 直接计算:

c 复制代码
periodStart = (rtcNow / Thb) * Thb;

其中:

  • rtcNow 为以秒为单位的系统时间戳
  • Thb 为以秒为单位的心跳周期

示例:

c 复制代码
rtcNow = 1719833725;
Thb = 30;
periodStart = (1719833725 / 30) * 30 = 1719833710;

所有节点只要 RTC 已同步到同一系统时间基准,计算得到的 periodStart 就一致。


发送窗口计算

每个节点固定映射到一个发送窗口:

c 复制代码
windowSize = Thb / N;
sendOffset = (NodeId % N) * windowSize;

参数代入:

c 复制代码
windowSize = 30000ms / 200 = 150ms;

即在每个 30 秒周期内,200 个节点均匀分散到 200 个发送窗口中。


本地Tick映射

在轮询中根据当前 RTC 计算当前周期起点:

c 复制代码
periodStartRtc = (rtcNowSec / T_hbSec) * T_hbSec;

若检测到进入新周期,则记录当前本地 Tick:

c 复制代码
periodStartTick = localTickNow;

该值表示当前周期起点在本地 Tick 时间轴上的映射时刻。


心跳发送规则

本节点发送偏移为:

c 复制代码
sendOffset = (NodeId % N) * windowSize;
sendTick = periodStartTick + sendOffset;

发送条件:

c 复制代码
if (!sentFlag && localTickNow >= sendTick) {
    send_heartbeat();
    sentFlag = 1;
}

每个周期只发送一次心跳。

进入新周期后清除 sentFlag


RTC同步规则

系统 RTC 发布节点周期发送 RTC 同步报文。

节点收到后执行本地 RTC 同步。

RTC 同步报文只用于:

  • 更新本地 RTC

RTC 同步报文不用于:

  • 不直接计算 periodStart
  • 不直接修改发送窗口
  • 不直接触发心跳发送

运行流程

1. 节点初始化

初始化上下文参数:

  • 心跳周期 Thb
  • 系统容量 N
  • 节点号 NodeId
  • 本周期发送标志 sentFlag

2. 系统RTC同步

系统 RTC 发布节点周期发送 RTC 同步报文, 其他节点接收后同步本地 RTC。

3. 周期轮询

节点周期调用轮询函数,输入:

  • 当前 RTC
  • 当前本地 Tick

4. 计算周期起点

根据当前 RTC 计算:

c 复制代码
periodStartRtc = (rtcNowSec / T_hbSec) * T_hbSec;

5. 新周期处理

periodStartRtc 发生变化:

  • 更新记录的周期起点
  • 保存新的 periodStartTick
  • 清除 sentFlag

6. 发送判定

根据节点号计算发送偏移与发送时刻,满足条件后发送一次心跳。


参考实现

c 复制代码
#include <stdint.h>

typedef struct {
    uint32_t lastPeriodStartRtc;  // 当前周期起点,单位:秒
    uint32_t periodStartTick;     // 当前周期起点对应的本地Tick,单位:ms
    uint32_t windowSizeMs;        // 窗口宽度,单位:ms
    uint32_t periodSec;           // 心跳周期,单位:秒
    uint16_t nodeId;
    uint16_t nodeCount;
    uint8_t sentFlag;
} HeartbeatCtx;

void heartbeat_init(HeartbeatCtx *ctx,
                    uint16_t nodeId,
                    uint16_t nodeCount,
                    uint32_t periodSec)
{
    ctx->lastPeriodStartRtc = 0;
    ctx->periodStartTick = 0;
    ctx->windowSizeMs = (periodSec * 1000) / nodeCount;
    ctx->periodSec = periodSec;
    ctx->nodeId = nodeId;
    ctx->nodeCount = nodeCount;
    ctx->sentFlag = 0;
}

void heartbeat_poll(HeartbeatCtx *ctx, uint32_t rtcNowSec, uint32_t localTickNow)
{
    uint32_t periodStartRtc = (rtcNowSec / ctx->periodSec) * ctx->periodSec;

    if (periodStartRtc != ctx->lastPeriodStartRtc) {
        ctx->lastPeriodStartRtc = periodStartRtc;
        ctx->periodStartTick = localTickNow;
        ctx->sentFlag = 0;
    }

    uint32_t sendOffset =(uint32_t)(ctx->nodeId % ctx->nodeCount) * ctx->windowSizeMs;
    uint32_t sendTick = ctx->periodStartTick + sendOffset;

    if (!ctx->sentFlag && localTickNow >= sendTick) {
        send_heartbeat();
        ctx->sentFlag = 1;
    }
}

参数示例

按当前项目参数:

  • nodeCount = 200
  • periodSec = 30
  • windowSizeMs = 150
  • RTC 发布周期 = 10分钟
  • local Tick = 10ms

小结

本方案统一以系统 RTC 计算 periodStart,以节点号分配固定发送窗口,以本地 Tick 实现窗口级精确发送。系统 RTC 可由 Master 发布,也可在对等通信模式下由 1 号机发布。RTC 同步仅用于统一各节点本地时间,不直接参与心跳调度逻辑。

相关推荐
龙智DevSecOps解决方案3 天前
TESSY v5.1 新功能详解 :引入 Hyper Coverage 与基于变更的测试,大幅缩短 CI 测试时间
自动化测试·软件测试·ci/cd·单元测试·嵌入式开发·tessy
zmj3203244 天前
KW45芯片的安全启动
单片机·嵌入式开发·安全启动
zmj3203244 天前
芯片的ISP在系统编程-模式
单片机·嵌入式开发
Tronlong创龙4 天前
RK3576 单板机系统使用手册:配置、升级与组件安装指南(一)
开发板·嵌入式开发·硬件开发·工业控制
charlie1145141916 天前
2026年正点原子开发板移植方案——从0开始的Rootfs之路(4)Rootfs 目录结构创建:Linux 文件系统的“骨架“
linux·驱动开发·学习·嵌入式开发·嵌入式linux
charlie1145141918 天前
2026年正点原子开发板移植方案——从0开始的Rootfs之路(3)inittab 与 init 系统:Linux 启动的“第一号进程“全解析
linux·驱动开发·学习·嵌入式开发·嵌入式linux
wotaifuzao17 天前
从128-bit到16-bit:BLE UUID背后的带宽战争与架构设计
性能优化·蓝牙·uuid·低功耗蓝牙·架构设计·嵌入式开发·ble
Triv202518 天前
太阳能船远程信息处理:CAN数据记录 + Grafana仪表板实战案例
grafana·数据可视化·influxdb·嵌入式系统·can总线·数据采集与监控·智能船舶
ShiMetaPi19 天前
从帧触发到事件驱动:RGB+EVS多模态融合下的无人机识别重构
嵌入式硬件·计算机视觉·嵌入式开发·无人机避障·事件相机