跳到主要内容

属性与状态机

提示

属性

函数签名约定

所有 C API 函数第一个参数为 master_index。 下表中函数签名省略该参数,完整调用形式如: dll.GetLinkStatus(master)

类别函数返回类型读写说明
网络与链路GetLinkStatus(mi)uint8_t只读网络链路状态 (ec_link_state_t)
网络与链路GetNetworksPointer()void*只读网络适配器列表内部指针
错误码GetSlaveALStatusCode(mi, si)uint16_t只读从站 AL Status Code 错误码
从站分组GetGroupSlaveCount(mi, group)uint16_t只读指定组的从站数量
从站分组GetActiveGroupCount(mi)uint8_t只读活跃组数量(映射后有效)
运行时GetSlaveState(mi, si)uint8_t只读从站当前状态
运行时GetExpectedWKC(mi) / SetExpectedWKC(mi, wkc)uint16_t读写期望工作计数器
主时钟GetMasterDCTime(mi)int64_t只读DC 主时钟时间(纳秒)
主时钟GetReferenceClockSlave(mi)uint16_t只读参考时钟从站索引

EtherCAT 状态枚举:

typedef enum {
EC_STATE_NONE = 0x00, /* 无状态 / 未知 */
EC_STATE_INIT = 0x01, /* Init 状态 */
EC_STATE_PRE_OP = 0x02, /* Pre-Operational */
EC_STATE_BOOT = 0x03, /* Boot 状态 (固件更新) */
EC_STATE_SAFE_OP = 0x04, /* Safe-Operational */
EC_STATE_OPERATIONAL = 0x08, /* Operational */
EC_STATE_ACK = 0x10, /* 错误确认位 */
EC_STATE_ERROR = 0x10 /* 错误状态标志 */
} ec_state_t;

typedef enum {
EC_LINK_DOWN = 0, /* 链路断开 */
EC_LINK_UP = 1, /* 链路正常 (主网口和冗余网口都连接) */
EC_LINK_PRIMARY_ONLY = 3, /* 仅主网口连接 */
EC_LINK_SECONDARY_ONLY = 4 /* 仅冗余网口连接 */
} ec_link_state_t;

状态机管理

SetState()

BOOL SetState(uint16_t master_index, int state);
BOOL SetStateWithTimeout(uint16_t master_index, int state, uint32_t timeout_ms);
BOOL SetSlaveStateWithTimeout(uint16_t master_index, uint16_t slave_index,
int state, uint32_t timeout_ms);

切换主站和所有从站到目标状态(按 ETG 状态机链跨多级一次到位)。SetSlaveStateWithTimeout 仅切换单个从站。SDK 内部对硬件偶发抖动自动执行 3 次重试(每次失败后等待 1.5s),用户不需要自行包装重试逻辑。

进入 SafeOp 自动配置同步策略

切到 EC_STATE_SAFE_OP 时,SDK 按 ETG 标准自动为非 DC 从站配置同步循环计数阈值与 SyncManager 同步类型(FreeRun 兜底),避免部分驱动器缺省值导致 SafeOp 卡死。SetStateSequence / SetStateWithStartup 同样自动执行。从站不支持对应配置时静默跳过;自定义同步策略应在切到 SafeOp 之后覆盖。

SetStateSequence()

BOOL SetStateSequence(uint16_t master_index, int target_state, uint32_t timeout_ms);
BOOL SetStateWithStartup(uint16_t master_index, int target_state, uint32_t timeout_ms);

SetStateSequence 链式自动状态转换 (Init → PreOp → SafeOp → OP),自动执行启动参数。推荐使用此函数SetStateWithStartup 单步状态转换,自动执行 Before/After 启动参数。

示例:

dll_t dll;
LOAD_DLL(&dll, "DarraEtherCAT.dll");

uint16_t master = dll.Initialize();
dll.SetNetwork(master, adapter, "");

if (!dll.SetStateSequence(master, EC_STATE_OPERATIONAL, 10000)) {
printf("状态转换失败\n");
}
C 特有语法糖

状态切换失败需要走清理路径, C 可用 EC_TRY(SetStateSequence(mi, EC_STATE_OPERATIONAL, 5000)) 替代 if-not-return 样板: 失败自动 goto fail, 上面 EC_AUTO_DISPOSE 会接管句柄释放. 详见 错误处理宏.

Stop()

void Stop(uint16_t master_index);
void Start(uint16_t master_index);
void AbortNetwork(void);
void ResetAbortNetwork(void);

Stop() 停止 PDO 循环线程,主站切回 PRE_OP 状态。Start() 启动 PDO 循环(在 SafeOp/OP 后调用)。AbortNetwork() 中断协议层所有阻塞操作(如 SetState),可从任意线程调用。

C 特有语法糖

GCC/Clang 可用 __attribute__((cleanup)) 自动 Dispose, EC_AUTO_DISPOSE uint16_t mi = Initialize(); 离开作用域时自动调用 Dispose(mi), 无需手写 goto cleanup. 详见 cleanup attribute.

GetMasterState() / GetSlaveState()

void*    GetMasterState(uint16_t master_index);
uint8_t GetSlaveState(uint16_t master_index, uint16_t slave_index);
uint8_t GetLinkStatus(uint16_t master_index);
uint16_t GetSlaveALStatusCode(uint16_t master_index, uint16_t slave_index);

/* 实时直读 (强制刷新, 抢 ESC 端口锁) */
uint8_t GetSlaveStateLive(uint16_t master_index, uint16_t slave_index);
uint16_t GetSlaveALStatusCodeLive(uint16_t master_index, uint16_t slave_index);

GetMasterState() 返回一个指向内部状态结构的不透明指针 void*(无需释放),转 uint8_t* 取首字节即聚合状态(EC_STATE_*)。GetSlaveState() 返回单个从站当前状态。GetSlaveALStatusCode() 返回从站 AL Status Code 错误码。

GetSlaveStateLive() / GetSlaveALStatusCodeLive() 强制当周期实时读取从站 AL Status / AL Status Code(不读缓存),用于需要"此刻真值"的诊断场景。它们会抢占 ESC 端口锁,禁止高频调用(普通监视用 GetSlaveState() 缓存读即可)。返回类型同非 Live 版本。

返回值为枚举语义

GetSlaveState() / GetSlaveStateLive() 返回值对应 EC_STATE_* 状态枚举(EC_STATE_INIT / EC_STATE_PRE_OP / EC_STATE_SAFE_OP / EC_STATE_OPERATIONAL / EC_STATE_BOOT)。C 语言以整型承载,应按枚举语义解读,不要当裸数字使用。

推荐: 事件驱动 vs 轮询

首选订阅 RegisterSlaveStateChangeCallbackAsync, 状态变化时由 SDK 主动推送。仅在 UI 刷新 / 简单 demo 场景才用 GetMasterState 自己轮询(建议 ≥ 100ms 间隔, 不要塞进 PDO 回调)。

C 特有语法糖

轮询所有从站状态时, C 可用 EC_FOREACH_SLAVE(mi, i) { ... } 简洁遍历, 宏内部 GetSlaveCount(mi) 仅调用一次缓存进循环上界, 不会每次迭代都打 SDK. 详见 遍历宏.

状态转换的等待与超时

C SDK 暂未实现 WaitForState

C# SDK 提供 WaitForState / WaitForSlaveState / GetTransitionTimeoutMs 这组纯轮询等待方法,但它们是 C# 类库内的托管实现,未导出到底层运行时库,C SDK 头文件也不声明,无法在 C 中直接调用。

C SDK 中状态转换的等待与超时由带超时的状态切换函数自身完成:

  • SetStateWithTimeout(mi, state, timeout_ms) — 切换并在 timeout_ms 内等待主站到达目标态
  • SetSlaveStateWithTimeout(mi, si, state, timeout_ms) — 切换并等待单个从站
  • SetStateSequence(mi, target, timeout_ms) — 链式切换并等待

它们内部已按 ESI / 配置 / ETG.1020 选用兜底超时,无需单独的"只等待不切换"函数。若确实需要纯轮询,可自行用 GetSlaveState() / GetMasterState() 写循环。

过程数据看门狗

int SetAllSlaveWatchdog(uint16_t master_index, uint32_t timeout_ms);
int SetAllSlavePdiWatchdog(uint16_t master_index, uint32_t timeout_ms);

批量设置所有从站的过程数据看门狗 / PDI 看门狗超时(毫秒,0 = 禁用,最大 6553ms)。返回成功设置的从站数量。

备注

单个从站的看门狗配置请使用 SetSlaveWatchdog()

示例:

int count = dll.SetAllSlaveWatchdog(master, 100);
printf("已设置 %d 个从站看门狗超时为 100ms\n", count);

诊断控制方法

void ResetDiagnostics(uint16_t master_index);
BOOL ResetSlavePortErrorCounters(uint16_t master_index, uint16_t slave_index);

ResetDiagnostics() 一次性重置主站诊断统计、所有从站端口错误计数器和 PDO 丢帧统计。ResetSlavePortErrorCounters() 重置指定从站的端口错误计数器。

详细诊断 API 请参考 主站诊断

热插拔自修复

在断电重插/更换从站的场景下,SDK 自动识别身份不符并进入保护状态,避免错误设备被误纳入控制循环。事件流程见 SlaveIdentityMismatch 事件

AcknowledgeSlaveReplacement()

BOOL AcknowledgeSlaveReplacement(uint16_t master_index, uint16_t slave_index);

用户确认从站替换完毕,触发 EtherCAT 识别状态机重新探测该从站。

调用时机: 订阅 RegisterSlaveIdentityMismatchCallback 接收到身份不符报警后,操作员检查/更换设备完毕,调用本方法让 SDK 重新检测。

行为:

  • 若身份已纠正(换回正确设备 / 同型号升级 Revision)→ 自动恢复并触发 从站发现事件
  • 若身份仍不匹配 → 再次触发 SlaveIdentityMismatch,回到 IDENT_REJECTED 状态

参数:

  • master_index (uint16_t) — 主站索引
  • slave_index (uint16_t) — 从站编号(1-based,与配置一致)

返回值:

  • BOOLTRUE=已接受并复位 FSM;FALSE=参数无效,或从站当前不在 IDENT_REJECTED / FAILED 状态
必须先收到事件

SlaveIdentityMismatch 事件触发之前调用本方法无效。从站未进入保护状态时 SDK 会持续正常探测,无需手动确认。

示例:

static void on_identity_mismatch(uint16_t mi, uint16_t si,
uint32_t ev, uint32_t ep, uint32_t er,
uint32_t av, uint32_t ap, uint32_t ar)
{
printf("从站 %d 身份不符 (期望 Vendor=0x%08X, 实际 Vendor=0x%08X)\n",
si, ev, av);

if (show_replacement_dialog(si)) {
BOOL ok = dll.AcknowledgeSlaveReplacement(mi, si);
if (!ok)
printf("确认失败: 从站不在 IDENT_REJECTED 状态\n");
}
}

dll.RegisterSlaveIdentityMismatch(on_identity_mismatch);
与 Discovery (offline/online) 的区别
  • Discovery is_found=FALSE/TRUE — 从站断电/断线(身份仍匹配)→ 自动恢复,触发 is_found=TRUE
  • SlaveIdentityMismatch — 从站身份变了(换错设备 / 换同型号旧版本固件)→ 手动调 AcknowledgeSlaveReplacement

状态机工具函数 (ESM)

ETG.1000.6 §6.4 + ETG.1020 状态合法性 / 默认超时 / AL Status Code 分类 / 主站等级查询。这组函数是纯查询, 不发起任何 EtherCAT 帧, 用于在请求 SetSlaveStateWithTimeout 之前预校验, 或在事件回调里把 AL Status Code 翻译成中文/分类。

/* 转换合法性 (ETG.1000.6 §6.4 状态机表) */
int EsmIsLegalTransition(uint16_t from, uint16_t to);
int EsmGetLegalTransitions(uint16_t from, uint8_t* out_states, int max_count);
uint32_t EsmGetDefaultTimeoutMs(uint16_t from, uint16_t to);

/* AL Status Code 语义 (ETG.1000.6 §6.4.4 Table 58) */
int EsmIsKnownAlStatusCode(uint16_t al_status_code);
int EsmClassifyAlStatusCode(uint16_t al_status_code);

/* 主站等级 (ETG.1500 §5.3) */
uint8_t EsmGetMasterClass(uint16_t master_index); /* 'A'=0x41, 'B'=0x42 */

EsmIsLegalTransition / EsmGetLegalTransitions — 合法转换包括 IP/PS/SO/PI/SP/OS/SI/OI/IB/BI 与同状态自跳; 跳级升级 (Init→SafeOp / PreOp→Op) 非法。返回 1 合法 / 0 非法; EsmGetLegalTransitions 把 from 的所有合法目标状态写入 out_states, 返回写入数量, -1 表示 from 非法或缓冲不足。

EsmGetDefaultTimeoutMs — 返回 ETG.1020 Table 55 推荐的状态转换超时 (毫秒, 0=非法转换)。SetStateWithTimeout 内部已用此值做兜底, 用户层通常无需手动传超时。

EsmClassifyAlStatusCode — 错误分类: 0=NoError, 1=Transient (可立即重试), 2=Configuration (需重配置/SDO 修改), 3=Hardware (需用户介入), 4=Unknown (厂商特定/0x8000 段)。

EsmGetMasterClass — Darra Master 当前定位 Class A Full (0x41), 支持 Cable Redundancy + Hot Connect + SDO Info + FoE + Mailbox。

示例:

/* 在请求状态前预校验 */
int from = dll.GetSlaveState(master, 1);
if (!dll.EsmIsLegalTransition(from, EC_STATE_OPERATIONAL)) {
printf("非法转换 %d -> OP, 应该走 SetStateSequence\n", from);
} else {
uint32_t to = dll.EsmGetDefaultTimeoutMs(from, EC_STATE_OPERATIONAL);
dll.SetSlaveStateWithTimeout(master, 1, EC_STATE_OPERATIONAL, to);
}

/* AL Status Code 翻译 */
uint16_t code = dll.GetSlaveALStatusCode(master, 1);
const char* desc = AL_StatusCode_GetDescription(code);
int cls = dll.EsmClassifyAlStatusCode(code);
printf("AL=0x%04X (%s, class=%d)\n", code, desc, cls);

if (cls == 1) {
/* Transient → 直接重试切到当前请求的目标态 */
}
配合 protocol_codes.h

AL_StatusCode_GetDescription / AL_StatusCode_GetSeverity / AL_StatusCode_GetRecoveryHintprotocol/protocol_codes.h 中, 返回静态中文/英文描述, 与本节 ESM API 配合使用。

系统级生命周期与紧急控制

int  EnsureDriversRunning(void);
void EmergencyCloseNics(void);
void AbortNetwork(void);
void ResetAbortNetwork(void);
void AbortScan(void);
void ResetScanAbort(void);

EnsureDriversRunning — 静态函数, 不带 master 参数。检查所需的 DarraRT 内核驱动是否已运行, 未运行时尝试自动启动。返回 1 = 全部就绪, 0 = 启动失败 (上层应弹错误对话框, 禁止主界面继续)。Initialize() 入口已自动调一次, 用户在带 GUI 的程序里也可以单独显式调用以提前提示。

EmergencyCloseNics — 进程紧急退出钩子: 不论 master 是否 OP, 立即关闭所有已绑 NIC (PCAP / WDK), 释放线程, 不走正常 Dispose 链。仅在 signal(SIGINT) / SetConsoleCtrlHandler 等紧急路径调用。

AbortNetwork / AbortScan — 设置中断标志, 让阻塞的 SetState* / GetNetworkInfo / ScanSlaveInfo 立即返回失败。可从任意线程调用 (UI 取消按钮)。ResetAbortNetwork / ResetScanAbort 清除标志, 准备下一轮调用。

示例:

/* 启动前驱动检查 */
if (!dll.EnsureDriversRunning()) {
fprintf(stderr, "DarraRT 驱动未就绪, 程序无法继续\n");
return -1;
}

/* GUI 取消按钮 */
void on_cancel_clicked(void) {
dll.AbortNetwork(); /* 让阻塞的 SetState 立即返回 */
dll.AbortScan(); /* 让 ScanSlaveInfo 立即返回 */
}

/* SIGINT 紧急退出 */
void on_ctrl_c(int sig) {
dll.EmergencyCloseNics();
_exit(1);
}

启动配置校验

BOOL VerifyAllSlaveIdentities(uint16_t master_index,
ec_slave_identity_t* expected, uint32_t slave_count,
BOOL check_revision, BOOL check_serial,
uint64_t* mismatch_mask);

int ec_validate_config(uint16_t master_index);

VerifyAllSlaveIdentities — 一次性校验整网身份 (Vendor / ProductCode / 可选 Revision / 可选 Serial), 全部匹配返回 TRUE; mismatch_mask 按 bit 位置标记不匹配从站 (slave_index 1..64 对应 bit0..63)。配合 SetSlaveOptional 让某些位置允许换设备。

ec_validate_config — Build 前预检查: 网络配置 / 从站到组映射 / PDO 大小 / 看门狗参数等。返回配置问题条数 (0 = 配置 OK)。

诊断快照与组级 WKC

typedef struct {
int frequency;
uint32_t error_count;
float packet_loss_rate;
float late_frame_rate;
double mailbox_latency_avg_us; /* 邮箱收发延迟 - 平均 (微秒) */
double mailbox_latency_us; /* 邮箱收发延迟 - 最大 (微秒) */
int cycle_time_us;
uint16_t wkc_actual;
uint16_t wkc_expected;
uint32_t bus_cycle_hz;
double bus_max_jitter_us;
double bus_avg_jitter_us;
double bus_roundtrip_us;
double bus_load_percent;
uint32_t smi_count;
double smi_peak_us;
int primary_port_ok;
int secondary_port_ok;
int redundancy_active;
} ec_diagnostics_snapshot_t;

BOOL GetDiagnosticsSnapshot(uint16_t master_index, ec_diagnostics_snapshot_t* snapshot);

GetDiagnosticsSnapshot 一次调用拿到所有诊断指标 (线程安全, 内部加锁拷贝)。HMI / Web 仪表盘场景推荐用此 API, 不要逐个调 GetPacketLossRate / GetMasterDCTime / GetCommunicationStats (多次跨锁、值之间不一致)。详见 主站诊断