Re-implementation of rustbootd's ecat_el6695_* examples as a single binary with fixes found in review and on hardware: - DC-follow PLL hardened against period-2 hunting: slew-limited anchor (+/-50us/cycle), bistable-trap snap re-anchor, re-prime on stale deadline - drift-free absolute-grid ticker mode; probe mode for timestamp forensics - bounded-memory online stats (histograms), graceful SIGINT/SIGTERM shutdown with full report, error-streak abort - timestamp plausibility filter comparing against the previous raw sample (avoids the deadlock after a startup outlier) - XFC scope waveform options: --el2202 (with --el2202-dual), --el2262, --el1252 latch timestamp readback with per-channel edge statistics - register access unified in regs.rs: named bit constants everywhere, read-modify-write for enable/activation bytes - vendored patched ethercrab 0.7.1 (sdo_write_complete, send_raw_coe) Verified on J1900 (PREEMPT_RT 6.6.135): 600k cycles/600s exact 1 kHz, tx/rx zero errors, phase_err p50=164us std=5us; EL2202<->EL1252 loopback edge interval mean 2000.24us std=24.65us.
7.1 KiB
el6695-rt — J1900 ↔ TwinCAT 1 kHz 通讯验证(EL6695 EtherCAT 桥)
J1900 (Linux, PREEMPT_RT) 经 EL6695 EtherCAT 桥与 TwinCAT Windows PC 之间
1 kHz 双向过程数据(1024 字节/方向)通讯验证程序。参考
rustbootd/gateway-workspace 的 ecat_el6695_* 示例重新实现,并修复了
评审中发现的问题(见文末)。
拓扑
J1900(eth1) → EK1100 → EBUS → EL6695(X2/primary) → X1 → EK1100 → TwinCAT 主机
- SM2 @0x1C00:1024B,J1900 → TwinCAT(TwinCAT 侧
aIn1) - SM3 @0x8E00:1026B(ticker 模式:2B 诊断 + 1024B)或 1046B(follow/probe 模式:22B 0x1A02 时间戳 + 1024B),TwinCAT → J1900
- 每周期:fprd SM3 → 原样回显 → fpwr SM2,本地周期号写在字节 8..16 (TwinCAT PLC 用字节 0..8 的自身序列号计算往返)
构建与部署
# Windows + Git Bash,交叉编译 musl 静态二进制
RUSTFLAGS="-C linker=rust-lld -C link-self-contained=y" \
cargo build --release --target x86_64-unknown-linux-musl
# 部署(经跳板,按实际地址调整)
scp -J tonycao@192.168.58.8 \
target/x86_64-unknown-linux-musl/release/el6695-rt \
root@192.168.68.100:/root/gateway/el6695_rt
运行(J1900 上,root)
el6695_rt eth1 --mode follow --duration 600 # DC 跟随(推荐)
el6695_rt eth1 --mode ticker --duration 60 # 本地 1ms 自由跑
el6695_rt eth1 --mode probe --duration 10 --debug-ts 40 # 时间戳探针
参数:--phase-us N(follow 模式网格相位,默认 500)、--cycle-us N、
--core N(绑核,默认 2)、--prio N(SCHED_FIFO,默认 80)、
--quiet(不打每秒状态行)。--duration 0 = 跑到 Ctrl-C/SIGTERM 为止,
信号触发优雅退出并打印完整统计。
--el2202(默认关闭):每周期翻转 EL2202 数字输出(0x01/0x00),产生
~500Hz 方波供示波器观察发送时序抖动。--el2202-dual:双通道同相翻转
(写 0x05/0x00),输出 1 → EL1252 回采、输出 2 → 示波器,同一信号两种
视角。注意 EL2202 过程字节语义(EEPROM PDO 0x1600/0x1601 实证):
bit0=通道1数据, bit1=通道1三态, bit2=通道2数据, bit3=通道2三态——
写 0x03 会把通道 1 置为高阻(三态),双通道必须写 0x05。实现:手动
配置 EL2202 SM0(输出物理地址 0x0F00,1 字节,ctrl 0x44,enable 0x09
按 EEPROM),裸 AL 驱动进 OP,写回读验证后才启用;翻转帧使单圈执行
时间从 ~410µs 增至 ~650µs(仍在 1ms 预算内)。注意:输出端子可能
接着真实执行器,确认接线后再启用。 简单 DO 端子无邮箱且 SM/FMMU
在组 PREOP→SAFEOP 转换时才被编程(我们跳过该转换),这是必须手动配
SM0 的原因。
--el2262(默认关闭):EL2262 XFC 输出 DC 同步锁定方波(方案 B)。
配置 DC 同步单元(SYNC0/1 周期 1ms、+100ms 启动、0x0980=0x03)并手动
配 SM0(0x1000/1B/0x64/0x09),输出沿锁定 DC 1ms 网格,可作示波器或
EL1252 回采的基准。
--el1252(默认关闭):EL1252 锁存时间戳回采(方案 C)。每周期读
ESC 寄存器 0x09AE 起 34 字节(status + 双通道 Latch Pos/Neg u64),
统计两通道上升沿间隔与沿相位。详见 docs/scope-waveform-design.md §8。
帧预算:A+C 或 B+C 组合单圈 ~898µs,A+B+C 不要同开(超 1ms)。
三种模式
- ticker:
clock_nanosleep(TIMER_ABSTIME)绝对时间网格,无漂移; SM3 布局为 0x1A01 诊断字。 - follow(默认):软件 PLL 跟随 TwinCAT DC 时钟(0x1A02 外部时间戳), 读点锁在 TC 网格边界 + phase。与参考实现的差异见下。
- probe:ticker 节拍 + follow 布局,不做 PLL 反馈,只 dump 原始 时间戳相位(诊断用)。
相对参考实现(rustbootd ecat_el6695_*)的修复
- DC 跟随 PLL 的周期二狩猎(最重要):参考实现每个周期用原始
采样直接重算锚点(deadbeat:
floor(ext)+1ms+phase)。实测该环路是 带延迟的反馈系统(TwinCAT 发送相位与我们的帧时序相互耦合),存在 多个周期轨道:相位关系漂移(实测 ~155ppm)到不利区间时,周期时间 在 0.4/1.6ms 间交替、相位误差双峰跨度 >1ms。参考二进制只是恰好落 在稳定轨道上。本实现:锚点变化 slew 限幅 ±50µs/周期,且检测到 卡死(误差连续 100 周期顶在同一限幅边界,即"读到上一帧"的双稳态 陷阱)时一次性 snap 重锚定;deadline 落后 >2ms 时 re-prime(同时 丢弃被污染的 offset)。修复后此前必现狩猎的 phase=500 配置: 30000 周期/30s 精确 1kHz,phase_err p50=164µs、std 4.4µs, period_jitter p99.9=0.2µs,锁相质量优于参考实现(std 6~10µs)。 - 合理性与稳定性评测发现的参考实现其它问题:
bridge_dump用自旋凑满 1ms,无绝对时间锚点,超时即永久相移 → 本实现 ticker 模式用绝对时间网格。- 统计用逐样本 Vec 再排序(~30MB/小时/指标)→ 有界直方图 + Welford。
- 无信号处理,Ctrl-C 丢失全部统计 → SIGINT/SIGTERM 优雅退出。
- 配置代码在 4+ 个 example 中复制粘贴 ~300 行 → 单一
bridge模块。 - 错误只计数不上报,死链静默累积 → 连续 1000 周期错误即中止。
- 时间戳合理性过滤与"上一个原始样本"比较(而非上一个已接受样本), 单个坏样本最多造成两次拒绝、自动恢复(过滤与已接受值比较会在启动 离群值后永久死锁——实测踩中)。
- 移除模拟内存 loopback 线程/mutex(CH368/RFM2G 模拟遗留),RT 路径 直接回显。
- 删除循环内嵌 PLC 时间戳的端到端延迟指标(跨时钟域且依赖 PLC 程序 内嵌格式,实测数据无意义;端到端延迟应在 TwinCAT 侧用 PLC RTT 统计 测量)。
保留的参考实现经验(全部实测有效)
- EL6695 用户 OD 在主站更替时被固件清空,每次上线自检自配。
- 映射对象必须 Complete Access 单条邮箱消息写(
vendor/ethercrab补丁sdo_write_complete),标准分段流程被固件拒绝 MBXERR_INVALIDSIZE。 - PDO 布局逐字节复制 TwinCAT 线格式:0x1608/0x1A08 各 35 条目 (首条目 @240bit + 33 条 0x0000 延续 + 1 条 @32bit)。
- SM 用裸寄存器配置,AL 状态机裸 fpwr/fprd 驱动;SAFEOP 后邮箱坏死, 数据循环内禁止 SDO。
- RT 手段:PREEMPT_RT 内核 + SCHED_FIFO 80 + 绑核 + mlockall + 栈
prefault;musl 静态链接下
sched_setscheduler需走 raw syscall。
目录
src/main.rs— 主循环(ticker/follow/probe)、PLL、信号处理src/bridge.rs— EL6695 自检自配、SM 裸配置、AL 状态机src/rt.rs— RT 提升、绝对时间睡眠、Tickersrc/stats.rs— 有界在线统计(直方图 + Welford)vendor/ethercrab— 带sdo_write_complete/send_raw_coe补丁的 ethercrab 0.7.1(复制自 rustbootd/gateway-workspace/vendor)