跳到主要内容

冗余 (Redundancy)

EtherCAT 冗余通过双网卡 + 环形拓扑实现链路级容错: 主网口走 primary 帧, 副网口走 secondary 帧, 任一段断线时 SDK 自动从两侧 WKC 合并完成的从站状态, 整个网络仍正常运行.

启用冗余通过 SetNetwork 同时绑定主/副网卡完成。

启用方式

冗余在 初始化阶段 通过 SetNetwork(mi, if_primary, if_secondary) 指定双网口启用。运行时只需 EnableRedundancy(mi, TRUE) 打开开关 (Start 之前调用)。

相关页面

RedundancyState

typedef enum {
EC_REDUNDANCY_NONE = 0, /* 未启用 */
EC_REDUNDANCY_PRIMARY = 1, /* 仅主网口在用 */
EC_REDUNDANCY_SECONDARY = 2, /* 仅副网口在用 */
EC_REDUNDANCY_BOTH = 3 /* 主副均在用 (健康) */
} ec_redundancy_state_t;

typedef enum {
EC_RING_INACTIVE = 0, /* 未启用冗余 / 单网卡 */
EC_RING_DUAL = 1, /* 双向健康, 主副网口都收发正常 */
EC_RING_DEGRADED = 2 /* 环上有断点, 主副双发已合并恢复 */
} ec_ring_mode_t;

RedundancyStatus

冗余运行时状态详细信息。

typedef struct {
ec_redundancy_state_t state;
BOOL primary_link_up;
BOOL secondary_link_up;
uint32_t primary_tx_frames;
uint32_t primary_rx_frames;
uint32_t secondary_tx_frames;
uint32_t secondary_rx_frames;
uint32_t failover_count; /* 故障切换次数 */
uint32_t last_failover_time; /* 最近一次切换时间 (ms 时间戳) */
} ec_redundancy_status_t;

冗余控制函数:

BOOL  EnableRedundancy(uint16_t master_index, BOOL enable);
BOOL ForceRedundancyFailover(uint16_t master_index);
BOOL CheckRedundancyHealth(uint16_t master_index);
void* GetRedundancyStatus(uint16_t master_index); /* 返回 ec_redundancy_status_t* */
int GetRingMode(uint16_t master_index);
BOOL GetSecondaryLinkStatus(uint16_t master_index);
uint16_t GetPrimaryWKC(void);
uint16_t GetSecondaryWKC(void);
void SetRedProcessdata(int mode); /* 0=关闭手工镜像, 1=自动镜像 (默认), 2=用户自行控制副路 */
int GetRedProcessdata(void);
  • EnableRedundancy() 必须在 Start() 之前调用,且构造时已通过 SetNetwork 同时指定主/副网卡(if_secondary 不为空字符串)。
  • ForceRedundancyFailover() 强制切到副网口(调试 / 计划性维护)。
  • CheckRedundancyHealth() 任一路异常返回 FALSE
  • GetRedundancyStatus() 返回内部指针,无需释放,跨周期内容会变化,不要长期保存。
  • GetPrimaryWKC() / GetSecondaryWKC() 冗余模式下独立跟踪;单网卡模式 GetSecondaryWKC 返回 0。
  • SetRedProcessdata(mode=2) 后副路 PDO 由用户自行写,不写则发 0,可能让从站走入 fail-safe。

冗余 WKC 读取 (合并 WKC)

合并 WKC 是权威值, 别拿主口单口 WKC 判故障

环形冗余下, 主帧 (primary) 从 P0 入网走 ring + branch, 副帧 (secondary) 从 P1 入网仅走 ring。某段断开时一侧帧只能看到断点之前的从站, 另一侧从反方向补齐 —— DLL 把两侧 WKC 合并, 得到本周期真正完成 PDO 交换的从站数。

铁律: 合并 WKC == 期望 WKC, 即网络完好; 此时主口单口 WKC 可以 < 期望, 不是故障。 切勿用 GetPrimaryWKC() 单口值去比期望判掉站 —— 断点被冗余补偿时这必然误报。

/* 期望 / 合并 (实测) WKC — 判整体健康用这两个 */
uint16_t GetExpectedWKC(uint16_t master_index); /* 期望 WKC (固定真值, 永不下调) */
uint16_t GetWkcActualMirror(uint16_t master_index); /* 合并 WKC (内核镜像, 主+副合并, 零帧) */
uint16_t GetWkcExpectedMirror(uint16_t master_index); /* 期望 WKC 镜像 (零帧, 等价 GetExpectedWKC) */
uint16_t GetWcDeficit(uint16_t master_index); /* 缺额 = 期望 - 合并实测 (>0 才是真有从站掉) */

/* 主/副单口 WKC — 仅诊断断点位置用, 不拿来判整体健康 */
uint16_t GetPrimaryWKC(void); /* 主口单口实测; 断点被补偿时可 < 期望 */
uint16_t GetSecondaryWKC(void); /* 副口单口实测; 单网卡恒为 0 */
类别函数含义
合并 WKC (权威)GetWkcActualMirror(mi)主+副合并后本周期真正完成交换的从站数
期望 WKCGetExpectedWKC(mi) / GetWkcExpectedMirror(mi)拓扑固定真值, 永不下调迁就劣化总线
缺额GetWcDeficit(mi)期望 - 合并实测, >0 才表示真有映射从站此刻没贡献 WKC
主口单口GetPrimaryWKC()仅诊断用; 断点被补偿时可 < 期望, 属正常
副口单口GetSecondaryWKC()仅诊断用; 单网卡模式恒为 0

判健康的正确写法 — 以合并 WKC 为准:

/* ✅ 正确: 合并 (实测) WKC == 期望 WKC, 网络完好 (即使有断点被冗余补偿) */
uint16_t merged = dll.GetWkcActualMirror(mi);
uint16_t expected = dll.GetExpectedWKC(mi);

if (merged == expected) {
/* 网络完整; 若 GetRingMode()==2 (Degraded) 说明断点已被冗余补齐 */
printf("链路完好 (合并 WKC=%u/%u)\n", merged, expected);
} else {
printf("真有从站掉了: 合并 WKC=%u < 期望=%u (缺额=%u)\n",
merged, expected, dll.GetWcDeficit(mi));
}

/* 诊断断点位置时再看主/副单口 (不要拿单口去判整体健康) */
printf("主口单口 WKC=%u, 副口单口 WKC=%u\n",
dll.GetPrimaryWKC(), dll.GetSecondaryWKC());

完整示例

启用冗余 + 健康监控

#define DYNAMIC_LOAD
#include "ethercat.h"
#include <stdio.h>
#include <windows.h>

static void on_mode_change(uint16_t mi, int old_mode, int new_mode) {
const char* names[] = {"Inactive", "Dual", "Degraded"};
printf("冗余模式变化: %s -> %s\n", names[old_mode], names[new_mode]);
}

int main(void) {
dll_t dll;
LOAD_DLL(&dll, "DarraEtherCAT.dll");

uint16_t mi = dll.Initialize();
dll.SetNetwork(mi, "\\Device\\NPF_{primary-guid}",
"\\Device\\NPF_{secondary-guid}");

dll.EnableRedundancy(mi, TRUE);
dll.RegisterRedundancyChanged(on_mode_change);

dll.SetStateSequence(mi, EC_STATE_OPERATIONAL, 10000);
dll.Start(mi);

while (1) {
BOOL healthy = dll.CheckRedundancyHealth(mi);
int mode = dll.GetRingMode(mi);
uint16_t wp = dll.GetPrimaryWKC();
uint16_t ws = dll.GetSecondaryWKC();

ec_redundancy_status_t* st =
(ec_redundancy_status_t*) dll.GetRedundancyStatus(mi);
printf("健康=%d 环模式=%d WKC(P/S)=%u/%u failover=%u\n",
healthy, mode, wp, ws, st ? st->failover_count : 0u);
Sleep(1000);
}

dll.Stop(mi);
dll.Dispose(mi);
UNLOAD_DLL(&dll);
return 0;
}

故障转移演练 / 双路差异化测试:

/* 计划性维护: 强制切到副网口, 拔主网卡线缆做检修 */
dll.ForceRedundancyFailover(mi);
Sleep(500);

/* 双路差异化故障注入: 切到手工模式, 副路由调用方控制 */
dll.SetRedProcessdata(2);
/* ... 此时副路 PDO 通过 WriteSlaveOutput 单独写 ... */
dll.SetRedProcessdata(1); /* 恢复默认镜像 */