fix(sim): re-solve SPICE on ESP32/STM32/Pi output pin edges

WebSocket-backed boards (ESP32, STM32, Raspberry Pi) reach the electrical
simulation only through PinManager.triggerPinChange, which updated the pin
state + notified listeners but never requested an electrical re-solve. AVR
and RP2040 already resolve at their own toggle sites. As a result an analog
part on an MCU-driven net (e.g. a resistor-less LED whose brightness comes
from the SPICE solve) stayed at its first solved value until unrelated
activity (such as serial output) forced a solve — so an ESP32 blink with no
Serial in loop() left the LED stuck on.

Request an electrical re-solve after an 'mcu'-sourced pin edge, in one place
(triggerPinChange), covering all WS boards. Gated to source==='mcu' so the
solver's own input feedback (triggerPinChange with the default 'external'
source) can't loop; requestElectricalResolve coalesces overlapping ticks so
a per-edge call is cheap.
This commit is contained in:
David Montero 2026-07-02 22:33:46 +02:00
parent 6ccc090f0c
commit a1137f1929
1 changed files with 12 additions and 0 deletions

View File

@ -16,6 +16,8 @@
* - PWM duty cycle tracking (for servos, RGB LEDs, buzzers)
*/
import { requestElectricalResolve } from './spice/electricalResolveHook';
export type PinState = boolean;
export type PinChangeCallback = (pin: number, state: PinState) => void;
export type AnalogCallback = (pin: number, voltage: number) => void;
@ -155,6 +157,16 @@ export class PinManager {
if (callbacks) {
callbacks.forEach((cb) => cb(pin, state));
}
// An MCU output edge changes the circuit: request a SPICE re-solve so the
// analog parts on this net (LED brightness, etc.) update. WS-backed boards
// (ESP32 / STM32 / Raspberry Pi) reach the electrical sim ONLY through here
// — previously they never triggered a re-solve, so a resistor-less LED
// stayed at its first solved brightness until unrelated activity (e.g.
// serial output) forced a solve. AVR / RP2040 already resolve at their own
// toggle sites. Gated to 'mcu' so the solver's own input feedback
// (triggerPinChange with the default 'external' source) can't create a
// solve loop; the hook coalesces overlapping ticks so per-edge is cheap.
if (source === 'mcu') requestElectricalResolve();
}
/** Pins the MCU has actively driven this session. */