radio: harden T-Deck SX1262 link

This commit is contained in:
DeFiDude
2026-05-21 23:11:23 -06:00
parent 8031fe0c4d
commit b4553a2299
4 changed files with 188 additions and 14 deletions
+3 -1
View File
@@ -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
+142
View File
@@ -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()) {
+39 -13
View File
@@ -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, &regMode, 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));
+4
View File
@@ -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;