跳到主要内容

冗余 (Redundancy)

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

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

启用方式

冗余在 初始化阶段 通过 SetNetwork(mi, ifPrimary, ifSecondary) 指定双网口启用。运行时只需 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*, 调用方 FreeMemory 释放 */
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=标准 ETG 冗余 (默认), 1=仅主口, 2=动态双向冗余 (双发+合并) */
int GetRedProcessdata(void);
  • EnableRedundancy() 必须在 Start() 之前调用,且构造时已通过 SetNetwork 同时指定主/副网卡(ifSecondary 不为空字符串)。
  • ForceRedundancyFailover() 为 C 公开入口。返回 TRUE = native 执行了切换;返回 FALSE = 未切换。常见原因是双向 merge 不手动切(两侧帧同时在跑,没有可切的主/备路)。不得把 FALSE 当成成功。
  • CheckRedundancyHealth() 任一路异常返回 FALSE。
  • GetRedundancyStatus() 返回内部分配的 ec_redundancy_status_t*(快照),调用方须用 FreeMemory() 释放(恰好一次);长期监控应周期性重新获取,不要长期保存旧指针。
  • GetPrimaryWKC() / GetSecondaryWKC() 冗余模式下独立跟踪;单网卡模式 GetSecondaryWKC 返回 0。
  • SetRedProcessdata(mode) 切换过程数据收发模式:0 = 标准 ETG 冗余(默认),1 = 仅主口(primary-only,可用于单路诊断),2 = 动态双向冗余(两端各发完整 LRW,接收端按需合并)。OP 阶段 SDK 在存在真实副口时自动切入 2,一般无需手动调用。

冗余 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 = GetWkcActualMirror(mi);
uint16_t expected = GetExpectedWKC(mi);

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

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

完整示例​

启用冗余 + 健康监控​

#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) {
uint16_t mi = Initialize();
SetNetwork(mi, "\\Device\\NPF_{primary-guid}",
"\\Device\\NPF_{secondary-guid}");

EnableRedundancy(mi, TRUE);
RegisterRedundancyModeChangedCallback(on_mode_change);

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

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

ec_redundancy_status_t* st =
(ec_redundancy_status_t*) GetRedundancyStatus(mi);
printf("健康=%d 环模式=%d WKC(P/S)=%u/%u failover=%u\n",
healthy, mode, wp, ws, st ? st->failover_count : 0u);
if (st) FreeMemory(st); /* GetRedundancyStatus 返回的指针由调用方释放 */
Sleep(1000);
}

Stop(mi);
Dispose(mi);
return 0;
}

故障转移演练 / 过程数据模式切换:

/* 计划性维护: 请求切到副网口。FALSE = 未切换(双向 merge 常不手动切) */
BOOL switched = ForceRedundancyFailover(mi);
if (!switched) {
printf("未切换: 双向 merge 不手动切\n");
} else {
Sleep(500);
}

/* 过程数据模式: 0=标准 ETG 冗余 (默认), 1=仅主口, 2=动态双向冗余 (双发+合并) */
SetRedProcessdata(1); /* 切到仅主口, 用于单路差异化测试 */
SetRedProcessdata(2); /* 切回动态双向冗余 (两端各发完整 LRW, 接收端合并) */
SetRedProcessdata(0); /* 恢复默认 */