diff --git a/frontend/src/simulation/RP2040Simulator.ts b/frontend/src/simulation/RP2040Simulator.ts index 8a666b84..dd84a0c2 100644 --- a/frontend/src/simulation/RP2040Simulator.ts +++ b/frontend/src/simulation/RP2040Simulator.ts @@ -145,6 +145,12 @@ export class IdleSpinDetector { export type RP2040I2CDevice = I2CDevice; export class RP2040Simulator { + // Digital inputs are driven from the SPICE solve (connectDigitalInputsToMcu) + // for source-backed nets, so digitalRead reflects real wiring (a GP pin wired + // to a rail / button reads the right level). Floating nets are left to the + // part layer, so event-driven parts (encoder/keypad/…) keep working. Mirrors + // the AVR fix; the connector + sourcedNets gating are board-agnostic. + readonly spiceDrivenInputs = true; private rp2040: RP2040 | null = null; private running = false; private animationFrame: number | null = null; diff --git a/frontend/src/simulation/spice/connectDigitalInputsToMcu.ts b/frontend/src/simulation/spice/connectDigitalInputsToMcu.ts index cd8f5f84..843a02e2 100644 --- a/frontend/src/simulation/spice/connectDigitalInputsToMcu.ts +++ b/frontend/src/simulation/spice/connectDigitalInputsToMcu.ts @@ -21,6 +21,8 @@ */ import { useSimulatorStore, getBoardSimulator, getBoardPinManager } from '../../store/useSimulatorStore'; import { useElectricalStore } from '../../store/useElectricalStore'; +import { isStm32BoardKind } from '../../types/board'; +import { stm32PinNameToLinear } from '../Stm32Bridge'; // 3.3 V LVCMOS thresholds with a hysteresis band so a node hovering near the // midpoint doesn't chatter. A pulled-up idle input sits at ~3.3 V and a @@ -53,9 +55,13 @@ export function connectDigitalInputsToMcu(): () => void { const pm = getBoardPinManager(board.id); const driven = pm ? pm.getOutputPins() : new Set(); const prefix = `${board.id}:`; + // STM32 names pins PA0/PC13/… and its PinManager + setPinState key on the + // linear pin (port*16+pin); every other board uses plain GPIO numbers. + const isStm32 = isStm32BoardKind(board.boardKind); for (const [key, net] of pinNetMap) { if (!key.startsWith(prefix)) continue; - const gpio = gpioFromPinName(key.slice(prefix.length)); + const pinName = key.slice(prefix.length); + const gpio = isStm32 ? stm32PinNameToLinear(pinName) : gpioFromPinName(pinName); if (gpio < 0) continue; if (driven.has(gpio)) continue; // the MCU drives this pin (digitalWrite) // Only drive pins whose net is backed by a real source/element (rail, diff --git a/frontend/src/store/useSimulatorStore.ts b/frontend/src/store/useSimulatorStore.ts index ff31dba4..3d9be4d1 100644 --- a/frontend/src/store/useSimulatorStore.ts +++ b/frontend/src/store/useSimulatorStore.ts @@ -600,6 +600,12 @@ function makePinPullHandler(boardId: string) { // minus the ESP32-only WiFi / proxy-resync machinery. ────────────────────── class Stm32BridgeShim { pinManager: PinManager; + // Digital inputs are driven from the SPICE solve (connectDigitalInputsToMcu) + // for source-backed nets — digitalRead reflects the real wiring instead of a + // part seed. The connector maps STM32 pin names (PA0, PC13) to the linear pin + // setPinState expects via stm32PinNameToLinear. Floating nets stay with the + // part layer. Mirrors the AVR / RP2040 fix. + readonly spiceDrivenInputs = true; onSerialData: ((ch: string) => void) | null = null; onPinChangeWithTime: ((pin: number, state: boolean, timeMs: number) => void) | null = null; onBaudRateChange: ((baud: number) => void) | null = null;