跳到主要内容

主站诊断

通过 master.GetDiagnostics() 获取 Diagnostics& 引用,访问所有诊断、监控、统计功能。

配合事件使用

建议通过 事件 驱动异常处理,而非自行轮询。直接读取诊断属性适用于 UI 显示等场景。

从站诊断

单个从站的状态诊断、链路质量请参考 从站诊断

获取诊断对象

auto& diag = master.GetDiagnostics();

Diagnostics 对象通过延迟初始化创建,首次调用 GetDiagnostics() 时自动分配。

功能概览

功能说明
通信与性能统计帧计数、丢包、抖动、PDO 丢帧、网口状态、拓扑
DC 同步同步窗口阈值、DCSyncLost 事件
冗余状态冗余激活、故障点检测(断线 + CRC 故障定位)
诊断控制启停数据采集、重置统计

通信与性能统计

所有属性通过 C++ 属性风格方法访问(无参方法返回值)。

类别方法类型需启用说明
帧计数RTcnt()int每秒帧数(Hz),5秒平均
ErrorCnt()uint32_t每秒错误数,5秒平均
PacketLossRate()float最近5秒丢包率(TX vs RX, 5秒滑窗, 0.0~1.0)
LateFrameRate()float过慢帧率(idx 出 8 帧窗 stale, 不计丢, 0.0~1.0)
周期与抖动CycleTimeSpan()int实际周期时间(微秒),实时值
AvgJitterUs()double最近5秒平均抖动(微秒),总线抖动
MaxJitterUs()double最近5秒最大抖动(微秒),总线抖动
PDO 丢帧PDOTotalLost()uint32_t不可停累计丢帧数(所有组合计)
PDOConsecutiveLost()uint32_t不可停当前连续丢帧数(所有组中最大值)
GetPDOFrameLossStats(group)PDOFrameLossStats不可停获取指定组的 PDO 丢帧统计。0-7 返回对应组
从站异常WorstSlaveIndex()uint16_t不可停异常率最高的从站索引
WorstLinkQuality()int16_t不可停最差从站的通信健康度(%),越低越差
WKCPrimaryWKC()uint16_t不可停主网口工作计数器(冗余模式下独立跟踪)
SecondaryWKC()uint16_t不可停副网口工作计数器(冗余模式下独立跟踪)
网口状态PrimaryPortOk()bool不可停主端口是否正常(有流量且5秒内无错误)
SecondaryPortOk()bool不可停副端口是否正常(有流量且5秒内无错误,无冗余时始终 false)
PrimaryPortErrors()uint32_t不可停主端口最近5秒错误数
SecondaryPortErrors()uint32_t不可停副端口最近5秒错误数
拓扑TopologyDescription()std::string不可停拓扑模式描述("线性" / "环形" / "环+分支")
TimingMode()std::string不可停定时模式("硬件定时器" / "RT就绪" / "降级" / "RT错误")。WDK 驱动必须就绪,不再存在软件定时器路径
DC 同步SyncWindowThreshold() / SyncWindowThreshold(val)int不可停同步窗口阈值(纳秒),默认 1000ns。超出阈值触发 DCSyncLost 事件
冗余状态RedundancyActive()bool不可停冗余是否激活(存在断线但网络仍正常运行)
RingMode()int不可停环拓扑冗余运行模式(0=Inactive, 1=Dual, 2=Degraded)
GetBreakPoints(max)std::vector<BreakPoint>不可停当前故障点列表。支持断线和 CRC 故障两种类型,恢复后自动清除
诊断控制Enabled() / Enabled(val)bool诊断数据采集开关(默认关闭)。启用后周期性采样,记录标记为"是"的统计数据
计算公式
指标公式说明
RTcnt采样周期帧数 / 窗口秒数滑动窗口平均帧频
ErrorCnt采样周期错误数 / 窗口秒数滑动窗口平均错误率
PacketLossRate(TX-RX-pipeline) / TX5 秒滑窗, pipeline 在途不算丢
LateFrameRateLateDrop / TXidx 出 8 帧窗 stale, 不计入丢包

Enabled(true)

PDOFrameLossStats 结构
struct PDOFrameLossStats {
uint32_t total_lost; // 累计丢帧数
uint32_t consecutive_lost; // 当前连续丢帧数
uint32_t max_consecutive_lost; // 历史最大连续丢帧数
};

示例:

auto& diag = master.GetDiagnostics();
diag.Enabled(true); // 启用诊断数据采集

// 帧计数
printf("帧频: %d Hz\n", diag.RTcnt());
printf("丢包率: %.2f%%\n", diag.PacketLossRate() * 100.0);
printf("错误数: %u\n", diag.ErrorCnt());

// 周期与抖动
printf("周期时间: %d us\n", diag.CycleTimeSpan());
printf("抖动: 平均 %.2f us, 最大 %.2f us\n", diag.AvgJitterUs(), diag.MaxJitterUs());

// PDO 丢帧(所有组汇总)
printf("PDO 丢帧: 累计=%u, 连续=%u\n", diag.PDOTotalLost(), diag.PDOConsecutiveLost());
if (diag.PDOConsecutiveLost() > 10)
printf("警告: PDO 连续丢帧!\n");

// 按组查询丢帧(0-7)
auto group0Stats = diag.GetPDOFrameLossStats(0);
auto group1Stats = diag.GetPDOFrameLossStats(1);
printf("组0丢帧: %u, 组1丢帧: %u\n", group0Stats.total_lost, group1Stats.total_lost);

// 从站异常
printf("最差从站: #%u (%d%%)\n", diag.WorstSlaveIndex(), diag.WorstLinkQuality());

// 网口状态
printf("主端口: %s\n", diag.PrimaryPortOk() ? "正常" : "异常");
printf("副端口: %s\n", diag.SecondaryPortOk() ? "正常" : "未连接");

// 拓扑信息
printf("拓扑: %s\n", diag.TopologyDescription().c_str());
printf("定时: %s\n", diag.TimingMode().c_str());
从站通信诊断

每个从站的 ESC 端口错误通过 slave.GetDiagnostics().ReadPortErrors() 获取。详见 从站诊断 - 通信诊断

DC 同步

自动监控(ETG.1500 5.13.3),每秒检查各从站时间偏差。超出 SyncWindowThreshold() 阈值时触发 DCSyncLost 事件。

单个从站

单个从站的同步状态请使用 slave.GetDiagnostics().DC().IsInSync()slave.GetDiagnostics().DC().SyncTimeDifference()

冗余状态

RingMode() 反映环拓扑冗余的运行状态。0(Inactive) 表示冗余未初始化,1(Dual) 表示双向冗余正常工作,2(Degraded) 表示 secondary 链路不可用仅 primary 工作。

GetBreakPoints() 统一检测两类物理故障:

类型type 值检测方式典型场景
断线0DL Status 端口物理链路丢失拔线、线缆断裂
CRC 故障1端口级 RxError + InvalidFrame 持续增长接触不良、线缆老化、连接器氧化

故障线缆段定位: 当相邻从站的对向端口(如从站 N 的 P1 和从站 N+1 的 P0)同时报故障,说明连接线缆有问题。仅单侧报故障则定位到该端口连接器。

BreakPoint 结构
struct BreakPoint {
uint16_t slave; // 故障从站索引 (1-based)
uint8_t port; // 故障端口号 (0-3, 对应 P0-P3)
uint8_t type; // 故障类型: 0=断线, 1=CRC 故障
};
从站冗余诊断

单个从站的冗余状态请参考 从站诊断 - 冗余诊断

示例:

auto& diag = master.GetDiagnostics();

// 环拓扑冗余模式
printf("冗余模式: %d\n", diag.RingMode());
if (diag.RingMode() == 2)
printf("警告: secondary链路不可用,仅primary工作\n");

if (diag.RedundancyActive())
printf("冗余已激活\n");

auto bps = diag.GetBreakPoints();
for (auto& bp : bps) {
printf("故障: 从站 %d P%d %s\n", bp.slave, bp.port,
bp.type == 0 ? "断线" : "CRC 劣化");
if (bp.type == 1)
printf("建议检查线缆/连接器\n");
}

诊断快照

GetSnapshot()

DiagnosticsSnapshot GetSnapshot() const;

一次调用获取所有诊断数据的一致快照,避免多次属性访问导致的数据不一致和性能开销。

返回值:

struct DiagnosticsSnapshot {
int Frequency; // 每秒帧数 (Hz)
uint32_t ErrorCount; // 每秒错误数
float PacketLossRate; // 丢包率 (0.0-1.0) — TX vs RX 5s 滑窗
float LateFrameRate; // 过慢帧率 (0.0-1.0) — idx 出 8 帧窗 stale, 不计丢
double AvgJitterUs; // 平均抖动 (微秒)
double MaxJitterUs; // 最大抖动 (微秒)
double CycleTimeUs; // 实际周期时间 (微秒)
uint16_t WkcActual; // 当前 WKC
uint16_t WkcExpected; // 期望 WKC
bool PrimaryPortOk; // 主端口正常
bool SecondaryPortOk; // 副端口正常
bool RedundancyActive; // 冗余激活
};

示例:

auto& diag = master.GetDiagnostics();

// 获取一致快照
auto snap = diag.GetSnapshot();
printf("频率: %d Hz, 丢包率: %.2f%%\n", snap.Frequency, snap.PacketLossRate * 100.0);
printf("抖动: 平均 %.2f us, 最大 %.2f us\n", snap.AvgJitterUs, snap.MaxJitterUs);
printf("WKC: %u / %u\n", snap.WkcActual, snap.WkcExpected);
printf("主端口: %s, 冗余: %s\n",
snap.PrimaryPortOk ? "正常" : "异常",
snap.RedundancyActive ? "激活" : "未激活");
使用场景

当需要同时读取多个诊断指标时(例如 UI 面板刷新),使用 GetSnapshot() 比逐个读取属性更高效且数据一致。

诊断控制

标记为"是"的属性需先设置 Enabled(true) 启动周期性采样,标记为"不可停"的功能始终活跃。

Reset()

void Reset();

一次性重置所有诊断统计,包括:

  • 基础诊断统计(帧错误等)
  • PDO 丢帧统计
  • DC 同步窗口统计
  • 所有从站的端口错误计数器

AL 错误分类

对 AL Status Code 进行分类,帮助快速判断错误性质和处理策略。

ALErrorClassifier::Classify()

static ALErrorCategory ALErrorClassifier::Classify(uint16_t alStatusCode);
static ALErrorCategory ALErrorClassifier::Classify(EcALState alState);

对 AL Status Code(从站返回的错误码)进行分类。支持 uint16_tEcALState 两种参数类型。

参数:

  • alStatusCode (uint16_t) 或 alState (EcALState) — AL Status Code,从 slave.ErrorCode() 或状态转换失败时获取

返回值:

enum class ALErrorCategory {
None, // 无错误
Transient, // 瞬态错误,可重试状态转换,通常自动恢复
Configuration, // 配置错误,检查 PDO 映射、SM 配置、Startup 参数等
Hardware, // 硬件错误,检查从站硬件、线缆、电源
Unknown // 未知错误,查阅 ETG.1000 或从站手册
};

示例:

auto& slave = master.GetSlave(1);
auto ec = slave.ErrorCode();
if (ec != EcALState::NoError) {
auto category = ALErrorClassifier::Classify(ec);
printf("从站 1 错误 0x%04X: ", static_cast<uint16_t>(ec));
switch (category) {
case ALErrorCategory::Transient:
printf("瞬态错误,尝试重新切换状态\n"); break;
case ALErrorCategory::Configuration:
printf("配置错误,请检查 PDO/SM 配置\n"); break;
case ALErrorCategory::Hardware:
printf("硬件错误,请检查从站设备\n"); break;
default:
printf("未知错误\n"); break;
}
}
常见 AL Status Code
  • 0x001E — 无效输入映射 (Configuration)
  • 0x001D — 无效输出映射 (Configuration)
  • 0x0011 — 无效邮箱配置 (Configuration)
  • 0x002D — 同步错误 (Transient)
  • 0x0032 — DC 同步超时 (Transient)
  • 0x0050 — EEPROM 错误 (Hardware)

从站错误计数器

ReadSlaveErrorCounters()

SlaveErrorCounters ReadSlaveErrorCounters(int slaveIndex);

读取指定从站的错误计数器汇总 (CRC 错误、帧错误、丢帧统计)。

参数:

  • slaveIndex (int) — 从站索引 (1-based)

相关结构:

struct SlaveErrorCounters {
int SlaveIndex; // 从站编号
uint32_t Port0CrcErrors; // 端口 0 CRC 错误
uint32_t Port1CrcErrors; // 端口 1 CRC 错误
uint32_t Port2CrcErrors; // 端口 2 CRC 错误
uint32_t Port3CrcErrors; // 端口 3 CRC 错误
uint32_t FrameErrors; // 帧错误计数
uint32_t LostFrames; // 丢帧计数
};

配套方法:

int     ReadAllPortErrors();                            // 批量读取所有从站端口错误
bool ResetSlavePortErrorCounters(uint16_t slave); // 重置指定从站的端口错误计数器
int16_t SlaveLinkQuality(uint16_t slave); // 获取从站链路质量

示例:

auto counters = diag.ReadSlaveErrorCounters(1);
if (counters.Port0CrcErrors + counters.FrameErrors + counters.LostFrames > 0) {
printf("从站 1 错误计数: CRC P0=%u P1=%u P2=%u P3=%u, 帧=%u, 丢=%u\n",
counters.Port0CrcErrors, counters.Port1CrcErrors,
counters.Port2CrcErrors, counters.Port3CrcErrors,
counters.FrameErrors, counters.LostFrames);
}

诊断消息

slave.GetCoE().ReadDiagnosticMessages()

std::vector<DiagnosticMessage> ReadDiagnosticMessages();

通过 CoE 读取从站对象 0x10F3(诊断历史对象,ETG.1020)中的诊断消息。返回从站记录的诊断事件列表,包含时间戳、错误码和描述信息。

访问路径: slave.GetCoE().ReadDiagnosticMessages()

返回值:

  • std::vector<DiagnosticMessage> — 诊断消息列表,无消息时返回空列表

示例:

for (auto& slave : master.GetSlaves()) {
auto messages = slave.GetCoE().ReadDiagnosticMessages();
if (messages.empty()) continue;

for (auto& msg : messages) {
printf("[从站 %d] 代码=0x%08X, Flags=0x%04X\n",
slave.SlaveNum(), msg.diag_code, msg.flags);
}
}
备注

并非所有从站都支持 0x10F3 诊断历史对象。不支持的从站返回空列表。此方法通过 SDO 读取,不建议在实时路径中高频调用。

完整示例

#include "ethercat.hpp"
#include <cstdio>
#include <thread>
#include <chrono>

using namespace darra;

int main() {
try {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("C:/config.deni")
.Build();
master.SetState(EcState::OP);
master.Start();

auto& diag = master.GetDiagnostics();
diag.Enabled(true); // 启用诊断数据采集

// 注册异常回调
master.Events().PDOFrameLoss = [](uint16_t mi, uint8_t grp,
uint32_t c, uint32_t t) {
printf("组 %d 丢帧: 连续=%u, 累计=%u\n", grp, c, t);
};

// 定期打印诊断信息
for (int i = 0; i < 60; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));

// 帧计数
printf("[%02d] 帧频=%d Hz, 丢包率=%.2f%%\n",
i, diag.RTcnt(), diag.PacketLossRate() * 100.0);

// 周期与抖动
printf(" 周期=%d us, 抖动: 平均=%.2f us, 最大=%.2f us\n",
diag.CycleTimeSpan(), diag.AvgJitterUs(), diag.MaxJitterUs());

// PDO 丢帧
auto pdo_stats = diag.GetPDOFrameLossStats(0);
if (pdo_stats.total_lost > 0)
printf(" PDO 丢帧: 累计=%u, 连续=%u\n",
pdo_stats.total_lost, pdo_stats.consecutive_lost);

// 从站异常
if (diag.WorstLinkQuality() < 80)
printf(" 最差从站: #%u (%d%%)\n",
diag.WorstSlaveIndex(), diag.WorstLinkQuality());

// 网口状态
printf(" 主端口: %s, 副端口: %s\n",
diag.PrimaryPortOk() ? "正常" : "异常",
diag.SecondaryPortOk() ? "正常" : "未连接");

// 冗余状态
if (diag.RingMode() == 2) {
printf(" 警告: 冗余降级!\n");
auto bps = diag.GetBreakPoints();
for (auto& bp : bps) {
printf(" 故障: 从站 %d P%d %s\n", bp.slave, bp.port,
bp.type == 0 ? "断线" : "CRC故障");
}
}

// AL 错误分类
for (int si = 1; si <= master.SlaveCount(); ++si) {
auto& slave = master.GetSlave(si);
auto ec = slave.ErrorCode();
if (ec != EcALState::NoError) {
auto category = ALErrorClassifier::Classify(ec);
printf(" 从站%d 错误 0x%04X: %s\n", si,
static_cast<uint16_t>(ec),
category == ALErrorCategory::Transient ? "瞬态" :
category == ALErrorCategory::Configuration ? "配置" :
category == ALErrorCategory::Hardware ? "硬件" : "未知");
}
}
}

} catch (const ethercat::DarraException& e) {
printf("错误: %s\n", e.what());
return -1;
}
return 0;
}