fix(spice): always emit V-source per wired GPIO pin (root cause of intermittent dark-LED)

Bug reproduced via CDP probe across 5 Run/Stop cycles: cycle 1
worked (LED toggled), cycles 2-5 LED stayed dark — but exactly the
same code, same canvas, same circuit.

Tracing the live electrical store via __spiceDebug() showed:
  cycle-1 after-run: branchCurrentCount=3 (pin13 V-source present)
  cycle-2 after-run: branchCurrentCount=2 (pin13 V-source MISSING)
  cycle-3..5 after-run: branchCurrentCount=2

The flow:
  1. User clicks Run -> board.boards reference changes -> service ticks.
  2. runSolve calls collectPinStates(board, ...) to snapshot output pins.
  3. collectPinStates was emitting an entry ONLY when pinManager.getPinState(pin)
     was currently TRUE. If the pin was LOW at that exact instant
     (which is most of the time for a Blink sketch — 50% duty), no
     pinStates entry, no V-source card in the netlist.
  4. AVR runs, digitalWrite(13, HIGH) fires, handleMcuEdge calls
     scheduler.onMcuPinChange -> solver.alterSource('V_arduino-uno_13', 5).
  5. ngspice gets 'alter V_arduino-uno_13 dc 5' but that V-source
     doesn't exist in the deck. Silent no-op. branchCurrents never
     updates. LED stays dark forever.

The 'sometimes it works' impression came from cycle 1: the cold-boot
AVR happened to land on a HIGH state precisely when the tick fired,
so the V-source got emitted and every subsequent edge alter worked.
The other cycles caught the AVR in LOW.

Fix: always emit a digital PinSourceState — with v=0 when LOW, v=vcc
when HIGH — so the NetlistBuilder always produces V_<board>_<pin>
cards for every wired GPIO. alterSource then has a target to bind
to no matter what state the pin was in at solve time.

Verified live via CDP probe (_probe_blink.mjs in working tree):
  pin13 toggles 0V<->5V at the Blink frequency
  LED anode follows at 0V<->1.838V (matches manual calculation:
    (5 - 1.84) / 220 = 14.4 mA forward current through the red LED)
  branchCurrentCount = 3 stable across all cycles
This commit is contained in:
David Montero Crespo 2026-05-18 13:32:20 -03:00
parent 683a7f31e6
commit 7ce184c4c6
1 changed files with 19 additions and 2 deletions

View File

@ -72,8 +72,25 @@ export function collectPinStates(
const pwmDuty = pm.getPwmValue(arduinoPin);
if (pwmDuty > 0) {
result[pinName] = { type: 'pwm', duty: pwmDuty };
} else if (pm.getPinState(arduinoPin)) {
result[pinName] = { type: 'digital', v: vcc };
} else {
// ALWAYS emit a digital source — even when the pin is currently
// LOW. NetlistBuilder turns this into a `V_<board>_<pin>` card,
// and MixedModeScheduler.onMcuPinChange later calls
// `alterSource('V_<board>_<pin>', vcc | 0)` on every MCU edge.
// If the V-source isn't in the netlist (because we skipped it
// here while LOW), the alter is a silent no-op against a name
// that doesn't exist; the LED then never lights up no matter
// how many times the sketch toggles the pin afterwards.
//
// This was the root cause of the reported "sometimes the LED
// works after Run, sometimes it doesn't" symptom. The behaviour
// was deterministic but appeared random because it depended on
// whether the AVR happened to be in the HIGH half of its blink
// cycle at the moment runSolve() captured pin states.
result[pinName] = {
type: 'digital',
v: pm.getPinState(arduinoPin) ? vcc : 0,
};
}
}
return result;