fix(stop): reset CPU to PC=0 on Stop (real-life power-cycle semantics)

The previous fix preserved display state on Stop so Resume could pick
up the multiplexed frame seamlessly — but that's Pause semantics, not
Stop. On a real Arduino, hitting the physical Stop is cutting power:
the next Run must boot from setup(), not continue at the saved PC.

User report on https://velxio.dev/example/uno-7segment :
  > empieza a contar, le doy stop en el 6, le doy run y sigue desde 6

stopBoard now:
  - calls sim.reset() (was sim.stop()) — CPU back to PC=0
  - calls hardResetPinStates() (was the soft resetPinStates) — clears
    cached states AND notifies listeners so 7-seg / NeoPixel / LCD
    blank out instead of freezing on whatever was lit.

Reset and Stop are now the same cold-boot semantics; Reset still
additionally clears serial output + baud rate. The soft
resetPinStates() helper stays for internal SPICE-classification-only
paths that don't want listener fan-out.
This commit is contained in:
David Montero 2026-05-26 21:28:41 +02:00
parent 858e160e6f
commit d64eebc200
2 changed files with 22 additions and 24 deletions

View File

@ -139,16 +139,12 @@ export class PinManager {
}
/**
* Soft cleanup for stopBoard: drop the MCU-output classification so
* the next Run doesn't keep emitting stale V-sources on the SPICE
* side from a pin the previous program had driven. The cached pin
* STATES (`pinStates`) stay put components that hold visual state
* (7-segment, NeoPixel, LCD, dot-matrix) keep showing the last
* pattern, which is what users expect from a pause/stop. On resume,
* avr8js's port-listener fires only for bits that CHANGED relative
* to its internal oldValue, so blanking the cache here would race:
* if the sketch's port register matches what it was pre-stop, no
* pinChange fires and the display would never recover.
* Drop only the MCU-output classification (SPICE side). Used by
* paths that need to forget which pins were driven this session
* without disturbing the cached pin states or notifying listeners.
* For the user-facing Stop / Reset / firmware-reload flows use
* `hardResetPinStates` those are cold boots and the next Run
* must start from setup() with every visual cleared.
*/
resetPinStates(): void {
this.outputPins.clear();

View File

@ -1599,13 +1599,19 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
} else if (isEsp32Kind(board.boardKind)) {
getEsp32Bridge(boardId)?.disconnect();
} else {
getBoardSimulator(boardId)?.stop();
// Stop is "cut power": pressing Run again must boot from setup()
// not resume mid-loop, so reset the CPU to PC=0 here. Without
// this the AVR keeps its program counter and the next Run picks
// up wherever it left off — which is fine for Pause but wrong
// for the physical Stop button users expect.
getBoardSimulator(boardId)?.reset();
}
// Drop MCU-output classification so the next Run starts clean and
// collectPinStates doesn't emit stale V-sources before the new
// firmware has driven any pins.
getBoardPinManager(boardId)?.resetPinStates();
// Hard reset: clear cached pin states AND notify listeners so
// multiplexed displays (7-segment, LED matrix, NeoPixel) clear
// the frozen frame they were holding when power was cut, instead
// of carrying it into the next run.
getBoardPinManager(boardId)?.hardResetPinStates();
set((s) => {
const boards = s.boards.map((b) => (b.id === boardId ? { ...b, running: false } : b));
@ -1629,15 +1635,11 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
const sim = getBoardSimulator(boardId);
if (sim) {
sim.reset();
// Reset is a hard reboot — the CPU starts at PC=0, every pin
// goes back to floating, every output classification is
// dropped. The hardResetPinStates call also notifies
// listeners that previously-HIGH pins are now LOW, so visual
// components (7-segment, NeoPixel, LCD) clear their stale
// pattern instead of freezing on whatever was lit at the
// moment of Reset. The Stop path uses the lighter
// resetPinStates() that ONLY drops the output classification
// (preserves the display so it can resume) — see PinManager.
// Hard reboot: CPU back to PC=0, every pin floats, every
// output classification dropped, and listeners notified so
// visual components (7-segment, NeoPixel, LCD) clear their
// stale frame instead of freezing on whatever was lit.
// Same semantics as Stop — both behave like cutting power.
getBoardPinManager(boardId)?.hardResetPinStates();
// NOTE: do NOT reassign sim.onSerialData here. sim.reset()
// recreates the USART but the new usart.onByteTransmit