fix(spice): model pushbutton as a real 4-pin tactile switch
The pushbutton was modelled as a switch between only 1.l and 2.l; the other two legs (1.r, 2.r) connected to nothing. Wiring GND/GPIO to those legs silently produced a dead button, and the failure was invisible. Model it like hardware: 1.l is internally shorted to 1.r and 2.l to 2.r, and pressing bridges terminal 1 to terminal 2. Wiring to any leg now works, and putting GPIO and GND on the same terminal is a dead short, exactly as on a real tactile switch. Back-compat A/B variant preserved.
This commit is contained in:
parent
03339a132a
commit
d8b6c77335
|
|
@ -92,8 +92,8 @@ exports[`netlist snapshot — 100-days (49 examples) > 100d-esp32-ir-sensor-tele
|
|||
|
||||
exports[`netlist snapshot — 100-days (49 examples) > 100d-esp32-oled-smart-ui-eyes-animation-time-and-weather-micropython 1`] = `
|
||||
"* Velxio circuit
|
||||
R_btn-next n4 n0 1000000000
|
||||
R_btn-sel n4 n1 1000000000
|
||||
R_btn-next_sw n4 n0 1000000000
|
||||
R_btn-sel_sw n4 n1 1000000000
|
||||
R_autopull_n0 n0 0 100Meg
|
||||
R_autopull_n1 n1 0 100Meg
|
||||
R_autopull_n2 n2 0 100Meg
|
||||
|
|
@ -1166,9 +1166,9 @@ R_autopull_n6 n6 0 100Meg
|
|||
|
||||
exports[`netlist snapshot — circuits (41 examples) > full-adder 1`] = `
|
||||
"* Velxio circuit
|
||||
R_bA n0 n5 1000000000
|
||||
R_bB n1 n5 1000000000
|
||||
R_bCin n2 n5 1000000000
|
||||
R_bA_sw n0 n5 1000000000
|
||||
R_bB_sw n1 n5 1000000000
|
||||
R_bCin_sw n2 n5 1000000000
|
||||
R_rSum n3 n7 220
|
||||
R_rCout n4 n6 220
|
||||
V_sumLed_sense n7 sumLed_sense_mid DC 0
|
||||
|
|
@ -1393,8 +1393,8 @@ R_autopull_n10 n10 0 100Meg
|
|||
|
||||
exports[`netlist snapshot — circuits (41 examples) > nand-sr-latch 1`] = `
|
||||
"* Velxio circuit
|
||||
R_setBtn n5 n6 1000000000
|
||||
R_rstBtn n4 n6 1000000000
|
||||
R_setBtn_sw n5 n6 1000000000
|
||||
R_rstBtn_sw n4 n6 1000000000
|
||||
R_rpuS n0 n5 10000
|
||||
R_rpuR n0 n4 10000
|
||||
B_g1 n1 0 V = 5 * (1 - u(V(n5)-2.5) * u(V(n2)-2.5))
|
||||
|
|
@ -1905,8 +1905,8 @@ R_autopull_n3 n3 0 100Meg
|
|||
|
||||
exports[`netlist snapshot — circuits (41 examples) > xor-toggle-detector 1`] = `
|
||||
"* Velxio circuit
|
||||
R_sw1 n0 n5 1000000000
|
||||
R_sw2 n1 n5 1000000000
|
||||
R_sw1_sw n0 n5 1000000000
|
||||
R_sw2_sw n1 n5 1000000000
|
||||
B_u1 n6 0 V = 5 * (u(V(n2)-2.5) + u(V(n3)-2.5) - 2*u(V(n2)-2.5)*u(V(n3)-2.5))
|
||||
R_u1_load n6 0 1Meg
|
||||
R_rl n6 n4 220
|
||||
|
|
|
|||
|
|
@ -222,6 +222,49 @@ describe('PASSIVE_PRESETS — preset variants share their base mapper', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('pushbutton — 4-pin tactile model', () => {
|
||||
const lookup = (wired: Record<string, string>) => (pin: string) => wired[pin] ?? null;
|
||||
const emit = (wired: Record<string, string>, pressed = false) =>
|
||||
componentToSpice(
|
||||
{ id: 'b1', metadataId: 'pushbutton', properties: { pressed } },
|
||||
lookup(wired),
|
||||
{ vcc: 3.3 },
|
||||
);
|
||||
|
||||
it('switches between terminal 1 and 2 wired on the .l legs', () => {
|
||||
const e = emit({ '1.l': 'gpio', '2.l': 'gnd' });
|
||||
expect(e!.cards).toEqual(['R_b1_sw gpio gnd 1000000000']);
|
||||
});
|
||||
|
||||
it('works wired on the .r legs too (terminal legs are interchangeable)', () => {
|
||||
const e = emit({ '1.r': 'gpio', '2.r': 'gnd' });
|
||||
expect(e!.cards).toEqual(['R_b1_sw gpio gnd 1000000000']);
|
||||
});
|
||||
|
||||
it('pressing closes the switch (low resistance)', () => {
|
||||
const e = emit({ '1.l': 'gpio', '2.l': 'gnd' }, true);
|
||||
expect(e!.cards).toEqual(['R_b1_sw gpio gnd 0.01']);
|
||||
});
|
||||
|
||||
it('GPIO and GND on the SAME terminal is a dead short, like real hardware', () => {
|
||||
// The classic miswire: GND lands on 1.r while the GPIO is on 1.l. Both legs
|
||||
// belong to terminal 1, so they are internally shorted — the pin can never
|
||||
// read anything but the GND it is tied to, and there is no switch path.
|
||||
const e = emit({ '1.l': 'gpio', '1.r': 'gnd' });
|
||||
expect(e!.cards).toEqual(['R_b1_t1 gpio gnd 0.01']);
|
||||
expect(e!.cards.some((c) => c.includes('_sw'))).toBe(false);
|
||||
});
|
||||
|
||||
it('back-compat: 2-pin A/B variant still emits a switch', () => {
|
||||
const e = emit({ A: 'n1', B: 'n2' });
|
||||
expect(e!.cards).toEqual(['R_b1_sw n1 n2 1000000000']);
|
||||
});
|
||||
|
||||
it('returns null when nothing is wired', () => {
|
||||
expect(emit({})).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// Mappers whose output depends on live runtime state rather than the static
|
||||
// component (pins + properties) a fixture can describe. `custom-chip` emits its
|
||||
// SPICE sources from getChipDrivenPins(comp.id) — the chip's currently-driven
|
||||
|
|
|
|||
|
|
@ -639,14 +639,29 @@ const MAPPERS: Record<string, Mapper> = {
|
|||
};
|
||||
},
|
||||
|
||||
// Switch / pushbutton
|
||||
// Switch / pushbutton — a real 4-pin tactile switch. The two legs of each
|
||||
// terminal are internally shorted (1.l is the same node as 1.r; 2.l as 2.r),
|
||||
// and pressing bridges terminal 1 to terminal 2. Modelling BOTH legs (not
|
||||
// just 1.l/2.l) means a user can wire GND/GPIO to any leg and it behaves like
|
||||
// hardware — and wiring both a GPIO and GND to the SAME terminal is a dead
|
||||
// short, exactly as on a real button. Back-compat: 2-pin variants expose A/B.
|
||||
pushbutton: (comp, netLookup) => {
|
||||
const pins = twoPin(comp, netLookup, '1.l', '2.l');
|
||||
const alt = pins ?? twoPin(comp, netLookup, 'A', 'B');
|
||||
if (!alt) return null;
|
||||
const pressed = Boolean(comp.properties.pressed);
|
||||
const R = pressed ? 0.01 : 1e9;
|
||||
return emitResistor(comp, alt, R);
|
||||
const t1l = netLookup('1.l');
|
||||
const t1r = netLookup('1.r');
|
||||
const t2l = netLookup('2.l');
|
||||
const t2r = netLookup('2.r');
|
||||
const cards: string[] = [];
|
||||
// Internal shorts between the two legs of a terminal, when both are wired.
|
||||
if (t1l && t1r && t1l !== t1r) cards.push(`R_${comp.id}_t1 ${t1l} ${t1r} 0.01`);
|
||||
if (t2l && t2r && t2l !== t2r) cards.push(`R_${comp.id}_t2 ${t2l} ${t2r} 0.01`);
|
||||
// The switch itself: terminal 1 to terminal 2 (prefer the .l leg's net).
|
||||
const term1 = t1l ?? t1r ?? netLookup('A');
|
||||
const term2 = t2l ?? t2r ?? netLookup('B');
|
||||
if (term1 && term2) {
|
||||
const R = Boolean(comp.properties.pressed) ? 0.01 : 1e9;
|
||||
cards.push(`R_${comp.id}_sw ${term1} ${term2} ${R}`);
|
||||
}
|
||||
return cards.length ? { cards, modelsUsed: new Set() } : null;
|
||||
},
|
||||
'slide-switch': (comp, netLookup) => {
|
||||
const pins = twoPin(comp, netLookup, '1', '2');
|
||||
|
|
|
|||
Loading…
Reference in New Issue