主站诊断
通过 master.GetDiagnostics() 获取 Diagnostics& 引用,再用它构造 MasterDiagnosticsInfo 访问高层统计。
C++ SDK 诊断分两层:
Diagnostics—master.GetDiagnostics()直接返回的底层类。提供Enabled()/ExpectedWKC()/SlaveLinkQuality()/GetBreakPoints()/RingMode()/GetPDOFrameLossStats()/PrimaryWKC()/SecondaryWKC()/BusRoundtripUs()/BusLoadPercent()/BusCycleHz()/PacketLossRate()/LateFrameRate()/BusMaxJitterUs()/Summary()/ZeroWkcCount()等。MasterDiagnosticsInfo— 高层包装类, 由Diagnostics&构造, 提供RTcnt()/ErrorCnt()/CycleTimeSpan()/AvgJitterUs()/MailboxLatencyUs()/MailboxLatencyAvgUs()/WorstSlaveIndex()/WorstLinkQuality()/PrimaryPortOk()/TopologyDescription()/GetSnapshot()/Reset()/SyncWindowThreshold()/ReadSlaveErrorCounters()/RtPreemptCount()等便捷属性与 WDK 硬实时诊断。
本页下文示例中以 diag 命名的对象, 凡使用 RTcnt() / CycleTimeSpan() / GetSnapshot() /
WorstSlaveIndex() / Reset() 等便捷属性时, 都应是 MasterDiagnosticsInfo 实例:
auto& base = master.GetDiagnostics(); // Diagnostics&
MasterDiagnosticsInfo diag(base, master.MasterNumber()); // 高层包装
建议通过 事件 驱动异常处理,而非自行轮询。直接读取诊断属性适用于 UI 显示等场景。
单个从站的状态诊断、链路质量请参考 从站诊断。
获取诊断对象
// 底层 Diagnostics
auto& base = master.GetDiagnostics();
// 高层 MasterDiagnosticsInfo (本页便捷属性都在这一层)
MasterDiagnosticsInfo diag(base, master.MasterNumber());
Diagnostics 对象通过延迟初始化创建,首次调用 GetDiagnostics() 时自动分配。
MasterDiagnosticsInfo 是轻量包装, 持有 Diagnostics&, 可随用随构造。
功能概览
| 功能 | 说明 |
|---|---|
| 通信与性能统计 | 帧计数、丢包、抖动、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 | 只读 | 是 | 总线平均抖动(微秒),RT 内核线程帧发送时序偏差 | |
| 邮箱延迟 | MailboxLatencyUs() | double | 只读 | 是 | 邮箱收发延迟 - 最大(微秒)。邮箱(CoE/SoE/FoE/EoE/AoE/VoE 等非周期、请求-响应)事务从请求发出到响应返回的最大往返耗时(最近1秒结算) |
| MailboxLatencyAvgUs() | double | 只读 | 是 | 邮箱收发延迟 - 平均(微秒)。同上事务的平均往返耗时(最近1秒结算) | |
| PDO 丢帧 | GetPDOFrameLossStats(group) | PDOFrameLossStats | 只读 | 不可停 | 获取指定组的 PDO 丢帧统计。0-7 返回对应组。该方法在底层 Diagnostics 类上 (base.GetPDOFrameLossStats(group)),返回结构含 total_lost / consecutive_lost / max_consecutive_lost |
| 从站异常 | WorstSlaveIndex() | uint16_t | 只读 | 不可停 | 异常率最高的从站索引 |
| WorstLinkQuality() | int16_t | 只读 | 不可停 | 最差从站的通信健康度(%),越低越差 | |
| WKC | PrimaryWKC() | 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) / TX | 5 秒滑窗, pipeline 在途不算丢 |
| LateFrameRate | LateDrop / TX | idx 出 8 帧窗 stale, 不计入丢包 |
需 Enabled(true)。
PDOFrameLossStats 结构
struct PDOFrameLossStats {
uint32_t total_lost; // 累计丢帧数
uint32_t consecutive_lost; // 当前连续丢帧数
uint32_t max_consecutive_lost; // 历史最大连续丢帧数
};
示例:
auto& base = master.GetDiagnostics();
MasterDiagnosticsInfo diag(base, master.MasterNumber());
diag.Enabled(true); // 启用诊断数据采集
// 帧计数
printf("帧频: %u Hz\n", diag.RTcnt());
printf("丢包率: %.2f%%\n", diag.PacketLossRate() * 100.0);
printf("错误数: %u\n", diag.ErrorCnt());
// 周期与抖动
printf("周期时间: %.1f us\n", diag.CycleTimeSpan());
printf("总线抖动: 平均 %.2f us\n", diag.AvgJitterUs());
printf("邮箱延迟: 平均 %.2f us, 最大 %.2f us\n",
diag.MailboxLatencyAvgUs(), diag.MailboxLatencyUs());
// PDO 丢帧 — 在底层 Diagnostics 上按组查询 (无 PDOTotalLost/PDOConsecutiveLost 汇总属性)
auto group0Stats = base.GetPDOFrameLossStats(0);
auto group1Stats = base.GetPDOFrameLossStats(1);
printf("组0丢帧: 累计=%u 连续=%u, 组1丢帧: 累计=%u\n",
group0Stats.total_lost, group0Stats.consecutive_lost, group1Stats.total_lost);
if (group0Stats.consecutive_lost > 10)
printf("警告: 组0 PDO 连续丢帧!\n");
// 从站异常
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_stats 命名空间的自由函数 slave_stats::read_port_errors(dll, mi, si) 获取
(Slave 类没有 GetDiagnostics() 成员)。详见 从站诊断 - 通信诊断。
WKC 镜像(掉站可观测)
WKC 镜像由头文件 slave/diag_mirror.hpp 的 darra::ethercat::diag_mirror 命名空间提供(不在 Diagnostics / MasterDiagnosticsInfo 类上),是内核 per-slave WcState 诊断缓存的主站级聚合镜像,全部 header-only 自由函数、薄读零帧(不缓存、不需刷新——内核每周期、WKC 异常时立即维护,DLL 保证读到即此刻真实总线现实)。配合每个从站的 dm::slave_wc_contributed(mi, si) 可定位到具体掉了哪个从站。
namespace dm = darra::ethercat::diag_mirror;
| 函数 | 类型 | 读写 | 说明 |
|---|---|---|---|
dm::wkc_actual_mirror(mi) | uint16_t | 只读 | 总线实测工作计数器镜像(actual WKC)— 反映此刻哪些从站真的在响应。R1:如实值永不篡改 |
dm::wkc_expected_mirror(mi) | uint16_t | 只读 | 期望工作计数器镜像(expected WKC)— 配置期/进 OP 确定的固定真值。R1:拓扑固定它就固定,永不动态下调迁就劣化总线 |
dm::wc_deficit(mi) | uint16_t | 只读 | WKC 缺额 = expected − actual(>0 表示有映射从站此刻没贡献 WKC,疑似掉站/热插拔)。从站恢复后自动归 0 |
dm::mapped_slave_count(mi) | uint16_t | 只读 | 已映射(参与 WKC 计算)的从站数 |
dm::wc_state_seq(mi) | uint64_t | 只读 | 内核 WcState 缓存序列号(每次刷新自增)。判断 per-slave 镜像是否在两次读取间更新过 |
dm::wkc_complete(mi) | bool | 只读 | 便捷:actual == expected 且 expected > 0。false 不代表 master 故障,而是有从站此刻未贡献(合法热插拔) |
参数
mi= master index(master.MasterNumber())。
R1 可观测性约定(不可违反):
dm::wkc_expected_mirror(mi)= 配置期 / 进 OP 时确定的固定真值,拓扑固定它就固定,概念上不可变,永不动态下调迁就劣化总线。dm::wkc_actual_mirror(mi)= 总线实测值,如实反映此刻哪些从站真的在响应,永不篡改。dm::wc_deficit(mi) = expected − actual。> 0不是 master 故障,而是有映射从站此刻没贡献 WKC(疑似掉站 / 热插拔恢复中);从站恢复后自动归 0,无需任何重置调用。
示例:
#include "slave/diag_mirror.hpp"
namespace dm = darra::ethercat::diag_mirror;
uint16_t mi = master.MasterNumber();
printf("WKC: %u / %u (映射从站 %u 个)\n",
dm::wkc_actual_mirror(mi), dm::wkc_expected_mirror(mi), dm::mapped_slave_count(mi));
if (dm::wc_deficit(mi) > 0) {
printf("⚠️ WKC 缺额 %u — 有从站没在响应,逐个从站定位:\n", dm::wc_deficit(mi));
for (int si = 1; si <= master.SlaveCount(); ++si) {
if (dm::slave_wc_contributed(mi, si) == dm::WcContribution::NotContributed)
printf(" 从站 %d 未贡献 WKC\n", si);
}
}
uint64_t seq = dm::wc_state_seq(mi); // 序列号判断镜像是否更新过(无需轮询帧)
单个从站对 WKC 的贡献状态请用 dm::slave_wc_contributed(mi, si)(dm::WcContribution 枚举)。dm::wc_deficit(mi) > 0 时遍历从站即可定位掉站点。
热插拔重建
dm::HotSwapRebuild()
namespace dm = darra::ethercat::diag_mirror;
int dm::HotSwapRebuild(uint16_t mi); // 别名: dm::hot_swap_rebuild(mi)
与 WKC 镜像同属头文件 slave/diag_mirror.hpp 的 darra::ethercat::diag_mirror 命名空间(自由函数,参数 mi = master.MasterNumber())。
运行中一次性热插拔重建拓扑:任意状态下调用一次,在线重扫总线 + 重建拓扑图 + 重配 PDO + 恢复到运行态(OP)。适用于现场拔掉 / 换上一个模块后,不停总线地把网络重新带回运行态。它不是 Build() 那种"释放全网从头扫"的重建——是面向热插拔的在线增量恢复。
返回值(错误码语义):
| 返回码 | 名称 | 含义与处理 |
|---|---|---|
0 | OK | 成功:从站全部回到 OP |
-20 | BUSY | 另一操作正在进行——稍后重试 |
-21 | RESCAN_0 | 重扫到 0 个从站——已停在 PreOp 安全态,检查物理链路 / 供电 |
-22 | SDO_ABORT | 重配被从站 SDO abort 拒绝——检查 PDO 映射 / Startup 参数 |
-23 | NO_OP | 部分从站未达 OP——已回滚停安全态,查各从站 AL Status Code |
-24 | TIMEOUT | 重建超时 |
-25 | IDX_FULL | 帧索引池饱和 |
-1 | (通用失败) | DLL 未导出该函数等,不抛异常直接返通用失败码 |
行为 / 约束(R1 如实可观测):
- 在 OP 状态调用会短暂中断 PDO(内部降 PreOp 重配再升回 OP),调用方需容忍这一周期的过程数据空窗。
- 失败时返错误码不掩盖——绝不靠篡改内部计数让结果"看起来对"。
- 即使最终 0 个从站在 OP,PDO 循环也照常运行(周期不掉、帧不停),期望 WKC 不动态下调迁就劣化总线。
示例:
#include "slave/diag_mirror.hpp"
namespace dm = darra::ethercat::diag_mirror;
int rc = dm::HotSwapRebuild(master.MasterNumber());
switch (rc) {
case 0:
printf("热插拔重建成功,从站已回到 OP\n");
break;
case -20:
printf("有操作进行中,稍后重试\n");
break;
case -21:
printf("重扫到 0 站,已停 PreOp 安全态——检查链路/供电\n");
break;
case -23:
printf("部分从站未达 OP,已回滚安全态——逐个查 AL Status Code:\n");
for (int si = 1; si <= master.SlaveCount(); ++si) {
auto& s = master.GetSlave(si);
if (s.ErrorCode() != EcALState::NoError)
printf(" 从站 %d: AL=0x%04X\n", si,
static_cast<uint16_t>(s.ErrorCode()));
}
break;
default:
printf("热插拔重建失败: %d\n", rc);
break;
}
DC 同步
自动监控(ETG.1500 5.13.3),每秒检查各从站时间偏差。超出 SyncWindowThreshold() 阈值时触发 DCSyncLost 事件。
单个从站的同步状态请使用 Slave 成员方法 slave.IsInSync() / slave.SyncTimeDifference() /
slave.GetSyncWindowStatus() (Slave 类没有 GetDiagnostics() 成员)。
冗余状态
RingMode() 反映环拓扑冗余的运行状态。0(Inactive) 表示冗余未初始化,1(Dual) 表示双向冗余正常工作,2(Degraded) 表示 secondary 链路不可用仅 primary 工作。
GetBreakPoints() 统一检测两类物理故障:
| 类型 | type 值 | 检测方式 | 典型场景 |
|---|---|---|---|
| 断线 | 0 | DL Status 端口物理链路丢失 | 拔线、线缆断裂 |
| CRC 故障 | 1 | 端口级 RxError + InvalidFrame 持续增长 | 接触不良、线缆老化、连接器氧化 |
故障线缆段定位: 当相邻从站的对向端口(如从站 N 的 P1 和从站 N+1 的 P0)同时报故障,说明连接线缆有问题。仅单侧报故障则定位到该端口连接器。
BreakPoint 结构
enum class FaultType : uint8_t {
LinkDown = 0, // 断线
CrcFault = 1, // CRC 故障 (线缆/连接器劣化)
Unknown = 255 // 未知故障
};
struct Diagnostics::BreakPoint {
uint16_t slave; // 故障从站索引 (1-based)
uint8_t port; // 故障端口号 (0-3, 对应 P0-P3)
FaultType fault_type; // 故障类型枚举
bool is_link_down() const; // 是否断线故障
bool is_crc_fault() const; // 是否 CRC 故障
};
单个从站的冗余状态请参考 从站诊断 - 冗余诊断。
示例:
auto& base = master.GetDiagnostics();
MasterDiagnosticsInfo diag(base, master.MasterNumber());
// 环拓扑冗余模式 (RingMode() 在 MasterDiagnosticsInfo 返回 darra::RingMode 枚举)
printf("冗余模式: %d\n", static_cast<int>(diag.RingMode()));
if (diag.RingMode() == darra::RingMode::Degraded)
printf("警告: secondary链路不可用,仅primary工作\n");
if (diag.RedundancyActive()) // RedundancyActive() 在 MasterDiagnosticsInfo
printf("冗余已激活\n");
auto bps = base.GetBreakPoints(); // GetBreakPoints() 在底层 Diagnostics
for (auto& bp : bps) {
printf("故障: 从站 %d P%d %s\n", bp.slave, bp.port,
bp.is_crc_fault() ? "CRC 劣化" : "断线");
if (bp.is_crc_fault())
printf("建议检查线缆/连接器\n");
}
诊断快照
GetSnapshot()
DiagnosticsSnapshot GetSnapshot() const;
一次调用获取所有诊断数据的一致快照,避免多次属性访问导致的数据不一致和性能开销。
返回值:
GetSnapshot() 是 MasterDiagnosticsInfo 的成员; 返回的嵌套结构 MasterDiagnosticsInfo::DiagnosticsSnapshot:
struct MasterDiagnosticsInfo::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 MailboxLatencyUs; // 邮箱收发延迟 - 最大 (微秒)
double MailboxLatencyAvgUs;// 邮箱收发延迟 - 平均 (微秒)
double CycleTimeUs; // 实际周期时间 (微秒)
uint16_t WkcActual; // 当前 WKC (= PrimaryWKC)
uint16_t WkcExpected; // 期望 WKC
uint32_t BusCycleHz; // 总线频率 (Hz)
double BusMaxJitterUs; // 总线最大抖动 (微秒)
double BusAvgJitterUs; // 总线平均抖动 (微秒)
double BusRoundtripUs; // 总线往返延迟 (微秒)
double BusLoadPercent; // 通讯负载 (%) — RTT/周期×100
uint32_t SmiCount; // SMI 次数
double SmiPeakUs; // SMI 峰值 (微秒)
bool PrimaryPortOk; // 主端口正常
bool SecondaryPortOk; // 副端口正常
bool RedundancyActive; // 冗余激活
};
示例:
auto& base = master.GetDiagnostics();
MasterDiagnosticsInfo diag(base, master.MasterNumber());
// 获取一致快照
auto snap = diag.GetSnapshot();
printf("频率: %d Hz, 丢包率: %.2f%%\n", snap.Frequency, snap.PacketLossRate * 100.0);
printf("总线抖动: 平均 %.2f us\n", snap.AvgJitterUs);
printf("邮箱延迟: 平均 %.2f us, 最大 %.2f us\n", snap.MailboxLatencyAvgUs, snap.MailboxLatencyUs);
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_t 和 EcALState 两种参数类型。
参数:
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;
}
}
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>— 诊断消息列表,无消息时返回空列表
示例:
// 注意: GetSlaves() 返回 std::vector<uint16_t> (从站索引, 1-based),
// 直接 range-based for 遍历 master 才能拿到 Slave 对象.
for (auto& slave : master) {
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& base = master.GetDiagnostics();
MasterDiagnosticsInfo diag(base, master.MasterNumber());
diag.Enabled(true); // 启用诊断数据采集
// 注册异常回调 (PDO 丢帧事件用 OnPDOFrameLoss 注册, 不是字段赋值)
master.Events().OnPDOFrameLoss([](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(" 周期=%.1f us, 总线抖动 平均=%.2f us, 邮箱延迟 平均/最大=%.2f/%.2f us\n",
diag.CycleTimeSpan(), diag.AvgJitterUs(),
diag.MailboxLatencyAvgUs(), diag.MailboxLatencyUs());
// PDO 丢帧 (GetPDOFrameLossStats 在底层 Diagnostics 上)
auto pdo_stats = base.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() == darra::RingMode::Degraded) {
printf(" 警告: 冗余降级!\n");
auto bps = base.GetBreakPoints();
for (auto& bp : bps) {
printf(" 故障: 从站 %d P%d %s\n", bp.slave, bp.port,
bp.is_crc_fault() ? "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;
}
按问题分块的诊断字段指南
下表的 API 全部是真实存在的接口: MasterDiagnosticsInfo (高层包装, 表中 diag) 与
底层 Diagnostics (表中 base) 各自的成员。
| 现场症状 | C++ API |
|---|---|
| 是不是丢包? | diag.PacketLossRate() + diag.LateFrameRate() |
| WKC 偶发还是持续? | base.ZeroWkcCount() / base.LowWkcCount() / base.HighWkcCount() |
| 哪个从站链路最差? | diag.WorstSlaveIndex() + diag.WorstLinkQuality() |
| 单从站链路质量? | base.SlaveLinkQuality(slaveIndex) |
| ESC 端口错误 (CRC 等)? | slave_stats::read_port_errors(dll, mi, si) (从站诊断页) |
| 哪根网线断了? | base.GetBreakPoints() |
| RT 内核稳定? | diag.RtPreemptCount() / diag.BigGapUnknownCount() / diag.SmiCount() |
1. 丢包与帧时序
diag.PacketLossRate() (TX vs RX 5 秒滑窗) | diag.LateFrameRate() (idx 出 8 帧窗 stale) |
diag.RTcnt() (帧频 Hz) | diag.ErrorCnt() (每秒错误数)
2. WKC 分类统计
base.ZeroWkcCount() (WKC=0 完全丢失) | base.LowWkcCount() (低于预期, 部分从站未响应) |
base.HighWkcCount() (高于预期, 异常)。需每周期调 base.UpdateWkcClassification(wkc, expected) 累计。
3. 端口状态
diag.PrimaryPortOk() / diag.SecondaryPortOk() (有流量且 5 秒内无错误) |
diag.PrimaryPortErrors() / diag.SecondaryPortErrors() (5 秒滑窗错误数)
4. per-slave 物理层 (ESC 寄存器)
ESC 端口错误计数器 (RX / 无效帧 / 链路丢失) 通过 slave_stats 自由函数读取, 见
从站诊断 - 通信诊断。链路质量也可经 base.SlaveLinkQuality(si) 单点查询。
5. RT 性能 + 内核稳定性
diag.BusCycleHz() / diag.BusMaxJitterUs() / diag.BusRoundtripUs() / diag.BusLoadPercent() |
diag.RtPreemptCount() (RT 线程被抢占次数) / diag.BigGapUnknownCount() / diag.SmiCount() / diag.SmiPeakUs() |
diag.JitterHistogramBins() (7 桶直方图)
完整诊断流程示例
auto& base = master.GetDiagnostics();
MasterDiagnosticsInfo diag(base, master.MasterNumber());
// 1. 丢包率
if (diag.PacketLossRate() > 0.01)
std::cout << "[警告] 丢包率 " << (diag.PacketLossRate() * 100) << "% 严重\n";
// 2. WKC 分类 (需每周期 UpdateWkcClassification 累计)
if (base.LowWkcCount() > 0 || base.ZeroWkcCount() > 0)
std::cout << "[警告] WKC 异常: 完全丢失 " << base.ZeroWkcCount()
<< " 次, 低于预期 " << base.LowWkcCount() << " 次\n";
// 3. 故障定位 (GetBreakPoints 返回 vector, 不是单值)
for (auto& bp : base.GetBreakPoints())
std::cout << "故障点: 从站 " << bp.slave << " P" << (int)bp.port
<< (bp.is_crc_fault() ? " CRC 劣化" : " 断线") << "\n";
// 4. 最差链路从站
if (diag.WorstLinkQuality() < 80)
std::cout << "最差从站: #" << diag.WorstSlaveIndex()
<< " (" << diag.WorstLinkQuality() << "%)\n";
// 5. RT 内核 (WDK 硬实时诚实计数)
if (diag.RtPreemptCount() > 0)
std::cout << "[警告] RT 线程被抢占 " << diag.RtPreemptCount()
<< " 次, 峰值 " << diag.RtPreemptPeakNs() << " ns\n";