Seedbox v0.1 + firmware, generic kit/synth, ROOT/SCALE pages
Some checks failed
check / check (push) Has been cancelled
Some checks failed
check / check (push) Has been cancelled
First commit of the repo. Contents beyond the v0.1 simulator and layout: - Full Pico 2 W firmware (firmware/app, drivers, engine port, host test). - BANK page and 404 mode A/B removed; learned pads play exactly what was learned. - 404/MiniFreak renamed to KIT/SYNTH throughout (jacks, code, sim, docs, layout labels; wiring rebuilt). - ROLL returns to page select after a re-roll; a freshly learned track turns on. - LCD contrast 66. - ROOT and SCALE pages: 21 scales, AUTO = the seed picks; global and sticky. Intended music change: the scale table grew from 8 to 21 and AUTO picks from all of them, so every seed's scale changed. Vectors regenerated with npm run vectors, plus new key-override cases. Flash record is now version 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
243
tools/bom.html
Normal file
243
tools/bom.html
Normal file
@@ -0,0 +1,243 @@
|
||||
<title>Seedbox Build Sheet</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=Martian+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* Layout: one column workbench sheet. Parts list grouped by subsystem with a tick-off column, then pin map and wiring notes. */
|
||||
:root{
|
||||
--bg:#E8ECEF; --surface:#F6F7F9; --ink:#18202A; --muted:#566271; --line:#C3CBD3; --accent:#1E4C9E;
|
||||
--have:#1D7A4F; --check:#9A6B10; --buy:#B4441F;
|
||||
--f-ui:'Hanken Grotesk', system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
--f-mono:'Martian Mono', ui-monospace, Menlo, Consolas, monospace;
|
||||
}
|
||||
@media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){
|
||||
--bg:#12161B; --surface:#1A1F25; --ink:#E2E7EC; --muted:#8C98A5; --line:#333B44; --accent:#8EB0F2;
|
||||
--have:#5CCB92; --check:#E2B455; --buy:#F07B52; color-scheme:dark } }
|
||||
:root[data-theme="dark"]{
|
||||
--bg:#12161B; --surface:#1A1F25; --ink:#E2E7EC; --muted:#8C98A5; --line:#333B44; --accent:#8EB0F2;
|
||||
--have:#5CCB92; --check:#E2B455; --buy:#F07B52; color-scheme:dark }
|
||||
*{box-sizing:border-box}
|
||||
body{background:var(--bg);color:var(--ink);font:15px/1.55 var(--f-ui);padding-block:28px 48px;padding-inline:16px}
|
||||
.wrap{max-width:980px;margin:0 auto;display:grid;gap:22px}
|
||||
h1{font:700 24px/1.15 var(--f-mono);margin:0;letter-spacing:-.01em}
|
||||
h2{font:700 13px var(--f-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--accent);margin:0}
|
||||
.sub{color:var(--muted);margin:6px 0 0;max-width:68ch}
|
||||
.bar{display:flex;flex-wrap:wrap;gap:10px 16px;align-items:center;justify-content:space-between;background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:12px 14px}
|
||||
.tally{display:flex;flex-wrap:wrap;gap:6px 18px;font:500 12px var(--f-mono);font-variant-numeric:tabular-nums;color:var(--muted)}
|
||||
.tally b{color:var(--ink)}
|
||||
.ctrls{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
|
||||
.btn{font:600 12px var(--f-mono);border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:6px;padding:7px 12px;cursor:pointer}
|
||||
.btn:hover{border-color:var(--accent)}
|
||||
.btn:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
||||
.check{display:flex;gap:8px;align-items:center;font:500 13px var(--f-ui);color:var(--muted)}
|
||||
section{display:grid;gap:10px}
|
||||
.tbl{overflow-x:auto;background:var(--surface);border:1px solid var(--line);border-radius:10px}
|
||||
table{width:100%;border-collapse:collapse;min-width:640px}
|
||||
th{font:600 10px var(--f-mono);letter-spacing:.08em;text-transform:uppercase;color:var(--muted);text-align:left;padding:9px 12px;border-bottom:1px solid var(--line)}
|
||||
td{padding:9px 12px;border-bottom:1px solid var(--line);vertical-align:top}
|
||||
tr:last-child td{border-bottom:0}
|
||||
td.part{font-weight:600}
|
||||
td.part small{display:block;font-weight:400;color:var(--muted);font-size:12px}
|
||||
td.qty{font:500 13px var(--f-mono);font-variant-numeric:tabular-nums;white-space:nowrap}
|
||||
td.note{color:var(--muted);font-size:13px;max-width:44ch}
|
||||
td.tick{width:44px;text-align:center}
|
||||
td.tick input{width:18px;height:18px;accent-color:var(--have)}
|
||||
.st{display:inline-block;font:600 10px var(--f-mono);letter-spacing:.06em;text-transform:uppercase;padding:2px 7px;border-radius:999px;border:1px solid currentColor;white-space:nowrap}
|
||||
.st.have{color:var(--have)} .st.check{color:var(--check)} .st.buy{color:var(--buy)} .st.opt{color:var(--muted)}
|
||||
tr.got td:not(.tick){opacity:.5}
|
||||
tr.got td.part{text-decoration:line-through}
|
||||
.hide-got tr.got{display:none}
|
||||
.mono{font-family:var(--f-mono);font-size:12.5px}
|
||||
.pins td:first-child{font:600 13px var(--f-mono);white-space:nowrap}
|
||||
.cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px}
|
||||
.card{background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:14px 16px;min-width:0}
|
||||
.card h3{font:700 14px var(--f-ui);margin:0 0 8px}
|
||||
.card ul{margin:0;padding-left:18px;display:grid;gap:4px;font-size:14px}
|
||||
.card p{margin:0 0 8px;font-size:14px}
|
||||
pre{font:400 12px/1.5 var(--f-mono);margin:0;overflow-x:auto;color:var(--ink)}
|
||||
.gone{color:var(--muted);font-size:14px}
|
||||
</style>
|
||||
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<h1>Seedbox build sheet</h1>
|
||||
<p class="sub">Everything for the Pico 2W build: Nokia 5110 screen with the on-screen track map, 11 track buttons in a diode matrix, encoder, two pots, SEED, RUN, one MIDI in and two MIDI outs. "Have" means it was on your parts list; tick items off as you find them in the drawers.</p>
|
||||
</header>
|
||||
|
||||
<div class="bar">
|
||||
<div class="tally" id="tally"></div>
|
||||
<div class="ctrls">
|
||||
<label class="check"><input type="checkbox" id="hideGot"> Hide ticked</label>
|
||||
<button class="btn" id="copyBuy" type="button">Copy shopping list</button>
|
||||
<button class="btn" id="reset" type="button">Clear ticks</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="parts"></div>
|
||||
|
||||
<section>
|
||||
<h2>Pin map</h2>
|
||||
<div class="tbl"><table class="pins">
|
||||
<thead><tr><th>GPIO</th><th>Function</th><th>Connects to</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>GP0</td><td>UART0 TX · MIDI OUT A</td><td>10 Ω → OUT KIT tip (pin 5)</td></tr>
|
||||
<tr><td>GP1</td><td>UART0 RX · MIDI IN</td><td>6N137 pin 6 (1 kΩ pull-up to 3V3)</td></tr>
|
||||
<tr><td>GP4</td><td>UART1 TX · MIDI OUT B</td><td>10 Ω → OUT SYNTH tip (pin 5)</td></tr>
|
||||
<tr><td>GP2 GP3 GP5</td><td>Button matrix rows</td><td>Inputs with internal pull-ups</td></tr>
|
||||
<tr><td>GP6 GP7 GP8 GP9</td><td>Button matrix columns</td><td>Driven low one at a time</td></tr>
|
||||
<tr><td>GP10 GP11</td><td>Encoder A / B</td><td>Encoder module SIA / SIB</td></tr>
|
||||
<tr><td>GP12</td><td>Encoder push</td><td>Encoder module SW</td></tr>
|
||||
<tr><td>GP13</td><td>SEED button</td><td>Button to GND, internal pull-up</td></tr>
|
||||
<tr><td>GP14</td><td>RUN toggle</td><td>Toggle to GND, internal pull-up</td></tr>
|
||||
<tr><td>GP17</td><td>LCD SCE</td><td>Can be tied to GND instead (frees the pin)</td></tr>
|
||||
<tr><td>GP18</td><td>SPI0 SCK</td><td>LCD SCLK</td></tr>
|
||||
<tr><td>GP19</td><td>SPI0 TX</td><td>LCD DN (MOSI)</td></tr>
|
||||
<tr><td>GP20</td><td>LCD DC</td><td>LCD D/C</td></tr>
|
||||
<tr><td>GP21</td><td>LCD RST</td><td>LCD RST</td></tr>
|
||||
<tr><td>GP22</td><td>Free</td><td>Only for backlight dimming later, through a transistor</td></tr>
|
||||
<tr><td>GP26</td><td>ADC0 · Energy pot</td><td>Pot wiper (ends to 3V3 and AGND)</td></tr>
|
||||
<tr><td>GP27</td><td>ADC1 · Drift pot</td><td>Pot wiper (ends to 3V3 and AGND)</td></tr>
|
||||
<tr><td>GP15 GP16 GP28</td><td>Spare</td><td>GP28 is also an analog input</td></tr>
|
||||
<tr><td>GP23 24 25 29</td><td>Reserved</td><td>Used by the Pico 2W's wireless chip, don't wire</td></tr>
|
||||
</tbody>
|
||||
</table></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Wiring notes</h2>
|
||||
<div class="cols">
|
||||
<div class="card">
|
||||
<h3>MIDI OUT (×2, 3.3 V spec, TRS Type A)</h3>
|
||||
<pre>3V3 ──33 Ω── ring (DIN pin 4)
|
||||
TX ──10 Ω── tip (DIN pin 5)
|
||||
GND ──────── sleeve (DIN pin 2)</pre>
|
||||
<p style="margin-top:8px">Lug order differs between jack models: find tip, ring and sleeve with the meter and a plug inserted.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>MIDI IN (6N137)</h3>
|
||||
<pre>ring ──220 Ω── opto 2 (anode)
|
||||
tip ───────── opto 3 (cathode)
|
||||
1N4148 across 2–3, band to pin 2
|
||||
opto 8 → VBUS (5 V) opto 5 → GND
|
||||
opto 6 → GP1, 1 kΩ to 3V3
|
||||
opto 7 ──────── opto 8 (enable high)
|
||||
100 nF across 8–5 (required)</pre>
|
||||
<p style="margin-top:8px">Pull the output up to 3V3, never 5 V. The sleeve stays unconnected on the input.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Button matrix (3 × 4)</h3>
|
||||
<pre> C0 GP6 C1 GP7 C2 GP8 C3 GP9
|
||||
R0 GP2 KICK KICK 2 SNARE (RUN toggle, not in matrix)
|
||||
R1 GP3 HAT HAT 16 PERC GHOST
|
||||
R2 GP5 BASS MEL A MEL B PAD</pre>
|
||||
<p style="margin-top:8px">Same layout as the lid: the RUN toggle sits in row 1 / column 4, but it has its own pin (GP14), so that matrix slot is empty. Each button: row → switch → 1N4148 → column, band toward the column. The diodes let you hold several buttons at once without phantom presses.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Nokia 5110 module</h3>
|
||||
<ul>
|
||||
<li>VCC to 3V3, not 5 V. Logic connects straight to the Pico, no resistors.</li>
|
||||
<li>Backlight polarity differs between red and blue boards. Test which way yours lights (BL to GND or to 3V3), then either hard-wire it or switch that side with a transistor for dimming (NPN on the ground side, PNP on the 3.3 V side).</li>
|
||||
<li>Contrast is set in firmware and drifts a little with temperature. Expect to tune it once.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Encoder and pots</h3>
|
||||
<ul>
|
||||
<li>Power the encoder module from 3V3. Its onboard pull-ups would otherwise put 5 V on the Pico pins.</li>
|
||||
<li>Pots: one end to 3V3, the other to AGND (pin 33), wiper to the ADC pin. A 100 nF cap from each wiper to AGND calms jitter.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Panel layout</h3>
|
||||
<ul>
|
||||
<li>See the panel layout page (landscape): screen with the encoder on the left, ENERGY / DRIFT / SEED in the middle, the 4 × 3 block on the right (11 track buttons, RUN toggle top-right).</li>
|
||||
<li>Screen sits on the lid, wires through one hole hidden under the module. USB and the three jacks on the back wall; the Pico's USB end sits at the stripboard edge, behind the slot.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>No longer needed</h2>
|
||||
<p class="gone">USB panel extension, standoffs and screws (stripboard glued behind a slot instead) · 0.91" OLED (replaced by the Nokia screen) · WS2812 LED strip (the screen shows track state) · 4.7 kΩ opto resistor (6N138 only) · 470 Ω resistors (old 5 V MIDI out values) · 5-pin DIN jacks (3.5 mm TRS instead).</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
/* status: have = on your parts list, check = probably in the drawer, buy = likely missing, opt = optional */
|
||||
var GROUPS=[
|
||||
{name:'Core',rows:[
|
||||
['pico','Raspberry Pi Pico 2W','RP2350, micro-USB','1','have','The whole engine runs on it with lots of headroom.'],
|
||||
['lcd','Nokia 5110 LCD module','PCD8544, 84×48, SPI','1','have','The module you used with Arduino.'],
|
||||
['box','Enclosure','','1','have','About 80 × 150 × 40 mm. Panel layout: tools/panel.html.']
|
||||
]},
|
||||
{name:'Controls',rows:[
|
||||
['enc','Rotary encoder with push','KY-040 module or bare EC11','1','have','Power it from 3V3.'],
|
||||
['pots','Potentiometer, linear','B10K (anything 5k–50k linear works)','2','have','Energy and Drift.'],
|
||||
['knobs','Knobs','to fit the pot and encoder shafts','3','check','Shaft types differ (D, knurled, 6 mm). Match them.'],
|
||||
['btn','Momentary push button, panel mount','~12 mm, non-latching','11','check','7 kit + 4 line tracks.'],
|
||||
['seedbtn','Momentary push button, large','16–22 mm, non-latching','1','check','SEED. Any big momentary works.'],
|
||||
['tog','Toggle switch, SPDT','','1','have','RUN / STOP. Your old bypass switch.'],
|
||||
['dio','1N4148 diode','or 1N4448 / 1N914 / any small-signal diode','12','buy','11 for the button matrix + 1 for MIDI IN. 1N4448 works too. Every button needs one: a missing diode gives phantom presses with three buttons held.']
|
||||
]},
|
||||
{name:'MIDI',rows:[
|
||||
['trs','3.5 mm stereo (TRS) jack, panel mount','3 lugs: tip, ring, sleeve. Not mono.','3','check','IN, OUT KIT, OUT SYNTH. MIDI TRS Type A. 6 mm hole each.'],
|
||||
['adapt','TRS-to-DIN MIDI adapter, Type A','female DIN, 3.5 mm plug','1','buy','For a DIN synth or sampler (e.g. MiniFreak). Must say Type A (Type B swaps tip and ring). Type A TRS gear (e.g. SP-404MKII) takes a plain stereo 3.5 mm cable.'],
|
||||
['opto','6N137 optocoupler','DIP-8 (6N138 also works: then drop the pin 7 link)','1','have','MIDI IN. You have three; one is used.'],
|
||||
['sock','DIP-8 IC socket','','1','buy','So the opto can be swapped.'],
|
||||
['r330','Resistor 330 Ω','220–470 Ω','1','check','LCD backlight (LED pin) to 3.3 V, inline at the module.'],
|
||||
['r220','Resistor 220 Ω','150–220 Ω','1','have','MIDI IN, in series with the opto LED. The 6N137 wants a bit more LED current: use 150 Ω if learning misses notes.'],
|
||||
['r1k','Resistor 1 kΩ','470 Ω–2.2 kΩ is fine','2','have','Opto output pull-up, NPN base.'],
|
||||
['r33','Resistor 33 Ω, ½ W','27–47 Ω is fine','2','buy','MIDI OUT ring (pin 4) line, per the MIDI 3.3 V spec. ½ W because a mono plug in an OUT jack shorts it straight to ground (0.33 W); a ¼ W one would cook.'],
|
||||
['r10','Resistor 10 Ω','10–22 Ω is fine','2','check','MIDI OUT tip (pin 5) line, from TX. Don\'t replace with a wire: it protects the pin.'],
|
||||
['cap','Ceramic capacitor 100 nF','anything 47–220 nF','3','check','C1 on the board (6N137 supply bypass, required). Optional: one per pot, soldered at the pot from wiper to the left lug, if the readings jitter.']
|
||||
]},
|
||||
{name:'Screen extras',rows:[
|
||||
['blt','NPN or PNP transistor','2N3904 / BC547 (NPN) or 2N3906 / BC557 (PNP)','1','opt','Only for a dimmable backlight. NPN if your BL lights when grounded, PNP if it lights from 3.3 V. Otherwise hard-wire it.']
|
||||
]},
|
||||
{name:'Power and assembly',rows:[
|
||||
['perf','Stripboard','at least 34 × 28 holes (about 86 × 71 mm)','1','check','Pico socketed at the top edge, so its USB port meets a slot in the back wall.'],
|
||||
['hdr','Female header strip, 1 × 20','2.54 mm','2','check','Socket the Pico instead of soldering it in.'],
|
||||
['mhdr','Male header strip, 1 × 20','2.54 mm','2','check','Soldered to the Pico so it plugs into the female headers. Skip if yours is a Pico 2 WH (headers fitted).'],
|
||||
['tape','Double-sided foam tape or hot glue','','—','check','Holds the stripboard and the screen without screws.']
|
||||
]}
|
||||
];
|
||||
var LABEL={have:'have',check:'check',buy:'buy',opt:'optional'};
|
||||
var ticks={}; try{ ticks=JSON.parse(localStorage.getItem('seedbox.bom')||'{}')||{}; }catch(e){ ticks={}; }
|
||||
function saveTicks(){ try{ localStorage.setItem('seedbox.bom',JSON.stringify(ticks)); }catch(e){} }
|
||||
var root=document.getElementById('parts'), rowEls={};
|
||||
GROUPS.forEach(function(g){
|
||||
var sec=document.createElement('section'), h=document.createElement('h2'); h.textContent=g.name; sec.appendChild(h);
|
||||
var wrap=document.createElement('div'); wrap.className='tbl';
|
||||
var t=document.createElement('table'); t.innerHTML='<thead><tr><th>Got</th><th>Part</th><th>Qty</th><th>Status</th><th>Notes</th></tr></thead>';
|
||||
var tb=document.createElement('tbody');
|
||||
g.rows.forEach(function(r){
|
||||
var tr=document.createElement('tr'), id='p_'+r[0];
|
||||
tr.innerHTML='<td class="tick"><input type="checkbox" id="'+id+'" aria-label="Got '+r[1].replace(/"/g,'')+'"></td>'+
|
||||
'<td class="part"></td><td class="qty"></td><td><span class="st '+r[4]+'">'+LABEL[r[4]]+'</span></td><td class="note"></td>';
|
||||
var p=tr.querySelector('.part'); p.textContent=r[1]; if(r[2]){ var sm=document.createElement('small'); sm.textContent=r[2]; p.appendChild(sm); }
|
||||
tr.querySelector('.qty').textContent=r[3]; tr.querySelector('.note').textContent=r[5];
|
||||
var cb=tr.querySelector('input'); cb.checked=!!ticks[r[0]]; tr.classList.toggle('got',cb.checked);
|
||||
cb.addEventListener('change',function(){ ticks[r[0]]=cb.checked; saveTicks(); tr.classList.toggle('got',cb.checked); tally(); });
|
||||
rowEls[r[0]]={tr:tr,row:r}; tb.appendChild(tr);
|
||||
});
|
||||
t.appendChild(tb); wrap.appendChild(t); sec.appendChild(wrap); root.appendChild(sec);
|
||||
});
|
||||
function tally(){
|
||||
var n=0,got=0,buy=0,miss=[];
|
||||
Object.keys(rowEls).forEach(function(k){ var r=rowEls[k].row; n++; if(ticks[k]) got++; else if(r[4]!=='opt'){ miss.push(r); if(r[4]==='buy') buy++; } });
|
||||
document.getElementById('tally').innerHTML='<span><b>'+got+'</b> of '+n+' ticked</span><span><b>'+miss.length+'</b> still to find</span><span><b>'+buy+'</b> likely to buy</span>';
|
||||
return miss;
|
||||
}
|
||||
tally();
|
||||
var hide=document.getElementById('hideGot');
|
||||
hide.addEventListener('change',function(){ root.classList.toggle('hide-got',hide.checked); });
|
||||
document.getElementById('reset').addEventListener('click',function(){ ticks={}; saveTicks(); Object.keys(rowEls).forEach(function(k){ rowEls[k].tr.querySelector('input').checked=false; rowEls[k].tr.classList.remove('got'); }); tally(); });
|
||||
document.getElementById('copyBuy').addEventListener('click',function(){
|
||||
var b=this, txt=tally().map(function(r){ return r[3]+' × '+r[1]+(r[2]?' ('+r[2]+')':''); }).join('\n');
|
||||
function ok(){ b.textContent='Copied'; setTimeout(function(){ b.textContent='Copy shopping list'; },1400); }
|
||||
try{ navigator.clipboard.writeText(txt).then(ok,function(){ b.textContent='Copy failed'; }); }catch(e){ b.textContent='Copy failed'; }
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user