Only re-wake the loop once the RX buffer is drained, reset the frame state in decoder setup, test reconfiguration

This commit is contained in:
J. Nick Koston
2026-08-19 14:14:28 -05:00
parent f633fd5566
commit cd350825c7
3 changed files with 61 additions and 2 deletions
@@ -22,6 +22,8 @@ class SoftwareSerialRxDecoder {
public:
static constexpr uint8_t RX_IDLE = 0xFF;
/// Configure the framing and buffer. Drops buffered bytes and any partial frame and
/// assumes an idle high line; call reset() afterwards with the real line level.
void setup(uint32_t bit_cycles, uint8_t data_bits, bool parity, uint8_t stop_bits, uint8_t *buffer,
size_t buffer_size) {
this->bit_cycles_ = bit_cycles;
@@ -33,11 +35,13 @@ class SoftwareSerialRxDecoder {
this->buffer_size_ = buffer_size;
this->in_pos_ = 0;
this->out_pos_ = 0;
this->reset(0, true);
}
/// Forget any partial frame; `level` is the current line level.
void reset(uint32_t now, bool level) {
this->bit_ = RX_IDLE;
this->cur_byte_ = 0;
this->last_level_ = level;
this->last_edge_ = now;
}
@@ -315,8 +315,10 @@ void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr_edge(ESP8266SoftwareSerial *arg)
void ESP8266SoftwareSerial::rx_finalize_pending_() {
if (!this->rx_.finalize_due(arch_get_cpu_cycle_count())) {
#ifdef USE_UART_WAKE_LOOP_ON_RX
// Not old enough yet: run the loop again right away instead of after a full loop_interval_.
wake_loop_threadsafe();
// Not old enough yet: once the caller has drained what is there, run the loop
// again right away instead of after a full loop_interval_.
if (this->rx_.available() == 0)
wake_loop_threadsafe();
#endif
return;
}
@@ -219,6 +219,59 @@ TEST(SoftwareSerialRxDecoder, DropsBytesWhenBufferIsFullAndKeepsOldest) {
EXPECT_EQ(sim.received()[n], n);
}
TEST(SoftwareSerialRxDecoder, SetupAgainDropsStaleStateAndUsesNewBufferAndFraming) {
// Mirrors load_settings(): bytes buffered and a frame left open under 8N1 in a
// 64 byte buffer, then setup() again with 5E2 in a 4 byte buffer.
LineSim sim(9600, 8, false, false, 1);
uint32_t t = 5000;
for (int n = 0; n < 10; n++)
t = sim.send(static_cast<uint8_t>(0x40 + n), t);
sim.edge(t + 8 * sim.bit_cycles(), false); // open a frame, never closed
SoftwareSerialRxDecoder &dec = sim.decoder();
ASSERT_GE(dec.available(), 9u);
std::vector<uint8_t> small(4, 0xEE);
const uint32_t bit = CPU_HZ / 2400;
dec.setup(bit, 5, true, 2, small.data(), small.size());
EXPECT_EQ(dec.available(), 0u);
EXPECT_FALSE(dec.pending());
EXPECT_EQ(dec.read_byte(), 0);
// 5E2 frames fed straight into the reconfigured decoder: capacity is 3, the
// rest are dropped and nothing is written past the end of the new buffer.
auto send_5e2 = [&](uint8_t value, uint32_t start) {
bool line = true;
uint32_t at = start;
auto put = [&](bool b) {
if (b != line) {
dec.on_edge(at, b);
line = b;
}
at += bit;
};
put(false);
int ones = 0;
for (int i = 0; i < 5; i++) {
bool b = (value >> i) & 1;
ones += b;
put(b);
}
put(ones & 1);
put(true);
put(true);
return at;
};
uint32_t t2 = 5000;
for (int n = 1; n <= 6; n++)
t2 = send_5e2(static_cast<uint8_t>(n), t2);
dec.finalize(t2 + 20 * bit);
ASSERT_EQ(dec.available(), 3u);
EXPECT_EQ(dec.read_byte(), 1);
EXPECT_EQ(dec.read_byte(), 2);
EXPECT_EQ(dec.read_byte(), 3);
EXPECT_EQ(small[3], 0xEE); // capacity slot is never written
}
TEST(SoftwareSerialRxDecoder, ResetDiscardsPartialFrame) {
LineSim sim(9600, 8, false, false, 1);
const uint32_t bit = sim.bit_cycles();