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
|
||||
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++;
|
||||
if (frameCount % 60 === 0) {
|
||||
console.log(`[CPU] Frame ${frameCount}, PC: ${this.cpu.pc}, Cycles: ${this.cpu.cycles}`);
|
||||
|
|
@ -820,6 +832,13 @@ export class AVRSimulator {
|
|||
}
|
||||
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');
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue