diff --git a/docs/800b-pdo-plan.md b/docs/800b-pdo-plan.md new file mode 100644 index 0000000..4c78cab --- /dev/null +++ b/docs/800b-pdo-plan.md @@ -0,0 +1,124 @@ +# EL6695 PDO 扩展至 800 字节 — 方案文档 + +> **版本**: 1.0 +> **日期**: 2026-07-27 +> **状态**: 待评审 +> **前置文档**: `8b-bridge-design.md`(8B 实现)、`500us-latency-plan.md`(延迟优化) + +--- + +## 1. 目标 + +在现有 8B 工程基础上,新建 **800 字节 PDO** 变体工程 `TwinCAT_EL6695_Primary_800B`,实现 TwinCAT ↔ J1900 双向 800B/周期数据交换,并保持逐字节可验证性。 + +选型说明:800B 介于 8B(验证通路)与 1024B(ESI 默认上限)之间,可作为实际应用的带宽档位;同时验证任意尺寸的映射 blob 生成规则。 + +--- + +## 2. 关键技术点(复用既有经验) + +| # | 经验 | 应用到 800B | +|---|---|---| +| 1 | 链接必须整数组(`LinkVariables`) | 同 8B,PLC 数组与 PDO 条目同类型同尺寸 | +| 2 | 类型 GUID 必须复用 PLC 构建生成的随机 GUID | 先建 PLC `ARRAY[0..799]`,构建取 GUID 填 PDO 条目 | +| 3 | SM3 不挂 0x1A01 诊断 PDO,否则设备报 `size not allowed` | SM3 纯 800B,不含诊断(若含诊断则需 802B) | +| 4 | EL6695 固件按 PDO 赋值总和校验 SM 长度 | SM2=SM3=800(0x0320),两侧一致 | +| 5 | 映射 blob 须为 TwinCAT 行格式(240bit 条目 + 余量) | 见 §3 数学 | +| 6 | 先激活 TwinCAT,再启动 J1900 ticker(同一会话) | 部署顺序不变 | + +--- + +## 3. PDO 映射 blob 数学(800B) + +EL6695 固件要求映射为 TwinCAT 行格式:真实对象条目 @240bit + 若干 0x0000:00 延续条目,总位数 = payload 位数。 + +``` +800 B = 6400 bit = 26 × 240 bit + 160 bit +blob = [条目数=27 (u16 LE)] + + [240, 1, obj_lo, obj_hi] (真实对象 0x6000/0x7000 子索引1) + + 25 × [240, 0, 0, 0] (延续) + + 1 × [160, 0, 0, 0] (余量延续) +``` + +`bridge.rs` 的 `twin_layout_blob_sized` 需泛化:`payload_bytes ≤ 30` 走单条目;否则按 `ceil` 生成多条目(1 真实 + (n-2) 延续 + 1 余量延续),1024B 时退化为现有 35 条目格式(字节级兼容已验证)。 + +--- + +## 4. 工程结构 + +``` +twincat3-auto-cli/ +├─ TwinCAT_EL6695_Primary\ (8B, 不动) +├─ TwinCAT_EL6695_Primary_2kHz\ (2kHz, 不动) +├─ TwinCAT_EL6695_Primary_4kHz\ (4kHz, 不动) +└─ TwinCAT_EL6695_Primary_800B\ (新建, 本方案) +``` + +J1900 二进制: `/root/gateway/el6695_rt_800b`(新构建,旧的全部保留)。 + +--- + +## 5. 实施步骤 + +### 5.1 TwinCAT 侧(副本工程) + +| 步骤 | 内容 | +|---|---| +| 1 | robocopy 复制 `TwinCAT_EL6695_Primary` → `_800B`;修 sln 为相对路径(防绝对路径 bug) | +| 2 | PLC: `GVL_Sync.aIn1/aOut1` → `ARRAY[0..799] OF BYTE`;MAIN 协议改 800B 布局(§6) | +| 3 | 构建 PLC → 从 tsproj 取 `ARRAY [0..799]` 的随机 GUID | +| 4 | tsproj PDO 条目(0x1608/0x1A08)类型替换为该 GUID 的 `ARRAY [0..799] OF BYTE` | +| 5 | SM2 记录: `001c200364...`(len=0x0320=800);SM3 记录: `008e200320...`(len=800),0x1A01 仍不挂 SM3 | +| 6 | `tc-relink.ps1` 整数组重建链接;部署激活;验证 _Boot tszip 中 SM2/SM3=800 | + +### 5.2 J1900 侧 + +| 步骤 | 内容 | +|---|---| +| 1 | `twin_layout_blob_sized` 泛化为多条目(§3) | +| 2 | 常量: `PAYLOAD_LEN=SM_TX_LEN=SM_RX_LEN=SM_RX_PLD_LEN=800` | +| 3 | SM 长度写 800;FMMU len 写 800 | +| 4 | ticker 走 1KB 式协议路径(f(x)=x·7+3 + 图案回显 + 时间戳字段),现有编译期守卫 `PAYLOAD_LEN>=256/280` 自动生效 | +| 5 | 交叉编译(rust-lld)→ scp 为 `el6695_rt_800b` | + +### 5.3 数据协议(800B 布局) + +| 偏移 | 内容 | 方向 | +|---|---|---| +| [0..7] | TwinCAT `nCycles`(ULINT) → J1900 回 `nCycles*7+3` | 双向 | +| [8..15] | J1900 本地周期号 | J→T | +| [16..799] | 逐字节图案 `(nCycles + i) mod 256`,J1900 原样回显 | 双向 | +| [264..267] | J1900 周期抖动 ns | J→T | +| [268..271] | J1900 图案失配计数 | J→T | +| [272..279] | J1900 回存的原始 tc_seq | J→T | + +TwinCAT 校验(复用 1KB 逻辑):从 `nProcessedSeq` 反推原始 seq → 按 seq 重建预期图案逐字节比对 [16..799] → `bSync`;RTT = nCycles − nReturnedSeq。 + +### 5.4 验证 + +1. J1900 日志:SM2/SM3 len=800、OP、stale=0%、mism=0; +2. TwinCAT ADS: `bSync=True`、`aIn1` 非零字节数 ≈ 784(图案区)、RTT 稳定; +3. 逐字节证据:抽 [16..799] 中多个偏移核对 `(seq+i) mod 256`; +4. 连续 10 分钟:nMismatch 零增长、tx/rx 零错误; +5. 频率:保持 1kHz(带宽目标,不引入 2k/4k 变量)。 + +--- + +## 6. 风险与注意点 + +| # | 风险 | 缓解 | +|---|---|---| +| R1 | 多条目 blob 被固件拒绝(余量条目不认) | 先用 verify 模式观察 AL 状态码;备选:全部按 240bit 进位到 27×240(6480bit,多 80bit 填 0) | +| R2 | 800B 时 SM 长度设备校验失败 | 事件日志看 `min/max size`;按报告值对齐 | +| R3 | PLC 构建后 tsproj 类型 GUID 抓取失败 | 构建后 rg 校验 GUID 存在再替换 | +| R4 | 1kHz 下 800B 帧对 E-bus 其他端子无影响 | 既有端子已在 1kHz 验证过,800B 增量小 | + +## 7. 工时估计 + +约 2–3 小时(含一次失败重试余量):tsproj 手术 30min、PLC 30min、J1900 blob 泛化+构建 40min、联调验证 40min。 + +## 8. 修订历史 + +| 日期 | 内容 | +|---|---| +| 2026-07-27 | 初版方案 | diff --git a/docs/8b-bridge-design.md b/docs/8b-bridge-design.md index a9cc74f..301c9df 100644 --- a/docs/8b-bridge-design.md +++ b/docs/8b-bridge-design.md @@ -1,6 +1,6 @@ # EL6695 桥接 8 字节双向实时通讯 — 设计文档 -> **版本**: 1.0 +> **版本**: 1.1(新增 §8 800B 变体) > **日期**: 2026-07-27 > **状态**: 已实现并验证通过 > **前置文档**: `1kb-verification-progress.md`(1KB 版本演进史) @@ -224,3 +224,64 @@ scp -J tonycao@192.168.58.8 target/x86_64-unknown-linux-musl/release/el6695-rt ` 1. **无诊断 PDO**: 8B 配置不含 "External device not connected" 等状态位。若需要,须在 SM3 同时赋值 0x1A01+0x1A08 并保持 SM3=10(TwinCAT 侧重算逻辑需相应修正)。 2. **延迟**: 往返固定 4 个 PLC 周期(≈4ms @1kHz),未做 DC 同步优化。 3. **尺寸扩展**: 本设计的修改方法(GUID 类型 + SM 记录 + J1900 常量)适用于任意 ≤240bit 的单条目尺寸;更大尺寸需在 `twin_layout_blob_sized` 中恢复多条目延续格式。 + +--- + +## 8. 800B 变体(已实现并验证) + +在 8B 验证通路基础上,扩展出 800 字节 PDO 工程 `TwinCAT_EL6695_Primary_800B`,作为实际应用的带宽档位,并验证了任意尺寸的映射 blob 生成规则。 + +### 8.1 与 8B 设计的差异 + +| 项 | 8B | 800B | +|---|---|---| +| PDO 条目类型 | `ARRAY [0..7] OF BYTE` | `ARRAY [0..799] OF BYTE`(GUID `{7D10FDFE-13E3-A012-D822-BE30F7D36D23}`,PLC 构建生成) | +| SM2/SM3 长度 | 8 / 8 | **800 / 800**(0x0320) | +| 0x1A01 诊断 PDO | 不挂 SM3 | 同左(沿用,否则设备报 `size not allowed`) | +| 映射 blob | 单条目 64bit | **27 条目**(见 §8.2) | +| 数据协议 | 每字节 XOR 0x5A + 历史环 | seq + f(x)=x·7+3 + 逐字节图案 + 反推校验(类 1KB) | +| 周期 | 1kHz(基线) | 1kHz | + +### 8.2 多条目映射 blob 生成规则 + +EL6695 固件接受 TwinCAT 行格式:1 真实对象条目 @240bit + N 延续条目(0x0000:00)@240bit + 1 余量延续条目。 + +``` +800 B = 6400 bit = 26 × 240 bit + 160 bit +blob = [27 (u16 LE)] + + [240, 1, obj_lo, obj_hi] 真实对象 (0x6000:01 / 0x7000:01) + + 25 × [240, 0, 0, 0] 延续 + + 1 × [160, 0, 0, 0] 余量延续 +``` + +`twin_layout_blob_sized(obj, n)` 泛化实现:≤240bit 单条目;>240bit 按上式多条目;1024B 特判保持原 35 条目格式(字节级兼容)。 + +### 8.3 800B 数据布局 + +| 偏移 | 内容 | 方向 | +|---|---|---| +| [0..7] | TwinCAT `nCycles` → J1900 回 `nCycles*7+3` | 双向 | +| [8..15] | J1900 本地周期号 | J→T | +| [16..263] | 逐字节图案 `(nCycles+i) mod 256`,J1900 原样回显 | 双向 | +| [264..267] | J1900 周期抖动 ns | J→T | +| [268..271] | J1900 图案失配计数 | J→T | +| [272..279] | J1900 回存的原始 tc_seq | J→T | +| [280..799] | 逐字节图案(同上) | 双向 | + +**设计要点:图案区必须避开 [264..279] 统计字段**——首版实现按 [16..799] 连续图案,J1900 统计字段覆盖 16 字节导致 PLC 每周期 16 字节失配。 + +### 8.4 新增陷阱(800B 实测) + +| # | 现象 | 根因 | 解决 | +|---|---|---|---| +| 8 | 激活时 EL6695 SAFEOP 失败,AL 状态码 0x0003 | **激活与 J1900 ticker 会话并发**:relink 激活时 ticker 正在跑,桥两侧主站会话冲突 | 激活必须在 J1900 空闲时进行;失败后空载重新激活即恢复 | +| 9 | 数据通但 PLC bSync=False,每周期固定 16 字节失配 | 图案区 [16..799] 与 J1900 统计字段 [264..279] 重叠 | 图案区改为 [16..263]+[280..799](见 §8.3) | + +### 8.5 800B 实测性能 + +| 指标 | 值 | +|---|---| +| J1900 exec(800B,同步写) | 均值 384µs / p99 400µs | +| FPRD(读 800B) | 均值 139µs / p99 156µs(8B 时为 71µs) | +| FPWR(写 800B) | 均值 244µs / p99 260µs(与 8B 基本持平) | +| 桥接 RTT(1kHz) | 4 周期(lag=4,同 8B/1KB 结构) | diff --git a/docs/8b-test-report.md b/docs/8b-test-report.md index 2b37732..1b5dafd 100644 --- a/docs/8b-test-report.md +++ b/docs/8b-test-report.md @@ -1,6 +1,6 @@ # EL6695 8B 双向通讯 — 测试文档 -> **版本**: 1.0 +> **版本**: 1.1(新增 §8 800B 测试) > **日期**: 2026-07-27 > **结论**: ✅ 全部通过 > **配套文档**: `8b-bridge-design.md`、`8b-verification-methodology.md` @@ -172,3 +172,60 @@ Copy-Item "C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary\TwinCA | 日期 | 内容 | |---|---| | 2026-07-27 | 初版:8B 双向交换全部测试通过 | + +--- + +## 8. 800B 变体测试(2026-07-28) + +### 8.1 环境差异 + +| 项 | 值 | +|---|---| +| TwinCAT 工程 | `TwinCAT_EL6695_Primary_800B`(1kHz 任务) | +| PDO 尺寸 | 800B(SM2/SM3 = 0x0320) | +| J1900 二进制 | `/root/gateway/el6695_rt_800b` | +| 校验脚本 | `scripts/tc-ads-verify-800b.ps1`、`tc-ads-mismatch-delta.ps1`、`tc-relink-800b.ps1`、`tc-deploy-retry-800b.ps1` | + +### 8.2 配置确认(激活后 _Boot tszip) + +``` +SM2: 001c200364... (start=0x1C00 len=0x0320=800 ✓ 保留) +SM3: 008e200320... (start=0x8E00 len=0x0320=800 ✓ 保留) +PDO 条目: ARRAY [0..799] OF BYTE (GUID {7D10FDFE-13E3-A012-D822-BE30F7D36D23}) +事件日志: 无 EL6695 错误 +``` + +### 8.3 功能验证 + +| 项 | 结果 | +|---|---| +| J1900 映射 blob | 27 条目(26×240+160bit)被固件接受,SAFEOP/OP 通过 | +| 处理证明 | `proc = raw*7+3` 逐样本精确(如 107636 → 753455) | +| PLC 校验 | bSync=True,lag=4 | +| 图案覆盖 | aIn1 非零 775–776/800;[16..263]+[280..799] 逐字节 `(seq+i)%256` 抽查全对 | + +### 8.4 10 分钟稳定性(600,000 周期) + +| 指标 | 结果 | +|---|---| +| 周期完整 | 600000 周期 / 600.0s,零丢失 | +| 传输错误 | tx_err=0, rx_err=0 | +| J1900 图案失配 | mism=0 | +| PLC nMismatch | 295s 运行窗口零增长;90s 确认测试 delta=0 | +| stale | 2999 周期(0.5%) | +| 周期抖动 | 均值 4.89µs / p99 5.4µs(单次 499µs 离群) | +| exec | 均值 384µs / p99 400µs | +| FPRD / FPWR | 139µs / 244µs | + +注:起止时刻的 nMismatch 增长均为**对端缺席期**的预期累积(ticker 未启动/已停止),运行窗口内零失配。 + +### 8.5 800B 特有故障记录 + +| # | 故障 | 根因 | 处置 | +|---|---|---|---| +| F1 | relink 激活时 EL6695 SAFEOP 失败(AL 0x0003) | 激活与运行中的 ticker 会话并发冲突 | J1900 空闲后重新激活即恢复;流程约定:**先激活 TwinCAT,后启动 ticker,激活期间 J1900 必须空闲** | +| F2 | 数据通但每周期固定 16 字节失配 | 图案区 [16..799] 覆盖 J1900 统计字段 [264..279] | 图案区改为 [16..263]+[280..799](MAIN 与校验循环同步修改) | + +### 8.6 回退 + +800B 工程为独立副本,不影响 8B/1kHz 主工程;切回:重新激活 `TwinCAT_EL6695_Primary`(`tc-deploy-retry.ps1`),J1900 使用 `el6695_rt_8b_xor`。 diff --git a/docs/8b-verification-methodology.md b/docs/8b-verification-methodology.md index 75f7587..7b2b736 100644 --- a/docs/8b-verification-methodology.md +++ b/docs/8b-verification-methodology.md @@ -1,6 +1,6 @@ # EL6695 8B 双向通讯 — 验证说明文档 -> **版本**: 1.0 +> **版本**: 1.1(新增 §7 800B 验证) > **日期**: 2026-07-27 > **配套文档**: `8b-bridge-design.md`(设计)、`8b-test-report.md`(测试记录) @@ -151,3 +151,47 @@ aIn1^5A = 98-B8-D8-F8-18-38-58-78 ← 8/8 全等 1. 验证图案为周期性确定图案,未做随机负载压力测试; 2. `lag=4` 为当前拓扑/周期下的实测值,未验证不同任务周期下的行为; 3. 8B 配置不含诊断 PDO,"对端是否在线"需以数据流本身判断(失配即掉线)。 + +--- + +## 7. 800B 变体的验证方法 + +800B 工程采用**序列号 + 图案 + 反推校验**协议(类 1KB),与 8B 的 XOR 历史环不同,原因是 800B 要同时验证"大载荷每个字节"与"序号处理"两个维度。 + +### 7.1 协议与验证链 + +``` +TwinCAT 发送: aOut1[0..7] = nCycles; aOut1[i] = (nCycles+i) mod 256 + (i ∈ [16..263] ∪ [280..799], 避开 [264..279] 统计字段) +J1900 处理: tx[0..7] = rx_seq*7+3; tx[8..15] = 本地周期号; + tx[264..279] = 抖动/失配/原始 seq; 其余字节原样回显 +TwinCAT 校验: nProcessedSeq = aIn1[0..7] + nReturnedSeq = (nProcessedSeq - 3) / 7 (MOD 7 = 0 才有效) + 逐字节比对 aIn1[i] == (nReturnedSeq + i) mod 256 +``` + +- **反推校验**:J1900 的 f(x)=x·7+3 可逆,PLC 从回传值反推原始发送周期,无需预设管线延迟(实测 lag=4); +- **J1900 侧自检**:ticker 对图案区 [16..255] 做 `(tc_seq+i) mod 256` 独立校验(`mism` 计数),与 PLC 侧校验互为对侧证据。 + +### 7.2 与 8B 方法的取舍 + +| 维度 | 8B(XOR 历史环) | 800B(seq+图案反推) | +|---|---|---| +| 每字节独立性 | 强(每字节独立 XOR) | 中(图案含周期序号,错位立现) | +| 延迟测量 | 直接(nMatchLag) | 直接(nCycles − nReturnedSeq) | +| 大载荷成本 | 历史环 16×8B 比对/周期 | 768 字节比对/周期 | +| 适合 | 小载荷通路证明 | 应用级带宽验证 | + +### 7.3 ADS 快照判读(800B 特有) + +外部 ADS 校验脚本用 `nReturnedSeq` 重建预期图案时,因 `nCycles/nReturnedSeq/aIn1` 分次读取,可能出现 `nReturnedSeq > nCycles`(显示 lag 为负)与全图案"失配"的假象——**以 PLC 内部 1kHz 实时计算的 `bSync` 为唯一权威判据**;ADS 全数组读取仅用于非零覆盖率统计(800B 时应 ≈770+/800)。 + +### 7.4 800B 稳定性判据 + +| 判据 | 通过条件 | 实测 | +|---|---|---| +| J1900 周期完整 | 周期数 = 时长 × 频率 | 600000 / 600.0s ✅ | +| J1900 `mism` | 0 | ✅ | +| PLC `nMismatch` | 运行窗口内零增长 | 295s 窗口 delta=0;90s 确认 delta=0 ✅ | +| 传输错误 | tx/rx = 0 | ✅ | +| 非零覆盖率 | ≈770/800 | 775–776 ✅ | diff --git a/docs/d2000-dev-plan.md b/docs/d2000-dev-plan.md new file mode 100644 index 0000000..33278c5 --- /dev/null +++ b/docs/d2000-dev-plan.md @@ -0,0 +1,122 @@ +# 飞腾 D2000 ↔ EL6695 ↔ TwinCAT 通讯链路 — 开发计划 + +> **版本**: 1.0 +> **日期**: 2026-07-28 +> **状态**: 待启动 +> **前置成果**: J1900 链路全部方法论(8B/800B 设计、验证体系、脚本集、Rust 代码库) + +--- + +## 1. 目标与拓扑 + +在现有 J1900 链路之外,新增一条**并列**通讯链路:飞腾 D2000(ARM64)经 EL6695#2 与 TwinCAT PC 实时数据交换,复用已验证的配置方法与验证体系。 + +``` +TwinCAT PC (169.254.176.217) + │ + ├─ [现有链路] EK1100 → ... → EL6695#1 → J1900 (x86_64, Debian13+RT) ✅ 已验收 + │ + └─ [新增链路] E-bus 扩展或独立网口 → EL6695#2 → 飞腾 D2000 (aarch64) 🆕 + ▲ + 跳板机#2 网口 (SSH 管理通道) +``` + +### 待确认事项(启动前) + +| # | 问题 | 影响 | +|---|---|---| +| Q1 | EL6695#2 挂在哪里?(现有 E-bus 末端 EL9011 前扩展,还是 TwinCAT 第二网口独立段?) | TwinCAT 工程拓扑/网卡规划 | +| Q2 | D2000 的 OS 与内核(麒麟 V10? UOS? Ubuntu? 内核版本? 是否有 PREEMPT_RT?) | RT 性能预期、移植工作量 | +| Q3 | D2000 管理通道:跳板机#2 的地址、SSH 凭据、能否免密 | 自动化部署方式 | +| Q4 | D2000 接 EL6695 的网口(板载 GbE? 独立网卡? 驱动?) | ethercrab 裸 socket 可用性 | + +--- + +## 2. 阶段计划 + +### 阶段 1:环境与可达性(0.5 天) + +| # | 任务 | 产出 | +|---|---|---| +| 1.1 | 经跳板机#2 SSH 到 D2000,采集:`uname -a`、`/etc/os-release`、`/proc/version`、CPU/核数、网口列表(`ip link`)、中断分布(`/proc/interrupts`) | 环境档案 | +| 1.2 | 确认 PREEMPT_RT(`uname -v` 含 rt?)、`chrt`/`mlockall` 可用性 | RT 基线判定 | +| 1.3 | 网络打通:D2000 → EL6695#2 网线;D2000 → 跳板机 管理网 | 拓扑照片/记录 | +| 1.4 | 空口探测:tcpdump/裸 socket 读 ethX,确认能收发二层帧 | 网卡可用性 | + +### 阶段 2:Rust 交叉编译 aarch64(0.5 天) + +| # | 任务 | 备注 | +|---|---|---| +| 2.1 | `rustup target add aarch64-unknown-linux-musl`;`cargo build --target aarch64-unknown-linux-musl --release` | ethercrab 为纯 Rust,理论上零改动;libc 裸 socket(AF_PACKET)在 aarch64 内核同样支持 | +| 2.2 | 链接器:沿用 `RUSTFLAGS="-C linker=rust-lld"`(musl 静态,免去 D2000 上 glibc 版本问题) | musl 静态二进制通吃 Kylin/UOS/Debian | +| 2.3 | 部署 scp → D2000,跑 `--mode probe`(只读扫描) | 首次上机验证 | +| 2.4 | 若 AF_PACKET 在目标内核异常,备选 `aarch64-unknown-linux-gnu` + 检查目标 glibc | 风险预案 | + +### 阶段 3:EL6695 链路开通(0.5–1 天,方法全部现成) + +| # | 任务 | 复用资产 | +|---|---|---| +| 3.1 | TwinCAT 侧:复制 8B 工程为 `TwinCAT_EL6695_Primary_D2000`(或在 EL6695#2 所在段新建拓扑),激活确认 SM2/SM3 | `tc-deploy-retry.ps1` 模式 | +| 3.2 | D2000 侧:`el6695_rt ethX --mode verify` CoE 审计 → ConfigOwner 到 OP | 与 J1900 完全相同的流程 | +| 3.3 | **严格遵守顺序:TwinCAT 激活时 D2000 侧空闲;先激活,后启 ticker**(800B 踩坑 F1) | 教训文档化 | +| 3.4 | ticker 8B XOR 模式联通,PLC bSync=True | `GVL_Sync` 验证体系 | + +### 阶段 4:数据验证与实时性测量(0.5 天) + +| # | 任务 | 判据 | +|---|---|---| +| 4.1 | 8B 逐字节验证(图案+XOR+历史环) | bSync=True,lag 稳定 | +| 4.2 | 10 分钟稳定性 | 60 万周期零错误,nMismatch 零增长 | +| 4.3 | D2000 实时性基线:周期抖动/exec/FPRD/FPWR 统计 | 与 J1900 对照(7µs 抖动基准) | +| 4.4 | 如需 800B:换 `el6695_rt_800b` 对应构建(同源码,常量切换) | 800B 工程现成 | + +### 阶段 5:D2000 RT 调优(按需,0.5–1 天) + +| # | 措施 | 场景 | +|---|---|---| +| 5.1 | IRQ 亲和:管理网口 IRQ 钉到非 RT 核 | 必做 | +| 5.2 | 核隔离 `isolcpus` + ticker 绑核(程序已支持 `--core`) | 推荐 | +| 5.3 | 若无 RT 补丁且抖动不可接受:评估编译 aarch64 PREEMPT_RT 内核(源码 kernel.org + rt patch,交叉编译) | 兜底 | +| 5.4 | 抖动向因分析:hwlatdetect/cyclictest 对照 | 诊断工具 | + +### 阶段 6:文档与归档(0.5 天) + +- 新增 `docs/d2000-bringup.md`(环境档案、移植记录、与 J1900 的性能对照) +- 更新三份主文档的适用范围章节;git 提交 + +--- + +## 3. 复用率评估 + +| 资产 | 复用程度 | +|---|---| +| el6695_rt 源码(main.rs/bridge.rs) | ~100%(唯一改动:默认网口名/可能的字节序检查——ARM 与 x86 同小端,无影响) | +| TwinCAT 工程与 PLC 验证程序 | 100%(复制副本改拓扑) | +| 部署/诊断/监视脚本(20+ 个) | ~90%(J1900 地址替换为 D2000 地址) | +| 配置方法论(SM/PDO/blob/链接/激活顺序) | 100% | + +## 4. 风险登记 + +| # | 风险 | 概率 | 缓解 | +|---|---|---|---| +| R1 | D2000 OS 无 PREEMPT_RT,抖动大(几十~几百 µs) | 高 | 阶段 5.3 自编译 RT 内核;或先测后判,1kHz 场景通常可接受 | +| R2 | 麒麟/UOS 老内核(4.19)与 ethercrab 兼容性 | 低 | musl 静态;AF_PACKET 自 2.2 内核即有 | +| R3 | D2000 网口驱动不支持混杂/裸包或性能差 | 低 | 阶段 1.4 提前探测;备 USB 网卡 | +| R4 | 跳板机#2 到 D2000 的 SSH 不稳定 | 中 | 串口兜底;长会话用 nohup+日志(既有模式) | +| R5 | 双链路并存时 TwinCAT 两张网卡/两段总线资源冲突 | 低 | Q1 拓扑确认后评审 | + +## 5. 里程碑 + +| 里程碑 | 判据 | +|---|---| +| M1 | D2000 SSH 通,环境档案完成(Q1–Q4 全部答案) | +| M2 | aarch64 二进制在 D2000 上 `--mode probe` 发现 EL6695 | +| M3 | 链路 OP,8B bSync=True | +| M4 | 10 分钟零错误 + 抖动基线报告 | +| M5 | 文档归档 + git 提交 | + +## 6. 修订历史 + +| 日期 | 内容 | +|---|---| +| 2026-07-28 | 初版计划 | diff --git a/docs/jitter-analysis.md b/docs/jitter-analysis.md new file mode 100644 index 0000000..6872276 --- /dev/null +++ b/docs/jitter-analysis.md @@ -0,0 +1,69 @@ +# J1900 实时性能偶发最大抖动分析 + +> **日期**: 2026-07-28 +> **对象**: J1900 (Debian 13, Linux 6.6.135-rt74 PREEMPT_RT) 上 el6695_rt ticker 的周期抖动 +> **数据来源**: 1kHz 8B 600s 测试、800B 10 分钟测试、4kHz 测试、800B 5 小时(1800 万周期)测试 + +--- + +## 1. 现象 + +周期抖动(实际周期相对名义周期的偏差)的**均值极小(5–7µs)**,但偶发数百微秒的最大值: + +| 测试 | 样本数 | jitter 均值 | jitter max | +|---|---|---|---| +| 1kHz 8B, 600s | 600,000 | 7.29µs | **10.0µs** | +| 800B, 10 分钟 | 600,000 | 4.89µs | **499.1µs** | +| 4kHz, 120s | 352,917 | 90µs(含滑相) | **1549µs** | +| **800B, 5 小时** | **18,000,000** | **7.39µs** | **41.8µs** | + +关键观察:尖峰幅度**不是系统固有属性**——5 小时静默运行(1800 万样本)最大仅 41.8µs,而活动密集的 10 分钟测试出现 499µs。尖峰与并发系统活动强相关。 + +## 2. 诊断证据 + +### 2.1 系统环境(J1900 实测) + +| 项 | 值 | 影响 | +|---|---|---| +| 根文件系统 | **NFS**:`192.168.68.1:/j1900-node2/rootfs`(TwinCAT PC 做 NFS 服务器) | 一切文件访问走网络 | +| /tmp | tmpfs(内存) | ticker 日志不落盘 ✓ | +| 设备中断 | eth0-rx 30 万次全在 CPU0;ahci/ttyS0 也在 CPU0/1 | ticker 核 CPU2 无设备 IRQ ✓ | +| 调度 | ticker 绑定核 2,SCHED_FIFO 80,mlockall,栈预缺页 | 基础保障已做 | +| dmesg | 无热事件、无异常;sshd 栈深度提示(良性) | 排除过热 | + +### 2.2 相关性 + +尖峰最频繁的测试(10 分钟、4kHz)期间,Windows 侧正密集进行 TwinCAT 构建/激活与 SSH 会话;5 小时静默期几乎无尖峰。 + +## 3. 原因排序 + +| # | 原因 | 机理 | 符合度 | +|---|---|---|---| +| 1 | **NFS 根文件系统停顿** | Windows NFS 服务器忙(VS 构建、大文件 I/O)→ J1900 侧触盘进程阻塞 → 网络 softirq、内存回收压力波及 RT 核 | ★★★★ 主因(静默期 max 仅 41.8µs) | +| 2 | **SMI 系统管理中断** | J1900(Bay Trail Atom)BIOS 的 USB legacy / 嵌入式控制器 / 热管理 SMI,100–500µs 不可屏蔽、OS 不可见 | ★★★ 次嫌疑(偶发形态吻合,需 hwlatdetect 证实) | +| 3 | **深 C 状态退出** | Bay Trail C6 退出延迟 ~100–200µs;周期睡眠偶入深态 | ★★(均值仅 5–7µs,多数时间为浅态) | + +## 4. 验证方法 + +```bash +# 区分硬件(SMI) vs 软件(NFS/OS)延迟 — 内核 6.6 自带 rtla: +rtla hwlatdetect --cpus 2 --duration 60 +# 判定: hwlatdetect 报数百微秒 => SMI/硬件; 干净 => NFS/OS 软件因素 + +# 辅助: cyclictest 对照(无 EtherCAT 负载时测量裸调度抖动) +cyclictest -m -p 80 -i 1000 -l 60000 -a 2 +``` + +## 5. 缓解建议(按收益排序) + +| # | 措施 | 预期效果 | +|---|---|---| +| 1 | gateway 目录与日志移出 NFS(tmpfs 或本地存储) | 消除主因 | +| 2 | BIOS 关闭 USB Legacy、不需要的 EC 功能 | 消 SMI 尖峰 | +| 3 | 内核启动参数 `isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3` | 完全隔离 RT 核 | +| 4 | `intel_idle.max_cstate=1` 限制最深 C 状态 | 消 C6 退出尖峰(功耗换延迟) | +| 5 | IRQ 亲和性复核(保持设备 IRQ 远离核 2/3) | 已满足,固化 | + +## 6. 结论 + +J1900 基础实时性良好(均值 5–7µs,p99 < 10µs,1800 万周期零丢帧)。偶发数百微秒尖峰的主因是 **NFS 根文件系统在 Windows 服务器繁忙时的停顿**;SMI 为次要嫌疑,可用 `rtla hwlatdetect` 一测定论。对当前 EL6695 数据通道无实质影响(尖峰只占 ~0.0001%,且 1kHz 预算 1ms 内 exec 最大 400µs 仍有富余)。 diff --git a/monitor_5h.csv b/monitor_5h.csv new file mode 100644 index 0000000..92aa75c --- /dev/null +++ b/monitor_5h.csv @@ -0,0 +1,301 @@ +timestamp,nCycles,bSync,nReturnedSeq,nMismatch,nProcMismatch +2026-07-28 02:13:04,1825622,False,0,648246528,844072 +2026-07-28 02:14:04,1885662,False,0,694359552,904116 +2026-07-28 02:15:04,1945680,False,0,740451072,964130 +2026-07-28 02:16:04,2005694,False,0,786541824,1024144 +2026-07-28 02:17:04,2065708,True,2065707,789764352,1028339 +2026-07-28 02:18:04,2125730,True,2125727,789764352,1028339 +2026-07-28 02:19:04,2185753,True,2185751,789764352,1028339 +2026-07-28 02:20:04,2245766,True,2245766,789764352,1028339 +2026-07-28 02:21:04,2305794,True,2305794,789764352,1028339 +2026-07-28 02:22:04,2365820,True,2365819,789764352,1028339 +2026-07-28 02:23:04,2425842,True,2425840,789764352,1028339 +2026-07-28 02:24:04,2485861,True,2485861,789764352,1028339 +2026-07-28 02:25:04,2545882,True,2545880,789764352,1028339 +2026-07-28 02:26:04,2605900,True,2605898,789764352,1028339 +2026-07-28 02:27:04,2665917,True,2665914,789764352,1028339 +2026-07-28 02:28:04,2725936,True,2725934,789764352,1028339 +2026-07-28 02:29:04,2785961,True,2785960,789764352,1028339 +2026-07-28 02:30:04,2845983,True,2845981,789764352,1028339 +2026-07-28 02:31:04,2906006,True,2906006,789764352,1028339 +2026-07-28 02:32:05,2966030,True,2966028,789764352,1028339 +2026-07-28 02:33:05,3026050,True,3026048,789764352,1028339 +2026-07-28 02:34:05,3086068,True,3086066,789764352,1028339 +2026-07-28 02:35:05,3146087,True,3146087,789764352,1028339 +2026-07-28 02:36:05,3206113,True,3206111,789764352,1028339 +2026-07-28 02:37:05,3266139,True,3266139,789764352,1028339 +2026-07-28 02:38:05,3326161,True,3326159,789764352,1028339 +2026-07-28 02:39:05,3386183,True,3386181,789764352,1028339 +2026-07-28 02:40:05,3446206,True,3446206,789764352,1028339 +2026-07-28 02:41:05,3506232,True,3506232,789764352,1028339 +2026-07-28 02:42:05,3566251,True,3566249,789764352,1028339 +2026-07-28 02:43:05,3626266,True,3626264,789764352,1028339 +2026-07-28 02:44:05,3686283,True,3686281,789764352,1028339 +2026-07-28 02:45:05,3746311,True,3746310,789764352,1028339 +2026-07-28 02:46:05,3806331,True,3806329,789764352,1028339 +2026-07-28 02:47:05,3866346,True,3866344,789764352,1028339 +2026-07-28 02:48:05,3926364,True,3926364,789764352,1028339 +2026-07-28 02:49:05,3986384,True,3986382,789764352,1028339 +2026-07-28 02:50:05,4046400,True,4046400,789764352,1028339 +2026-07-28 02:51:05,4106421,True,4106419,789764352,1028339 +2026-07-28 02:52:05,4166439,True,4166439,789764352,1028339 +2026-07-28 02:53:05,4226466,True,4226466,789764352,1028339 +2026-07-28 02:54:05,4286494,True,4286493,789764352,1028339 +2026-07-28 02:55:05,4346518,True,4346518,789764352,1028339 +2026-07-28 02:56:05,4406539,True,4406537,789764352,1028339 +2026-07-28 02:57:05,4466565,True,4466565,789764352,1028339 +2026-07-28 02:58:05,4526588,True,4526586,789764352,1028339 +2026-07-28 02:59:05,4586605,True,4586603,789764352,1028339 +2026-07-28 03:00:05,4646620,True,4646618,789764352,1028339 +2026-07-28 03:01:05,4706637,True,4706636,789764352,1028339 +2026-07-28 03:02:05,4766661,True,4766661,789764352,1028339 +2026-07-28 03:03:05,4826684,True,4826681,789764352,1028339 +2026-07-28 03:04:05,4886701,True,4886699,789764352,1028339 +2026-07-28 03:05:05,4946724,True,4946724,789764352,1028339 +2026-07-28 03:06:05,5006746,True,5006744,789764352,1028339 +2026-07-28 03:07:05,5066761,True,5066759,789764352,1028339 +2026-07-28 03:08:05,5126775,True,5126773,789764352,1028339 +2026-07-28 03:09:05,5186795,True,5186795,789764352,1028339 +2026-07-28 03:10:05,5246822,True,5246822,789764352,1028339 +2026-07-28 03:11:05,5306841,True,5306839,789764352,1028339 +2026-07-28 03:12:05,5366855,True,5366852,789764352,1028339 +2026-07-28 03:13:05,5426873,True,5426870,789764352,1028339 +2026-07-28 03:14:05,5486893,True,5486891,789764352,1028339 +2026-07-28 03:15:05,5546919,True,5546919,789764352,1028339 +2026-07-28 03:16:05,5606940,True,5606938,789764352,1028339 +2026-07-28 03:17:05,5666962,True,5666962,789764352,1028339 +2026-07-28 03:18:05,5727000,True,5727000,789764352,1028339 +2026-07-28 03:19:05,5787020,True,5787018,789764352,1028339 +2026-07-28 03:20:06,5847036,True,5847034,789764352,1028339 +2026-07-28 03:21:06,5907050,True,5907047,789764352,1028339 +2026-07-28 03:22:06,5967067,True,5967067,789764352,1028339 +2026-07-28 03:23:06,6027086,True,6027084,789764352,1028339 +2026-07-28 03:24:06,6087101,True,6087099,789764352,1028339 +2026-07-28 03:25:06,6147119,True,6147117,789764352,1028339 +2026-07-28 03:26:06,6207138,True,6207136,789764352,1028339 +2026-07-28 03:27:06,6267161,True,6267161,789764352,1028339 +2026-07-28 03:28:06,6327180,True,6327178,789764352,1028339 +2026-07-28 03:29:06,6387197,True,6387195,789764352,1028339 +2026-07-28 03:30:06,6447216,True,6447215,789764352,1028339 +2026-07-28 03:31:06,6507241,True,6507241,789764352,1028339 +2026-07-28 03:32:06,6567266,True,6567266,789764352,1028339 +2026-07-28 03:33:06,6627289,True,6627287,789764352,1028339 +2026-07-28 03:34:06,6687315,True,6687315,789764352,1028339 +2026-07-28 03:35:06,6747338,True,6747336,789764352,1028339 +2026-07-28 03:36:06,6807356,True,6807354,789764352,1028339 +2026-07-28 03:37:06,6867377,True,6867375,789764352,1028339 +2026-07-28 03:38:06,6927395,True,6927393,789764352,1028339 +2026-07-28 03:39:06,6987416,True,6987416,789764352,1028339 +2026-07-28 03:40:06,7047439,True,7047436,789764352,1028339 +2026-07-28 03:41:06,7107458,True,7107456,789764352,1028339 +2026-07-28 03:42:06,7167475,True,7167473,789764352,1028339 +2026-07-28 03:43:06,7227497,True,7227497,789764352,1028339 +2026-07-28 03:44:06,7287521,True,7287519,789764352,1028339 +2026-07-28 03:45:06,7347541,True,7347539,789764352,1028339 +2026-07-28 03:46:06,7407560,True,7407558,789764352,1028339 +2026-07-28 03:47:06,7467578,True,7467577,789764352,1028339 +2026-07-28 03:48:06,7527598,True,7527596,789764352,1028339 +2026-07-28 03:49:06,7587620,True,7587619,789764352,1028339 +2026-07-28 03:50:06,7647641,True,7647639,789764352,1028339 +2026-07-28 03:51:06,7707657,True,7707655,789764352,1028339 +2026-07-28 03:52:06,7767680,True,7767678,789764352,1028339 +2026-07-28 03:53:06,7827700,True,7827700,789764352,1028339 +2026-07-28 03:54:06,7887729,True,7887729,789764352,1028339 +2026-07-28 03:55:06,7947752,True,7947750,789764352,1028339 +2026-07-28 03:56:06,8007768,True,8007766,789764352,1028339 +2026-07-28 03:57:06,8067790,True,8067788,789764352,1028339 +2026-07-28 03:58:06,8127817,True,8127816,789764352,1028339 +2026-07-28 03:59:06,8187839,True,8187838,789764352,1028339 +2026-07-28 04:00:06,8247866,True,8247866,789764352,1028339 +2026-07-28 04:01:06,8307889,True,8307887,789764352,1028339 +2026-07-28 04:02:06,8367908,True,8367906,789764352,1028339 +2026-07-28 04:03:06,8427927,True,8427927,789764352,1028339 +2026-07-28 04:04:06,8487947,True,8487945,789764352,1028339 +2026-07-28 04:05:06,8547969,True,8547969,789764352,1028339 +2026-07-28 04:06:06,8607993,True,8607992,789764352,1028339 +2026-07-28 04:07:06,8668018,True,8668017,789764352,1028339 +2026-07-28 04:08:06,8728040,True,8728040,789764352,1028339 +2026-07-28 04:09:07,8788059,True,8788057,789764352,1028339 +2026-07-28 04:10:07,8848075,True,8848073,789764352,1028339 +2026-07-28 04:11:07,8908092,True,8908090,789764352,1028339 +2026-07-28 04:12:07,8968110,True,8968108,789764352,1028339 +2026-07-28 04:13:07,9028132,True,9028132,789764352,1028339 +2026-07-28 04:14:07,9088155,True,9088153,789764352,1028339 +2026-07-28 04:15:07,9148175,True,9148174,789764352,1028339 +2026-07-28 04:16:07,9208198,True,9208195,789764352,1028339 +2026-07-28 04:17:07,9268217,True,9268215,789764352,1028339 +2026-07-28 04:18:07,9328235,True,9328233,789764352,1028339 +2026-07-28 04:19:07,9388256,True,9388256,789764352,1028339 +2026-07-28 04:20:07,9448276,True,9448274,789764352,1028339 +2026-07-28 04:21:07,9508295,True,9508293,789764352,1028339 +2026-07-28 04:22:07,9568317,True,9568316,789764352,1028339 +2026-07-28 04:23:07,9628343,True,9628343,789764352,1028339 +2026-07-28 04:24:07,9688364,True,9688362,789764352,1028339 +2026-07-28 04:25:07,9748382,True,9748379,789764352,1028339 +2026-07-28 04:26:07,9808397,True,9808395,789764352,1028339 +2026-07-28 04:27:07,9868412,True,9868410,789764352,1028339 +2026-07-28 04:28:07,9928428,True,9928426,789764352,1028339 +2026-07-28 04:29:07,9988442,True,9988440,789764352,1028339 +2026-07-28 04:30:07,10048458,True,10048456,789764352,1028339 +2026-07-28 04:31:07,10108475,True,10108473,789764352,1028339 +2026-07-28 04:32:07,10168490,True,10168488,789764352,1028339 +2026-07-28 04:33:07,10228506,True,10228504,789764352,1028339 +2026-07-28 04:34:07,10288522,True,10288520,789764352,1028339 +2026-07-28 04:35:07,10348545,True,10348545,789764352,1028339 +2026-07-28 04:36:07,10408568,True,10408566,789764352,1028339 +2026-07-28 04:37:07,10468588,True,10468586,789764352,1028339 +2026-07-28 04:38:07,10528606,True,10528604,789764352,1028339 +2026-07-28 04:39:07,10588623,True,10588621,789764352,1028339 +2026-07-28 04:40:07,10648642,True,10648641,789764352,1028339 +2026-07-28 04:41:07,10708667,True,10708667,789764352,1028339 +2026-07-28 04:42:07,10768686,True,10768684,789764352,1028339 +2026-07-28 04:43:07,10828711,True,10828710,789764352,1028339 +2026-07-28 04:44:07,10888733,True,10888730,789764352,1028339 +2026-07-28 04:45:07,10948755,True,10948754,789764352,1028339 +2026-07-28 04:46:07,11008776,True,11008774,789764352,1028339 +2026-07-28 04:47:07,11068798,True,11068798,789764352,1028339 +2026-07-28 04:48:07,11128823,True,11128821,789764352,1028339 +2026-07-28 04:49:07,11188847,True,11188847,789764352,1028339 +2026-07-28 04:50:07,11248870,True,11248868,789764352,1028339 +2026-07-28 04:51:07,11308890,True,11308888,789764352,1028339 +2026-07-28 04:52:07,11368912,True,11368910,789764352,1028339 +2026-07-28 04:53:07,11428935,True,11428934,789764352,1028339 +2026-07-28 04:54:07,11488961,True,11488959,789764352,1028339 +2026-07-28 04:55:07,11548978,True,11548976,789764352,1028339 +2026-07-28 04:56:07,11608993,True,11608991,789764352,1028339 +2026-07-28 04:57:07,11669013,True,11669011,789764352,1028339 +2026-07-28 04:58:07,11729031,True,11729030,789764352,1028339 +2026-07-28 04:59:07,11789060,True,11789060,789764352,1028339 +2026-07-28 05:00:08,11849083,True,11849081,789764352,1028339 +2026-07-28 05:01:08,11909100,True,11909098,789764352,1028339 +2026-07-28 05:02:08,11969117,True,11969114,789764352,1028339 +2026-07-28 05:03:08,12029135,True,12029133,789764352,1028339 +2026-07-28 05:04:08,12089154,True,12089152,789764352,1028339 +2026-07-28 05:05:08,12149172,True,12149170,789764352,1028339 +2026-07-28 05:06:08,12209191,True,12209189,789764352,1028339 +2026-07-28 05:07:08,12269215,True,12269215,789764352,1028339 +2026-07-28 05:08:08,12329245,True,12329245,789764352,1028339 +2026-07-28 05:09:08,12389273,True,12389273,789764352,1028339 +2026-07-28 05:10:08,12449292,True,12449290,789764352,1028339 +2026-07-28 05:11:08,12509311,True,12509309,789764352,1028339 +2026-07-28 05:12:08,12569337,True,12569337,789764352,1028339 +2026-07-28 05:13:08,12629364,True,12629364,789764352,1028339 +2026-07-28 05:14:08,12689386,True,12689384,789764352,1028339 +2026-07-28 05:15:08,12749405,True,12749403,789764352,1028339 +2026-07-28 05:16:08,12809427,True,12809427,789764352,1028339 +2026-07-28 05:17:08,12869455,True,12869455,789764352,1028339 +2026-07-28 05:18:08,12929478,True,12929476,789764352,1028339 +2026-07-28 05:19:08,12989502,True,12989502,789764352,1028339 +2026-07-28 05:20:08,13049528,True,13049526,789764352,1028339 +2026-07-28 05:21:08,13109545,True,13109542,789764352,1028339 +2026-07-28 05:22:08,13169562,True,13169560,789764352,1028339 +2026-07-28 05:23:08,13229578,True,13229576,789764352,1028339 +2026-07-28 05:24:08,13289595,True,13289593,789764352,1028339 +2026-07-28 05:25:08,13349611,True,13349609,789764352,1028339 +2026-07-28 05:26:08,13409629,True,13409629,789764352,1028339 +2026-07-28 05:27:08,13469651,True,13469649,789764352,1028339 +2026-07-28 05:28:08,13529673,True,13529673,789764352,1028339 +2026-07-28 05:29:08,13589702,True,13589701,789764352,1028339 +2026-07-28 05:30:08,13649725,True,13649723,789764352,1028339 +2026-07-28 05:31:08,13709741,True,13709740,789764352,1028339 +2026-07-28 05:32:08,13769765,True,13769763,789764352,1028339 +2026-07-28 05:33:08,13829783,True,13829781,789764352,1028339 +2026-07-28 05:34:08,13889802,True,13889800,789764352,1028339 +2026-07-28 05:35:08,13949825,True,13949825,789764352,1028339 +2026-07-28 05:36:08,14009845,True,14009843,789764352,1028339 +2026-07-28 05:37:08,14069859,True,14069857,789764352,1028339 +2026-07-28 05:38:08,14129878,True,14129876,789764352,1028339 +2026-07-28 05:39:08,14189894,True,14189891,789764352,1028339 +2026-07-28 05:40:08,14249915,True,14249913,789764352,1028339 +2026-07-28 05:41:08,14309933,True,14309931,789764352,1028339 +2026-07-28 05:42:08,14369958,True,14369958,789764352,1028339 +2026-07-28 05:43:08,14429982,True,14429982,789764352,1028339 +2026-07-28 05:44:08,14490000,True,14489998,789764352,1028339 +2026-07-28 05:45:08,14550017,True,14550015,789764352,1028339 +2026-07-28 05:46:08,14610035,True,14610033,789764352,1028339 +2026-07-28 05:47:08,14670056,True,14670056,789764352,1028339 +2026-07-28 05:48:09,14730086,True,14730085,789764352,1028339 +2026-07-28 05:49:09,14790110,True,14790109,789764352,1028339 +2026-07-28 05:50:09,14850133,True,14850133,789764352,1028339 +2026-07-28 05:51:09,14910155,True,14910153,789764352,1028339 +2026-07-28 05:52:09,14970174,True,14970172,789764352,1028339 +2026-07-28 05:53:09,15030193,True,15030191,789764352,1028339 +2026-07-28 05:54:09,15090216,True,15090216,789764352,1028339 +2026-07-28 05:55:09,15150245,True,15150245,789764352,1028339 +2026-07-28 05:56:09,15210267,True,15210265,789764352,1028339 +2026-07-28 05:57:09,15270285,True,15270282,789764352,1028339 +2026-07-28 05:58:09,15330308,True,15330308,789764352,1028339 +2026-07-28 05:59:09,15390329,True,15390327,789764352,1028339 +2026-07-28 06:00:09,15450348,True,15450348,789764352,1028339 +2026-07-28 06:01:09,15510376,True,15510376,789764352,1028339 +2026-07-28 06:02:09,15570397,True,15570395,789764352,1028339 +2026-07-28 06:03:09,15630422,True,15630421,789764352,1028339 +2026-07-28 06:04:09,15690443,True,15690441,789764352,1028339 +2026-07-28 06:05:09,15750461,True,15750459,789764352,1028339 +2026-07-28 06:06:09,15810489,True,15810488,789764352,1028339 +2026-07-28 06:07:09,15870512,True,15870510,789764352,1028339 +2026-07-28 06:08:09,15930533,True,15930533,789764352,1028339 +2026-07-28 06:09:09,15990556,True,15990554,789764352,1028339 +2026-07-28 06:10:09,16050574,True,16050572,789764352,1028339 +2026-07-28 06:11:09,16110587,True,16110585,789764352,1028339 +2026-07-28 06:12:09,16170604,True,16170604,789764352,1028339 +2026-07-28 06:13:09,16230627,True,16230627,789764352,1028339 +2026-07-28 06:14:09,16290650,True,16290648,789764352,1028339 +2026-07-28 06:15:09,16350673,True,16350672,789764352,1028339 +2026-07-28 06:16:09,16410698,True,16410696,789764352,1028339 +2026-07-28 06:17:09,16470717,True,16470715,789764352,1028339 +2026-07-28 06:18:09,16530735,True,16530733,789764352,1028339 +2026-07-28 06:19:09,16590757,True,16590757,789764352,1028339 +2026-07-28 06:20:09,16650780,True,16650778,789764352,1028339 +2026-07-28 06:21:09,16710798,True,16710798,789764352,1028339 +2026-07-28 06:22:09,16770825,True,16770824,789764352,1028339 +2026-07-28 06:23:09,16830844,True,16830842,789764352,1028339 +2026-07-28 06:24:09,16890861,True,16890859,789764352,1028339 +2026-07-28 06:25:09,16950878,True,16950875,789764352,1028339 +2026-07-28 06:26:09,17010896,True,17010894,789764352,1028339 +2026-07-28 06:27:09,17070912,True,17070910,789764352,1028339 +2026-07-28 06:28:09,17130929,True,17130927,789764352,1028339 +2026-07-28 06:29:09,17190947,True,17190945,789764352,1028339 +2026-07-28 06:30:09,17250966,True,17250964,789764352,1028339 +2026-07-28 06:31:09,17310985,True,17310983,789764352,1028339 +2026-07-28 06:32:09,17371003,True,17371001,789764352,1028339 +2026-07-28 06:33:09,17431023,True,17431021,789764352,1028339 +2026-07-28 06:34:09,17491047,True,17491046,789764352,1028339 +2026-07-28 06:35:09,17551074,True,17551074,789764352,1028339 +2026-07-28 06:36:10,17611098,True,17611096,789764352,1028339 +2026-07-28 06:37:10,17671122,True,17671122,789764352,1028339 +2026-07-28 06:38:10,17731145,True,17731143,789764352,1028339 +2026-07-28 06:39:10,17791173,True,17791172,789764352,1028339 +2026-07-28 06:40:10,17851193,True,17851192,789764352,1028339 +2026-07-28 06:41:10,17911221,True,17911221,789764352,1028339 +2026-07-28 06:42:10,17971243,True,17971241,789764352,1028339 +2026-07-28 06:43:10,18031261,True,18031258,789764352,1028339 +2026-07-28 06:44:10,18091285,True,18091285,789764352,1028339 +2026-07-28 06:45:10,18151306,True,18151304,789764352,1028339 +2026-07-28 06:46:10,18211322,True,18211320,789764352,1028339 +2026-07-28 06:47:10,18271342,True,18271340,789764352,1028339 +2026-07-28 06:48:10,18331360,True,18331358,789764352,1028339 +2026-07-28 06:49:10,18391383,True,18391383,789764352,1028339 +2026-07-28 06:50:10,18451405,True,18451403,789764352,1028339 +2026-07-28 06:51:10,18511423,True,18511421,789764352,1028339 +2026-07-28 06:52:10,18571443,True,18571442,789764352,1028339 +2026-07-28 06:53:10,18631472,True,18631472,789764352,1028339 +2026-07-28 06:54:10,18691494,True,18691492,789764352,1028339 +2026-07-28 06:55:10,18751520,True,18751520,789764352,1028339 +2026-07-28 06:56:10,18811545,True,18811545,789764352,1028339 +2026-07-28 06:57:10,18871568,True,18871566,789764352,1028339 +2026-07-28 06:58:10,18931586,True,18931584,789764352,1028339 +2026-07-28 06:59:10,18991609,True,18991608,789764352,1028339 +2026-07-28 07:00:10,19051633,True,19051632,789764352,1028339 +2026-07-28 07:01:10,19111652,True,19111649,789764352,1028339 +2026-07-28 07:02:10,19171669,True,19171667,789764352,1028339 +2026-07-28 07:03:10,19231688,True,19231686,789764352,1028339 +2026-07-28 07:04:10,19291705,True,19291703,789764352,1028339 +2026-07-28 07:05:10,19351730,True,19351730,789764352,1028339 +2026-07-28 07:06:10,19411758,True,19411758,789764352,1028339 +2026-07-28 07:07:10,19471781,True,19471779,789764352,1028339 +2026-07-28 07:08:10,19531799,True,19531797,789764352,1028339 +2026-07-28 07:09:10,19591815,True,19591815,789764352,1028339 +2026-07-28 07:10:10,19651838,True,19651837,789764352,1028339 +2026-07-28 07:11:10,19711862,True,19711862,789764352,1028339 +2026-07-28 07:12:10,19771883,True,19771881,789764352,1028339 diff --git a/scripts/tc-ads-mismatch-delta.ps1 b/scripts/tc-ads-mismatch-delta.ps1 new file mode 100644 index 0000000..4bd5aa1 --- /dev/null +++ b/scripts/tc-ads-mismatch-delta.ps1 @@ -0,0 +1,15 @@ +#Requires -RunAsAdministrator +Add-Type -Path 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll' +$c = New-Object TwinCAT.Ads.TcAdsClient +$c.Connect('169.254.176.217.1.1', 851) +$h = $c.CreateVariableHandle('GVL_Sync.nMismatch') +$h2 = $c.CreateVariableHandle('GVL_Sync.bSync') +$m1 = $c.ReadAny($h, [int]); $b1 = $c.ReadAny($h2, [bool]) +Write-Host ("t+15s : bSync=$b1 nMismatch=$m1") +Start-Sleep 30 +$m2 = $c.ReadAny($h, [int]); $b2 = $c.ReadAny($h2, [bool]) +Write-Host ("t+45s : bSync=$b2 nMismatch=$m2 delta=$($m2-$m1)") +Start-Sleep 30 +$m3 = $c.ReadAny($h, [int]); $b3 = $c.ReadAny($h2, [bool]) +Write-Host ("t+75s : bSync=$b3 nMismatch=$m3 delta=$($m3-$m2)") +$c.Dispose() diff --git a/scripts/tc-ads-verify-800b.ps1 b/scripts/tc-ads-verify-800b.ps1 new file mode 100644 index 0000000..742779b --- /dev/null +++ b/scripts/tc-ads-verify-800b.ps1 @@ -0,0 +1,48 @@ +#Requires -RunAsAdministrator +#Requires -Version 5.1 +$ErrorActionPreference = 'Continue' +$adsDll = 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll' +Add-Type -Path $adsDll -ErrorAction Stop +$c = New-Object TwinCAT.Ads.TcAdsClient +$c.Connect('169.254.176.217.1.1', 851) +function ReadSym($name, $type) { + $h = $c.CreateVariableHandle($name) + $v = $c.ReadAny($h, $type) + $c.DeleteVariableHandle($h) + return $v +} +function ReadBytes($name, $len) { + $h = $c.CreateVariableHandle($name) + $arr = $c.ReadAny($h, [byte[]], @($len)) + $c.DeleteVariableHandle($h) + return $arr +} +$nc = ReadSym 'GVL_Sync.nCycles' ([uint64]) +$bs = ReadSym 'GVL_Sync.bSync' ([bool]) +$nrs = ReadSym 'GVL_Sync.nReturnedSeq' ([uint64]) +$nm = ReadSym 'GVL_Sync.nMismatch' ([int]) +$in = ReadBytes 'GVL_Sync.aIn1' 800 +$out = ReadBytes 'GVL_Sync.aOut1' 800 + +$nzIn = ($in | Where-Object { $_ -ne 0 }).Count +$nzOut = ($out | Where-Object { $_ -ne 0 }).Count +Write-Host ("nCycles={0} bSync={1} nReturnedSeq={2} lag={3} nMismatch={4}" -f $nc, $bs, $nrs, ($nc - $nrs), $nm) +Write-Host ("aIn1 nonzero = {0}/800" -f $nzIn) +Write-Host ("aOut1 nonzero = {0}/800" -f $nzOut) + +# per-byte pattern check against nReturnedSeq: expected[i] = (nReturnedSeq + i) mod 256, pattern region +$bad = 0 +$badIdx = @() +for ($i = 16; $i -lt 800; $i++) { + if ($i -ge 264 -and $i -le 279) { continue } + $exp = [byte](($nrs + $i) % 256) + if ($in[$i] -ne $exp) { $bad++; if ($badIdx.Count -lt 8) { $badIdx += $i } } +} +Write-Host ("pattern[16..799 excl 264..279] mismatches = {0} {1}" -f $bad, $(if ($badIdx.Count) { 'at ' + ($badIdx -join ',') } else { '' })) + +# show window samples at several offsets +foreach ($off in @(0, 16, 128, 256, 288, 512, 768)) { + $seg = [BitConverter]::ToString($in[$off..($off + 7)]) + Write-Host ("aIn1[{0,3}..{1,3}] = {2}" -f $off, ($off + 7), $seg) +} +$c.Dispose() diff --git a/scripts/tc-deploy-retry-800b.ps1 b/scripts/tc-deploy-retry-800b.ps1 new file mode 100644 index 0000000..f9ec601 --- /dev/null +++ b/scripts/tc-deploy-retry-800b.ps1 @@ -0,0 +1,61 @@ +#Requires -RunAsAdministrator +#Requires -Version 5.1 +$ErrorActionPreference = 'Continue' + +function Invoke-Com { + param([scriptblock]$Fn, [int]$Retries = 30, [int]$DelayMs = 2000) + for ($i = 0; $i -lt $Retries; $i++) { + try { return & $Fn } catch { + $hr = $_.Exception.HResult + if ($hr -eq -2147418111) { Start-Sleep -Milliseconds $DelayMs; continue } + throw + } + } + throw "COM call rejected $Retries times (RPC_E_CALL_REJECTED)" +} + +Write-Host "=== Deploy 8B with RPC retry ===" +$dte = Invoke-Com { New-Object -ComObject TcXaeShell.DTE.17.0 } +Invoke-Com { $dte.MainWindow.Visible = $true } | Out-Null +try { Invoke-Com { $dte.SuppressUI = $false } | Out-Null } catch {} +Invoke-Com { $dte.Solution.Open('C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary_800B\TwinCATProject1.sln') } | Out-Null +Write-Host "solution opened" + +$sm = $null +for ($i = 0; $i -lt 40; $i++) { + try { $sm = Invoke-Com { $dte.Solution.Projects.Item(1).Object } -Retries 3 -DelayMs 3000; if ($sm) { break } } catch { Write-Host " waiting sysmgr..." } +} +if (-not $sm) { Write-Host "FATAL: no sysmgr"; exit 1 } +Write-Host "sysmgr OK" +Invoke-Com { $sm.SetTargetNetId('169.254.176.217.1.1') } | Out-Null + +Write-Host "building..." +try { Invoke-Com { $dte.ExecuteCommand('Build.BuildSolution') } -Retries 10 | Out-Null } catch { Write-Host "build cmd: $_" } +# wait for build done (BuildState 3 = done) +$done = $false +for ($i = 0; $i -lt 90; $i++) { + try { + $bs = Invoke-Com { $dte.Solution.SolutionBuild.BuildState } -Retries 5 -DelayMs 3000 + $bi = Invoke-Com { $dte.Solution.SolutionBuild.LastBuildInfo } -Retries 5 -DelayMs 1000 + if ($bs -eq 3) { Write-Host "build done (LastBuildInfo=$bi) after $($i*2)s"; $done = $true; break } + } catch { Write-Host " build poll err: $_" } + Start-Sleep -Seconds 2 +} +if (-not $done) { Write-Host "WARN: build state never reached done" } + +# dump build errors if any +try { + $ew = $dte.ToolWindows.ErrorList + Write-Host "error list check skipped" +} catch {} + +Write-Host "activating..." +Invoke-Com { $sm.ActivateConfiguration() } -Retries 30 -DelayMs 3000 | Out-Null +Write-Host "activated" +Start-Sleep 10 +Write-Host "restarting runtime..." +Invoke-Com { $sm.StartRestartTwinCAT() } -Retries 30 -DelayMs 3000 | Out-Null +Write-Host "restart issued" +Start-Sleep 12 +try { Write-Host "IsTwinCATStarted=$($sm.IsTwinCATStarted)" } catch {} +Write-Host "=== done ===" diff --git a/scripts/tc-monitor-5h.ps1 b/scripts/tc-monitor-5h.ps1 new file mode 100644 index 0000000..fb50e8a --- /dev/null +++ b/scripts/tc-monitor-5h.ps1 @@ -0,0 +1,30 @@ +#Requires -RunAsAdministrator +# 5-hour stability monitor: logs PLC sync metrics to CSV every 60s. +$ErrorActionPreference = 'Continue' +Add-Type -Path 'C:\Program Files (x86)\Beckhoff\TwinCAT\3.1\Components\Base\v170\TwinCAT.Ads.dll' +$logFile = 'C:\Users\tonycao\work\ethercat-linux\monitor_5h.csv' +'timestamp,nCycles,bSync,nReturnedSeq,nMismatch,nProcMismatch' | Out-File $logFile -Encoding utf8 +$start = Get-Date +for ($i = 0; $i -lt 300; $i++) { + try { + $c = New-Object TwinCAT.Ads.TcAdsClient + $c.Connect('169.254.176.217.1.1', 851) + $h1 = $c.CreateVariableHandle('GVL_Sync.nCycles') + $h2 = $c.CreateVariableHandle('GVL_Sync.bSync') + $h3 = $c.CreateVariableHandle('GVL_Sync.nReturnedSeq') + $h4 = $c.CreateVariableHandle('GVL_Sync.nMismatch') + $h5 = $c.CreateVariableHandle('GVL_Sync.nProcMismatch') + $nc = $c.ReadAny($h1, [uint64]) + $bs = $c.ReadAny($h2, [bool]) + $nrs = $c.ReadAny($h3, [uint64]) + $nm = $c.ReadAny($h4, [int]) + $npm = $c.ReadAny($h5, [uint32]) + $c.Dispose() + $ts = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') + "$ts,$nc,$bs,$nrs,$nm,$npm" | Out-File $logFile -Append -Encoding utf8 + } catch { + $ts = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') + "$ts,ERROR,,,," | Out-File $logFile -Append -Encoding utf8 + } + Start-Sleep -Seconds 60 +} diff --git a/scripts/tc-relink-800b.ps1 b/scripts/tc-relink-800b.ps1 new file mode 100644 index 0000000..b62c176 --- /dev/null +++ b/scripts/tc-relink-800b.ps1 @@ -0,0 +1,62 @@ +#Requires -RunAsAdministrator +$ErrorActionPreference = "Continue" +$dte = New-Object -ComObject TcXaeShell.DTE.17.0 +$dte.MainWindow.Visible = $true +try { $dte.SuppressUI = $false } catch {} +$dte.Solution.Open("C:\Users\tonycao\work\twincat3-auto-cli\TwinCAT_EL6695_Primary_800B\TwinCATProject1.sln") +Start-Sleep 20 +$sm = $null +for ($i = 0; $i -lt 20; $i++) { + try { $sm = $dte.Solution.Projects.Item(1).Object; if ($sm) { break } } catch {} + Start-Sleep 3 +} +if (-not $sm) { Write-Host "no sysmgr"; exit 1 } +try { $sm.SetTargetNetId("169.254.176.217.1.1") } catch {} +Start-Sleep 2 + +$box = "TIID^设备 1 (EtherCAT)^模块 3 (EK1100)^模块 10 (EL6695)" +$cands = @( + "$box^IO Outputs^IO Outputs", + "$box^IO Outputs^IO Outputs[0]", + "$box^IO Inputs^IO Inputs", + "$box^IO Inputs^IO Inputs[0]" +) +foreach ($c in $cands) { + $it = $null + try { $it = $sm.TryLookupTreeItem($c) } catch { Write-Host "err $c : $_" } + if ($it) { + $sz = "" + try { $sz = $it.BitSize } catch {} + Write-Host "OK: $c name=$($it.Name) type=$($it.ItemType) bitsize=$sz" + } else { + Write-Host "MISS: $c" + } +} + +$varAin = "TIPC^PlcSync^PlcSync Instance^PlcTask Inputs^GVL_Sync.aIn1" +$varAout = "TIPC^PlcSync^PlcSync Instance^PlcTask Outputs^GVL_Sync.aOut1" +foreach ($v in @($varAin, $varAout)) { + $it = $null + try { $it = $sm.TryLookupTreeItem($v) } catch {} + if ($it) { Write-Host "PLC OK: $v" } else { Write-Host "PLC MISS: $v" } +} + +Write-Host "--- unlink existing ---" +try { $sm.UnlinkVariables($varAin, "$box^IO Inputs^IO Inputs") ; Write-Host "unlinked aIn1" } catch { Write-Host "unlink aIn1: $_" } +try { $sm.UnlinkVariables($varAout, "$box^IO Outputs^IO Outputs"); Write-Host "unlinked aOut1" } catch { Write-Host "unlink aOut1: $_" } + +Write-Host "--- link whole arrays ---" +try { $sm.LinkVariables($varAin, "$box^IO Inputs^IO Inputs"); Write-Host "linked aIn1" } catch { Write-Host "link aIn1: $_" } +try { $sm.LinkVariables($varAout, "$box^IO Outputs^IO Outputs"); Write-Host "linked aOut1" } catch { Write-Host "link aOut1: $_" } + +$xml = $sm.ProduceMappingInfo() +[System.IO.File]::WriteAllText("C:\Users\tonycao\work\ethercat-linux\tmp_mapping2.xml", $xml, [System.Text.Encoding]::UTF8) +Write-Host "mapping saved" + +Write-Host "--- save + activate + restart ---" +try { $dte.Solution.SaveAll(); Write-Host "saved" } catch { Write-Host "save: $_" } +try { $sm.ActivateConfiguration(); Write-Host "activated" } catch { Write-Host "activate: $_" } +Start-Sleep 10 +try { $sm.StartRestartTwinCAT(); Write-Host "restart issued" } catch { Write-Host "restart: $_" } +Start-Sleep 10 +Write-Host "done" diff --git a/src/bridge.rs b/src/bridge.rs index dab48e0..16dfa09 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -38,18 +38,35 @@ pub fn twin_layout_blob(obj: u16) -> Vec { /// Size-parameterised TwinCAT line-format blob. /// /// For payloads of 1024 bytes this is byte-identical to `twin_layout_blob`. -/// For small payloads (<= 30 bytes, fitting one 240-bit entry) a single -/// entry references the real object at the exact bit length. +/// For payloads up to 240 bits a single entry references the real object at +/// the exact bit length. Larger payloads use the TwinCAT line format: +/// 1 real entry @240bit + continuation entries @240bit + 1 continuation +/// entry at the remainder bit length. pub fn twin_layout_blob_sized(obj: u16, payload_bytes: usize) -> Vec { if payload_bytes == 1024 { return twin_layout_blob(obj); } - let bits = (payload_bytes * 8).min(240) as u8; + let total_bits = payload_bytes * 8; let idx_lo = (obj & 0xFF) as u8; let idx_hi = (obj >> 8) as u8; - let mut blob = Vec::with_capacity(6); - blob.extend_from_slice(&1u16.to_le_bytes()); - blob.extend_from_slice(&[bits, 1, idx_lo, idx_hi]); + if total_bits <= 240 { + let mut blob = Vec::with_capacity(6); + blob.extend_from_slice(&1u16.to_le_bytes()); + blob.extend_from_slice(&[total_bits as u8, 1, idx_lo, idx_hi]); + return blob; + } + let full = total_bits / 240; + let rem = (total_bits % 240) as u8; + let n = if rem > 0 { full + 1 } else { full }; + let mut blob = Vec::with_capacity(2 + n * 4); + blob.extend_from_slice(&(n as u16).to_le_bytes()); + blob.extend_from_slice(&[240, 1, idx_lo, idx_hi]); + for _ in 1..full { + blob.extend_from_slice(&[240, 0, 0, 0]); + } + if rem > 0 { + blob.extend_from_slice(&[rem, 0, 0, 0]); + } blob } diff --git a/src/main.rs b/src/main.rs index da3e615..850ec54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,15 +31,15 @@ const PDI_LEN: usize = 3072; // Secondary side SM layout (ESI-defined process data SMs) const SM_TX_ADDR: u16 = 0x1C00; // SM2: outputs (J1900 writes data to TwinCAT) -const SM_TX_LEN: usize = 8; +const SM_TX_LEN: usize = 800; const SM_RX_ADDR: u16 = 0x8E00; // SM3: inputs (J1900 reads data from TwinCAT) // Full PDO: 2B diagnostic word + 8B payload (single-entry twin_layout blob // expands 0x6000:01 to 8 bytes). -const SM_RX_LEN: usize = 8; // SM3 buffer (no diag word, pure 8B payload) -const SM_RX_PLD_LEN: usize = 8; // actual payload after stripping diagnostic word +const SM_RX_LEN: usize = 800; // SM3 buffer (no diag word, pure 8B payload) +const SM_RX_PLD_LEN: usize = 800; // actual payload after stripping diagnostic word const RX_PREFIX: usize = 0; // no diag word prefix (0x1C13=[0x1A08] only) -const PAYLOAD_LEN: usize = 8; +const PAYLOAD_LEN: usize = 800; /// Abort after this many consecutive cycles with a TX or RX error. const MAX_ERR_STREAK: u64 = 1000;