fix(avr/serial): drain RX queue every frame and clear it on stop
User report: Arduino Nano connected to an Uno-TX wire received bytes
but displayed them poorly, and pressing Stop then Run "killed" the
serial link until the page reloaded.
Two paired bugs in the cross-board serial path:
(1) drainSerialRxQueue was only ever re-fired from usart.onRxComplete,
which itself only fires AFTER a successful delivery. If the very
first delivery attempt fails (rxEnable=false because the sketch
hasn't reached Serial.begin yet — extremely common when one board
starts emitting bytes before the receiving board's setup() runs)
nothing re-kicks the queue and every subsequent byte from the
sibling board sits in serialRxQueue indefinitely. Adding a
per-frame drain attempt (no-op when queue is empty or rxBusyValue
is set, so cost is negligible) makes the link self-heal across
cold-start races and Serial.end()/begin() toggles.
(2) stop() never cleared serialRxQueue. On Run after Stop the new
USART would re-drain the previous run's leftovers into the fresh
sketch before its setup() ran, corrupting the first bytes the
user saw on the receiving side. Clearing the queue in stop() —
same place we already clear scheduledPinChanges — keeps each Run
a clean slate.
Verified 52 existing tests still pass (dual-pico-serial-passthrough,
dual-arduino-software-serial, interconnect-routing, avr-uart-tx
-waveform, serial-batching, AVRSimulator, pin-position-rotation).
This commit is contained in:
parent
467ca4455f
commit
56d3bc4f12
|
|
@ -791,6 +791,18 @@ export class AVRSimulator {
|
||||||
// Poll PWM registers every frame
|
// Poll PWM registers every frame
|
||||||
this.pollPwmRegisters();
|
this.pollPwmRegisters();
|
||||||
|
|
||||||
|
// Try to drain any pending RX byte every frame. The primary
|
||||||
|
// drain path is onRxComplete (re-fires after each successful
|
||||||
|
// delivery), but that callback only ever fires AFTER a byte was
|
||||||
|
// accepted — if the very first delivery attempt fails (sketch
|
||||||
|
// hasn't called Serial.begin yet, so rxEnable is false) nothing
|
||||||
|
// would ever re-kick the queue and bytes from a sibling board
|
||||||
|
// sit there forever. A per-frame retry is cheap (no-op when the
|
||||||
|
// queue is empty or rxBusyValue is set) and makes the link
|
||||||
|
// self-heal across both startup races and Serial.end()/begin()
|
||||||
|
// toggles in the sketch.
|
||||||
|
if (this.serialRxQueue.length > 0) this.drainSerialRxQueue();
|
||||||
|
|
||||||
frameCount++;
|
frameCount++;
|
||||||
if (frameCount % 60 === 0) {
|
if (frameCount % 60 === 0) {
|
||||||
console.log(`[CPU] Frame ${frameCount}, PC: ${this.cpu.pc}, Cycles: ${this.cpu.cycles}`);
|
console.log(`[CPU] Frame ${frameCount}, PC: ${this.cpu.pc}, Cycles: ${this.cpu.cycles}`);
|
||||||
|
|
@ -820,6 +832,13 @@ export class AVRSimulator {
|
||||||
}
|
}
|
||||||
this.scheduledPinChanges = [];
|
this.scheduledPinChanges = [];
|
||||||
|
|
||||||
|
// Drop any bytes the previous run had queued for the sketch's RX
|
||||||
|
// but never delivered (RX disabled, busy, or the sketch hadn't
|
||||||
|
// reached Serial.begin yet). Without this the next run starts with
|
||||||
|
// a stale tail that drains into the fresh USART before the sketch
|
||||||
|
// is ready, and from the user's point of view the link is "dead".
|
||||||
|
this.serialRxQueue = [];
|
||||||
|
|
||||||
console.log('AVR simulation stopped');
|
console.log('AVR simulation stopped');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue