从站分组
从站分组功能允许将从站分配到不同的组(0-7),每组可配置独立的 PDO 周期分频器,实现不同组以不同频率交换数据。
典型应用场景:
- 伺服驱动(组0):默认组,每周期发送,31.25µs 32KHz
- IO 模块(组1):每4周期发送一次,125µs 8000Hz
- 传感器(组2):每6400周期发送一次,200ms 5Hz
- 无需手动启用组 — 系统在进入 SafeOp 时自动启用有从站的组,禁用空组
- 每组独立收发 — 每个组对应独立的 PDO 数据报文
快速开始
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.dll");
uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{GUID}", "");
/* 1. 分配从站到组(必须在 SAFE_OP 之前) */
dll.SetSlaveGroup(master, 3, 1); /* 从站 3 → 组1 */
dll.SetSlaveGroup(master, 4, 1);
dll.SetSlaveGroup(master, 5, 2);
/* 2. 设置组分频器 */
dll.SetGroupCycleDivider(master, 1, 4); /* 组1: 每4周期 */
dll.SetGroupCycleDivider(master, 2, 10); /* 组2: 每10周期 */
/* 组0 默认分频器=1, 每周期发送 */
/* 3. 切换到 OP — 系统自动启用有从站的组 */
dll.SetStateSequence(master, EC_STATE_OPERATIONAL, 10000);
dll.Start(master);
组语义
| 组号 | 含义 |
|---|---|
| 0 | 默认组 — 未显式分组的从站自动归入此组 |
| 1-7 | 显式分组 — 用户手动分配,组号无需连续 |
- 有效组范围为 0-7(共 8 组),每个组独立收发 PDO 数据
- 组号可以不连续(如 1, 3, 5),系统会跳过空组
- 进入 SafeOp 时自动启用有从站的组,禁用空组
从站组分配
BOOL SetSlaveGroup(uint16_t master_index, uint16_t slave_index, uint8_t group);
uint8_t GetSlaveGroup(uint16_t master_index, uint16_t slave_index);
uint8_t GetActiveGroupCount(uint16_t master_index);
uint16_t GetGroupSlaveCount(uint16_t master_index, uint8_t group);
uint16_t GetGroupExpectedWKC(uint16_t master_index, uint8_t group);
SetSlaveGroup() 必须在 SAFE_OP 之前调用。GetSlaveGroup() 读取从站所属组号。GetActiveGroupCount() 返回当前活跃组数量,GetGroupSlaveCount() 返回组内从站数量,GetGroupExpectedWKC() 返回组的期望 WKC。
组分频器
BOOL SetGroupCycleDivider(uint16_t master_index, uint8_t group, uint8_t divider);
uint8_t GetGroupCycleDivider(uint16_t master_index, uint8_t group);
设置组的 PDO 周期分频器(1-255)。例如 divider=4 表示该组每 4 个主周期执行一次 PDO 交换。
分频器与频率对照(主周期 1ms 时):
1— 1000Hz(每周期)2— 500Hz4— 250Hz10— 100Hz100— 10Hz
示例:
dll.SetGroupCycleDivider(master, 1, 4); /* 组1: 每4周期 */
dll.SetGroupCycleDivider(master, 2, 10); /* 组2: 每10周期 */
自动启用/禁用
进入 SafeOp 状态时,系统自动扫描每个组(0-7)是否有从站:
- 有从站的组 → 自动启用,确保分频器至少为 1
- 空组 → 自动禁用,跳过 PDO 收发
无需手动调用 SetGroupEnabled,组号也无需连续。
BOOL SetGroupEnabled(uint16_t master_index, uint8_t group, BOOL enabled);
BOOL GetGroupEnabled(uint16_t master_index, uint8_t group);
仅在需要手动覆盖系统自动管理时使用。
组诊断
每组独立跟踪 PDO 丢帧统计 + 实际 WKC + 组级累计计数。
uint16_t GetGroupExpectedWKC(uint16_t master_index, uint8_t group);
uint16_t GetGroupActualWKC (uint16_t master_index, uint8_t group);
int GetGroupDiag (uint16_t master_index, uint8_t group,
uint16_t* out_consecutive_miss,
uint32_t* out_total_frames,
uint32_t* out_mismatch_frames);
GetGroupActualWKC — 该组上一次 PDO 交换收到的 WKC。每组一个 LRW 报文, 各组 actual_wkc 独立维护。WKC 不等于 GetGroupExpectedWKC() 时本帧视为该组丢帧。
GetGroupDiag — 三项组级累计计数 (任一输出指针可为 NULL):
out_consecutive_miss— 当前连续丢帧数 (任意一帧 WKC 匹配后清零)out_total_frames— 该组累计 TX 帧数 (重启 /ResetDiagnostics后清零)out_mismatch_frames— WKC 不匹配累计帧数, 与 total 的比值即组级丢帧率
示例:
/* 查询组 1 的诊断 */
uint16_t miss = 0; uint32_t total = 0, mismatch = 0;
if (dll.GetGroupDiag(master, 1, &miss, &total, &mismatch)) {
uint16_t exp = dll.GetGroupExpectedWKC(master, 1);
uint16_t act = dll.GetGroupActualWKC(master, 1);
printf("组1: WKC=%u/%u, 连续丢=%u, 累计=%u/%u (%.3f%%)\n",
act, exp, miss, mismatch, total,
total ? 100.0 * mismatch / total : 0.0);
}
/* 旧 API (兼容): 单从站维度的 PDO 丢帧累计 */
uint32_t consec, t, max_consec;
dll.GetPDOFrameLossStats(master, 1, &t, &consec, &max_consec);
PDOFrameLoss 事件的 group 参数标识哪个组发生了丢帧:
static void on_frame_loss(uint16_t mi, uint8_t group, uint32_t consecutive, uint32_t total)
{
printf("组 %d 丢帧: 连续=%u, 累计=%u\n", group, consecutive, total);
}
dll.RegisterPDOFrameLoss(on_frame_loss);
配置时序
INIT → PRE_OP → SAFE_OP → OP
│ │ │
│ │ └─ 组配置已锁定,PDO 开始按组发送
│ │
│ └─ SetSlaveGroup(master, slave, group)
│ SetGroupCycleDivider(master, group, divider)
│ (进入 SafeOp 时自动启用有从站的组)
│
└─ Initialize / SetNetwork
SetSlaveGroup()在进入 SAFE_OP 时生效- 组分频器建议在
SetNetwork()之后、SetStateSequence()之前设置 - 进入 SAFE_OP 后组配置自动锁定
Hot Connect 组 (ETG.1020)
Hot Connect 是 EtherCAT 标准 (ETG.1020 §8.3) 定义的"可选从站段"机制. 在主站启动时, Hot Connect 组内的从站可以缺席, 主站不会因此停在 INIT, 而会跳过该段并仍然把其他从站推到 OP; 现场后续把可选从站接入时, 主站再触发该组的扫描-配置-启动流程, 自动把它纳入控制循环.
典型场景: 装配线/物流台车工件附带 IO 模块、巡检车断电脱离、移动机械臂末端工具切换。
SetSlaveGroup把从站划入 PDO 周期分频组(实时控制层)- Hot Connect 组是状态机/拓扑层概念, 决定该段是否"必须存在", 与 PDO 分频组正交
#define EC_HOTCONNECT_MAX_GROUPS 32
typedef enum {
EC_HOTCONNECT_UNKNOWN = -1,
EC_HOTCONNECT_ABSENT = 0,
EC_HOTCONNECT_PRESENT = 1
} ec_hotconnect_status_t;
#pragma pack(push, 1)
typedef struct {
uint16_t group_id;
uint16_t alias_address; /* ESC 0x0012, 必须非 0 且组内唯一 */
uint32_t vendor_id; /* 0=不校验 */
uint32_t product_code; /* 0=不校验 */
int32_t is_present;
uint16_t detected_slave_idx;
uint16_t reserved;
} ec_hotconnect_group_t;
#pragma pack(pop)
int HotConnectAddGroup(uint16_t master_index, uint16_t group_id,
uint16_t alias_address, uint32_t vendor_id, uint32_t product_code);
int HotConnectRemoveGroup(uint16_t master_index, uint16_t group_id);
int HotConnectGetGroupStatus(uint16_t master_index, uint16_t group_id);
int HotConnectEnumerate(uint16_t master_index, ec_hotconnect_group_t* out_buf, int max);
void HotConnectClearAll(uint16_t master_index);
int HotConnectGetGroupCount(uint16_t master_index);
HotConnectAddGroup() 必须在 SetStateSequence 之前调用。group_id 同主站下唯一(1..65535)。HotConnectGetGroupStatus() 返回 1=Present、0=Absent、-1=Unknown。
Alias Address (ESC 0x0012) 必须预先烧录到从站 EEPROM 中, 非 0 且组内唯一. Alias=0 的从站不支持 Hot-Connect.
插入身份不匹配的设备会通过 RegisterSlaveIdentityMismatchCallback 触发, 操作员检查/更换设备后再调用 AcknowledgeSlaveReplacement 重置识别状态机.
完整示例
#define DYNAMIC_LOAD
#include "ethercat.h"
#include <stdio.h>
static void on_frame_loss(uint16_t mi, uint8_t group, uint32_t consecutive, uint32_t total) {
printf("组 %d 丢帧: 连续=%u, 累计=%u\n", group, consecutive, total);
}
int main(void) {
dll_t dll;
LOAD_DLL(&dll, "Darra.Core.dll");
uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{GUID}", "");
/* 1. 分配从站到组 */
dll.SetSlaveGroup(master, 3, 1); /* IO 模块 */
dll.SetSlaveGroup(master, 4, 1);
/* 2. 设置组分频器 */
dll.SetGroupCycleDivider(master, 1, 4); /* 组1: 250Hz */
/* 3. 设置周期 */
dll.SetMasterLoopCycleTime(master, 1000000); /* 1ms */
/* 4. 注册回调 */
dll.RegisterPDOFrameLoss(on_frame_loss);
/* 5. 启动 */
dll.SetStateSequence(master, EC_STATE_OPERATIONAL, 10000);
dll.Start(master);
/* 6. 查看组状态 */
printf("活跃组数: %d\n", dll.GetActiveGroupCount(master));
printf("组 0: %d 个从站, 分频=%d\n",
dll.GetGroupSlaveCount(master, 0), dll.GetGroupCycleDivider(master, 0));
printf("组 1: %d 个从站, 分频=%d\n",
dll.GetGroupSlaveCount(master, 1), dll.GetGroupCycleDivider(master, 1));
getchar();
dll.Stop(master);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}