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>
94 lines
7.4 KiB
JavaScript
94 lines
7.4 KiB
JavaScript
/* Checks hardware/layout.js: geometry (on board, not on a cut, one lead per hole),
|
|
footprints (nothing parked under the Pico or the DIP socket that should not be),
|
|
and the electrical rules of the circuit (MIDI 1.0 3.3 V spec CA-033, TRS Type A jacks, Pico pin map).
|
|
Usage: node hardware/check-layout.js [--nets] exit code 0 = clean. */
|
|
var L=require('./layout.js');
|
|
function rowName(r){ return r<26 ? String.fromCharCode(65+r) : 'A'+String.fromCharCode(65+r-26); }
|
|
function pos(h){ return rowName(h[1])+(h[0]+1); }
|
|
var key=function(h){ return h[0]+','+h[1]; };
|
|
var cut={}; L.cuts.forEach(function(h){ cut[key(h)]=1; });
|
|
var issues=[];
|
|
|
|
// --- copper: each uncut run of a strip is a segment; jumpers join segments ---
|
|
function seg(h){ if(cut[key(h)]) return null; var a=h[0], b=h[0];
|
|
while(a>0 && !cut[key([a-1,h[1]])]) a--; while(b<L.W-1 && !cut[key([b+1,h[1]])]) b++; return 'r'+h[1]+':'+a+'-'+b; }
|
|
var par={}; function find(x){ while(par[x]!==undefined && par[x]!==x) x=par[x]; return x; }
|
|
function join(a,b){ a=find(a); b=find(b); if(a!==b) par[a]=b; }
|
|
|
|
// --- every lead and wire end, by name ---
|
|
var occ={}, ends={}; // ends: name -> hole
|
|
function use(h,what){
|
|
if(h[0]<0||h[1]<0||h[0]>=L.W||h[1]>=L.H) issues.push('off the board: '+what+' at '+pos(h));
|
|
if(cut[key(h)]) issues.push('on a cut: '+what+' at '+pos(h));
|
|
(occ[key(h)]=occ[key(h)]||[]).push(what);
|
|
}
|
|
L.parts.forEach(function(p){ p.pins.forEach(function(pin,i){
|
|
var nm = p.id==='pico' ? 'pico.'+pin.num+'.'+pin.n : p.id+'.'+(i+1);
|
|
if(p.id==='U1') nm='U1.'+pin.n.split(' ')[0];
|
|
if(p.id==='D1') nm='D1.'+pin.n;
|
|
use(pin.h,nm); ends[nm]=pin.h; }); });
|
|
L.jumpers.forEach(function(j,i){ use(j.a,'J'+(i+1)+'a'); use(j.b,'J'+(i+1)+'b'); var a=seg(j.a), b=seg(j.b); if(a&&b) join(a,b); });
|
|
// the Pico's GND pins are one plane inside the module
|
|
var gnds=L.parts[0].pins.filter(function(p){ return p.n==='GND'; }).map(function(p){ return seg(p.h); }).filter(Boolean);
|
|
for(var gi=1;gi<gnds.length;gi++) join(gnds[0],gnds[gi]);
|
|
L.wires.forEach(function(w,i){ var nm='P'+(i+1)+' '+w.to; use(w.h,nm); ends['P'+(i+1)]=w.h; ends[w.to]=w.h; });
|
|
Object.keys(occ).forEach(function(k){ if(occ[k].length>1) issues.push('two leads in one hole '+pos(k.split(',').map(Number))+': '+occ[k].join(' & ')); });
|
|
|
|
function net(name){ var h=ends[name]; if(!h){ issues.push('rule names an unknown end: '+name); return '?'+name; } var s=seg(h); return s?find(s):'cut'; }
|
|
function picoPin(sig){ var p=L.parts[0].pins.filter(function(x){ return x.n===sig; })[0]; return p?'pico.'+p.num+'.'+p.n:'?'; }
|
|
function same(a,b,why){ if(net(a)!==net(b)) issues.push('not connected: '+a+' and '+b+' ('+why+')'); }
|
|
function differ(a,b,why){ if(net(a)===net(b)) issues.push('shorted: '+a+' and '+b+' ('+why+')'); }
|
|
function bridge(part,a,b,why){ var n1=net(part+'.1'), n2=net(part+'.2'), A=net(a), B=net(b);
|
|
if(!((n1===A&&n2===B)||(n1===B&&n2===A))) issues.push(part+' should sit between '+a+' and '+b+' ('+why+')'); }
|
|
|
|
// --- footprints: keep-out zones ---
|
|
var pico=L.parts[0].pins, rows=pico.map(function(p){ return p.h[1]; });
|
|
var pr0=Math.min.apply(null,rows), pr1=Math.max.apply(null,rows), pc0=12, pc1=19;
|
|
L.parts.forEach(function(p){ if(p.id==='pico') return; p.pins.forEach(function(pin){
|
|
if(pin.h[1]>=pr0-1 && pin.h[1]<=pr1+1 && pin.h[0]>=pc0 && pin.h[0]<=pc1) issues.push(p.id+' lead at '+pos(pin.h)+' is inside the Pico footprint'); }); });
|
|
var u=L.parts.filter(function(p){ return p.id==='U1'; })[0].pins.map(function(p){ return p.h; });
|
|
var uc0=Math.min.apply(null,u.map(function(h){return h[0];})), uc1=Math.max.apply(null,u.map(function(h){return h[0];}));
|
|
var ur0=Math.min.apply(null,u.map(function(h){return h[1];})), ur1=Math.max.apply(null,u.map(function(h){return h[1];}));
|
|
L.parts.forEach(function(p){ if(p.id==='U1'||p.id==='pico') return; p.pins.forEach(function(pin){
|
|
if(pin.h[0]>uc0 && pin.h[0]<uc1 && pin.h[1]>=ur0 && pin.h[1]<=ur1) issues.push(p.id+' lead at '+pos(pin.h)+' is under the DIP-8 socket'); }); });
|
|
|
|
// --- the circuit ---
|
|
var VBUS=picoPin('VBUS'), V33=picoPin('3V3'), GND=picoPin('GND'), AGND=picoPin('AGND');
|
|
// rails
|
|
differ(VBUS,V33,'5 V vs 3.3 V'); differ(VBUS,GND,'5 V vs GND'); differ(V33,GND,'3.3 V vs GND');
|
|
// MIDI IN: pin 4 -> 220R -> opto anode; pin 5 -> opto cathode; 1N4148 reverse across the LED
|
|
bridge('R1','MIDI IN jack · ring (pin 4)','U1.2','MIDI IN current limit');
|
|
same('MIDI IN jack · tip (pin 5)','U1.3','MIDI IN pin 5 to opto cathode');
|
|
same('D1.K','U1.2','diode cathode on the LED anode'); same('D1.A','U1.3','diode anode on the LED cathode');
|
|
same('U1.8',VBUS,'6N137 VCC from 5 V'); same('U1.5',GND,'6N137 GND');
|
|
same('U1.6',picoPin('GP1'),'opto out to GP1 (UART0 RX)'); bridge('R2','U1.6',V33,'1k pull-up to 3.3 V');
|
|
same('U1.7',VBUS,'6N137 enable (VE) high, or the output stays off'); bridge('C1',VBUS,GND,'100 nF supply bypass, required for the 6N137');
|
|
// MIDI OUTs: TX -> 10R -> pin 5, 3V3 -> 33R -> pin 4, pin 2 -> GND
|
|
bridge('R4',picoPin('GP0'),'OUT KIT jack · tip (pin 5)','10 Ω on TX (UART0)');
|
|
bridge('R5',V33,'OUT KIT jack · ring (pin 4)','33 Ω on 3.3 V');
|
|
same('OUT KIT jack · sleeve (pin 2)',GND,'shield');
|
|
bridge('R6',picoPin('GP4'),'OUT SYNTH jack · tip (pin 5)','10 Ω on TX (UART1)');
|
|
bridge('R7',V33,'OUT SYNTH jack · ring (pin 4)','33 Ω on 3.3 V');
|
|
same('OUT SYNTH jack · sleeve (pin 2)',GND,'shield');
|
|
// panel
|
|
var SIG={'LCD · RST (pin 4)':'GP21','LCD · SCE (pin 3)':'GP17','LCD · D/C (pin 5)':'GP20','LCD · DN/MOSI (pin 6)':'GP19','LCD · SCLK (pin 7)':'GP18',
|
|
'Encoder · SIA':'GP10','Encoder · SIB':'GP11','Encoder · SW':'GP12','SEED · one lug':'GP13','RUN toggle · middle lug':'GP14',
|
|
'ENERGY pot · middle lug':'GP26','DRIFT pot · middle lug':'GP27',
|
|
'Matrix row 1 (KICK, KICK 2, SNARE)':'GP2','Matrix row 2 (HAT, HAT 16, PERC, GHOST)':'GP3','Matrix row 3 (BASS, MEL A, MEL B, PAD)':'GP5',
|
|
'Matrix column 1 (KICK, HAT, BASS)':'GP6','Matrix column 2 (KICK 2, HAT 16, MEL A)':'GP7','Matrix column 3 (SNARE, PERC, MEL B)':'GP8','Matrix column 4 (GHOST, PAD)':'GP9'};
|
|
Object.keys(SIG).forEach(function(w){ same(w,picoPin(SIG[w]),'pin map'); });
|
|
['LCD · VCC (pin 1)','LCD · LED (pin 8) through 330 Ω','Encoder · VCC','Pots · right lugs (chain both)'].forEach(function(w){ same(w,V33,'3.3 V supply'); });
|
|
['LCD · GND (pin 2)','Encoder · GND','Panel ground bus · SEED other lug, RUN one outer lug'].forEach(function(w){ same(w,GND,'ground'); });
|
|
same('Pots · left lugs (chain both)',AGND,'pots on analog ground');
|
|
// every signal net is distinct from every other
|
|
var sigs=Object.keys(SIG).map(function(w){ return picoPin(SIG[w]); }).concat([picoPin('GP0'),picoPin('GP1'),picoPin('GP4')]);
|
|
for(var i=0;i<sigs.length;i++) for(var j=i+1;j<sigs.length;j++) if(net(sigs[i])===net(sigs[j])) issues.push('shorted: '+sigs[i]+' and '+sigs[j]);
|
|
sigs.forEach(function(s){ [VBUS,V33,GND].forEach(function(r){ if(net(s)===net(r)) issues.push('shorted to a rail: '+s+' and '+r); }); });
|
|
|
|
if(process.argv.indexOf('--nets')>=0){ var nets={};
|
|
Object.keys(ends).forEach(function(n){ if(/^P\d+$/.test(n)) return; (nets[net(n)]=nets[net(n)]||[]).push(n+' @'+pos(ends[n])); });
|
|
Object.keys(nets).sort().forEach(function(k){ console.log(k); nets[k].forEach(function(x){ console.log(' '+x); }); }); }
|
|
console.log(L.parts.length+' parts, '+L.jumpers.length+' links, '+L.wires.length+' wire pads, '+L.cuts.length+' cut holes');
|
|
if(issues.length){ console.log(issues.length+' issue(s):'); issues.forEach(function(x){ console.log(' - '+x); }); process.exit(1); }
|
|
console.log('layout clean');
|