2026-09-10 03:02:14 +08:00
<!DOCTYPE html>
< html lang = "zh-CN" >< head >< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width,initial-scale=1" >
< title > EtherCAT 周期速度 × 故障率 × CPU 占用 调研</ title >
< style >
: root { --fg : #111827 ; --mut : #6b7280 ; --bd : #e5e7eb ; --bg : #f8fafc ; --acc : #1d4ed8 ; --warn : #b45309 ; --bad : #b91c1c }
* { box-sizing : border-box }
body { margin : 0 ; background : #fff ; color : var ( -- fg );
font : 15.5 px / 1.72 - apple-system , "Segoe UI" , "Microsoft YaHei" , system-ui , sans-serif }
main { max-width : 1120 px ; margin : 0 auto ; padding : 40 px 28 px 90 px }
h1 { font-size : 29 px ; line-height : 1.3 ; margin : 0 0 6 px ; letter-spacing : -.2 px }
h2 { font-size : 20 px ; margin : 44 px 0 12 px ; padding-bottom : 7 px ; border-bottom : 2 px solid var ( -- bd )}
h3 { font-size : 16 px ; margin : 26 px 0 8 px ; color : #374151 }
p , li { margin : 8 px 0 }
. meta { color : var ( -- mut ); font-size : 13.5 px ; margin-bottom : 26 px }
. card { background : var ( -- bg ); border : 1 px solid var ( -- bd ); border-left : 4 px solid var ( -- acc );
border-radius : 8 px ; padding : 16 px 20 px ; margin : 18 px 0 }
. card . warn { border-left-color : var ( -- warn ); background : #fffbeb }
. card . bad { border-left-color : var ( -- bad ); background : #fef2f2 }
. card h3 { margin : 0 0 8 px ; font-size : 15 px ; color : inherit }
. grid { display : grid ; grid-template-columns : repeat ( auto - fit , minmax ( 430 px , 1 fr )); gap : 18 px ; margin : 18 px 0 }
. fig { border : 1 px solid var ( -- bd ); border-radius : 8 px ; padding : 12 px 10 px 8 px ; background : #fff }
. fig > . cap { font-size : 12.5 px ; font-weight : 600 ; color : #374151 ; margin : 0 0 4 px 8 px }
. fig > . sub { font-size : 11.5 px ; color : var ( -- mut ); margin : 0 0 4 px 8 px }
table { border-collapse : collapse ; width : 100 % ; font-size : 13 px ; margin : 14 px 0 }
th , td { border : 1 px solid var ( -- bd ); padding : 6 px 9 px ; 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 }
tr . dead td { background : #fef2f2 ; color : #7f1d1d }
code { background : #f1f5f9 ; padding : 1.5 px 5 px ; border-radius : 4 px ; font-size : 12.5 px ;
font-family : ui-monospace , "Cascadia Mono" , Consolas , monospace }
. kpi { display : flex ; gap : 14 px ; flex-wrap : wrap ; margin : 22 px 0 8 px }
. kpi div { flex : 1 1 168 px ; border : 1 px solid var ( -- bd ); border-radius : 8 px ; padding : 13 px 15 px ; background : #fff }
. kpi b { display : block ; font-size : 23 px ; line-height : 1.25 ; color : var ( -- acc )}
. kpi span { font-size : 12 px ; color : var ( -- mut )}
ul { padding-left : 22 px }
. excl { font-size : 12.5 px ; color : var ( -- bad ); background : #fef2f2 ; border : 1 px dashed #fca5a5 ;
border-radius : 6 px ; padding : 9 px 13 px ; margin : 12 px 0 }
. foot { margin-top : 44 px ; padding-top : 16 px ; border-top : 1 px solid var ( -- bd );
font-size : 12.5 px ; color : var ( -- mut )}
</ style ></ head >< body >< main >
< h1 > EtherCAT 周期速度 × 故障率 × CPU 占用 调研</ h1 >
< div class = "meta" > 调研日期 2026-09-08 · 本机 I210 + 1 个 GD32E508 从站 · 两轮扫描:带 CAN / 拔掉全部 CAN · 数据 < code > lab/ecat_rate_sweep_*.csv</ code > · 生成器 < code > scripts/build_ecat_report_html.py</ code ></ div >
< div class = "kpi" >< div >< b > 0.5ms</ b >< span > 实测可用上限(2kHz,带 CAN 工况)</ span ></ div >< div >< b > ≈9.0pp</ b >< span > CAN 每拍恒定负担(3 档配对,饱和档除外)</ span ></ div >< div >< b > 4000 帧/s</ b >< span > 电机 STATUS 实测刷新(1/0.5ms) </ span ></ div >< div >< b > ≈8.2kHz</ b >< span > 纯 ECAT 实测硬上限(设 10/12.5/16kHz 实测均不涨)</ span ></ div ></ div >
< div class = "card" >< h3 > 结论</ h3 >< ul >< li >< b > 带 CAN 工况的可用上限在 0.5ms,瓶颈在从站 GD32 不在主站。</ b > 实测可用到 0.5ms;0.25ms 起电机数据刷新率从 4000 帧/s 掉到 88 帧/s(带 CAN 那轮当场确认 < code > online=True</ code > ,是真失效)。可用上限只夹在 (0.25ms, 0.5ms] 内,< b > 具体位置没测到</ b > 。</ li >< li >< b > 纯 ECAT 硬上限 ≈8.2kHz,这次测到了墙本身。</ b > 断电重启后无 CAN 补测:0.25/0.125ms 满速通过(99.7% / 98.0%);再往下设 0.1 / 0.08 / 0.0625ms,实测循环率全部顶在 8196– 8238Hz(达成 82.3% / 65.9% / 51.2%),wkc 全程 3/3、从站始终应答。墙的来历:主站单拍硬耗时 < code > cyc p50</ code > 各档恒 117– 126µs, 1/121µs ≈ 8.2kHz —— 是"每拍必须花的往返+处理时间",不是主机 CPU 不够(顶墙时主机 CPU 反而降到 ~85%)。原「3.5kHz 外推」< b > 被证伪</ b > ;首轮无 CAN 0.25ms 停报为残状态偶发,不复现。注意"跑得快"≠"能用":带 CAN 的可用上限仍由电机数据刷新决定(下一条)。</ li >< li >< b > 拔 CAN 对照解开了混淆</ b > :未饱和三档 Δ 稳定 8.7–9.3pp,不随频率张开 → 快档塌陷不是 CAN 挤出来的;饱和两档 Δ 收窄到 ≤0.5pp,正是读数被夹紧的特征。</ li >< li >< b > 主机 native CPU 恒约 100% 单核、与周期无关</ b > (自旋恒转,< code > /api/motor/spin</ code > 已是空操作)。唯一的例外是带 CAN 轮的 0.125ms 档降到 87.5% —— 因为从站已停答。无 CAN 补测里从站活着,同档就回到 99.7%。< b > 主机 CPU 掉下来那一刻,正是从站死掉那一刻。</ b ></ li >< li > 建议:< b > 要吃到余量就停在 0.5ms</ b > 。0.25ms 不是"更快",是功能损坏。</ li ></ ul ></ div >
< h2 > 一、曲线</ h2 >
< div class = "grid" >
< div class = "fig" >< div class = "cap" > ① 循环率:设定 vs 实测(主站侧)</ div >< svg viewBox = "0 0 560 320" width = "100%" xmlns = "http://www.w3.org/2000/svg" role = "img" aria-label = "循环率:设定 vs 实测" >< line x1 = "64" y1 = "271.2" x2 = "544" y2 = "271.2" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "274.7" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 200</ text >< line x1 = "64" y1 = "224.2" x2 = "544" y2 = "224.2" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "227.7" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 500</ text >< line x1 = "64" y1 = "188.7" x2 = "544" y2 = "188.7" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "192.2" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 1,000</ text >< line x1 = "64" y1 = "153.2" x2 = "544" y2 = "153.2" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "156.7" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 2,000</ text >< line x1 = "64" y1 = "106.3" x2 = "544" y2 = "106.3" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "109.8" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 5,000</ text >< line x1 = "64" y1 = "70.8" x2 = "544" y2 = "70.8" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "74.3" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 10,000</ text >< line x1 = "64" y1 = "35.3" x2 = "544" y2 = "35.3" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "38.8" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 20,000</ text >< line x1 = "77.0" y1 = "26" x2 = "77.0" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "77.0" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 200</ text >< line x1 = "166.4" y1 = "26" x2 = "166.4" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "166.4" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 500</ text >< line x1 = "234.0" y1 = "26" x2 = "234.0" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "234.0" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 1,000</ text >< line x1 = "301.6" y1 = "26" x2 = "301.6" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "301.6" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 2,000</ text >< line x1 = "391.0" y1 = "26" x2 = "391.0" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "391.0" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 5,000</ text >< line x1 = "458.6" y1 = "26" x2 = "458.6" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "458.6" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 10,000</ text >< line x1 = "526.2" y1 = "26" x2 = "526.2" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "526.2" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 20,000</ text >< path d = "M64.0,278.0 L544.0,26.0" fill = "none" stroke = "#6b7280" stroke-width = "2.4" stroke-dasharray = "5,4" stroke-linejoin = "round" />< circle cx = "64.0" cy = "278.0" r = "4.2" fill = "#6b7280" stroke = "#fff" stroke-width = "1.4" />< circle cx = "544.0" cy = "26.0" r = "4.2" fill = "#6b7280" stroke = "#fff" stroke-width = "1.4" />< path d = "M98.8,259.9 L166.4,224.4 L234.0,188.8 L301.6,153.3 L369.2,117.9 L436.8,83.7" fill = "none" stroke = "#dc2626" stroke-width = "2.4" stroke-linejoin = "round" />< circle cx = "98.8" cy = "259.9" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "166.4" cy = "224.4" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "234.0" cy = "188.8" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "301.6" cy = "153.3" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "369.2" cy = "117.9" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "436.8" cy = "83.7" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< path d = "M166.4,224.4 L234.0,188.8 L301.6,153.3 L369.2,117.9 L436.8,83.3 L504.5,81.0 L458.6,80.8 L480.4,80.8" fill = "none" stroke = "#2563eb" stroke-width = "2.4" stroke-linejoin = "round" />< rect x = "162.2" y = "220.2" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "229.8" y = "184.6" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "297.4" y = "149.1" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "365.0" y = "113.7" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-widt
< div class = "fig" >< div class = "cap" > ② 从站每拍负担:带 CAN vs 无 CAN</ div >< svg viewBox = "0 0 620 350" width = "100%" xmlns = "http://www.w3.org/2000/svg" role = "img" aria-label = "从站每拍负担" >< line x1 = "64" y1 = "308.0" x2 = "604" y2 = "308.0" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "311.5" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 0</ text >< line x1 = "64" y1 = "245.1" x2 = "604" y2 = "245.1" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "248.6" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 25.0</ text >< line x1 = "64" y1 = "182.1" x2 = "604" y2 = "182.1" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "185.6" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 50.0</ text >< line x1 = "64" y1 = "119.2" x2 = "604" y2 = "119.2" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "122.7" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 75.0</ text >< line x1 = "64" y1 = "56.2" x2 = "604" y2 = "56.2" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "59.7" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 100</ text >< line x1 = "73.0" y1 = "26" x2 = "73.0" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "73.0" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 200</ text >< line x1 = "194.0" y1 = "26" x2 = "194.0" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "194.0" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 500</ text >< line x1 = "285.7" y1 = "26" x2 = "285.7" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "285.7" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 1,000</ text >< line x1 = "377.3" y1 = "26" x2 = "377.3" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "377.3" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 2,000</ text >< line x1 = "498.4" y1 = "26" x2 = "498.4" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "498.4" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 5,000</ text >< line x1 = "590.0" y1 = "26" x2 = "590.0" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "590.0" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 10,000</ text >< line x1 = "64" y1 = "56.2" x2 = "604" y2 = "56.2" stroke = "#6b7280" stroke-width = "1.2" stroke-dasharray = "2,4" />< text x = "600" y = "51.2" text-anchor = "end" font-size = "10" fill = "#6b7280" > 100%</ text >< path d = "M102.0,257.1 L193.7,236.7 L285.4,196.0 L377.2,114.9 L468.5,57.0 L556.8,58.0" fill = "none" stroke = "#dc2626" stroke-width = "2.4" stroke-linejoin = "round" />< circle cx = "102.0" cy = "257.1" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "193.7" cy = "236.7" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "285.4" cy = "196.0" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "377.2" cy = "114.9" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "468.5" cy = "57.0" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "556.8" cy = "58.0" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< path d = "M193.7,260.2 L285.4,218.9 L377.2,136.8 L468.5,57.7 L557.8,59.2 L563.7,58.7 L564.2,59.0 L564.3,59.0" fill = "none" stroke = "#2563eb" stroke-width = "2.4" stroke-linejoin = "round" />< rect x = "189.5" y = "256.0" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "281.2" y = "214.7" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "373.0" y = "132.6" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "464.3" y = "53.5" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "553.6" y = "55.0" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "559.5" y = "54.5" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "560.0" y = "54.8" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< rect x = "560.1" y = "54.8" width = "8.4" height = "8.4" fill = "#2563eb" stroke = "#fff" stroke-width = "1.4" />< text x = "193.7" y = "248.5" text-anchor = "middle" font-size = "10" font-weight = "600" fill = "#7c3aed" > Δ9.3pp</ text >< text x = "285.4" y = "207.4" text-anchor = "middle" font-size = "10" font-weight = "600" fill = "#7c3aed" > Δ9.1pp</ text >< text x = "377.2
<div class=" fig " >< div class = "cap" > ③ CAN 流量分层:STATUS 刷新与 CONFIG 事件流</ div >< svg viewBox = "0 0 620 350" width = "100%" xmlns = "http://www.w3.org/2000/svg" role = "img" aria-label = "CAN 流量分层" >< line x1 = "64" y1 = "308.0" x2 = "604" y2 = "308.0" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "311.5" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 10.0</ text >< line x1 = "64" y1 = "282.3" x2 = "604" y2 = "282.3" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "285.8" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 20.0</ text >< line x1 = "64" y1 = "248.3" x2 = "604" y2 = "248.3" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "251.8" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 50.0</ text >< line x1 = "64" y1 = "222.6" x2 = "604" y2 = "222.6" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "226.1" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 100</ text >< line x1 = "64" y1 = "196.9" x2 = "604" y2 = "196.9" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "200.4" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 200</ text >< line x1 = "64" y1 = "162.9" x2 = "604" y2 = "162.9" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "166.4" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 500</ text >< line x1 = "64" y1 = "137.1" x2 = "604" y2 = "137.1" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "140.6" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 1,000</ text >< line x1 = "64" y1 = "111.4" x2 = "604" y2 = "111.4" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "114.9" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 2,000</ text >< line x1 = "64" y1 = "77.4" x2 = "604" y2 = "77.4" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "80.9" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 5,000</ text >< line x1 = "64" y1 = "51.7" x2 = "604" y2 = "51.7" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "55.2" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 10,000</ text >< line x1 = "64" y1 = "26.0" x2 = "604" y2 = "26.0" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "29.5" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 20,000</ text >< line x1 = "73.1" y1 = "26" x2 = "73.1" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "73.1" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 200</ text >< line x1 = "195.9" y1 = "26" x2 = "195.9" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "195.9" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 500</ text >< line x1 = "288.8" y1 = "26" x2 = "288.8" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "288.8" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 1,000</ text >< line x1 = "381.7" y1 = "26" x2 = "381.7" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "381.7" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 2,000</ text >< line x1 = "504.5" y1 = "26" x2 = "504.5" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "504.5" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 5,000</ text >< line x1 = "597.4" y1 = "26" x2 = "597.4" y2 = "308" stroke = "#eef1f5" stroke-width = "1" />< text x = "597.4" y = "324" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 10,000</ text >< path d = "M102.6,85.8 L195.5,85.8 L288.5,85.8 L381.7,85.8 L474.2,205.3 L563.8,198.9" fill = "none" stroke = "#16a34a" stroke-width = "2.4" stroke-linejoin = "round" />< circle cx = "102.6" cy = "85.8" r = "4.2" fill = "#16a34a" stroke = "#fff" stroke-width = "1.4" />< circle cx = "195.5" cy = "85.8" r = "4.2" fill = "#16a34a" stroke = "#fff" stroke-width = "1.4" />< circle cx = "288.5" cy = "85.8" r = "4.2" fill = "#16a34a" stroke = "#fff" stroke-width = "1.4" />< circle cx = "381.7" cy = "85.8" r = "4.2" fill = "#16a34a" stroke = "#fff" stroke-width = "1.4" />< circle cx = "474.2" cy = "205.3" r = "4.2" fill = "#16a34a" stroke = "#fff" stroke-width = "1.4" />< circle cx = "563.8" cy = "198.9" r = "4.2" fill = "#16a34a" stroke = "#fff" stroke-width = "1.4" />< rect x = "411.6" y = "26" width = "171.7" height = "282" fill = "#ef4444" opacity = "0.07" />< text x = "333.9" y = "74.6" text-anchor = "middle" font-size = "10" font-weight = "400" fill = "#166534" > 带 CAN 轮:STATUS 全流量</ text >< text x = "481.2" y = "207.5" text-anchor = "middle" font-size = "11" font-weight = "700" fill = "#b91c1c" > 塌陷</ text >< line x1 = "76" y1 = "38" x2 = "98" y2 = "38" stroke = "#16a34a" stroke-width = "2.6" />< circl
< div class = "fig" >< div class = "cap" > ④ 主站错拍率</ div >< svg viewBox = "0 0 560 320" width = "100%" xmlns = "http://www.w3.org/2000/svg" role = "img" aria-label = "主站错拍率" >< line x1 = "64" y1 = "273.7" x2 = "544" y2 = "273.7" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "277.2" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 0.10</ text >< line x1 = "64" y1 = "260.2" x2 = "544" y2 = "260.2" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "263.7" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 0.20</ text >< line x1 = "64" y1 = "242.4" x2 = "544" y2 = "242.4" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "245.9" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 0.50</ text >< line x1 = "64" y1 = "229.0" x2 = "544" y2 = "229.0" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "232.5" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 1</ text >< line x1 = "64" y1 = "215.5" x2 = "544" y2 = "215.5" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "219.0" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 2</ text >< line x1 = "64" y1 = "197.7" x2 = "544" y2 = "197.7" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "201.2" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 5</ text >< line x1 = "64" y1 = "184.2" x2 = "544" y2 = "184.2" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "187.7" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 10.0</ text >< line x1 = "64" y1 = "170.8" x2 = "544" y2 = "170.8" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "174.3" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 20.0</ text >< line x1 = "64" y1 = "153.0" x2 = "544" y2 = "153.0" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "156.5" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 50.0</ text >< line x1 = "64" y1 = "139.5" x2 = "544" y2 = "139.5" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "143.0" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 100</ text >< line x1 = "64" y1 = "126.1" x2 = "544" y2 = "126.1" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "129.6" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 200</ text >< line x1 = "64" y1 = "108.3" x2 = "544" y2 = "108.3" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "111.8" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 500</ text >< line x1 = "64" y1 = "94.8" x2 = "544" y2 = "94.8" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "98.3" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 1,000</ text >< line x1 = "64" y1 = "81.4" x2 = "544" y2 = "81.4" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "84.9" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 2,000</ text >< line x1 = "64" y1 = "63.6" x2 = "544" y2 = "63.6" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "67.1" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 5,000</ text >< line x1 = "64" y1 = "50.1" x2 = "544" y2 = "50.1" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "53.6" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 10,000</ text >< line x1 = "64" y1 = "36.7" x2 = "544" y2 = "36.7" stroke = "#e8ebf0" stroke-width = "1" />< text x = "56" y = "40.2" text-anchor = "end" font-size = "10.5" fill = "#6b7280" > 20,000</ text >< line x1 = "105.1" y1 = "20" x2 = "105.1" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "105.1" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 500</ text >< line x1 = "203.2" y1 = "20" x2 = "203.2" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "203.2" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 1,000</ text >< line x1 = "301.2" y1 = "20" x2 = "301.2" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "301.2" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 2,000</ text >< line x1 = "430.9" y1 = "20" x2 = "430.9" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "430.9" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 5,000</ text >< line x1 = "529.0" y1 = "20" x2 = "529.0" y2 = "278" stroke = "#eef1f5" stroke-width = "1" />< text x = "529.0" y = "294" text-anchor = "middle" font-size = "10.5" fill = "#6b7280" > 10,000</ text >< path d = "M104.7,278.0 L202.9,227.1 L301.2,180.9 L398.9,139.8 L493.4,63.2" fill = "none" stroke = "#dc2626" stroke-width = "2.4" stroke-linejoin = "round" />< circle cx = "104.7" cy = "278.0" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-width = "1.4" />< circle cx = "202.9" cy = "227.1" r = "4.2" fill = "#dc2626" stroke = "#fff" stroke-w
</ div >
< div class = "card" >< h3 > 名词解释(表里这几个计数器到底是什么)</ h3 >< ul >< li >< b > WKC( working counter,工作计数器)</ b > :EtherCAT 报文自带一个计数器,每有一个从站硬件处理了该报文就 +1。主站知道自己发出了几个报文,这个应得值就是 < code > expected_wkc</ code > (本台子 1 个从站、每拍 3 个报文 → 期望 3)。所以 < code > wkc 3/3</ code > = 从站每拍都完整处理了;< code > wkc -1</ code > = 整帧没回来(从站停答/掉 OP);< code > wr( watchdog reset) 0→1</ code > = 看门狗把总线重拉过一次。</ li >< li >< b > 主站错拍 skip</ b > :主站按周期给自己排了一个"节拍网格"(< code > ecat_native.c:776-791</ code > )。每拍做完 EtherCAT 收发后,若发现已经错过了下一个节拍点(甚至错过多个),就把错过的个数累进 < code > skip_total</ code > 。< b > 它量的是"主机软件迟到",不是从站故障</ b > —— 1ms 档 1.1 次/s 只是偶发 OS 调度抖动;0.125ms 档 5107 次/s 说明单拍耗时已超过两个周期,主机也在勉强。它不区分"从站慢"和"主机慢"。</ li >< li >< b > 在线</ b > :电机槽位的 < code > online</ code > 标志(槽位数据在超时窗内持续刷新才为 true)。带 CAN 轮显示"未记录"是因为当时脚本还没有这列,< b > 不是电机掉线</ b > ;该轮的电机在线状况另有 8s/档定点实测佐证。</ li ></ ul ></ div >
< h2 > 二、数据全表</ h2 >
< h3 > 带 CAN(主结论轮)</ h3 >< table >< tr >< th > 周期</ th >< th > 实测Hz</ th >< th > 达成率</ th >< th > 从站load</ th >< th > CAN接收/s</ th >< th > skip/s</ th >< th > wkc</ th >< th > 在线</ th ></ tr >< tr >< td > 4ms</ td >< td > 249</ td >< td > 99.7%</ td >< td > 20.2%</ td >< td > 3,987</ td >< td > 0.02</ td >< td > 3/3</ td >< td > 未记录*</ td ></ tr >< tr >< td > 2ms</ td >< td > 499</ td >< td > 99.7%</ td >< td > 28.3%</ td >< td > 3,990</ td >< td > 0.08</ td >< td > 3/3</ td >< td > 未记录*</ td ></ tr >< tr >< td > 1ms</ td >< td > 998</ td >< td > 99.8%</ td >< td > 44.5%</ td >< td > 3,992</ td >< td > 1.10</ td >< td > 3/3</ td >< td > 未记录*</ td ></ tr >< tr class = "best" >< td > 0.5ms</ td >< td > 1,999</ td >< td > 100.0%</ td >< td > 76.7%</ td >< td > 3,995</ td >< td > 11.86</ td >< td > 3/3</ td >< td > 未记录*</ td ></ tr >< tr >< td > 0.25ms</ td >< td > 3,989</ td >< td > 99.7%</ td >< td > 99.7%</ td >< td > 159</ td >< td > 98.61</ td >< td > 3/3</ td >< td > 未记录*</ td ></ tr >< tr >< td > 0.125ms</ td >< td > 7,780</ td >< td > 97.2%</ td >< td > 99.3%</ td >< td > 190</ td >< td > 5,106.62</ td >< td > 3/3</ td >< td > 未记录*</ td ></ tr ></ table >
< h3 > 拔掉全部 CAN 设备(补测轮)</ h3 >< table >< tr >< th > 周期</ th >< th > 实测Hz</ th >< th > 达成率</ th >< th > 从站load</ th >< th > CAN接收/s</ th >< th > skip/s</ th >< th > wkc</ th >< th > 在线</ th ></ tr >< tr >< td > 2ms</ td >< td > 499</ td >< td > 99.7%</ td >< td > 19.0%</ td >< td > 0</ td >< td > 0.08</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr >< tr >< td > 1ms</ td >< td > 998</ td >< td > 99.8%</ td >< td > 35.4%</ td >< td > 0</ td >< td > 0.58</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr >< tr class = "best" >< td > 0.5ms</ td >< td > 2,000</ td >< td > 100.0%</ td >< td > 68.0%</ td >< td > 0</ td >< td > 17.08</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr >< tr >< td > 0.25ms</ td >< td > 3,988</ td >< td > 99.7%</ td >< td > 99.4%</ td >< td > 0</ td >< td > 39.72</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr >< tr >< td > 0.125ms</ td >< td > 7,839</ td >< td > 98.0%</ td >< td > 98.8%</ td >< td > 0</ td >< td > 4,672.07</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr >< tr >< td > 0.0625ms</ td >< td > 8,196</ td >< td > 51.2%</ td >< td > 99.0%</ td >< td > 0</ td >< td > 15,720.46</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr >< tr >< td > 0.1ms</ td >< td > 8,232</ td >< td > 82.3%</ td >< td > 98.9%</ td >< td > 0</ td >< td > 10,039.19</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr >< tr >< td > 0.08ms</ td >< td > 8,238</ td >< td > 65.9%</ td >< td > 98.9%</ td >< td > 0</ td >< td > 12,282.87</ td >< td > 3/3</ td >< td > 0.00</ td ></ tr ></ table >
< p class = "meta" >< b > *</ b > 「在线」列是扫描脚本后来才加的(< code > motors_online</ code > /< code > online_frac</ code > ),带 CAN 那轮跑在加列< b > 之前</ b > ,逐档数据没采 —— < b > 不是全部掉线</ b > 。该轮电机在线另有 8s/档定点实测(「电机数据刷新率」表)佐证。</ p >
< h3 > 电机数据刷新率(带 CAN 轮,8s/档定点实测)</ h3 >< table >< tr >< th > 周期</ th >< th > 刷新率 帧/s</ th >< th > 判定</ th ></ tr >< tr >< td > 1ms</ td >< td > 4,000.2</ td >< td > 正常</ td ></ tr >< tr >< td > 0.5ms</ td >< td > 4,000.2</ td >< td > 正常</ td ></ tr >< tr >< td > 0.25ms</ td >< td > 87.9</ td >< td > 塌陷</ td ></ tr >< tr >< td > 0.125ms</ td >< td > 147.5</ td >< td > 塌陷</ td ></ tr ></ table >< p class = "meta" > 这是唯一直接回答"用户还能不能用"的指标 —— λ 与 drop 都是中间量。塌陷两档当场确认 < code > online=True</ code > ,故为真失效而非掉线。</ p >
< h2 > 三、机理与上限推算</ h2 >
< div class = "card" >< h3 > 上限已实测:≈8.2kHz,墙是单拍 ≈121µs 的硬耗时</ h3 >< p >< b > 证据链</ b > :无 CAN 设定 0.25 → 0.0625ms 五档,实测循环率在 ≤0.125ms (8kHz)前跟随设定,再快就被钉死在 8196–8238Hz;与此同时主站单拍耗时 < code > cyc p50</ code > 在< b > 所有档位</ b > (含 2ms 慢档)恒为 117– 126µs, 1/121µs ≈ 8.2kHz,与顶墙实测严丝合缝。即:每拍有一段不可压缩的「发送→从站处理→回读+主站处理」往返时间,周期设定小于它时就堵在这里。</ p >< p >< b > 排除项</ b > :不是主机 CPU 不够 —— 顶墙档主机 CPU 只有 ~85%(自旋等待占比变小);不是从站拒答 —— wkc 全程 3/3。< code > load_perm</ code > 在 ≥4kHz 全饱和 99.x%,既定位不了拐点也喂不准模型,早先「两项模型 load=F/T+R → 2.7/3.5kHz」的外推已被满速补测证伪,仅留档。</ p >< p >< b > 未闭合的一处</ b > :121µs 里主站协议栈与从站 ESC/固件各占多少,本轮数据分不开 —— 需给固件 < code > fillRxWindow</ code > 加直接计时(固件改动,需批准)或抓总线帧时序才能归因。</ p ></ div >
< h2 > 四、判据口径与踩过的坑</ h2 >
2026-09-12 20:10:47 +08:00
< div class = "card warn" >< h3 > 仪表陷阱(每条都实际踩过)</ h3 >< ul >< li >< b > 【本次真正翻车处】把两个同速计数器相除当成比率</ b > —— < code > drop</ code > /< code > rx_count</ code > 都约 4000/s,相除恒得 100%,看着像"全丢",其实只是< b > 同速量之比</ b > 。判丢帧必须有< b > 独立</ b > 的分子与分母:本次真正的独立证据是「STATUS 实测 4001 帧/s 正常刷新」+「CONFIG 事件流去重后仅约 92 帧/s」,两者一起否掉了 100% 丢帧。< b > 任何"A/B 比率"指标,先问 A 与 B 是否独立计数。</ b ></ li >< li >< b > 从站停报后 < code > load_perm</ code > 保留残值</ b > —— 实测 < code > age_ms=214338</ code > 时仍报 99.2%。任何取 load_perm 的脚本必须先过 < code > age_ms</ code > 闸。< b > 扫描脚本已加该闸</ b > 并新增 < code > gd32_load_samples</ code > /< code > gd32_load_stale</ code > 两列。</ li >< li >< b >< code > load_perm</ code > 不是整机 CPU 占用率</ b > —— 固件 < code > sys/cpu_load.h</ code > 定义收窄为"中断+非延迟处理段", < b > 排除</ b > SSC MainLoop 轮询;当前 free-run( < code > dc=0</ code > )下 PDO 应用走主循环轮询路径、< b > 不计入</ b > 。所以不能把"load→100%"读成"CPU 耗尽"。</ li >< li >< b >< code > OVF</ code > 每拍被 < code > s_rxWinOvf=0</ code > 清零</ b > —— GD32 侧没有可用的累计丢帧计数。读它恒 0 < b > 不能</ b > 当"无丢帧"证据(我据此误判 < code > bridge.drop</ code > 是死字段,结论已收回)。</ li >< li >< b >< code > /api/can/cadence</ code > 只存最近 512 拍</ b > —— 0.25ms 档 =128ms、0.125ms=64ms,环没装满时取到的 < code > count</ code > /< code > lost</ code > 是< b > 行数</ b > 不是每拍帧数。对应 CSV 列已从结论排除。</ li >< li >< b > u16 回绕</ b > : < code > drop</ code > 在 4000/s 下 16 秒绕一圈 → 10Hz 逐次累加 + 取模,单次增量> 32768 记为作废。< b > 本次作废点数=0</ b > 。</ li >< li >< b > 「seq 值域 − distinct」不是丢帧率</ b > :报 99.686%,方向与 drop≈λ 一致但分母含 STATUS 全流量,口径不干净 → 只作旁证。</ li >< li >< b > 周期可被 localStorage 劫持</ b > ( < code > transport-ecat.js:227-228</ code > )→ 每档下发后回读校验。本次六档 < code > hijacked=no</ code > 、native PID 全程 29284 未变。</ li >< li >< b >< code > cyc_us.late</ code > /< code > lost</ code > 恒 0</ b > (遗留占位,未接线),未使用。</ li ></ ul ></ div >
2026-09-10 03:02:14 +08:00
< h2 > 五、可信度边界</ h2 >
< div class = "card bad" >< h3 > 这份数据不能证明什么</ h3 >< ul >< li >< b > 板载固件版本无法确认</ b > :烧写台账 < code > 201832b5a8e258d6…</ code > 与在盘 .hex 展平哈希(63d6ab9b…、e6a6e75f…)< b > 均不匹配</ b > ,且产物时间戳晚于台账。→ 本报告不复用任何历史固件结论。</ li >< li > 主站在测量期间又重编过(源码 02:39 / exe 02:40, PID 起于 02:32),无法证明运行中的进程就是盘上那份。</ li >< li >< b >< code > rx_torn</ code > 与 < code > pdo_reentry</ code > 整段恒零 = 无检出功效</ b > ,不是"无撕裂"。二者分别需固件 1.6.6/1.6.7+,版本未知 → 无法区分"没发生"与"字段根本没实现、恒读 0"。</ li >< li > λ≈3993/s 是本次 CAN 工况。< b > λ 变则负担变</ b > , 0.5ms 这个数不可外搬到别流量。</ li >< li > 单台单次,未做重复性与温漂验证。</ li ></ ul ></ div >
< h2 > 六、下一步</ h2 >
< div class = "card" >< h3 > 按优先级</ h3 >< ul >< li >< b > 查明 < code > drop</ code > 口径(当前最高优先,唯一未决的承重项)</ b > 。必须用< b > 一对独立可比</ b > 的计数,不能再拿同速量相除:从 WiFi 侧读固件 u32 < code > rx_drop_count</ code > ( < code > app_spi_cmd_can_feedback.cpp:99</ code > )对速率,同时用 CAN 监控页统计< b > 实收</ b > CONFIG 帧数作分母。只有「独立到达计数 − 独立交付计数」才是丢帧数。</ li >< li >< b > 台子现状</ b > :断电重启后重新 open+start 成功(OP、wkc=3),无 CAN 的 0.25→0.0625ms 五档补测完成,周期已自动恢复 1ms。< b > CAN 设备当前处于拔除状态</ b > ,电机数据相关指标要插回 CAN 才能再测。</ li >< li > 插回 CAN 后补测 < b > 0.35 / 0.4 / 0.5ms</ b > 三档的< b > 电机数据刷新率</ b > ,把带 CAN 工况的可用上限定位出来。load_perm 已被证明在快档饱和,< b > 不能再用它定位拐点</ b > ;扫描脚本的 < code > motors_online</ code > /< code > online_frac</ code > /< code > motor_refresh_per_s</ code > 三列才是可用性判据。</ li >< li > 给 < code > fillRxWindow</ code > 加直接计时,把 121µs 硬耗时里的主/从占比拆开(属固件改动,需批准)。</ li >< li > 0.5ms 档实跑电机页完整命令序列 —— 本调研只测了计数器,没测界面实际可用性。</ li ></ ul ></ div >
< div class = "foot" > 本报告由 < code > scripts/build_ecat_report_html.py</ code > 从 < code > lab/*.csv</ code > 与 < code > lab/ecat_calib.json</ code > 现场渲染,图中每个数值均从原始数据计算,未手抄。native 实时:op=1 loop=1 wkc=3 period=1.0ms。Markdown 版(含同源结论):< code > ECAT_RATE_TRADEOFF.md</ code > 。</ div >
</ main ></ body ></ html >