feat(sim): Phase 5 — migrate 7-segment handler to PinResolver
The 7-segment display was the canary case for the original problem: multiplexed displays with BJTs driving digit-select pins (COM/DIG) required the `[C, B]` shortcut in PASSIVE_PIN_PAIRS to even discover that the COM was wired to an Arduino pin. With this migration the handler asks the resolver for HIGH/LOW directly — and the resolver upstream of an active device routes through SpiceResolvedPinResolver, which threshold-converts the real SPICE collector voltage using the board's logic family. Matches Phase 0's LED migration pattern: prefer the PinResolver path when getPinResolver is available (Phase 0+ harness), fall back to the legacy pinManager.onPinChange + getArduinoPinHelper for tests / builds without it. Backwards-compatible — both digit-select (COM.1/COM.2 on 1-digit, DIG1..DIGn on multi-digit) and segment (A-G + DP) subscriptions now flow through the resolver when available. Seeds initial state from resolver.getCurrentState() so static-wire topologies (e.g. COM directly to GND) work at sim-start without an explicit edge event. Phase 5 progress: 2 of ~12 *Parts handlers migrated (LED, 7-segment). Remaining handlers (pushbutton, switch, 74HC595, etc.) follow the same pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
46aa16bfc2
commit
73478b7433
|
|
@ -295,30 +295,52 @@ PartSimulationRegistry.register('74hc595', {
|
|||
// ─── 7-segment display (direct-drive, when connected directly to Arduino) ────
|
||||
|
||||
PartSimulationRegistry.register('7segment', {
|
||||
attachEvents: (element, simulator, getArduinoPinHelper) => {
|
||||
attachEvents: (element, simulator, getArduinoPinHelper, _componentId, getPinResolver) => {
|
||||
const pinManager = (simulator as any).pinManager;
|
||||
if (!pinManager) return () => {};
|
||||
|
||||
const unsubscribers: (() => void)[] = [];
|
||||
const s = get7SegState(element);
|
||||
|
||||
// Phase 5 migration of the mixed-mode simulator project: prefer the
|
||||
// PinResolver path so digit-select pins driven through a BJT (the
|
||||
// classic multiplexed 7-segment topology) get SPICE-aware HIGH/LOW
|
||||
// detection instead of relying on the `[C, B]` PASSIVE_PIN_PAIRS
|
||||
// shortcut. Falls back to direct pinManager + getArduinoPinHelper
|
||||
// for harnesses or builds without Phase 0.
|
||||
const useResolver = typeof getPinResolver === 'function';
|
||||
|
||||
// Figure out which digit-select pins this display exposes and subscribe
|
||||
// to whichever ones are actually wired to an Arduino pin. Polarity is
|
||||
// determined by the helper's HIGH/LOW report (we treat HIGH = enabled).
|
||||
// to whichever ones are actually wired. Polarity: HIGH = digit enabled.
|
||||
const digitPinNames: string[] = s.digits === 1
|
||||
? ['COM.1', 'COM.2'] // 1-digit: both COM pins map to digit 0
|
||||
: Array.from({ length: s.digits }, (_, i) => `DIG${i + 1}`); // 2/3/4-digit: DIG1..DIGn
|
||||
let digitPinsWired = 0;
|
||||
for (let d = 0; d < digitPinNames.length; d++) {
|
||||
const pin = getArduinoPinHelper(digitPinNames[d]);
|
||||
if (pin === null) continue;
|
||||
digitPinsWired++;
|
||||
const pinName = digitPinNames[d];
|
||||
const digitIdx = s.digits === 1 ? 0 : d;
|
||||
unsubscribers.push(
|
||||
pinManager.onPinChange(pin, (_: number, state: boolean) => {
|
||||
handle7SegDigit(element, digitIdx, state);
|
||||
}),
|
||||
);
|
||||
if (useResolver) {
|
||||
const resolver = getPinResolver!(pinName);
|
||||
if (!resolver) continue;
|
||||
digitPinsWired++;
|
||||
// Seed initial state — important when the wire is already
|
||||
// settled at sim start (e.g. examples that hard-wire a COM to GND).
|
||||
handle7SegDigit(element, digitIdx, resolver.getCurrentState() === 'HIGH');
|
||||
unsubscribers.push(
|
||||
resolver.onChange((state) => {
|
||||
handle7SegDigit(element, digitIdx, state === 'HIGH');
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const pin = getArduinoPinHelper(pinName);
|
||||
if (pin === null) continue;
|
||||
digitPinsWired++;
|
||||
unsubscribers.push(
|
||||
pinManager.onPinChange(pin, (_: number, state: boolean) => {
|
||||
handle7SegDigit(element, digitIdx, state);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
// No digit-select pin wired → direct drive (common cathode tied to GND,
|
||||
// or a single display being lit unconditionally). Enable every digit
|
||||
|
|
@ -330,13 +352,24 @@ PartSimulationRegistry.register('7segment', {
|
|||
// Subscribe to A-G + DP segment pins.
|
||||
for (let i = 0; i < SEGMENT_NAMES.length; i++) {
|
||||
const seg = SEGMENT_NAMES[i];
|
||||
const arduinoPin = getArduinoPinHelper(seg);
|
||||
if (arduinoPin === null) continue;
|
||||
unsubscribers.push(
|
||||
pinManager.onPinChange(arduinoPin, (_: number, state: boolean) => {
|
||||
handle7SegSegment(element, i, state);
|
||||
}),
|
||||
);
|
||||
if (useResolver) {
|
||||
const resolver = getPinResolver!(seg);
|
||||
if (!resolver) continue;
|
||||
handle7SegSegment(element, i, resolver.getCurrentState() === 'HIGH');
|
||||
unsubscribers.push(
|
||||
resolver.onChange((state) => {
|
||||
handle7SegSegment(element, i, state === 'HIGH');
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const arduinoPin = getArduinoPinHelper(seg);
|
||||
if (arduinoPin === null) continue;
|
||||
unsubscribers.push(
|
||||
pinManager.onPinChange(arduinoPin, (_: number, state: boolean) => {
|
||||
handle7SegSegment(element, i, state);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return () => unsubscribers.forEach((u) => u());
|
||||
|
|
|
|||
Loading…
Reference in New Issue