mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-06-06 15:51:42 +00:00
radio: harden T-Deck SX1262 link
This commit is contained in:
@@ -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
@@ -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
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user