fix(spice): overlay boards were skipped before a single volt was injected
ADC_PIN_MAP and ADC_PIN_TO_GPIO are keyed by the BoardKind union. An overlay board's kind is a runtime string that can never join that union, so connectAnalogInputsToMcu hit `if (!adcPins) continue;` at all three sites for every pro board: a divider, an LDR, an op-amp output wired to one solved correctly in SPICE and then had nowhere to go. Parts that inject their own value (a potentiometer, through partUtils) were unaffected, which is exactly what made this easy to miss — analog looked like it worked. A pro board now declares its pads through ProBoardDef.adcPins, and pad->GPIO resolution consults its own pinToNumber first. Both fall back to the existing tables untouched, so no OSS board changes behaviour. The pad NAME matters, not the GPIO: on the Pimoroni Pico Plus 2 W the analog pads are silked 26/27/28 and tied to GP40/41/42 through 1k, so the two disagree by design.
This commit is contained in:
parent
07ba0f729f
commit
f35805175c
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* The SPICE analog path and overlay boards.
|
||||
*
|
||||
* ADC_PIN_MAP and ADC_PIN_TO_GPIO in connectAnalogInputsToMcu are keyed by the
|
||||
* BoardKind union. An overlay board's kind is a runtime string that can never
|
||||
* join that union, so every pro board was skipped before a single volt was
|
||||
* injected — a divider or an LDR wired to one solved correctly in SPICE and
|
||||
* then had nowhere to go. Parts that inject directly (a potentiometer, through
|
||||
* partUtils) were unaffected, which is what made the gap easy to miss.
|
||||
*
|
||||
* A pro board now declares its own pads via ProBoardDef.adcPins.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { registerProBoards, getProBoard, type ProBoardDef } from '../lib/proBoardRegistry';
|
||||
|
||||
const KIND = 'test-overlay-adc-board';
|
||||
|
||||
describe('ProBoardDef.adcPins', () => {
|
||||
beforeEach(() => {
|
||||
registerProBoards([
|
||||
{
|
||||
kind: KIND,
|
||||
label: 'Overlay ADC board',
|
||||
fqbn: null,
|
||||
description: 'test',
|
||||
tag: 'velxio-test-adc',
|
||||
size: { w: 10, h: 10 },
|
||||
// The shape the Pimoroni Pico Plus 2 W uses: pads silked with a GP
|
||||
// number that are wired to a different, ADC-capable GPIO.
|
||||
pinToNumber: (name: string) => (name === 'GP26' ? 26 : null),
|
||||
adcPins: [{ pinName: 'GP26', channel: 0 }],
|
||||
} as ProBoardDef,
|
||||
]);
|
||||
});
|
||||
|
||||
it('is readable from the registry, which is what the SPICE path consults', () => {
|
||||
const def = getProBoard(KIND);
|
||||
expect(def?.adcPins).toEqual([{ pinName: 'GP26', channel: 0 }]);
|
||||
});
|
||||
|
||||
it('carries the pad name a wire uses, not the GPIO behind it', () => {
|
||||
// connectAnalogInputsToMcu looks the net up by `${boardId}:${pinName}`, so
|
||||
// the name has to match pinInfo. Resolving it to a GPIO is pinToNumber's
|
||||
// job, and the two can disagree — that is the whole point on a board whose
|
||||
// pads are tied to a second GPIO.
|
||||
const def = getProBoard(KIND)!;
|
||||
const pin = def.adcPins![0];
|
||||
expect(pin.pinName).toBe('GP26');
|
||||
expect(def.pinToNumber!(pin.pinName)).toBe(26);
|
||||
});
|
||||
|
||||
it('leaves a board that declares nothing without an analog path', () => {
|
||||
registerProBoards([
|
||||
{
|
||||
kind: 'test-overlay-no-adc',
|
||||
label: 'No ADC',
|
||||
fqbn: null,
|
||||
description: 'test',
|
||||
tag: 'velxio-test-noadc',
|
||||
size: { w: 10, h: 10 },
|
||||
} as ProBoardDef,
|
||||
]);
|
||||
expect(getProBoard('test-overlay-no-adc')?.adcPins).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -84,6 +84,21 @@ export interface ProBoardDef {
|
|||
/** pinInfo name -> GPIO number (power/ground pins -> -1). Falls back to the
|
||||
* generic numeric parse when omitted. Return null for "not mine". */
|
||||
pinToNumber?: (pinName: string) => number | null;
|
||||
/**
|
||||
* Which pads reach the ADC, and on which channel — the overlay's own version
|
||||
* of the OSS ADC_PIN_MAP.
|
||||
*
|
||||
* That map is keyed by the BoardKind union, which no overlay kind can join,
|
||||
* so the SPICE analog path skipped every pro board outright: a divider or an
|
||||
* LDR wired to one of these boards solved correctly and then had nowhere to
|
||||
* go. Parts that inject directly (a potentiometer through partUtils) were
|
||||
* unaffected, which is why this looked like it worked.
|
||||
*
|
||||
* Names are pad names as they appear in pinInfo. A pad that is analog under a
|
||||
* different silk (the Pimoroni Pico Plus 2 W ties GP26/27/28 to the ADC-
|
||||
* capable GP40/41/42 through 1k) is declared under the name a wire uses.
|
||||
*/
|
||||
adcPins?: Array<{ pinName: string; channel: number }>;
|
||||
/** In-browser simulator factory (e.g. the RP2350/Hazard3 emulator). The pm
|
||||
* argument is the store's PinManager instance. */
|
||||
createSimulator?: (pm: unknown) => ProBoardSimulator;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { setAdcVoltage } from '../parts/partUtils';
|
|||
import type { BoardKind } from '../../types/board';
|
||||
import { interpolateAt } from './waveformStats';
|
||||
import { boardPinToNumber } from '../../utils/boardPinMapping';
|
||||
import { getProBoard } from '../../lib/proBoardRegistry';
|
||||
|
||||
// Which Arduino-style pin name maps to which ADC channel, per board.
|
||||
function adcRange(prefix: string, start: number, count: number) {
|
||||
|
|
@ -119,6 +120,18 @@ const ADC_PIN_TO_GPIO: Partial<Record<BoardKind, (pinName: string, channel: numb
|
|||
'aitewinrobot-esp32c3-supermini': gpioPinFromName,
|
||||
};
|
||||
|
||||
/**
|
||||
* Which pads on this board reach the ADC.
|
||||
*
|
||||
* ADC_PIN_MAP is keyed by the BoardKind union, so an overlay board — whose kind
|
||||
* is a runtime string, not a member of that union — could never appear in it
|
||||
* and was skipped outright. A board registered through proBoardRegistry
|
||||
* declares its own list instead (ProBoardDef.adcPins).
|
||||
*/
|
||||
function adcPinsFor(kind: BoardKind): Array<{ pinName: string; channel: number }> | undefined {
|
||||
return ADC_PIN_MAP[kind] ?? getProBoard(kind)?.adcPins;
|
||||
}
|
||||
|
||||
/** Silkscreen name -> GPIO for the ADC injection. ESP32-family boards go
|
||||
* through the canonical boardPinToNumber table (nano-esp32 'A0' -> GPIO1,
|
||||
* xiao 'D1' -> GPIO2, bare '34' -> 34, ...); AVR/ATtiny/Pico keep their
|
||||
|
|
@ -131,6 +144,13 @@ function gpioForBoardPin(kind: BoardKind, pinName: string, channel: number): num
|
|||
const n = boardPinToNumber(kind, pinName);
|
||||
if (n != null && n >= 0) return n;
|
||||
}
|
||||
// An overlay board owns its pad->GPIO mapping (ProBoardDef.pinToNumber);
|
||||
// ADC_PIN_TO_GPIO cannot hold it for the same union reason as above.
|
||||
const proDef = getProBoard(kind);
|
||||
if (proDef?.pinToNumber) {
|
||||
const n = proDef.pinToNumber(pinName);
|
||||
if (n != null && n >= 0) return n;
|
||||
}
|
||||
const legacy = ADC_PIN_TO_GPIO[kind];
|
||||
return legacy ? legacy(pinName, channel) : -1;
|
||||
}
|
||||
|
|
@ -148,7 +168,7 @@ export function connectAnalogInputsToMcu(): () => void {
|
|||
const { nodeVoltages, pinNetMap, sourcedNets } = useElectricalStore.getState();
|
||||
const { boards } = useSimulatorStore.getState();
|
||||
for (const board of boards) {
|
||||
const adcPins = ADC_PIN_MAP[board.boardKind];
|
||||
const adcPins = adcPinsFor(board.boardKind);
|
||||
if (!adcPins) continue;
|
||||
const sim = getBoardSimulator(board.id);
|
||||
if (!sim) continue;
|
||||
|
|
@ -187,7 +207,7 @@ export function connectAnalogInputsToMcu(): () => void {
|
|||
const { boards } = useSimulatorStore.getState();
|
||||
const { pinNetMap, timeWaveforms } = useElectricalStore.getState();
|
||||
for (const board of boards) {
|
||||
const adcPins = ADC_PIN_MAP[board.boardKind];
|
||||
const adcPins = adcPinsFor(board.boardKind);
|
||||
if (!adcPins) continue;
|
||||
const sim = getBoardSimulator(board.id);
|
||||
if (!sim) continue;
|
||||
|
|
@ -257,7 +277,7 @@ export function connectAnalogInputsToMcu(): () => void {
|
|||
pushEsp32Waveforms();
|
||||
const { boards } = useSimulatorStore.getState();
|
||||
for (const board of boards) {
|
||||
const adcPins = ADC_PIN_MAP[board.boardKind];
|
||||
const adcPins = adcPinsFor(board.boardKind);
|
||||
if (!adcPins) continue;
|
||||
const sim = getBoardSimulator(board.id);
|
||||
if (!sim) continue;
|
||||
|
|
|
|||
Loading…
Reference in New Issue