From 8031fe0c4dbe53edd89185b9ccf1805595a6c1e7 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Thu, 21 May 2026 19:57:26 -0600 Subject: [PATCH] radio: harden SX126x LoRa mode on Ratdeck --- src/main.cpp | 8 ++++ src/radio/RadioConstants.h | 1 + src/radio/SX1262.cpp | 97 ++++++++++++++++++++++++++++++++++---- src/radio/SX1262.h | 7 ++- 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d34774d..988d232 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -395,6 +395,14 @@ void onHotkeyDiag() { devErr, status, (status >> 4) & 0x07, (status >> 1) & 0x07); if (devErr & 0x40) Serial.println(" *** PLL LOCK FAILED ***"); Serial.printf("Current RSSI: %d dBm\n", radio.currentRssi()); + uint8_t packetType = radio.getPacketType(); + const char* packetTypeName = + (packetType == 0x00) ? "GFSK" : + (packetType == 0x01) ? "LoRa" : + (packetType == 0x02) ? "LR-FHSS" : "unknown"; + Serial.printf("Packet type: 0x%02X (%s)%s\n", + packetType, packetTypeName, + packetType == 0x01 ? "" : " *** NOT LoRa ***"); } Serial.printf("Free heap: %lu bytes PSRAM: %lu bytes\n", (unsigned long)ESP.getFreeHeap(), (unsigned long)ESP.getFreePsram()); diff --git a/src/radio/RadioConstants.h b/src/radio/RadioConstants.h index fae8fd9..023de2c 100644 --- a/src/radio/RadioConstants.h +++ b/src/radio/RadioConstants.h @@ -25,6 +25,7 @@ #define OP_STATUS_6X 0xC0 #define OP_TX_PARAMS_6X 0x8E #define OP_PACKET_TYPE_6X 0x8A +#define OP_GET_PACKET_TYPE_6X 0x11 #define OP_BUFFER_BASE_ADDR_6X 0x8F #define OP_READ_REGISTER_6X 0x1D #define OP_WRITE_REGISTER_6X 0x0D diff --git a/src/radio/SX1262.cpp b/src/radio/SX1262.cpp index a1be219..e07fd2b 100644 --- a/src/radio/SX1262.cpp +++ b/src/radio/SX1262.cpp @@ -8,6 +8,28 @@ SX1262* SX1262::_instance = nullptr; +namespace { +constexpr uint8_t IMAGE_CAL_UNSUPPORTED = 0xFF; + +uint8_t imageCalParams(uint32_t frequency, uint8_t out[2]) { + if (frequency >= 430000000UL && frequency <= 440000000UL) { out[0] = 0x6B; out[1] = 0x6F; return 0; } + else if (frequency >= 470000000UL && frequency <= 510000000UL) { out[0] = 0x75; out[1] = 0x81; return 1; } + else if (frequency >= 779000000UL && frequency <= 787000000UL) { out[0] = 0xC1; out[1] = 0xC5; return 2; } + else if (frequency >= 863000000UL && frequency <= 870000000UL) { out[0] = 0xD7; out[1] = 0xDB; return 3; } + else if (frequency >= 902000000UL && frequency <= 928000000UL) { out[0] = 0xE1; out[1] = 0xE9; return 4; } + return IMAGE_CAL_UNSUPPORTED; +} + +const char* packetTypeName(uint8_t packetType) { + switch (packetType) { + case 0x00: return "GFSK"; + case 0x01: return "LoRa"; + case 0x02: return "LR-FHSS"; + default: return "unknown"; + } +} +} + SX1262::SX1262(SPIClass* spi, int ss, int sclk, int mosi, int miso, int reset, int irq, int busy, int rxen, bool tcxo, bool dio2_as_rf_switch) @@ -171,15 +193,25 @@ void SX1262::calibrate() { waitOnBusy(); } -void SX1262::calibrate_image(uint32_t frequency) { +bool SX1262::calibrate_image(uint32_t frequency) { uint8_t image_freq[2] = {0}; - if (frequency >= 430E6 && frequency <= 440E6) { image_freq[0] = 0x6B; image_freq[1] = 0x6F; } - else if (frequency >= 470E6 && frequency <= 510E6) { image_freq[0] = 0x75; image_freq[1] = 0x81; } - else if (frequency >= 779E6 && frequency <= 787E6) { image_freq[0] = 0xC1; image_freq[1] = 0xC5; } - else if (frequency >= 863E6 && frequency <= 870E6) { image_freq[0] = 0xD7; image_freq[1] = 0xDB; } - else if (frequency >= 902E6 && frequency <= 928E6) { image_freq[0] = 0xE1; image_freq[1] = 0xE9; } + uint8_t band = imageCalParams(frequency, image_freq); + + if (band == IMAGE_CAL_UNSUPPORTED) { + Serial.printf("[SX1262] No image calibration table for %lu Hz\n", + (unsigned long)frequency); + return false; + } + + if (_imageCalBand == band) return true; + + standby(); executeOpcode(OP_CALIBRATE_IMAGE_6X, image_freq, 2); waitOnBusy(); + _imageCalBand = band; + Serial.printf("[SX1262] Image calibrated for %lu Hz (0x%02X 0x%02X)\n", + (unsigned long)frequency, image_freq[0], image_freq[1]); + return true; } void SX1262::enableTCXO() { @@ -192,9 +224,36 @@ void SX1262::enableTCXO() { } } -void SX1262::loraMode() { +bool SX1262::loraMode() { uint8_t mode = MODE_LONG_RANGE_MODE_6X; - executeOpcode(OP_PACKET_TYPE_6X, &mode, 1); + for (int attempt = 0; attempt < 5; attempt++) { + executeOpcode(OP_PACKET_TYPE_6X, &mode, 1); + delay(2); + + uint8_t packetType = getPacketType(); + if (packetType == MODE_LONG_RANGE_MODE_6X) { + if (attempt > 0) { + Serial.printf("[SX1262] LoRa packet type set after %d attempts\n", attempt + 1); + } + return true; + } + + Serial.printf("[SX1262] SetPacketType LoRa attempt %d/5 read back 0x%02X (%s)\n", + attempt + 1, packetType, packetTypeName(packetType)); + } + + Serial.println("[SX1262] Failed to enter LoRa packet mode"); + return false; +} + +bool SX1262::ensureLoRaMode(const char* context) { + uint8_t packetType = getPacketType(); + if (packetType == MODE_LONG_RANGE_MODE_6X) return true; + + Serial.printf("[SX1262] %s found packet_type=0x%02X (%s), reasserting LoRa\n", + context ? context : "config", packetType, packetTypeName(packetType)); + standby(); + return loraMode(); } void SX1262::rxAntEnable() { @@ -206,6 +265,7 @@ void SX1262::rxAntEnable() { bool SX1262::begin(uint32_t frequency) { _frequency = frequency; reset(); + _imageCalBand = IMAGE_CAL_UNSUPPORTED; if (_busy != -1) { pinMode(_busy, INPUT); } if (!_preinitDone) { if (!preInit()) { @@ -234,7 +294,9 @@ bool SX1262::begin(uint32_t frequency) { calibrate_image(_frequency); // Set LoRa packet type and return to STDBY_XOSC - loraMode(); + if (!loraMode()) { + return false; + } standby(); // Post-calibration diagnostic @@ -287,6 +349,8 @@ void SX1262::end() { int SX1262::beginPacket(int implicitHeader) { standby(); + if (!ensureLoRaMode("beginPacket")) return 0; + if (implicitHeader) { implicitHeaderMode(); } else { explicitHeaderMode(); } _payloadLength = 0; _fifo_tx_addr_ptr = 0; @@ -295,6 +359,7 @@ int SX1262::beginPacket(int implicitHeader) { } int SX1262::endPacket(bool async) { + if (!ensureLoRaMode("endPacket")) return 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); uint8_t timeout[3] = {0}; @@ -378,6 +443,8 @@ size_t SX1262::write(const uint8_t* buffer, size_t size) { } void SX1262::receive(int size) { + if (!ensureLoRaMode("receive")) return; + uint8_t clear[2] = {0xFF, 0xFF}; executeOpcode(OP_CLEAR_IRQ_STATUS_6X, clear, 2); @@ -521,6 +588,12 @@ uint8_t SX1262::getStatus() { return buf[0]; } +uint8_t SX1262::getPacketType() { + uint8_t buf[1] = {0xFF}; + executeOpcodeRead(OP_GET_PACKET_TYPE_6X, buf, 1); + return buf[0]; +} + uint16_t SX1262::getIrqFlags() { uint8_t buf[2] = {0}; executeOpcodeRead(OP_GET_IRQ_STATUS_6X, buf, 2); @@ -535,6 +608,8 @@ bool IRAM_ATTR SX1262::getPacketValidity() { } void SX1262::setFrequency(uint32_t frequency) { + calibrate_image(frequency); + standby(); _frequency = frequency; uint32_t freq = (uint32_t)((double)frequency / (double)FREQ_STEP_6X); uint8_t buf[4]; @@ -628,11 +703,15 @@ void SX1262::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro) { // SetModulationParams is only valid in STDBY mode (SX1262 DS Table 11-2). // Calling from RX/TX mode is silently rejected by the hardware. standby(); + if (!ensureLoRaMode("setModulationParams")) return; + uint8_t buf[4] = {sf, bw, cr, (uint8_t)ldro}; executeOpcode(OP_MODULATION_PARAMS_6X, buf, 4); } void SX1262::setPacketParams(uint32_t preamble, uint8_t headermode, uint8_t length, uint8_t crc) { + if (!ensureLoRaMode("setPacketParams")) return; + uint8_t buf[6]; buf[0] = (uint8_t)((preamble & 0xFF00) >> 8); buf[1] = (uint8_t)(preamble & 0x00FF); diff --git a/src/radio/SX1262.h b/src/radio/SX1262.h index 8cb6050..6910cb0 100644 --- a/src/radio/SX1262.h +++ b/src/radio/SX1262.h @@ -60,6 +60,7 @@ public: uint16_t getDeviceErrors(); void clearDeviceErrors(); uint8_t getStatus(); + uint8_t getPacketType(); uint16_t getIrqFlags(); float getAirtime(uint16_t written); @@ -91,10 +92,11 @@ private: void writeBuffer(const uint8_t* buffer, size_t size); void waitOnBusy(); - void loraMode(); + bool loraMode(); + bool ensureLoRaMode(const char* context); void rxAntEnable(); void calibrate(); - void calibrate_image(uint32_t frequency); + bool calibrate_image(uint32_t frequency); void enableTCXO(); void setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro); void setPacketParams(uint32_t preamble, uint8_t headermode, uint8_t length, uint8_t crc); @@ -131,6 +133,7 @@ private: bool _radioOnline = false; bool _tcxo = false; bool _dio2_as_rf_switch = false; + uint8_t _imageCalBand = 0xFF; bool _txActive = false; uint32_t _txStartMs = 0; uint32_t _txTimeoutMs = 0;