revert(boards): undo spice-driven digital inputs for AVR/RP2040/STM32
The spiceDrivenInputs change (e81450e+f4401cc) fixed plain-INPUT-wired-to-rail reads but BROKE the far more common INPUT_PULLUP + button-to-GND pattern: the internal pull-up is not modeled in the netlist, so the input floated LOW and read as permanently pressed (verified live on the stm32-bluepill-button example). Revert all the spice-driven-input changes to the pre-fix part-seed behaviour, which handles INPUT_PULLUP correctly. Proper fix (model the internal pull-up per board so BOTH patterns work) is a follow-up. Keeps the Pi LED fix.
This commit is contained in:
parent
f4401cc2dd
commit
c11c1954e1
|
|
@ -295,15 +295,6 @@ const MEGA_PORT_CONFIGS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export class AVRSimulator {
|
export class AVRSimulator {
|
||||||
// Digital input pins are driven from the SPICE solve
|
|
||||||
// (connectDigitalInputsToMcu) for nets backed by a real source/element, so
|
|
||||||
// `digitalRead()` reflects the real wiring (a pin wired to 5V reads HIGH, a
|
|
||||||
// button to 5V reads HIGH when pressed) instead of a hardcoded part seed.
|
|
||||||
// The connector skips floating nets, so event-driven parts with no SPICE
|
|
||||||
// model (rotary encoder, keypad, dialer, dip-switch, stepper) keep driving
|
|
||||||
// their pins via the part layer. Input-control parts (button / slide-switch)
|
|
||||||
// check this flag and skip their direct seed — see BasicParts.spiceDriven().
|
|
||||||
readonly spiceDrivenInputs = true;
|
|
||||||
private cpu: CPU | null = null;
|
private cpu: CPU | null = null;
|
||||||
/** Peripherals kept alive by reference so GC doesn't collect their CPU hooks */
|
/** Peripherals kept alive by reference so GC doesn't collect their CPU hooks */
|
||||||
private peripherals: unknown[] = [];
|
private peripherals: unknown[] = [];
|
||||||
|
|
|
||||||
|
|
@ -145,12 +145,6 @@ export class IdleSpinDetector {
|
||||||
export type RP2040I2CDevice = I2CDevice;
|
export type RP2040I2CDevice = I2CDevice;
|
||||||
|
|
||||||
export class RP2040Simulator {
|
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 rp2040: RP2040 | null = null;
|
||||||
private running = false;
|
private running = false;
|
||||||
private animationFrame: number | null = null;
|
private animationFrame: number | null = null;
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,6 @@ export interface ElectricalSnapshot {
|
||||||
timeWaveforms?: TimeWaveforms;
|
timeWaveforms?: TimeWaveforms;
|
||||||
/** Convergence warnings from the solver. */
|
/** Convergence warnings from the solver. */
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
/** Nets backed by a real source/element (see NetlistBuilder). Gates which
|
|
||||||
* MCU input pins connectDigitalInputsToMcu may drive from the solve. */
|
|
||||||
sourcedNets: Set<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** What the service needs from the scheduler. */
|
/** What the service needs from the scheduler. */
|
||||||
|
|
@ -133,7 +130,6 @@ export class CircuitSimulationService {
|
||||||
nets: string[];
|
nets: string[];
|
||||||
voltageSources: string[];
|
voltageSources: string[];
|
||||||
analysisKind: 'op' | 'tran' | 'ac';
|
analysisKind: 'op' | 'tran' | 'ac';
|
||||||
sourcedNets: Set<string>;
|
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
/** Set by `stop()`. Once true, `tick()` and `handleMcuEdge()`
|
/** Set by `stop()`. Once true, `tick()` and `handleMcuEdge()`
|
||||||
|
|
@ -306,7 +302,7 @@ export class CircuitSimulationService {
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
const input = buildInputFromStore(snap as Parameters<typeof buildInputFromStore>[0]);
|
const input = buildInputFromStore(snap as Parameters<typeof buildInputFromStore>[0]);
|
||||||
const { netlist, pinNetMap, nets, voltageSources, sourcedNets } = buildNetlist(input);
|
const { netlist, pinNetMap, nets, voltageSources } = buildNetlist(input);
|
||||||
|
|
||||||
// Tell the scheduler exactly which vectors we want — every net
|
// Tell the scheduler exactly which vectors we want — every net
|
||||||
// voltage + every branch current.
|
// voltage + every branch current.
|
||||||
|
|
@ -330,7 +326,6 @@ export class CircuitSimulationService {
|
||||||
nets,
|
nets,
|
||||||
voltageSources,
|
voltageSources,
|
||||||
analysisKind: input.analysis.kind,
|
analysisKind: input.analysis.kind,
|
||||||
sourcedNets,
|
|
||||||
};
|
};
|
||||||
this.publishFromLastResult();
|
this.publishFromLastResult();
|
||||||
}
|
}
|
||||||
|
|
@ -385,7 +380,6 @@ export class CircuitSimulationService {
|
||||||
analysisMode: ctx.analysisKind,
|
analysisMode: ctx.analysisKind,
|
||||||
timeWaveforms,
|
timeWaveforms,
|
||||||
warnings: result.warnings,
|
warnings: result.warnings,
|
||||||
sourcedNets: ctx.sourcedNets,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,18 +67,6 @@ export interface BuildNetlistResult {
|
||||||
* request branch currents (`i(v_<name>)`).
|
* request branch currents (`i(v_<name>)`).
|
||||||
*/
|
*/
|
||||||
voltageSources: string[];
|
voltageSources: string[];
|
||||||
/**
|
|
||||||
* Nets that are backed by a real electrical source/element — a power rail
|
|
||||||
* ('0' / 'vcc_rail'), a board GPIO V-source, an internal pull resistor, or
|
|
||||||
* any net a componentToSpice mapper emitted a card for (resistor, button
|
|
||||||
* switch, divider, etc.). Excludes purely-floating nets (which only get the
|
|
||||||
* step-7 auto-pull-down). `connectDigitalInputsToMcu` drives an MCU input pin
|
|
||||||
* from the solve ONLY when its net is in here, so event-driven parts with no
|
|
||||||
* SPICE model (rotary encoder, keypad, dialer, dip-switch, stepper) keep
|
|
||||||
* driving their own pins via the part layer instead of being forced LOW by a
|
|
||||||
* floating-net read.
|
|
||||||
*/
|
|
||||||
sourcedNets: Set<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
||||||
|
|
@ -150,11 +138,6 @@ export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
||||||
const cards: string[] = [];
|
const cards: string[] = [];
|
||||||
const modelLines = new Set<string>();
|
const modelLines = new Set<string>();
|
||||||
const dominantVcc = boards[0]?.vcc ?? 5;
|
const dominantVcc = boards[0]?.vcc ?? 5;
|
||||||
// Nets backed by a real source/element (see BuildNetlistResult.sourcedNets).
|
|
||||||
// Rails are always sourced; component + GPIO-source + pull nets are added
|
|
||||||
// below. Deliberately NOT populated from the step-7 auto-pull-down cards,
|
|
||||||
// since those mark FLOATING nets.
|
|
||||||
const sourcedNets = new Set<string>(['0', 'vcc_rail']);
|
|
||||||
|
|
||||||
for (const comp of components) {
|
for (const comp of components) {
|
||||||
const localLookup = (pinName: string) => netLookup(comp.id, pinName);
|
const localLookup = (pinName: string) => netLookup(comp.id, pinName);
|
||||||
|
|
@ -162,11 +145,6 @@ export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
||||||
if (!emission) continue;
|
if (!emission) continue;
|
||||||
cards.push(...emission.cards);
|
cards.push(...emission.cards);
|
||||||
for (const m of emission.modelsUsed) modelLines.add(m);
|
for (const m of emission.modelsUsed) modelLines.add(m);
|
||||||
// Every net this component connects to now has a real SPICE element on it.
|
|
||||||
for (const pinName of pinsReferencedByWires(comp.id, wires)) {
|
|
||||||
const n = netLookup(comp.id, pinName);
|
|
||||||
if (n) sourcedNets.add(n);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 5. Board GPIO sources ─────────────────────────────────────────────────
|
// ── 5. Board GPIO sources ─────────────────────────────────────────────────
|
||||||
|
|
@ -196,7 +174,6 @@ export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
||||||
cards.push(
|
cards.push(
|
||||||
`R_pull_${sanitizeSpiceId(board.id)}_${sanitizeSpiceId(pinName)} ${net} ${rail} 45000`,
|
`R_pull_${sanitizeSpiceId(board.id)}_${sanitizeSpiceId(pinName)} ${net} ${rail} 45000`,
|
||||||
);
|
);
|
||||||
sourcedNets.add(net); // weak pull to a rail → determinate idle level
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +181,6 @@ export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
||||||
if (net === '0' || net === 'vcc_rail') continue; // already served
|
if (net === '0' || net === 'vcc_rail') continue; // already served
|
||||||
const v = state.type === 'digital' ? state.v : state.duty * board.vcc;
|
const v = state.type === 'digital' ? state.v : state.duty * board.vcc;
|
||||||
cards.push(`V_${sanitizeSpiceId(board.id)}_${sanitizeSpiceId(pinName)} ${net} 0 DC ${v}`);
|
cards.push(`V_${sanitizeSpiceId(board.id)}_${sanitizeSpiceId(pinName)} ${net} 0 DC ${v}`);
|
||||||
sourcedNets.add(net); // board GPIO V-source drives this net (e.g. cross-board input)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,8 +203,6 @@ export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
||||||
const b = netLookup(w.end.componentId, w.end.pinName);
|
const b = netLookup(w.end.componentId, w.end.pinName);
|
||||||
if (!a || !b) continue;
|
if (!a || !b) continue;
|
||||||
cards.push(`R_wire_${w.id} ${a} ${b} ${ohms}`);
|
cards.push(`R_wire_${w.id} ${a} ${b} ${ohms}`);
|
||||||
sourcedNets.add(a);
|
|
||||||
sourcedNets.add(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 7. Auto pull-downs for floating nets ─────────────────────────────────
|
// ── 7. Auto pull-downs for floating nets ─────────────────────────────────
|
||||||
|
|
@ -305,7 +279,6 @@ export function buildNetlist(input: BuildNetlistInput): BuildNetlistResult {
|
||||||
pinNetMap,
|
pinNetMap,
|
||||||
nets,
|
nets,
|
||||||
voltageSources,
|
voltageSources,
|
||||||
sourcedNets,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@
|
||||||
*/
|
*/
|
||||||
import { useSimulatorStore, getBoardSimulator, getBoardPinManager } from '../../store/useSimulatorStore';
|
import { useSimulatorStore, getBoardSimulator, getBoardPinManager } from '../../store/useSimulatorStore';
|
||||||
import { useElectricalStore } from '../../store/useElectricalStore';
|
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
|
// 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
|
// midpoint doesn't chatter. A pulled-up idle input sits at ~3.3 V and a
|
||||||
|
|
@ -45,7 +43,7 @@ export function connectDigitalInputsToMcu(): () => void {
|
||||||
const lastLevel = new Map<string, boolean>();
|
const lastLevel = new Map<string, boolean>();
|
||||||
|
|
||||||
function injectDigitalInputs() {
|
function injectDigitalInputs() {
|
||||||
const { nodeVoltages, pinNetMap, sourcedNets } = useElectricalStore.getState();
|
const { nodeVoltages, pinNetMap } = useElectricalStore.getState();
|
||||||
const { boards } = useSimulatorStore.getState();
|
const { boards } = useSimulatorStore.getState();
|
||||||
for (const board of boards) {
|
for (const board of boards) {
|
||||||
const sim = getBoardSimulator(board.id) as
|
const sim = getBoardSimulator(board.id) as
|
||||||
|
|
@ -55,23 +53,11 @@ export function connectDigitalInputsToMcu(): () => void {
|
||||||
const pm = getBoardPinManager(board.id);
|
const pm = getBoardPinManager(board.id);
|
||||||
const driven = pm ? pm.getOutputPins() : new Set<number>();
|
const driven = pm ? pm.getOutputPins() : new Set<number>();
|
||||||
const prefix = `${board.id}:`;
|
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) {
|
for (const [key, net] of pinNetMap) {
|
||||||
if (!key.startsWith(prefix)) continue;
|
if (!key.startsWith(prefix)) continue;
|
||||||
const pinName = key.slice(prefix.length);
|
const gpio = gpioFromPinName(key.slice(prefix.length));
|
||||||
const gpio = isStm32 ? stm32PinNameToLinear(pinName) : gpioFromPinName(pinName);
|
|
||||||
if (gpio < 0) continue;
|
if (gpio < 0) continue;
|
||||||
if (driven.has(gpio)) continue; // the MCU drives this pin (digitalWrite)
|
if (driven.has(gpio)) continue; // the MCU drives this pin (digitalWrite)
|
||||||
// Only drive pins whose net is backed by a real source/element (rail,
|
|
||||||
// pull, button switch, divider, cross-board output, …). A net that is
|
|
||||||
// only floating (an event-driven part like a rotary encoder / keypad
|
|
||||||
// that has no SPICE model) is left to the part layer, which seeds the
|
|
||||||
// pin directly — otherwise its ~0 V floating read would force it LOW
|
|
||||||
// and fight the part. This is what makes it safe to enable
|
|
||||||
// spiceDrivenInputs on the AVR (which has many such part-driven pins).
|
|
||||||
if (!sourcedNets.has(net)) continue;
|
|
||||||
const v = nodeVoltages[net];
|
const v = nodeVoltages[net];
|
||||||
if (v == null) continue;
|
if (v == null) continue;
|
||||||
const stateKey = `${board.id}:${gpio}`;
|
const stateKey = `${board.id}:${gpio}`;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ function createElectricalStorePort(): ElectricalStorePort {
|
||||||
error: snapshot.warnings[0] ?? null,
|
error: snapshot.warnings[0] ?? null,
|
||||||
lastSolveMs: 0,
|
lastSolveMs: 0,
|
||||||
submittedNetlist: '',
|
submittedNetlist: '',
|
||||||
sourcedNets: snapshot.sourcedNets,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,6 @@ export interface ElectricalSnapshot {
|
||||||
error: string | null;
|
error: string | null;
|
||||||
lastSolveMs: number;
|
lastSolveMs: number;
|
||||||
submittedNetlist: string;
|
submittedNetlist: string;
|
||||||
/** Nets backed by a real source/element (rail, GPIO V-source, pull, or any
|
|
||||||
* component card). connectDigitalInputsToMcu only drives MCU input pins
|
|
||||||
* whose net is here, so floating event-part pins aren't forced LOW. */
|
|
||||||
sourcedNets: Set<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ElectricalState extends ElectricalSnapshot {
|
interface ElectricalState extends ElectricalSnapshot {
|
||||||
|
|
@ -55,7 +51,6 @@ const EMPTY: ElectricalSnapshot = {
|
||||||
error: null,
|
error: null,
|
||||||
lastSolveMs: 0,
|
lastSolveMs: 0,
|
||||||
submittedNetlist: '',
|
submittedNetlist: '',
|
||||||
sourcedNets: new Set(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useElectricalStore = create<ElectricalState>((set) => ({
|
export const useElectricalStore = create<ElectricalState>((set) => ({
|
||||||
|
|
|
||||||
|
|
@ -600,12 +600,6 @@ function makePinPullHandler(boardId: string) {
|
||||||
// minus the ESP32-only WiFi / proxy-resync machinery. ──────────────────────
|
// minus the ESP32-only WiFi / proxy-resync machinery. ──────────────────────
|
||||||
class Stm32BridgeShim {
|
class Stm32BridgeShim {
|
||||||
pinManager: PinManager;
|
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;
|
onSerialData: ((ch: string) => void) | null = null;
|
||||||
onPinChangeWithTime: ((pin: number, state: boolean, timeMs: number) => void) | null = null;
|
onPinChangeWithTime: ((pin: number, state: boolean, timeMs: number) => void) | null = null;
|
||||||
onBaudRateChange: ((baud: number) => void) | null = null;
|
onBaudRateChange: ((baud: number) => void) | null = null;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue