2026-09-12 20:10:47 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
|
|
|
|
<title>DCU EtherCAT PDO · 字节布局 1.6.21 · 固件 1.6.24</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
:root{
|
|
|
|
|
|
--bg:#070b10; --panel:#101820; --panel2:#141e28;
|
|
|
|
|
|
--line:#1c2834; --line2:#2a3a4a;
|
|
|
|
|
|
--ink:#d8e4f0; --dim:#6d8299; --dim2:#4a5d72;
|
|
|
|
|
|
--cyan:#00d4a0; --blue:#4c9aff; --amber:#f0b429; --violet:#a78bfa;
|
|
|
|
|
|
--rose:#ff5c7a; --slate:#8b9cb0;
|
|
|
|
|
|
--mono:"Cascadia Code","Cascadia Mono","Consolas",monospace;
|
|
|
|
|
|
--sans:"Segoe UI","Microsoft YaHei",system-ui,sans-serif;
|
|
|
|
|
|
}
|
|
|
|
|
|
*{box-sizing:border-box}
|
|
|
|
|
|
body{
|
|
|
|
|
|
margin:0;padding:0 0 72px;
|
|
|
|
|
|
background:
|
|
|
|
|
|
radial-gradient(1100px 500px at 8% -8%, rgba(0,212,160,.07), transparent 50%),
|
|
|
|
|
|
radial-gradient(800px 420px at 95% 0%, rgba(76,154,255,.05), transparent 45%),
|
|
|
|
|
|
var(--bg);
|
|
|
|
|
|
color:var(--ink);
|
|
|
|
|
|
font:14px/1.7 var(--sans);
|
|
|
|
|
|
-webkit-font-smoothing:antialiased;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.wrap{max-width:1120px;margin:0 auto;padding:0 28px}
|
|
|
|
|
|
|
|
|
|
|
|
/* header */
|
|
|
|
|
|
header{
|
|
|
|
|
|
border-bottom:1px solid var(--line);
|
|
|
|
|
|
background:rgba(7,11,16,.92);
|
|
|
|
|
|
backdrop-filter:blur(10px);
|
|
|
|
|
|
position:sticky;top:0;z-index:40;
|
|
|
|
|
|
}
|
|
|
|
|
|
.hd-in{
|
|
|
|
|
|
max-width:1120px;margin:0 auto;padding:14px 28px;
|
|
|
|
|
|
display:flex;align-items:center;gap:20px;flex-wrap:wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
.brand{display:flex;align-items:center;gap:12px}
|
|
|
|
|
|
.brand h1{margin:0;font-size:18px;font-weight:650;letter-spacing:.2px}
|
|
|
|
|
|
.ver{
|
|
|
|
|
|
font:650 11px/1 var(--mono);color:#07120f;background:var(--cyan);
|
|
|
|
|
|
padding:5px 8px;border-radius:5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.sub{color:var(--dim);font-size:12.5px}
|
|
|
|
|
|
.stats{display:flex;gap:8px;margin-left:auto;flex-wrap:wrap}
|
|
|
|
|
|
.stat{
|
|
|
|
|
|
padding:6px 12px;border:1px solid var(--line);border-radius:7px;
|
|
|
|
|
|
background:var(--panel);min-width:86px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.stat b{display:block;font:650 14px/1.3 var(--mono)}
|
|
|
|
|
|
.stat span{display:block;font-size:10.5px;color:var(--dim);letter-spacing:.3px}
|
|
|
|
|
|
|
|
|
|
|
|
/* sections */
|
|
|
|
|
|
section{padding:40px 0 4px}
|
|
|
|
|
|
.sec-head{
|
|
|
|
|
|
display:flex;align-items:center;gap:12px;
|
|
|
|
|
|
margin:0 0 18px;padding-bottom:10px;border-bottom:1px solid var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
.sec-no{
|
|
|
|
|
|
font:700 11px/1 var(--mono);color:#07120f;background:var(--cyan);
|
|
|
|
|
|
width:24px;height:24px;border-radius:50%;display:grid;place-items:center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.sec-head h2{margin:0;font-size:17px;font-weight:650}
|
|
|
|
|
|
.sec-head .tagline{margin-left:auto;color:var(--dim);font:12px var(--mono)}
|
|
|
|
|
|
|
|
|
|
|
|
/* cards */
|
|
|
|
|
|
.card{
|
|
|
|
|
|
background:var(--panel);border:1px solid var(--line);border-radius:12px;
|
|
|
|
|
|
overflow:hidden;margin-bottom:16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-hd{
|
|
|
|
|
|
display:flex;align-items:center;gap:10px;flex-wrap:wrap;
|
|
|
|
|
|
padding:12px 16px;border-bottom:1px solid var(--line);background:var(--panel2);
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-hd h3{margin:0;font-size:13.5px;font-weight:650}
|
|
|
|
|
|
.card-hd .meta{margin-left:auto;color:var(--dim);font:11.5px var(--mono)}
|
|
|
|
|
|
.card-bd{padding:16px}
|
|
|
|
|
|
|
|
|
|
|
|
/* callout */
|
|
|
|
|
|
.callout{
|
|
|
|
|
|
border:1px solid var(--line);border-left:3px solid var(--cyan);
|
|
|
|
|
|
border-radius:10px;background:var(--panel);
|
|
|
|
|
|
padding:14px 16px;margin:0 0 16px;font-size:13.5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.callout.warn{border-left-color:var(--amber)}
|
|
|
|
|
|
.callout.danger{border-left-color:var(--rose)}
|
|
|
|
|
|
.callout h4{margin:0 0 8px;font-size:13px;font-weight:650;color:var(--cyan)}
|
|
|
|
|
|
.callout.warn h4{color:var(--amber)}
|
|
|
|
|
|
.callout.danger h4{color:var(--rose)}
|
|
|
|
|
|
.callout ul{margin:8px 0 0;padding-left:20px}
|
|
|
|
|
|
.callout li{margin:5px 0}
|
|
|
|
|
|
.callout p{margin:0 0 8px}
|
|
|
|
|
|
.callout p:last-child{margin:0}
|
|
|
|
|
|
|
|
|
|
|
|
/* pills / legend */
|
|
|
|
|
|
.pills{display:flex;gap:8px;flex-wrap:wrap;margin:22px 0 18px}
|
|
|
|
|
|
.pill{
|
|
|
|
|
|
font:11.5px var(--mono);color:var(--dim);
|
|
|
|
|
|
border:1px solid var(--line);border-radius:99px;
|
|
|
|
|
|
padding:5px 11px;background:var(--panel);
|
|
|
|
|
|
}
|
|
|
|
|
|
.pill b{color:var(--ink);font-weight:650}
|
|
|
|
|
|
.legend{
|
|
|
|
|
|
display:flex;flex-wrap:wrap;gap:10px 18px;
|
|
|
|
|
|
margin:0 0 18px;font-size:12px;color:var(--dim);
|
|
|
|
|
|
}
|
|
|
|
|
|
.legend i{
|
|
|
|
|
|
display:inline-block;width:11px;height:11px;border-radius:3px;
|
|
|
|
|
|
margin-right:6px;vertical-align:-1px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* svg maps */
|
|
|
|
|
|
.map-wrap{margin:0 0 8px}
|
|
|
|
|
|
.map-wrap + .map-wrap{margin-top:14px}
|
|
|
|
|
|
.map-wrap svg{display:block;width:100%;height:auto}
|
|
|
|
|
|
.seg-label{
|
|
|
|
|
|
font:600 11px var(--mono);color:var(--dim);
|
|
|
|
|
|
margin:18px 0 8px;letter-spacing:.2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.seg-label:first-child{margin-top:0}
|
|
|
|
|
|
|
|
|
|
|
|
/* field bar (HTML, 防 SVG 叠压) */
|
|
|
|
|
|
.fbar{
|
|
|
|
|
|
display:flex;gap:2px;height:52px;
|
|
|
|
|
|
border:1px solid var(--line);border-radius:6px;overflow:hidden;
|
|
|
|
|
|
background:#0a1218;
|
|
|
|
|
|
}
|
|
|
|
|
|
.fcell{
|
|
|
|
|
|
position:relative;display:flex;flex-direction:column;
|
|
|
|
|
|
align-items:center;justify-content:center;gap:2px;
|
|
|
|
|
|
min-width:0;padding:4px 2px;overflow:hidden;
|
|
|
|
|
|
border-right:1px solid rgba(0,0,0,.45);
|
|
|
|
|
|
}
|
|
|
|
|
|
.fcell:last-child{border-right:0}
|
|
|
|
|
|
.fcell .fn{
|
|
|
|
|
|
font:650 11px/1.2 var(--mono);color:var(--ink);
|
|
|
|
|
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.fcell .fo{
|
|
|
|
|
|
font:10px/1.2 var(--mono);color:var(--dim);
|
|
|
|
|
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.fcell.narrow .fn{font-size:10px}
|
|
|
|
|
|
.fcell.pad .fn,.fcell.pad .fo{color:var(--dim2)}
|
|
|
|
|
|
.ruler{
|
|
|
|
|
|
display:flex;gap:2px;margin-top:4px;
|
|
|
|
|
|
font:10px var(--mono);color:var(--dim2);
|
|
|
|
|
|
}
|
|
|
|
|
|
.ruler span{min-width:0;overflow:hidden;white-space:nowrap}
|
|
|
|
|
|
|
|
|
|
|
|
/* tables in cards */
|
|
|
|
|
|
.tbl-card{margin-top:0}
|
|
|
|
|
|
table{border-collapse:collapse;width:100%;font-size:13px}
|
|
|
|
|
|
th,td{
|
|
|
|
|
|
border-bottom:1px solid var(--line);
|
|
|
|
|
|
padding:9px 12px;text-align:left;vertical-align:top;
|
|
|
|
|
|
}
|
|
|
|
|
|
th{
|
|
|
|
|
|
color:var(--dim);font-weight:600;font-size:11px;
|
|
|
|
|
|
letter-spacing:.4px;text-transform:uppercase;
|
|
|
|
|
|
background:var(--panel2);
|
|
|
|
|
|
}
|
|
|
|
|
|
td.n,th.n{text-align:right;font-family:var(--mono);white-space:nowrap}
|
|
|
|
|
|
td.mono{font-family:var(--mono);white-space:nowrap;color:var(--dim)}
|
|
|
|
|
|
tbody tr:hover td{background:rgba(0,212,160,.035)}
|
|
|
|
|
|
tbody tr:last-child td{border-bottom:0}
|
|
|
|
|
|
tfoot td{font-weight:650;background:var(--panel2);border-bottom:0}
|
|
|
|
|
|
.dot{
|
|
|
|
|
|
display:inline-block;width:9px;height:9px;border-radius:2px;
|
|
|
|
|
|
margin-right:8px;vertical-align:0;
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip{
|
|
|
|
|
|
display:inline-block;padding:2px 8px;border-radius:99px;
|
|
|
|
|
|
font:600 11px/1.5 var(--mono);
|
|
|
|
|
|
}
|
|
|
|
|
|
.chip.fix{background:rgba(255,92,122,.12);color:var(--rose)}
|
|
|
|
|
|
.chip.adp{background:rgba(0,212,160,.12);color:var(--cyan)}
|
|
|
|
|
|
.chip.hdr{background:rgba(240,180,41,.12);color:var(--amber)}
|
|
|
|
|
|
.chip.gone{background:rgba(167,139,250,.12);color:var(--violet)}
|
|
|
|
|
|
.chip.note{background:rgba(139,156,176,.12);color:var(--slate)}
|
|
|
|
|
|
|
|
|
|
|
|
code{
|
|
|
|
|
|
font-family:var(--mono);font-size:12px;
|
|
|
|
|
|
background:#0a1218;padding:1px 6px;border-radius:3px;border:1px solid var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
pre{
|
|
|
|
|
|
margin:10px 0 0;padding:12px 14px;
|
|
|
|
|
|
background:#0a1218;border:1px solid var(--line);border-radius:8px;
|
|
|
|
|
|
overflow-x:auto;font:12px/1.6 var(--mono);color:var(--ink);
|
|
|
|
|
|
}
|
|
|
|
|
|
pre code{background:none;border:0;padding:0}
|
|
|
|
|
|
|
|
|
|
|
|
/* cost */
|
|
|
|
|
|
.cost-grid{
|
|
|
|
|
|
display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:0 0 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
@media(max-width:800px){.cost-grid{grid-template-columns:1fr}}
|
|
|
|
|
|
.cost-card{
|
|
|
|
|
|
background:var(--panel);border:1px solid var(--line);border-radius:10px;
|
|
|
|
|
|
padding:14px 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.cost-card .lbl{font-size:11px;color:var(--dim);letter-spacing:.4px;text-transform:uppercase}
|
|
|
|
|
|
.cost-card .val{font:650 32px/1.2 var(--mono);margin:6px 0 2px}
|
|
|
|
|
|
.cost-card .unit{font-size:12.5px;color:var(--dim)}
|
|
|
|
|
|
.cost-card .bar{height:5px;border-radius:3px;background:var(--line);margin-top:12px;overflow:hidden}
|
|
|
|
|
|
.cost-card .bar i{display:block;height:100%;border-radius:3px}
|
|
|
|
|
|
.cost-card.c-cyan .val{color:var(--cyan)}
|
|
|
|
|
|
.cost-card.c-cyan .bar i{background:var(--cyan)}
|
|
|
|
|
|
.cost-card.c-blue .val{color:var(--blue)}
|
|
|
|
|
|
.cost-card.c-blue .bar i{background:var(--blue)}
|
|
|
|
|
|
.cost-card.c-amber .val{color:var(--amber)}
|
|
|
|
|
|
.cost-card.c-amber .bar i{background:var(--amber)}
|
|
|
|
|
|
|
|
|
|
|
|
footer{
|
|
|
|
|
|
margin-top:48px;padding-top:18px;border-top:1px solid var(--line);
|
|
|
|
|
|
color:var(--dim);font-size:12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
footer code{font-size:11px}
|
|
|
|
|
|
|
|
|
|
|
|
@media(max-width:720px){
|
|
|
|
|
|
.wrap,.hd-in{padding-left:16px;padding-right:16px}
|
|
|
|
|
|
.stats{width:100%;margin-left:0}
|
|
|
|
|
|
.fbar{height:46px}
|
|
|
|
|
|
.fcell .fn{font-size:10px}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<div class="hd-in">
|
|
|
|
|
|
<div class="brand">
|
|
|
|
|
|
<h1>DCU EtherCAT PDO 布局</h1>
|
|
|
|
|
|
<span class="ver">布局 1.6.21</span>
|
|
|
|
|
|
<span class="sub">固件 1.6.24 · 字段级 · 稀疏写</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stats">
|
|
|
|
|
|
<div class="stat"><b>384 B</b><span>输出 SM2</span></div>
|
|
|
|
|
|
<div class="stat"><b>600 B</b><span>输入 SM3</span></div>
|
|
|
|
|
|
<div class="stat"><b>96–600 B</b><span>稀疏写实占</span></div>
|
|
|
|
|
|
<div class="stat"><b>40 B</b><span>DPRAM 余量</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="wrap">
|
|
|
|
|
|
|
|
|
|
|
|
<div class="pills">
|
|
|
|
|
|
<span class="pill">真源 <b>ethercat_can_bridge.h</b></span>
|
|
|
|
|
|
<span class="pill">尺寸 <b>ethercat_pdo_size.h</b></span>
|
2026-09-13 04:13:28 +08:00
|
|
|
|
<span class="pill">PRAM 写 <b>从站侧</b>(≠ 主站 rx)</span>
|
2026-09-12 20:10:47 +08:00
|
|
|
|
<span class="pill">魔数 <b>@35=0xA5</b></span>
|
|
|
|
|
|
<span class="pill">电机槽 <b>≤9 × 56 B</b></span>
|
|
|
|
|
|
<span class="pill">稀疏写 <b>CONFIG=1</b></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="callout">
|
|
|
|
|
|
<h4>布局 1.6.21 · 固件 1.6.24 · 写侧 1.6.22 起稀疏</h4>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li><b>字节布局仍是 1.6.21</b>:段 2 密排、删段 4、魔数 <code>@35=0xA5</code>;out 384 + in 600 不变,SII/EEPROM 无需重刷。</li>
|
|
|
|
|
|
<li><b>1.6.22 稀疏写 ESC</b>(<code>CONFIG_ECAT_SPARSE_ESC_WRITE=1</code>):按 <code>count</code> 只写「段1+段2有效区」与「段3+段5」,跳过空槽区。主站解码不变,仍只按 count 迭代。</li>
|
|
|
|
|
|
<li><b>1.6.23/1.6.24</b> 为 LAN9252 电源管理诊断(pmDrive D1/D2/D3),不影响 PDO 布局。</li>
|
|
|
|
|
|
<li>回退整包 600B 的条件:开关置 0 / magic≠0xA5 / count 越界 / count 撑满(9 电机两段相接)。</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="legend">
|
|
|
|
|
|
<span><i style="background:var(--blue)"></i>桥状态 / 快照</span>
|
|
|
|
|
|
<span><i style="background:var(--amber)"></i>段头 / 元数据</span>
|
|
|
|
|
|
<span><i style="background:var(--cyan)"></i>电机密排槽</span>
|
|
|
|
|
|
<span><i style="background:var(--violet)"></i>批量发送区</span>
|
|
|
|
|
|
<span><i style="background:var(--rose)"></i>诊断</span>
|
|
|
|
|
|
<span><i style="background:var(--slate)"></i>保留 / 填充</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ═══ 输入 PDO ═══ -->
|
|
|
|
|
|
<section id="in">
|
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
|
<span class="sec-no">1</span>
|
|
|
|
|
|
<h2>输入 PDO · 600 B · 从站 → 主站</h2>
|
|
|
|
|
|
<span class="tagline">稀疏写 96–600 B · 整包 ≈265 µs · 3 电机 ≈116 µs</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-hd">
|
|
|
|
|
|
<h3>段总览</h3>
|
|
|
|
|
|
<span class="meta">SM3 · 0x1880–0x1F67 · 3-buffer · 写侧稀疏</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-bd">
|
|
|
|
|
|
<div class="map-wrap" id="in-overview"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-hd">
|
|
|
|
|
|
<h3>段内字段</h3>
|
|
|
|
|
|
<span class="meta">每段独占全宽 · 宽度 ∝ 字节数</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-bd" id="in-fields"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card tbl-card">
|
|
|
|
|
|
<div class="card-hd"><h3>字段明细</h3></div>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>段 / 字段</th><th class="n">偏移</th><th class="n">宽</th>
|
|
|
|
|
|
<th>类型</th><th>内容</th><th>属性</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody id="in-tbody"></tbody>
|
|
|
|
|
|
<tfoot>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td colspan="2">合计</td>
|
|
|
|
|
|
<td class="n">600</td>
|
|
|
|
|
|
<td></td>
|
|
|
|
|
|
<td>逻辑总长;写侧按 count 稀疏(见下)</td>
|
|
|
|
|
|
<td class="n">96–600 B</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tfoot>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ═══ 输出 PDO ═══ -->
|
|
|
|
|
|
<section id="out">
|
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
|
<span class="sec-no">2</span>
|
|
|
|
|
|
<h2>输出 PDO · 384 B · 主站 → 从站</h2>
|
|
|
|
|
|
<span class="tagline">pramRead(384) · 96 DWORD · ≈169 µs 折算</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-hd">
|
|
|
|
|
|
<h3>段总览</h3>
|
|
|
|
|
|
<span class="meta">SM2 · 0x1400–0x187F · WIN@8 忽略</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-bd">
|
|
|
|
|
|
<div class="map-wrap" id="out-overview"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-hd">
|
|
|
|
|
|
<h3>段内字段</h3>
|
|
|
|
|
|
<span class="meta">每段独占全宽 · 宽度 ∝ 字节数</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-bd" id="out-fields"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card tbl-card">
|
|
|
|
|
|
<div class="card-hd"><h3>字段明细</h3></div>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>段 / 字段</th><th class="n">偏移</th><th class="n">宽</th>
|
|
|
|
|
|
<th>类型</th><th>内容</th><th>属性</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody id="out-tbody"></tbody>
|
|
|
|
|
|
<tfoot>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td colspan="2">合计</td>
|
|
|
|
|
|
<td class="n">384</td>
|
|
|
|
|
|
<td></td>
|
|
|
|
|
|
<td>每拍一次 <code>pramRead(384)</code>(未实测)</td>
|
|
|
|
|
|
<td class="n">≈169 µs</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tfoot>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ═══ 电机槽 ═══ -->
|
|
|
|
|
|
<section id="slot">
|
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
|
<span class="sec-no">3</span>
|
|
|
|
|
|
<h2>电机槽内部 · 56 B × ≤9</h2>
|
|
|
|
|
|
<span class="tagline">PDO 地址 = 40 + i×56 · node_id 升序</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-hd">
|
|
|
|
|
|
<h3>密排槽区 @40..543</h3>
|
|
|
|
|
|
<span class="meta">空节点不占位 · count 决定有效槽数</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-bd">
|
|
|
|
|
|
<div class="map-wrap" id="slot-strip"></div>
|
|
|
|
|
|
<div class="seg-label">单槽 56 B 字段</div>
|
|
|
|
|
|
<div id="slot-fields"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-hd"><h3>主站解码</h3></div>
|
|
|
|
|
|
<div class="card-bd">
|
|
|
|
|
|
<pre><code>count = in[32]; // 0..9
|
|
|
|
|
|
magic = in[35]; // 必须 0xA5
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
|
|
p = in + 40 + i*56;
|
|
|
|
|
|
node = p[0]; ch = p[1]; online = p[2];
|
|
|
|
|
|
seq = le32(p+4);
|
|
|
|
|
|
status = p+8; // 48 B
|
|
|
|
|
|
}</code></pre>
|
|
|
|
|
|
<p style="margin:14px 0 0;color:var(--dim);font-size:13px">
|
|
|
|
|
|
同号多通道取 <b style="color:var(--ink)">seq 最新</b>的一条;写侧 seqlock 奇数(写入中)的槽本拍省略、下拍自愈。
|
|
|
|
|
|
节点集合与各 seq/online 未变时整段复用影子区(CPU 差分),<b style="color:var(--ink)">PRAM 写侧仍按 count 决定字节数</b>。
|
|
|
|
|
|
<b style="color:var(--amber)">1.6.22+ 稀疏写</b>:ESC 中 <code>40+count×56 .. 543</code> 为上次残留,
|
|
|
|
|
|
主站<b style="color:var(--ink)">只许按 count 迭代</b>,不得读空洞区。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card tbl-card">
|
|
|
|
|
|
<div class="card-hd"><h3>单槽字段明细</h3></div>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr><th>字段</th><th class="n">槽内偏移</th><th class="n">宽</th><th>类型</th><th>内容</th></tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody id="slot-tbody"></tbody>
|
|
|
|
|
|
<tfoot>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td colspan="2">单槽合计</td><td class="n">56</td>
|
|
|
|
|
|
<td colspan="2">8 B 头 + 48 B STATUS</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tfoot>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ═══ 成本 ═══ -->
|
|
|
|
|
|
<section id="cost">
|
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
|
<span class="sec-no">4</span>
|
|
|
|
|
|
<h2>每拍成本与 SM 预算</h2>
|
2026-09-13 04:13:28 +08:00
|
|
|
|
<span class="tagline">从站写 ESC 成本 · 稀疏写 1.6.22+(不是主站 rx)</span>
|
2026-09-12 20:10:47 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cost-grid">
|
|
|
|
|
|
<div class="cost-card c-cyan">
|
|
|
|
|
|
<div class="lbl">输入写侧 · 3 电机稀疏</div>
|
2026-09-13 04:13:28 +08:00
|
|
|
|
<div class="val">—</div>
|
|
|
|
|
|
<div class="unit">从站写 ESC · 按实占字节估(≠ 主站 rx)</div>
|
2026-09-12 20:10:47 +08:00
|
|
|
|
<div class="bar"><i style="width:11.6%"></i></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cost-card c-blue">
|
|
|
|
|
|
<div class="lbl">输入写侧 · 整包基线</div>
|
2026-09-13 04:13:28 +08:00
|
|
|
|
<div class="val">—</div>
|
|
|
|
|
|
<div class="unit">从站写 ESC · 600B 整包(≠ 主站 rx)</div>
|
2026-09-12 20:10:47 +08:00
|
|
|
|
<div class="bar"><i style="width:26.5%"></i></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cost-card c-amber">
|
|
|
|
|
|
<div class="lbl">输出读侧 · 探针命中</div>
|
|
|
|
|
|
<div class="val">12</div>
|
|
|
|
|
|
<div class="unit">B 探针 · 无沿时跳过 384B 全量读</div>
|
|
|
|
|
|
<div class="bar"><i style="width:5%"></i></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="callout">
|
|
|
|
|
|
<h4>1.6.22 稀疏写 ESC · 写侧字节随 count 缩</h4>
|
|
|
|
|
|
<p><code>CONFIG_ECAT_SPARSE_ESC_WRITE=1</code>(默认开)。<code>HW_EscWriteIsr</code> 只写两段:</p>
|
|
|
|
|
|
<pre><code>段A = [0 .. 40+count×56) // 段1 桥状态 + 段2 头 + 有效密排槽
|
|
|
|
|
|
段B = [544 .. 600) // 段3 长帧 + 段5 诊断 + 保留
|
|
|
|
|
|
跳过 = [40+count×56 .. 544) // 空槽区(ESC 中为 SM 缓冲上次残留)
|
|
|
|
|
|
|
|
|
|
|
|
count=0 → 96 B ≈ 42 µs
|
|
|
|
|
|
count=3 → 264 B ≈ 116 µs
|
|
|
|
|
|
count=8 → 544 B ≈ 240 µs
|
|
|
|
|
|
count=9 → 整包 600 B(两段相接,回退)</code></pre>
|
|
|
|
|
|
<p style="margin-top:10px;color:var(--dim);font-size:12.5px">
|
|
|
|
|
|
APPL 仍整填 <code>aPdInputData</code>(对齐 SDO 0x6000);省的是 PRAM 搬运时间,不是 RAM 拷贝。
|
|
|
|
|
|
主站必须继续只按 <code>count</code> 迭代——空洞区内容是上次残留,不可读。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="callout warn">
|
|
|
|
|
|
<h4>实测锚点:从站自报负载</h4>
|
|
|
|
|
|
<p><b>1 ms 档 49.2% → 0.5 ms 档 81.4% → 0.25 ms 档 98.7%</b>,同时 λ 从 6000 帧/s 塌到 317 帧/s —— 饱和发生在从站,不在主站的 Npcap 或钉核上。</p>
|
|
|
|
|
|
<p style="color:var(--dim);font-size:12.5px">
|
|
|
|
|
|
上表为 1.6.21 整包基线。1.6.22 稀疏写开启后写侧字节随 count 缩,同工况 load_perm 应显著下降;
|
|
|
|
|
|
验收看 0x2000 sub10(EscWrite 周期)与 load_perm,同工况开关各测一轮。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<div class="card-hd">
|
|
|
|
|
|
<h3>DPRAM 预算 · out + in ≤ 1024 B</h3>
|
|
|
|
|
|
<span class="meta">LAN9252 · 过程区 0x1400–0x1FFF · 3-buffer</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-bd">
|
|
|
|
|
|
<div class="map-wrap" id="dpram-map"></div>
|
|
|
|
|
|
<p style="margin:14px 0 0;color:var(--dim);font-size:12.5px">
|
|
|
|
|
|
邮箱 SM0/SM1 各 256 B 固定占 0x1000–0x13FF;SM2/SM3 均为 3-buffer(control 0x64/0x20,bit1=0),实占 (out+in)×3。
|
|
|
|
|
|
要再大只有两条路:邮箱压小(上限约 1280 B,代价 SDO/FoE 变慢)或改 1-buffer(上限 3072 B,代价失去三缓冲并发保护)。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ═══ 段4 删除 ═══ -->
|
|
|
|
|
|
<section id="seg4">
|
|
|
|
|
|
<div class="sec-head">
|
|
|
|
|
|
<span class="sec-no">5</span>
|
|
|
|
|
|
<h2>段 4 历史覆盖窗口 · 已删除</h2>
|
|
|
|
|
|
<span class="tagline">1.6.15 引入 → 1.6.21 删除</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="callout danger">
|
|
|
|
|
|
<h4>为什么删</h4>
|
|
|
|
|
|
<p>1.6.15 曾把批量接收区做成「定长覆盖窗口」:5 个固定格子(每格 44 B = 12 B 头 + 32 B 数据)+ 写游标绕圈。它是「最近 5 帧」而不是队列 —— 第 6 帧盖掉第 1 帧,未取走的计入 <code>OVF</code>。</p>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li>覆盖窗口反复重发最近几条非 STATUS 帧,属于「历史帧 / 重复帧」,对电机状态视图是噪声;</li>
|
|
|
|
|
|
<li>用户实测病理:接 3 个电机,网页却看到 5 个状态帧 —— 旧段 2 固定 5 槽里空槽 <code>node_id=0</code> 也占位,主站不滤空槽就多报。</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<p style="margin-top:10px">本版语义:<b>每一拍只携带「总线上已识别电机的完整一轮状态」</b>。版本/CONFIG 仍走段 1/段 3「最近一帧」快照。对象字典 sub21 恒 0;<code>596..599</code> 保留空洞。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
布局真源 <code>comm/ethercat_can_bridge.h</code>(1.6.21)· 尺寸 <code>comm/ethercat_pdo_size.h</code> ·
|
|
|
|
|
|
固件版本 <code>config.h APP_FW_VERSION = 1.6.24</code> ·
|
|
|
|
|
|
稀疏写 <code>ethercat_pdo.cpp sparseEscWritePlan</code> + <code>ecatappl.c PDO_InputMapping</code> ·
|
2026-09-13 04:13:28 +08:00
|
|
|
|
成本说明:从站写 ESC 与主站 rx 是两段时间 · 布局魔数 <code>kEcatMotLayoutMagic = 0xA5</code>
|
2026-09-12 20:10:47 +08:00
|
|
|
|
</footer>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
const C = {
|
|
|
|
|
|
blue:'#4c9aff', amber:'#f0b429', cyan:'#00d4a0',
|
|
|
|
|
|
violet:'#a78bfa', rose:'#ff5c7a', slate:'#8b9cb0',
|
|
|
|
|
|
dim:'#6d8299', line:'#1c2834', ink:'#d8e4f0',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 段定义(总览用)── */
|
|
|
|
|
|
const IN_SEGS = [
|
|
|
|
|
|
{name:'段1 桥状态', off:0, len:32, color:C.blue},
|
|
|
|
|
|
{name:'段2 头', off:32, len:8, color:C.amber},
|
|
|
|
|
|
{name:'段2 密排槽', off:40, len:504, color:C.cyan},
|
|
|
|
|
|
{name:'段3 长帧', off:544, len:40, color:C.blue},
|
|
|
|
|
|
{name:'段5 诊断', off:584, len:12, color:C.rose},
|
|
|
|
|
|
{name:'保留', off:596, len:4, color:C.slate},
|
|
|
|
|
|
];
|
|
|
|
|
|
const OUT_SEGS = [
|
|
|
|
|
|
{name:'段1 单帧发送', off:0, len:48, color:C.blue},
|
|
|
|
|
|
{name:'段2 批量发送', off:48, len:336, color:C.violet},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/* ── 字段(按段分组)── */
|
|
|
|
|
|
const IN_GROUPS = [
|
|
|
|
|
|
{title:'段 1 · 桥状态 0..31 · 32 B', total:32, fields:[
|
|
|
|
|
|
{off:0,len:1,name:'ack',color:C.blue,type:'u8',txt:'回显已执行的发送序号',attr:'固定'},
|
|
|
|
|
|
{off:1,len:1,name:'ch',color:C.blue,type:'u8',txt:'该帧来自哪个通道 0/1/2',attr:'固定'},
|
|
|
|
|
|
{off:2,len:1,name:'dlc',color:C.blue,type:'u8',txt:'原始 DLC(截断前)',attr:'固定'},
|
|
|
|
|
|
{off:3,len:1,name:'flags',color:C.blue,type:'u8',txt:'bit0=有过接收;bit1=截断到 8B',attr:'固定'},
|
|
|
|
|
|
{off:4,len:4,name:'id',color:C.blue,type:'u32le',txt:'最近一帧 CAN ID',attr:'固定'},
|
|
|
|
|
|
{off:8,len:8,name:'data',color:C.blue,type:'u8[8]',txt:'短快照(长帧见段 3)',attr:'固定'},
|
|
|
|
|
|
{off:16,len:4,name:'rx_count',color:C.blue,type:'u32le',txt:'累计接收帧数',attr:'固定'},
|
|
|
|
|
|
{off:20,len:1,name:'txres',color:C.blue,type:'u8',txt:'最近发送结果(0=OK)',attr:'固定'},
|
|
|
|
|
|
{off:21,len:1,name:'backlog',color:C.blue,type:'u8',txt:'该通道软件 TX 队列积压',attr:'固定'},
|
|
|
|
|
|
{off:22,len:2,name:'txcnt',color:C.blue,type:'u16le',txt:'累计发送受理帧数',attr:'固定'},
|
|
|
|
|
|
{off:24,len:2,name:'drop',color:C.blue,type:'u16le',txt:'快照「未取先覆」次数',attr:'固定'},
|
|
|
|
|
|
{off:26,len:6,name:'pad',color:C.slate,type:'pad',txt:'段 1 尾部填充',attr:'保留'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
{title:'段 2 · 头 32..39 · 8 B', total:8, fields:[
|
|
|
|
|
|
{off:32,len:1,name:'count',color:C.amber,type:'u8',txt:'本拍密排电机数 0..9',attr:'头部'},
|
|
|
|
|
|
{off:33,len:1,name:'active',color:C.amber,type:'u8',txt:'在线电机数(可与 count 不同)',attr:'头部'},
|
|
|
|
|
|
{off:34,len:1,name:'boot',color:C.amber,type:'u8',txt:'1=近 3s 收到过 0x103 BOOT',attr:'头部'},
|
|
|
|
|
|
{off:35,len:1,name:'magic',color:C.amber,type:'u8',txt:'恒 0xA5:密排标识',attr:'头部'},
|
|
|
|
|
|
{off:36,len:4,name:'gseq',color:C.amber,type:'u32le',txt:'状态表全局刷新序号',attr:'头部'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
{title:'段 2 · 密排槽 40..543 · 最多 9×56 B', total:504, fields:[
|
|
|
|
|
|
{off:40,len:504,name:'slots[9]',color:C.cyan,type:'9×56B',txt:'密排:每槽 node/ch/online/dlc/seq + 48B STATUS;空节点不占位',attr:'密排'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
{title:'段 3 · 长帧快照 544..583 · 40 B', total:40, fields:[
|
|
|
|
|
|
{off:544,len:1,name:'ch',color:C.blue,type:'u8',txt:'通道',attr:'固定'},
|
|
|
|
|
|
{off:545,len:1,name:'dlc',color:C.blue,type:'u8',txt:'原始 DLC(可达 32)',attr:'固定'},
|
|
|
|
|
|
{off:546,len:1,name:'flags',color:C.blue,type:'u8',txt:'bit0=有效',attr:'固定'},
|
|
|
|
|
|
{off:547,len:1,name:'rsvd',color:C.slate,type:'u8',txt:'保留',attr:'保留'},
|
|
|
|
|
|
{off:548,len:4,name:'id',color:C.blue,type:'u32le',txt:'CAN ID',attr:'固定'},
|
|
|
|
|
|
{off:552,len:32,name:'data',color:C.blue,type:'u8[32]',txt:'完整 payload(CONFIG / 版本帧)',attr:'固定'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
{title:'段 5 · 诊断 584..595 · 12 B', total:12, fields:[
|
|
|
|
|
|
{off:584,len:2,name:'load',color:C.rose,type:'u16le',txt:'负载率 0.1%(1000=100%)',attr:'固定'},
|
|
|
|
|
|
{off:586,len:2,name:'torn',color:C.rose,type:'u16le',txt:'CAN 取帧撕裂丢帧数',attr:'固定'},
|
|
|
|
|
|
{off:588,len:2,name:'seq',color:C.rose,type:'u16le',txt:'诊断区序号,1s 窗口 +1',attr:'固定'},
|
|
|
|
|
|
{off:590,len:2,name:'RFO0',color:C.rose,type:'u16le',txt:'CAN RX FIFO 满溢计数',attr:'固定'},
|
|
|
|
|
|
{off:592,len:2,name:'relto',color:C.rose,type:'u16le',txt:'RFD0 释放握手超时计数',attr:'固定'},
|
|
|
|
|
|
{off:594,len:2,name:'reject',color:C.rose,type:'u16le',txt:'内容级拼接帧命中计数',attr:'固定'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
{title:'保留 596..599 · 4 B', total:4, fields:[
|
|
|
|
|
|
{off:596,len:4,name:'—',color:C.slate,type:'pad',txt:'原段 4 空洞',attr:'保留'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const OUT_GROUPS = [
|
|
|
|
|
|
{title:'段 1 · 单帧发送 0..47 · 48 B', total:48, fields:[
|
|
|
|
|
|
{off:0,len:1,name:'seq',color:C.blue,type:'u8',txt:'变化沿触发一次发送',attr:'固定'},
|
|
|
|
|
|
{off:1,len:1,name:'ch',color:C.blue,type:'u8',txt:'目标通道 0/1/2',attr:'固定'},
|
|
|
|
|
|
{off:2,len:1,name:'dlc',color:C.blue,type:'u8',txt:'0..32;0=只发批量',attr:'固定'},
|
|
|
|
|
|
{off:3,len:1,name:'flags',color:C.blue,type:'u8',txt:'bit0=1 请求清零统计',attr:'固定'},
|
|
|
|
|
|
{off:4,len:4,name:'id',color:C.blue,type:'u32le',txt:'CAN ID(29 位扩展帧)',attr:'固定'},
|
|
|
|
|
|
{off:8,len:1,name:'win',color:C.slate,type:'u8',txt:'1.6.21 起忽略',attr:'忽略'},
|
|
|
|
|
|
{off:9,len:7,name:'pad',color:C.slate,type:'pad',txt:'对齐到 16B 边界',attr:'保留'},
|
|
|
|
|
|
{off:16,len:32,name:'data',color:C.blue,type:'u8[32]',txt:'一帧电机 CMD 数据区',attr:'固定'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
{title:'段 2 · 批量发送 48..383 · 336 B', total:336, fields:[
|
|
|
|
|
|
{off:48,len:1,name:'count',color:C.violet,type:'u8',txt:'本次要发的帧数',attr:'头部'},
|
|
|
|
|
|
{off:49,len:1,name:'flags',color:C.violet,type:'u8',txt:'bit0=1 遇错即停',attr:'头部'},
|
|
|
|
|
|
{off:50,len:2,name:'rsvd',color:C.slate,type:'pad',txt:'帧数组 8B 对齐',attr:'保留'},
|
|
|
|
|
|
{off:52,len:332,name:'items[]',color:C.violet,type:'变长',txt:'每项 8B 头 + dlc 数据(CANFD ≤64B/帧)',attr:'批量'},
|
|
|
|
|
|
]},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const SLOT_FIELDS = [
|
|
|
|
|
|
{off:0,len:1,name:'node',color:C.cyan,type:'u8',txt:'节点号 1..9(密排下不会出现 0)',attr:'头部'},
|
|
|
|
|
|
{off:1,len:1,name:'ch',color:C.cyan,type:'u8',txt:'CAN 通道 0/1/2',attr:'头部'},
|
|
|
|
|
|
{off:2,len:1,name:'online',color:C.cyan,type:'u8',txt:'1=在线',attr:'头部'},
|
|
|
|
|
|
{off:3,len:1,name:'dlc',color:C.cyan,type:'u8',txt:'最后一帧 STATUS 的 DLC(正常 48)',attr:'头部'},
|
|
|
|
|
|
{off:4,len:4,name:'seq',color:C.amber,type:'u32le',txt:'STATUS 刷新序号',attr:'头部'},
|
|
|
|
|
|
{off:8,len:48,name:'status',color:C.blue,type:'u8[48]',txt:'原始 STATUS payload',attr:'固定'},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/* ── SVG helpers ── */
|
|
|
|
|
|
function svgEl(tag, attrs, text) {
|
|
|
|
|
|
const e = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
|
|
|
|
|
for (const k in attrs) e.setAttribute(k, attrs[k]);
|
|
|
|
|
|
if (text != null) e.textContent = text;
|
|
|
|
|
|
return e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 段总览:只画大块,标签宽敞 */
|
|
|
|
|
|
function drawOverview(container, segs, total) {
|
|
|
|
|
|
const W = 1000, pad = 6, barY = 28, barH = 56, H = barY + barH + 28;
|
|
|
|
|
|
const inner = W - pad * 2;
|
|
|
|
|
|
const svg = svgEl('svg', { viewBox:`0 0 ${W} ${H}`, role:'img' });
|
|
|
|
|
|
const x0 = b => pad + (b / total) * inner;
|
|
|
|
|
|
const xw = n => (n / total) * inner;
|
|
|
|
|
|
|
|
|
|
|
|
// ruler
|
|
|
|
|
|
const tick = total >= 400 ? 100 : 50;
|
|
|
|
|
|
for (let t = 0; t <= total; t += tick) {
|
|
|
|
|
|
const x = x0(t);
|
|
|
|
|
|
svg.appendChild(svgEl('line', { x1:x,y1:16,x2:x,y2:24, stroke:C.line,'stroke-width':1 }));
|
|
|
|
|
|
svg.appendChild(svgEl('text', {
|
|
|
|
|
|
x, y:12, 'text-anchor': t===0 ? 'start' : t===total ? 'end' : 'middle',
|
|
|
|
|
|
fill:C.dim, 'font-size':10, 'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, String(t)));
|
|
|
|
|
|
}
|
|
|
|
|
|
svg.appendChild(svgEl('line', { x1:pad,y1:24,x2:W-pad,y2:24, stroke:C.line,'stroke-width':1 }));
|
|
|
|
|
|
|
|
|
|
|
|
segs.forEach(s => {
|
|
|
|
|
|
const x = x0(s.off), w = xw(s.len);
|
|
|
|
|
|
const g = svgEl('g');
|
|
|
|
|
|
g.appendChild(svgEl('title', {}, `${s.name} @${s.off} · ${s.len} B`));
|
|
|
|
|
|
g.appendChild(svgEl('rect', {
|
|
|
|
|
|
x, y:barY, width:w, height:barH,
|
|
|
|
|
|
fill:s.color, 'fill-opacity':0.22,
|
|
|
|
|
|
stroke:s.color, 'stroke-width':1.2, rx:4
|
|
|
|
|
|
}));
|
|
|
|
|
|
g.appendChild(svgEl('rect', {
|
|
|
|
|
|
x, y:barY, width:w, height:5, fill:s.color, rx:2
|
|
|
|
|
|
}));
|
|
|
|
|
|
if (w >= 56) {
|
|
|
|
|
|
g.appendChild(svgEl('text', {
|
|
|
|
|
|
x:x+w/2, y:barY+barH/2-4, 'text-anchor':'middle', 'dominant-baseline':'central',
|
|
|
|
|
|
fill:C.ink, 'font-size': w>=120 ? 13 : 11,
|
|
|
|
|
|
'font-family':'Cascadia Code,Consolas,monospace', 'font-weight':650
|
|
|
|
|
|
}, s.name));
|
|
|
|
|
|
g.appendChild(svgEl('text', {
|
|
|
|
|
|
x:x+w/2, y:barY+barH/2+14, 'text-anchor':'middle',
|
|
|
|
|
|
fill:C.dim, 'font-size':10, 'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, `${s.off}..${s.off+s.len-1} · ${s.len}B`));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 窄段:标签放在下方
|
|
|
|
|
|
g.appendChild(svgEl('line', {
|
|
|
|
|
|
x1:x+w/2, y1:barY+barH, x2:x+w/2, y2:barY+barH+8,
|
|
|
|
|
|
stroke:s.color, 'stroke-width':1
|
|
|
|
|
|
}));
|
|
|
|
|
|
g.appendChild(svgEl('text', {
|
|
|
|
|
|
x:x+w/2, y:barY+barH+20, 'text-anchor':'middle',
|
|
|
|
|
|
fill:s.color, 'font-size':10, 'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, `${s.name.replace('段','')}`));
|
|
|
|
|
|
}
|
|
|
|
|
|
svg.appendChild(g);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
container.appendChild(svg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 段内字段条:HTML flex,宽度 ∝ 字节,不叠压 */
|
|
|
|
|
|
function drawFieldBar(host, group) {
|
|
|
|
|
|
const wrap = document.createElement('div');
|
|
|
|
|
|
wrap.style.marginBottom = '22px';
|
|
|
|
|
|
|
|
|
|
|
|
const label = document.createElement('div');
|
|
|
|
|
|
label.className = 'seg-label';
|
|
|
|
|
|
label.textContent = group.title;
|
|
|
|
|
|
wrap.appendChild(label);
|
|
|
|
|
|
|
|
|
|
|
|
const bar = document.createElement('div');
|
|
|
|
|
|
bar.className = 'fbar';
|
|
|
|
|
|
|
|
|
|
|
|
group.fields.forEach(f => {
|
|
|
|
|
|
const cell = document.createElement('div');
|
|
|
|
|
|
cell.className = 'fcell' + (f.len <= 2 ? ' narrow' : '') + (f.attr === '保留' || f.name === 'pad' || f.name === '—' || f.name === 'rsvd' ? ' pad' : '');
|
|
|
|
|
|
cell.style.flex = `${f.len} 1 0`;
|
|
|
|
|
|
cell.style.background = f.color;
|
|
|
|
|
|
cell.style.opacity = f.attr === '保留' || f.name === 'pad' || f.name === '—' || f.name === 'rsvd' ? '0.35' : '0.85';
|
|
|
|
|
|
cell.title = `${f.name} @${f.off} · ${f.len} B · ${f.type}\n${f.txt}`;
|
|
|
|
|
|
|
|
|
|
|
|
const fn = document.createElement('div');
|
|
|
|
|
|
fn.className = 'fn';
|
|
|
|
|
|
fn.textContent = f.name;
|
|
|
|
|
|
cell.appendChild(fn);
|
|
|
|
|
|
|
|
|
|
|
|
if (f.len >= 2 || group.total <= 16) {
|
|
|
|
|
|
const fo = document.createElement('div');
|
|
|
|
|
|
fo.className = 'fo';
|
|
|
|
|
|
fo.textContent = `@${f.off}`;
|
|
|
|
|
|
cell.appendChild(fo);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bar.appendChild(cell);
|
|
|
|
|
|
});
|
|
|
|
|
|
wrap.appendChild(bar);
|
|
|
|
|
|
|
|
|
|
|
|
// 底部偏移尺
|
|
|
|
|
|
const ruler = document.createElement('div');
|
|
|
|
|
|
ruler.className = 'ruler';
|
|
|
|
|
|
group.fields.forEach(f => {
|
|
|
|
|
|
const sp = document.createElement('span');
|
|
|
|
|
|
sp.style.flex = `${f.len} 1 0`;
|
|
|
|
|
|
sp.textContent = f.len >= 4 ? String(f.off) : '';
|
|
|
|
|
|
ruler.appendChild(sp);
|
|
|
|
|
|
});
|
|
|
|
|
|
wrap.appendChild(ruler);
|
|
|
|
|
|
|
|
|
|
|
|
host.appendChild(wrap);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function drawFieldGroups(hostId, groups) {
|
|
|
|
|
|
const host = document.getElementById(hostId);
|
|
|
|
|
|
host.innerHTML = '';
|
|
|
|
|
|
groups.forEach(g => drawFieldBar(host, g));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 槽区示意 */
|
|
|
|
|
|
function drawSlotStrip(container) {
|
|
|
|
|
|
const W = 1000, H = 70;
|
|
|
|
|
|
const svg = svgEl('svg', { viewBox:`0 0 ${W} ${H}`, role:'img' });
|
|
|
|
|
|
const pad = 4, gap = 6;
|
|
|
|
|
|
const n = 9;
|
|
|
|
|
|
const totalGaps = gap * (n - 1);
|
|
|
|
|
|
const slotW = (W - pad * 2 - totalGaps) / n;
|
|
|
|
|
|
|
|
|
|
|
|
svg.appendChild(svgEl('text', {
|
|
|
|
|
|
x:pad, y:14, fill:C.dim, 'font-size':11,
|
|
|
|
|
|
'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, '9 个等宽槽 · 每槽 56 B · 密排后有效槽 = count · 空节点不占位'));
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
|
|
|
|
const x = pad + i * (slotW + gap);
|
|
|
|
|
|
const g = svgEl('g');
|
|
|
|
|
|
g.appendChild(svgEl('title', {}, `slot[${i}] · 56 B`));
|
|
|
|
|
|
g.appendChild(svgEl('rect', {
|
|
|
|
|
|
x, y:26, width:slotW, height:34,
|
|
|
|
|
|
fill:C.cyan, 'fill-opacity': i===0 ? 0.4 : 0.14,
|
|
|
|
|
|
stroke:C.cyan, 'stroke-width': i===0 ? 1.6 : 0.8,
|
|
|
|
|
|
rx:4
|
|
|
|
|
|
}));
|
|
|
|
|
|
g.appendChild(svgEl('text', {
|
|
|
|
|
|
x:x+slotW/2, y:43, 'text-anchor':'middle', 'dominant-baseline':'central',
|
|
|
|
|
|
fill: i===0 ? C.ink : C.dim, 'font-size':12,
|
|
|
|
|
|
'font-family':'Cascadia Code,Consolas,monospace', 'font-weight': i===0 ? 650 : 400
|
|
|
|
|
|
}, i===0 ? 'slot 0' : `slot ${i}`));
|
|
|
|
|
|
svg.appendChild(g);
|
|
|
|
|
|
}
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
container.appendChild(svg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* DPRAM 预算 */
|
|
|
|
|
|
function drawDpram(container) {
|
|
|
|
|
|
const W = 1000, H = 100;
|
|
|
|
|
|
const svg = svgEl('svg', { viewBox:`0 0 ${W} ${H}`, role:'img' });
|
|
|
|
|
|
const pad = 10, inner = W - pad * 2, total = 1024;
|
|
|
|
|
|
const y = 30, h = 36;
|
|
|
|
|
|
const x0 = b => pad + (b / total) * inner;
|
|
|
|
|
|
const xw = n => (n / total) * inner;
|
|
|
|
|
|
|
|
|
|
|
|
svg.appendChild(svgEl('text', {
|
|
|
|
|
|
x:pad, y:16, fill:C.dim, 'font-size':11,
|
|
|
|
|
|
'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, '逻辑预算 (out+in) ≤ 1024 B · 3-buffer 等效 · 1.6.22+ 写侧按 count 稀疏'));
|
|
|
|
|
|
|
|
|
|
|
|
function block(from, len, color, label, opacity) {
|
|
|
|
|
|
const g = svgEl('g');
|
|
|
|
|
|
g.appendChild(svgEl('title', {}, `${label} · ${len} B`));
|
|
|
|
|
|
g.appendChild(svgEl('rect', {
|
|
|
|
|
|
x:x0(from), y, width:xw(len), height:h,
|
|
|
|
|
|
fill:color, 'fill-opacity':opacity, stroke:color, 'stroke-width':1.2, rx:4
|
|
|
|
|
|
}));
|
|
|
|
|
|
if (xw(len) > 48) {
|
|
|
|
|
|
g.appendChild(svgEl('text', {
|
|
|
|
|
|
x:x0(from)+xw(len)/2, y:y+h/2,
|
|
|
|
|
|
'text-anchor':'middle', 'dominant-baseline':'central',
|
|
|
|
|
|
fill:C.ink, 'font-size':12, 'font-family':'Cascadia Code,Consolas,monospace', 'font-weight':650
|
|
|
|
|
|
}, label));
|
|
|
|
|
|
}
|
|
|
|
|
|
svg.appendChild(g);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
block(0, 384, C.blue, 'OUT 384', 0.28);
|
|
|
|
|
|
block(384, 600, C.cyan, 'IN 600', 0.28);
|
|
|
|
|
|
block(984, 40, C.slate, '', 0.22);
|
|
|
|
|
|
|
|
|
|
|
|
// 余量标注放在下方,避免与 984/1024 刻度挤在一起
|
|
|
|
|
|
svg.appendChild(svgEl('text', {
|
|
|
|
|
|
x:x0(1004), y:y+h+18, 'text-anchor':'middle',
|
|
|
|
|
|
fill:C.slate, 'font-size':10, 'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, '余 40'));
|
|
|
|
|
|
|
|
|
|
|
|
[0, 384, 984, 1024].forEach((t, i) => {
|
|
|
|
|
|
const x = x0(t);
|
|
|
|
|
|
svg.appendChild(svgEl('line', { x1:x,y1:y,x2:x,y2:y-6, stroke:C.line,'stroke-width':1 }));
|
|
|
|
|
|
svg.appendChild(svgEl('text', {
|
|
|
|
|
|
x, y:y-10,
|
|
|
|
|
|
'text-anchor': i===0 ? 'start' : i===3 ? 'end' : 'middle',
|
|
|
|
|
|
fill:C.dim, 'font-size':10, 'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, String(t)));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
svg.appendChild(svgEl('text', {
|
|
|
|
|
|
x:pad, y:H-8, fill:C.dim, 'font-size':11,
|
|
|
|
|
|
'font-family':'Cascadia Code,Consolas,monospace'
|
|
|
|
|
|
}, 'SM2 0x1400–0x187F (3×384) · SM3 0x1880–0x1F67 (3×600) · DPRAM 侧余 120 B'));
|
|
|
|
|
|
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
container.appendChild(svg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 表格 */
|
|
|
|
|
|
function attrChip(a) {
|
|
|
|
|
|
const map = {固定:'fix',密排:'adp',头部:'hdr',保留:'note',忽略:'gone',批量:'adp'};
|
|
|
|
|
|
return `<span class="chip ${map[a]||'note'}">${a}</span>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fillTable(tbody, groups, segOf) {
|
|
|
|
|
|
const rows = [];
|
|
|
|
|
|
groups.forEach(g => {
|
|
|
|
|
|
g.fields.forEach(f => {
|
|
|
|
|
|
rows.push(`
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<span class="dot" style="background:${f.color}"></span>
|
|
|
|
|
|
<code>${f.name}</code>
|
|
|
|
|
|
<span style="color:var(--dim);font-size:12px;margin-left:6px">${segOf(g)}</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="n">${f.off}</td>
|
|
|
|
|
|
<td class="n">${f.len}</td>
|
|
|
|
|
|
<td class="mono">${f.type}</td>
|
|
|
|
|
|
<td>${f.txt}</td>
|
|
|
|
|
|
<td>${attrChip(f.attr)}</td>
|
|
|
|
|
|
</tr>`);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
tbody.innerHTML = rows.join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fillFlatTable(tbody, fields) {
|
|
|
|
|
|
tbody.innerHTML = fields.map(f => `
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td><span class="dot" style="background:${f.color}"></span><code>${f.name}</code></td>
|
|
|
|
|
|
<td class="n">+${f.off}</td>
|
|
|
|
|
|
<td class="n">${f.len}</td>
|
|
|
|
|
|
<td class="mono">${f.type}</td>
|
|
|
|
|
|
<td>${f.txt}</td>
|
|
|
|
|
|
</tr>`).join('');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* boot */
|
|
|
|
|
|
drawOverview(document.getElementById('in-overview'), IN_SEGS, 600);
|
|
|
|
|
|
drawOverview(document.getElementById('out-overview'), OUT_SEGS, 384);
|
|
|
|
|
|
drawFieldGroups('in-fields', IN_GROUPS);
|
|
|
|
|
|
drawFieldGroups('out-fields', OUT_GROUPS);
|
|
|
|
|
|
drawSlotStrip(document.getElementById('slot-strip'));
|
|
|
|
|
|
|
|
|
|
|
|
const slotHost = document.getElementById('slot-fields');
|
|
|
|
|
|
[{title:'单槽 56 B', total:56, fields:SLOT_FIELDS}].forEach(g => drawFieldBar(slotHost, g));
|
|
|
|
|
|
drawDpram(document.getElementById('dpram-map'));
|
|
|
|
|
|
|
|
|
|
|
|
fillTable(document.getElementById('in-tbody'), IN_GROUPS, g => g.title.split('·')[0].trim());
|
|
|
|
|
|
fillTable(document.getElementById('out-tbody'), OUT_GROUPS, g => g.title.split('·')[0].trim());
|
|
|
|
|
|
fillFlatTable(document.getElementById('slot-tbody'), SLOT_FIELDS);
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|