跳到主要内容

属性与状态机

提示

属性

类别属性类型读写说明
基本信息MasterNumber()uint16_t只读主站实例编号
SlaveCount()int只读网络中检测到的从站数量
IsInitialized()bool只读主站是否已初始化
IsDisposed()bool只读主站是否已释放
IsRedundant()bool只读是否冗余模式
网络与链路LinkState()EcLinkState只读网络链路状态
错误码ErrorCode()EcALState只读异常时的自动报警错误码
ErrorCnt()uint32_t只读错误计数
DC 与周期DCtime()int64_t只读DC 时间戳(纳秒)
LoopCycle() / LoopCycle(uint32_t)uint32_t读写PDO 周期时间(纳秒)
运行时State()EcState只读主站 EtherCAT 状态
WKC()uint16_t只读工作计数器 (WKC)
ExpectedWKC()uint16_t只读期望工作计数器
PrimaryWKC() / SecondaryWKC()uint16_t只读冗余模式下主 / 备网卡 WKC
DCtime() / GetMasterDCTime()int64_t只读主站 DC 时间戳 (纳秒)
GetReferenceClockSlaveIndex()int只读参考时钟从站索引 (0 = 无)
分组Divider(group) / Divider(group, value)uint8_t读写组循环分频器
ActiveGroupCount()uint8_t只读活跃组数量
FilterThreshold() / FilterThreshold(value)uint32_t读写判断滤波阈值

示例:

using namespace darra;

EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();

printf("主站编号: %d\n", master.MasterNumber());
printf("从站数量: %d\n", master.SlaveCount());
printf("链路状态: %d\n", static_cast<int>(master.LinkState()));
printf("已初始化: %s\n", master.IsInitialized() ? "是" : "否");

网络绑定 (Builder 模式)

SetNetwork()

EtherCATMaster& SetNetwork(const std::string& primary, const std::string& redundant = "");

设置主网口。返回 EtherCATMaster& 引用,支持链式调用。

参数:

  • primary (const std::string&) — 主网卡名称
  • redundant (const std::string&) — 冗余网卡名称(空字符串表示单网卡)

返回值:

  • EtherCATMaster& — 自身引用(链式调用)

示例:

// 链式调用
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();

// 冗余模式
master.SetNetwork("adapter1", "adapter2");
master.Build();

Build()

bool Build();

构建并初始化主站。调用 SetNetwork() 后必须调用此方法完成初始化。

返回值:

  • bool — 是否成功

Validate()

ValidationResult Validate() const;

Build 前配置预检查,不执行实际初始化。返回验证结果,包含是否通过和错误列表。

返回值:

struct ValidationResult {
bool IsValid; // 验证是否通过
std::vector<std::string> Errors; // 验证错误列表(通过时为空)
};

检查项:

  • 是否设置了网口或 ENI 配置
  • ENI 文件是否存在
  • 是否超过主站实例上限

示例:

EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.SetENI("config.deni");

auto result = master.Validate();
if (!result.IsValid) {
for (auto& err : result.Errors) {
printf("验证错误: %s\n", err.c_str());
}
return -1;
}

master.Build(); // 验证通过后再执行 Build

状态机管理

EtherCAT 状态机遵循标准流程:INIT -> PRE_OP -> SAFE_OP -> OP。

C++ 提供 3 种调用形式 (与其他 SDK 对齐):

形式签名用途
同步EtherCATMaster::SetState(target, timeoutMs) -> bool阻塞切换, 内部已含中间状态自动切换 + 超时
异步EtherCATMaster::SetStateAsync(target, timeoutMs) -> std::future<bool>不阻塞调用线程, 由 future.get() 取结果
带错误master_state::SetState(m, target, timeoutMs) -> StateResult同步执行并返回 {bool success; std::string message;}, 失败时附带错误描述

SetState() (同步)

bool SetState(EcState target, int timeoutMs = 10000) const;

设置主站状态(带超时)。内部按 EtherCAT 标准顺序自动经过 INIT → PRE_OP → SAFE_OP → OP 中间状态。

参数:

  • target (EcState) — 目标状态
  • timeoutMs (int) — 超时毫秒数, 默认 10000

返回值:

  • bool — 是否成功

示例:

// 切换到 OP 状态
bool ok = master.SetState(EcState::OP);
if (!ok) {
printf("状态转换失败\n");
}
SafeOp 自动同步配置

切换到 EcState::SafeOp 时, SDK 按 ETG 标准为支持 CoE 的非 DC 从站自动配置同步循环计数阈值与 SyncManager 同步类型 (FreeRun 兜底), 应用层无需 hardcode。

写入失败 (从站不支持 / 邮箱忙) 不阻断状态切换, SDK 仅记录日志并继续。如需自定义同步策略, 在 SetState(SafeOp) 之后通过 master.GetSlave(i).GetCoE().SDOWrite(...) 覆盖即可。

SetStateAsync() (异步)

std::future<bool> SetStateAsync(EcState target, int timeoutMs = 10000) const;

异步版状态切换, 不阻塞调用线程。

参数:

  • target (EcState) — 目标状态
  • timeoutMs (int) — 超时毫秒数, 默认 10000

返回值:

  • std::future<bool> — 异步结果, true 表示成功

示例:

#include <future>

auto future = master.SetStateAsync(EcState::OP);
// ... 此处可以做其他事情 ...
bool ok = future.get();
if (!ok) {
printf("异步状态转换失败\n");
}

master_state::SetState() (带错误信息)

namespace darra { namespace ethercat { namespace master_state {

struct StateResult {
bool success = false;
std::string message;
};

StateResult SetState(EtherCATMaster& m, EcState state, uint32_t timeoutMs = 10000);

} } }

带错误描述的同步切换形式 (与 C# (bool, string) 元组返回值对齐)。失败时 message 字段包含具体原因 (例如"切换到中间状态 SafeOp 失败")。

参数:

  • m (EtherCATMaster&) — 主站实例
  • state (EcState) — 目标状态
  • timeoutMs (uint32_t) — 超时毫秒数, 默认 10000

返回值:

  • StateResult — { success, message }

示例:

using namespace darra::ethercat;

auto r = master_state::SetState(master, EcState::OP);
if (!r.success) {
printf("状态切换失败: %s\n", r.message.c_str());
}

WaitForState() (阻塞等待, 不主动切换)

namespace darra { namespace ethercat { namespace master_state {

bool WaitForState(const EtherCATMaster& m, EcState state,
uint32_t timeoutMs = 10000,
uint32_t pollIntervalMs = 50);

} } }

阻塞等待主站到达指定 EtherCAT 状态, 不主动发起切换。适合"别的线程负责切, 我只等结果"的场景。

参数:

  • m — 主站实例
  • state — 目标状态 (EcState::PreOp / SafeOp / OP 等)
  • timeoutMs — 超时 (毫秒), 默认 10000
  • pollIntervalMs — 轮询间隔 (毫秒), 默认 50

返回值:

  • bool — true 表示在超时前观察到 master.State() == state

示例:

using namespace darra::ethercat;

// 别的线程在切, 当前线程只等到位
auto fut = master.SetStateAsync(EcState::OP);
if (master_state::WaitForState(master, EcState::OP, 5000)) {
master.Start();
} else {
printf("超时: 主站未能进入 OP\n");
}
选择指南
  • 同步等待结果 → SetState()
  • 异步发起 + future.get() → SetStateAsync()
  • 需要错误描述 → master_state::SetState()
  • 别人切, 我只等 → master_state::WaitForState()

启停控制

Start() / Stop()

bool Start() const;   // 启动 PDO 循环线程
bool Stop() const; // 停止 PDO 循环线程

返回值: bool — 是否成功

示例:

master.SetState(EcState::OP);
master.Start(); // 启动 PDO 循环

// ... 运行 ...

master.Stop(); // 停止 PDO 循环
// 或者让析构函数自动调用 Stop() + Dispose()

从站访问

GetSlave(index)

Slave& GetSlave(uint16_t index);

获取从站引用(1-based 索引)。内部缓存,重复调用返回同一对象。

示例:

auto& s = master.GetSlave(1);

// 链式访问协议接口
auto data = master.GetSlave(1).GetCoE().SDORead(0x1018, 0x01);

// 零拷贝 PDO 读写
void* input = master.GetSlave(1).InputDataPointer();
void* output = master.GetSlave(1).OutputDataPointer();

GetSlaves()

std::vector<uint16_t> GetSlaves() const;

获取所有从站索引列表(1-based)。

从站迭代器EtherCATMaster 支持 range-based for 循环遍历从站:

for (auto slave : master) {
printf("从站 %d: VID=0x%08X\n", slave.SlaveNum(), slave.VendorId());
}

从站状态 / 身份:

auto& s = master.GetSlave(1);
EcState state = s.State();
EcALState al = s.ErrorCode();
bool ok = s.SetState(EcState::OP, 5000);

printf("VID=0x%08X PID=0x%08X Rev=0x%08X SN=0x%08X 名称=%s 可选=%s\n",
s.VendorId(), s.ProductId(), s.RevId(), s.SerialNumber(),
s.Name().c_str(), s.IsOptional() ? "是" : "否");

PDO 丢帧阈值

uint32_t PDOFrameLossThreshold() const;
void PDOFrameLossThreshold(uint32_t value);

PDO 连续丢帧阈值 (C++ 属性风格重载)。当连续丢帧达到此阈值时触发 PDOFrameLoss 事件。

PDO 丢帧统计

主站级 GetPDOFrameLossStats(group)PDOFrameLossStats 结构唯一定义在 主站诊断 - 通信与性能统计, 通过 master.GetDiagnostics().GetPDOFrameLossStats(group) 获取。原始事件请订阅 PDOFrameLoss

子对象访问

// 获取诊断对象
auto& diag = master.GetDiagnostics();

// 获取配置对象
auto& config = master.GetConfig();

// 获取事件集合
auto& events = master.Events();

PDO 周期配置

// 获取/设置 PDO 周期时间(纳秒)
uint32_t cycle = master.LoopCycle();
master.LoopCycle(1000000); // 1ms

DC 配置

// 为所有 DC 从站配置 DC 同步
master.ConfigureDC(1000000); // SYNC0 = 1ms
master.ConfigureDC(1000000, 500000); // SYNC0 = 1ms, SYNC1 = 500us

主站状态变化回调

OnStateChanged()

using StateChangedCallback = std::function<void(EcState, EcState)>;
void OnStateChanged(StateChangedCallback callback);

设置主站状态变化回调(旧状态, 新状态)。

示例:

master.OnStateChanged([](EcState oldS, EcState newS) {
printf("主站状态: %s -> %s\n",
EcStateFormat::Format(oldS).c_str(),
EcStateFormat::Format(newS).c_str());
});
提示

也可通过 master.Events().StateChanged 设置相同功能的回调。

热插拔自修复

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

acknowledge_slave_replacement

namespace darra { namespace ethercat { namespace master_other {

bool acknowledge_slave_replacement(EtherCATMaster& m, uint16_t slaveIndex);

} } }

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

调用时机: 订阅 master.Events().OnSlaveIdentityMismatch(...) 接收到身份不符报警后,操作员检查/更换设备完毕,调用本函数让 SDK 重新检测。

行为:

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

参数:

  • m (EtherCATMaster&) — 主站引用
  • slaveIndex (uint16_t) — 从站索引(1-based,与配置一致)

返回值:

  • booltrue=已接受并复位 FSM;false=参数无效,或从站当前不在 IDENT_REJECTED / FAILED 状态(收到 SlaveIdentityMismatch 事件之前调用返回 false
必须先收到事件

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

示例:

using namespace darra::ethercat;

master.Events().OnSlaveIdentityMismatch(
[&](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("从站 %u 身份不符: 期望 V=0x%08X, 实际 V=0x%08X\n", si, eV, aV);

// UI 弹窗: "请检查从站 N 的设备, 换回正确型号后点击确认"
if (ShowReplacementDialog(si)) {
bool ok = master_other::acknowledge_slave_replacement(master, si);
if (!ok) {
printf("确认失败: 从站不在 IDENT_REJECTED 状态\n");
}
}
});
与 SlaveOffline/SlaveOnline 的区别
  • SlaveOffline (从站断电 / 断线,身份仍匹配) → 自动恢复,触发 SlaveOnline
  • SlaveIdentityMismatch (从站身份变了,换错设备 / 换同型号旧版本固件) → 手动调 acknowledge_slave_replacement

资源管理

Dispose()

void Dispose();

手动释放主站资源。析构函数会自动调用,通常无需手动调用。

EmergencyCleanup() (静态方法)

static void EmergencyCleanup(dll_t& dll);

紧急关闭网卡(静态方法)。

示例:

// 紧急清理
EtherCATMaster::EmergencyCleanup(dll);

Close() (自由函数)

namespace darra::ethercat {
void Close(EtherCATMaster& m);
}

Close()initialise.hpp 提供的有序关闭流程: 先 Stop() 终止 PDO 循环, 再 Dispose() 释放主站资源. 析构 EtherCATMaster 已经会自动 Dispose, 但直接析构会跳过 Stop, 在 PDO 仍在跑时强行回收容易触发 NIC 释放冲突. 显式生命周期管理推荐:

EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}").Build();
master.SetState(EcState::OP);
master.Start();

// ... 业务运行 ...

darra::ethercat::Close(master); // Stop -> Dispose, 异常吞掉不抛出

Close 内部用 try/catch(...) 包裹 Stop, 避免应急关闭时再抛二次异常. 已 Dispose 的主站再次 Close 是空操作.

启动配置验证

VerifyStartupConfiguration() (自由函数)

namespace darra::ethercat::master_state {
struct StartupVerifyResult {
bool valid = true;
int slaveCount = 0;
std::vector<std::string> issues;
};

StartupVerifyResult VerifyStartupConfiguration(EtherCATMaster& m);
}

进入 OP 之前的启动检查 — 验证链路状态、逐从站状态、AL 错误、配置有效性 (ec_validate_config). 任意一项失败都会写入 issues 列表并把 validfalse. 适合在 master.Start() 之前做最终把关, 让 SDK 把"链路未连接 / 从站 X 状态异常 / 从站 Y AL 错误 0xZZZZ / 配置验证失败"等问题汇总成一份结构化报告, 不需要应用层一项项 polling.

示例:

using darra::ethercat::master_state::VerifyStartupConfiguration;

auto result = VerifyStartupConfiguration(master);
if (!result.valid) {
printf("启动验证失败 (从站数=%d), 共 %zu 项问题:\n",
result.slaveCount, result.issues.size());
for (auto& issue : result.issues) {
printf(" - %s\n", issue.c_str());
}
return -1;
}

master.SetState(EcState::OP);
master.Start();
何时调用
  • Build() 之后, SetState(OP) 之前 — 把启动失败前置, 避免进了 OP 才发现配置错
  • 切到 SafeOp 之后再次调用 — 验证 PDO 映射没问题, 对 IsOptional 从站缺席的报警需要应用自行过滤

组级辅助 (free functions)

master_other namespace 提供了组维度的快捷查询, 与 从站分组 页面的成员方法功能一致, 但用 free function 风格, 适合不持有 master 引用、只有 master_index 的工具代码.

namespace darra::ethercat::master_other {
bool set_group_cycle_divider(EtherCATMaster& m, uint8_t group, uint8_t divider);
uint8_t get_group_cycle_divider(const EtherCATMaster& m, uint8_t group);
bool set_group_enabled(EtherCATMaster& m, uint8_t group, bool enabled);
bool get_group_enabled(const EtherCATMaster& m, uint8_t group);
uint16_t get_group_expected_wkc(const EtherCATMaster& m, uint8_t group);
uint16_t get_group_slave_count(const EtherCATMaster& m, uint8_t group);
uint8_t active_group_count(const EtherCATMaster& m);
}

get_group_expected_wkc(m, g) 是组内所有从站期望 WKC 之和, 与全局 master.ExpectedWKC() 不同 — 后者是整网总和, 前者按组单独统计, 用于按组诊断 (例如组 1 跑 4ms / 组 2 跑 1ms, 各自的 WKC 监控独立). 若该组没有从站, 返回 0.

示例:

using namespace darra::ethercat::master_other;

for (uint8_t g = 0; g < active_group_count(master); ++g) {
if (get_group_slave_count(master, g) == 0) continue;
printf("组 %u: 期望 WKC=%u, 分频=%u, 启用=%s\n",
g,
get_group_expected_wkc(master, g),
get_group_cycle_divider(master, g),
get_group_enabled(master, g) ? "是" : "否");
}
与成员方法的关系
  • master.ActiveGroupCount()active_group_count(master)
  • master.GetGroupSlaveCount(g)get_group_slave_count(master, g)
  • master.Divider(g)get_group_cycle_divider(master, g) 没有 m.GetGroupExpectedWKC(g) 成员, 这个统计只能走 free function.

完整示例

#include "ethercat.hpp"
#include <cstdio>

using namespace darra;

int main() {
try {
// 创建并初始化主站 (Builder 模式)
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();

printf("发现 %d 个从站\n", master.SlaveCount());

// 配置 DC
master.ConfigureDC(1000000); // SYNC0 = 1ms

// 切换到 OP
master.SetState(EcState::OP);
master.Start();

// 设置 PDO 回调
master.Events().ProcessDataCyclicSync = [&](uint16_t mi) {
auto& servo = master.GetSlave(1);
// 零拷贝 PDO 读写
void* output = servo.OutputDataPointer();
void* input = servo.InputDataPointer();
};

// 读取从站 SDO
auto& s = master.GetSlave(1);
auto vendorId = s.GetCoE().SDOReadU32(0x1018, 0x01);
if (vendorId) {
printf("VendorID: 0x%08X\n", *vendorId);
}

// 运行
printf("运行中... 按 Enter 停止\n");
getchar();

} catch (const ethercat::DarraException& e) {
printf("错误: %s\n", e.what());
return -1;
}
return 0;
}