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:
parent
858e160e6f
commit
d64eebc200
|
|
@ -139,16 +139,12 @@ export class PinManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Soft cleanup for stopBoard: drop the MCU-output classification so
|
* Drop only the MCU-output classification (SPICE side). Used by
|
||||||
* the next Run doesn't keep emitting stale V-sources on the SPICE
|
* paths that need to forget which pins were driven this session
|
||||||
* side from a pin the previous program had driven. The cached pin
|
* without disturbing the cached pin states or notifying listeners.
|
||||||
* STATES (`pinStates`) stay put — components that hold visual state
|
* For the user-facing Stop / Reset / firmware-reload flows use
|
||||||
* (7-segment, NeoPixel, LCD, dot-matrix) keep showing the last
|
* `hardResetPinStates` — those are cold boots and the next Run
|
||||||
* pattern, which is what users expect from a pause/stop. On resume,
|
* must start from setup() with every visual cleared.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
resetPinStates(): void {
|
resetPinStates(): void {
|
||||||
this.outputPins.clear();
|
this.outputPins.clear();
|
||||||
|
|
|
||||||
|
|
@ -1599,13 +1599,19 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
} else if (isEsp32Kind(board.boardKind)) {
|
} else if (isEsp32Kind(board.boardKind)) {
|
||||||
getEsp32Bridge(boardId)?.disconnect();
|
getEsp32Bridge(boardId)?.disconnect();
|
||||||
} else {
|
} 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
|
// Hard reset: clear cached pin states AND notify listeners so
|
||||||
// collectPinStates doesn't emit stale V-sources before the new
|
// multiplexed displays (7-segment, LED matrix, NeoPixel) clear
|
||||||
// firmware has driven any pins.
|
// the frozen frame they were holding when power was cut, instead
|
||||||
getBoardPinManager(boardId)?.resetPinStates();
|
// of carrying it into the next run.
|
||||||
|
getBoardPinManager(boardId)?.hardResetPinStates();
|
||||||
|
|
||||||
set((s) => {
|
set((s) => {
|
||||||
const boards = s.boards.map((b) => (b.id === boardId ? { ...b, running: false } : b));
|
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);
|
const sim = getBoardSimulator(boardId);
|
||||||
if (sim) {
|
if (sim) {
|
||||||
sim.reset();
|
sim.reset();
|
||||||
// Reset is a hard reboot — the CPU starts at PC=0, every pin
|
// Hard reboot: CPU back to PC=0, every pin floats, every
|
||||||
// goes back to floating, every output classification is
|
// output classification dropped, and listeners notified so
|
||||||
// dropped. The hardResetPinStates call also notifies
|
// visual components (7-segment, NeoPixel, LCD) clear their
|
||||||
// listeners that previously-HIGH pins are now LOW, so visual
|
// stale frame instead of freezing on whatever was lit.
|
||||||
// components (7-segment, NeoPixel, LCD) clear their stale
|
// Same semantics as Stop — both behave like cutting power.
|
||||||
// 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.
|
|
||||||
getBoardPinManager(boardId)?.hardResetPinStates();
|
getBoardPinManager(boardId)?.hardResetPinStates();
|
||||||
// NOTE: do NOT reassign sim.onSerialData here. sim.reset()
|
// NOTE: do NOT reassign sim.onSerialData here. sim.reset()
|
||||||
// recreates the USART but the new usart.onByteTransmit
|
// recreates the USART but the new usart.onByteTransmit
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue