chore(hygiene): P3 报告归档 docs/reports + scripts 收敛 + lab 清理 + CSS/README 约定
正式 scripts 仅保留 README 自测清单;根目录报告迁入 docs/reports; lab 采样 dump 出库;CSS 注明 btn/toast 所有权并 bump ?v=。
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>EtherCAT 单拍耗时 345µs→124µs:改动原理与验证</title>
|
||||
<style>
|
||||
:root{--fg:#111827;--mut:#6b7280;--bd:#e5e7eb;--bg:#f8fafc;--acc:#1d4ed8;--warn:#b45309;--bad:#b91c1c;--ok:#047857}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:#fff;color:var(--fg);
|
||||
font:15.5px/1.72 -apple-system,"Segoe UI","Microsoft YaHei",system-ui,sans-serif}
|
||||
main{max-width:1120px;margin:0 auto;padding:40px 28px 90px}
|
||||
h1{font-size:29px;line-height:1.3;margin:0 0 6px;letter-spacing:-.2px}
|
||||
h2{font-size:20px;margin:44px 0 12px;padding-bottom:7px;border-bottom:2px solid var(--bd)}
|
||||
h3{font-size:16px;margin:26px 0 8px;color:#374151}
|
||||
p,li{margin:8px 0}
|
||||
.meta{color:var(--mut);font-size:13.5px;margin-bottom:26px}
|
||||
.card{background:var(--bg);border:1px solid var(--bd);border-left:4px solid var(--acc);
|
||||
border-radius:8px;padding:16px 20px;margin:18px 0}
|
||||
.card.warn{border-left-color:var(--warn);background:#fffbeb}
|
||||
.card.bad{border-left-color:var(--bad);background:#fef2f2}
|
||||
.card.ok{border-left-color:var(--ok);background:#ecfdf5}
|
||||
.card h3{margin:0 0 8px;font-size:15px;color:inherit}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(430px,1fr));gap:18px;margin:18px 0}
|
||||
.fig{border:1px solid var(--bd);border-radius:8px;padding:12px 10px 8px;background:#fff}
|
||||
.fig > .cap{font-size:12.5px;font-weight:600;color:#374151;margin:0 0 4px 8px}
|
||||
.fig > .sub{font-size:11.5px;color:var(--mut);margin:0 0 4px 8px}
|
||||
table{border-collapse:collapse;width:100%;font-size:13px;margin:14px 0}
|
||||
th,td{border:1px solid var(--bd);padding:6px 9px;text-align:right}
|
||||
th{background:#f1f5f9;font-weight:600;text-align:center}
|
||||
td:first-child,th:first-child{text-align:left}
|
||||
tr.best td{background:#ecfdf5;font-weight:600}
|
||||
code{background:#f1f5f9;padding:1.5px 5px;border-radius:4px;font-size:12.5px;
|
||||
font-family:ui-monospace,"Cascadia Mono",Consolas,monospace}
|
||||
.kpi{display:flex;gap:14px;flex-wrap:wrap;margin:22px 0 8px}
|
||||
.kpi div{flex:1 1 168px;border:1px solid var(--bd);border-radius:8px;padding:13px 15px;background:#fff}
|
||||
.kpi b{display:block;font-size:23px;line-height:1.25;color:var(--acc)}
|
||||
.kpi span{font-size:12px;color:var(--mut)}
|
||||
ul{padding-left:22px}
|
||||
.foot{margin-top:44px;padding-top:16px;border-top:1px solid var(--bd);
|
||||
font-size:12.5px;color:var(--mut)}
|
||||
.legend{font-size:12px;color:#374151;margin:2px 0 4px 8px}
|
||||
.legend i{display:inline-block;width:11px;height:11px;border-radius:2px;margin:0 4px -1px 10px}
|
||||
</style></head><body><main>
|
||||
<h1>EtherCAT 单拍耗时 345µs→124µs:改动原理与验证</h1>
|
||||
<div class="meta">调查日期 2026-09-11 · 台子:i7-14700K + Intel I210(100 Mbps 全双工)+ DCU(GD32E508 + LAN9252)+ 3 台电机 · 源报告 <code>tools/ecat/REPORT_ecat_beat_tail_20260911.md</code> · 本页所有数字已对照源码与实机复测核验(<code>layout=dense</code>、rx p50=92.1µs、wkc 3/3、v1.6.22.0)</div>
|
||||
|
||||
<div class="kpi"><div><b>345 → 124µs</b><span>9/11 当天 0.5ms 档整拍 p50 实测(cyc_us.work)</span></div><div><b>310 → 92µs</b><span>主站等回包 rx p50 —— 变化都在这一段</span></div><div><b>124.6µs</b><span>9/8 同为 win 布局的实测单拍(CSV)—— 345µs 并非 win 固有成本</span></div><div><b>线速主导</b><span>主站 rx ≈ 帧在线上的往返(~1KB @100Mbit ≈ 80µs 量级);从站写 ESC 是另一段时间</span></div></div>
|
||||
|
||||
<div class="card"><h3>结论(2026-09-12 修订)</h3>
|
||||
<ul>
|
||||
<li><b>实测事实不变</b>:9/11 当天 0.5ms 档整拍从 345µs 变 124µs、rx 从 ~310µs 变 92.1µs(≈纯线路 81.3µs + ~10µs),与板子重启进 1.6.21/22 密排同时发生;1.6.22 稀疏写开关 A/B/A 无可测影响(92.2 / 92.2 / 93.0µs)。</li>
|
||||
<li><b>但"345µs 是 win 布局的固有成本、密排省出 220µs"这条归因已被动摇</b>:9/8 的存量扫描在同样 win 布局下实测单拍 <b>124.6µs</b>(0.125ms 档 7780Hz 的硬约束),与 dense 相同——345µs 是 9/10 夜才出现的异常态,不是 win 的常态。详见下方"重大更正"卡。</li>
|
||||
<li><b>仍然成立的独立事实</b>:刷写 ≠ 生效(镜像 01:44 刷入、约 10 小时后断电重启才生效,三方计数器锚点);台账只能证明"写进去了"——<code>flash_gd32.py</code> 已加生效守卫。</li>
|
||||
</ul></div>
|
||||
<div class="card bad"><h3>重大更正(2026-09-12 复核):220µs 的归因被动摇,两个变量被混在一起</h3>
|
||||
<p>用户质疑"5 个 STATUS 变 3 个,怎么会差 20 倍"——顺着查,发现两件原调查没做的事:</p>
|
||||
<ul>
|
||||
<li><b>机制核对(代码层)</b>:两版 buildInput 逐行对比,纯 CPU 差仅 <b>1~3µs</b>(旧:5×48B + 5×44B 无条件拷贝;新:27 槽选最新 + 影子比对 + 3×48B)。对 ESC 的 PRAM 写两版同为整块 600B(从站侧写 ESC 成本,与主站 rx 不是同一段时间),且 A/B/A 证明它对主站往返不可见——<b>"删掉覆盖窗 / 差分填充省出的 CPU"解释不了 229µs</b>。另:差分填充在本台工况下基本不命中(STATUS ~1.25 帧/拍/台,槽 seq 几乎每拍都变,重建路径照样走)。</li>
|
||||
<li><b>反例数据(9/8 存量扫描)</b>:<code>lab/ecat_rate_sweep.csv</code>——win 布局、0.5ms 档 cyc_p50=<b>124.6µs</b>、达成 100%;1ms=126.9µs;0.125ms=119.5µs 且实测 7780Hz(单拍必须 ≤128µs 才可能)。与 dense 的 124µs 同水平。</li>
|
||||
</ul>
|
||||
<p><b>混淆的变量</b>(刷板台账 git 史 + 主站 git 史):9/8 板上镜像 <code>201832b5…</code>(win)→ 9/10 前已刷 <code>4e84a354…</code>(win 期 1.6.15→1.6.20 演进);主站 9/9 03:05 提交 <code>97fab23</code>(三布局自适应、发现窗基址 {0,7,14,20}→{0,5,10,15,20,22}、覆盖窗按 seq 归一)。<b>345µs 出现在这两个改动之后、9/8 之前</b>——原归因把回归记在 1.6.21 密排头上,但密排也可能只是"绕开"了回归(dense 下主站强制 n_bases=1,不轮转窗口)。</p>
|
||||
<p><b>钉死实验(一次刷板,未执行)</b>:① 刷回 <code>959e2ae</code>(win 固件)+ <code>build_native/ecat_native.prev.exe</code>(9/6 构建,早于 97fab23;它不认识 dense 布局,必须配 win 板)→ 回 ~124µs 即主站回归实锤;② 同 win 板 + 现主站 → 345µs 复现即双向确认;③ 若 prev.exe 也 345µs ⇒ 回归在从站 win 期固件演进里,对 1.6.15→1.6.20 bisect;④ win 模式读 DWT 剖分计数器 0x2000 sub5/6/9/10(PDI_Isr 全程 / 输出映射 / 输入映射 / 其中写侧)——SDO 走独占路由会停环掉 OP,需专门开窗。</p></div>
|
||||
|
||||
<h2>一、一拍的生命周期:210µs 差在哪一环</h2>
|
||||
<p>主站每一拍做的事:<b>组帧 → <code>pcap_sendpacket</code> 发出 → 等帧从线路绕回来(<code>pcap_next_ex</code>)→ 解码</b>。等回包(rx)的时长 = 线路传输时间 + <b>从站把这一拍的输入镜像备好的时间</b>——LAN9252 的 SM2 事件触发 PDI 中断,从站要在 ESC 把输入区搬上线路之前把 600B 输入镜像写好,主站就得等这段活干完。</p>
|
||||
<div class="grid">
|
||||
<div class="fig"><div class="cap">① 同一 0.5ms 档、同一静默判据下的一拍构成(µs)</div>
|
||||
<div class="legend"><i style="background:#94a3b8"></i>主站自身(pre+post 合计 0.3µs)<i style="background:#2563eb"></i>发送(组帧 0.2 + pcap_sendpacket ≈28.4)<i style="background:#fca5a5"></i>线路传输 81.3µs(两布局相同)<i style="background:#dc2626"></i>从站每拍准备(PDI buildInput)</div>
|
||||
<svg viewBox="0 0 620 212" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="一拍构成对比">
|
||||
<line x1="150" y1="180" x2="600" y2="180" stroke="#9aa3af" stroke-width="1.2"/>
|
||||
<line x1="150" y1="28" x2="150" y2="180" stroke="#9aa3af" stroke-width="1.2"/>
|
||||
<line x1="278" y1="176" x2="278" y2="184" stroke="#9aa3af"/><text x="278" y="197" text-anchor="middle" font-size="10.5" fill="#6b7280">100</text>
|
||||
<line x1="406" y1="176" x2="406" y2="184" stroke="#9aa3af"/><text x="406" y="197" text-anchor="middle" font-size="10.5" fill="#6b7280">200</text>
|
||||
<line x1="534" y1="176" x2="534" y2="184" stroke="#9aa3af"/><text x="534" y="197" text-anchor="middle" font-size="10.5" fill="#6b7280">300</text>
|
||||
<text x="150" y="197" text-anchor="middle" font-size="10.5" fill="#6b7280">0</text>
|
||||
<text x="596" y="197" text-anchor="middle" font-size="10.5" fill="#6b7280">µs</text>
|
||||
<text x="142" y="64" text-anchor="end" font-size="12" fill="#374151">1.6.15 滑窗</text>
|
||||
<text x="170" y="40" text-anchor="middle" font-size="10.5" fill="#1e40af">发送 ≈28.6µs</text>
|
||||
<rect x="150" y="48" width="2" height="24" fill="#94a3b8"/>
|
||||
<rect x="152" y="48" width="36.6" height="24" fill="#2563eb"/>
|
||||
<rect x="188.6" y="48" width="104.1" height="24" fill="#fca5a5"/>
|
||||
<rect x="292.7" y="48" width="293.1" height="24" fill="#dc2626"/>
|
||||
<rect x="585.8" y="48" width="2" height="24" fill="#94a3b8"/>
|
||||
<text x="240" y="64" text-anchor="middle" font-size="10.5" fill="#7f1d1d" font-weight="600">线路 81.3</text>
|
||||
<text x="439" y="64" text-anchor="middle" font-size="10.5" fill="#fff" font-weight="600">从站每拍准备 ≈229µs</text>
|
||||
<text x="152" y="86" text-anchor="start" font-size="11" font-weight="600" fill="#b91c1c">rx 往返 ≈310µs = 线路 81.3 + 从站准备 ≈229 · 整拍 p50 345µs · 达成 93.9%</text>
|
||||
<text x="142" y="134" text-anchor="end" font-size="12" fill="#374151">1.6.21 密排</text>
|
||||
<text x="308" y="110" text-anchor="start" font-size="11.5" font-weight="600" fill="#047857">整拍 p50 124µs · 达成 99.9%</text>
|
||||
<rect x="150" y="118" width="2" height="24" fill="#94a3b8"/>
|
||||
<rect x="152" y="118" width="36.6" height="24" fill="#2563eb"/>
|
||||
<rect x="188.6" y="118" width="104.1" height="24" fill="#fca5a5"/>
|
||||
<rect x="292.7" y="118" width="12.8" height="24" fill="#dc2626"/>
|
||||
<rect x="305.5" y="118" width="2" height="24" fill="#94a3b8"/>
|
||||
<text x="240" y="134" text-anchor="middle" font-size="10.5" fill="#7f1d1d" font-weight="600">线路 81.3</text>
|
||||
<text x="152" y="156" text-anchor="start" font-size="11" font-weight="600" fill="#047857">rx 92.1µs = 线路 81.3 + 从站准备 ≈10(差分填充命中,接近纯线时)</text>
|
||||
<line x1="292.7" y1="30" x2="292.7" y2="172" stroke="#dc2626" stroke-width="1" stroke-dasharray="4,3" opacity="0.55"/>
|
||||
<text x="298" y="36" font-size="10.5" fill="#b91c1c">虚线处线路段用完(28.6+81.3≈110µs),之后整段在等从站</text>
|
||||
</svg>
|
||||
<div class="sub">数字出处:整拍与 rx 取自同机同档静默窗实测(win 345 / 310;dense 124 / 92.2,1ms 档复测 92.1);发送 ≈28.6µs 为 0.5ms 档 A 臂 p50(1ms 档 dbg 拆段 26.6~31.3µs,内含组帧 0.2µs);线路 81.3µs = 1016B×8÷100Mbps;win 从站准备 ≈229 = 310−81.3,与固件自量 buildInput 357µs(更重负载下所测)同量级。三段的逐层组成见下表。</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>三段逐层拆解(对应图中蓝 / 粉 / 红三个色块)</h3>
|
||||
<table>
|
||||
<tr><th>段</th><th>0.5ms 实测 p50</th><th>从外到内的组成</th><th>出处 / 尚未拆开的部分</th></tr>
|
||||
<tr><td>发送(蓝)</td><td>28.6µs</td><td>组帧 ≈0.2µs(组 LRW + 填输出区)→ <code>pcap_sendpacket</code> ≈28.4µs:NPF 栈把 1016B 交给 I210 驱动并提交发送</td><td><code>/api/motor/dbg</code> 拆段(sd_us 列)。驱动/DMA 内部无仪表;B 臂曾见发送 p99 348.9µs 的偶发阻塞(单窗样本,未立论)</td></tr>
|
||||
<tr><td>线路(粉)</td><td>81.3µs</td><td>帧 1016B = 过程数据 984B(输出 384 + 输入 600)+ 以太网头 14 + EtherType 2 + 数据报头 10 + WKC 2 + FCS 4;8128bit ÷ 100Mbps;再加前导 8B + 帧间隙 12B ≈ +1.6µs;从站 ESC 硬件转发 µs 级</td><td>由帧构成计算,两布局逐字节同长——结构上不可能解释 220µs 差</td></tr>
|
||||
<tr><td>从站准备(红)· win</td><td>≈229µs</td><td>1.6.15 每拍在 PDI 里:① 按 win_base 轮转准备 27 槽表 ② 段4 覆盖窗 5×44B 拷贝 ③ MotorState 重读 + 逐槽 48B 拷贝 ④ 整块 600B 写入 ESC</td><td>实测差值 310−81.3。①②③ 纯 CPU 差仅 1~3µs;④ 是从站写 ESC(两布局同在、不影响主站往返)——229µs 载体未闭环;9/8 同布局 124.6µs 表明"345µs=win 固有"不成立</td></tr>
|
||||
<tr><td>从站准备(红)· dense</td><td>≈10µs</td><td>差分填充命中:节点集合与各 seq 未变 → 整段复用影子,①②③全免;只剩 count 个槽的更新推送 + ESC 取数</td><td>rx 92.1 − 81.3。220µs 的差全部消失在这里</td></tr>
|
||||
</table>
|
||||
<p class="meta">口径备注:win 时代插桩拆段亦记录到 <code>pcap_sendpacket</code> 181.1µs、其后续中位 1 次 poll 即得帧(§四表)——该口径下发送调用内部吸收了部分等待,<b>发送/等回包两段的分界随仪表口径浮动</b>;但整拍总量(345)与"扣除线路后的从站侧总延迟"(≈229)不受此影响。另 1ms 档每拍 next_ex 轮询中位 79 次(轮询间隔 ~1.2µs),0.5ms 档为紧轮询。</p>
|
||||
<p><b>原理(按 9/11 当天的观测口径):</b>SM 长度(600B)与线速两布局完全相同,所以"线路那一截"不变;rx 的差值实测全部落在"等从站"这一段。但这段差值的载体机制存在重大疑点——两版 buildInput 的纯 CPU 差仅 1~3µs,且 9/8 同布局实测 124.6µs 表明 345µs 并非 win 固有成本——见开头"重大更正"卡。图①对比的是 9/11 上午 win 异常态 vs 午后 dense 的实测,不代表两种布局的固有差距。</p>
|
||||
|
||||
<h2>二、改动原理 A:从站输入映射 1.6.15 滑窗 → 1.6.21 密排整轮</h2>
|
||||
<p>两种布局的输入区都是 600B(SM 长度、SII 都不变),只靠输入区第 35 字节的魔数 <code>0xA5</code> 区分(<code>ecat_native.c:74-84</code>,固件 <code>ethercat_can_bridge.h:154</code> 同款注释):</p>
|
||||
<div class="grid">
|
||||
<div class="fig"><div class="cap">② 输入 PDO 600B 布局对比(按字节偏移)</div>
|
||||
<svg viewBox="0 0 620 252" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="输入区布局对比">
|
||||
<text x="142" y="68" text-anchor="end" font-size="12" fill="#374151">1.6.15 滑窗</text>
|
||||
<rect x="150" y="52" width="22.4" height="26" fill="#cbd5e1"/>
|
||||
<rect x="172.4" y="52" width="5.6" height="26" fill="#64748b"/>
|
||||
<rect x="178" y="52" width="196" height="26" fill="#2563eb"/>
|
||||
<rect x="374" y="52" width="28" height="26" fill="#7c3aed"/>
|
||||
<rect x="402" y="52" width="156.8" height="26" fill="#dc2626"/>
|
||||
<rect x="558.8" y="52" width="11.2" height="26" fill="#16a34a"/>
|
||||
<text x="276" y="68" text-anchor="middle" font-size="10.5" fill="#fff" font-weight="600">段2 STATUS 5×56B(固定 5 槽,空槽占位)</text>
|
||||
<text x="480" y="47" text-anchor="middle" font-size="10.5" fill="#b91c1c" font-weight="600">段4 覆盖窗 5×44B(每拍维护)</text>
|
||||
<text x="480" y="90" text-anchor="middle" font-size="10" fill="#7f1d1d">fillRxWindow / RxWinSlot 整套</text>
|
||||
<text x="161" y="47" text-anchor="middle" font-size="10" fill="#475569">桥计数</text>
|
||||
<text x="142" y="178" text-anchor="end" font-size="12" fill="#374151">1.6.21 密排</text>
|
||||
<rect x="150" y="162" width="22.4" height="26" fill="#cbd5e1"/>
|
||||
<rect x="172.4" y="162" width="5.6" height="26" fill="#64748b"/>
|
||||
<rect x="178" y="162" width="117.6" height="26" fill="#2563eb"/>
|
||||
<rect x="295.6" y="162" width="235.2" height="26" fill="#eff6ff" stroke="#93c5fd" stroke-dasharray="4,3"/>
|
||||
<rect x="530.8" y="162" width="28" height="26" fill="#7c3aed"/>
|
||||
<rect x="558.8" y="162" width="11.2" height="26" fill="#16a34a"/>
|
||||
<text x="236" y="178" text-anchor="middle" font-size="10.5" fill="#fff" font-weight="600">密排槽×3(56B/台)</text>
|
||||
<text x="413" y="157" text-anchor="middle" font-size="10" fill="#1d4ed8">按 count 扩展,最多 9 台(空节点不占位)</text>
|
||||
<text x="413" y="200" text-anchor="middle" font-size="10" fill="#1d4ed8">1.6.22 稀疏写:这段不再推入 ESC RAM</text>
|
||||
<text x="413" y="178" text-anchor="middle" font-size="10" fill="#6b7280">count=3 时空闲</text>
|
||||
<text x="161" y="157" text-anchor="middle" font-size="10" fill="#475569">桥计数</text>
|
||||
<line x1="150" y1="216" x2="570" y2="216" stroke="#9aa3af" stroke-width="1.2"/>
|
||||
<text x="150" y="233" text-anchor="middle" font-size="10" fill="#6b7280">0</text>
|
||||
<text x="220" y="233" text-anchor="middle" font-size="10" fill="#6b7280">100</text>
|
||||
<text x="290" y="233" text-anchor="middle" font-size="10" fill="#6b7280">200</text>
|
||||
<text x="360" y="233" text-anchor="middle" font-size="10" fill="#6b7280">300</text>
|
||||
<text x="430" y="233" text-anchor="middle" font-size="10" fill="#6b7280">400</text>
|
||||
<text x="500" y="233" text-anchor="middle" font-size="10" fill="#6b7280">500</text>
|
||||
<text x="570" y="233" text-anchor="middle" font-size="10" fill="#6b7280">600(字节)</text>
|
||||
<text x="150" y="248" font-size="10" fill="#6b7280">头区 32..39:win= active/boot/win_base/win_count/gseq@36 · 密排= count/active/boot/magic@35/gseq@36 —— 两种布局 gseq 同在偏移 36</text>
|
||||
</svg>
|
||||
<div class="sub">段3 长帧(CONFIG/版本应答):win 在 320..359,密排前移到 544..583;段5 诊断两者都在 584..599。主站 <code>decode_input</code> 每拍判 magic@35 自适应,两端可任意先后升级。</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>1.6.21 的三个子改动,各砍掉什么活</h3>
|
||||
<div class="card"><h3>① 密排整轮:固定 5 槽 + win_base 轮转扫 27 槽 → count 个密排槽</h3>
|
||||
<p>旧方案段 2 固定 5 个 STATUS 槽(空槽 node_id=0 也占位),主站按 <code>win_base</code> 轮转窗口才能盖到 9 节点×3 通道=27 槽的表——接 3 台电机却会看到 5 个状态帧。新方案一拍直接带齐总线上全部电机(本台 3 台=3 槽×56B),槽内按 node_id 升序,主站按 <code>count</code> 迭代。砍掉的是<b>主站轮转窗口带来的多拍刷新分频</b>与从站"为凑满 5 槽而做的填充"。</p></div>
|
||||
<div class="card"><h3>② 删段 4 覆盖窗口(fillRxWindow / RxWinSlot 整套)</h3>
|
||||
<p>旧方案每拍要把非 STATUS 帧抄进 5×44B 的历史覆盖窗反复重发——这是纯粹的每拍 CPU 拷贝活。新方案非 STATUS 帧只走段 1/3 的"最近一帧",覆盖窗整套删除(图中 win 红段在密排侧不复存在)。</p></div>
|
||||
<div class="card"><h3>③ 差分填充:节点集合与各 seq 未变时整段复用影子</h3>
|
||||
<p>注意一个容易误解的点:电机"不动"≠数据不变——STATUS 帧按电机自己的节奏持续上报(本台 3 台合计 ~3750 帧/s,每台 ~1.25 帧/拍),所以大多数拍里槽 seq 都变了、重建路径照样走(实测槽 seq 每 ~1 拍刷新一次)。差分填充真正的收益是"没变的槽不重读不重拷、重建范围从无条件 5 槽缩到实际变化的槽"——量级是 µs 级的,<b>不能用它解释 rx 310→92</b>(见开头"重大更正"卡)。</p></div>
|
||||
|
||||
<h2>三、改动原理 B:1.6.22 稀疏写为何对主站往返"无效"</h2>
|
||||
<p>1.6.22 的思路:PDI 往 ESC RAM 写输入镜像时按 count 只写「段1+段2 有效区」与「段3+段5」,跳过空槽区,省 <code>HW_EscWriteIsr</code> 的 PRAM 字节。<b>方向是对的,但省错了地方</b>——这里有一个关键的不对称:</p>
|
||||
<div class="card warn"><h3>不对称原理:差分填充有效、稀疏写无效</h3>
|
||||
<ul>
|
||||
<li>主站等的关键路径 = <b>SM 交换</b>(ESC 引擎把 600B 输入区搬上线路)+ 交换前从站把镜像<b>备好</b>。</li>
|
||||
<li><b>差分填充砍的是"备好镜像"的 CPU 活</b>(MotorState 重读、逐槽拷贝、覆盖窗维护)→ 直接缩短关键路径 ⇒ rx 310→92µs。</li>
|
||||
<li><b>稀疏写砍的是"推镜像进 ESC RAM"的字节数</b>,但 ESC 引擎上线搬运量由 <b>SM 长度(600B)</b>决定,不看 PDI 实际写了多少 → 主站等待不变。</li>
|
||||
</ul></div>
|
||||
<table>
|
||||
<tr><th>A/B/A 轮次(只改这一个开关)</th><th>板上版本</th><th>0.5ms 达成</th><th>长拍%</th><th>发送 p50/p99 µs</th><th>等回包 rx p50/p99 µs</th><th>load_perm</th></tr>
|
||||
<tr class="best"><td>A:稀疏写=1(出厂)</td><td>1.6.22.0</td><td>99.90%</td><td>0.10</td><td>28.6 / 45.9</td><td>92.2 / 190.9</td><td>489</td></tr>
|
||||
<tr><td>B:整包写=0(A/B 基线)</td><td>1.6.22.1</td><td>99.42%</td><td>0.83</td><td>29.8 / 348.9</td><td>92.2 / 210.4</td><td>441</td></tr>
|
||||
<tr class="best"><td>A 回归</td><td>1.6.22.0</td><td>99.85%</td><td>0.10</td><td>29.4 / 86.9</td><td>93.0 / 101.2</td><td>—</td></tr>
|
||||
</table>
|
||||
<p class="meta">判读:rx p50 三轮 92.2/92.2/93.0µs ⇒ 稀疏写对主站往返无可测影响;<code>load_perm</code> 反而在整包写侧更低(441 vs 489)⇒ 它不能当这项的验收口径(真正该看 ESC 对象 0x2000 sub10,需独占路由停循环才能读,未做)。B 臂发送 p99 348.9µs 与 A 臂不同量级,但单窗样本不足以立论。当前 <code>config.h</code> 已回置出厂 A 臂(<code>CONFIG_ECAT_SPARSE_ESC_WRITE=1</code>)。</p>
|
||||
|
||||
<h2>四、配套改动:把"看不见的东西"变成仪表</h2>
|
||||
<p>排查过程暴露了四个观测缺口,对应的改动与各自原理:</p>
|
||||
<table>
|
||||
<tr><th>改动</th><th>解决什么问题</th><th>原理</th><th>验证</th></tr>
|
||||
<tr><td><code>flash_gd32.py</code> 生效守卫</td><td>台账只能证明"写进去了",不能证明"板上在跑"(本次 10 小时空窗的根因)</td><td>刷前记 <code>ota.gd32_app</code> 版本 + <code>gd32_adc.uptime_sec</code>;刷完必须"版本变了<b>或</b> uptime 归零"才写台账,否则非零退出不写</td><td>两轮真实刷写 1.6.22.0→.1→.0 通过</td></tr>
|
||||
<tr><td><code>ecat_native.c</code> <code>cyc_us.late/lost</code> 接真值</td><td>两字段旧为硬编码 0,超期/跳拍完全不可见</td><td>最近 STATS_N 拍窗口内统计:<code>late</code>=起跑即超期(k>0)的拍数,<code>lost</code>=这些拍跳过的周期数(累计仍在 skip_total)</td><td>重载窗 late=21/lost=28,健康窗 0/0</td></tr>
|
||||
<tr><td><code>/api/motor/dbg</code> 尾部追加两列</td><td>发送段是黑盒:不知 sendpacket 花多久、轮询了几次</td><td>第 7/8 列 = 本拍 <code>pcap_sendpacket</code> 净耗时与 <code>pcap_next_ex</code> 次数;旧列序不变,按前 6 列下标读的消费方不受影响</td><td>0.5ms 档 181µs 调用、每拍中位 1 次 poll</td></tr>
|
||||
<tr><td><code>soem-src/oshw/win32/nicdrv.c</code> pcap 调用计数</td><td>主/从栈调用混在一起没法归因</td><td>只在 <code>stacknumber==0</code>(主栈)累加 QPC 计数,冗余栈不计防双计</td><td>A/B 与旧构建行为一致(中性)</td></tr>
|
||||
<tr><td><code>watch_beat_tail.py</code>(新)</td><td>跨分钟现象无法回溯(布局翻转恰好发生在两次采样之间)</td><td>每 10s 只读一轮 <code>motor/state</code>+<code>rxraw</code>+<code>app/load</code>,落 27 列 CSV(layout/work/send/rx/late/lost/λ/达成)</td><td><code>--once</code> 跑通</td></tr>
|
||||
<tr><td><code>transport-ecat.js</code> 常量适配</td><td>前端仍按旧 5 槽/6 周期口径显示</td><td>密排下一拍整轮:<code>MOT_SLOTS_PER_PDO</code> 5→9、<code>MOTOR_SCAN_CYCLES</code> 6→1;能否拿齐电机由后端 decode 决定,常量只影响展示口径</td><td>电机页原样复用,无回归</td></tr>
|
||||
<tr><td><code>lab/spi_test.html</code> 跳拍卡</td><td>旧版需开逐拍环才有超期数据</td><td>直接读 <code>cyc_us.late/lost</code>(最近 400 拍窗),并注明旧后端恒报 0 不可当"无超期"</td><td>真机复跑成功,<code>node --check</code> 通过</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>五、证据:dense 是真的,以及为什么今天才生效</h2>
|
||||
<div class="grid">
|
||||
<div class="fig"><div class="cap">③ 判据:槽 seq 距当前 gseq 的滞后分布(111 次连续采样 ×3 槽=333 样本)</div>
|
||||
<svg viewBox="0 0 560 232" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="槽滞后分布">
|
||||
<rect x="376" y="20" width="160" height="180" fill="#dc2626" opacity="0.06"/>
|
||||
<rect x="376" y="20" width="160" height="180" fill="none" stroke="#dc2626" stroke-dasharray="4,3" opacity="0.5"/>
|
||||
<text x="456" y="52" text-anchor="middle" font-size="10.5" fill="#b91c1c" font-weight="600">滑窗方案预期落点</text>
|
||||
<text x="456" y="68" text-anchor="middle" font-size="10" fill="#7f1d1d">每槽每 ≥2 拍才轮到一次</text>
|
||||
<text x="456" y="82" text-anchor="middle" font-size="10" fill="#7f1d1d">滞后应 ≈4~8 帧 —— 实测 0 个样本</text>
|
||||
<rect x="73" y="24.5" width="34" height="170.5" fill="#2563eb"/>
|
||||
<rect x="125" y="63.3" width="34" height="131.7" fill="#2563eb"/>
|
||||
<rect x="177" y="75.7" width="34" height="119.3" fill="#2563eb"/>
|
||||
<rect x="229" y="102" width="34" height="93" fill="#2563eb"/>
|
||||
<rect x="281" y="193.5" width="34" height="1.5" fill="#2563eb"/>
|
||||
<text x="90" y="20" text-anchor="middle" font-size="10" fill="#1e40af">110</text>
|
||||
<text x="142" y="58" text-anchor="middle" font-size="10" fill="#1e40af">85</text>
|
||||
<text x="194" y="71" text-anchor="middle" font-size="10" fill="#1e40af">77</text>
|
||||
<text x="246" y="97" text-anchor="middle" font-size="10" fill="#1e40af">60</text>
|
||||
<text x="298" y="189" text-anchor="middle" font-size="10" fill="#1e40af">1</text>
|
||||
<line x1="60" y1="195" x2="536" y2="195" stroke="#9aa3af" stroke-width="1.2"/>
|
||||
<text x="90" y="211" text-anchor="middle" font-size="10.5" fill="#6b7280">0</text>
|
||||
<text x="142" y="211" text-anchor="middle" font-size="10.5" fill="#6b7280">1</text>
|
||||
<text x="194" y="211" text-anchor="middle" font-size="10.5" fill="#6b7280">2</text>
|
||||
<text x="246" y="211" text-anchor="middle" font-size="10.5" fill="#6b7280">3</text>
|
||||
<text x="298" y="211" text-anchor="middle" font-size="10.5" fill="#6b7280">4</text>
|
||||
<text x="456" y="211" text-anchor="middle" font-size="10.5" fill="#6b7280">≥6(帧)</text>
|
||||
<text x="60" y="16" font-size="10.5" fill="#6b7280">样本数 · λ≈3.75 帧/拍(3750 帧/s @ 1kHz)</text>
|
||||
<text x="280" y="228" text-anchor="middle" font-size="11" fill="#374151">槽 seq 滞后于当前 global_seq(帧数)</text>
|
||||
</svg>
|
||||
<div class="sub">三台电机落在槽 2/5/8:滑窗方案盖住它们需 ≥2 个基址轮转 → 每槽每 ≥2 拍才刷新、滞后应集中在 4~8 帧。实测 1/3 样本滞后为 0、全部 ≤4 帧 ⇒ 只能是"每拍带齐整轮"的密排语义。seq 与 gseq 都是 GD32 自己盖的号,滞后能对上同时证明解码偏移没错。(原报告曾把"gseq≡rx_count"当最强证据,复核发现两种布局 gseq 同在偏移 36,该条不能区分布局,已撤换为本判据。)</div>
|
||||
</div>
|
||||
<div class="fig"><div class="cap">④ 时间线:刷写 ≠ 生效</div>
|
||||
<svg viewBox="0 0 620 150" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="时间线">
|
||||
<line x1="70" y1="70" x2="560" y2="70" stroke="#9aa3af" stroke-width="1.6"/>
|
||||
<circle cx="100" cy="70" r="5" fill="#2563eb"/>
|
||||
<circle cx="290" cy="70" r="5" fill="#dc2626"/>
|
||||
<circle cx="430" cy="70" r="5" fill="#b45309"/>
|
||||
<circle cx="530" cy="70" r="5" fill="#047857"/>
|
||||
<text x="100" y="30" text-anchor="middle" font-size="11" fill="#374151">01:44 镜像刷入 1.6.22</text>
|
||||
<text x="100" y="45" text-anchor="middle" font-size="10" fill="#6b7280">台账 sha=hex 展平 ✓</text>
|
||||
<text x="290" y="30" text-anchor="middle" font-size="11" fill="#b91c1c">白天:仍在跑老代码</text>
|
||||
<text x="290" y="45" text-anchor="middle" font-size="10" fill="#6b7280">主站实测 win 布局 · 232B · 整拍 345µs</text>
|
||||
<text x="430" y="100" text-anchor="middle" font-size="11" fill="#b45309">中午断电重启</text>
|
||||
<text x="530" y="100" text-anchor="middle" font-size="11" fill="#047857">生效:dense · 124µs</text>
|
||||
<path d="M100,84 L100,96 L430,96 L430,84" fill="none" stroke="#b45309" stroke-width="1.4"/>
|
||||
<text x="265" y="115" text-anchor="middle" font-size="11.5" font-weight="600" fill="#b45309">≈10 小时:写入 ≠ 生效</text>
|
||||
<text x="70" y="140" font-size="10" fill="#6b7280">三方计数器锚点(不依赖系统时钟):主站进程存活 2084s · GD32 桥计数归零 1914s(gseq÷λ)· ESP32 上电 1922s —— 后两者差 8s,GD32 与 ESP32 同次上电;板子比主站晚 ~2.8 分钟重启,且 uptime 单调增(只重启过一次)</text>
|
||||
</svg>
|
||||
<div class="sub">主站在 02:3x 直接读到 <code>layout="win" len=232</code> 配 345µs——刷入后 10 小时仍是老代码在跑。台账 <code>_last_gd32_sha.txt</code> 当时就与 Release hex 展平一致,即"写进去了"为真,"在跑了"为假。(A/B/A 之后台账现为 dda92d11…,对应 12:46 刷回 A 臂的产物,本次复核独立展平重算一致。)</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>同机同日前后对照(观察性证据)</h3>
|
||||
<table>
|
||||
<tr><th>从站布局</th><th>rx 往返</th><th>整拍 p50</th><th>达成</th><th>长拍>1.4×</th><th>跳拍 k>0</th><th>gap p99</th></tr>
|
||||
<tr><td><code>win</code>(1.6.15 滑窗)</td><td>~310µs</td><td>345µs</td><td>93.9%(1879Hz)</td><td>7.9%</td><td>8.6%</td><td>1144µs</td></tr>
|
||||
<tr class="best"><td><code>dense</code>(1.6.21/22 密排)</td><td>92.2µs</td><td>123µs</td><td>99.9%(1998Hz)</td><td>0.10%</td><td>~0.6%</td><td>=周期本身</td></tr>
|
||||
</table>
|
||||
<p class="meta">0.5ms 档余量从 155µs 变为 377µs:旧方案任何调度抖动都会成片跳拍,新方案长拍基本消失。另 1ms 档现网 <code>late/lost</code>(k>0 口径,连 1µs 超期都算)为个位数/400 拍窗,与"长拍>1.4×≈0%"不矛盾——两套阈值灵敏度不同,并列引用时必须标注口径。</p>
|
||||
|
||||
<h2>六、被实测排除的原因(别再重测)</h2>
|
||||
<table>
|
||||
<tr><th>候选解释</th><th>排除依据</th></tr>
|
||||
<tr><td>线路变快 / 帧变小</td><td>两种布局 Ibytes 同为 600B(<code>ecat_native.c:74</code> 与固件头文件双侧注释),帧长 ~1016B 不变,线速仍 100Mbps 全双工</td></tr>
|
||||
<tr><td>收帧超时(EC_TIMEOUTRET)</td><td>预算 2000µs(µs 单位),11,264 拍里仅 1 拍 EC_NOFRAME;win 时代 rx p50 7~9µs 起步,预算从不消耗——收紧它是自证式"变好"</td></tr>
|
||||
<tr><td>主站构建差异 / 双实例</td><td>插桩版与旧构建 A/B 一致;单进程单端口;旧 exe 留档 <code>build_native/ecat_native.pre-instr.exe</code></td></tr>
|
||||
<tr><td>观察者负载</td><td>面板全量 10Hz×3、单 dbg 10Hz(≈1.5MB/s JSON)、零请求静默窗,三者结果在噪声内相同</td></tr>
|
||||
<tr><td>CPU 抢占</td><td>另开 4 / 12 个纯算线程压机,send/rx/work 一动不动</td></tr>
|
||||
<tr><td>"PRAM 逐 DWORD 轮数 150→45"</td><td>被 A/B/A 否:整包写 600B 的 B 臂同样 92µs——写侧省字节数不进入主站关键路径(§三不对称原理)</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>七、复核更正(本次排查自己错过又改掉的判断)</h2>
|
||||
<div class="card bad"><h3>证据层的两处撤换</h3>
|
||||
<ul>
|
||||
<li><b>"gseq≡rx_count,差恒 0"是同义反复</b>:<code>MOTW_OFF_GLOBAL_SEQ</code> 与 <code>MOTW_OFF_GSEQ_DENSE</code> 都是偏移 36(<code>ecat_native.c:69,82</code>),两种布局读同一字节,分不开 dense 与 win。已撤换为 §五-③ 的槽滞后判据。</li>
|
||||
<li><b>魔数 0xA5 无法从主机侧直接读</b>:<code>/api/motor/rxraw</code> 返回的是批量接收区缓冲快照,不是 PDO 输入镜像;路由表中没有任何路由吐输入镜像。故"直接验魔数"这条路不存在,dense 判定只能靠上述间接判据 + 60s×30 与 111 次采样稳定性(layout 恒 dense、len 恒 176、count 恒 3、win_bases 恒 None)。</li>
|
||||
</ul></div>
|
||||
<div class="card bad"><h3>限制声明的一处更正(本次复核新增)</h3>
|
||||
<ul>
|
||||
<li><b>"DCU_APP 不在任何 git 仓库、布局无法 A/B"不成立</b>:<code>DCU_APP</code> 是独立 git 仓库(38 个提交)——<code>959e2ae</code> = 1.6.14~1.6.20 滑窗覆盖窗源码、<code>1742d9a</code> = 1.6.21 密排;命令行构建链(cbuild + AC6)现成。因此"win→dense 换来 220µs"这一环<b>可以做受控实验钉死</b>:checkout <code>959e2ae</code> 重编刷板 → 预期 rx 回到 ~310µs → 再刷回 1.6.22(flash_gd32.py 生效守卫会留台账)。属刷板操作,未执行。</li>
|
||||
<li><b>遗留:重刷同一版本无法证明生效</b>——<code>gd32_adc.uptime_sec</code> 实测不随 GD32 换镜像归零,守卫的"uptime 归零"分支不可靠,目前只能靠版本号变化。根治要 BOOT 上报复位计数/复位原因,或 OTA 完成后强制 APP 复位回读。</li>
|
||||
</ul></div>
|
||||
<div class="card warn"><h3>归因的置信度边界(2026-09-12 修订)</h3>
|
||||
<ul>
|
||||
<li>已受控验证:稀疏写开关(A/B/A,单臂 2048 拍静默窗,效应上界 ≲1µs/拍);排除项(§六);"刷写≠生效"时间锚点(§五)。</li>
|
||||
<li><b>已动摇</b>:win→dense 与 220µs 的因果归因——9/8 同布局实测 124.6µs 的反例 + 两版 buildInput 纯 CPU 差仅 1~3µs。原"三条独立吻合"里"旧值≈357µs"一条存疑:357µs 是 1.6.11 时代(496B 输入 + 环形批量区)的测量,与滑窗 600B 版不同代。</li>
|
||||
<li>待做:开头"重大更正"卡的刷板 A/B(959e2ae + prev.exe 双轮)是当前唯一能闭合因果的实验。</li>
|
||||
</ul></div>
|
||||
|
||||
<h2>八、遗留与下一步</h2>
|
||||
<div class="card"><h3>按优先级</h3>
|
||||
<ul>
|
||||
<li><b>布局 A/B 钉死归因(方案已更新)</b>:刷回 <code>959e2ae</code>(win 固件)后做两轮——配 <code>ecat_native.prev.exe</code>(旧主站,≤78fe6cf)与现主站各测一轮 0.5ms:124µs / 345µs 的分野即判定回归在主站还是从站 win 期固件;再用 0x2000 sub5/6/9/10 定位。刷板会中断 OP 会话,需专门安排。</li>
|
||||
<li><b>0.25ms 档第一次有理论余量</b>(124/250=50%),值得按同一静默判据实测——但注意此前结论:从站 SSC 的 SM2 事件路径在快档有自己的墙,别只看主站达成率。</li>
|
||||
<li><b>重刷同版本的生效证明</b>:BOOT 上报复位计数,或 OTA 后强制 APP 复位回读版本。</li>
|
||||
<li><code>0x2000 sub10</code>(EscWrite 周期)精确验收需独占路由(停循环、从站掉 OP),要专门开一窗。</li>
|
||||
<li>ms 级离群未清零:dense 时代仍见 work max 2153µs / rx max 1658µs(p90 仅 ~110µs),量级无害但没定位。</li>
|
||||
<li>本次全部改动<b>未提交</b>(tools/ecat 3 文件+报告、tools/ESP32 7 文件、DCU_APP 4 文件),旧主站 exe 留档在 <code>build_native/ecat_native.pre-instr.exe</code>。</li>
|
||||
</ul></div>
|
||||
<div class="foot">本报告依据 <code>tools/ecat/REPORT_ecat_beat_tail_20260911.md</code>、当日调查会话与复核证据整理;所有代码引证(偏移定义、357µs、路由表、台账 sha)已于 2026-09-11 对照源码与实机独立核验,实时复测 rx p50=92.1µs / layout=dense / wkc 3/3 / v1.6.22.0。相关页面:<a href="ECAT_RATE_TRADEOFF.html">ECAT_RATE_TRADEOFF.html</a>(周期×故障率×CPU 调研)· <code>lab/spi_test.html</code>(SPI/ECAT 测速)。</div>
|
||||
</main></body></html>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,291 @@
|
||||
# EtherCAT 周期速度 × 故障率 × CPU 占用 调研
|
||||
|
||||
> **⚠ 本 Markdown 版含一条已作废的结论,权威版本是 `ECAT_RATE_TRADEOFF.html`。**
|
||||
> 作废内容:第一节「`drop` ≡ λ → 丢帧率 100%、与周期无关」。**错因**:`drop`、
|
||||
> `bridge.rx_count`、`head.global_seq` **各自都以约 4000/s 增长**,两两相除必然得到 100%,
|
||||
> 那只是同速量之比、不是丢帧。**反证**(后续实测):两台电机 STATUS 以 4001 帧/s 正常刷新,
|
||||
> 帧确实收到了;而 ECAT 批量窗口去重后的 CONFIG 事件流只有约 92 帧/s,与 `drop` 的 4000/s
|
||||
> 完全不成比例。`drop` 的口径至今未查明,**不得用它做任何丢帧判断**。
|
||||
> 仍然成立:速度上限、从站 CPU 负担、拔 CAN 对照、>2kHz 时 STATUS/CONFIG 一同塌陷。
|
||||
|
||||
调研日期:2026-09-08 · 台子:本机 I210 + 1 个 GD32E508 从站(双电机在线)
|
||||
数据与脚本:`scripts/sweep_ecat_rate.py`(扫描)、`scripts/calibrate_ecat_metrics.py`(现场标定)、
|
||||
`scripts/plot_ecat_sweep.py`(出图)、`scripts/plot_ecat_nocan_vs_can.py`(带/无 CAN 对比);
|
||||
曲线 `lab/ecat_rate_sweep.png`、`lab/ecat_rate_sweep_nocan_vs.png`;原始数据
|
||||
`lab/ecat_rate_sweep_withcan.csv`、`lab/ecat_rate_sweep_nocan.csv`、`lab/ecat_calib.json`
|
||||
(`*.csv`/`*.jsonl` 按 `.gitignore` 约定不入库)。
|
||||
本轮共两轮扫描:**带 CAN**(第四~五节主结论)与**拔掉 CAN 的补测**(第四 b 节)。
|
||||
|
||||
---
|
||||
|
||||
## 一、结论
|
||||
|
||||
**速度余量约 2×,且瓶颈在从站 GD32,不在主站。** 实测可用到 **0.5ms(2kHz)**;
|
||||
0.25ms 起电机数据刷新率从 4000 帧/s 掉到 88 帧/s(带 CAN 那轮 8s/档定点实测,当场确认
|
||||
`online=True`,故为真失效)。上限的**具体位置没测到** —— 只夹在 (0.25ms, 0.5ms] 之间。
|
||||
|
||||
**「加快 ECAT 周期能减少 CAN 丢帧」这个前提被本次数据推翻。** 在 4ms→0.5ms 整个区间,
|
||||
桥接层丢帧 `bridge.drop` 与 CAN 到帧率 λ **完全相等**(丢帧率恒 100.0%),与周期无关。
|
||||
该字段属 GD32 段1 桥接层的接收计数,其消费者是 GD32 主循环而非 ECAT PDO 填充路径,
|
||||
所以 ECAT 周期快慢改不了它(字段的精确含义尚有一处未闭合矛盾,见第五节)。
|
||||
|
||||
**提速的实际收益与代价**:
|
||||
|
||||
| | 4ms | 2ms | 1ms(现行) | **0.5ms** | 0.25ms | 0.125ms |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 实测循环率 | 249Hz | 499Hz | 998Hz | **1999Hz** | 3989Hz | 7780Hz |
|
||||
| 达成率 | 99.7% | 99.7% | 99.8% | **100.0%** | 99.7% | 97.2% |
|
||||
| GD32 负载 | 20.2% | 28.3% | 44.5% | **76.7%** | 99.7% | 99.3% |
|
||||
| 主站错拍 skip/s | 0.02 | 0.08 | 1.1 | **11.9** | 98.6 | 5106.6 |
|
||||
| 电机数据刷新 /s ¹ | 4000† | 4000† | 4000.2 | **4000.2** | 87.9 | 147.5 |
|
||||
| CAN 到帧 λ /s | 3987 | 3990 | 3992 | **3995** | 159 | 190 |
|
||||
| 桥接层丢帧 drop /s | 3987 | 3990 | 3992 | **3995** | 159 | 190 |
|
||||
| 丢帧率 | 100.0% | 100.0% | 100.0% | **99.99%** | 100.0% | 100.0% |
|
||||
| 主机 native CPU | 100.3% | 100.3% | 100.3% | **100.3%** | 100.3% | 87.5% |
|
||||
|
||||
¹ 电机刷新率只在 **1 / 0.5 / 0.25 / 0.125ms** 四档定点实测(8s/档)。标 † 的 4ms、2ms 两列
|
||||
是按"λ 不随周期变化"推的,**未实测**。
|
||||
|
||||
建议:**要吃到这份余量就停在 0.5ms**(功能与 1ms 完全同速、达成率满分、CPU 76.7% 仍有余量,
|
||||
代价是错拍从 1.1/s 升到 11.9/s)。0.25ms 不是"更快",是**功能损坏**。
|
||||
|
||||
**补测(拔掉全部 CAN)后新增两点**:
|
||||
- CAN 的每拍负担是**常量 ≈9 个百分点**(2/1/0.5ms 三档 Δ=9.3/9.1/8.7pp),不随频率张开
|
||||
→ 带 CAN 那轮的快档塌陷不是 CAN 挤出来的,上限由纯 ECAT 的过程数据交换决定。
|
||||
- 纯 ECAT 到 2kHz 只需 68.0%,模型外推上限约 **3.5kHz**(带 CAN 约 2.7kHz)。
|
||||
但 **0.25ms 那档没测到数**:加压时从站停止应答(`wkc=-1`、`load_perm` 成残值),
|
||||
所以"3.5kHz"目前是外推而非实测,**不要据此设定期望值**。详见第四 b 节。
|
||||
|
||||
---
|
||||
|
||||
## 二、工况与数据身份(先声明,因为对账不上)
|
||||
|
||||
| 项 | 值 | 能否确认 |
|
||||
|---|---|---|
|
||||
| GD32 烧写台账 | `lab/_last_gd32_sha.txt` = `201832b5a8e258d6…`(未提交状态) | 台账本身可读 |
|
||||
| 在盘固件产物 | 展平后哈希 `63d6ab9b…`(09-08 02:31)、`e6a6e75f…`(09-05) | **均不等于台账** |
|
||||
| 主站构建 | `ecat_native.c` 改于 02:39、`ecat_native.exe` 改于 02:40,均有未提交改动 | 运行中 PID 29284 起于 02:32 |
|
||||
| 板载固件版本 | — | **无法确认**(设备不回报版本字) |
|
||||
|
||||
三点后果,均已在结论中按保守口径处理:
|
||||
|
||||
1. 台账哈希与任何在盘产物都不匹配,且产物时间戳(02:31)**晚于**台账记录 —— 无法证明板上跑的
|
||||
就是当前源码编出的那一版。**本报告不复用任何历史固件结论**(含"1.6.5 已修 seqlock 撕裂")。
|
||||
2. 测量期间主站构建又更新过(02:39/02:40),无法证明 02:32 启动的进程内存里就是盘上那份 exe。
|
||||
3. `pdo_reentry` 字段要固件 1.6.7+ 才有、`rx_torn` 要 1.6.6+。两者本次恒零,**既可能是"没发生",
|
||||
也可能是"该字段根本没实现、恒读 0"** —— 因版本未知,二者无法区分。
|
||||
|
||||
---
|
||||
|
||||
## 三、判据口径(含三个当场失效/需修正的测法)
|
||||
|
||||
先做 Phase 0 标定(`scripts/calibrate_ecat_metrics.py`)再扫描,因为固件改过很多版,
|
||||
不能假设任何历史判据仍然有效。标定过程中我自己有三个判据当场失效或被我误判,记录如下以免重犯:
|
||||
|
||||
| 测法 | 结果 | 处置 |
|
||||
|---|---|---|
|
||||
| 拿「seq 值域 − 观测到的 distinct seq」当丢帧率 | 报出 **99.686%** 丢帧 | **方向与 `drop`≈λ 一致,不是假信号**;但分母是全局 seq 值域(含 STATUS 全流量),口径不干净 → 不单独引用,只作 `drop` 的旁证。曾据 `OVF=0` 判它"被证伪",该否定**收回**(见下节 OVF 说明) |
|
||||
| 用 HTTP 轮询看 `rx_batch.count` 当"每拍几帧" | 读出 cap=2,与源码 3 帧/拍不符 | 轮询欠采样。每拍口径必须取 native 的按拍环 `/api/can/cadence`(实测 count 众数=2,与轮询一致,但与 1.6.15 的 5 槽上限不是一回事) |
|
||||
| 用 `rxraw` 的环快照列 `rxb_count_mode_per_cycle` / `lost` / `pending` | 快档读出 5、4、2 | 该环只存最近 512 拍,0.25ms 档 = 128ms、0.125ms 档 = 64ms,环没装满 —— 读到的是**行数**不是帧数。**这几列不入结论** |
|
||||
|
||||
最终采用的口径:
|
||||
|
||||
- **速度达成**:`cycles`(u32)增量 / 墙钟,**cycles 与时刻同取同舍**(沿用 `tools/ecat/rate_probe.py` 的纪律,其注释记过一次因分子分母不同源把 99.2% 误报成 96% 的实踩)。
|
||||
- **CAN 到帧 λ**:`head.global_seq`(u32,固件 `ECAT_MOTW_OFF_GLOBAL_SEQ`「状态表全局刷新序号」)。
|
||||
与电机槽 `seq` 同速(实测均 4000/s)—— 两条独立字段互证,λ 口径可信。
|
||||
- **桥接层丢帧**:`bridge.drop`,固件出处 `ECAT_CAN_RX_OFF_DROP = 24`,注释明确为
|
||||
「**投递时上一帧还没被取走**」的丢帧数(u16)—— 即窗口被覆盖而未送达主站的帧,
|
||||
语义正对本题。自增另有 `canfd.h:53 rx_drop_count`(CAN RX 软件队列满,`canfd.cpp:1615`
|
||||
`queue.count >= kRxQueueDepth`,队列深 `kRxQueueDepth=16`);两者都指向"消费不及"。
|
||||
u16 在 4000/s 下 16 秒绕一圈 → 10Hz 逐次累加 + `(b-a) mod 65536`,单次增量 >32768 记为
|
||||
绕圈作废。**本次作废点数 = 0**,即采样率足够。
|
||||
- **不能当"无丢帧"证据的字段**:`rx_batch.lost` / cadence 的 `lost`/`pending`。它们对应固件
|
||||
`OVF`,而 `fillRxWindow` 里 `in[...] = s_rxWinOvf; s_rxWinOvf = 0;` —— **每拍清零取增量**,
|
||||
实测恒 0 只说明单拍增量小于分辨率,不代表没有丢帧。曾据它判 `drop` 为死字段,此推断**收回**。
|
||||
- **功能存活(真故障轴)**:电机槽 `seq`(u32)前进速率。这是唯一直接回答"用户还能不能用"的指标。
|
||||
- **CPU(从站)**:`load_perm`(千分比,1s 滚动窗,`ECAT_DIAG_OFF_LOAD`)。**它不是整机 CPU 占用率**
|
||||
—— 固件 `sys/cpu_load.h:9-27` 定义收窄为「中断 + 非延迟处理段」占墙钟之比,并**刻意排除**
|
||||
SSC MainLoop 轮询与 `AppSpiCommandBus.poll()`;且当前是 free-run(`native/health.dc=0`),
|
||||
注释写明"free-run 调试模式下 PDO 应用跑在主循环轮询路径,**不计入**"。
|
||||
因此:① 不能把"load→100%"读成"CPU 耗尽";② 但它仍随循环率强增(20.2%→76.7%),
|
||||
这份增量只能来自**被计入的部分**(PDI/EXTI 等挂钩 ISR 每拍触发一次),
|
||||
所以作为"每拍中断负担"的代理量仍然成立 —— 第五节据此建模,并把结论限定在这个口径内。
|
||||
- **CPU(主机)**:`psutil` 按 PID 差分,同时报单核% 与整机%(`scripts/cpu_sample.ps1` 只报前者,
|
||||
28 核机器上会把 3.6% 误读成 100%)。
|
||||
- **撕裂/重入**:`rx_torn` / `pdo_reentry` 为 u16 **饱和**计数(到 65535 停),按
|
||||
`delta_saturate` 处理,封顶即报"下界"而非精确值。
|
||||
|
||||
---
|
||||
|
||||
## 四、曲线与解读
|
||||
|
||||

|
||||
|
||||
**① 主站循环率** 在 4ms→0.25ms 全程贴着 1:1(≥99.7%),0.125ms 才降到 97.2%。
|
||||
主站单拍耗时 `cyc_us.total.p50` 各档恒约 120–134µs、与设定周期无关(自旋钉核所致),
|
||||
所以**主站侧根本不是限制项**。附带修正一条历史记录:`rate_probe.py` 写着"真机一送一回
|
||||
≈165µs 物理墙",本次实测 0.125ms 档跑到 7780Hz,那道墙在当前 5 槽布局下已经不是限制。
|
||||
|
||||
**② CPU 占用** 是唯一陡峭的分界线。GD32 负载 20.2 → 28.3 → 44.5 → **76.7 → 99.7**%,
|
||||
在 0.5ms 与 0.25ms 之间越顶。主机 native 全程 100.3%(自旋线程恒占一个逻辑核,
|
||||
`/api/motor/spin` 已是空操作,CPU 降不下来)—— 有意思的是 0.125ms 档反而**降到 87.5%**,
|
||||
因为从站已经不回数据、总线事务提前完成:**主机 CPU 掉下来的那一刻,恰恰是从站死掉的那一刻**。
|
||||
这条曲线说明"提速免费"的判断只在一半意义上成立:不花主机成本,但花从站成本,而从站才是预算。
|
||||
|
||||
**③ 故障率** 是本次的核心图。λ 与 `drop` 两条线**重合到肉眼分不开**,横跨 4ms→0.5ms 全程,
|
||||
然后一起从 4000/s 断崖式塌到 ~150/s。读法:
|
||||
- 重合 = 到多少丢多少,**丢帧率 100% 且与周期无关** → 提速不改善它;
|
||||
- 一起塌 = 0.25ms 起连 CAN 收帧本身都停了 → 提速会**打断**它。
|
||||
电机数据刷新率(红)与 λ 同步塌陷,确认这不是测量假象而是功能失效。
|
||||
|
||||
**④ 主站错拍 `skip_total`** 是次级上限:0.02 → 11.9(0.5ms)→ 98.6(0.25ms)→ 5107/s(0.125ms)。
|
||||
它在 GD32 越顶之前就已经指数上升,若只看主站视角,0.5ms 的 11.9 次/s 是可接受代价,
|
||||
但 0.25ms 的 98.6 次/s 已说明主站也在勉强。
|
||||
|
||||
---
|
||||
|
||||
## 四b、补测:拔掉全部 CAN 设备后的纯 EtherCAT 速度
|
||||
|
||||
**动机**:带 CAN 的那轮里,快档同时出现「λ 塌陷」与「电机掉线」两种征象,二者在 λ/seq 上
|
||||
长得一模一样,无法区分是**每拍过程数据交换太重**还是**CAN 流量挤掉了主循环**。
|
||||
拔 CAN 是解这个混淆的判别性实验。
|
||||
|
||||
**结果**(同脚本同参数,`lab/ecat_rate_sweep_nocan.csv`,对比图 `lab/ecat_rate_sweep_nocan_vs.png`):
|
||||
|
||||
| 周期 | 循环率 | 带 CAN | 无 CAN | Δ |
|
||||
|---|---|---|---|---|
|
||||
| 2ms | 500Hz | 28.3% | **19.0%** | 9.3pp |
|
||||
| 1ms | 1000Hz | 44.5% | **35.4%** | 9.1pp |
|
||||
| 0.5ms | 2000Hz | 76.7% | **68.0%** | 8.7pp |
|
||||
| 0.25ms | 4000Hz | 99.7% | **不可引用** | — |
|
||||
|
||||
**三条读数**:
|
||||
|
||||
1. **CAN 的负担是常量、不是变量**:三档 Δ 稳定在 8.7–9.3pp。所以 CAN 既不是上限的成因,
|
||||
也不随频率张开 —— 带 CAN 那轮的快档塌陷**不是 CAN 挤出来的**。
|
||||
2. **纯 ECAT 到 2kHz 毫无问题**(68.0%,达成率 100.0%,错拍 17.1 次/s)。
|
||||
3. **上限仍没测到**:加压到 0.25ms 时**从站停止用有效数据应答** ——
|
||||
`wkc=-1`(cadence 环里 512/512 拍全为 65535)、`bridge.valid=false`、`head.active=0`、
|
||||
`app/load` 的 `age_ms=214338`,而主站 `cycles` 仍以 1004Hz 前进、`op=1`、`al_status=0`。
|
||||
即总线在空转、从站不应答。**0.25ms 档的 99.2% 是从站停报后的 load_perm 残值,
|
||||
本报告不引用。**
|
||||
|
||||
**用无 CAN 数据重拟合第五节的两项模型**:F=(0.680−0.190)/(2.0−0.25)≈**272µs**、
|
||||
R≈4.0%,硬上限周期 F/(1−R)≈**284µs(3.5kHz)**。
|
||||
对比带 CAN 的 F≈323µs → 2.7kHz。**拔 CAN 大约把上限从 2.7kHz 抬到 3.5kHz,
|
||||
但 0.25ms=4kHz 按此模型仍不可行** —— 与实测"0.25ms 出事"方向一致,只是这次
|
||||
出事的表征是从站不应答而非负载封顶,机理未定。
|
||||
|
||||
**扫描工具的两个缺陷由这轮暴露并已修**:
|
||||
- 原脚本会把从站停报后的 `load_perm` 残值当实测写入结论 → 已加 `age_ms<1000` 闸,
|
||||
并新增 `gd32_load_samples` / `gd32_load_stale` 两列,使"这个数有没有被测到"可见。
|
||||
- 原脚本未记电机在线标志 → 已加 `motors_online` / `online_frac` / `motor_refresh_per_s`。
|
||||
|
||||
**台子现状**:从站仍停在 `wkc=-1`,恢复到 1ms 也不能自愈(native 的楔死看门狗未触发,`wr=0`)。
|
||||
继续测快档前需要先断电重启板子。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 五、机理:为什么是 2 倍而不是更多
|
||||
|
||||
从站每拍要写满输入 PDO。1.6.15 布局下输入 600B,`fillRxWindow` 在
|
||||
PDI_Isr/Sync0_Isr(preempt=0)里跑,**每拍都要跑一次**,与 CAN 是否有新帧无关。
|
||||
|
||||
先否定一个想当然的模型:把「负载 × 周期」当作每拍固定开销,反推得到
|
||||
808 / 566 / 445 / 384µs(4ms→0.5ms),**单调下降**——说明它量的是每拍总工作量,
|
||||
里面还含一项与周期成正比的后台摊薄,不是常数。改成两项模型:
|
||||
|
||||
```
|
||||
load(T) = F / T + R F = 每拍固定开销,R = 与周期无关的本底
|
||||
```
|
||||
|
||||
用两端点(4ms/20.2% 与 0.5ms/76.7%)辨识:
|
||||
|
||||
```
|
||||
F = (0.767 − 0.202) / (1/0.5 − 1/4) = 0.565 / 1.750 ≈ 323µs
|
||||
R = 0.202 − 323/4000 ≈ 11.7%
|
||||
```
|
||||
|
||||
用**未参与拟合**的中间两点检验:
|
||||
|
||||
| T | 模型 F/T+R | 实测 | 残差 |
|
||||
|---|---|---|---|
|
||||
| 2ms | 17.8% | 28.3% | +10.5 |
|
||||
| 1ms | 44.0% | 44.5% | +0.5 |
|
||||
| 0.25ms | **140.9%** | 99.7%(封顶) | 算术上 >100% → 不可行 |
|
||||
| 0.125ms | **269.9%** | 99.3%(封顶) | 同上 |
|
||||
|
||||
模型在 1ms 处几乎精确、2ms 处偏低(低速档还有别的开销项),但关键预测成立:
|
||||
**存在硬上限周期 F/(1−R) ≈ 367µs ≈ 2.7kHz**,实测分界正好落在它两侧
|
||||
(0.5ms=2kHz 可跑、0.25ms=4kHz 不可跑)。这条是可证伪的:若下一次扫描在 0.4ms
|
||||
也能满速运行且负载 <100%,本模型即被推翻。
|
||||
|
||||
用第四 b 节的**无 CAN** 数据独立重拟合,得 F≈272µs、R≈4.0%、上限 ≈284µs(3.5kHz)——
|
||||
同一模型形式、两组独立数据、F 相差 16%(正是 CAN 那份负担),说明 F/T+R 这个结构站得住。
|
||||
但**两组都没能直接测到上限本身**:带 CAN 时在 0.25–0.5ms 之间靠"功能塌陷"间接定位,
|
||||
无 CAN 时 0.25ms 直接从站停报。所以上限目前是被**夹在** (0.25ms, 0.5ms] 区间内,
|
||||
不是被测出来的。
|
||||
|
||||
结论:从站侧的极限周期由**每拍固定开销 F**决定,要突破只能减小每拍要搬的字节数
|
||||
(当前 600B / 5 槽 / 56B 电机槽),**不是调主站周期能解决的**。主机侧完全有余量
|
||||
(native 恒约 100% 单核,与周期无关),所以预算全在从站。
|
||||
|
||||
丢帧是另一条独立链路,与 ECAT 周期无关:`drop` 字段位于**段1(CAN RX 桥,偏移 24)**,
|
||||
属 GD32 桥接层自己的接收/投递计数,其消费者是主循环(`sys/task_runner.cpp:248`)而非
|
||||
ECAT PDO 填充路径 —— 所以 ECAT 周期快慢改不了它,实测四个档位恒等于 λ 也印证了这点。
|
||||
|
||||
但这里有一处**尚未闭合的矛盾**,不能糊过去:固件 `config.h:125` 的 1.6.15 设计说明写着
|
||||
「STATUS 自 1.6.9 起不入窗,滤掉后入窗流量降到 CONFIG 量级」「5 槽=5000 帧/s 对 4000 帧/s
|
||||
留 25% 余量」—— 按此设计意图,丢帧应当接近 0。实测却是 `drop` 以 3992/s 持续增长、
|
||||
且恰好等于 λ(含 STATUS 的全流量)。两种解释都还活着:
|
||||
(a) 该计数统计的是入窗**之前**的 CAN RX 队列/桥接层丢弃,STATUS 在入队环节就被丢,与窗口
|
||||
容量无关(则 `drop`≈λ 完全合理,设计意图也没被违背);
|
||||
(b) 字段在当前固件里已改义或读错偏移,`drop`≈λ 是巧合。
|
||||
本次数据不足以区分二者,**列为待验证第 1 项**。无论哪种,都不影响主结论:
|
||||
`drop` 与 ECAT 周期无关 → 提速不改善它。
|
||||
|
||||
---
|
||||
|
||||
## 六、局限
|
||||
|
||||
1. 单台单次,未做重复性与温漂验证。
|
||||
2. λ≈3993/s 是本次台子的 CAN 工况。λ 变则分界变,本报告给的是**相对关系**,
|
||||
0.5ms 这个具体数字不可直接搬到别的 CAN 流量下。
|
||||
3. 板载固件版本无法确认(见第二节),历史固件结论一律未采信;`rx_torn` / `pdo_reentry`
|
||||
恒零属**无检出功效**,只能表述为"本次测不到",**不得**读成"无撕裂/已修好"。
|
||||
4. `cyc_us.late` / `cyc_us.lost` 恒为 0(`ecat_native.c` 里是 Python 时代遗留占位),未接线,未使用。
|
||||
5. 扫描用 HTTP 以 10Hz 打 native,对被测对象有扰动;但主机 CPU 已恒 100% 单核,
|
||||
且 native 另有 HTTP 线程,扰动量级判断为小 —— **未单独做无负载对照实验**。
|
||||
6. `rxb_count_mode_per_cycle` / `rxb_lost_max` / `rxb_pending_max` 三列因环缓冲快照问题不可引用
|
||||
(见第三节),CSV 中保留原值但结论未使用。
|
||||
6b. **快档塌陷这条结论的证据来源要分清**:电机刷新率与 λ 的塌陷,取自扫描后另做的 8s/档
|
||||
定点实测,那四次都当场确认 `online=True` 且 seq 在动,所以是**真功能失效**,不是电机
|
||||
掉线造成的假象。但扫描 CSV 本身**没记在线标志**,事后无法复核。补该列前不要引用
|
||||
CSV 的快档数字。
|
||||
(2026-09-08 复测时电机已处于 `online=False`、`global_seq` 冻结状态 —— 总线本身正常,
|
||||
cycles 仍以 1000Hz 前进、CAN 侧仍以 ~3700 帧/s 收帧,停的是 STATUS 上报。
|
||||
该状态下的 0.4/0.35ms 快档读数一律不采信,也**未**写入本报告。)
|
||||
7. 每拍固定开销 F≈323µs 是从 4 个负载点**反推**的,不是直接计时测得;两项模型在
|
||||
2ms 档残差 +10.5 个百分点,说明低速档还有未建模的开销项。F 只能当量级用。
|
||||
|
||||
## 七、待验证(下一步该做的判据)
|
||||
|
||||
1. **`drop` 语义二选一**(第五节的 (a)/(b)):从 ESP32/WiFi 侧读固件那份 u32
|
||||
`rx_drop_count`(`app_spi_cmd_can_feedback.cpp:99`,CAN 通道队列满丢弃的权威口径),
|
||||
与 ECAT 侧 u16 `bridge.drop` 对速率。若两侧同为 ~4000/s → (a) 成立,丢帧发生在入窗之前,
|
||||
与 ECAT 无关;若 WiFi 侧接近 0 而 ECAT 侧 4000/s → (b) 成立,字段读错偏移,
|
||||
本报告的故障率轴需要整体重做。**这是唯一还悬着的承重结论。**
|
||||
2. **从站侧健康度目前不可读**:`OVF` 在 `fillRxWindow` 里每拍清零,只能取到单拍增量,
|
||||
而单拍增量落在 u8 分辨率以下即恒 0 —— 等于从站没有可用的累计丢帧仪表。
|
||||
要判窗口是否真的健康,需要固件改成累计值或主站侧累加(属改动,需另行批准)。
|
||||
3. **每拍固定开销 F 直接计时**:给 `fillRxWindow` 加计时(属改动,需另行批准)。
|
||||
目前 F≈323µs 是从 4 个负载点反推的;直接量到 F 就能把「上限 2.7kHz」从拟合
|
||||
变成算式,也才能判断减字节能否真的换到速度。
|
||||
4. **模型已被证伪(保守方向)**:F/T+R 预测 0.4ms 约 92% 可行,但 0.4ms/0.35ms 的复测因
|
||||
电机掉线作废,**上限究竟在 0.4 还是 0.5ms 仍未定**。恢复电机后需重跑这两档;
|
||||
在此之前,建议值只写"0.5ms 已验证可跑",不要外推到 0.4ms。
|
||||
5. **重复性**:同一档跑 3 次看 λ / GD32 负载 / 电机刷新率 / **online 标志** 的散布,
|
||||
确认 0.5ms 与 0.25ms 的分界不是单次偶然,也不是电机掉线的副产物。
|
||||
6. **0.5ms 档实跑功能验证**:在 0.5ms 下开电机页跑一段完整命令序列,确认前端
|
||||
`drainRx()` 与波形在 2kHz 下不积压(本调研只测了计数器,没测界面实际可用性)。
|
||||
@@ -0,0 +1,269 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>GD32 刷写流程对比:ESP32 中继(老)× EtherCAT 直灌(新)</title>
|
||||
<style>
|
||||
:root{--fg:#111827;--mut:#6b7280;--bd:#e5e7eb;--bg:#f8fafc;--acc:#1d4ed8;--warn:#b45309;--bad:#b91c1c;--ok:#15803d}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:#fff;color:var(--fg);font:15.5px/1.72 -apple-system,"Segoe UI","Microsoft YaHei",system-ui,sans-serif}
|
||||
main{max-width:1180px;margin:0 auto;padding:34px 28px 90px}
|
||||
h1{font-size:28px;line-height:1.3;margin:0 0 6px;letter-spacing:-.2px}
|
||||
h2{font-size:19px;margin:34px 0 11px;padding-bottom:7px;border-bottom:2px solid var(--bd)}
|
||||
h3{font-size:15.5px;margin:24px 0 7px;color:#374151}
|
||||
.meta{color:var(--mut);font-size:13.5px;margin-bottom:22px}
|
||||
.card{background:var(--bg);border:1px solid var(--bd);border-left:4px solid var(--acc);border-radius:8px;padding:14px 19px;margin:16px 0}
|
||||
.card.warn{border-left-color:var(--warn);background:#fffbeb}
|
||||
.card.bad{border-left-color:var(--bad);background:#fef2f2}
|
||||
.card.good{border-left-color:var(--ok);background:#f0fdf4}
|
||||
.card h3{margin:0 0 7px;font-size:15px;color:inherit}
|
||||
table{border-collapse:collapse;width:100%;font-size:13px;margin:13px 0}
|
||||
th,td{border:1px solid var(--bd);padding:6px 9px;text-align:left;vertical-align:top}
|
||||
th{background:#f1f5f9;font-weight:600;text-align:center}
|
||||
td.n,th.n{text-align:right;font-variant-numeric:tabular-nums}
|
||||
td.c{text-align:center}
|
||||
tr.hi td{background:#ecfdf5}
|
||||
tr.lo td{background:#fef2f2}
|
||||
code{background:#f1f5f9;padding:1.5px 5px;border-radius:4px;font-size:12.5px;font-family:ui-monospace,"Cascadia Mono",Consolas,monospace}
|
||||
.ev{color:var(--mut);font-size:11.5px;font-family:ui-monospace,Consolas,monospace}
|
||||
ul{padding-left:21px}
|
||||
.kpi{display:flex;gap:13px;flex-wrap:wrap;margin:20px 0 6px}
|
||||
.kpi div{flex:1 1 172px;border:1px solid var(--bd);border-radius:8px;padding:12px 14px;background:#fff}
|
||||
.kpi b{display:block;font-size:22px;line-height:1.25;color:var(--acc)}
|
||||
.kpi span{font-size:12px;color:var(--mut)}
|
||||
.kpi div.warn b{color:var(--warn)}
|
||||
.tabs{position:sticky;top:0;z-index:9;background:#fff;padding:14px 0 0;margin:22px 0 4px;border-bottom:2px solid var(--bd);display:flex;gap:8px;flex-wrap:wrap}
|
||||
.tab{appearance:none;border:1px solid var(--bd);border-bottom:none;background:#f1f5f9;color:#374151;padding:9px 17px;border-radius:8px 8px 0 0;font-size:14px;cursor:pointer;font-family:inherit;position:relative;top:2px}
|
||||
.tab:hover{background:#e8eef6}
|
||||
.tab.on{background:#fff;color:var(--acc);font-weight:600;box-shadow:0 2px 0 #fff}
|
||||
.tab .cnt{font-size:11px;color:var(--mut);font-weight:400}
|
||||
.page{display:none}.page.on{display:block}
|
||||
.chain{display:flex;align-items:stretch;flex-wrap:wrap;margin:16px 0 6px}
|
||||
.node{flex:1 1 128px;min-width:124px;border:1px solid var(--bd);border-radius:8px;background:#fff;padding:9px 11px;font-size:12.5px;line-height:1.5}
|
||||
.node b{display:block;font-size:13px;margin-bottom:2px}
|
||||
.node .t{color:var(--mut);font-size:11.5px}
|
||||
.node.hi{border-color:var(--acc);background:#eff6ff}
|
||||
.node.drop{border-color:#fca5a5;background:#fef2f2}
|
||||
.arrow{flex:0 0 24px;display:flex;align-items:center;justify-content:center;color:#9aa3af;font-size:17px}
|
||||
.byte{display:flex;gap:2px;flex-wrap:wrap;margin:9px 0}
|
||||
.byte div{border:1px solid var(--bd);border-radius:4px;padding:5px 7px;font-size:11.5px;font-family:ui-monospace,Consolas,monospace;background:#fff;text-align:center;line-height:1.45}
|
||||
.byte div small{display:block;color:var(--mut);font-size:10.5px;font-family:inherit}
|
||||
.byte div.key{background:#eff6ff;border-color:#bfdbfe}
|
||||
.byte div.meta2{background:#fffbeb;border-color:#fde68a}
|
||||
.legend{font-size:12.5px;color:var(--mut);margin:6px 0 0}
|
||||
.foot{margin-top:44px;padding-top:15px;border-top:1px solid var(--bd);font-size:12.5px;color:var(--mut)}
|
||||
</style></head><body><main>
|
||||
|
||||
<h1>GD32 刷写流程对比:ESP32 中继(老)× EtherCAT 直灌(新)</h1>
|
||||
<div class="meta">2026-09-13 · BOOT <code>v1.1.0.7</code>(<code>ER_ROM0 64,552 / 73,728 B</code>)· 主站 <code>ecat_native.exe</code> 22:18 构建 · 每条数字带 <span class="ev">文件:行号</span> · 镜像基准 = <code>DCU_APP</code> Debug <code>.hex</code> 展平后 <b>124,992 B</b>(本机实测)</div>
|
||||
|
||||
<div class="kpi">
|
||||
<div><b>2 604 → 527</b><span>同张镜像的链路往返次数(结构硬数字)</span></div>
|
||||
<div><b>48 → 241 B</b><span>每帧净落 flash 的字节数</span></div>
|
||||
<div><b>252 KB → 无</b><span>中间存储:ESP32 缓存分区整个消失</span></div>
|
||||
<div class="warn"><b>0 次</b><span>新流程的上板实测次数 —— 一条都没跑过</span></div>
|
||||
</div>
|
||||
|
||||
<div class="card"><h3>一句话结论</h3>
|
||||
新流程<b>不是"更快的刷法",是"少一层先决条件的刷法"</b>。省下的 5 倍往返是副产品,真正的收益是:出厂首刷不再要求板上先有一颗刷好并配好网的 ESP32。两条路并存互为备份,而且刻意复用了同一个 flash 写入者、同一个提交顺序、同一条掉电不变式 —— 新流程没有引入第二套"刷坏了怎么回来"的语义。</div>
|
||||
|
||||
<nav class="tabs">
|
||||
<button class="tab on" data-p="cmp">对比</button>
|
||||
<button class="tab" data-p="old">老流程 <span class="cnt">WiFi → ESP32 → SPI</span></button>
|
||||
<button class="tab" data-p="new">新流程 <span class="cnt">EtherCAT CoE SDO</span></button>
|
||||
</nav>
|
||||
|
||||
<section class="page on" id="p-cmp">
|
||||
<h2>逐维度对照</h2>
|
||||
<table>
|
||||
<tr><th style="width:126px">维度</th><th>老:WiFi → ESP32 中继 → SPI</th><th>新:PC 主站 → EtherCAT → SDO</th></tr>
|
||||
<tr class="hi"><td><b>先决条件</b></td><td>板上必须已有 ① 刷好固件的 ESP32 ② 配好网的 ESP32 ③ 上位机知道设备 IP。<span class="ev">esp32_server.py:830</span></td><td>总线上有电、从站在 PREOP 即可。<b>ESP32 可以完全没刷过。</b></td></tr>
|
||||
<tr><td><b>参与部件</b></td><td>浏览器 → 本机 2625(只发静态页)→ ESP32:80 → GD32。<span class="ev">esp32_server.py:1-7</span></td><td>浏览器 → 同一个 <code>ecat_native.exe</code>(页面也它发的)→ Npcap/I210 → GD32。<b>ESP32 不在链路里。</b></td></tr>
|
||||
<tr><td><b>镜像中间存储</b></td><td>ESP32 flash 的 <code>gd32_ota</code> 分区 <b>252 KB</b>@0x11000,两段式:先整份存下算 SHA,<code>/ota/relay</code> 才起中继。<span class="ev">partitions.csv:7-8;http_handlers.cpp:515;ota_relay.cpp:1128</span></td><td>PC 内存 216 KB 静态缓冲 <code>BOOT_DL_IMG_MAX 0x36000</code>。不落任何中间设备。</td></tr>
|
||||
<tr><td><b>浏览器→服务端请求</b></td><td>1 次整文件 POST(<code>/ota/upload</code>)+ 1 次 <code>/ota/relay</code> + 轮询。<b>无断点续传</b>:每次从偏移 0 重写。<span class="ev">upload-esp32.js:238-247;http_handlers.cpp:440</span></td><td>6 次 push(每次 24,576 B → hex 49,152 字符,卡在 native 的 65,535 B body 上限内)+ 1 次 run。<span class="ev">已冒烟:received 24576→49152→73728 精确累加</span></td></tr>
|
||||
<tr class="hi"><td><b>每帧净落 flash</b></td><td>线上一帧 = v2 头 16 B + DATA 64 B + CRC 4 B;64 B 里只有 <b>48 B</b> 是固件。<span class="ev">config.h:140-146;ota_relay.cpp:1135-1151</span></td><td>4 KB 块 = <b>17 个邮箱帧</b>(1 initiate 带 240 B + 16 段×247 B)⇒ 每帧 <b>241 B</b>,是老的 5.0 倍。<span class="ev">sdoserv.c:210,744</span></td></tr>
|
||||
<tr class="hi"><td><b>链路往返次数</b></td><td>124,992 / 48 = <b>2 604 包</b>,每包一次「发 → 等 ACK(50 ms 上限,最多重发 5 次)」。<span class="ev">ota_relay.cpp:1181,1185</span></td><td>124,992 / 4096 = <b>31 次 SDO 传输</b> = 527 帧,外加总长 / begin / commit 三个控制 SDO。</td></tr>
|
||||
<tr><td><b>鉴权</b></td><td>AES-128 三步双向握手,会话密钥作为前缀喂进每包 CRC32(防重放/篡改)。<span class="ev">OTA.cpp:976-1246,1414-1442</span></td><td><b>出厂形态零鉴权。</b>唯一裁决点 <code>ecatArmed()</code> = 配置页令牌 <b>或</b> A 区不可启动;commit 成功即收回令牌,用完即弃。<span class="ev">OTA.cpp:792-812,905-915</span></td></tr>
|
||||
<tr><td><b>主站复判身份?</b></td><td>不存在该问题 —— ESP32 是唯一持有 AES 密钥的对端。</td><td><b>不复判。</b>读 <code>0x1008</code> 只当日志读数。APP 的对象字典里没有 <code>0x2500/0x2501</code>(实测只有 0x1600/0x1A00/0x1C12/0x1C13/0x2000/0x6000/0x7000),写上去只会 SDO abort,擦不到一个字节。</td></tr>
|
||||
<tr class="hi"><td><b>掉电不变式</b></td><td colspan="2"><b>完全相同,且是刻意复用的。</b>擦除前先把 A 区三面标志(CHECK/AVAL/EN)全标 <code>NOT_AVAL</code> ⇒ 任何时刻掉电都表现为「AVAL 未置 = 不可启动 = 留守 BOOT 可重灌」。<span class="ev">OTA.cpp:635-637;config.h:37-43;main.cpp:229-402</span></td></tr>
|
||||
<tr><td><b>提交顺序</b></td><td>向量表校验 → <code>calculateAppCrc</code>+<code>writeAppCrc</code> → CHECK → AVAL → EN;跳转延后到 <code>process()</code> 排空 <code>pending_app_jump</code>。<span class="ev">OTA.cpp:372-410</span></td><td><b>逐字对齐同一条</b>,只在 CRC 之后多插一步收回令牌。CRC 必须先于 AVAL —— <code>verifyAppCrc()</code> 对擦除态 0xFFFFFFFF 返回 true,真门控是三面标志。<span class="ev">OTA.cpp:900-915</span></td></tr>
|
||||
<tr><td><b>对在跑业务的影响</b></td><td>SPI 中继是独立链路,不占 EtherCAT 环;但会话期间 BOOT 会丢弃 CAN OTA 帧。<span class="ev">OTA.cpp:303-309;Can.cpp:174-179</span></td><td><code>run</code> 全程持 <code>g_bus_lock</code> + <code>loop_stop()</code> ⇒ PDO 循环冻结数秒。但目标本来就得停在 BOOT(进不了 SAFEOP/OP),实际没有循环可冻。</td></tr>
|
||||
<tr><td><b>可观测性</b></td><td>进度靠轮询 <code>/system/status</code> 的 <code>relay_seq/relay_total_packets</code>;每 32 包一次健康检查。<span class="ev">upload-gd32.js:58,194;app_config.h:86</span></td><td>从站 <code>0x2500:03</code> 实时报已落盘字节、<code>:04</code> 报 arm/会话位;主站 <code>probe</code> 一次读回,进度条两侧各一个独立读数。</td></tr>
|
||||
<tr><td><b>刷 BOOT 自己</b></td><td colspan="2"><b>两条都不行。</b>BOOT 跑在 <code>0x08000000</code>,改不了自己正在执行的 flash ⇒ 永远只能 SWD/JTAG。新流程没改变这一点。</td></tr>
|
||||
<tr class="lo"><td><b>刷"正在跑 APP 的机器"</b></td><td>可以 —— 这正是老流程的主用途(APP 收 OTA 请求、复位进 BOOT 再灌)。<span class="ev">main.cpp:249-272</span></td><td><b>还不行。</b>A 区可启动 ⇒ 令牌是硬门,而置令牌的入口没接(<code>ecatArmSet</code> 目前只有 commit 里那个"收回"调用者)。要么 BOOT 加密会话加命令码(要花余下的 9,176 B),要么 APP 侧复位进 BOOT 前自己写 word8。</td></tr>
|
||||
<tr><td><b>BOOT flash 代价</b></td><td>基线 36,340 B</td><td>+28,212 B ⇒ 64,552 B,<b>余 9,176 B(用 87.6%)</b>;RAM 32,392 B(堆为分段下载放大到 16 KB,栈仍 1 KB)。<span class="ev">GD32E50X_BOOT.axf.map</span></td></tr>
|
||||
<tr><td><b>成熟度</b></td><td>产线在用;有 CLI 与刷写台账 —— 只有 <code>ESP32/scripts/flash_gd32.py</code> 写 <code>lab/_last_gd32_sha.txt</code>,且要拿到"板上版本变了 / uptime 归零"的证据才写。<span class="ev">scripts/flash_gd32.py:36,132-148</span></td><td><b>代码接通、零上板。</b>主站软件路径已冒烟(路由分发 / 缓冲累加 / 边界值全过),但 ESC 起板、<code>0x1008</code> 可读、SDO 字节序三条只有源码级推导。</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="card warn"><h3>关于"快了多少" —— 不能说</h3>
|
||||
老流程 ESP32 侧注释有实测:<b>正常 ~2 ms/包</b>、"777 包正常仅 ~1.6 s"。<span class="ev">ota_relay.cpp:356-368</span> 按这个常数换算,124,992 B = 2 604 包 ≈ <b>5.2 s</b> 纯传输。<br>
|
||||
新流程<b>没有任何一次真机计时</b>,这里填不出可比的数字 —— 5 倍往返差是代码结构决定的硬事实,端到端耗时不是。第一次上板要记的就是三件事:begin 实际阻塞多久、单块实际耗时、整轮总耗时。</div>
|
||||
|
||||
<div class="card good"><h3>为什么值得两条都留</h3>
|
||||
<ul>
|
||||
<li><b>覆盖场景不重叠</b>:出厂首刷只有新流程能做(那时还没有 ESP32 固件);刷在跑的机器目前只有老流程能做(有 AES 通道)。</li>
|
||||
<li><b>故障域独立</b>:WiFi 配不上 / ESP32 没刷 / 网卡拉不起 / 从站没进 PREOP —— 任意一边坏,另一边还是条路。</li>
|
||||
<li><b>共用同一个落盘点</b>:两条最终都调 <code>eraseApplicationArea / writeApplicationData / validateApplication / OTA_SET</code>;BOOT 里始终只有一个 flash writer,<code>ecatBegin</code> 在 SPI/CAN 会话活跃时直接拒绝并发。</li>
|
||||
</ul></div>
|
||||
|
||||
<h2>新流程的未证清单</h2>
|
||||
<table>
|
||||
<tr><th style="width:40px">#</th><th>待证</th><th>判据(一条就够)</th></tr>
|
||||
<tr><td class="c">1</td><td>BOOT 侧 ESC 起不起得来(EXMC/HBI/RST#/IRQ 这套移植层从未在 BOOT 跑过)</td><td>串口出 <code>[BOOT][ESC] PDI ok DL=… 邮箱可用</code>;否则 <code>FAIL PDI 未就绪 DL=… EEP=…</code></td></tr>
|
||||
<tr><td class="c">2</td><td>主站认不认出它在 BOOT</td><td><code>probe</code> 回 <code>name="GD32E508 DCU BOOT"</code> 且 <code>armed=1</code></td></tr>
|
||||
<tr><td class="c">3</td><td>SDO 分段载荷字节序(源码推得原序,未实测)</td><td>用现成 <code>/api/sdo</code> 往 <code>0x2501</code> 写 <code>00 01 02 … 0F</code>,看落 flash 后回读是否逐字节同序;不对就只在 <code>ObjFoeDlWrite</code> 一处加转换</td></tr>
|
||||
<tr><td class="c">4</td><td>begin 的整区预擦到底阻塞多久、8 s 超时够不够</td><td>串口时间戳与主站 <code>ec_SDOwrite</code> 往返计时对表</td></tr>
|
||||
<tr><td class="c">5</td><td>刷完 APP 真能起来</td><td>重新连接后 <code>probe</code> 的设备名变回 <code>"GD32E508 DCU"</code> —— 从另一次启动读的,不自证</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="card bad"><h3>顺带查到的一个既有缺陷(老流程同样有)</h3>
|
||||
<code>writeApplicationData()</code> 的循环条件是 <code>i + 4 <= size</code> ⇒ <b>不足 4 字节的尾巴被静默丢弃</b>,注释里写的"用 0xFF 补齐一个 word"代码并没有做。<span class="ev">OTA.cpp:762-782</span><br>
|
||||
老流程每包固定 48 B(4 对齐)所以碰不到,只有<b>非 4 对齐的镜像总长</b>会在最后一包丢 1~3 字节;新流程在主站侧补 0xFF 到 4 对齐并按补齐长度声明总量,绕开了这个坑,但<b>没有去改已在产线跑的 SPI 路径</b>。要修该单独一次改动、单独一次回归。</div>
|
||||
</section>
|
||||
<section class="page" id="p-old">
|
||||
<h2>老流程:浏览器 → ESP32 → SPI → GD32 BOOT</h2>
|
||||
<div class="chain">
|
||||
<div class="node"><b>浏览器</b><span class="t">算 SHA-256<br>整文件一次 POST</span></div><div class="arrow">→</div>
|
||||
<div class="node"><b>本机 2625</b><span class="t">只发静态页<br>不碰硬件</span></div><div class="arrow">→</div>
|
||||
<div class="node"><b>ESP32 :80</b><span class="t">/ota/upload<br>每 4 KB 写分区</span></div><div class="arrow">→</div>
|
||||
<div class="node drop"><b>ESP32 flash</b><span class="t">gd32_ota 252 KB<br>NVS 存元数据</span></div><div class="arrow">→</div>
|
||||
<div class="node"><b>/ota/relay</b><span class="t">AES 三步握手<br>逐包读回转发</span></div><div class="arrow">→</div>
|
||||
<div class="node hi"><b>GD32 BOOT</b><span class="t">SPI ISR 入队<br>主循环写 flash</span></div>
|
||||
</div>
|
||||
<p class="legend">红框 = 新流程里整个消失的一跳。</p>
|
||||
|
||||
<h3>关键常量</h3>
|
||||
<table>
|
||||
<tr><th>项</th><th class="n">值</th><th>出处</th></tr>
|
||||
<tr><td>每帧总长 / 其中固件数据</td><td class="n">64 B / <b>48 B</b></td><td class="ev">config.h:140-146</td></tr>
|
||||
<tr><td>SPI v2 帧开销</td><td class="n">20 B(16 头 + 4 CRC)</td><td class="ev">spi_link_v2.h:34-36</td></tr>
|
||||
<tr><td>SPI 时钟 / 注释实测每帧</td><td class="n">13.333 MHz / ≈96 µs</td><td class="ev">sdkconfig.defaults:20;spi_master.cpp:80</td></tr>
|
||||
<tr><td>ISR→主循环 DATA 槽 / flash 写缓冲槽</td><td class="n">4 / <b>8</b></td><td class="ev">config.h:178,180</td></tr>
|
||||
<tr><td>ESP32 上传块 / 固件上限</td><td class="n">4 096 B / 216 KB</td><td class="ev">app_config.h:74,76</td></tr>
|
||||
<tr><td>单包等 ACK / 最多重发 / 跨包停刷</td><td class="n">50 ms / 5 次 / 10 s</td><td class="ev">ota_relay.cpp:1181,1185,53</td></tr>
|
||||
<tr><td>转发预算</td><td class="n">25 ms/包,总 10~120 s</td><td class="ev">ota_relay.cpp:356-368</td></tr>
|
||||
<tr><td>BOOT 会话超时 / APP 延后启动窗口</td><td class="n">50 000(≈1 s) / 10 000 ms</td><td class="ev">config.h:168,169</td></tr>
|
||||
<tr><td>握手各步超时(ESP32 侧)</td><td class="n">800 / 5000 / 3000 / 3000 ms</td><td class="ev">ota_relay.cpp:942,1036,1062,1090</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>一帧 DATA 的字节排布</h3>
|
||||
<div class="byte">
|
||||
<div class="key">0–3<small>会话号 LE</small></div>
|
||||
<div class="key">4–7<small>包号 LE</small></div>
|
||||
<div class="meta2">8–55<small>48 B AES-128 密文</small></div>
|
||||
<div class="key">56–59<small>结束标志 IFEND</small></div>
|
||||
<div class="key">60–63<small>CRC32(密钥参与)</small></div>
|
||||
</div>
|
||||
<p class="legend">外面再套 SPI v2 头:SOF0/SOF1/VER/FLAGS、SESSION@4、TX_SEQ@8、ACK_SEQ@9、CREDIT@10、TYPE@11、LEN@12,尾部 4 B MPEG-2 CRC32。<span class="ev">spi_link_v2.h:34-51;OTA.cpp:1002-1018;ota_relay.cpp:1135-1151</span></p>
|
||||
|
||||
<h3>握手三步(双向认证)</h3>
|
||||
<table>
|
||||
<tr><th style="width:38px">步</th><th>上位机 → BOOT</th><th>BOOT → 上位机</th><th>出处</th></tr>
|
||||
<tr><td class="c">1</td><td>魔术字 <code>00 AC</code></td><td>回显 ACK,转 <code>WAITING_RANDOM</code></td><td class="ev">OTA.cpp:976-988</td></tr>
|
||||
<tr><td class="c">2</td><td>16 B <code>nonce_server</code></td><td>36 B = UID(12)+nonce[8..15](8)+<code>AES(K_master,K_session)</code>(16),转 <code>WAITING_CONFIRM</code></td><td class="ev">OTA.cpp:1195-1219</td></tr>
|
||||
<tr><td class="c">3</td><td><code>AES(K_session, nonce XOR 0x55)</code> 16 B</td><td><code>0xBA 0xC0</code>,转 <code>CHECKING_FIRMWARE</code></td><td class="ev">OTA.cpp:1224-1246</td></tr>
|
||||
</table>
|
||||
<p>状态机全序 <code>IDLE → WAITING_RANDOM → WAITING_CONFIRM → CHECKING_FIRMWARE → RECEIVING_FIRMWARE → FINISHED</code><span class="ev">OTA.h:223-230</span>。每包 CRC32(IEEE) 先把 16 B 会话密钥喂进去、再喂 48 B 明文,所以改一比特或重放旧包都对不上。<span class="ev">OTA.cpp:1414-1442</span> 主密钥两端硬编码同值。<span class="ev">config.h:158-161;app_config.h:150-153</span></p>
|
||||
|
||||
<h3>从收到字节到落 flash</h3>
|
||||
<ul>
|
||||
<li>EXTI ISR 只把帧入队(4 槽)→ <code>SpiOta::poll()</code> 在主循环解密校验并回 ACK → 再 drain 8 槽环形缓冲逐字 <code>fmc_word_program</code>。<span class="ev">SpiOta.cpp:99-114;OTA.cpp:725-757</span></li>
|
||||
<li>背压靠 credit:<code>g_localCredit = 8 − flash_buf_cnt − pending</code>。<b>没有固定速率上限,也没有每包往返耗时统计。</b><span class="ev">OTA.cpp:1082-1085</span></li>
|
||||
<li>去重:<code>包号 < next_expected_packet</code> 判重,只回 ACK 不二次写 flash;重复 SPI 帧走 DUP-SEQ 回放缓存的最终 ACK。<span class="ev">OTA.cpp:1021-1031;SpiOta.cpp:409-427</span></li>
|
||||
<li>擦除只擦本轮需要的页:<code>ceil(expected_size/2048)+1</code> 页,另单独保证 CRC 页被擦。<span class="ev">OTA.cpp:635-712</span></li>
|
||||
<li>ACK 帧:<code>0xBA 0xC0</code> + 4 B 期望下一包号;末包特殊 seq <code>0xF0F0F0F0</code>;错误帧多 1 B 错误码。<span class="ev">OTA.cpp:251-260,1103,1285-1295</span></li>
|
||||
<li>前端防呆:选文件即读前 8 字节判包身份(MSP∈SRAM、复位向量∈A 区;落 BOOT 区即拒),HEX 自动展平成 BIN(间隙填 0xFF)。<span class="ev">file-handle.js:458-516,563-574</span></li>
|
||||
</ul>
|
||||
|
||||
<h3>失败与恢复分流</h3>
|
||||
<p><code>main.cpp run()</code> 读三面标志分流:二者皆 AVAL 且 CRC 过 → 直接跳;CHECK AVAL / EN NOT_AVAL → 开 10 s 等 OTA 窗口;A 区坏但 B 区有效 → <code>move_app_data()</code> 整体搬回;都不行 → 长期留守等重下。FWDGT 复位会把 EN 标 NOT_AVAL,阻止反复跳进卡死的固件。<span class="ev">main.cpp:204-210,229-402</span></p>
|
||||
|
||||
<div class="card warn"><h3>代码里没有的东西(别当成有据)</h3>
|
||||
<ul>
|
||||
<li>SPI 中继链路的<b>实测</b>端到端耗时 / KB 每秒 —— 只有预算常数和注释里的 ~2 ms/包。</li>
|
||||
<li>WiFi/HTTP 上传的断点续传:NVS 元数据预留了 <code>stored_size/last_seq</code>,HTTP 路径<b>未实装</b>。<span class="ev">gd32_flash_store.h:23;ota_coordinator.cpp:246</span></li>
|
||||
<li>BOOT 侧握手各步的独立毫秒超时:统一走 <code>OTA_TIMEOUT</code> 计数。<span class="ev">OTA.cpp:431</span></li>
|
||||
</ul></div>
|
||||
|
||||
<div class="card"><h3>同一后端的三条入口</h3>
|
||||
浏览器、<code>tools/flash_gd32.py</code>(裸 BIN、不写台账)、<code>tools/ESP32/scripts/flash_gd32.py</code>(带 <code>{"total_size":N}</code> 头、写台账)三条都打 ESP32 固件 80 端口的同一组 <code>/ota/upload + /ota/relay + /system/status</code>,<b>没有独立通道</b>;设备侧对带不带 JSON 头都兼容。<span class="ev">http_handlers.cpp:403-407;tools/flash_gd32.py:88;scripts/flash_gd32.py:98</span></div>
|
||||
</section>
|
||||
<section class="page" id="p-new">
|
||||
<h2>新流程:PC 主站 → EtherCAT 邮箱 → BOOT 写 flash</h2>
|
||||
<div class="chain">
|
||||
<div class="node"><b>浏览器</b><span class="t">ecat.html「BOOT 刷写」<br>页内自带 HEX→BIN</span></div><div class="arrow">→</div>
|
||||
<div class="node"><b>ecat_native :8090</b><span class="t">/api/ota/ecat<br>镜像攒在 PC 内存</span></div><div class="arrow">→</div>
|
||||
<div class="node"><b>I210 / Npcap</b><span class="t">loop_stop 独占<br>CoE SDO 分段下载</span></div><div class="arrow">→</div>
|
||||
<div class="node"><b>LAN9252</b><span class="t">SM0 邮箱 256 B<br>PRAM FIFO 逐 DWORD</span></div><div class="arrow">→</div>
|
||||
<div class="node hi"><b>GD32 BOOT</b><span class="t">SSC 攒满 4 KB 才回调<br>OTAHandler 写 flash</span></div>
|
||||
</div>
|
||||
<p class="legend">只有 5 跳,且 ESP32 完全不在里面。页面本身也是 <code>ecat_native</code> 发的,所以没有跨域、没有第二个服务。</p>
|
||||
|
||||
<h3>操作时序(六个 op,只有后两个碰总线)</h3>
|
||||
<table>
|
||||
<tr><th style="width:100px">op</th><th>做什么</th><th class="n">碰总线?</th><th>要点</th></tr>
|
||||
<tr><td><code>open</code></td><td>声明镜像长度,清 PC 侧缓冲</td><td class="c">否</td><td>超 221,184 B 直接拒。<b>不抢 <code>g_bus_lock</code>、不 <code>loop_stop</code></b>,所以上传过程不会把正在跑的电机循环冻掉</td></tr>
|
||||
<tr><td><code>push</code></td><td>追加一段 hex 到镜像尾部</td><td class="c">否</td><td>24,576 B/次 = hex 49,152 字符,卡在 <code>handle_conn</code> 的 65,535 B body 上限内</td></tr>
|
||||
<tr><td><code>status</code> / <code>abort</code></td><td>查已收字节 / 丢缓冲</td><td class="c">否</td><td><code>abort</code> 只清 PC 侧,总线一个字节都没动</td></tr>
|
||||
<tr><td><code>probe</code></td><td>读 <code>0x1008</code> 设备名 + <code>0x2500:03/:04</code></td><td class="c">是</td><td><b>纯诊断读数,不是门槛</b></td></tr>
|
||||
<tr><td><code>run</code></td><td>真正的灌写:总长 → begin → 31 块 → commit</td><td class="c">是</td><td>全程持 <code>g_bus_lock</code> + <code>loop_stop()</code>;只有 commit 成功才不把 PDO 循环还回来(从站正在硬复位)</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>BOOT 侧只挂两个对象</h3>
|
||||
<table>
|
||||
<tr><th style="width:92px">索引</th><th class="n">类型</th><th>条目</th><th>语义</th></tr>
|
||||
<tr><td><code>0x2500</code></td><td class="n">REC ×4<br>UINT32</td><td>:0 条目数 / :1 命令 / :2 总长 / :3 已收 / :4 状态</td>
|
||||
<td>:1 写 <code>1</code>=begin、<code>2</code>=commit、<code>3</code>=abort;:4 bit0=允许刷写、bit1=会话活跃、高 16 位=错误码。<b>:3/:4 只读</b>,读的时候现取 <code>ota.ecatReceived()</code></td></tr>
|
||||
<tr><td><code>0x2501</code></td><td class="n">VAR<br>OCTETSTRING<br>4 096 B</td><td>只有 <code>:00</code></td>
|
||||
<td>数据通道。建成 VAR 而不是 ARR:VAR 的 <code>maxSubindex</code> 恒为 0,所以<b>只能写 <code>:00</code>,且 complete access 一律 UNSUPPORTED_ACCESS</b> ⇒ SOEM 侧必须传 <code>CA=FALSE</code>。<span class="ev">objdef.c:1278,1295,1352</span></td></tr>
|
||||
</table>
|
||||
<p>BitSize 是 UINT16 ⇒ 单条目天花板 8191 B,块长取 4096 就是这个原因。SSC 的分段下载<b>攒满整块才调一次 <code>OBJ_Write</code></b>(最后一帧才分发,且分发前不做通用长度校验),所以从站侧不需要 begin/data/commit 拼包状态机。<span class="ev">sdoserv.c:292-295;objdef.c:1352;sdoserv.c:714</span></p>
|
||||
|
||||
<h3>一个 4 KB 块在总线上的样子</h3>
|
||||
<div class="byte">
|
||||
<div class="meta2">initiate<small>带 240 B</small></div>
|
||||
<div class="key">段 1<small>247 B</small></div>
|
||||
<div class="key">段 2<small>247 B</small></div>
|
||||
<div class="key">…<small>×14</small></div>
|
||||
<div class="key">段 16<small>151 B</small></div>
|
||||
</div>
|
||||
<p class="legend">共 <b>17 帧搬 4096 B</b>。段长上限两端各算一次取小者:SOEM <code>maxdata = mbx_l − 0x10 = 240</code>(首帧带数),SSC 段 <code>256 − 6 − 3 = 247</code>。全图 124,992 B ⇒ 31 块 = 527 帧。<span class="ev">ethercatcoe.c:346,450;sdoserv.c:210</span></p>
|
||||
|
||||
<h3>放行判据只有一个执行点</h3>
|
||||
<div class="card good">
|
||||
<p><code>ecatArmed() = 配置页 word8 == 0xACA70001 || !(CHECK==AVAL && EN==AVAL)</code></p>
|
||||
<p>后半句和 <code>main.cpp run()</code> 决定"跳 APP"用的是同两个 flash 字,所以四种 BOOT 留守情形(空板首刷 / 下载中断电 / A 区 CRC 失败 / FWDGT 复位)天然放行,不需要任何先决条件;反过来 APP 正常在跑时两字皆 <code>OTA_AVAL</code>,令牌就是硬门 —— 而令牌写不进(SDO 摸不到配置页),所以刷不动一台正常机器。</p>
|
||||
<p><code>ecatCommit()</code> 成功路径里先 <code>ecatArmSet(false)</code> 收回令牌再置三面标志:用完即弃。此刻 CHECK/EN 还是 <code>eraseApplicationArea</code> 标的 NOT_AVAL,所以收回这一拍救砖分支照样为真,不影响本次提交。<span class="ev">OTA.cpp:792-812,905-915</span></p>
|
||||
</div>
|
||||
|
||||
<h3>刻意复用老流程的部分(一条都没新写)</h3>
|
||||
<ul>
|
||||
<li>擦:<code>eraseApplicationArea()</code> —— 同样先标三面 NOT_AVAL,同样按 <code>ceil(size/2048)+1</code> 页。</li>
|
||||
<li>写:<code>writeApplicationData()</code> —— 唯一区别是主站先把镜像补 0xFF 到 4 对齐(<code>ecatFeed</code> 另有游标 4 对齐前置校验)。</li>
|
||||
<li>校验:<code>validateApplication()</code> —— MSP∈SRAM、复位向量∈A 区,与前端包身份判定同判据。</li>
|
||||
<li>提交:CRC → CHECK → AVAL → EN 同序;跳转都靠 <code>process()</code> 排空 <code>pending_app_jump</code>,<b>不在收包/邮箱上下文里跳</b>(否则会把 SM0 半包留在总线上)。</li>
|
||||
<li>并发:<code>ecatBegin</code> 在 <code>isActive()</code>(SPI/CAN 会话活跃)时直接拒绝 ⇒ BOOT 里始终只有一个 flash writer。</li>
|
||||
<li>退出:<code>jumpToApplication()</code> 里补 <code>bootEcatDeinit()</code> 拆 EXTI1/NVIC、LAN9252 IRQ 引脚、EXMC、DWT,否则 APP 会吃到 BOOT 留的 pending 中断并在被占用的总线上重跑 init。</li>
|
||||
</ul>
|
||||
|
||||
<h3>新增的移植层花了多少</h3>
|
||||
<table>
|
||||
<tr><th>项</th><th class="n">值</th><th>备注</th></tr>
|
||||
<tr><td>无 EtherCAT 基线</td><td class="n">36,340 B</td><td class="ev">Phase 0 对照组:关掉唯一调用者后 --gc-sections 剥回此值</td></tr>
|
||||
<tr><td>邮箱-only SSC(空移植层桩件)</td><td class="n">57,816 B</td><td>净增 21,472 B —— 原方案声称的"12 KB"是假的</td></tr>
|
||||
<tr><td><b>真移植层 + 会话(定版)</b></td><td class="n"><b>64,552 B</b></td><td>再 +6,736 B(EXMC/HBI/CSR 双读/PRAM/RST#/IRQ + 0x2500/0x2501 + arm 门控);<b>余 9,176 B</b></td></tr>
|
||||
<tr><td>RAM</td><td class="n">32,392 / 131,072 B</td><td>堆 0xC00→<b>0x4000</b>(每笔分段下载 <code>ALLOCMEM(块长)</code> 走 malloc);栈仍 1 KB ⇒ SDO 回调里不许开块级大数组</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<div class="foot">生成:Qoder 会话 · 证据基线 <code>GD32E50X_BOOT</code> v1.1.0.7 / <code>tools/ecat</code> ecat_native 22:18 / <code>ESP32_COMM</code> 现网分支 · 本轮改动均未提交 git</div>
|
||||
</main>
|
||||
<script>
|
||||
document.querySelectorAll(".tab").forEach(function(b){b.onclick=function(){
|
||||
document.querySelectorAll(".tab").forEach(function(x){x.classList.remove("on")});
|
||||
document.querySelectorAll(".page").forEach(function(x){x.classList.remove("on")});
|
||||
b.classList.add("on");
|
||||
document.getElementById("p-"+b.dataset.p).classList.add("on");
|
||||
scrollTo(0,0);
|
||||
};});
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,107 @@
|
||||
# EtherCAT 跳拍优化实验报告
|
||||
|
||||
日期:2026-09-17
|
||||
台子:i7-14700K + I210「以太网 2」100Mbps + GD32E508 从站(3 电机在线)
|
||||
主判据:**不跳拍率**(`late_per_s` / `skip_per_s`,来自 `/api/motor/dbg` 拍环 `k>0`)
|
||||
原始数据:`lab/skip_meter/*.json`
|
||||
|
||||
## 一、结论(按性价比)
|
||||
|
||||
| 措施 | 1ms late/s | 判定 |
|
||||
|---|---|---|
|
||||
| 基线(进程 Normal,原绑定) | ~10–25(漂) | 起点 |
|
||||
| 进程 RealTime | 均值更好(~8–12) | **有效,保留** |
|
||||
| 关 I210 上 VMware Bridge 等协议 | 有助(单独测过 4.1/s 一枪;混方差) | **有效,保留** |
|
||||
| 关中断裁决 ITR=0 | **70/s** | **有害,已还原** |
|
||||
| 关 EEE | **98/s** | **有害,已还原** |
|
||||
| ITR=50(非法)/固定低 ITR | 更差或无效 | **已还原** |
|
||||
| 关 IPv4/IPv6(本卡仅链路本地) | 无可测收益 | **已重新打开** |
|
||||
| **周期 2ms** | **0.06/s ≈ 不跳** | **主判据下的最优运行点** |
|
||||
| 周期 4ms | 0 | 同上,更宽裕 |
|
||||
|
||||
**主判据一句话**:
|
||||
- 必须 **1ms**:当前约 **10–17 late/s**(work p50≈366µs,余量只有 ~630µs,偶发 1ms 尖峰必跳)。
|
||||
- 允许 **2ms**:约 **0.06 late/s(16s 仅 1 拍)**,达成 500.00Hz,**不跳拍率优先时选这个**。
|
||||
|
||||
## 二、机理回顾(本轮再确认)
|
||||
|
||||
跳拍 = `pdo_worker` 自旋到绝对截止点时 `next_t <= now`,补 `k` 个周期。
|
||||
本轮形态:
|
||||
|
||||
1. **send 长尾**(`pcap_sendpacket` ≈1ms,rxc=1)— Npcap/驱动/DPC,不是从站。
|
||||
2. **rx 长尾**(等回包 ≈1ms,rxc≈200+)— 主机接收路径,不是总线被占。
|
||||
|
||||
从站 `load_perm≈336‰`、WKC 3/3、layout=dense,**没有被别的 EtherCAT 用户抢走**。
|
||||
|
||||
注意:本轮中后期 `rx p50` 从早晨的 ~90µs 变成 ~330µs(进程重启/绑定调整后未回到 90)。
|
||||
这压缩了 1ms 档余量,是 late 从 ~10 抬到 ~15–25 的主要原因之一;机理未完全闭合(怀疑桌面负载/DPC 形态漂移,非网卡高级属性)。
|
||||
|
||||
## 三、已落地的改动(可回滚)
|
||||
|
||||
### 3.1 保留
|
||||
|
||||
1. **`ecat_native` 启动尝试 RealTime**(`tools/ecat/ecat_native.c`)
|
||||
- 先提 `SE_INC_BASE_PRIORITY_NAME` 再 `SetPriorityClass(RT)`。
|
||||
- **实测自设仍会被钳成 High**(令牌无该特权时 API 仍可能返回成功/或被系统钳位)。
|
||||
- 因此 **`esp32_server.py` 在 Popen 后从外部 `SetPriorityClass(RT)`**(能粘住,已验证)。
|
||||
- 备份:`tools/ecat/build_native/ecat_native.pre-rt.exe`。
|
||||
|
||||
2. **I210 绑定裁剪**(仅「以太网 2」)
|
||||
- 已禁用:`vmware_bridge`, `ms_pacer`, `ms_lldp`, `ms_rspndr`, `ms_lltdio`, `ms_server`, `ms_msclient`
|
||||
- 仍启用:`INSECURE_NPCAP`(必须)、`ms_tcpip`/`ms_tcpip6`(已恢复)
|
||||
- 快照:`lab/skip_meter/nic_bind_*.json`
|
||||
|
||||
3. **I210 高级属性**:已还原为实验前原值(ITR=适应性,EEE 按系统,FlowControl Rx+Tx 等)。
|
||||
|
||||
### 3.2 运行态
|
||||
|
||||
- 周期:**已恢复 1ms**(与实验前一致,避免破坏你当前用法)
|
||||
- WKC 3/3、OP、loop=1、`pcore_mask=0x50`
|
||||
- 进程优先级:当前为 **RealTime**(外部设置)
|
||||
|
||||
## 四、回滚
|
||||
|
||||
```powershell
|
||||
# 1) 恢复 I210 协议绑定
|
||||
python scripts/skip_bind_experiment.py --restore
|
||||
python scripts/skip_bind_noip.py --restore # 若曾关过 IP,脚本会 Enable
|
||||
|
||||
# 2) 恢复 I210 高级属性(用任一 nic_before_*.json)
|
||||
python scripts/skip_nic_experiment.py --restore --snap lab/skip_meter/nic_before_1789580690.json
|
||||
|
||||
# 3) 还原旧主站二进制
|
||||
# 先停 ecat_native,再:
|
||||
Copy-Item tools\ecat\build_native\ecat_native.pre-rt.exe tools\ecat\build_native\ecat_native.exe -Force
|
||||
# 然后由 esp32_server 自动重拉,或手动跑 ecat_web.bat
|
||||
|
||||
# 4) 进程优先级改回 High(不必还原绑定时用)
|
||||
Get-Process ecat_native | ForEach-Object { $_.PriorityClass = 'High' }
|
||||
```
|
||||
|
||||
网卡高级属性原始快照:`lab/skip_meter/nic_before_1789580690.json`(含 ITR=65535 等)。
|
||||
|
||||
## 五、标定/实验脚本
|
||||
|
||||
| 脚本 | 用途 |
|
||||
|---|---|
|
||||
| `scripts/skip_rate_meter.py` | 标准跳拍率标定(主判据) |
|
||||
| `scripts/skip_host_snapshot.py` | 主机/I210/进程现场快照 |
|
||||
| `scripts/skip_prio_ab.py` | High vs RealTime A/B |
|
||||
| `scripts/skip_quiet_periods.py` | 1/2/4ms 静默对比 |
|
||||
| `scripts/skip_nic_experiment.py` | 网卡高级属性实验 + `--restore` |
|
||||
| `scripts/skip_bind_experiment.py` | 绑定裁剪 + `--restore` |
|
||||
| `scripts/skip_restart_native.py` | 进程热重启对照 |
|
||||
|
||||
## 六、建议
|
||||
|
||||
1. **不跳拍优先 → 生产用 2ms**(本轮实测 16s 仅 1 拍 late)。若必须 1ms,接受 ~1%/拍 late,并保证 RealTime + 无 VMware 桥。
|
||||
2. **重启 `esp32_server` 后**,新代码会在拉起 `ecat_native` 时自动提 RealTime;当前若只重启 native 不重启 server,需再手动设一次。
|
||||
3. 勿再关 ITR/EEE 做“低延迟”优化——本台子上是反向的。
|
||||
4. `rx p50` 90→330µs 未闭合,下一轮可专查桌面 DPC(关 360/同步盘/任务管理器常刷)对照。
|
||||
|
||||
## 七、我在这轮里错过又改掉的判断
|
||||
|
||||
1. 「关中断裁决能降 send 长尾」→ 实测 70/s,立刻还原。
|
||||
2. 「关 EEE 能降功耗相关抖动」→ 实测 98/s,立刻还原。
|
||||
3. 「进程自设 RealTime 会粘住」→ 日志显示成功但 `PriorityClass=High`,改为启动方外部设置。
|
||||
4. 首版标定器拿 unix 时间去比 `now_s()` 拍环 → 恒 0 late,已修。
|
||||
@@ -0,0 +1,359 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>对接电机 2/3 积分差归因报告</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg:#0f1419; --card:#161c24; --ink:#e8eef7; --mut:#8a9bb0;
|
||||
--ok:#3d8f5b; --warn:#d4a017; --bad:#e25555; --line:#2a3340;
|
||||
}
|
||||
* { box-sizing:border-box; }
|
||||
body {
|
||||
margin:0; background:var(--bg); color:var(--ink);
|
||||
font-family:"Segoe UI","Microsoft YaHei",system-ui,sans-serif;
|
||||
line-height:1.55; padding:24px;
|
||||
}
|
||||
h1 { font-size:22px; font-weight:600; margin:0 0 6px; }
|
||||
h2 { font-size:16px; font-weight:600; margin:0 0 12px; }
|
||||
.sub { color:var(--mut); font-size:13px; margin-bottom:18px; }
|
||||
.card {
|
||||
background:var(--card); border:1px solid var(--line); border-radius:12px;
|
||||
padding:18px 20px; margin-bottom:16px;
|
||||
}
|
||||
.lead { margin:0 0 12px; color:var(--ink); }
|
||||
code {
|
||||
background:#0c1014; padding:1px 6px; border-radius:4px;
|
||||
font-family:Consolas,monospace; font-size:13px; color:#c5d4e8;
|
||||
}
|
||||
ul.verdict { list-style:none; padding:0; margin:0 0 14px; }
|
||||
ul.verdict li {
|
||||
margin:0 0 10px; padding:10px 12px; background:#121820;
|
||||
border-left:3px solid var(--line); border-radius:0 8px 8px 0;
|
||||
}
|
||||
.tag {
|
||||
display:inline-block; font-size:12px; font-weight:600; padding:2px 8px;
|
||||
border-radius:999px; margin-right:8px; vertical-align:middle;
|
||||
}
|
||||
.tag.ok { background:rgba(61,143,91,.2); color:#6dca8f; }
|
||||
.tag.warn { background:rgba(212,160,23,.2); color:#f0c35a; }
|
||||
.callout {
|
||||
background:#121a14; border:1px solid #2a4a34; border-radius:8px;
|
||||
padding:12px 14px; margin-top:8px;
|
||||
}
|
||||
.callout.small { font-size:13px; background:#1a1520; border-color:#3a2a4a; }
|
||||
table {
|
||||
width:100%; border-collapse:collapse; font-size:12.5px; margin-top:8px;
|
||||
}
|
||||
th, td {
|
||||
border-bottom:1px solid var(--line); padding:6px 8px; text-align:left;
|
||||
}
|
||||
th { color:var(--mut); font-weight:500; }
|
||||
td.ok { color:#6dca8f; }
|
||||
td.warn { color:#f0c35a; }
|
||||
td.bad { color:#ff8a8a; }
|
||||
.annot-grid {
|
||||
display:grid; grid-template-columns:1fr 1fr; gap:12px;
|
||||
}
|
||||
@media (max-width:800px) { .annot-grid { grid-template-columns:1fr; } }
|
||||
.annot {
|
||||
background:#121820; border-radius:8px; padding:12px; border-left:3px solid var(--line);
|
||||
}
|
||||
.annot.ok { border-left-color:var(--ok); }
|
||||
.annot.warn { border-left-color:var(--warn); }
|
||||
.annot.bad { border-left-color:var(--bad); }
|
||||
.ann-title { font-weight:600; margin-bottom:6px; font-size:13px; }
|
||||
.annot p { margin:0; font-size:13px; color:#c5d0dc; }
|
||||
.arrow-legend { margin-top:12px; font-size:13px; color:var(--mut); }
|
||||
.arrow-legend div { margin:4px 0; }
|
||||
.arrow { display:inline-block; width:18px; height:3px; margin-right:8px; vertical-align:middle; }
|
||||
.ok-arrow { background:var(--ok); }
|
||||
.warn-arrow { background:var(--warn); }
|
||||
.bad-arrow { background:var(--bad); }
|
||||
.metrics {
|
||||
display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-top:10px;
|
||||
}
|
||||
@media (max-width:800px) { .metrics { grid-template-columns:1fr 1fr; } }
|
||||
.metric {
|
||||
background:#121820; border:1px solid var(--line); border-radius:8px; padding:10px 12px;
|
||||
}
|
||||
.metric .k { color:var(--mut); font-size:12px; }
|
||||
.metric .v { font-size:20px; font-weight:600; margin-top:2px; }
|
||||
.metric .v.ok { color:#6dca8f; }
|
||||
.metric .v.warn { color:#f0c35a; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>对接电机 2 / 3:积分差是谁造成的?</h1>
|
||||
<div class="sub">
|
||||
数据:ECAT 无源采集 <code>lab/ecat_passive_latest.csv</code> · 周期 0.25ms ·
|
||||
理论:刚性镜像对接时 <code>v2+v3≡0</code>,<code>∫(v2+v3)dt≡0</code>,<code>a2+a3=常数</code><br/>
|
||||
方法:对齐 cycles 后做梯形积分;先加后积与先积后加一致。
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>关键数字</h2>
|
||||
<div class="metrics">
|
||||
<div class="metric"><div class="k">巡航 |v| N2 / N3</div>
|
||||
<div class="v ok">199.38 / 199.38</div></div>
|
||||
<div class="metric"><div class="k">巡航段 ∫(v2+v3) 平均</div>
|
||||
<div class="v ok">0.11°</div></div>
|
||||
<div class="metric"><div class="k">停车段 ∫(v2+v3) |均值|</div>
|
||||
<div class="v warn">6.43°</div></div>
|
||||
<div class="metric"><div class="k">干净起动 Δt 中位/均值</div>
|
||||
<div class="v ok">0.00 / 0.38 ms</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<section class="card">
|
||||
<h2>通俗结论:到底是谁慢?</h2>
|
||||
<p class="lead">先说人话:<strong>两台电机并不是“有一台全程都慢”</strong>。
|
||||
你看到的积分差,主要不是巡航时谁转得慢,而是<strong>快停下 / 刚要走的那几十一到几百毫秒里,两台对不齐</strong>,
|
||||
把理论应为 0 的 <code>∫(v2+v3)dt</code> 顶出 5~20° 左右的偏差。</p>
|
||||
|
||||
<ul class="verdict">
|
||||
<li><span class="tag ok">启动</span>
|
||||
干净起动时(|Δt|≤50ms 的行程),N3−N2 约为
|
||||
<strong>0.375 ms</strong> 量级 →
|
||||
<strong>几乎是同时启动</strong>,ECAT 同帧下发没有问题。
|
||||
有 2 个行程测出上百毫秒的“假早启动”,那是<strong>上一段还没停稳、N3 仍在动</strong>,
|
||||
不是这一段命令发晚了。</li>
|
||||
<li><span class="tag ok">巡航</span>
|
||||
两边平均 |v|:N2 <strong>199.38</strong>°/s,N3 <strong>199.38</strong>°/s,
|
||||
差值约 <strong>0.000</strong>°/s → <strong>稳态速度几乎一样,谈不上谁慢</strong>。
|
||||
巡航段 ∫(v2+v3) 平均只有约 <strong>0.11°</strong>,贴合理论 0。</li>
|
||||
<li><span class="tag warn">停车 / 换向</span>
|
||||
行程末端 ∫(v2+v3) 平均约 <strong>-1.09°</strong>,绝对值平均约 <strong>6.43°</strong>,
|
||||
明显大于巡航。停止窗口里:
|
||||
出现「只有 N2 还在走」的行程约 <strong>4</strong> 次(积分贡献合计 7.23°),
|
||||
「只有 N3 还在走」约 <strong>2</strong> 次(合计 2.13°)。
|
||||
<strong>两边都有刹车尾巴,不是单边永久变慢</strong>;有的行程 N2 拖尾更明显,有的行程 N3 更明显。</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout">
|
||||
<strong>一句话:</strong>积分差的大头在 <strong>停车阶段</strong>。
|
||||
对接约束要求 <code>v2 = −v3</code>;只要刹停瞬间一台还带着速度、另一台已经接近 0,
|
||||
<code>v2+v3</code> 就会冒尖峰,积分就会被拉离理论 0。这属于<strong>动态不同步</strong>,
|
||||
不等于某台电机“标定转速低、一直慢”。
|
||||
</div>
|
||||
|
||||
<div class="arrow-legend">
|
||||
<div><span class="arrow ok-arrow"></span> 正常:同时启动 / 巡航一致 / 中段积分≈0</div>
|
||||
<div><span class="arrow warn-arrow"></span> 注意:停车沿残速、行程衔接重叠、积分被顶开</div>
|
||||
<div><span class="arrow bad-arrow"></span> 偏大:该行程 ∫(v2+v3) 或编码器 Δ(a2+a3) 明显偏离 0</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="card">
|
||||
<h2>曲线(带标注)</h2>
|
||||
|
||||
<svg viewBox="0 0 960 420" width="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="960" height="420" fill="#0f1419"/>
|
||||
<line x1="54.0" x2="54.0" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="57.0" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#0</text><line x1="323.2" x2="323.2" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="326.2" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#1</text><line x1="411.7" x2="411.7" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="414.7" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#2</text><line x1="500.3" x2="500.3" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="503.3" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#3</text><line x1="588.7" x2="588.7" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="591.7" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#4</text><line x1="677.3" x2="677.3" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="680.3" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#5</text><line x1="765.7" x2="765.7" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="768.7" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#6</text><line x1="854.8" x2="854.8" y1="40" y2="390" stroke="#2a3340" stroke-width="1"/><text x="857.8" y="54" fill="#5c6b7e" font-size="9" font-family="Segoe UI,sans-serif">#7</text>
|
||||
<polyline fill="none" stroke="#4da3ff" stroke-width="1.3" points="56.0,97.8 58.3,97.9 60.4,97.9 62.6,97.8 64.7,97.8 66.8,97.9 69.0,97.8 71.1,97.9 73.0,97.8 75.3,97.8 77.3,97.8 79.3,97.9 81.0,97.8 83.2,97.8 85.4,97.8 87.3,97.8 89.4,97.8 91.2,97.9 93.3,97.8 95.5,97.9 97.5,97.7 99.7,97.9 102.1,97.9 104.1,97.8 106.2,97.8 108.2,97.9 110.3,97.8 112.5,97.8 114.9,97.7 117.1,98.0 119.2,97.9 121.4,97.9 123.8,97.8 126.1,97.9 128.2,97.9 130.7,97.8 133.0,97.8 135.5,97.8 137.6,97.8 140.0,97.8 142.0,97.8 144.4,97.8 146.7,97.8 148.9,97.8 151.2,97.8 153.7,97.9 155.8,97.8 157.9,97.8 160.0,97.8 162.1,97.9 164.1,97.9 166.4,97.8 168.5,97.9 170.8,97.8 173.1,97.8 175.1,97.8 177.2,97.9 179.4,97.9 181.5,97.8 183.7,97.9 185.8,97.8 188.0,97.8 189.8,97.8 191.9,97.8 194.0,97.8 196.1,97.9 198.2,97.9 200.4,97.9 202.5,97.8 204.7,97.8 206.8,97.8 209.0,97.8 211.0,97.9 213.0,97.8 215.0,97.8 217.2,97.8 219.2,97.7 221.2,97.8 223.3,97.9 225.3,97.7 227.4,97.9 229.4,97.9 231.9,97.9 234.3,97.7 236.3,97.9 238.4,97.9 240.5,97.8 242.5,97.9 244.6,97.9 246.6,97.8 248.5,134.8 250.8,140.0 253.0,140.3 255.2,140.6 257.2,141.0 259.4,140.9 261.5,141.1 263.6,141.1 265.7,140.8 267.8,140.7 270.1,140.8 272.1,141.0 274.1,139.7 276.6,140.6 278.8,140.4 281.1,140.4 283.1,140.4 285.2,140.5 287.2,140.9 289.2,140.5 291.2,139.6 293.3,139.0 295.5,139.2 297.5,139.6 299.7,139.3 301.9,139.6 303.8,140.2 306.0,139.9 308.1,139.2 310.4,139.1 312.5,139.2 314.7,139.6 316.8,139.4 318.6,140.3 320.9,139.7 323.2,102.4 325.1,97.6 327.2,97.8 329.3,97.8 331.2,97.8 333.3,98.0 335.2,97.7 337.4,59.0 339.3,55.7 341.6,55.2 343.7,55.2 345.9,55.6 348.1,55.4 350.3,55.7 352.5,55.6 354.8,55.4 356.9,55.5 359.0,55.3 361.1,55.2 363.5,55.1 365.6,55.0 367.8,55.0 369.9,55.5 372.1,55.7 374.4,55.6 376.5,55.4 378.6,55.6 381.0,55.7 383.4,55.3 385.6,55.2 388.0,55.3 390.8,55.5 393.1,55.6 395.3,55.5 397.4,55.5 399.8,55.3 402.2,55.4 404.2,56.1 406.3,55.2 408.6,56.0 410.8,60.8 412.9,97.7 415.5,97.8 417.7,97.8 419.9,97.9 422.0,97.9 424.3,97.8 426.2,136.0 428.4,139.5 430.7,139.2 432.9,139.0 434.7,140.4 436.9,140.4 438.8,140.6 440.9,140.7 442.9,140.7 445.4,140.4 447.5,140.3 449.8,140.2 452.1,139.6 454.5,140.6 456.7,140.5 458.9,140.8 460.9,140.7 462.9,139.4 465.2,140.7 467.5,140.8 469.6,140.6 471.7,140.8 473.7,140.5 475.8,139.8 477.8,138.7 479.9,139.5 481.7,140.4 483.6,140.5 485.8,140.7 487.9,140.3 490.3,140.1 492.5,140.5 494.6,140.4 497.0,139.7 499.5,124.7 501.8,97.8 504.0,97.9 506.5,97.8 508.8,97.8 511.3,97.8 513.5,97.9 515.6,56.5 517.6,55.4 519.9,55.2 522.1,55.4 524.2,55.7 526.2,55.6 528.2,55.4 530.4,55.5 532.5,55.2 534.7,55.2 537.0,55.7 539.1,55.3 541.2,55.3 543.4,55.2 545.5,55.6 547.7,55.6 549.7,55.4 551.6,55.3 553.7,55.5 555.9,55.4 557.8,55.6 560.0,55.2 562.5,55.3 564.5,55.4 566.5,55.3 568.5,55.4 570.6,55.8 573.2,55.4 575.3,55.2 577.3,55.4 579.5,55.6 581.6,55.6 583.8,55.5 586.3,55.4 588.3,88.5 590.4,97.8 592.3,97.9 594.6,97.9 597.1,97.8 599.0,97.9 601.2,97.9 603.6,137.6 605.8,140.3 608.1,140.8 610.3,140.5 612.7,140.5 615.0,139.6 617.3,138.9 619.6,139.4 621.7,139.7 623.8,139.3 625.9,138.9 628.1,139.1 630.3,139.1 632.2,139.7 634.4,139.2 636.4,139.8 638.6,139.6 640.8,139.3 643.0,139.7 645.4,140.5 647.5,140.7 649.7,140.6 651.9,140.5 654.3,140.5 656.2,140.7 658.2,140.8 660.7,140.4 662.7,140.7 664.9,140.5 667.0,140.5 669.2,140.5 671.5,140.4 673.8,140.2 675.8,140.7 678.2,98.1 680.9,97.8 683.2,97.8 685.4,97.7 687.6,97.8 689.8,97.9 692.0,58.0 694.3,55.3 696.6,55.3 698.9,55.1 701.2,55.2 703.5,55.2 705.9,56.3 708.5,55.3 710.8,55.6 713.2,55.8 715.5,55.5 717.8,55.4 720.5,55.3 722.8,55.6 725.0,55.9 727.2,56.1 729.9,55.2 731.9,55.6 734.1,55.8 736.4,55.5 739.1,55.6 741.3,55.4 743.3,55.3 745.5,55.4 747.8,55.2 749.8,55.2 752.1,55.2 754.4,55.4 756.7,55.3 759.1,55.3 761.2,55.0 763.4,55.2 765.7,93.3 767.9,97.9 770.3,97.8 772.4,97.8 774.8,97.9 777.0,97.7 779.3,112.1 781.3,138.6 783.5,140.0 785.7,140.0 787.8,140.3 790.3,138.5 792.9,140.3 795.5,139.8 797.9,139.4 800.1,140.6 802.3,140.9 804.9,140.4 807.4,139.5 809.5,140.0 812.0,141.0 814.1,140.6 816.3,140.9 818.3,139.7 820.6,140.7 823.0,140.5 825.3,140.4 827.6,140.3 830.0,139.3 832.6,140.5 834.9,140.4 837.0,140.5 839.3,140.4 841.5,140.5 843.9,139.9 846.4,140.5 848.7,140.4 851.4,139.7 853.9,125.1 856.2,97.8 858.8,97.8 861.2,97.9 863.6,97.8 865.9,97.8 868.4,58.3 870.8,55.5 873.1,55.1 875.4,55.4 877.9,55.2 880.2,55.5 882.3,55.3 884.6,55.3 887.1,55.0 889.6,55.5 891.9,55.6 894.1,55.2 896.6,55.5 898.9,55.0 901.2,56.1 903.6,55.3 905.6,55.8 908.1,55.2 910.3,55.5 912.7,55.4 914.9,55.7 917.3,55.3 919.4,55.4 921.5,55.4 923.8,55.4 926.0,55.5 928.2,55.2 930.5,55.0 933.3,55.4 935.5,55.5 937.7,55.6 940.0,55.6"/>
|
||||
<polyline fill="none" stroke="#f0a030" stroke-width="1.3" points="56.0,97.8 58.3,97.8 60.4,97.8 62.6,97.8 64.7,97.8 66.8,97.7 69.0,97.8 71.1,97.8 73.0,97.8 75.3,97.8 77.3,97.8 79.3,97.9 81.0,97.9 83.2,97.8 85.4,97.7 87.3,97.9 89.4,97.8 91.2,97.8 93.3,97.8 95.5,97.9 97.5,97.8 99.7,97.8 102.1,97.9 104.1,97.8 106.2,97.8 108.2,97.8 110.3,97.8 112.5,97.8 114.9,97.8 117.1,97.8 119.2,97.9 121.4,97.8 123.8,97.8 126.1,97.9 128.2,97.8 130.7,97.8 133.0,97.8 135.5,97.8 137.6,97.9 140.0,97.9 142.0,97.8 144.4,97.9 146.7,97.8 148.9,97.9 151.2,97.8 153.7,97.8 155.8,97.8 157.9,97.8 160.0,97.8 162.1,97.8 164.1,97.9 166.4,97.8 168.5,97.8 170.8,97.8 173.1,97.8 175.1,97.8 177.2,97.8 179.4,97.9 181.5,97.8 183.7,97.8 185.8,97.8 188.0,97.8 189.8,97.9 191.9,97.8 194.0,97.8 196.1,97.9 198.2,97.8 200.4,97.8 202.5,97.8 204.7,97.9 206.8,97.8 209.0,97.8 211.0,97.8 213.0,97.8 215.0,97.8 217.2,97.9 219.2,97.9 221.2,97.8 223.3,97.8 225.3,97.8 227.4,97.9 229.4,97.9 231.9,97.8 234.3,97.8 236.3,97.8 238.4,97.8 240.5,97.9 242.5,97.8 244.6,97.8 246.6,97.9 248.5,61.2 250.8,56.2 253.0,55.1 255.2,54.9 257.2,55.6 259.4,55.6 261.5,55.8 263.6,55.9 265.7,55.6 267.8,55.5 270.1,55.6 272.1,55.4 274.1,55.0 276.6,55.4 278.8,55.4 281.1,55.4 283.1,55.2 285.2,55.2 287.2,55.5 289.2,55.8 291.2,55.9 293.3,55.4 295.5,55.3 297.5,55.3 299.7,55.2 301.9,55.3 303.8,55.4 306.0,55.5 308.1,55.7 310.4,55.5 312.5,55.4 314.7,55.5 316.8,55.6 318.6,55.5 320.9,55.3 323.2,55.6 325.1,61.9 327.2,97.6 329.3,97.9 331.2,97.8 333.3,97.9 335.2,97.8 337.4,136.4 339.3,139.5 341.6,140.2 343.7,140.2 345.9,140.2 348.1,140.1 350.3,140.2 352.5,140.1 354.8,140.2 356.9,140.2 359.0,140.2 361.1,140.3 363.5,140.3 365.6,140.3 367.8,140.4 369.9,140.1 372.1,140.3 374.4,140.2 376.5,139.9 378.6,140.2 381.0,140.3 383.4,140.5 385.6,140.5 388.0,140.2 390.8,139.9 393.1,140.2 395.3,140.2 397.4,140.2 399.8,140.3 402.2,140.4 404.2,140.1 406.3,140.2 408.6,140.3 410.8,140.3 412.9,99.4 415.5,97.8 417.7,97.8 419.9,97.8 422.0,97.8 424.3,97.8 426.2,59.3 428.4,56.4 430.7,55.6 432.9,55.5 434.7,55.8 436.9,55.6 438.8,55.8 440.9,55.7 442.9,55.3 445.4,55.7 447.5,55.7 449.8,55.6 452.1,55.4 454.5,55.2 456.7,55.1 458.9,55.1 460.9,55.3 462.9,55.3 465.2,55.3 467.5,55.2 469.6,55.7 471.7,55.3 473.7,55.2 475.8,55.3 477.8,55.2 479.9,54.9 481.7,55.4 483.6,55.8 485.8,55.8 487.9,55.7 490.3,55.5 492.5,55.4 494.6,55.6 497.0,55.3 499.5,84.1 501.8,98.0 504.0,98.0 506.5,97.9 508.8,97.9 511.3,97.9 513.5,97.9 515.6,138.9 517.6,139.6 519.9,140.2 522.1,140.2 524.2,140.2 526.2,140.0 528.2,140.1 530.4,140.2 532.5,140.0 534.7,140.2 537.0,140.1 539.1,139.9 541.2,140.0 543.4,140.2 545.5,140.0 547.7,140.6 549.7,140.0 551.6,140.1 553.7,140.3 555.9,140.0 557.8,140.1 560.0,140.0 562.5,139.9 564.5,140.2 566.5,140.2 568.5,140.3 570.6,140.3 573.2,140.1 575.3,140.2 577.3,140.2 579.5,140.2 581.6,140.4 583.8,140.5 586.3,140.3 588.3,100.7 590.4,97.9 592.3,97.7 594.6,97.8 597.1,97.9 599.0,97.8 601.2,97.8 603.6,57.7 605.8,55.5 608.1,55.6 610.3,55.7 612.7,55.6 615.0,55.5 617.3,55.1 619.6,55.4 621.7,55.2 623.8,55.2 625.9,55.4 628.1,55.3 630.3,55.3 632.2,55.2 634.4,55.2 636.4,55.2 638.6,55.1 640.8,55.2 643.0,55.4 645.4,55.6 647.5,55.9 649.7,55.6 651.9,55.4 654.3,55.5 656.2,55.5 658.2,55.5 660.7,55.6 662.7,55.4 664.9,55.5 667.0,55.5 669.2,55.1 671.5,55.3 673.8,55.2 675.8,72.1 678.2,98.0 680.9,97.9 683.2,97.8 685.4,97.8 687.6,97.9 689.8,97.8 692.0,138.1 694.3,139.8 696.6,140.2 698.9,140.3 701.2,140.0 703.5,140.5 705.9,140.3 708.5,140.0 710.8,140.0 713.2,140.2 715.5,140.0 717.8,140.0 720.5,140.2 722.8,140.3 725.0,140.3 727.2,140.3 729.9,139.9 731.9,140.0 734.1,139.9 736.4,140.2 739.1,140.1 741.3,140.0 743.3,140.2 745.5,140.1 747.8,140.0 749.8,139.9 752.1,139.9 754.4,140.3 756.7,140.2 759.1,140.1 761.2,140.3 763.4,140.6 765.7,99.0 767.9,97.8 770.3,97.8 772.4,97.9 774.8,97.8 777.0,97.8 779.3,83.1 781.3,57.2 783.5,55.9 785.7,55.6 787.8,55.5 790.3,55.0 792.9,55.6 795.5,55.0 797.9,55.3 800.1,55.6 802.3,55.7 804.9,55.5 807.4,55.3 809.5,55.2 812.0,55.7 814.1,55.7 816.3,55.8 818.3,55.6 820.6,55.4 823.0,55.5 825.3,55.2 827.6,55.6 830.0,55.6 832.6,55.2 834.9,55.4 837.0,55.4 839.3,55.2 841.5,55.6 843.9,55.5 846.4,55.0 848.7,55.2 851.4,55.6 853.9,94.6 856.2,98.0 858.8,97.9 861.2,97.8 863.6,97.9 865.9,97.9 868.4,136.5 870.8,139.9 873.1,140.1 875.4,140.1 877.9,140.1 880.2,139.8 882.3,140.1 884.6,140.0 887.1,140.4 889.6,140.2 891.9,140.2 894.1,140.3 896.6,140.1 898.9,140.0 901.2,140.2 903.6,140.2 905.6,140.0 908.1,140.2 910.3,140.4 912.7,140.4 914.9,140.4 917.3,140.1 919.4,140.0 921.5,140.0 923.8,140.0 926.0,140.0 928.2,140.1 930.5,140.3 933.3,140.3 935.5,140.1 937.7,140.1 940.0,140.5"/>
|
||||
<text x="56" y="44" fill="#8a9bb0" font-size="11" font-family="Segoe UI,Microsoft YaHei,sans-serif">速度 deg/s — 蓝 N2 / 橙 N3</text>
|
||||
<line x1="56" x2="940" y1="97.8" y2="97.8" stroke="#2a3340" stroke-dasharray="4 3"/>
|
||||
<polyline fill="none" stroke="#ff5c5c" stroke-width="1.3" points="56.0,229.9 58.3,229.9 60.4,229.9 62.6,229.9 64.7,229.8 66.8,229.8 69.0,229.8 71.1,229.9 73.0,229.8 75.3,229.8 77.3,229.8 79.3,229.9 81.0,229.9 83.2,229.8 85.4,229.8 87.3,229.9 89.4,229.8 91.2,229.8 93.3,229.8 95.5,230.0 97.5,229.8 99.7,229.9 102.1,229.9 104.1,229.9 106.2,229.8 108.2,229.9 110.3,229.8 112.5,229.8 114.9,229.8 117.1,230.0 119.2,229.9 121.4,229.9 123.8,229.9 126.1,229.9 128.2,229.9 130.7,229.8 133.0,229.8 135.5,229.9 137.6,229.9 140.0,229.9 142.0,229.9 144.4,229.9 146.7,229.9 148.9,229.9 151.2,229.8 153.7,230.0 155.8,229.8 157.9,229.8 160.0,229.9 162.1,229.9 164.1,229.9 166.4,229.8 168.5,229.9 170.8,229.9 173.1,229.8 175.1,229.8 177.2,229.9 179.4,230.0 181.5,229.8 183.7,229.9 185.8,229.8 188.0,229.8 189.8,229.9 191.9,229.9 194.0,229.8 196.1,229.9 198.2,229.9 200.4,229.9 202.5,229.8 204.7,229.9 206.8,229.8 209.0,229.8 211.0,229.9 213.0,229.9 215.0,229.8 217.2,229.9 219.2,229.8 221.2,229.8 223.3,229.9 225.3,229.8 227.4,230.0 229.4,229.9 231.9,229.9 234.3,229.8 236.3,229.9 238.4,229.9 240.5,229.9 242.5,229.9 244.6,229.9 246.6,229.9 248.5,230.1 250.8,230.3 253.0,229.7 255.2,229.7 257.2,230.6 259.4,230.5 261.5,230.8 263.6,230.9 265.7,230.5 267.8,230.3 270.1,230.5 272.1,230.5 274.1,229.1 276.6,230.2 278.8,230.0 281.1,230.0 283.1,229.8 285.2,229.8 287.2,230.5 289.2,230.4 291.2,229.7 293.3,228.9 295.5,229.0 297.5,229.3 299.7,228.9 301.9,229.3 303.8,229.8 306.0,229.7 308.1,229.2 310.4,229.0 312.5,229.0 314.7,229.4 316.8,229.3 318.6,229.9 320.9,229.4 323.2,199.7 325.1,200.9 327.2,229.6 329.3,229.9 331.2,229.9 333.3,230.0 335.2,229.7 337.4,229.6 339.3,229.5 341.6,229.7 343.7,229.7 345.9,230.0 348.1,229.7 350.3,230.0 352.5,229.9 354.8,229.9 356.9,229.9 359.0,229.7 361.1,229.8 363.5,229.7 365.6,229.5 367.8,229.6 369.9,229.9 372.1,230.2 374.4,230.0 376.5,229.6 378.6,230.0 381.0,230.1 383.4,230.0 385.6,229.9 388.0,229.7 390.8,229.7 393.1,230.0 395.3,229.9 397.4,229.9 399.8,229.8 402.2,230.0 404.2,230.3 406.3,229.6 408.6,230.3 410.8,234.3 412.9,231.0 415.5,229.9 417.7,229.9 419.9,230.0 422.0,229.9 424.3,229.9 426.2,229.6 428.4,230.1 430.7,229.2 432.9,229.0 434.7,230.2 436.9,230.2 438.8,230.5 440.9,230.5 442.9,230.2 445.4,230.3 447.5,230.1 449.8,230.0 452.1,229.3 454.5,230.0 456.7,229.8 458.9,230.1 460.9,230.2 462.9,229.1 465.2,230.2 467.5,230.2 469.6,230.3 471.7,230.2 473.7,229.9 475.8,229.4 477.8,228.4 479.9,228.9 481.7,230.0 483.6,230.4 485.8,230.5 487.9,230.1 490.3,229.8 492.5,230.1 494.6,230.1 497.0,229.3 499.5,240.3 501.8,229.9 504.0,230.1 506.5,230.0 508.8,229.9 511.3,229.9 513.5,229.9 515.6,229.7 517.6,229.3 519.9,229.6 522.1,229.9 524.2,230.1 526.2,229.8 528.2,229.7 530.4,229.9 532.5,229.5 534.7,229.7 537.0,230.0 539.1,229.5 541.2,229.6 543.4,229.6 545.5,229.7 547.7,230.2 549.7,229.7 551.6,229.6 553.7,230.0 555.9,229.7 557.8,229.9 560.0,229.5 562.5,229.5 564.5,229.8 566.5,229.7 568.5,229.9 570.6,230.3 573.2,229.8 575.3,229.7 577.3,229.9 579.5,230.0 581.6,230.1 583.8,230.1 586.3,229.9 588.3,224.7 590.4,229.9 592.3,229.8 594.6,229.9 597.1,229.9 599.0,229.9 601.2,229.9 603.6,229.6 605.8,230.0 608.1,230.5 610.3,230.3 612.7,230.3 615.0,229.4 617.3,228.5 619.6,229.2 621.7,229.2 623.8,228.9 625.9,228.7 628.1,228.9 630.3,228.8 632.2,229.2 634.4,228.8 636.4,229.3 638.6,229.2 640.8,229.0 643.0,229.4 645.4,230.2 647.5,230.6 649.7,230.2 651.9,230.1 654.3,230.1 656.2,230.3 658.2,230.4 660.7,230.1 662.7,230.2 664.9,230.1 667.0,230.2 669.2,229.8 671.5,229.9 673.8,229.7 675.8,243.6 678.2,230.2 680.9,229.9 683.2,229.8 685.4,229.8 687.6,229.9 689.8,229.9 692.0,230.2 694.3,229.4 696.6,229.7 698.9,229.7 701.2,229.5 703.5,229.9 705.9,230.6 708.5,229.6 710.8,229.8 713.2,230.1 715.5,229.7 717.8,229.6 720.5,229.7 722.8,230.1 725.0,230.3 727.2,230.5 729.9,229.5 731.9,229.8 734.1,229.9 736.4,229.9 739.1,230.0 741.3,229.7 743.3,229.8 745.5,229.7 747.8,229.5 749.8,229.4 752.1,229.4 754.4,229.9 756.7,229.7 759.1,229.7 761.2,229.5 763.4,230.0 765.7,227.2 767.9,229.9 770.3,229.8 772.4,229.9 774.8,229.8 777.0,229.8 779.3,229.5 781.3,230.0 783.5,230.1 785.7,229.8 787.8,229.9 790.3,228.2 792.9,230.1 795.5,229.2 797.9,229.1 800.1,230.3 802.3,230.6 804.9,230.1 807.4,229.1 809.5,229.4 812.0,230.6 814.1,230.4 816.3,230.7 818.3,229.6 820.6,230.3 823.0,230.1 825.3,229.7 827.6,230.0 830.0,229.2 832.6,229.9 834.9,230.0 837.0,230.0 839.3,229.8 841.5,230.2 843.9,229.7 846.4,229.7 848.7,229.8 851.4,229.5 853.9,249.1 856.2,229.9 858.8,229.9 861.2,229.9 863.6,229.9 865.9,229.9 868.4,229.2 870.8,229.6 873.1,229.5 875.4,229.8 877.9,229.6 880.2,229.6 882.3,229.6 884.6,229.6 887.1,229.7 889.6,229.9 891.9,229.9 894.1,229.7 896.6,229.9 898.9,229.4 901.2,230.4 903.6,229.8 905.6,230.0 908.1,229.7 910.3,230.1 912.7,230.0 914.9,230.2 917.3,229.7 919.4,229.6 921.5,229.7 923.8,229.6 926.0,229.7 928.2,229.5 930.5,229.6 933.3,229.9 935.5,229.8 937.7,229.9 940.0,230.2"/>
|
||||
<text x="56" y="184" fill="#8a9bb0" font-size="11" font-family="Segoe UI,Microsoft YaHei,sans-serif">合速度 v2+v3(理论 0)— 停车尖峰在这里</text>
|
||||
<line x1="56" x2="940" y1="229.9" y2="229.9" stroke="#3a4656" stroke-dasharray="4 3"/>
|
||||
<polyline fill="none" stroke="#b48cff" stroke-width="1.3" points="56.0,373.0 58.3,373.0 60.4,373.0 62.6,373.0 64.7,373.0 66.8,373.0 69.0,373.0 71.1,373.0 73.0,373.0 75.3,373.0 77.3,373.0 79.3,373.0 81.0,373.0 83.2,373.0 85.4,373.0 87.3,373.0 89.4,373.0 91.2,373.0 93.3,373.0 95.5,373.0 97.5,373.0 99.7,373.0 102.1,373.0 104.1,373.0 106.2,373.0 108.2,373.0 110.3,373.0 112.5,373.0 114.9,373.0 117.1,373.0 119.2,373.0 121.4,373.0 123.8,373.0 126.1,373.0 128.2,373.0 130.7,373.0 133.0,373.0 135.5,373.1 137.6,373.1 140.0,373.0 142.0,373.0 144.4,373.0 146.7,373.0 148.9,373.0 151.2,373.1 153.7,373.1 155.8,373.1 157.9,373.1 160.0,373.1 162.1,373.1 164.1,373.1 166.4,373.1 168.5,373.1 170.8,373.1 173.1,373.1 175.1,373.1 177.2,373.0 179.4,373.0 181.5,373.0 183.7,373.1 185.8,373.1 188.0,373.1 189.8,373.1 191.9,373.1 194.0,373.1 196.1,373.1 198.2,373.1 200.4,373.1 202.5,373.1 204.7,373.1 206.8,373.1 209.0,373.1 211.0,373.1 213.0,373.1 215.0,373.1 217.2,373.1 219.2,373.1 221.2,373.1 223.3,373.0 225.3,373.0 227.4,373.0 229.4,373.0 231.9,373.0 234.3,373.0 236.3,373.0 238.4,373.0 240.5,373.0 242.5,373.1 244.6,373.1 246.6,373.1 248.5,372.3 250.8,371.9 253.0,371.9 255.2,372.0 257.2,372.0 259.4,372.1 261.5,372.0 263.6,372.1 265.7,372.1 267.8,372.1 270.1,372.2 272.1,372.3 274.1,372.4 276.6,372.5 278.8,372.5 281.1,372.6 283.1,372.6 285.2,372.6 287.2,372.6 289.2,372.6 291.2,372.6 293.3,372.6 295.5,372.7 297.5,372.8 299.7,372.8 301.9,372.9 303.8,373.1 306.0,373.1 308.1,373.1 310.4,373.1 312.5,373.2 314.7,373.2 316.8,373.2 318.6,373.3 320.9,373.3 323.2,360.9 325.1,316.7 327.2,307.2 329.3,307.2 331.2,307.2 333.3,307.2 335.2,307.2 337.4,307.2 339.3,307.3 341.6,307.3 343.7,307.2 345.9,307.2 348.1,307.1 350.3,307.1 352.5,307.0 354.8,307.0 356.9,307.0 359.0,307.0 361.1,307.0 363.5,307.0 365.6,306.9 367.8,306.9 369.9,306.9 372.1,306.9 374.4,306.9 376.5,306.9 378.6,306.9 381.0,306.9 383.4,306.8 385.6,306.8 388.0,306.8 390.8,306.8 393.1,306.8 395.3,306.8 397.4,306.8 399.8,306.8 402.2,306.7 404.2,306.7 406.3,306.7 408.6,306.7 410.8,306.8 412.9,325.2 415.5,325.6 417.7,325.5 419.9,325.5 422.0,325.5 424.3,325.5 426.2,324.6 428.4,324.3 430.7,324.2 432.9,324.1 434.7,324.2 436.9,324.2 438.8,324.2 440.9,324.2 442.9,324.2 445.4,324.2 447.5,324.2 449.8,324.2 452.1,324.2 454.5,324.1 456.7,324.2 458.9,324.2 460.9,324.2 462.9,324.2 465.2,324.2 467.5,324.2 469.6,324.2 471.7,324.2 473.7,324.2 475.8,324.1 477.8,324.2 479.9,324.2 481.7,324.3 483.6,324.1 485.8,324.1 487.9,324.2 490.3,324.3 492.5,324.3 494.6,324.3 497.0,324.3 499.5,327.7 501.8,331.0 504.0,331.0 506.5,331.0 508.8,331.0 511.3,331.0 513.5,331.0 515.6,330.8 517.6,330.9 519.9,330.8 522.1,330.8 524.2,330.9 526.2,330.9 528.2,330.9 530.4,330.8 532.5,330.8 534.7,330.8 537.0,330.8 539.1,330.8 541.2,330.8 543.4,330.8 545.5,330.8 547.7,330.8 549.7,330.8 551.6,330.8 553.7,330.9 555.9,330.8 557.8,330.8 560.0,330.8 562.5,330.8 564.5,330.8 566.5,330.9 568.5,330.9 570.6,330.9 573.2,330.8 575.3,330.9 577.3,330.8 579.5,330.9 581.6,330.9 583.8,330.8 586.3,330.9 588.3,320.6 590.4,319.1 592.3,319.1 594.6,319.1 597.1,319.1 599.0,319.1 601.2,319.1 603.6,317.9 605.8,317.7 608.1,317.6 610.3,317.7 612.7,317.8 615.0,317.8 617.3,317.7 619.6,317.6 621.7,317.6 623.8,317.7 625.9,317.7 628.1,317.7 630.3,317.6 632.2,317.7 634.4,317.7 636.4,317.8 638.6,317.8 640.8,317.8 643.0,317.8 645.4,317.7 647.5,317.7 649.7,317.7 651.9,317.6 654.3,317.7 656.2,317.7 658.2,317.7 660.7,317.7 662.7,317.7 664.9,317.7 667.0,317.7 669.2,317.7 671.5,317.7 673.8,317.7 675.8,318.9 678.2,332.3 680.9,332.3 683.2,332.3 685.4,332.3 687.6,332.3 689.8,332.3 692.0,331.9 694.3,331.8 696.6,331.7 698.9,331.7 701.2,331.7 703.5,331.7 705.9,331.7 708.5,331.7 710.8,331.7 713.2,331.7 715.5,331.8 717.8,331.8 720.5,331.8 722.8,331.8 725.0,331.8 727.2,331.8 729.9,331.8 731.9,331.8 734.1,331.8 736.4,331.8 739.1,331.9 741.3,331.9 743.3,331.9 745.5,331.9 747.8,331.9 749.8,331.8 752.1,331.9 754.4,331.9 756.7,331.8 759.1,331.8 761.2,331.8 763.4,331.8 765.7,320.6 767.9,319.7 770.3,319.7 772.4,319.8 774.8,319.8 777.0,319.8 779.3,319.7 781.3,318.9 783.5,318.7 785.7,318.7 787.8,318.6 790.3,318.5 792.9,318.6 795.5,318.6 797.9,318.5 800.1,318.5 802.3,318.5 804.9,318.6 807.4,318.5 809.5,318.5 812.0,318.5 814.1,318.5 816.3,318.5 818.3,318.4 820.6,318.5 823.0,318.5 825.3,318.5 827.6,318.4 830.0,318.4 832.6,318.4 834.9,318.4 837.0,318.4 839.3,318.3 841.5,318.3 843.9,318.4 846.4,318.3 848.7,318.4 851.4,318.4 853.9,334.2 856.2,340.8 858.8,340.7 861.2,340.7 863.6,340.7 865.9,340.7 868.4,340.4 870.8,340.3 873.1,340.3 875.4,340.3 877.9,340.4 880.2,340.5 882.3,340.4 884.6,340.4 887.1,340.4 889.6,340.4 891.9,340.4 894.1,340.4 896.6,340.4 898.9,340.4 901.2,340.3 903.6,340.4 905.6,340.4 908.1,340.3 910.3,340.3 912.7,340.3 914.9,340.4 917.3,340.4 919.4,340.4 921.5,340.5 923.8,340.5 926.0,340.5 928.2,340.5 930.5,340.5 933.3,340.7 935.5,340.8 937.7,340.8 940.0,340.8"/>
|
||||
<text x="56" y="294" fill="#8a9bb0" font-size="11" font-family="Segoe UI,Microsoft YaHei,sans-serif">积分 ∫(v2+v3)dt(理论 0)— 每段末的偏离就是耦合误差</text>
|
||||
<line x1="56" x2="940" y1="373.0" y2="373.0" stroke="#3a4656" stroke-dasharray="4 3"/>
|
||||
<text x="56" y="408" fill="#6b7a8d" font-size="11" font-family="Segoe UI,sans-serif">t_rel 0.1..60.1s · ECAT 0.25ms · 竖线为行程起点</text>
|
||||
<!-- 标注箭头 -->
|
||||
<g font-family="Segoe UI,Microsoft YaHei,sans-serif" font-size="11">
|
||||
<text x="750" y="70" fill="#3d8f5b">↑ 巡航:两线贴合,都不慢</text>
|
||||
<text x="730" y="210" fill="#d4a017">↓ 停车沿:v_sum 尖峰 → 积分爬升</text>
|
||||
</g>
|
||||
</svg>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="card">
|
||||
<h2>图上怎么看(对照上方曲线)</h2>
|
||||
<div class="annot-grid">
|
||||
<div class="annot ok">
|
||||
<div class="ann-title">↑ 巡航段(两条速度曲线几乎贴合)</div>
|
||||
<p>N2 蓝线、N3 橙线在 |v|≈200°/s 附近重合 → <strong>都不慢</strong>。
|
||||
下方红线 <code>v2+v3</code> 贴 0,积分曲线几乎水平 → 对接约束在稳态是成立的。</p>
|
||||
</div>
|
||||
<div class="annot warn">
|
||||
<div class="ann-title">↓ 停车沿 / 换向点(红线尖峰)</div>
|
||||
<p>接近终点时,一台速度先掉下来、另一台还在走 → <code>v2+v3</code> 出现 ±100~200°/s 尖峰。
|
||||
积分曲线在这里<strong>爬上或爬下</strong>,行程结束就停在理论 0 以外几度到二十度。
|
||||
<strong>这里是积分差的主要来源。</strong></p>
|
||||
</div>
|
||||
<div class="annot warn">
|
||||
<div class="ann-title">↓ 行程刚衔接时(起动窗)</div>
|
||||
<p>若上一段没完全停稳就反向,会看到“N3 早就动了”的假象(表格里 onΔ 为 −800ms 量级)。
|
||||
这<strong>不是启动指令不同步</strong>,而是<strong>速度还在反向爬坡 / 残速</strong>,
|
||||
也会把起动窗积分抬高一点。</p>
|
||||
</div>
|
||||
<div class="annot bad">
|
||||
<div class="ann-title">↓ 紫色角度和 a2+a3</div>
|
||||
<p>理论应为常数(理想为 0)。行程终点若偏到 ±10~25°,与该行程 ∫(v2+v3) 同量级 →
|
||||
说明偏差是<strong>真实位移耦合误差</strong>,不只是速度噪声。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="callout small">
|
||||
<strong>标定建议(若要压积分差):</strong>
|
||||
① 先统一两台的减速段(同样斜率刹停),而不是去改巡航速度;
|
||||
② 检查 N2/N3 负载与制动是否对称;
|
||||
③ 控制器上对对接轴做 <code>v2+v3 → 0</code> 的纠偏,比单轴调 PID 更对症;
|
||||
④ 序列 hold 不要短于“两台都确认停稳”的时间,避免重叠行程把积分搅浑。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="card">
|
||||
<h2>各行程明细(积分从行程起点重算)</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th><th>方向</th><th>起动Δ(N3-N2)</th><th>巡航|v| N2/N3</th>
|
||||
<th>∫起动</th><th>∫巡航</th><th>∫停车</th>
|
||||
<th>停时仅N2在动</th><th>停时仅N3在动</th><th>停车归因</th><th>Δ(a2+a3)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td>#0</td><td>down</td>
|
||||
<td class="ok">+0.00 ms</td>
|
||||
<td class="ok">199.30 / 199.23</td>
|
||||
<td>+0.00</td>
|
||||
<td class="ok">-0.28</td>
|
||||
<td class="warn">+7.34</td>
|
||||
<td>+0.00</td>
|
||||
<td>+1.29</td>
|
||||
<td class="warn">N3 残速</td>
|
||||
<td>-21.32°</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>#1</td><td>up</td>
|
||||
<td class="bad">-827.50 ms</td>
|
||||
<td class="ok">199.42 / 199.42</td>
|
||||
<td>+28.52</td>
|
||||
<td class="ok">+0.11</td>
|
||||
<td class="warn">-7.58</td>
|
||||
<td>+0.00</td>
|
||||
<td>-0.84</td>
|
||||
<td class="warn">N3 残速</td>
|
||||
<td>+25.81°</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>#2</td><td>down</td>
|
||||
<td class="bad">-840.50 ms</td>
|
||||
<td class="ok">199.28 / 199.30</td>
|
||||
<td>-2.08</td>
|
||||
<td class="ok">+0.09</td>
|
||||
<td class="warn">-3.39</td>
|
||||
<td>+0.00</td>
|
||||
<td>+0.00</td>
|
||||
<td class="warn">两台接近同步刹停</td>
|
||||
<td>-1.12°</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>#3</td><td>up</td>
|
||||
<td class="ok">+2.25 ms</td>
|
||||
<td class="ok">199.47 / 199.43</td>
|
||||
<td>+0.12</td>
|
||||
<td class="ok">+0.26</td>
|
||||
<td class="warn">+5.99</td>
|
||||
<td>+0.58</td>
|
||||
<td>+0.00</td>
|
||||
<td class="warn">N2 残速</td>
|
||||
<td>-0.14°</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>#4</td><td>down</td>
|
||||
<td class="ok">-1.50 ms</td>
|
||||
<td class="ok">199.34 / 199.40</td>
|
||||
<td>+0.74</td>
|
||||
<td class="ok">+0.22</td>
|
||||
<td class="warn">-7.46</td>
|
||||
<td>-1.24</td>
|
||||
<td>+0.00</td>
|
||||
<td class="warn">N2 残速</td>
|
||||
<td>+0.19°</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>#5</td><td>up</td>
|
||||
<td class="ok">+1.50 ms</td>
|
||||
<td class="ok">199.47 / 199.43</td>
|
||||
<td>+0.26</td>
|
||||
<td class="ok">+0.20</td>
|
||||
<td class="warn">+6.13</td>
|
||||
<td>+0.92</td>
|
||||
<td>+0.00</td>
|
||||
<td class="warn">N2 残速</td>
|
||||
<td>-0.19°</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>#6</td><td>down</td>
|
||||
<td class="ok">+0.00 ms</td>
|
||||
<td class="ok">199.25 / 199.35</td>
|
||||
<td>+0.64</td>
|
||||
<td class="ok">+0.35</td>
|
||||
<td class="warn">-11.65</td>
|
||||
<td>-4.49</td>
|
||||
<td>+0.00</td>
|
||||
<td class="warn">N2 残速</td>
|
||||
<td>+0.21°</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>#7</td><td>up</td>
|
||||
<td class="ok">+0.00 ms</td>
|
||||
<td class="ok">199.51 / 199.49</td>
|
||||
<td>+0.15</td>
|
||||
<td class="ok">-0.04</td>
|
||||
<td class="ok">+1.92</td>
|
||||
<td>+0.00</td>
|
||||
<td>+0.00</td>
|
||||
<td class="ok">两台接近同步刹停</td>
|
||||
<td>-13.38°</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="sub" style="margin-top:10px">
|
||||
单位:角度 deg(积分)· 速度 deg/s · 时间 ms。
|
||||
「停时仅N2/N3在动」为该窗口内单边残速对 ∫(v2+v3) 的贡献。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>原始结论脚本输出(备查)</h2>
|
||||
<pre style="white-space:pre-wrap;font-size:12px;color:#a8b6c8;margin:0">{
|
||||
"main_source_of_integral_error": "起动段",
|
||||
"phase_Isum_mean": {
|
||||
"on": 3.5436874809462364,
|
||||
"cruise": 0.11441910352287721,
|
||||
"stop": 1.0892628954283632
|
||||
},
|
||||
"who_lags_at_stop": "N2",
|
||||
"stop_lag_ms_n3_minus_n2_mean": 0,
|
||||
"cruise_speed": "巡航速度接近",
|
||||
"cruise_bias_deg_s": 0.0004292367234076469,
|
||||
"plain": [
|
||||
"启动:两台几乎同时动(差约 0.x ms),启动不是积分变大的主因。",
|
||||
"巡航:两台平均速度几乎一样,积分在中段也很小(贴合理论 0)。",
|
||||
"停车:积分差主要在这里积出来;N3 相对 N2 平均晚停约 0 ms。",
|
||||
"停止窗里「只剩一台在动」时:主要是 N2 还在走,把 ∫(v2+v3) 推离 0。",
|
||||
"所以:不是谁「全程都慢」,而是 N3 停车更拖、在停机瞬间继续走,积分被顶起来。",
|
||||
"N2 也有停车滞后,但幅度和样本都更小;巡航略快一点点时对积分贡献有限。"
|
||||
]
|
||||
}</pre>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user