跳到主要内容

主站诊断

配合事件使用

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

从站诊断

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

功能概览

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

通信与性能统计

类别函数类型读写说明
帧计数GetPacketLossRate(mi)float只读丢包率(0.0~1.0),TX vs RX 5 秒滑窗
帧计数GetLateFrameRate(mi)float只读过慢帧率(0.0~1.0),idx 出 8 帧窗 stale
WKCGetExpectedWKC(mi) / SetExpectedWKC(mi, wkc)uint16_t读写期望工作计数器
WKCGetPrimaryWKC()uint16_t只读主网口 WKC(冗余模式独立跟踪)
WKCGetSecondaryWKC()uint16_t只读副网口 WKC(冗余模式独立跟踪)
WKC 镜像GetWkcActualMirror(mi)uint16_t只读总线实测工作计数器镜像(actual WKC)— 反映此刻哪些从站真的在响应。R1:如实值永不篡改。薄读零帧,实时
WKC 镜像GetWkcExpectedMirror(mi)uint16_t只读期望工作计数器镜像(expected WKC)— 配置期/进 OP 确定的固定真值。R1:永不动态下调迁就劣化总线。薄读零帧,实时
WKC 镜像GetWcDeficit(mi)uint16_t只读WKC 缺额 = expected − actual(>0 表示有映射从站此刻没贡献 WKC,疑似掉站/热插拔)。从站恢复后自动归 0。薄读零帧,实时
WKC 镜像GetMappedSlaveCount(mi)uint16_t只读已映射(参与 WKC 计算)的从站数。薄读零帧,实时
WKC 镜像GetWcStateSeq(mi)uint64_t只读内核 WcState 缓存序列号(每次刷新自增)。判断 per-slave 镜像是否在两次读取间更新过。薄读零帧,实时
从站异常GetSlaveLinkQuality(mi, si)int16_t只读从站链路质量 0-100%
网口帧计数GetWdkPrimaryFrameTx(mi) / GetWdkPrimaryFrameRx(mi)uint32_t只读主网卡发送/接收累计帧数
网口帧计数GetWdkSecondaryFrameTx(mi) / GetWdkSecondaryFrameRx(mi)uint32_t只读副网卡发送/接收累计帧数(冗余有效)
网口帧计数GetWdkPdoIdxDropCount(mi)uint32_t只读PDO idx 槽位被覆盖丢弃次数
详细诊断GetDetailedDiagnostics(mi)void*只读详细诊断数据指针(无需释放)
通信统计GetCommunicationStats(mi) / ResetCommunicationStats(mi)void*读写通信统计指针 + 重置

详细诊断数据结构:

typedef struct {
uint32_t FrameErrors;
uint32_t LostFrames;
uint32_t ChecksumErrors;
uint32_t TimeoutFrames;
uint32_t RxErrorCount[EC_MAXSLAVE];
uint32_t TxErrorCount[EC_MAXSLAVE];
uint16_t LostLinkCount[EC_MAXSLAVE];
uint16_t InvalidFrameCount[EC_MAXSLAVE];
uint16_t WorkingCounterErrors;
uint16_t ConsecutiveWkcErrors;
uint32_t TotalWkcMismatches;
uint32_t PdoLostFrames[EC_MAXGROUP];
uint32_t PdoConsecutiveLost[EC_MAXGROUP];
uint32_t PdoMaxConsecutiveLost[EC_MAXGROUP];
int16_t LinkQualityPercent[EC_MAXSLAVE];
uint32_t PrimaryPortTxCount;
uint32_t PrimaryPortRxCount;
uint32_t SecondaryPortTxCount;
uint32_t SecondaryPortRxCount;
uint32_t PrimaryPortErrors;
uint32_t SecondaryPortErrors;
} ec_diagnostics_data_t;

PDO 丢帧统计

void GetPDOFrameLossStats(uint16_t master_index, uint8_t group,
uint32_t* total_lost, uint32_t* consecutive_lost,
uint32_t* max_consecutive_lost);
void ResetPDOFrameLossStats(uint16_t master_index, uint8_t group);

按组(0-7)查询 PDO 丢帧统计:累计丢帧、当前连续丢帧、历史最大连续丢帧。

WKC 镜像(掉站可观测)

uint16_t GetWkcActualMirror(uint16_t master_index);   /* 实测 WKC 镜像 */
uint16_t GetWkcExpectedMirror(uint16_t master_index); /* 期望 WKC 镜像 (固定真值) */
uint16_t GetWcDeficit(uint16_t master_index); /* 缺额 = expected - actual */
uint16_t GetMappedSlaveCount(uint16_t master_index); /* 已映射从站数 */
uint64_t GetWcStateSeq(uint16_t master_index); /* WcState 缓存序列号 (自增) */

WKC 镜像是内核 per-slave WcState 诊断缓存的主站级聚合镜像,全部薄读零帧(不缓存、不需刷新——内核每周期、WKC 异常时立即维护,DLL 保证读到即此刻真实总线现实)。配合每个从站的 GetSlaveWcState(mi, si) 可定位到具体掉了哪个从站。

R1 可观测性约定(不可违反):

  • GetWkcExpectedMirror(mi) = 配置期 / 进 OP 时确定的固定真值,拓扑固定它就固定,概念上不可变,永不动态下调迁就劣化总线
  • GetWkcActualMirror(mi) = 总线实测值,如实反映此刻哪些从站真的在响应,永不篡改
  • GetWcDeficit(mi) = expected − actual> 0 不是 master 故障,而是有映射从站此刻没贡献 WKC(疑似掉站 / 热插拔恢复中);从站恢复后自动归 0,无需任何重置调用。

示例:

uint16_t act = dll.GetWkcActualMirror(master);
uint16_t exp = dll.GetWkcExpectedMirror(master);
printf("WKC: %u / %u (映射从站 %u 个)\n", act, exp, dll.GetMappedSlaveCount(master));

uint16_t deficit = dll.GetWcDeficit(master);
if (deficit > 0) {
printf("WKC 缺额 %u — 有从站没在响应, 逐个从站定位:\n", deficit);
for (int si = 1; si <= dll.GetSlaveCount(master); si++) {
if (dll.GetSlaveWcState(master, si) == 0) /* 0 = 未贡献 */
printf(" 从站 %d 未贡献 WKC\n", si);
}
}

uint64_t seq = dll.GetWcStateSeq(master); /* 序列号判断镜像是否更新过 (无需轮询帧) */

热插拔重建

/* [roundHOTSWAP1] 运行中一次性热插拔重建拓扑: 重扫从站 + 重建拓扑图 + 恢复 OP, 不停总线.
* 返回 int 错误码: 0=成功, 负值=失败. */
int HotSwapRebuild(uint16_t master_index);

运行中一次性热插拔重建拓扑:任意状态下调用一次,在线重扫总线 + 重建拓扑图 + 重配 PDO + 恢复到运行态(OP)。适用于现场拔掉 / 换上一个模块后,不停总线地把网络重新带回运行态。它不是 EcConfigInit() 那种"释放全网从头扫"的重建——是面向热插拔的在线增量恢复。

返回值(错误码语义):

返回码名称含义与处理
0OK成功:从站全部回到 OP
-20BUSY另一操作正在进行——稍后重试
-21RESCAN_0重扫到 0 个从站——已停在 PreOp 安全态,检查物理链路 / 供电
-22SDO_ABORT重配被从站 SDO abort 拒绝——检查 PDO 映射 / Startup 参数
-23NO_OP部分从站未达 OP——已回滚停安全态,查各从站 AL Status Code
-24TIMEOUT重建超时
-25IDX_FULL帧索引池饱和
-1(通用失败)DLL 未导出该函数等的通用失败码

行为 / 约束(R1 如实可观测):

  • OP 状态调用会短暂中断 PDO(内部降 PreOp 重配再升回 OP),调用方需容忍这一周期的过程数据空窗。
  • 失败时返错误码不掩盖——绝不靠篡改内部计数让结果"看起来对"。
  • 即使最终 0 个从站在 OP,PDO 循环也照常运行(周期不掉、帧不停),期望 WKC 不动态下调迁就劣化总线。

示例:

int rc = dll.HotSwapRebuild(master);
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 <= dll.GetSlaveCount(master); si++) {
uint16_t al = dll.GetSlaveALStatusCode(master, si);
if (al) printf(" 从站 %d: AL=0x%04X\n", si, al);
}
break;
default:
printf("热插拔重建失败: %d\n", rc);
break;
}

DC 同步

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

void SetSyncWindowThreshold(uint16_t master_index, int threshold_ns);
int GetSyncWindowThreshold(uint16_t master_index);

threshold_ns 默认 1000ns。

单个从站

单个从站的同步状态请使用 GetSlaveSyncWindowStatus(),详见 从站 DC 同步

冗余状态

int  GetBreakPoints(uint16_t master_index,
uint16_t* out_slaves, uint8_t* out_ports,
uint8_t* out_types, uint16_t max_results);
int GetRingMode(uint16_t master_index);
BOOL GetSecondaryLinkStatus(uint16_t master_index);

GetBreakPoints() 检测当前所有故障点,统一覆盖两类物理故障:

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

GetRingMode() 返回值: 0=Inactive(未激活), 1=Dual(双向冗余), 2=Degraded(secondary 链路不可用,仅 primary 工作)。

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

从站冗余诊断

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

诊断快照

BOOL GetDiagnosticsSnapshot(uint16_t master_index, ec_diagnostics_snapshot_t* snapshot);

一次调用获取所有诊断数据的一致快照(内部加锁,线程安全),避免多次属性访问导致的数据不一致和性能开销。适合 UI 刷新和日志记录场景。

typedef struct {
int frequency; /* 每秒帧数 (Hz) */
uint32_t error_count; /* 每秒错误数 */
float packet_loss_rate; /* 丢包率 (0.0-1.0) */
float late_frame_rate; /* 过慢帧率 (0.0-1.0) */
double mailbox_latency_avg_us; /* 邮箱收发延迟 - 平均 (微秒) */
double mailbox_latency_us; /* 邮箱收发延迟 - 最大 (微秒) */
int cycle_time_us; /* 实际周期时间 (微秒) */
uint16_t wkc_actual; /* 当前 WKC */
uint16_t wkc_expected; /* 期望 WKC */
uint32_t bus_cycle_hz; /* 总线频率 (Hz) */
double bus_max_jitter_us; /* 总线最大抖动 (微秒) */
double bus_avg_jitter_us; /* 总线平均抖动 (微秒) */
double bus_roundtrip_us; /* 总线往返延迟 (微秒) */
double bus_load_percent; /* 通讯负载 (%) */
uint32_t smi_count; /* SMI 次数 */
double smi_peak_us; /* SMI 峰值 (微秒) */
int primary_port_ok; /* 主端口正常 (BOOL) */
int secondary_port_ok; /* 副端口正常 (BOOL) */
int redundancy_active; /* 冗余激活 (BOOL) */
} ec_diagnostics_snapshot_t;
邮箱收发延迟(原"应用抖动")

mailbox_latency_us / mailbox_latency_avg_us 反映邮箱(CoE/SoE/FoE/EoE/AoE/VoE 等非周期、请求-响应)事务的收发往返时延 —— 从请求发出到响应返回的耗时(最近 1 秒结算的最大值 / 平均值)。

PDO 过程数据已是纯内核 RT 收发 + 内核共享内存指针零拷贝,不存在"用户态每周期通知"环节,旧的"应用抖动"指标失去意义,故改为统计邮箱事务时延。总线时序仍由 bus_max_jitter_us / bus_avg_jitter_us 反映。

示例:

ec_diagnostics_snapshot_t snap;
if (dll.GetDiagnosticsSnapshot(master, &snap)) {
printf("频率: %d Hz, 邮箱收发延迟: %.2f us, WKC: %d/%d, 丢包: %.4f\n",
snap.frequency, snap.mailbox_latency_us,
snap.wkc_actual, snap.wkc_expected, snap.packet_loss_rate);
}

诊断控制

void SetDiagnosticsEnabled(uint16_t master_index, BOOL enable);
BOOL GetDiagnosticsEnabled(uint16_t master_index);
void ResetDiagnostics(uint16_t master_index);

SetDiagnosticsEnabled() 启用/禁用诊断数据采集(默认关闭,启用后周期性采样)。ResetDiagnostics() 一次性重置所有诊断统计:基础诊断统计、PDO 丢帧统计、DC 同步窗口统计、所有从站的端口错误计数器。只清空主站层面的统计数据, 不影响 FSoE 安全连接状态机。

AL 错误分类

/* 定义于 master/diagnostics_ext.h, 按 AL Status Code 高位段分类 */
typedef enum {
AL_ERR_NONE = 0,
AL_ERR_GENERAL = 1, /* 0x0001-0x000F 通用错误 */
AL_ERR_STARTUP = 2, /* 0x0010-0x001F 启动 (PDO 映射 / SM 配置) */
AL_ERR_MAILBOX = 3, /* 0x0020-0x002F 邮箱 */
AL_ERR_WATCHDOG = 4, /* 0x0030-0x003F 看门狗 */
AL_ERR_SYNC = 5, /* 0x0040-0x004F 同步 */
AL_ERR_DC = 6, /* 0x0050-0x005F DC */
AL_ERR_MBX_EOE = 7, /* 0x0060-0x006F EoE 邮箱 */
AL_ERR_UNKNOWN = 99
} DarraAlErrorClass;

DarraAlErrorClass diagnostics_classify_al_error(uint16_t alStatusCode);

对从站 AL Status Code 进行分类,帮助快速判断错误性质和处理策略。分类依据 AL Status Code 的高位段。

分类处理建议
AL_ERR_GENERAL通用错误,查阅 ETG.1000 或从站手册
AL_ERR_STARTUP检查 PDO 映射、SM 配置、Startup 参数
AL_ERR_MAILBOX检查邮箱协议配置 (CoE/FoE 等)
AL_ERR_WATCHDOGPDO 周期过慢或通信中断,检查丢帧
AL_ERR_SYNC同步错误,检查 SYNC0 周期配置
AL_ERR_DCDC 同步配置问题,检查 DC 启动时间
AL_ERR_MBX_EOEEoE 邮箱错误
AL_ERR_UNKNOWN未在已知段内,查阅 ETG.1000 或从站手册
常见 AL Status Code
  • 0x001E 无效输入映射 — AL_ERR_STARTUP
  • 0x001D 无效输出映射 — AL_ERR_STARTUP
  • 0x0011 无效邮箱配置 — AL_ERR_GENERAL
  • 0x002D 同步错误 — AL_ERR_MAILBOX
  • 0x0032 DC 同步超时 — AL_ERR_WATCHDOG
  • 0x0052 DC PLL 错误 — AL_ERR_DC
C 特有语法糖

打印从站状态时, C 可用 ec_state_name(state) inline 把 EC_STATE_INIT/PRE_OP/SAFE_OP/OP/BOOT 直接转字符串, 屏蔽错误位 (& 0x0F); 同样 ec_link_name(ec_link(mi)) 输出 "UP" / "DOWN" / "REDUNDANCY". 详见 inline 包装.

从站错误计数器

BOOL ResetSlavePortErrorCounters(uint16_t master_index, uint16_t slave_index);
BOOL ReadSlavePortErrorCounters(uint16_t master_index, uint16_t slave_index,
uint8_t* rx_error, uint8_t* invalid_frame, uint8_t* lost_link);
int ReadAllSlavePortErrorCounters(uint16_t master_index);
void* GetSlavePortErrorStats(uint16_t master_index, uint16_t slave_index);

读取/重置从站 ESC 端口错误计数器(ETG.1000.4)。rx_error/invalid_frame/lost_link/fwd_rx_error 各为 4 字节数组,对应 P0-P3 端口(4 类 4 字节数组)。

示例:

uint8_t rx[4], inv[4], lost[4];
if (dll.ReadSlavePortErrorCounters(master, 1, rx, inv, lost)) {
for (int p = 0; p < 4; p++) {
if (rx[p] || inv[p] || lost[p])
printf("从站1 P%d: rx=%u inv=%u lost=%u\n", p, rx[p], inv[p], lost[p]);
}
}

配置预检查

int ec_validate_config(uint16_t master_index);

Build 前预检查主站配置是否完整(网络适配器、从站发现、PDO 映射等)。返回 0 表示配置有效,非 0 表示配置问题数量。

主站身份与诊断对象

BOOL GetMasterIdentity(uint16_t master_index, ec_master_identity_t* identity);
BOOL GetMasterDiagData(uint16_t master_index, ec_master_diag_data_t* diag);

读取主站 ETG.1510 标准对象:身份对象 0x1018(VendorID/ProductCode/RevisionNumber/SerialNumber)和诊断数据对象 0xF120。

诊断消息 (CoE 0x10F3 诊断历史)

从站对象 0x10F3(诊断历史对象,ETG.1020)是标准对象字典对象,应用层用通用 SDO 读写原语 dx_sdo_read / dx_sdo_write 直接读取其各子索引即可(0x10F3:04 是否有新消息、0x10F3:02 最新编号、0x10F3:05 标志、0x10F3:06..FF 各条消息、0x10F3:03 写编号确认已读)。完整子索引布局与读取流程示例见 CoE 诊断历史

备注

并非所有从站都支持 0x10F3 诊断历史对象。不支持的从站读取时 dx_sdo_read_ex 回填的 out_abort_code 为对象不存在错误。此对象通过 SDO 读取,不建议在实时路径中高频调用。

紧急控制与中止

void AbortScan(void);
void ResetScanAbort(void);
void AbortNetwork(void);
void ResetAbortNetwork(void);
void EmergencyCloseNics(void);

终止正在进行的网络扫描或紧急关闭网卡,避免长操作卡死调用方。

警告

EmergencyCloseNics 不走标准 Stop / Dispose 流程, 仅在死锁兜底场景使用. 正常退出请用 Stop + Dispose + EcClose.

完整示例

#define DYNAMIC_LOAD
#include "ethercat.h"
#include <stdio.h>

int main(void) {
dll_t dll;
LOAD_DLL(&dll, "DarraEtherCAT.dll");

uint16_t master = dll.Initialize();
dll.SetNetwork(master, "\\Device\\NPF_{GUID}", "");
dll.SetStateSequence(master, EC_STATE_OPERATIONAL, 10000);
dll.Start(master);

dll.SetDiagnosticsEnabled(master, TRUE);

/* 一致快照 */
ec_diagnostics_snapshot_t snap;
if (dll.GetDiagnosticsSnapshot(master, &snap)) {
printf("频率: %d Hz, 邮箱收发延迟: %.2f us, 丢包: %.4f\n",
snap.frequency, snap.mailbox_latency_us, snap.packet_loss_rate);
}

/* PDO 丢帧 */
uint32_t total, cur, max;
dll.GetPDOFrameLossStats(master, 0, &total, &cur, &max);
if (cur > 10) printf("警告: PDO 连续丢帧 %u\n", cur);

/* 故障点 */
uint16_t bp_s[8]; uint8_t bp_p[8], bp_t[8];
int n = dll.GetBreakPoints(master, bp_s, bp_p, bp_t, 8);
for (int i = 0; i < n; i++) {
printf("故障: 从站%d P%d %s\n", bp_s[i], bp_p[i],
bp_t[i] == 0 ? "断线" : "CRC故障");
}

/* AL 错误分类 */
uint16_t al = dll.GetSlaveALStatusCode(master, 1);
if (al) {
DarraAlErrorClass c = diagnostics_classify_al_error(al);
printf("从站1 错误 0x%04X: 分类=%d\n", al, c);
}

dll.ResetDiagnostics(master);
dll.Stop(master);
dll.Dispose(master);
UNLOAD_DLL(&dll);
return 0;
}

按问题分块的诊断字段指南 (C 风格)

GetDetailedDiagnostics(mi) 返回 ec_diagnostics_data_t*(字段名见上文 详细诊断数据结构,全部 PascalCase)。总线 RT 性能指标走 GetDiagnosticsSnapshot() 输出的 ec_diagnostics_snapshot_t

现场症状C 接口
是不是丢包?diag->LostFrames + GetPacketLossRate(mi)
是偶发还是持续?diag->ConsecutiveWkcErrors (≥5 = 持续)
WKC 累计不匹配?diag->TotalWkcMismatches / diag->WorkingCounterErrors
CRC 问题?diag->InvalidFrameCount[i] (ESC 0x0304)
哪根网线断了?GetBreakPoints(mi, ...)
总线抖动 / 周期?snap.bus_max_jitter_us / snap.bus_cycle_hz (GetDiagnosticsSnapshot)

1. 帧错误 (TX vs RX 对比)

ec_diagnostics_data_t 字段:

  • LostFrames: 帧根本没回来
  • TimeoutFrames: PDO 周期未响应
  • WorkingCounterErrors: WKC 不匹配
  • ConsecutiveWkcErrors: 连续 WKC 错误 (核心区分偶发/持续)
  • TotalWkcMismatches: 累计 WKC 不匹配总数
  • FrameErrors: 派生总和
  • ChecksumErrors: 物理层 CRC (从 ESC 派生)

2. 端口收发

PrimaryPortTxCount / PrimaryPortRxCount, SecondaryPortTxCount / SecondaryPortRxCount, PrimaryPortErrors / SecondaryPortErrors (5 秒滑窗)

3. per-slave 物理层 (ESC 寄存器, 数组 1-based)

RxErrorCount[i] (ESC 0x0300) | InvalidFrameCount[i] (ESC 0x0304 CRC) | LostLinkCount[i] (ESC 0x0310) | LinkQualityPercent[i]

4. PDO 丢帧 (每组)

PdoLostFrames[g] (累计丢帧) | PdoConsecutiveLost[g] (当前连续) | PdoMaxConsecutiveLost[g] (历史最大连续)

5. 总线 RT 性能

通过 GetDiagnosticsSnapshot(mi, &snap) 一致读 ec_diagnostics_snapshot_t:

  • bus_cycle_hz / bus_max_jitter_us / bus_avg_jitter_us / bus_roundtrip_us
  • frequency / mailbox_latency_us / mailbox_latency_avg_us / cycle_time_us / packet_loss_rate

完整诊断流程示例 (C)

ec_diagnostics_data_t* diag = (ec_diagnostics_data_t*)dll.GetDetailedDiagnostics(mi);

// 1. 持续 vs 偶发
if (diag->ConsecutiveWkcErrors >= 5)
printf("持续 WKC 错误\n");

// 2. 丢包率
float loss = dll.GetPacketLossRate(mi);
if (loss > 0.01f) printf("丢包率 %.2f%% 严重\n", loss * 100.0f);

// 3. 故障定位
uint16_t bp_slaves[16]; uint8_t bp_ports[16]; uint8_t bp_types[16];
int bpn = dll.GetBreakPoints(mi, bp_slaves, bp_ports, bp_types, 16);
for (int i = 0; i < bpn; i++)
printf("故障点: 从站 %d P%d (类型=%d)\n", bp_slaves[i], bp_ports[i], bp_types[i]);

// 4. 最不稳定从站 (按 ESC CRC 错误计数)
int worst = 0; uint16_t worstCnt = 0;
for (int i = 1; i < EC_MAXSLAVE; i++)
if (diag->InvalidFrameCount[i] > worstCnt)
{ worstCnt = diag->InvalidFrameCount[i]; worst = i; }
if (worstCnt > 0) printf("CRC 错误最多的从站: #%d (%u 次)\n", worst, worstCnt);

// 5. 总线 RT 性能
ec_diagnostics_snapshot_t snap;
if (dll.GetDiagnosticsSnapshot(mi, &snap))
printf("总线 %u Hz, 最大抖动 %.2f us\n",
snap.bus_cycle_hz, snap.bus_max_jitter_us);