跳到主要内容

事件

通过注册回调函数订阅所有主站级事件。

回调注册时机

回调注册函数是全局的,在 Initialize() 之前或之后都可以调用。回调函数在 DLL 内部线程中触发。

从站级事件

从站 PDO 数据变化通知、FSoE 安全事件请参考 从站事件

自动日志

所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。

PDO 周期回调(RegisterProcessDataCyclicCallbackSync/Async)为高频回调,不记录日志。

功能概览

类别函数名 (dll.XXX)说明自动日志
PDO 周期RegisterPDOCyclicSync / AsyncPDO 周期同步/异步回调
状态变化RegisterStateChangeSync / Async从站状态变化
热插拔RegisterDiscoverySync / Async从站发现 / 移除
RegisterSlaveIdentityMismatch从站身份不符
紧急事件RegisterEmergencyCoE Emergency 紧急消息
PDO 丢帧RegisterPDOFrameLoss连续丢帧通知(按组独立跟踪)
DC 同步丢失SetDCSyncLostCallbackDC 同步偏差超限
输入数据变化RegisterInputChanged从站输入 PDO 数据变化
冗余RegisterRedundancyChanged冗余模式变化
RegisterSlavePortLinkChanged从站端口 link 变化(P0-P3)
PreOP 重配RegisterPreOpReconfig从站 PreOP 重配置
日志SetLogCallbackDLL 内部日志
崩溃SetCrashCallback崩溃通知

回调类型定义

typedef void (*log_callback_t)(int category, const char* message);
typedef void (*crash_callback_t)(int exception_code, const char* message);
typedef void (*pdo_cyclic_callback_t)(uint16_t master_index);
typedef void (*state_change_callback_t)(uint16_t master_index, uint16_t slave_index,
int old_state, int new_state);
typedef void (*emergency_callback_t)(uint16_t master_index, uint16_t slave_index,
uint16_t error_code, uint16_t error_reg, uint8_t b1, uint16_t w1, uint16_t w2);
typedef void (*discovery_callback_t)(uint16_t master_index, uint16_t slave_index,
BOOL is_found);
typedef void (*frame_loss_callback_t)(uint16_t master_index, uint8_t group,
uint32_t consecutive_lost, uint32_t total_lost);
typedef void (*redundancy_changed_callback_t)(uint16_t master_index,
int old_mode, int new_mode);
typedef void (*preop_reconfig_callback_t)(uint16_t master_index, uint16_t slave_index);
typedef void (*input_changed_callback_t)(uint16_t master_index,
const uint8_t* changed_slave_bits, uint16_t changed_count);
typedef void (*dc_sync_lost_callback_t)(uint16_t master_index, uint16_t slave_index,
int diff_ns);
typedef void (*identity_mismatch_callback_t)(uint16_t master_index, uint16_t slave_index,
uint32_t expected_vendor, uint32_t expected_product, uint32_t expected_revision,
uint32_t actual_vendor, uint32_t actual_product, uint32_t actual_revision);
typedef void (*port_link_changed_callback_t)(uint16_t master_index, uint16_t slave_index,
uint8_t port, BOOL is_up);

PDO 周期回调

RegisterPDOCyclicAsync

每个 PDO 周期触发一次(异步模式,推荐)。回调不阻塞实时 PDO 线程,适合大多数场景。

static void on_pdo_cyclic(uint16_t master_index) {
uint16_t status = PDOReadInputU16(master_index, 1, 0);
}
dll.RegisterPDOCyclicAsync(on_pdo_cyclic);

RegisterPDOCyclicSync

每个 PDO 周期触发一次(同步模式)。回调在实时线程中直接执行,延迟最低但回调必须快速返回。

注意

同步回调中不要执行耗时操作(如 SDO 读写、文件 I/O、锁等待),否则会阻塞实时线程导致丢帧。

状态事件

RegisterStateChangeAsync

从站 EtherCAT 状态变化时触发。由 DLL 后台线程监控并回调。始终自动记录日志。

回调参数: master_index, slave_index (1-based), old_state, new_state (EC_STATE_*)。

static void on_state_change(uint16_t mi, uint16_t si, int old_s, int new_s) {
printf("从站 %d: %d -> %d\n", si, old_s, new_s);
uint16_t err = dll.GetSlaveALStatusCode(mi, si);
if (err) printf(" 错误码: 0x%04X\n", err);
}
dll.RegisterStateChangeAsync(on_state_change);

RegisterStateChangeSync

同上,同步模式。在实时线程内执行,回调必须快速返回。

热插拔事件

RegisterDiscoveryAsync

从站离线或上线时触发。热插拔断开时 PDO 线程内置恢复状态机自动恢复从站。始终自动记录日志。

回调参数: master_index, slave_index (1-based), is_found (TRUE=上线, FALSE=离线)。

static void on_discovery(uint16_t mi, uint16_t si, BOOL is_found) {
printf("从站 %d %s\n", si, is_found ? "上线" : "离线");
}
dll.RegisterDiscoveryAsync(on_discovery);

RegisterDiscoverySync

同上,同步模式。

RegisterSlaveIdentityMismatch

从站断电重插后身份不符时触发:识别状态机读取到的 Vendor/Product 与配置不匹配,或 Revision 低于配置(向后兼容:实际 Revision ≥ 配置值视为匹配)。始终自动记录日志。

触发后从站进入 IDENT_REJECTED 状态,不会自动重探测(防止错设备反复刷屏)。操作员检查/更换设备后需调用 AcknowledgeSlaveReplacement 让 SDK 重新探测。

回调参数: master_index, slave_index, 期望/实际 vendor/product/revision 各 3 个 uint32_t

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);
/* UI 弹窗提示后调用 AcknowledgeSlaveReplacement(mi, si) */
}
dll.RegisterSlaveIdentityMismatch(on_identity_mismatch);
去重机制

同一从站进入 IDENT_REJECTED 状态仅触发一次事件。调用 AcknowledgeSlaveReplacement 后重置探测,身份仍不匹配会再次触发。

异常事件

RegisterEmergency

CoE Emergency 紧急消息事件。从站固件检测到错误时发送,数据格式遵循 CANopen Emergency 协议(CiA 301)。始终自动记录日志。

回调参数: master_index, slave_index, error_code (CANopen Emergency Error Code), error_reg (对象 0x1001), b1/w1/w2 (制造商特定数据)。

常见 Emergency Error Code: 0x1000通用 / 0x2000电流 / 0x3000电压 / 0x4000温度 / 0x5000硬件 / 0x6000软件 / 0x8000通信监控。

static void on_emcy(uint16_t mi, uint16_t si,
uint16_t code, uint16_t reg, uint8_t b1, uint16_t w1, uint16_t w2)
{
printf("从站 %d EMCY: 错误码=0x%04X, 寄存器=0x%02X\n", si, code, reg);
}
dll.RegisterEmergency(on_emcy);

RegisterPDOFrameLoss

PDO 连续丢帧事件。当连续丢帧达到阈值时触发,单次丢帧仅计数不触发。每组独立跟踪。始终自动记录日志。

回调参数: master_index, group (0-7), consecutive_lost, total_lost

static void on_frame_loss(uint16_t mi, uint8_t grp, uint32_t cons, uint32_t total) {
printf("组 %d 丢帧: 连续=%u, 累计=%u\n", grp, cons, total);
}
dll.RegisterPDOFrameLoss(on_frame_loss);

SetDCSyncLostCallback

DC 同步丢失事件。当从站从同步→失同步(超出阈值)时触发一次,持续超出不重复触发。始终自动记录日志。

回调参数: master_index, slave_index, diff_ns (当前与参考时钟的时间差)。

dll.SetSyncWindowThreshold(master, 500);  /* 500ns */
static void on_dc_lost(uint16_t mi, uint16_t si, int diff) {
printf("从站 %d DC 同步丢失: 偏差 %dns\n", si, diff);
}
dll.SetDCSyncLostCallback(on_dc_lost);

输入数据变化事件

RegisterInputChanged

从站输入 PDO 数据变化时触发。仅当从站输入数据与上一周期不同时回调。

零开销设计
  • 无变化时: 不触发回调,零开销
  • 有变化时: 仅触发变化从站的回调
  • 检测精度: 逐字节比较,任何一个 bit 变化都能检测到

回调参数: master_index, changed_slave_bits (变化从站位图), changed_count

static void on_input_changed(uint16_t mi,
const uint8_t* changed_slave_bits, uint16_t changed_count)
{
printf("输入数据变化: %d 个从站\n", changed_count);
}
dll.RegisterInputChanged(on_input_changed);
与 PDOCyclic 的关系

InputChangedPDOCyclicSync/Async 之前触发。两者可以同时使用,互不影响。

冗余事件

RegisterRedundancyChanged

冗余运行模式发生变化时触发。old_mode/new_mode 对应冗余模式枚举值(0=Inactive, 1=Dual, 2=Degraded)。始终自动记录日志。

static void on_redundancy(uint16_t mi, int old_mode, int new_mode) {
printf("冗余模式: %d -> %d\n", old_mode, new_mode);
}
dll.RegisterRedundancyChanged(on_redundancy);

RegisterSlavePortLinkChanged

从站 ESC 端口物理链路变化时触发(P0-P3 之一断开或恢复)。每秒诊断周期检测 DL Status 寄存器的 link bit,从 1->0 触发"断开",从 0->1 触发"恢复"。始终自动记录日志。

用于精确定位故障线缆段(相邻从站的对向端口同时报断 = 中间线缆故障)。

回调参数: master_index, slave_index, port (0-3 对应 P0-P3), is_up (TRUE=恢复, FALSE=断开)。

static void on_port_link(uint16_t mi, uint16_t si, uint8_t port, BOOL is_up) {
printf("从站 %d P%d link %s\n", si, port, is_up ? "恢复" : "断开");
}
dll.RegisterSlavePortLinkChanged(on_port_link);
配合断点定位

GetBreakPoints() 提供聚合后的故障点视图(断线 + CRC 故障)。SlavePortLinkChanged 是原始事件源,适合做实时告警。详见 主站诊断

清除事件订阅

ClearAllEvents

void ClearAllEvents(uint16_t master_index);

清除所有已注册的事件回调,防止内存泄漏。建议在 Dispose() 之前调用。

dll.ClearAllEvents(master);
dll.Stop(master);
dll.Dispose(master);
何时使用
  • Dispose() 之前调用,确保回调不会访问已释放的资源
  • 重新初始化主站前,清除旧回调

线程安全

事件回调在 DLL 内部线程上触发,非 UI 线程。访问共享数据时需要使用互斥锁或原子操作。

注意

同步回调 (*Sync) 在实时 PDO 线程中直接执行,不要执行耗时操作(如 SDO 读写、文件 I/O、锁等待),否则会阻塞实时线程导致丢帧。 异步回调 (*Async) 在独立线程中执行,延迟略高但更安全,推荐大多数场景使用。

/* 线程安全示例 — 使用互斥锁保护共享数据 */
#include <pthread.h>

static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER;
static int shared_counter = 0;

static void on_pdo_cyclic(uint16_t mi) {
pthread_mutex_lock(&data_lock);
shared_counter++;
pthread_mutex_unlock(&data_lock);
}

完整示例

#define DYNAMIC_LOAD
#include "ethercat.h"
#include <stdio.h>

static void on_log(int cat, const char* msg) {
const char* tags[] = {"错误","警告","消息","邮箱","PDO","调试"};
printf("[%s] %s\n", (cat >= 0 && cat <= 5) ? tags[cat] : "?", msg);
}
static void on_pdo_cyclic(uint16_t mi) { /* IO 读写 */ }
static void on_state_change(uint16_t mi, uint16_t si, int o, int n) {
printf("从站 %d: %d -> %d\n", si, o, n);
}
static void on_discovery(uint16_t mi, uint16_t si, BOOL found) {
printf("从站 %d %s\n", si, found ? "上线" : "离线");
}
static void on_emcy(uint16_t mi, uint16_t si,
uint16_t c, uint16_t r, uint8_t b, uint16_t w1, uint16_t w2) {
printf("从站 %d EMCY 0x%04X\n", si, c);
}
static void on_frame_loss(uint16_t mi, uint8_t g, uint32_t c, uint32_t t) {
printf("组 %d 丢帧: 连续=%u\n", g, c);
}
static void on_dc_lost(uint16_t mi, uint16_t si, int d) {
printf("从站 %d DC 失步 %dns\n", si, d);
}

int main(void) {
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.dll");

dll.SetLogCallback(on_log);
dll.RegisterPDOCyclicAsync(on_pdo_cyclic);
dll.RegisterStateChangeAsync(on_state_change);
dll.RegisterDiscoveryAsync(on_discovery);
dll.RegisterEmergency(on_emcy);
dll.RegisterPDOFrameLoss(on_frame_loss);
dll.SetDCSyncLostCallback(on_dc_lost);

uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{GUID}", "");
dll.SetStateSequence(master, EC_STATE_OPERATIONAL, 10000);
dll.Start(master);

getchar();

dll.ClearAllEvents(master);
dll.Stop(master);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}