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.
127 lines
7.1 KiB
Markdown
127 lines
7.1 KiB
Markdown
# 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 的自身序列号计算往返)
|
||
|
||
## 构建与部署
|
||
|
||
```bash
|
||
# 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)
|
||
|
||
```bash
|
||
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_*)的修复
|
||
|
||
1. **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)。
|
||
2. **合理性与稳定性评测发现的参考实现其它问题**:
|
||
- `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 提升、绝对时间睡眠、Ticker
|
||
- `src/stats.rs` — 有界在线统计(直方图 + Welford)
|
||
- `vendor/ethercrab` — 带 `sdo_write_complete`/`send_raw_coe` 补丁的
|
||
ethercrab 0.7.1(复制自 rustbootd/gateway-workspace/vendor)
|