DC 配置
什么是 DC?
DC(分布式时钟)是 EtherCAT 的硬件级时间同步机制。它让网络中所有从站共享同一个时间基准,使各从站能在完全相同的时刻执行动作。
- 多轴同步运动 — 所有伺服驱动器在同一时刻更新位置指令,消除轴间抖动,轨迹插补更平滑
- IO 同步采样 — 所有输入输出在同一时刻,主站读取时数据一致,适合高速控制和测量
- 精确 I/O 时间戳 — 输入数据带有硬件时间戳,主站可精确知道采样发生的物理时刻
- 确定性输出 — 输出在固定时刻生效,不受 EtherCAT 帧到达时间的抖动影响,降低系统延迟和不确定性
主站级 DC 配置
ConfigureDC()
void ConfigureDC(uint32_t sync0CycleNs, uint32_t sync1CycleNs = 0);
为所有 DC 从站配置 DC 同步。所有参数单位:纳秒(ns)。默认自动完成:
- 传播延迟测量 — 读取各端口接收时间并计算延迟
- 偏移计算 — 基于传播延迟自动计算每个从站的相位偏移
- DC 应用 — 写入 SYNC0/SYNC1 周期和起始时间到各从站
参数:
sync0CycleNs(uint32_t) — SYNC0 周期(纳秒),0 表示禁用 DCsync1CycleNs(uint32_t) — SYNC1 增量时间(纳秒),0 表示仅 SYNC0
示例:
// SYNC0 = 1ms,仅 SYNC0
master.ConfigureDC(1000000);
// SYNC0 = 1ms, SYNC1 = 500us
master.ConfigureDC(1000000, 500000);
// SYNC0 = 125us(WDK 驱动推荐)
master.ConfigureDC(125000);
125us 高性能配置(WDK 驱动推荐):
master.LoopCycle(125000);
master.ConfigureDC(125000);
1ms 常规配置(通用场景):
master.LoopCycle(1000000);
master.ConfigureDC(1000000);
- SYNC0 与 LoopCycle — 应保持一致或为其整数倍,数据未到达的同步没有意义
- 自动偏移(推荐) — 主站自动测量传播延迟并计算
- 过采样例外 — Oversampling 设备中 SYNC0 < LoopCycle,需设备硬件和 PDO 映射专门支持
SYNC1 是相对于 SYNC0 的增量时间。例如 SYNC0=1,000,000ns, SYNC1=500,000ns 表示 SYNC1 在每个 SYNC0 后 500us 触发。
从站级 DC 配置
从站级 ConfigureDC() 支持 DcSyncMode 参数(通过 SlaveDC 命名空间实现),可对单个从站设置同步模式。
DcSyncMode 枚举:
enum class DcSyncMode : int {
FreeRun = 0, // 自由运行(无同步)
SmSynchron = 1, // SM 同步模式
DcSynchron = 2, // DC 同步(仅 SYNC0)
DcSynchron01 = 3 // DC 同步(SYNC0 + SYNC1)
};
using namespace darra;
auto& s = master.GetSlave(1);
// 通过 SlaveDC 命名空间按同步模式配置
SlaveDC::ConfigureDC(s.Dll(), master.MasterNumber(), 1,
DcSyncMode::DcSynchron, 1000000); // DC 同步 SYNC0 = 1ms
SlaveDC::ConfigureDC(s.Dll(), master.MasterNumber(), 1,
DcSyncMode::SmSynchron); // SM 同步模式
SlaveDC::ConfigureDC(s.Dll(), master.MasterNumber(), 1,
DcSyncMode::FreeRun); // 禁用 DC 同步
master.ConfigureDC()— 一次性配置所有 DC 从站SlaveDC::ConfigureDC()— 配置单个从站
仅在需要对个别从站设置不同参数时使用从站级方法。
从站级快捷方法
auto& s = master.GetSlave(1);
// 设置单个从站的 DC 同步参数
s.ConfigureDC(1000000); // SYNC0 = 1ms
s.ConfigureDC(1000000, 500000); // SYNC0 = 1ms, SYNC1 = 500us
s.ConfigureDC(125000, 0, 100000); // SYNC0 = 125us, 手动偏移 100us
// 禁用 DC
s.DisableDC();
持续传播延迟测量
EnableContinuousMeasurement()
// 通过 SlaveDC 命名空间调用
SlaveDC::EnableContinuousMeasurement(dll, masterIndex, enable, intervalSec);
启用或禁用持续传播延迟测量(ETG.1500 5.13.2)。启用后主站定期重新测量传播延迟,适用于温度漂移等导致延迟变化的场景。
参数:
enable(bool) —true启用,false禁用intervalSec(uint32_t) — 测量间隔(秒),0 表示使用默认间隔
示例:
// 每 60 秒重新测量
SlaveDC::EnableContinuousMeasurement(master.Dll(), master.MasterNumber(), true, 60);
// 禁用
SlaveDC::EnableContinuousMeasurement(master.Dll(), master.MasterNumber(), false);
默认情况下 DC 偏移在 ConfigureDC() 时一次性计算。仅在长时间运行且环境温度变化较大时启用此功能。
传播延迟
从站传播延迟
auto& s = master.GetSlave(1);
int delay = s.PropagationDelay();
printf("从站 1 传播延迟: %d ns\n", delay);
同步窗口监控
主站每秒自动检查各 DC 从站的时间偏差。当从站偏差超出阈值时触发 DCSyncLost 事件。
// 阈值配置
darra::set_sync_window_threshold(master, 500); // 设为 500ns
// 事件:从站从"同步"变为"失同步"时触发一次(不重复触发)
master.Events().DCSyncLost = [](uint16_t mi, uint16_t si, int diff) {
printf("从站 %d DC 同步丢失: 偏差 %d ns\n", si, diff);
};
GetMaxSyncDifference()
// 通过 SlaveDC 命名空间调用
int SlaveDC::GetMaxSyncDifference(dll_t& dll, uint16_t mi);
获取所有 DC 从站中的最大时间偏差(纳秒)。
返回值:
int— 最大时间偏差(纳秒),失败返回 -1
示例:
int maxDiff = SlaveDC::GetMaxSyncDifference(master.Dll(), master.MasterNumber());
printf("最大同步偏差: %d ns\n", maxDiff);
IsAllSlavesInSync()
// 通过 SlaveDC 命名空间调用
bool SlaveDC::IsAllSlavesInSync(dll_t& dll, uint16_t mi, int thresholdNs = 1000);
检查所有 DC 从站是否都在同步窗口内。
返回值:
bool— 所有从站同步返回true
示例:
if (!SlaveDC::IsAllSlavesInSync(master.Dll(), master.MasterNumber()))
printf("存在从站同步偏差过大\n");
ResetAllSyncWindowStats()
// 通过 SlaveDC 命名空间调用
void SlaveDC::ResetAllSyncWindowStats(dll_t& dll, uint16_t mi);
重置所有从站的同步窗口统计(最大/最小时间差、超出同步次数等)。
示例:
SlaveDC::ResetAllSyncWindowStats(master.Dll(), master.MasterNumber());
从站同步窗口状态
auto& s = master.GetSlave(1);
auto status = s.GetSyncWindowStatus();
if (status) {
printf("从站 1 同步状态:\n");
printf(" 当前偏差: %d ns\n", status->DiffNs);
printf(" 最大偏差: %d ns\n", status->MaxDiffNs);
printf(" 最小偏差: %d ns\n", status->MinDiffNs);
printf(" 同步状态: %s\n", status->InSync ? "正常" : "异常");
printf(" 失步次数: %u\n", status->OutOfSyncCount);
}
单个从站的同步窗口详细状态使用 slave.GetSyncWindowStatus(),包含 DiffNs、MaxDiffNs、MinDiffNs、InSync、OutOfSyncCount。
DC 同步丢失回调
master.Events().DCSyncLost = [](uint16_t mi, uint16_t si, int diff) {
printf("从站 %d DC 同步丢失: 偏差 %d ns\n", si, diff);
};
DC 漂移补偿
EnableDriftCompensation()
// 通过 MasterConfig 配置
auto& config = master.GetConfig();
config.DriftCompensation(bool enable, int32_t thresholdNs, int32_t gain);
启用或禁用 DC 漂移补偿。漂移补偿在主站侧持续修正系统时钟与 DC 参考时钟之间的偏差,防止长时间运行后同步精度下降。
参数:
enable(bool) —true启用,false禁用thresholdNs(int32_t) — 触发补偿的偏差阈值(纳秒),偏差低于此值时不做修正,默认 1000nsgain(int32_t) — 补偿增益,控制修正速率(值越大修正越快但可能引入振荡),默认 512
示例:
auto& config = master.GetConfig();
// 使用默认参数启用
config.DriftCompensation(true);
// 自定义参数:500ns 阈值,较低增益(更平滑的修正)
config.DriftCompensation(true, 500, 256);
// 禁用
config.DriftCompensation(false);
也可通过 SlaveDC 命名空间直接调用:
SlaveDC::EnableDriftCompensation(master.Dll(), master.MasterNumber(), true, 1000, 512);
对于长时间运行的系统(>24小时),建议启用漂移补偿。默认参数适合大多数场景,仅在需要更精细控制时调整阈值和增益。
完整示例
125us 高性能(WDK 驱动)
#include "ethercat.hpp"
using namespace darra;
int main() {
try {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
// 1. 配置周期 125us
master.LoopCycle(125000);
// 2. 配置 DC (SYNC0 = 125us)
master.ConfigureDC(125000);
// 3. 漂移补偿
master.GetConfig().DriftCompensation(true, 1000, 512);
// 4. 监听 DC 同步丢失
master.Events().DCSyncLost = [](uint16_t mi, uint16_t si, int diff) {
printf("从站 %d DC 同步丢失: %d ns\n", si, diff);
};
// 5. 切换到 OP
master.SetState(EcState::OP);
master.Start();
printf("运行中... 按 Enter 停止\n");
getchar();
} catch (const ethercat::DarraException& e) {
printf("错误: %s\n", e.what());
return -1;
}
return 0;
}
持续测量与同步监控
// 长时间运行:启用持续传播延迟测量(温度漂移补偿)
SlaveDC::EnableContinuousMeasurement(master.Dll(), master.MasterNumber(), true, 60);
// 同步窗口监控
if (!SlaveDC::IsAllSlavesInSync(master.Dll(), master.MasterNumber())) {
int maxDiff = SlaveDC::GetMaxSyncDifference(master.Dll(), master.MasterNumber());
printf("同步偏差过大: %d ns\n", maxDiff);
}
// 重置统计
SlaveDC::ResetAllSyncWindowStats(master.Dll(), master.MasterNumber());
1ms 常规配置
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}");
master.Build();
// 配置 1ms 周期
master.LoopCycle(1000000);
master.ConfigureDC(1000000);
// 启用漂移补偿
master.GetConfig().DriftCompensation(true, 1000, 512);
// 切换到 OP
master.SetState(EcState::OP);
master.Start();
// 定期检查同步状态
for (int i = 1; i <= master.SlaveCount(); ++i) {
auto status = master.GetSlave(i).GetSyncWindowStatus();
if (status && !status->InSync) {
printf("从站 %d 同步偏差: %d ns\n", i, status->DiffNs);
}
}
CurrentDcSyncMode 通过 SlaveDC::GetCurrentDcSyncMode() 获取从站当前的 DC 同步模式。
主站 DC 时间查询
主站提供两个只读接口查询 DC 时间和参考时钟信息:获取当前主站侧 DC 时间戳(用于日志关联、事件时间轴对齐),以及查询哪个从站被选为参考时钟源。
GetMasterDCTime()
class EtherCATMaster {
public:
uint64_t GetMasterDCTime() const;
};
获取主站当前的 DC 时间戳(纳秒),基准为 2000-01-01 00:00:00 UTC(EtherCAT DC 协议定义)。内部通过主站 DC 补偿后的本地时钟读取,精度与同步窗口阈值相关。
返回值:
uint64_t— 从 2000-01-01 起的纳秒数;主站未初始化或未启用 DC 时返回 0
示例:
using namespace std::chrono;
uint64_t dcNs = master.GetMasterDCTime();
// 转换为 time_point (2000-01-01 + dcNs)
constexpr auto ec_epoch = sys_days{2000y / January / 1};
auto tp = ec_epoch + nanoseconds{dcNs};
auto t = system_clock::to_time_t(system_clock::time_point{tp.time_since_epoch()});
std::cout << "DC 时间: " << std::put_time(std::localtime(&t), "%F %T")
<< " (+" << (dcNs % 1'000'000'000) << " ns)\n";
GetReferenceClockSlaveIndex()
int GetReferenceClockSlaveIndex() const;
获取被选为 DC 参考时钟的从站索引。参考时钟通常是总线上第一个支持 DC 的从站,SDK 在 ConfigureDC() 时自动选择。
返回值:
int— 参考时钟从站索引(1 基);无 DC 从站或未配置时返回 -1
示例:
int refSlave = master.GetReferenceClockSlaveIndex();
if (refSlave > 0) {
auto& s = master.GetSlave(refSlave);
std::cout << "参考时钟: 从站 " << refSlave
<< " (" << s.Name() << ")\n";
}
- 日志时间对齐: 将主站日志时间戳替换为 DC 时间,便于与从站侧硬件时间戳(如 IO 采样时间)精确关联
- 事件时间轴: 报警、诊断、PDO 异常事件统一使用 DC 时间戳,便于多站点数据汇总分析
- 参考时钟监控: 若参考时钟从站掉线,需手动触发 DC 重新配置
ETG.1000 Part 4 §6.8.3 分布式时钟参考时钟机制; ETG.1020 §6.4。