fix(spice): every overlay board was solved as a 5 V Arduino
BOARD_PIN_GROUPS is keyed by the BoardKind union, so a board registered by the overlay could never appear in it and fell through to `default` — 5 V, ground pads named GND/GND.1/GND.2, supply pads named 5V/VCC. Every RP2350 and ESP32-family pro board is a 3.3 V part whose supply pad is called 3V3, so the netlist powered it from the wrong rail and did not recognise its supply pad at all. Nothing errors; the numbers are simply wrong. Caught on the Pimoroni Pico Plus 2 W: a potentiometer across 3V3 and GND read 2.50 V at half travel instead of 1.65 V, because the divider was solving against a 5 V rail. The same 1.5x error was sitting under every LED current, pull-up and divider on every pro board. boardPinGroupFor() consults the pro registry before falling back, and a board declares its pads through ProBoardDef.power. The OSS table is untouched, so no existing board changes behaviour. Found while wiring the ADC seam: enabling the SPICE analog path for a pro board is what finally made the wrong rail visible.
This commit is contained in:
parent
6e543b5877
commit
0ef656849d
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* A pro board's supply rail.
|
||||
*
|
||||
* BOARD_PIN_GROUPS is keyed by the BoardKind union, so an overlay board could
|
||||
* never appear in it and fell through to `default`: 5 V, with supply pads named
|
||||
* "5V"/"VCC". Every RP2350 and ESP32-family board the overlay registers is a
|
||||
* 3.3 V part whose pad is called "3V3", so the whole netlist solved at the wrong
|
||||
* rail — silently. It surfaces as soon as anything analog is wired: a divider
|
||||
* off the 3V3 pad of a Pimoroni Pico Plus 2 W solved at 5 V, and a potentiometer
|
||||
* at half travel read 2.50 V where the hardware gives 1.65 V.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { registerProBoards, type ProBoardDef } from '../lib/proBoardRegistry';
|
||||
import { boardPinGroupFor, BOARD_PIN_GROUPS } from '../simulation/spice/boardPinGroups';
|
||||
|
||||
const KIND = 'test-overlay-3v3-board';
|
||||
|
||||
describe('boardPinGroupFor', () => {
|
||||
beforeEach(() => {
|
||||
registerProBoards([
|
||||
{
|
||||
kind: KIND,
|
||||
label: '3V3 overlay board',
|
||||
fqbn: null,
|
||||
description: 'test',
|
||||
tag: 'velxio-test-3v3',
|
||||
size: { w: 10, h: 10 },
|
||||
power: {
|
||||
vcc: 3.3,
|
||||
gnd: ['GND', 'GND.1'],
|
||||
vcc_pins: ['3V3'],
|
||||
aux: { volts: 5, pins: ['VBUS', 'VSYS'] },
|
||||
},
|
||||
} as ProBoardDef,
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses the rail a pro board declares, not the 5 V default', () => {
|
||||
const g = boardPinGroupFor(KIND);
|
||||
expect(g.vcc).toBe(3.3);
|
||||
expect(g.vcc_pins).toContain('3V3');
|
||||
expect(g.aux?.volts).toBe(5);
|
||||
});
|
||||
|
||||
it('still answers for the boards the OSS table names', () => {
|
||||
expect(boardPinGroupFor('raspberry-pi-pico').vcc).toBe(3.3);
|
||||
expect(boardPinGroupFor('arduino-uno').vcc).toBe(5);
|
||||
});
|
||||
|
||||
it('falls back to the default for a board nobody has described', () => {
|
||||
expect(boardPinGroupFor('some-unregistered-kind')).toBe(BOARD_PIN_GROUPS.default);
|
||||
});
|
||||
});
|
||||
|
|
@ -99,6 +99,25 @@ export interface ProBoardDef {
|
|||
* capable GP40/41/42 through 1k) is declared under the name a wire uses.
|
||||
*/
|
||||
adcPins?: Array<{ pinName: string; channel: number }>;
|
||||
/**
|
||||
* Supply pads and logic voltage, for the SPICE netlist — the overlay's
|
||||
* version of BOARD_PIN_GROUPS, which is keyed by the BoardKind union and so
|
||||
* can never name a pro board.
|
||||
*
|
||||
* Without it a board falls back to that table's `default`: 5 V, ground pads
|
||||
* called GND/GND.1/GND.2, supply pads called 5V/VCC. For a 3.3 V board with a
|
||||
* "3V3" pad that is wrong twice over, and silently — the rail is simply
|
||||
* solved at 5 V, so every divider, pull-up and LED current on the board is
|
||||
* off by 1.5x.
|
||||
*
|
||||
* Shape matches BoardPinGroup: { vcc, gnd[], vcc_pins[], aux? }.
|
||||
*/
|
||||
power?: {
|
||||
vcc: number;
|
||||
gnd: string[];
|
||||
vcc_pins: string[];
|
||||
aux?: { volts: number; pins: string[] };
|
||||
};
|
||||
/** In-browser simulator factory (e.g. the RP2350/Hazard3 emulator). The pm
|
||||
* argument is the store's PinManager instance. */
|
||||
createSimulator?: (pm: unknown) => ProBoardSimulator;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* Extend this table as new boards are added.
|
||||
*/
|
||||
import type { BoardKind } from '../../types/board';
|
||||
import { getProBoard } from '../../lib/proBoardRegistry';
|
||||
|
||||
export interface BoardPinGroup {
|
||||
/** Supply voltage (V). */
|
||||
|
|
@ -155,3 +156,24 @@ export const BOARD_PIN_GROUPS: Record<AllBoardKinds, BoardPinGroup> = {
|
|||
aux: { volts: 5, pins: ['5V'] },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The pin classification for a board, including boards this table cannot name.
|
||||
*
|
||||
* BOARD_PIN_GROUPS is keyed by the BoardKind union, so an overlay board — whose
|
||||
* kind is a runtime string — could never appear in it and fell through to
|
||||
* `default`: a 5 V part whose only supply pad is called "5V". Every RP2350 and
|
||||
* ESP32-family board registered by the overlay was therefore solved at the
|
||||
* wrong rail. It shows up the moment anything analog is wired: a divider off
|
||||
* the 3V3 pad of a Pimoroni Pico Plus 2 W solved at 5 V, so a potentiometer at
|
||||
* half travel read 2.50 V instead of 1.65 V.
|
||||
*
|
||||
* A pro board declares its own supply pads through ProBoardDef.power.
|
||||
*/
|
||||
export function boardPinGroupFor(kind: string): BoardPinGroup {
|
||||
const known = BOARD_PIN_GROUPS[kind as AllBoardKinds];
|
||||
if (known) return known;
|
||||
const pro = getProBoard(kind)?.power;
|
||||
if (pro) return pro;
|
||||
return BOARD_PIN_GROUPS.default;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import { stm32LinearToPinName, stm32PinNameToLinear } from '../Stm32Bridge';
|
|||
import { isStm32BoardKind, isPiBoardKind } from '../../types/board';
|
||||
import type { BoardKind } from '../../types/board';
|
||||
import { useElectricalStore } from '../../store/useElectricalStore';
|
||||
import { BOARD_PIN_GROUPS } from './boardPinGroups';
|
||||
import { boardPinGroupFor } from './boardPinGroups';
|
||||
import { pinNameToArduinoPin } from './collectPinStates';
|
||||
import type { CircuitSimulationService } from './CircuitSimulationService';
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ export function connectMcuEdgesToService(service: CircuitSimulationService): ()
|
|||
function subscribeBoard(boardId: string, boardKind: string): void {
|
||||
const pm = getBoardPinManager(boardId);
|
||||
if (!pm) return;
|
||||
const group = BOARD_PIN_GROUPS[boardKind as keyof typeof BOARD_PIN_GROUPS] ?? BOARD_PIN_GROUPS.default;
|
||||
const group = boardPinGroupFor(boardKind);
|
||||
const vcc = group.vcc;
|
||||
|
||||
const pinSubs = new Map<number, () => void>();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import type {
|
|||
} from './types';
|
||||
import type { Wire } from '../../types/wire';
|
||||
import type { BoardKind } from '../../types/board';
|
||||
import { BOARD_PIN_GROUPS } from './boardPinGroups';
|
||||
import { boardPinGroupFor } from './boardPinGroups';
|
||||
import { parseValueWithUnits } from './valueParser';
|
||||
import { PASSIVE_PRESETS } from './componentToSpice';
|
||||
|
||||
|
|
@ -182,7 +182,7 @@ export function buildInputFromStore(snap: StoreSnapshot): BuildNetlistInput {
|
|||
}));
|
||||
|
||||
const boards: BoardForSpice[] = snap.boards.map((b) => {
|
||||
const group = BOARD_PIN_GROUPS[b.boardKind] ?? BOARD_PIN_GROUPS.default;
|
||||
const group = boardPinGroupFor(b.boardKind);
|
||||
return {
|
||||
id: b.id,
|
||||
boardKind: b.boardKind,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
import { useSimulatorStore } from '../../store/useSimulatorStore';
|
||||
import { buildInputFromStore } from '../spice/storeAdapter';
|
||||
import { BOARD_PIN_GROUPS } from '../spice/boardPinGroups';
|
||||
import { boardPinGroupFor } from '../spice/boardPinGroups';
|
||||
import type { PinSourceState } from '../spice/types';
|
||||
import { verifyCircuit, type VerificationResult } from './circuitVerifier';
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export async function verifyCircuitFromStore(): Promise<VerificationResult | nul
|
|||
// current and doesn't trip overcurrent / overpower. A circuit
|
||||
// that would actually fault under HIGH is flagged correctly.
|
||||
const pinStates: Record<string, PinSourceState> = {};
|
||||
const group = BOARD_PIN_GROUPS[b.boardKind] ?? BOARD_PIN_GROUPS.default;
|
||||
const group = boardPinGroupFor(b.boardKind);
|
||||
const wiredPinNames = new Set<string>();
|
||||
for (const w of sim.wires) {
|
||||
if (w.start.componentId === b.id) wiredPinNames.add(w.start.pinName);
|
||||
|
|
|
|||
Loading…
Reference in New Issue