从站诊断
每个从站提供独立的诊断属性和方法, 可查询端口错误、冗余、DC 同步等信息。
从站状态 (State()、ErrorCode()、IsLost()) 等基础属性请参考 属性与方法。
建议通过 事件 驱动异常处理 (如 SlaveStateChanged、SlaveOffline、DCSyncLost), 而非轮询。直接读取从站诊断属性适用于 UI 显示等场景。
全局通信统计、丢包率、PDO 丢帧汇总请参考 主站诊断。
功能概览
| 功能 | 访问方式 | 说明 |
|---|---|---|
| WKC 与健康镜像 | dm::slave_wc_contributed(mi, si) / slave.MailboxHealth() 等 | per-slave WcState/AL/邮箱健康内核镜像 (薄读零帧, 实时) |
| 通信诊断 | slave_stats::read_port_errors(dll, mi, si) | ESC 端口错误计数器 (free function) |
| 冗余诊断 | slave.RedundancyActivated() / PrimaryLinkBroken() / SecondaryLinkBroken() | 冗余激活、主/冗余线路断路检测 |
| DC 同步 | slave.IsInSync() / SyncTimeDifference() / GetSyncWindowStatus() | 同步状态、时间差、失步次数 |
| 拓扑信息 | slave.ActivePorts() / ParentStation() | 端口连接和父节点 (注: ParentStation 实际是从站索引 1-based, 不是 station address; 0 表示父节点是主站) |
| 链路质量 | slave_stats::get_slave_link_quality(dll, mi, si) | 从站链路质量 (free function) |
WKC/健康镜像、冗余和 DC 属性为"不可停" (始终活跃), 无需启用 master.GetDiagnostics().Enabled。
slave_stats::read_port_errors() 需实时读取从站, 不建议高频调用。
WKC 与健康镜像
per-slave WcState / AL 状态镜像由头文件 slave/diag_mirror.hpp 的 darra::ethercat::diag_mirror
命名空间提供 (自由函数, 非 Slave 成员),邮箱健康度是 Slave 成员方法。全部为内核 per-slave
诊断缓存的薄读零帧——内核每周期 (WKC 异常时立即) 维护缓存, 读到即此刻真实总线现实, 无需任何刷新。
配合主站级 dm::wc_deficit(mi) 可在掉站时定位到具体从站。
#include "slave/diag_mirror.hpp"
namespace dm = darra::ethercat::diag_mirror;
dm::slave_wc_contributed() — 从站 WKC 贡献
dm::WcContribution dm::slave_wc_contributed(uint16_t mi, uint16_t si);
该从站对工作计数器 (WKC) 的贡献状态。R1 可观测性约定: NotContributed 不是 master 故障,
而是该从站此刻没在响应 (疑似掉站 / 热插拔恢复中); 修复后自动回 Contributed。内核未填充时诚实返回
Unknown (0xFF), 不臆造。
enum class dm::WcContribution : uint8_t {
NotContributed = 0, // 该从站此刻未响应 (掉站/热插拔中) — 如实暴露, 非错误处理失败
Contributed = 1, // 该从站此刻正常贡献 WKC
Unknown = 0xFF // 内核缓存未知 (未映射/未刷新) — 诚实暴露, 不臆测
};
dm::slave_al_status_mirror() — 从站 AL 状态镜像
dm::AlStatusMirror dm::slave_al_status_mirror(uint16_t mi, uint16_t si);
返回内核 16bit AL 镜像的解析结构。raw == 0 时 known == false, 表示内核尚无该从站镜像, 不臆测状态。
struct dm::AlStatusMirror {
uint16_t raw; // 原始 16bit 镜像 (0 = 未知/未刷新)
uint8_t state; // AL State 低 4 位 (1/2/4/8 = INIT/PREOP/SAFEOP/OP)
bool error; // AL Status Error 位 (bit4)
uint8_t code; // AL Status Code 高字节 (0 = 无错误码)
bool known; // raw != 0 时为 true; false = 内核尚无该从站镜像
bool is_op() const; // AL State 是否处于 OP
bool is_safeop() const; // 是否处于 SAFEOP
bool is_preop() const; // 是否处于 PREOP
bool is_init() const; // 是否处于 INIT
};
示例:
#include "slave/diag_mirror.hpp"
namespace dm = darra::ethercat::diag_mirror;
uint16_t mi = master.MasterNumber();
for (int si = 1; si <= master.SlaveCount(); ++si) {
// WKC 贡献:掉站定位
if (dm::slave_wc_contributed(mi, si) == dm::WcContribution::NotContributed)
printf("从站 %d 本周期未贡献 WKC, 疑似掉站\n", si);
// AL 状态镜像
auto al = dm::slave_al_status_mirror(mi, si);
if (al.known && al.error)
printf("从站 %d AL 错误: state=0x%X code=0x%02X\n", si, al.state, al.code);
}
Slave 类没有 ReadPortErrors() 成员方法。ESC 端口错误读取由 darra::ethercat::slave_stats
命名空间下的自由函数提供, 显式接收 dll_t& / 主站索引 / 从站索引参数。
冗余 / DC 诊断 (RedundancyActivated() / IsInSync() / GetSyncWindowStatus() 等) 才是 Slave 成员方法。
通信诊断
slave_stats::read_port_errors()
namespace darra::ethercat::slave_stats {
std::optional<EscPortErrors> read_port_errors(dll_t& dll, uint16_t mi, uint16_t si);
}
读取从站 ESC 端口错误计数器, 返回各端口的物理层错误统计。这是 darra::ethercat::slave_stats
命名空间下的自由函数, 显式接收 DLL 引用、主站索引、从站索引。
参数:
dll(dll_t&) — DLL 引用 (可由master.Dll()获取)mi(uint16_t) — 主站索引 (master.MasterNumber())si(uint16_t) — 从站索引 (1-based)
返回值:
std::optional<EscPortErrors>— 端口错误计数器, 读取失败返回std::nullopt
相关结构:
struct EscPortErrors {
uint8_t rx_error[4]; // 各端口 RX 错误计数 (0x0300-0x0303)
uint8_t invalid_frame[4]; // 各端口无效帧计数 (0x0304-0x0307)
uint8_t lost_link[4]; // 各端口链路丢失计数 (0x0310-0x0313)
uint8_t fwd_rx_error[4]; // 各端口转发 RX 错误计数 (0x0308-0x030B)
bool has_errors() const; // 是否存在任何错误
};
struct SyncWindowStatus {
int DiffNs; // 当前与参考时钟的时间差 (纳秒)
int MaxDiffNs; // 最大时间差 (纳秒)
int MinDiffNs; // 最小时间差 (纳秒)
bool InSync; // 是否在同步窗口内
uint32_t OutOfSyncCount; // 失步次数
};
配套自由函数 (同一 slave_stats 命名空间):
namespace darra::ethercat::slave_stats {
// 批量读取所有从站端口错误
std::vector<std::optional<EscPortErrors>>
read_all_port_errors(dll_t& dll, uint16_t mi, int slave_count);
// 重置端口错误计数器
bool reset_port_errors(dll_t& dll, uint16_t mi, uint16_t si);
int reset_all_port_errors(dll_t& dll, uint16_t mi, int slave_count);
// 链路质量评估
LinkQuality get_slave_link_quality(dll_t& dll, uint16_t mi, uint16_t si);
std::vector<LinkQuality>
get_all_slave_link_quality(dll_t& dll, uint16_t mi, int slave_count);
}
示例:
using namespace darra::ethercat;
dll_t& dll = master.Dll();
uint16_t mi = master.MasterNumber();
for (int i = 1; i <= master.SlaveCount(); ++i) {
auto errors = slave_stats::read_port_errors(dll, mi, static_cast<uint16_t>(i));
if (errors && errors->has_errors()) {
printf("从站 %d: RX=[%d,%d,%d,%d] 无效帧=[%d,%d,%d,%d] 链路丢失=[%d,%d,%d,%d]\n", i,
errors->rx_error[0], errors->rx_error[1], errors->rx_error[2], errors->rx_error[3],
errors->invalid_frame[0], errors->invalid_frame[1], errors->invalid_frame[2], errors->invalid_frame[3],
errors->lost_link[0], errors->lost_link[1], errors->lost_link[2], errors->lost_link[3]);
}
}
冗余诊断
通过从站对象访问冗余状态。仅在主站启用冗余时有意义。RedundancyActivated() 表示从站未丢失但网络中存在物理断线; PrimaryLinkBroken() / SecondaryLinkBroken() 表示主/副端口路径上存在断线。CRC 故障不触发这些标志。
全局冗余状态和断线点请参考 主站诊断 - 冗余状态。
示例:
for (int i = 1; i <= master.SlaveCount(); ++i) {
auto& s = master.GetSlave(i);
if (!s.RedundancyActivated()) continue;
printf("从站 %d: 冗余激活", i);
if (s.PrimaryLinkBroken()) printf(", 主线路断路");
if (s.SecondaryLinkBroken()) printf(", 冗余线路断路");
printf("\n");
}
DC 同步
通过从站对象访问 DC 同步状态。IsInSync() 返回是否在同步窗口内 (阈值由 master.GetDiagnostics().SyncWindowThreshold() 控制); SyncTimeDifference() 返回与参考时钟的纳秒级时间差; GetSyncWindowStatus() 返回完整 SyncWindowStatus 结构。
全局阈值和事件请参考 主站诊断 - DC 同步。
示例:
for (int i = 1; i <= master.SlaveCount(); ++i) {
auto& s = master.GetSlave(i);
if (!s.HasDC()) continue;
auto status = s.GetSyncWindowStatus();
if (status) {
printf("从站 %d: 时间差=%dns 最大=%dns 同步=%s 失步=%u\n", i,
status->DiffNs, status->MaxDiffNs,
status->InSync ? "是" : "否", status->OutOfSyncCount);
}
}
完整示例
#include "ethercat.hpp"
using namespace darra::ethercat;
void slave_diagnostics_report(EtherCATMaster& master) {
auto& diag = master.GetDiagnostics();
dll_t& dll = master.Dll();
uint16_t mi = master.MasterNumber();
for (int i = 1; i <= master.SlaveCount(); ++i) {
auto& s = master.GetSlave(i);
printf("--- 从站 %d: %s ---\n", i, s.Name().c_str());
// 端口错误是 slave_stats 自由函数, 不是 Slave 成员方法
auto errors = slave_stats::read_port_errors(dll, mi, static_cast<uint16_t>(i));
if (errors && errors->has_errors()) {
printf(" 端口错误: RX=[%d,%d,%d,%d]\n",
errors->rx_error[0], errors->rx_error[1],
errors->rx_error[2], errors->rx_error[3]);
}
if (s.RedundancyActivated()) {
printf(" 冗余激活: 主线路断=%s 冗余线路断=%s\n",
s.PrimaryLinkBroken() ? "是" : "否",
s.SecondaryLinkBroken() ? "是" : "否");
}
if (s.HasDC()) {
auto sync = s.GetSyncWindowStatus();
if (sync) {
printf(" DC: 时间差=%dns 同步=%s 失步=%u\n",
sync->DiffNs, sync->InSync ? "是" : "否",
sync->OutOfSyncCount);
}
}
// 注: ParentStation 实际是从站索引 1-based, 不是 station address; 0 表示父节点是主站
printf(" 活动端口=0x%02X 父节点=%u 链路质量=%d\n",
s.ActivePorts(), s.ParentStation(), diag.SlaveLinkQuality(i));
}
}
邮箱健康度可观测 (2.5.x 新增)
R1 可靠性铁律下的从站级邮箱健康度观测 — 让"从站仍在 OP 但邮箱通道半失效 (CoE/SDO 可能阻塞)"
这种半失效状态对上层可见。全部薄读零帧, 如实反映现实, 不参与 WKC 篡改。这四个是 Slave 成员方法。
class Slave {
public:
// 邮箱健康度: Unknown / Healthy / Degraded
// Degraded = 在 OP 但邮箱半失效 (handler 丢失 / DC 降级伴 0x001F)
ethercat::slave_stats::MailboxHealth MailboxHealth() const;
// 是否被迫降级到 FreeRun 同步模式 (配置期要 DC 但运行中退到 FreeRun)
bool IsFreeRunDemoted() const;
// 邮箱半失效连续累计计数 (≈秒数); 0 = 健康或未评估
uint32_t HealthDegradedCount() const;
// 手动触发邮箱半失效修复 (内核通常会自动修复, 一般无需手动调用)
bool RecoverMailboxHealth() const;
};
相关枚举:
namespace darra::ethercat::slave_stats {
enum class MailboxHealth : uint8_t {
Unknown = 0, // 未知 / 无邮箱 / 不在 OP — 邮箱健康度此时无意义
Healthy = 1, // 健康 — 在 OP 且邮箱可用
Degraded = 2 // 降级 — 在 OP 但邮箱半失效
};
}
示例:
using darra::ethercat::slave_stats::MailboxHealth;
for (int i = 1; i <= master.SlaveCount(); ++i) {
auto& s = master.GetSlave(i);
if (s.MailboxHealth() == MailboxHealth::Degraded) {
printf("从站 %d 邮箱半失效已 %u 秒 — 仍在 OP, WKC 未篡改\n",
i, s.HealthDegradedCount());
// 通常无需手动调用, 内核持续检测到半失效若干秒后会自动修复
s.RecoverMailboxHealth();
}
if (s.IsFreeRunDemoted())
printf("从站 %d 被迫从 DC 降级到 FreeRun\n", i);
}
C++ SDK 当前未单独封装 GetSlaveStateLive / GetSlaveALStatusCodeLive 实时薄读入口。
从站当前状态与 AL 错误码请直接使用 Slave::State() / Slave::ErrorCode() / Slave::GetALStatusCode(),
这些已直接读取从站结构体, 反映实时值。