跳到主要内容

从站事件

通过 master.Events() 获取 MasterEvents&,用 On*() / SetPDOCallback*() 注册回调。​

用 On*() 注册, 不是字段赋值

MasterEvents 每个事件是多订阅者 std::vector<std::function>, 必须调 On*() 注册方法, 不能 = 赋值。注册方法映射见 事件 - 多订阅者模型。

事件路由

C++ API 中主站级回调函数包含 masterIndex 和 slaveIndex 参数,由调用方自行过滤到对应从站。

功能概览​

类别注册方法读写说明
从站状态变化OnSlaveStateChanged(cb)写(注册)EtherCAT 状态变化
紧急消息OnEmergency(cb)写(注册)CoE Emergency 消息
从站上线OnSlaveDiscovered(cb)写(注册)从站上线
从站离线OnSlaveLost(cb)写(注册)从站离线
DC 同步丢失OnDCSyncLost(cb)写(注册)DC 同步偏差超限
PDO 周期SetPDOCallbackSync(cb)写(注册)PDO 周期回调 (同步, 供 FSoE 等实时控制)
PDO 丢帧OnPDOFrameLoss(cb)写(注册)PDO 帧丢失
热插拔身份不符OnSlaveIdentityMismatch(cb)写(注册)更换/重插后 Vendor/Product/Revision 不符,签名见 事件 - SlaveIdentityMismatch
从站端口链路变化OnSlavePortLinkChanged(cb)写(注册)P0-P3 端口断开/恢复,签名见 事件 - SlavePortLinkChanged
从站 PreOP 重配置OnSlavePreOpReconfig(cb)写(注册)热插拔重插后需重跑启动配置(回调参数 mi, si),其余见 事件

从站状态变化​

master.Events().OnSlaveStateChanged([](uint16_t mi, uint16_t si,
EcState oldState, EcState newState) {
printf("[从站%d] 状态: %s -> %s\n", si,
EcStateFormat::Format(oldState).c_str(),
EcStateFormat::Format(newState).c_str());
});

紧急消息​

master.Events().OnEmergency([](uint16_t mi, uint16_t si,
uint16_t errorCode, uint16_t errorReg,
uint8_t b1, uint16_t w1, uint16_t w2) {
printf("[从站%d] 紧急消息: 错误码=0x%04X\n", si, errorCode);
});

从站上线/离线​

master.Events().OnSlaveDiscovered([](uint16_t si) {
printf("[从站%d] 上线\n", si);
});

master.Events().OnSlaveLost([](uint16_t si) {
printf("[从站%d] 离线\n", si);
});

输入数据变化​

InputDataChanged 事件已移除

旧 InputDataChanged 事件靠 SDK 层每周期逐字节检测输入 PDO 变化。需要感知输入变化时,金路径是把输入用 darra::SlavePDO::InputsMapping<T> 映射一次钉在过程映像上(见 PDO 输入输出),轮询字段与上一次取值比对;也可以每周期用 CopyInputFrame 取当前 generation 整帧快照再比对(快照不是 0 拷贝)。

轮询过程映像检测变化​

也可以每周期取 CopyInputFrame generation 整帧快照,与上一次取值比对:

#pragma pack(push, 1)
struct ServoInput {
uint16_t statusWord;
int32_t actualPosition;
int32_t actualVelocity;
};
#pragma pack(pop)

ServoInput prev{};
std::vector<uint8_t> inFrame;

while (running) {
if (darra::SlavePDO::CopyInputFrame(dll, master.MasterNumber(), 1, inFrame) &&
inFrame.size() >= sizeof(ServoInput)) {
ServoInput input{};
std::memcpy(&input, inFrame.data(), sizeof(input));
if (std::memcmp(&input, &prev, sizeof(ServoInput)) != 0) {
prev = input;
printf("位置变化: %d\n", input.actualPosition);
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}

DC 同步丢失​

master.Events().OnDCSyncLost([](uint16_t mi, uint16_t si, int diffNs) {
printf("[从站%d] DC 同步丢失: 偏差 %dns\n", si, diffNs);
});

PDO 周期回调​

master.Events().SetPDOCallbackSync([&](uint16_t mi) {
std::vector<uint8_t> inFrame;
darra::SlavePDO::CopyInputFrame(dll, master.MasterNumber(), 1, inFrame);
});

PDO 丢帧​

master.Events().OnPDOFrameLoss([](uint16_t mi, uint8_t group,
uint32_t consecutive, uint32_t total) {
printf("组%d 丢帧: 连续=%u, 总计=%u\n", group, consecutive, total);
});

线程安全​

事件回调在 PDO 线程上触发,非主线程。更新共享数据时需要同步:

#include <atomic>
#include <queue>
#include <mutex>

// 方案 1: 原子变量(适合简单数据)
std::atomic<int32_t> shared_position{0};

master.Events().SetPDOCallbackSync([&](uint16_t mi) {
auto& slave = master.GetSlave(1);
uint8_t buf[64];
int bytes = slave.ReadInputDirect(buf, sizeof(buf));
if (bytes >= 6) {
int32_t pos;
std::memcpy(&pos, buf + 2, 4);
shared_position.store(pos, std::memory_order_release);
}
});

// 方案 2: 队列(适合复杂数据)— 同步 PDO 回调中入队
std::queue<ServoInput> dataQueue;
std::mutex queueMutex;

master.Events().SetPDOCallbackSync([&](uint16_t mi) {
std::vector<uint8_t> inFrame;
if (darra::SlavePDO::CopyInputFrame(dll, master.MasterNumber(), 1, inFrame) &&
inFrame.size() >= sizeof(ServoInput)) {
ServoInput input{};
std::memcpy(&input, inFrame.data(), sizeof(input));
std::lock_guard<std::mutex> lock(queueMutex);
dataQueue.push(input);
}
});
注意

回调中避免执行耗时操作。如需处理大量数据,将数据放入队列异步处理。

完整示例​

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

using namespace darra;

#pragma pack(push, 1)
struct ServoInput {
uint16_t statusWord;
int32_t actualPosition;
int32_t actualVelocity;
};
#pragma pack(pop)

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

auto& ev = master.Events();

// ===== 从站状态事件 (用 On*() 注册) =====
ev.OnSlaveStateChanged([](uint16_t mi, uint16_t si,
EcState o, EcState n) {
printf("[从站%d] 状态: %s -> %s\n", si,
EcStateFormat::Format(o).c_str(),
EcStateFormat::Format(n).c_str());
});

ev.OnEmergency([](uint16_t mi, uint16_t si,
uint16_t ec, uint16_t er,
uint8_t b1, uint16_t w1, uint16_t w2) {
printf("[从站%d] 紧急消息: 0x%04X\n", si, ec);
});

ev.OnSlaveDiscovered([](uint16_t si) {
printf("[从站%d] 上线\n", si);
});

ev.OnSlaveLost([](uint16_t si) {
printf("[从站%d] 离线\n", si);
});

ev.OnDCSyncLost([](uint16_t mi, uint16_t si, int diff) {
printf("[从站%d] DC 同步丢失: %dns\n", si, diff);
});

// ===== PDO 周期回调 (供 FSoE 等实时控制; 读输入取 CopyInputFrame 快照) =====
ev.SetPDOCallbackSync([&](uint16_t mi) {
std::vector<uint8_t> inFrame;
if (darra::SlavePDO::CopyInputFrame(dll, master.MasterNumber(), 1, inFrame) &&
inFrame.size() >= sizeof(ServoInput)) {
ServoInput input{};
std::memcpy(&input, inFrame.data(), sizeof(input));
printf("[从站1] 位置: %d\n", input.actualPosition);
}
});

// ===== PDO 丢帧 =====
ev.OnPDOFrameLoss([](uint16_t mi, uint8_t group,
uint32_t consecutive, uint32_t total) {
printf("组%d 丢帧: 连续=%u, 总计=%u\n", group, consecutive, total);
});

master.SetState(EcState::OP);
master.Start();

getchar();
return 0;
}