事件
通过注册回调函数订阅所有主站级事件。
回调注册函数是全局的,在 Initialize() 之前或之后都可以调用。回调函数在 DLL 内部线程中触发。
从站 PDO 数据变化通知、FSoE 安全事件请参考 从站事件。
所有事件(PDO 周期回调除外)触发时系统均自动记录日志,无论是否订阅。确保关键运行状态不会被静默丢失。
PDO 周期回调(RegisterProcessDataCyclicCallbackSync/Async)为高频回调,不记录日志。
功能概览
| 类别 | 函数名 (dll.XXX) | 说明 | 自动日志 |
|---|---|---|---|
| PDO 周期 | RegisterPDOCyclicSync / Async | PDO 周期同步/异步回调 | — |
| 状态变化 | RegisterStateChangeSync / Async | 从站状态变化 | ✓ |
| 热插拔 | RegisterDiscoverySync / Async | 从站发现 / 移除 | ✓ |
| RegisterSlaveIdentityMismatch | 从站身份不符 | ✓ | |
| 紧急事件 | RegisterEmergency | CoE Emergency 紧急消息 | ✓ |
| PDO 丢帧 | RegisterPDOFrameLoss | 连续丢帧通知(按组独立跟踪) | ✓ |
| DC 同步丢失 | SetDCSyncLostCallback | DC 同步偏差超限 | ✓ |
| 输入数据变化 | RegisterInputChanged | 从站输入 PDO 数据变化 | — |
| 冗余 | RegisterRedundancyChanged | 冗余模式变化 | ✓ |
| RegisterSlavePortLinkChanged | 从站端口 link 变化(P0-P3) | ✓ | |
| PreOP 重配 | RegisterPreOpReconfig | 从站 PreOP 重配置 | ✓ |
| 日志 | SetLogCallback | DLL 内部日志 | — |
| 崩溃 | 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);
InputChanged 在 PDOCyclicSync/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;
}