app
Base Scaffold Control Panel
Public Made by Adomby adom
The Base Scaffold System's control panel: electrical specifications and the connect/disconnect interface between a base scaffold and the workcell ecosystem.
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Base Scaffold Control Panel</title>
<style>
@font-face { font-family: 'Satoshi'; src: url('https://wiki.adom.inc/static/fonts/satoshi-400.woff2') format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Satoshi'; src: url('https://wiki.adom.inc/static/fonts/satoshi-500.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Familjen Grotesk'; src: url('https://wiki.adom.inc/static/fonts/familjen-grotesk-500.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Familjen Grotesk'; src: url('https://wiki.adom.inc/static/fonts/familjen-grotesk-700.woff2') format('woff2'); font-weight: 700; font-display: swap; }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { scrollbar-width: none; }
html::-webkit-scrollbar, body::-webkit-scrollbar { display: none; }
body {
background: #0d1117; color: #c9d1d9;
font-family: 'Satoshi', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
font-size: 16px; line-height: 1.6;
}
.ds-head { display: flex; align-items: baseline; gap: 12px; border-bottom: 1px solid #21262d; margin: 32px 0 16px; }
.ds-head h2 { flex: 1; border-bottom: none; margin: 0; padding-bottom: 8px; }
.ds-head a, .ds-download { font-size: 13px; color: #2f81f7; text-decoration: none; }
.ds-frame { height: 720px; overflow-y: auto; border: 1px solid #30363d; border-radius: 8px; background: #1b1f27; padding: 12px; }
.ds-frame img { display: block; width: 100%; height: auto; margin: 0 auto 12px; border-radius: 2px; box-shadow: 0 1px 6px rgba(0,0,0,.4); }
.ds-download { display: inline-block; margin-top: 10px; }
.wrap { max-width: 980px; margin: 0 auto; padding: 8px 16px 48px; }
h1, h2, h3 { font-family: 'Familjen Grotesk', 'Satoshi', -apple-system, 'Segoe UI', sans-serif; }
h1 { font-size: 2em; font-weight: 700; color: #e6edf3; padding-bottom: .3em; border-bottom: 1px solid #21262d; margin: 8px 0 16px; }
h2 { font-size: 1.5em; font-weight: 700; color: #e6edf3; padding-bottom: .3em; border-bottom: 1px solid #21262d; margin: 28px 0 16px; }
h3 { font-size: 1.25em; font-weight: 500; color: #e6edf3; margin: 24px 0 12px; }
p { margin: 0 0 16px; }
a { color: #00b8b0; text-decoration: none; }
a:hover { text-decoration: underline; }
strong { color: #e6edf3; }
ul { margin: 0 0 16px 2em; }
li { margin: 4px 0; }
table { border-collapse: collapse; margin: 0 0 16px; display: block; overflow-x: auto; max-width: 100%; }
th, td { border: 1px solid #30363d; padding: 6px 13px; text-align: left; vertical-align: top; }
th { background: #161b22; color: #e6edf3; font-weight: 600; }
tr:nth-child(2n) td { background: rgba(110, 118, 129, 0.06); }
img { max-width: 100%; border-radius: 6px; display: block; margin: 0 0 16px; }
.viewer-slot {
position: relative; height: 460px; margin: 0 0 16px;
border-radius: 8px; overflow: hidden; background: #0d1117;
border: 1px solid #21262d;
}
.viewer-slot .vframe { position: absolute; inset: 0; width: 100%; height: 100%; border: none; }
.viewer-slot .fallback { position: absolute; inset: 0; object-fit: contain; width: 100%; height: 100%; margin: 0; }
.viewer-slot .vctl {
position: absolute; left: 12px; bottom: 12px; z-index: 10; display: none; gap: 6px;
}
.viewer-slot .vctl button {
background: rgba(22, 27, 34, 0.85); border: 1px solid rgba(240, 246, 252, 0.12);
color: #8b949e; font-family: inherit; font-size: 12px; line-height: 1;
padding: 7px 11px; border-radius: 14px; cursor: pointer;
}
.viewer-slot .vctl button:hover { color: #c9d1d9; border-color: rgba(240, 246, 252, 0.25); }
.viewer-slot .vctl button.on { color: #e6edf3; border-color: #2f81f7; background: rgba(31, 111, 235, 0.18); }
.viewer-note { font-size: 12.5px; color: #8b949e; margin: -8px 0 20px; }
</style>
</head>
<body>
<div class="wrap">
<h1>Base Scaffold Control Panel</h1>
<p>The control panel is the electrical front door of the Base Scaffold System: it carries the scaffold's power and signal interface and provides the connect/disconnect that mates the scaffold to a workcell each time it docks.</p>
<p>It is a two-board pair that mate at a blade dock. One half stays with the workcell, the other rides on the base scaffold with the project.</p>
<p><strong>Current revision:</strong> Control Panel: Workcell V2.1.0 and Control Panel: Scaffold V2.1.0. Every function on this page was exercised on the bench in the V2.1.0 test campaign; measured values are quoted where they matter.</p>
<p><strong>Datasheet:</strong> Control Panel: Scaffold V2.1.0 — <a href="https://wiki.adom.inc/adom/base-scaffold-control-panel/files/docs/Control_Panel_Scaffold_Datasheet_V2.1.0.pdf" target="_blank" rel="noopener">PDF</a> · <a href="https://wiki.adom.inc/adom/base-scaffold-control-panel/files/docs/Control_Panel_Scaffold_Datasheet_V2.1.0.md" target="_blank" rel="noopener">Markdown</a></p>
<h2>The Pair</h2>
<table>
<tr><th>Half</th><th>Role</th></tr>
<tr><td><strong>Workcell side</strong> (system)</td><td>Power-distribution hub and workcell controller. Bench supplies land here, every rail is metered, and power + debug are delivered across the dock.</td></tr>
<tr><td><strong>Scaffold side</strong> (user)</td><td>Power and I/O dock board. Lands the supply rails, USB and user I/O on the scaffold's machine-contact field, where the project is built.</td></tr>
</table>
<h3>Workcell side</h3>
<div class="viewer-slot">
<iframe class="vframe" srcdoc="<!doctype html><html><head><meta charset="utf-8">
<link rel="stylesheet" href="https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/style.css">
<script type="module" src="https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260717e"></script>
<style>
* { margin:0; padding:0; box-sizing:border-box }
html,body { width:100%; height:100%; overflow:hidden; background:#0d1117 }
#wrap { position:absolute; inset:0 }
#fb { position:absolute; inset:0; width:100%; height:100%; object-fit:contain }
#ctl { position:absolute; left:12px; bottom:12px; z-index:10; display:none; gap:6px; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif }
#ctl button { background:rgba(22,27,34,.85); border:1px solid rgba(240,246,252,.12); color:#8b949e; font-size:12px; line-height:1; padding:7px 11px; border-radius:14px; cursor:pointer }
#ctl button.on { color:#e6edf3; border-color:#2f81f7; background:rgba(31,111,235,.18) }
</style></head><body>
<img id="fb" src="https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-workcell.png" alt="board render">
<div id="wrap"></div><div id="bar" style="position:absolute;top:0;left:0;right:0;padding:12px 16px;background:linear-gradient(180deg,rgba(13,17,23,.85) 0%,rgba(13,17,23,0) 100%);z-index:10;pointer-events:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif"><div style="font-size:16px;font-weight:600;color:#e6edf3;line-height:1.3">Control Panel: Workcell</div><div style="font-size:12px;color:#8b949e;margin-top:4px">Adom &middot; Base Scaffold Control Panel</div></div>
<div id="ctl"><button id="g" type="button">Ground</button><button id="l" type="button">Bottom light</button></div>
<script type="module">
const V = window.Adom3DViewerBabylon9;
const wrap = document.getElementById('wrap');
if (typeof V === 'undefined') { wrap.remove(); }
else {
try {
const viewer = V.init(wrap, { zUp: true, showViewCube: true, showGround: true });
viewer.loadModel('https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/models/workcell-q.glb?v=v210a').then(() => {
viewer.frameModel();
try { if (viewer.toggleAxes) viewer.toggleAxes('world', false); } catch (e) {}
document.getElementById('fb').remove();
const ctl = document.getElementById('ctl');
const g = document.getElementById('g'), l = document.getElementById('l');
let groundOn = true;
const paint = () => { g.classList.toggle('on', groundOn); l.classList.toggle('on', viewer.getBottomLightState()); };
g.onclick = () => { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); };
l.onclick = () => { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); };
paint(); ctl.style.display = 'flex';
// ---- block hover descriptions (workcell) ----
try {
const scene = viewer.getScene();
const tip = document.createElement('div');
tip.style.cssText = 'position:absolute;display:none;z-index:20;pointer-events:none;' +
'background:rgba(22,27,34,.96);border:1px solid rgba(240,246,252,.15);border-radius:8px;' +
'padding:8px 11px;max-width:300px;color:#e6edf3;font-size:12.5px;line-height:1.45;' +
"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif";
const tipT = document.createElement('div'); tipT.style.fontWeight = '600';
const tipB = document.createElement('div'); tipB.style.color = '#9aa4b2'; tipB.style.marginTop = '2px';
tip.appendChild(tipT); tip.appendChild(tipB);
document.body.appendChild(tip);
let tipsOn = true;
const dBtn = document.createElement('button');
dBtn.type = 'button'; dBtn.textContent = 'Descriptions'; dBtn.className = 'on';
ctl.appendChild(dBtn);
dBtn.onclick = () => { tipsOn = !tipsOn; dBtn.classList.toggle('on', tipsOn); if (!tipsOn) tip.style.display = 'none'; };
const HDR = {
pwr: 'Supply inputs (PS1–PS4)',
meter: 'Rail metering',
usb: 'USB ports',
wifi: 'M.2 Wi-Fi slot',
cm5: 'Workcell controller',
ring: 'ADOM LED ring',
sens: 'Sensing',
probe: 'Debug probe',
dock: 'Blade dock',
led: 'Status LEDs',
jmp: 'Configuration jumpers',
board: 'Control Panel: Workcell V2.1.0',
};
const T = {
pwr: [HDR.pwr, 'Bench supplies land on push-in terminals: PS1 0–60 V / 12 A (variable), PS2 5 V / 10 A (switched), PS3 spare user rail (0–24 V), PS4 5 V board supply.'],
meter: [HDR.meter, '20-bit INA228 monitor — bus voltage, current, power and temperature, live in the Control Panel Dashboard. Bench accuracy within 0.04 % across 5–60 V.'],
ps2sw: [HDR.pwr, 'PS2 rail switch — soft start (≈ 0.4 ms rise) on the way to the scaffold and the USB ports. Switching PS2 off also drops USB VBUS.'],
iso: [HDR.pwr, 'Isolated command path for the PS2 switch, so each user rail’s return can float up to ±2.5 V.'],
usba: [HDR.usb, 'USB-A 3.0 SuperSpeed host, 5 V VBUS from PS2 at 1.5 A behind a latching eFuse. Its USB 2.0 lanes are shared with Bluetooth — mutually exclusive.'],
usbc: [HDR.usb, 'USB-C 3.0 SuperSpeed host, 5 V VBUS from PS2 at 3.0 A behind a latching eFuse.'],
mux: [HDR.usb, 'High-speed switch — routes the shared USB 2.0 lane to Bluetooth or the USB-A port.'],
efuse: [HDR.usb, 'Latching eFuse on a USB VBUS path — trips on overload and stays off until reset.'],
cc: [HDR.usb, 'USB Type-C attach and orientation controller.'],
esd: [HDR.usb, 'USB ESD protection.'],
wifi: [HDR.wifi, 'M.2 Key-E slot for the AX210-class module: Wi-Fi 6E (2.4 / 5 / 6 GHz, 160 MHz, 2×2 MIMO) + Bluetooth 5.3. Wi-Fi on PCIe, Bluetooth on USB.'],
cm5: [HDR.cm5, 'Raspberry Pi CM5 (system side) — runs metering, switching and the Control Panel Dashboard. No user wiring lands here.'],
sd: [HDR.cm5, 'microSD — controller storage.'],
net: [HDR.cm5, 'Workcell network uplink (system side).'],
sw: [HDR.cm5, 'Power button for the workcell controller.'],
ring: [HDR.ring, '18 blue LEDs around the logo — a visual reference for vision-system checks. The chase animation mirrors the real behavior.'],
bme: [HDR.sens, 'BME690 environmental sensor slot — temperature −40..+85 °C, 0–100 %RH, 300–1100 hPa, gas/IAQ.'],
bmi: [HDR.sens, 'BMI270 inertial sensor slot — accel ±2–16 g, gyro ±125–2000 °/s.'],
mic: [HDR.sens, 'MEMS microphone module — digital audio capture for the dashboard.'],
sj: [HDR.sens, 'Sensor jumper — drops in to connect a sensor slot.'],
probe: [HDR.probe, 'Onboard debug probe — flashes, halts and resets the scaffold’s user MCU over SWD (verified to 31 MHz), plus a UART console.'],
dock: [HDR.dock, 'Delivers PS1–PS3 + returns, 5 V and 3.3 V logic, SWD/UART debug, MCU boot/reset and the ID bus across the blade dock to the scaffold.'],
ledR: [HDR.led, 'Red status indicator.'],
ledG: [HDR.led, 'Green status indicator.'],
jmp: [HDR.jmp, 'Configuration jumper.'],
mcL: [HDR.dock, 'Large machine contact — ~20.8 A (80% derated), mates 14 AWG solid core.'],
mc: [HDR.board, 'Machine contact — medium ~6.4 A, solderless press-fit.'],
ghost: ['Control Panel: Scaffold (ghost)', 'The scaffold half shown mated below — user power + I/O dock.'],
board: [HDR.board, 'CM5 carrier and smart power-distribution hub — the system half of the control panel.'],
};
const NAME_RULES = [
[/^GHOST/, 'ghost'],
[/^BME690/, 'bme'], [/^BMI270/, 'bmi'],
[/^J1$/, 'dock'],
[/^N2$/, 'mic'], [/^N1$/, 'probe'],
[/^SJ\d/, 'sj'],
[/^(D2|D23)_(glow|cone)$/, 'ledR'], [/^LED(2|22|23)$/, 'ledR'],
[/^(D3|D4)_(glow|cone)$/, 'ledG'], [/^LED(1|3)$/, 'ledG'],
[/^D\d+_(glow|cone)$/, 'ring'], [/^LED\d+$/, 'ring'],
[/^(U1|U3|U5|U7)$/, 'meter'],
[/^U4$/, 'ps2sw'], [/^U2$/, 'iso'],
[/^(U13|U19)$/, 'mux'], [/^(U17|U23)$/, 'efuse'], [/^U18$/, 'cc'],
[/^(U8|U9|U11|U12|U20|U21|U22|U24|U25)$/, 'esd'],
[/^J10$/, 'usba'], [/^J9$/, 'usbc'], [/^J8$/, 'wifi'],
[/^(J5|J7)$/, 'cm5'], [/^J6$/, 'sd'], [/^J4$/, 'net'],
[/^J2$/, 'pwr'],
[/^SW1$/, 'sw'],
[/^(JP\d|JMP|Adom_Jumper|GNDSPACER)/, 'jmp'],
[/^MpinContact_Large/, 'mcL'],
[/^(WIRE_MC|MC\d|MpinContact)/, 'mc'],
[/^(ETT|ETS)/, 'dock'],
];
const REGIONS = [
[0.161, 0.184, -0.101, -0.092, 'wifi'],
[0.141, 0.155, -0.122, -0.115, 'usba'],
[0.162, 0.173, -0.122, -0.116, 'usbc'],
[0.114, 0.153, -0.094, -0.070, 'cm5'],
[0.063, 0.078, -0.1005, -0.086, 'ring'],
[0.118, 0.133, -0.123, -0.108, 'mic'],
[0.106, 0.117, -0.123, -0.111, 'bme'],
[0.093, 0.105, -0.123, -0.111, 'bmi'],
[0.079, 0.108, -0.107, -0.086, 'probe'],
[0.028, 0.080, -0.135, -0.109, 'dock'],
[0.018, 0.049, -0.090, -0.074, 'meter'],
[0.027, 0.070, -0.069, -0.049, 'pwr'],
[0.094, 0.111, -0.074, -0.052, 'net'],
];
const inv = new window.BABYLON.Matrix();
const root = scene.transformNodes.concat(scene.meshes).find(n => (n.name||'').indexOf('control-panel_workcell') === 0);
function describe(pick) {
let n = pick.pickedMesh; const names = [];
while (n) { names.push(n.name || ''); n = n.parent; }
for (const [re, key] of NAME_RULES) {
if (names.some(nm => re.test(nm))) return T[key];
}
if (root && pick.pickedPoint) {
root.getWorldMatrix().invertToRef(inv);
const p = window.BABYLON.Vector3.TransformCoordinates(pick.pickedPoint, inv);
for (const [x0, x1, y0, y1, key] of REGIONS) {
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1) return T[key];
}
return T.board;
}
return null;
}
let raf = 0;
wrap.addEventListener('pointermove', (ev) => {
if (!tipsOn || raf) return;
raf = requestAnimationFrame(() => {
raf = 0;
if (!tipsOn) return;
const pick = scene.pick(scene.pointerX, scene.pointerY, m => m.isEnabled() && (m.name||'') !== 'ground');
const d = pick && pick.hit ? describe(pick) : null;
if (d) {
tipT.textContent = d[0]; tipB.textContent = d[1];
tip.style.display = 'block';
const r = wrap.getBoundingClientRect();
tip.style.left = Math.min(ev.clientX - r.left + 14, r.width - 310) + 'px';
tip.style.top = Math.max(ev.clientY - r.top - 10, 46) + 'px';
} else tip.style.display = 'none';
});
});
wrap.addEventListener('pointerleave', () => { tip.style.display = 'none'; });
} catch (e) {}
addEventListener('message', (e) => {
const d = e.data || {};
if (d.type !== 'cp-cam' || (d.board && d.board !== 'workcell')) return;
try {
const c = viewer.getCamera();
if (Array.isArray(d.target)) c.setTarget(new window.BABYLON.Vector3(...d.target));
if (typeof d.alpha === 'number') c.alpha = d.alpha;
if (typeof d.beta === 'number') c.beta = d.beta;
if (typeof d.radius === 'number') c.radius = d.radius;
} catch (err) {}
});
}).catch(() => wrap.remove());
} catch (e) { wrap.remove(); }
}
</script></body></html>" loading="lazy" title="3D viewer"></iframe>
</div>
<p class="viewer-note">Drag to orbit · wheel to zoom · right-drag to pan · the cube snaps the view.</p>
<h3>Scaffold side</h3>
<div class="viewer-slot">
<iframe class="vframe" srcdoc="<!doctype html><html><head><meta charset="utf-8">
<link rel="stylesheet" href="https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/style.css">
<script type="module" src="https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260717e"></script>
<style>
* { margin:0; padding:0; box-sizing:border-box }
html,body { width:100%; height:100%; overflow:hidden; background:#0d1117 }
#wrap { position:absolute; inset:0 }
#fb { position:absolute; inset:0; width:100%; height:100%; object-fit:contain }
#ctl { position:absolute; left:12px; bottom:12px; z-index:10; display:none; gap:6px; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif }
#ctl button { background:rgba(22,27,34,.85); border:1px solid rgba(240,246,252,.12); color:#8b949e; font-size:12px; line-height:1; padding:7px 11px; border-radius:14px; cursor:pointer }
#ctl button.on { color:#e6edf3; border-color:#2f81f7; background:rgba(31,111,235,.18) }
</style></head><body>
<img id="fb" src="https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-scaffold.png" alt="board render">
<div id="wrap"></div><div id="bar" style="position:absolute;top:0;left:0;right:0;padding:12px 16px;background:linear-gradient(180deg,rgba(13,17,23,.85) 0%,rgba(13,17,23,0) 100%);z-index:10;pointer-events:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif"><div style="font-size:16px;font-weight:600;color:#e6edf3;line-height:1.3">Control Panel: Scaffold</div><div style="font-size:12px;color:#8b949e;margin-top:4px">Adom &middot; Base Scaffold Control Panel</div></div>
<div id="ctl"><button id="g" type="button">Ground</button><button id="l" type="button">Bottom light</button></div>
<script type="module">
const V = window.Adom3DViewerBabylon9;
const wrap = document.getElementById('wrap');
if (typeof V === 'undefined') { wrap.remove(); }
else {
try {
const viewer = V.init(wrap, { zUp: true, showViewCube: true, showGround: true });
viewer.loadModel('https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/models/scaffold-q.glb?v=v210a').then(() => {
viewer.frameModel();
try { if (viewer.toggleAxes) viewer.toggleAxes('world', false); } catch (e) {}
document.getElementById('fb').remove();
const ctl = document.getElementById('ctl');
const g = document.getElementById('g'), l = document.getElementById('l');
let groundOn = true;
const paint = () => { g.classList.toggle('on', groundOn); l.classList.toggle('on', viewer.getBottomLightState()); };
g.onclick = () => { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); };
l.onclick = () => { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); };
paint(); ctl.style.display = 'flex'; // ---- block hover descriptions (scaffold) ----
try {
const scene = viewer.getScene();
const tip = document.createElement('div');
tip.style.cssText = 'position:absolute;display:none;z-index:20;pointer-events:none;' +
'background:rgba(22,27,34,.96);border:1px solid rgba(240,246,252,.15);border-radius:8px;' +
'padding:8px 11px;max-width:300px;color:#e6edf3;font-size:12.5px;line-height:1.45;' +
"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif";
const tipT = document.createElement('div'); tipT.style.fontWeight = '600';
const tipB = document.createElement('div'); tipB.style.color = '#9aa4b2'; tipB.style.marginTop = '2px';
tip.appendChild(tipT); tip.appendChild(tipB);
document.body.appendChild(tip);
let tipsOn = true;
const dBtn = document.createElement('button');
dBtn.type = 'button'; dBtn.textContent = 'Descriptions'; dBtn.className = 'on';
ctl.appendChild(dBtn);
dBtn.onclick = () => { tipsOn = !tipsOn; dBtn.classList.toggle('on', tipsOn); if (!tipsOn) tip.style.display = 'none'; };
const HDR = {
mini: 'RP2350A Mini (user MCU)',
usbc: 'USB-C',
usb: 'USB VBUS power path',
id: 'ID EEPROM',
pwr: 'Power contacts (MC1–MC12, MC20–MC28)',
io: 'USER_IO contact field (MC29–MC54)',
usbmc: 'USB contacts (MC17–MC19)',
led: 'Power LED',
jmp: 'Rail ground bonds',
dock: 'Blade dock',
board: 'Control Panel: Scaffold V2.1.0',
};
const T = {
mini: [HDR.mini, 'RP2350A: dual Cortex-M33 / RISC-V @ 150 MHz, 520 KB SRAM, PIO, 2 MB flash. 26 USER_IO (PWM, PIO, 4× ADC), USB 2.0 full-speed dual-role. Programmed, reset and booted from the workcell over the dock.'],
miniSW: [HDR.mini, 'Boot select button on the RP2350A Mini.'],
miniD1: [HDR.mini, 'Red power indicator on the RP2350A Mini.'],
miniD2: [HDR.mini, 'Blue user status LED on the RP2350A Mini.'],
usbc: [HDR.usbc, 'USB 2.0 dual-role (host or device). In host role it sources 5 V VBUS from PS2 at 3.0 A; unavailable while PS2 floats.'],
efuse: [HDR.usb, 'Latching eFuse on VBUS — 3.0 A continuous, trips at 3.3 A (bench) and stays off until reset; clamps overvoltage at 5.7 V.'],
cc: [HDR.usb, 'Type-C controller — attach, orientation and host/device role detect.'],
esd: [HDR.usb, 'USB data-line ESD protection.'],
q1: [HDR.usb, 'Back-to-back VBUS blocking switch — in device role nothing back-feeds into PS2 (0 V measured at the PS2 contacts).'],
id: [HDR.id, 'Identification memory with a factory 128-bit serial number, read by the workcell over the dock — tells the dashboard which scaffold is docked.'],
bulk: [HDR.usb, 'Bulk capacitance on the switched 5 V VBUS rail.'],
ps1: [HDR.pwr, 'PS1 — variable rail, 0–60 V / 12 A: + on MC1–MC4, isolated return on MC20–MC22, bonded to ground by JP1.'],
ps2: [HDR.pwr, 'PS2 — switched 5 V / 10 A rail: + on MC5–MC8, isolated return on MC23–MC25, bonded by JP2. Floated, the return may sit up to ±2.5 V from ground.'],
ps3: [HDR.pwr, 'PS3 — spare user rail, 0–24 V / 10 A: + on MC9–MC12, isolated return on MC26–MC28, bonded by JP3.'],
gndmc: [HDR.io, 'Ground return contact (MC15, MC16, MC55–MC63).'],
spare: [HDR.dock, 'Spare signal contact — a general-purpose line from the workcell controller, through the dock.'],
jp1: [HDR.jmp, 'JP1 — large machine-pin jumper: bonds the PS1 return to ground. Remove to float PS1.'],
jp2: [HDR.jmp, 'JP2 — bonds the PS2 return to ground; floated it may ride to ±2.5 V.'],
jp3: [HDR.jmp, 'JP3 — bonds the PS3 return to ground. Remove to float PS3.'],
jp4: [HDR.usbmc, 'JP4 — USB CC override: enables VBUS at MC17 for a device without CC wiring.'],
posts: [HDR.jmp, '12×6 machine-pin jumper stock for the rail ground bonds.'],
ccj: [HDR.usbmc, 'CC jumper — place when VBUS is broken out through machine pins without CC termination satisfied.'],
usbmc: [HDR.usbmc, 'MC17 USB VBUS, MC18 / MC19 USB D+ / D− from the RP2350A — USB at the contact field.'],
led: [HDR.led, 'Scaffold power indicator.'],
dock: [HDR.dock, 'Blade dock: PS1/PS2/PS3 + returns, 5 V and 3.3 V logic, SWD/UART debug, MCU boot/reset and the ID bus — mates to the workcell each dock.'],
ghost: ['Control Panel: Workcell (ghost)', 'The workcell half shown mated above: CM5 carrier and power-distribution hub.'],
board: [HDR.board, 'User power and I/O dock board for the base scaffold.'],
};
function ioOf(mc) {
if (mc >= 1 && mc <= 4) return T.ps1;
if (mc >= 5 && mc <= 8) return T.ps2;
if (mc >= 9 && mc <= 12) return T.ps3;
if (mc === 13 || mc === 14) return T.spare;
if (mc === 15 || mc === 16 || (mc >= 55 && mc <= 63)) return T.gndmc;
if (mc >= 17 && mc <= 19) return T.usbmc;
if (mc >= 20 && mc <= 22) return T.ps1;
if (mc >= 23 && mc <= 25) return T.ps2;
if (mc >= 26 && mc <= 28) return T.ps3;
let io = null, adc = false;
if (mc >= 29 && mc <= 32) { io = 29 - (mc - 29); adc = true; }
else if (mc >= 33 && mc <= 36) io = 25 - (mc - 33);
else if (mc >= 37 && mc <= 44) io = 19 - (mc - 37);
else if (mc >= 45 && mc <= 54) io = 9 - (mc - 45);
if (io === null) return null;
return [HDR.io, 'MC' + mc + ' → USER_IO' + io + ' — 3.3 V digital I/O, PWM, PIO' + (adc ? ', ADC-capable' : '') + '. Medium contact ~6.4 A.'];
}
const NAME_RULES = [
[/^GHOST/, 'ghost'],
[/^SW1$/, 'miniSW'],
[/^(MINI_D1|mini-D1)/, 'miniD1'],
[/^(MINI_D2|mini-D2)/, 'miniD2'],
[/^(U2$|Molecule_RP2350A_Mini)/, 'mini'],
[/^J1$/, 'usbc'],
[/^U1$/, 'efuse'], [/^U3$/, 'cc'], [/^U4$/, 'id'], [/^U5$/, 'esd'], [/^Q1$/, 'q1'],
[/^JP1/, 'jp1'], [/^JP2/, 'jp2'], [/^JP3/, 'jp3'], [/^JP4/, 'jp4'],
[/^(JMP|Adom_Jumper|GNDSPACER)/, 'posts'],
[/^CCJ/, 'ccj'],
[/^(LED1$|D1_glow|D1_cone)/, 'led'],
[/^(WIRE_MC|MC)(\d+)$/, (m) => ioOf(+m[2])],
[/^(ETT|ETS|J2)/, 'dock'],
];
const REGIONS = [
[0.1145, 0.1250, -0.0855, -0.0790, 'usbc'],
[0.1120, 0.1160, -0.0920, -0.0865, 'jp4'],
[0.0540, 0.0640, -0.0830, -0.0775, 'jp1'],
[0.0720, 0.0820, -0.0830, -0.0775, 'jp2'],
[0.0900, 0.1000, -0.0830, -0.0775, 'jp3'],
[0.0930, 0.1450, -0.0620, -0.0320, 'dock'],
[0.1230, 0.1262, -0.0675, -0.0652, 'led'],
[0.1270, 0.1300, -0.0655, -0.0625, 'efuse'],
[0.1260, 0.1330, -0.0725, -0.0670, 'q1'],
[0.1290, 0.1315, -0.0765, -0.0745, 'cc'],
[0.1780, 0.1815, -0.0540, -0.0503, 'id'],
[0.1520, 0.1880, -0.0780, -0.0575, 'mini'],
[0.0480, 0.0660, -0.0915, -0.0775, 'ps1'],
[0.0660, 0.0840, -0.0915, -0.0775, 'ps2'],
[0.0840, 0.0960, -0.0915, -0.0775, 'ps3'],
[0.1100, 0.1222, -0.0915, -0.0865, 'usbmc'],
[0.1222, 0.1910, -0.0915, -0.0865, 'io'],
];
T.io = [HDR.io, '26 RP2350 USER_IO lines on MC29–MC54 (3.3 V, 12 mA per pin); interleaved ground returns on MC15, MC16 and MC55–MC63.'];
const inv = new window.BABYLON.Matrix();
const root = scene.transformNodes.concat(scene.meshes).find(n => (n.name||'').indexOf('control-panel_scaffold') === 0);
function describe(pick) {
let n = pick.pickedMesh; const names = [];
while (n) { names.push(n.name || ''); n = n.parent; }
for (const [re, val] of NAME_RULES) {
for (const nm of names) {
const m = re.exec(nm);
if (m) {
const r = typeof val === 'function' ? val(m) : T[val];
if (r) return r;
}
}
}
if (root && pick.pickedPoint) {
root.getWorldMatrix().invertToRef(inv);
const p = window.BABYLON.Vector3.TransformCoordinates(pick.pickedPoint, inv);
for (const [x0, x1, y0, y1, key] of REGIONS) {
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1) return T[key];
}
return T.board;
}
return null;
}
let raf = 0;
wrap.addEventListener('pointermove', (ev) => {
if (!tipsOn || raf) return;
raf = requestAnimationFrame(() => {
raf = 0;
if (!tipsOn) return;
const pick = scene.pick(scene.pointerX, scene.pointerY, m => m.isEnabled() && (m.name||'') !== 'ground');
const d = pick && pick.hit ? describe(pick) : null;
if (d) {
tipT.textContent = d[0]; tipB.textContent = d[1];
tip.style.display = 'block';
const r = wrap.getBoundingClientRect();
tip.style.left = Math.min(ev.clientX - r.left + 14, r.width - 310) + 'px';
tip.style.top = Math.max(ev.clientY - r.top - 10, 46) + 'px';
} else tip.style.display = 'none';
});
});
wrap.addEventListener('pointerleave', () => { tip.style.display = 'none'; });
} catch (e) {}
addEventListener('message', (e) => {
const d = e.data || {};
if (d.type !== 'cp-cam' || (d.board && d.board !== 'scaffold')) return;
try {
const c = viewer.getCamera();
if (Array.isArray(d.target)) c.setTarget(new window.BABYLON.Vector3(...d.target));
if (typeof d.alpha === 'number') c.alpha = d.alpha;
if (typeof d.beta === 'number') c.beta = d.beta;
if (typeof d.radius === 'number') c.radius = d.radius;
} catch (err) {}
});
}).catch(() => wrap.remove());
} catch (e) { wrap.remove(); }
}
</script></body></html>" loading="lazy" title="3D viewer"></iframe>
</div>
<p class="viewer-note">Drag to orbit · wheel to zoom · right-drag to pan · the cube snaps the view.</p>
<h3>Mated — how the pair comes together</h3>
<p>The two halves mate coplanar at the blade dock, with the project's molecules, jumpers and wires dropping into place:</p>
<img src="/api/pages/adom/base-scaffold-control-panel/files/docs/mated-assembly.gif" alt="Mated control panel assembly">
<h2>Block Diagrams</h2>
<img src="/api/pages/adom/base-scaffold-control-panel/files/diagrams/workcell-block-diagram-user.png?v=v210" alt="Control Panel: Workcell — Block Diagram (User)">
<img src="/api/pages/adom/base-scaffold-control-panel/files/diagrams/scaffold-block-diagram-user.png?v=v210" alt="Control Panel: Scaffold — Block Diagram (User)">
<h2>Input power</h2>
<p>Four independent bench-supply inputs land on the workcell control panel. The user rails are individually metered at 20-bit precision with <strong>INA228</strong>; on the bench the meter reads within <strong>0.04 %</strong> of a reference meter across 5–60 V and drifts under 0.2 % over a 20-minute, 10 A soak.</p>
<table>
<tr><th>Rail</th><th>Rating</th><th>Description</th></tr>
<tr><td><strong>PS1</strong></td><td>0–60 V / 12 A</td><td>User variable power supply with full control</td></tr>
<tr><td><strong>PS2</strong></td><td>5 V / 10 A (4.75–5.25 V)</td><td>User-enabled switched supply with soft start; also sources USB VBUS. Held 9 A for 20 minutes on the bench</td></tr>
<tr><td><strong>PS3</strong></td><td>0–24 V / 10 A</td><td>Spare user rail, passed through to the scaffold</td></tr>
<tr><td><strong>PS4</strong></td><td>5.0–5.25 V</td><td>Board supply for the workcell controller and logic; typically 0.9 A idle, 1.3 A under load</td></tr>
</table>
<p><strong>Input limits.</strong> PS2 and PS4 must not exceed <strong>5.5 V</strong>; neither input tolerates more.</p>
<p><strong>Isolation and ground scheme.</strong> Each user rail's return is isolated and may sit up to <strong>±2.5 V</strong> from ground when its bond is removed. Large machine-pin jumpers on the scaffold bond a single return-to-ground per rail (default: bridged), so each rail's grounding is an explicit, per-project choice rather than a fixed assumption.</p>
<h2>I/O available to the project</h2>
<p>The user is given full control of the nested <strong>RP2350A</strong>, with <strong>26 available I/O</strong> (<a href="https://wiki.adom.inc/adom/rp2350a-mini" target="_blank" rel="noopener">RP2350A Mini</a>) and <strong>USB 2.0</strong> capable of dual data roles. Further, the workcell side of the control panel offers <strong>two USB 3.0 SuperSpeed host ports</strong>.</p>
<p><strong>Wireless.</strong> AX210-class module provides Wi-Fi 6E across all three bands (2.4 / 5 / 6 GHz) with 160 MHz channel width and 2×2 MIMO for link rates to ~2.4 Gbps, plus Bluetooth 5.3. Wi-Fi runs on PCIe and Bluetooth on USB. Measured on the bench: <strong>636 / 815 Mbit/s</strong> over 6 GHz.</p>
<p><strong>Sensing.</strong> The panel carries on-board environmental sensing (temperature, relative humidity, barometric pressure and air quality) and 6-axis motion sensing (3-axis accelerometer + 3-axis gyroscope), both available to the project through the Control Panel Dashboard. A digital microphone is also fitted for acoustic capture.</p>
<h3>User MCU I/O</h3>
<p>26 user I/O reach the machine-contact field at <strong>3.3 V logic</strong> (−0.3 to 3.6 V absolute, 12 mA per pin). Every line supports digital input/output, PWM and the MCU's programmable I/O; four are additionally analog-capable. All 26 lines were verified toggling at 250 kHz, full swing.</p>
<table>
<tr><th>Machine contacts</th><th>User I/O</th><th>Capabilities</th></tr>
<tr><td>MC29–MC32</td><td>IO29, IO28, IO27, IO26</td><td>Digital I/O, PWM, programmable I/O, <strong>ADC (4 analog channels)</strong>, I²C / SPI / UART alternate functions</td></tr>
<tr><td>MC33–MC36</td><td>IO25, IO24, IO23, IO22</td><td>Digital I/O, PWM, programmable I/O, I²C / SPI / UART alternate functions</td></tr>
<tr><td>MC37–MC44</td><td>IO19 … IO12</td><td>Digital I/O, PWM, programmable I/O, I²C / SPI / UART alternate functions</td></tr>
<tr><td>MC45–MC54</td><td>IO9 … IO0</td><td>Digital I/O, PWM, programmable I/O, I²C / SPI / UART alternate functions</td></tr>
</table>
<h3>USB</h3>
<table>
<tr><th>Port</th><th>Side</th><th>Protocol</th><th>Data role</th><th>Power</th><th>Availability</th></tr>
<tr><td><strong>USB-C</strong></td><td>Scaffold (user)</td><td>USB 2.0</td><td><strong>Dual-role</strong> — host or device</td><td>5 V VBUS from PS2, <strong>3.0 A</strong>; latching eFuse trips at 3.3 A</td><td><strong>VBUS unavailable while PS2 is floating</strong> (isolated by removing its ground jumper)</td></tr>
<tr><td><strong>USB-C</strong></td><td>Workcell</td><td>USB 3.0 SuperSpeed (5 Gb/s)</td><td>Host</td><td>5 V VBUS from PS2, <strong>3.0 A</strong>; latching eFuse trips at 3.3 A</td><td><strong>VBUS unavailable while PS2 is floating</strong></td></tr>
<tr><td><strong>USB-A</strong></td><td>Workcell</td><td>USB 3.0 SuperSpeed (5 Gb/s)</td><td>Host</td><td>5 V VBUS from PS2, <strong>1.5 A</strong>; latching eFuse trips at 1.5 A</td><td><strong>VBUS unavailable while PS2 is floating.</strong> Its USB 2.0 lanes are muxed with Bluetooth and are <strong>mutually exclusive</strong> — USB-A is unavailable while Bluetooth is in use</td></tr>
</table>
<p><strong>USB behavior.</strong> In host role the board sources VBUS from the switched 5 V (PS2) rail through a protected path; a latching eFuse turns the port off on overload until it is reset. In device role (for example, plugged into a PC) VBUS sourcing is off and back-feed into the supply is blocked — 0 V measured at the PS2 contacts with a PC attached. If VBUS is broken out through machine pins and CC termination is not satisfied, the CC jumper must be placed.</p>
<h2>Debug and control across the dock</h2>
<p>The dock carries the facilities needed to program and control the project's MCU without opening anything up: <strong>SWD and UART debug</strong>, plus boot and reset control of the user MCU, and low-voltage service rails. Bench results: SWD clean to <strong>31 MHz</strong>, UART to <strong>3 Mbaud</strong>, and boot/reset control 20 of 20 cycles. An <strong>ID EEPROM</strong> with a factory 128-bit serial number tells the workcell which scaffold is docked. A <strong>companion dashboard application</strong> provides live control and telemetry — meter the rails, set PS1 like a bench supply, switch rails, drive the scaffold I/O, and program the user MCU.</p>
<h2>Machine contacts</h2>
<p>Project connections land on the scaffold's machine-contact field. Two contact classes are used; both are solderless press-fit receptacles that accept a machine pin tail, a machine needle, or a bare solid-core wire.</p>
<table>
<tr><th>Property</th><th><a href="https://wiki.adom.inc/adom/machinecontactmedium" target="_blank" rel="noopener">Medium contact</a></th><th><a href="https://wiki.adom.inc/adom/machinecontactlarge" target="_blank" rel="noopener">Large contact</a></th></tr>
<tr><td><strong>Current rating</strong></td><td>~<strong>6.4 A</strong> (80 % derated)</td><td>~<strong>20.8 A</strong> (80 % derated)</td></tr>
<tr><td><strong>Mating wire</strong></td><td>24 AWG solid core (precision-bent, enamel-coated)</td><td>14 AWG solid core (precision-bent, enamel-coated)</td></tr>
<tr><td><strong>Also mates with</strong></td><td>Medium machine pin tail (Ø0.51 mm), medium needle</td><td>Large machine pin tail (Ø1.85 mm), large needle</td></tr>
<tr><td><strong>Grid pitch</strong></td><td>2 mm within a row; rows ≥ 4 mm apart</td><td>8 mm technical; 32 mm as implemented on the base scaffold</td></tr>
</table>
<p>Both ratings are <strong>contact-limited</strong> — the mating pin can carry more, so the contact sets the figure — and are quoted at 80 % derating for margin.</p>
<h3>Contact field map</h3>
<p>The scaffold carries 63 machine contacts.</p>
<table>
<tr><th>Machine contacts</th><th>Function</th></tr>
<tr><td>MC1–MC4 / MC20–MC22</td><td>PS1 + / isolated return (bonded by JP1)</td></tr>
<tr><td>MC5–MC8 / MC23–MC25</td><td>PS2 + / isolated return (bonded by JP2)</td></tr>
<tr><td>MC9–MC12 / MC26–MC28</td><td>PS3 + / isolated return (bonded by JP3)</td></tr>
<tr><td>MC13, MC14</td><td>Spare signals from the workcell controller</td></tr>
<tr><td>MC15, MC16, MC55–MC63</td><td>Ground</td></tr>
<tr><td>MC17</td><td>USB VBUS (JP4 enables it for a device without CC wiring)</td></tr>
<tr><td>MC18, MC19</td><td>USB D+ / D− (RP2350A)</td></tr>
<tr><td>MC29–MC54</td><td>USER_IO, see above</td></tr>
</table>
<h2>Environmental</h2>
<p>The base scaffold and its control panel operate in a <strong>controlled 20 – 25 °C</strong> environment. The workcell electronics are rated <strong>−20 to +70 °C</strong>.</p>
<p>On-board sensing spans a considerably wider envelope:</p>
<table>
<tr><th>Measurement</th><th>Range</th></tr>
<tr><td>Temperature</td><td>−40 to +85 °C</td></tr>
<tr><td>Relative humidity</td><td>0 to 100 % RH</td></tr>
<tr><td>Barometric pressure</td><td>300 to 1100 hPa</td></tr>
<tr><td>Air quality</td><td>Gas-resistance based indoor air-quality sensing</td></tr>
<tr><td>Acceleration</td><td>±2 / ±4 / ±8 / ±16 g selectable</td></tr>
<tr><td>Angular rate</td><td>±125 to ±2000 °/s selectable</td></tr>
</table>
<h2>Relationship to the Rest of the System</h2>
<ul>
<li>The <strong>electrical boundary</strong> sits at the dock: everything upstream — bench inputs, metering and the workcell controller — is the workcell half. Everything the project touches — rails at the machine contacts, user I/O, USB and ground bonding — is the scaffold half.</li>
<li>Wiring from the panels to the control panel is bent automatically; gauges and termination are documented in the <a href="https://wiki.adom.inc/adom/base-scaffold-system" target="_blank" rel="noopener">Base Scaffold System — Wiring</a> section (James/Luke).</li>
<li>The project's connections terminate on the machine-contact field exactly like every other contact on the scaffold.</li>
</ul>
<div class="ds-head">
<h2>Datasheet</h2>
<a href="https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/docs/Control_Panel_Scaffold_Datasheet_V2.1.0.pdf" target="_blank" rel="noopener">Open in new tab ↗</a>
</div>
<p>Control Panel: Scaffold V2.1.0 datasheet.</p>
<div class="ds-frame">
<img src="/api/pages/adom/base-scaffold-control-panel/files/docs/datasheet/page-1.png?v=v210" alt="Control Panel: Scaffold datasheet, page 1 of 4">
<img src="/api/pages/adom/base-scaffold-control-panel/files/docs/datasheet/page-2.png?v=v210" alt="Control Panel: Scaffold datasheet, page 2 of 4">
<img src="/api/pages/adom/base-scaffold-control-panel/files/docs/datasheet/page-3.png?v=v210" alt="Control Panel: Scaffold datasheet, page 3 of 4">
<img src="/api/pages/adom/base-scaffold-control-panel/files/docs/datasheet/page-4.png?v=v210" alt="Control Panel: Scaffold datasheet, page 4 of 4">
</div>
<a class="ds-download" href="https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/docs/Control_Panel_Scaffold_Datasheet_V2.1.0.pdf" download>Download PDF</a> · <a class="ds-download" href="https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/docs/Control_Panel_Scaffold_Datasheet_V2.1.0.md" target="_blank" rel="noopener">Markdown</a>
</div>
<script>
(function () {
function post() {
// Measure the content wrapper, not the document: documentElement.scrollHeight
// is >= the iframe viewport, so posting it back creates a grow-forever loop.
var w = document.querySelector(".wrap");
if (!w) return;
var h = Math.ceil(w.getBoundingClientRect().height);
parent.postMessage({ type: "readme-height", height: Math.min(h, 16000) }, "*");
}
addEventListener("load", post);
setTimeout(post, 1200); setTimeout(post, 4000);
post();
})();
</script>
<script>
// cp-cam relay: forward camera messages from the parent page into the viewer iframes
addEventListener('message', function (e) {
var d = e.data || {};
if (d.type !== 'cp-cam') return;
document.querySelectorAll('iframe.vframe').forEach(function (f) {
try { f.contentWindow.postMessage(d, '*'); } catch (err) {}
});
});
</script>
</body>
</html>