From b4553a22998430ecce094ab0ac9b2c52bf788b3e Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Thu, 21 May 2026 23:11:23 -0600 Subject: [PATCH] radio: harden T-Deck SX1262 link --- src/config/BoardConfig.h | 4 +- src/main.cpp | 142 +++++++++++++++++++++++++++++++++++++++ src/radio/SX1262.cpp | 52 ++++++++++---- src/radio/SX1262.h | 4 ++ 4 files changed, 188 insertions(+), 14 deletions(-) diff --git a/src/config/BoardConfig.h b/src/config/BoardConfig.h index 24fc265..231c230 100644 --- a/src/config/BoardConfig.h +++ b/src/config/BoardConfig.h @@ -19,8 +19,10 @@ // --- SX1262 Radio Configuration --- #define LORA_HAS_TCXO true #define LORA_DIO2_AS_RF_SWITCH true -// TCXO voltage: 1.8V for T-Deck Plus integrated SX1262 (Ratputer Cap LoRa uses 3.0V/0x06) +// TCXO voltage: 1.8V for T-Deck Plus integrated SX1262 (Cardputer Cap LoRa uses 3.0V/0x06) #define LORA_TCXO_VOLTAGE 0x02 // MODE_TCXO_1_8V_6X +#define LORA_USE_DCDC_REGULATOR true // T-Deck Plus integrated SX1262 works in DC-DC regulator mode +#define LORA_OCP_TUNED 0x38 // Existing T-Deck PA tuning #define LORA_DEFAULT_FREQ 915000000 #define LORA_DEFAULT_BW 250000 // Long Fast preset #define LORA_DEFAULT_SF 11 diff --git a/src/main.cpp b/src/main.cpp index 988d232..0b67892 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -388,12 +388,18 @@ void onHotkeyDiag() { (unsigned long)radio.getSignalBandwidth(), radio.getCodingRate4(), radio.getTxPower()); + Serial.printf("Regulator: %s\n", LORA_USE_DCDC_REGULATOR ? "DC-DC" : "LDO"); Serial.printf("Preamble: %ld symbols\n", radio.getPreambleLength()); + Serial.printf("IQ invert: %s\n", radio.getInvertIQ() ? "ON" : "off"); + Serial.printf("SyncWord regs: 0x%02X%02X\n", + radio.readRegister(REG_SYNC_WORD_MSB_6X), + radio.readRegister(REG_SYNC_WORD_LSB_6X)); uint16_t devErr = radio.getDeviceErrors(); uint8_t status = radio.getStatus(); Serial.printf("DevErrors: 0x%04X Status: 0x%02X (mode=%d cmd=%d)\n", devErr, status, (status >> 4) & 0x07, (status >> 1) & 0x07); if (devErr & 0x40) Serial.println(" *** PLL LOCK FAILED ***"); + Serial.printf("IRQ flags: 0x%04X\n", radio.getIrqFlags()); Serial.printf("Current RSSI: %d dBm\n", radio.currentRssi()); uint8_t packetType = radio.getPacketType(); const char* packetTypeName = @@ -410,6 +416,45 @@ void onHotkeyDiag() { Serial.println("======================="); } +static void printIrqFlags(uint16_t flags) { + Serial.printf("0x%04X", flags); + if (flags & 0x0001) Serial.print(" TX_DONE"); + if (flags & 0x0002) Serial.print(" RX_DONE"); + if (flags & 0x0004) Serial.print(" PREAMBLE"); + if (flags & 0x0008) Serial.print(" SYNC"); + if (flags & 0x0010) Serial.print(" HEADER_VALID"); + if (flags & 0x0020) Serial.print(" HEADER_ERR"); + if (flags & 0x0040) Serial.print(" CRC_ERR"); + if (flags & 0x0080) Serial.print(" CAD_DONE"); + if (flags & 0x0100) Serial.print(" CAD_DET"); + if (flags & 0x0200) Serial.print(" TIMEOUT"); +} + +void onHotkeyIrqMonitor() { + if (!radioOnline) { Serial.println("[IRQ] Radio offline"); return; } + + radio.receive(); + Serial.println("[IRQ] Sampling IRQ/RSSI for 5 seconds..."); + uint16_t lastFlags = 0xFFFF; + unsigned long start = millis(); + unsigned long lastLine = 0; + while (millis() - start < 5000) { + uint16_t flags = radio.getIrqFlags(); + unsigned long now = millis(); + if (flags != lastFlags || now - lastLine >= 500) { + Serial.printf("[IRQ] t=%lums rssi=%d flags=", + now - start, radio.currentRssi()); + printIrqFlags(flags); + Serial.println(); + lastFlags = flags; + lastLine = now; + } + delay(50); + } + radio.receive(); + Serial.println("[IRQ] Done"); +} + // RSSI monitor — non-blocking state machine (sampled in main loop) volatile bool rssiMonitorActive = false; unsigned long rssiMonitorStart = 0; @@ -446,6 +491,101 @@ void onHotkeyRadioTest() { radio.receive(); } +static void cycleDiagnosticTxPower() { + static constexpr int8_t kPowers[] = {-9, -3, 0, 2, 6, 10, 14, 17, 22}; + int current = radio.getTxPower(); + size_t next = 0; + for (size_t i = 0; i < sizeof(kPowers) / sizeof(kPowers[0]); i++) { + if (current == kPowers[i]) { + next = (i + 1) % (sizeof(kPowers) / sizeof(kPowers[0])); + break; + } + } + + radio.setTxPower(kPowers[next]); + radio.receive(); + Serial.printf("[SERIAL] transient TX power set to %d dBm\n", (int)kPowers[next]); +} + +static void setDiagnosticMinTxPower() { + radio.setTxPower(-9); + radio.receive(); + Serial.println("[SERIAL] transient TX power set to -9 dBm"); +} + +static void toggleDiagnosticInvertIQ() { + radio.setInvertIQ(!radio.getInvertIQ()); + radio.receive(); + Serial.printf("[SERIAL] IQ inversion %s\n", radio.getInvertIQ() ? "ON" : "off"); +} + +static void nudgeDiagnosticFrequency(int32_t deltaHz) { + uint32_t next = radio.getFrequency() + deltaHz; + radio.setFrequency(next); + radio.receive(); + Serial.printf("[SERIAL] transient frequency set to %lu Hz\n", (unsigned long)next); +} + +static void printSerialHelp() { + Serial.println("[SERIAL] commands: ? help | a announce | t raw-test | d diag | r rssi | i irq | p tx-power | m min-power | q iq | +/- freq"); +} + +static void handleSerialCommands() { + while (Serial.available() > 0) { + char c = (char)Serial.read(); + if (c == '\r' || c == '\n' || c == ' ') continue; + switch (c) { + case '?': + printSerialHelp(); + break; + case 'a': + case 'A': + onHotkeyAnnounce(); + break; + case 't': + case 'T': + onHotkeyRadioTest(); + break; + case 'd': + case 'D': + onHotkeyDiag(); + break; + case 'r': + case 'R': + onHotkeyRssiMonitor(); + break; + case 'i': + case 'I': + onHotkeyIrqMonitor(); + break; + case 'p': + case 'P': + cycleDiagnosticTxPower(); + break; + case 'm': + case 'M': + setDiagnosticMinTxPower(); + break; + case 'q': + case 'Q': + toggleDiagnosticInvertIQ(); + break; + case '+': + case '=': + nudgeDiagnosticFrequency(1000); + break; + case '-': + case '_': + nudgeDiagnosticFrequency(-1000); + break; + default: + Serial.printf("[SERIAL] unknown command '%c'\n", c); + printSerialHelp(); + break; + } + } +} + // ============================================================================= // Helper: render boot screen immediately // ============================================================================= @@ -1251,6 +1391,8 @@ void setup() { // ============================================================================= void loop() { + handleSerialCommands(); + // 1. Input polling inputManager.update(); if (inputManager.hadStrongActivity()) { diff --git a/src/radio/SX1262.cpp b/src/radio/SX1262.cpp index e07fd2b..9369839 100644 --- a/src/radio/SX1262.cpp +++ b/src/radio/SX1262.cpp @@ -224,6 +224,13 @@ void SX1262::enableTCXO() { } } +void SX1262::enableDio2RfSwitch() { + if (_dio2_as_rf_switch) { + uint8_t byte = 0x01; + executeOpcode(OP_DIO2_RF_CTRL_6X, &byte, 1); + } +} + bool SX1262::loraMode() { uint8_t mode = MODE_LONG_RANGE_MODE_6X; for (int attempt = 0; attempt < 5; attempt++) { @@ -285,8 +292,11 @@ bool SX1262::begin(uint32_t frequency) { // Force STDBY_XOSC to start the TCXO oscillator standby(); - // Set DC-DC regulator mode (both T-Deck Plus and Cap LoRa-1262 have inductors) - uint8_t regMode = 0x01; // 0x00=LDO (default), 0x01=DC-DC + // Set regulator mode. The T-Deck Plus integrated radio works in DC-DC + // mode; cap-style modules may need board-specific LDO-only mode. + uint8_t regMode = LORA_USE_DCDC_REGULATOR ? 0x01 : 0x00; // 0x00=LDO, 0x01=DC-DC + Serial.printf("[SX1262] Regulator mode: %s\n", + LORA_USE_DCDC_REGULATOR ? "DC-DC" : "LDO"); executeOpcode(OP_REGULATOR_MODE_6X, ®Mode, 1); // Calibrate from STDBY_RC with TCXO already running @@ -308,10 +318,7 @@ bool SX1262::begin(uint32_t frequency) { setSyncWord(SYNC_WORD_6X); - if (_dio2_as_rf_switch) { - uint8_t byte = 0x01; - executeOpcode(OP_DIO2_RF_CTRL_6X, &byte, 1); - } + enableDio2RfSwitch(); rxAntEnable(); setFrequency(_frequency); @@ -330,6 +337,7 @@ bool SX1262::begin(uint32_t frequency) { irqBuf[4] = 0x00; irqBuf[5] = 0x00; irqBuf[6] = 0x00; irqBuf[7] = 0x00; executeOpcode(OP_SET_IRQ_FLAGS_6X, irqBuf, 8); + enableDio2RfSwitch(); // Keep TCXO running between TX/RX transitions (don't fall back to RC oscillator) uint8_t fallback = MODE_FALLBACK_STDBY_XOSC_6X; @@ -361,6 +369,7 @@ int SX1262::beginPacket(int implicitHeader) { int SX1262::endPacket(bool async) { if (!ensureLoRaMode("endPacket")) return 0; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + enableDio2RfSwitch(); uint8_t timeout[3] = {0}; _txStartMs = millis(); @@ -461,6 +470,7 @@ void SX1262::receive(int size) { pinMode(_irq, INPUT); uint8_t irqBuf[8] = {0xFF, 0xFF, 0x00, IRQ_RX_DONE_MASK_6X, 0x00, 0x00, 0x00, 0x00}; executeOpcode(OP_SET_IRQ_FLAGS_6X, irqBuf, 8); + enableDio2RfSwitch(); attachInterrupt(digitalPinToInterrupt(_irq), onDio0Rise, RISING); } @@ -630,7 +640,7 @@ void SX1262::setTxPower(int level) { if (level > 22) level = 22; else if (level < -9) level = -9; _txp = level; - writeRegister(REG_OCP_6X, OCP_TUNED); + writeRegister(REG_OCP_6X, LORA_OCP_TUNED); uint8_t tx_buf[2]; tx_buf[0] = level; tx_buf[1] = 0x02; // PA ramp: 40us @@ -689,6 +699,11 @@ void SX1262::setPreambleLength(long length) { setPacketParams(length, _implicitHeaderMode, _payloadLength, _crcMode); } +void SX1262::setInvertIQ(bool invert) { + _invertIq = invert; + setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); +} + void SX1262::enableCrc() { _crcMode = 1; setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); @@ -705,27 +720,37 @@ void SX1262::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, int ldro) { standby(); if (!ensureLoRaMode("setModulationParams")) return; - uint8_t buf[4] = {sf, bw, cr, (uint8_t)ldro}; - executeOpcode(OP_MODULATION_PARAMS_6X, buf, 4); + // Match the RNode SX1262 driver: write the complete LoRa parameter block, + // including reserved trailing bytes, so modem state is not left stale. + uint8_t buf[8] = {sf, bw, cr, (uint8_t)ldro, 0x00, 0x00, 0x00, 0x00}; + executeOpcode(OP_MODULATION_PARAMS_6X, buf, 8); } void SX1262::setPacketParams(uint32_t preamble, uint8_t headermode, uint8_t length, uint8_t crc) { if (!ensureLoRaMode("setPacketParams")) return; - uint8_t buf[6]; + // Match the RNode SX1262 driver: write the complete packet parameter block. + uint8_t buf[9]; buf[0] = (uint8_t)((preamble & 0xFF00) >> 8); buf[1] = (uint8_t)(preamble & 0x00FF); buf[2] = headermode; buf[3] = length; buf[4] = crc; - buf[5] = 0x00; // Standard IQ (no inversion) - executeOpcode(OP_PACKET_PARAMS_6X, buf, 6); + buf[5] = _invertIq ? 0x01 : 0x00; + buf[6] = 0x00; + buf[7] = 0x00; + buf[8] = 0x00; + executeOpcode(OP_PACKET_PARAMS_6X, buf, 9); // SX1262 errata 15.1: IQ polarity register must be corrected after SetPacketParams. // For standard IQ (no inversion), bit 2 of register 0x0736 must be SET. // For inverted IQ, bit 2 must be CLEARED. (RadioLib: SX126x.cpp) uint8_t iqReg = readRegister(REG_IQ_POLARITY_6X); - iqReg |= 0x04; // Standard IQ: set bit 2 + if (_invertIq) { + iqReg &= ~0x04; + } else { + iqReg |= 0x04; + } writeRegister(REG_IQ_POLARITY_6X, iqReg); } @@ -803,6 +828,7 @@ void SX1262::onReceive(void(*callback)(int)) { pinMode(_irq, INPUT); uint8_t buf[8] = {0xFF, 0xFF, 0x00, IRQ_RX_DONE_MASK_6X, 0x00, 0x00, 0x00, 0x00}; executeOpcode(OP_SET_IRQ_FLAGS_6X, buf, 8); + enableDio2RfSwitch(); attachInterrupt(digitalPinToInterrupt(_irq), onDio0Rise, RISING); } else { detachInterrupt(digitalPinToInterrupt(_irq)); diff --git a/src/radio/SX1262.h b/src/radio/SX1262.h index 6910cb0..9983fcc 100644 --- a/src/radio/SX1262.h +++ b/src/radio/SX1262.h @@ -47,6 +47,8 @@ public: void setCodingRate4(int denominator); uint8_t getCodingRate4(); void setPreambleLength(long length); + void setInvertIQ(bool invert); + bool getInvertIQ() const { return _invertIq; } void enableCrc(); void disableCrc(); @@ -98,6 +100,7 @@ private: void calibrate(); bool calibrate_image(uint32_t frequency); void enableTCXO(); + void enableDio2RfSwitch(); 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); void setSyncWord(uint16_t sw); @@ -120,6 +123,7 @@ private: uint8_t _cr = 0; int8_t _txp = 0; bool _ldro = false; + bool _invertIq = false; long _preambleLength = 0; int _packetIndex = 0;