属性与状态机
属性
| 类别 | 属性 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 基本信息 | 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");
}
切换到 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,与配置一致)
返回值:
bool—true=已接受并复位 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(从站断电 / 断线,身份仍匹配) → 自动恢复,触发SlaveOnlineSlaveIdentityMismatch(从站身份变了,换错设备 / 换同型号旧版本固件) → 手动调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 列表并把 valid 置 false. 适合在 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;
}