diff --git a/include/virtual_sx1262.h b/include/virtual_sx1262.h index d412f97..5edf571 100644 --- a/include/virtual_sx1262.h +++ b/include/virtual_sx1262.h @@ -214,7 +214,8 @@ typedef struct { /* Events the chip had and the firmware never saw, because IrqMask had them * masked off. A firmware waiting on an interrupt it did not enable looks * exactly like a quiet mesh, and this is the only thing that tells the two - * apart. */ + * apart. One per event, however many of its bits were masked: a header is one + * event whether one interrupt or three would have announced it. */ uint32_t irq_suppressed; /* Scans run, and scans that found a carrier. A host comparing a firmware that * scans against one that does not needs to know the scans happened and what diff --git a/src/VirtualSX1262.cpp b/src/VirtualSX1262.cpp index e9bd58d..fd4d5d2 100644 --- a/src/VirtualSX1262.cpp +++ b/src/VirtualSX1262.cpp @@ -149,9 +149,14 @@ void VirtualSX1262::tick(uint64_t nowMs) { // The receiver's own preamble length stands in for the transmitter's: a // mesh agrees on one, and the chip is told nothing about who is sending. // An implicit-header receiver has no header to find. + // + // HeaderValid alone: SyncWordValid is the GFSK modem's interrupt, and a LoRa + // receiver reports its header through HeaderValid (DS_SX1261-2 table 13-29). + // Raising both made every header count as suppressed on a firmware that + // correctly leaves the FSK bit out of its mask. const double headerEnd = preambleSyms_ + kSyncAndSfdSymbols + kHeaderBlockSymbols; if (!headerImplicit_ && !headerRaised_ && sinceMs >= headerEnd * symMs) { - raiseIrq(kIrqHeaderValid | kIrqSyncWordValid); + raiseIrq(kIrqHeaderValid); headerRaised_ = true; } } @@ -203,7 +208,7 @@ void VirtualSX1262::setChannelBusy(bool busy) { preambleRaised_ = false; headerRaised_ = false; if (stuckIrqMs_ == 0) { - irq_ &= (uint16_t) ~(kIrqPreambleDetected | kIrqHeaderValid | kIrqSyncWordValid); + irq_ &= (uint16_t) ~(kIrqPreambleDetected | kIrqHeaderValid); } } channelBusy_ = busy; diff --git a/src/registers.h b/src/registers.h index 73e0210..285ce6e 100644 --- a/src/registers.h +++ b/src/registers.h @@ -164,9 +164,10 @@ namespace { // The header cannot be read until the whole preamble, the two sync symbols and // the 2.25-symbol SFD have gone by, and it is the first interleaver block after // them: eight symbols, always at CR 4/8. A fixed twelve symbols raised -// HeaderValid 32 symbols early for MeshCore's 32-symbol preamble at SF8 and -// below (16 at SF9 and up), which shrank the stretch where a firmware that can see -// preambles answers differently from one that cannot to a third of its real width. +// HeaderValid 32.25 symbols early for MeshCore's 32-symbol preamble at SF8 and +// below (16 at SF9 and up). That shrank the stretch where a firmware that can +// see preambles answers differently from one that cannot, from PreambleDetected +// to HeaderValid, to eight symbols of its real 40.25: a fifth. [[maybe_unused]] constexpr double kPreambleSymbols = 4.0; [[maybe_unused]] constexpr double kSyncAndSfdSymbols = 4.25; [[maybe_unused]] constexpr double kHeaderBlockSymbols = 8.0; diff --git a/test/test_interrupts.cpp b/test/test_interrupts.cpp index 4e97abe..9f18b27 100644 --- a/test/test_interrupts.cpp +++ b/test/test_interrupts.cpp @@ -7,6 +7,34 @@ */ #include "harness.h" +/* Two frames heard under one IrqMask, the way a receive loop takes them: a + * carrier long enough to carry its header, the frame, the carrier gone, and the + * status cleared before the next. Answers what the chip counted as suppressed. */ +static uint32_t suppressed_over_two_frames(uint16_t mask) { + vsx_chip* c = vsx_create(); + uint64_t now = 0; + bring_up(c, &now); + /* SetDioIrqParams: the mask under test, and RxDone alone routed to DIO1. */ + const uint8_t dio[] = {0x08, (uint8_t)(mask >> 8), (uint8_t)mask, 0, 0x02, 0, 0, 0, 0}; + vsx_spi_transaction(c, dio, nullptr, sizeof(dio)); + const uint8_t frame[] = {1, 2, 3, 4}; + for (int n = 0; n < 2; ++n) { + vsx_set_channel_busy(c, 1); + for (int i = 0; i < 150; ++i) { + vsx_tick(c, ++now); + } + vsx_deliver_frame(c, frame, sizeof(frame)); + vsx_set_channel_busy(c, 0); + vsx_tick(c, ++now); + const uint8_t clear[] = {0x02, 0xFF, 0xFF}; + vsx_spi_transaction(c, clear, nullptr, sizeof(clear)); + } + vsx_counters k; + vsx_get_counters(c, &k); + vsx_destroy(c); + return k.irq_suppressed; +} + int main() { std::printf("virtual-sx1262"); @@ -293,5 +321,19 @@ int main() { vsx_destroy(c); } + /* ---------------------------------------------------------------- */ + CASE("a LoRa header is not counted against a mask that leaves FSK out"); + /* The header was raised as HeaderValid and SyncWordValid together, and + * SyncWordValid is the GFSK modem's. No LoRa firmware enables it, so every + * header counted as suppressed and a correctly configured firmware looked as + * though it were still missing interrupts. The two masks are MeshCore's own: + * 1.17 enables PreambleDetected for receive and 1.16 does not. */ + { + check(suppressed_over_two_frames(0x0276) == 0, + "1.17's mask misses nothing on a frame it receives"); + check(suppressed_over_two_frames(0x0272) == 2, + "1.16's misses PreambleDetected, once per frame"); + } + return report(""); } diff --git a/test/test_reception.cpp b/test/test_reception.cpp index d125a7d..ef9e1b9 100644 --- a/test/test_reception.cpp +++ b/test/test_reception.cpp @@ -31,6 +31,8 @@ int main() { } check((irq_flags(c) & IRQ_HEADER) != 0, "while the carrier is on the air, HeaderValid is set"); + check((irq_flags(c) & IRQ_SYNC_WORD) == 0, + "and SyncWordValid is not: that is the FSK modem's, and this is LoRa"); const uint8_t frame[] = {0x11, 0x00, 0x33, 0x0a, 0xc2, 0x1f}; vsx_deliver_frame(c, frame, sizeof(frame)); @@ -41,7 +43,6 @@ int main() { check((f & IRQ_RX_DONE) != 0, "the delivered packet raises RxDone"); check((f & IRQ_HEADER) == 0, "HeaderValid is clear once the carrier has gone"); check((f & IRQ_PREAMBLE) == 0, "PreambleDetected is clear too"); - check((f & IRQ_SYNC_WORD) == 0, "SyncWordValid is clear too"); vsx_destroy(c); } @@ -220,7 +221,7 @@ int main() { /* ---------------------------------------------------------------- */ CASE("HeaderValid waits for the preamble, the SFD and the header block"); /* It was raised twelve symbols into the carrier whatever the preamble, so for - * MeshCore's sixteen-symbol preamble it came sixteen symbols early. A + * the chip's default sixteen-symbol preamble it came 16.25 symbols early. A * firmware that sees preambles and one that does not answer differently only * between PreambleDetected and HeaderValid, and that stretch was a third of * its real width. At SF8 and 62.5 kHz a symbol is 4.096 ms, so the header ends