从站分组
从站分组功能允许将从站分配到不同的组(0-7),每组可配置独立的 PDO 周期分频器,实现不同组以不同频率交换数据。
典型应用场景:
- 伺服驱动(组0):默认组,每周期发送,31.25us 32KHz
- IO 模块(组1):每4周期发送一次,125us 8000Hz
- 传感器(组2):每6400周期发送一次,200ms 5Hz
核心简化
- 无需手动启用组 -- 系统在进入 SafeOp 时自动启用有从站的组,禁用空组
- 属性式分频器 -- 通过
master.Divider(group, value)直接设置 - 每组独立收发 -- 每个组对应独立的 PDO 数据报文
快速开始
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
// 1. 分配从站到组(必须在 SAFE_OP 之前)
// 从站 1~2 不设置,保持 group=0(默认组,每周期发送)
master.GetSlave(3).Group(1); // IO 模块 -> 组1
master.GetSlave(4).Group(1);
master.GetSlave(5).Group(2); // 传感器 -> 组2
// 2. 设置组分频器
master.Divider(1, 4); // 组1: 每4周期发送
master.Divider(2, 10); // 组2: 每10周期发送
// 组0 默认分频器=1,无需额外设置
// 3. 切换到 OP -- 系统自动启用有从站的组
master.SetState(EcState::OP);
master.Start();
// 4. 查看组状态
printf("活跃组数: %d\n", master.ActiveGroupCount());
组语义
| 组号 | 含义 |
|---|---|
| 0 | 默认组 -- 未显式分组的从站自动归入此组 |
| 1-7 | 显式分组 -- 用户手动分配,组号无需连续 |
备注
- 有效组范围为 0-7(共 8 组),每个组独立收发 PDO 数据
- 组号可以不连续(如 1, 3, 5),系统会跳过空组
- 进入 SafeOp 时自动启用有从站的组,禁用空组
从站组分配
通过从站对象的 Group() 方法设置组归属。
slave.Group()
uint8_t Group() const;
void Group(uint8_t grp);
获取或设置从站的组归属(0-7)。必须在 SAFE_OP 之前设置。
示例:
// 分配从站到组
master.GetSlave(1).Group(1); // 显式分到组1
master.GetSlave(2).Group(2); // 显式分到组2
// master.GetSlave(3) 不设置,保持 0(默认组)
// 读取从站所在组
uint8_t group = master.GetSlave(1).Group();
主站级组属性
Divider()
uint8_t Divider(uint8_t group) const;
void Divider(uint8_t group, uint8_t value);
获取或设置指定组的 PDO 周期分频器(1-255)。分频器值 N 表示该组每 N 个周期交换一次 PDO 数据。
1-- 每周期发送(1000Hz,主周期 1ms 时)2-- 每2周期发送(500Hz)4-- 每4周期发送(250Hz)10-- 每10周期发送(100Hz)
示例:
// 设置分频器
master.Divider(1, 4); // 组1: 每4周期
master.Divider(2, 10); // 组2: 每10周期
// 组0 默认分频器=1(每周期),无需设置
// 读取分频器
uint8_t div = master.Divider(2); // 10
ActiveGroupCount()
uint8_t ActiveGroupCount() const;
获取当前有从站分配的活跃组数量。通过扫描所有从站的组归属计算。
示例:
printf("活跃组数: %d\n", master.ActiveGroupCount());
GetGroupSlaveCount()
uint16_t GetGroupSlaveCount(uint8_t group) const;
获取指定组当前包含的从站数量。
参数:
group(uint8_t) — 组号 (0-7)
返回值:
uint16_t— 该组中已分配的从站个数, 空组返回 0
示例:
for (uint8_t g = 0; g < 8; ++g) {
uint16_t cnt = master.GetGroupSlaveCount(g);
if (cnt) printf("组 %u: %u 个从站\n", g, cnt);
}
GetGroupSlave()
Slave& GetGroupSlave(uint8_t group, uint16_t slotIndex);
按组号 + 组内 0-based 序号获取从站引用, 等价于先按组过滤再按顺序取第 slotIndex 个。
参数:
group(uint8_t) — 组号 (0-7)slotIndex(uint16_t) — 组内 0-based 序号, 范围[0, GetGroupSlaveCount(group))
返回值:
Slave&— 该位置的从站引用, 越界时抛出DarraException
示例:
// 默认组 (group 0) 的第一个从站, 通常是伺服 0
auto& servo0 = master.GetGroupSlave(0, 0);
printf("伺服 0 状态: %d\n", static_cast<int>(servo0.State()));
与 GetSlave 的区别
master.GetSlave(slaveIndex)— 按全网 1-based 索引取从站master.GetGroupSlave(group, slotIndex)— 按组 + 组内 0-based 序号取从站, 适合"按组遍历"风格代码
自动启用/禁用
进入 SafeOp 状态时,系统自动扫描每个组(0-7)是否有从站:
- 有从站的组 -> 自动启用,确保分频器至少为 1
- 空组 -> 自动禁用,跳过 PDO 收发
无需手动调用启用/禁用,组号也无需连续。
组诊断
每组独立跟踪 PDO 丢帧统计。
按组查询丢帧
// 查询指定组的丢帧统计
auto stats = master.Diagnostics().PDO().GetFrameLossStats(1); // 组1
printf("组1: 累计丢帧=%u, 连续=%u\n", stats.TotalLost, stats.ConsecutiveLost);
auto stats2 = master.Diagnostics().PDO().GetFrameLossStats(2); // 组2
printf("组2: 累计丢帧=%u\n", stats2.TotalLost);
汇总所有组
// 不传参数,返回所有组的汇总统计
auto total = master.Diagnostics().PDO().GetFrameLossStats();
printf("总丢帧: %u\n", total.TotalLost);
PDOFrameLoss 事件
PDO 连续丢帧事件的 group 参数标识哪个组发生了丢帧:
master.Events().PDOFrameLoss = [](uint16_t masterIndex, uint8_t group,
uint32_t consecutive, uint32_t total) {
printf("组 %d 丢帧: 连续=%u, 累计=%u\n", group, consecutive, total);
if (group == 0) {
// 伺服组丢帧,紧急处理
printf("警告: 伺服组通信异常!\n");
}
};
配置时序
INIT -> PRE_OP -> SAFE_OP -> OP
| | |
| | +-- 组配置已锁定,PDO 开始按组发送
| |
| +-- 设置从站组归属: slave.Group(N)
| 设置组分频器: master.Divider(N, divider)
| (进入 SafeOp 时自动启用有从站的组)
|
+-- 初始化主站
警告
slave.Group()在进入 SAFE_OP 时生效- 组分频器建议在
Build()之后、SetState(OP)之前设置 - 进入 SAFE_OP 后组配置自动锁定
完整示例
#include "ethercat.hpp"
#include <cstdio>
using namespace darra;
int main() {
try {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
printf("发现 %d 个从站\n", master.SlaveCount());
// 1. 分配从站到组
// 从站 1~2 保持 group=0(伺服驱动,默认组)
for (int i = 3; i <= 4; i++)
master.GetSlave(i).Group(1); // IO 模块 -> 组1
master.GetSlave(5).Group(2); // 传感器 -> 组2
// 2. 设置组分频器
master.Divider(1, 4); // 组1: 250Hz
// 组0 默认分频器=1(1000Hz),无需设置
// 3. 周期
master.LoopCycle(1000000); // 1ms
// 4. PDO 丢帧监控(每组独立)
master.Events().PDOFrameLoss = [](uint16_t mi, uint8_t group,
uint32_t consecutive, uint32_t total) {
printf("组 %d 丢帧: 连续=%u\n", group, consecutive);
};
// 5. 启动 -- 自动启用有从站的组
master.SetState(EcState::OP);
master.Start();
// 6. 查看组状态
printf("活跃组: %d\n", master.ActiveGroupCount());
// 7. 周期回调中按组处理
master.Events().ProcessDataCyclicSync = [&](uint16_t mi) {
// 组0的伺服控制
auto& servo = master.GetSlave(1);
void* output = servo.OutputDataPointer();
void* input = servo.InputDataPointer();
};
printf("运行中... 按 Enter 停止\n");
getchar();
} catch (const ethercat::DarraException& e) {
printf("错误: %s\n", e.what());
return -1;
}
return 0;
}
性能建议
| 场景 | 推荐配置 |
|---|---|
| 伺服驱动(高实时性) | 组0, 分频=1 |
| 数字 IO 模块 | 组1, 分频=4~10 |
| 模拟传感器 | 组2, 分频=10~100 |
| 慢速设备(温度传感器等) | 组3+, 分频=100+ |
提示
分频器设置越大,该组占用的总线带宽越低。对于不需要高频更新的设备,适当增大分频器可以减少总线负载,提升实时性能。