diff --git a/Boards.h b/Boards.h index 462eefd..f316088 100755 --- a/Boards.h +++ b/Boards.h @@ -151,6 +151,10 @@ #endif #endif + #define LORA_PA_UNKNOWN 0x00 + #define LORA_PA_GC1109 0x01 + #define LORA_PA_KCT8103L 0x02 + #define HAS_DISPLAY false #define HAS_BLUETOOTH false #define HAS_BLE false @@ -363,7 +367,7 @@ #define HAS_SLEEP true #define PIN_WAKEUP GPIO_NUM_0 #define WAKEUP_LEVEL 0 - #define OCP_TUNED 0x18 + #define OCP_TUNED 0x28 const int pin_btn_usr1 = 0; @@ -411,8 +415,9 @@ #define HAS_LORA_LNA true #define PIN_WAKEUP GPIO_NUM_0 #define WAKEUP_LEVEL 0 - #define OCP_TUNED 0x18 + #define OCP_TUNED 0x28 #define Vext GPIO_NUM_36 + #define LORA_PA_MODEL LORA_PA_UNKNOWN; const int pin_btn_usr1 = 0; @@ -434,14 +439,17 @@ #define LORA_LNA_GAIN 17 #define LORA_LNA_GVT 12 - #define LORA_PA_GC1109 true #define LORA_PA_PWR_EN 7 - #define LORA_PA_CSD 2 - #define LORA_PA_CPS 46 + #define LORA_PA_CSD 2 // Same pin on GC1109 + #define LORA_PA_CPS 46 // Same pin on GC1109 + #define LORA_PA_CTX 5 // Only used on KCT8103 #define PA_MAX_OUTPUT 28 #define PA_GAIN_POINTS 22 - #define PA_GAIN_VALUES 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7 + + #define LORA_LNA_KCT8103L_GAIN 21 + const int PA_GC1109_VALUES[PA_GAIN_POINTS] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7}; + const int PA_KCT8103L_VALUES[PA_GAIN_POINTS] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7}; const int pin_cs = 8; const int pin_busy = 13; @@ -529,13 +537,10 @@ const int pin_np = 12; const int pin_dac = 25; const int pin_adc = 34; - // CBA already defined by framework - //const int SD_MISO = 2; - // CBA already defined by framework - //const int SD_MOSI = 15; + const int SD_MISO = 2; + const int SD_MOSI = 15; const int SD_CLK = 14; - // CBA already defined by framework - //const int SD_CS = 13; + const int SD_CS = 13; #if HAS_NP == false #if defined(EXTERNAL_LEDS) const int pin_led_rx = 12; @@ -980,7 +985,7 @@ // Default OCP value if not specified // in board configuration #ifndef OCP_TUNED - #define OCP_TUNED 0x18 + #define OCP_TUNED 0x28 #endif #ifndef PA_MAX_OUTPUT diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino index 402df88..a870f30 100755 --- a/RNode_Firmware.ino +++ b/RNode_Firmware.ino @@ -1586,7 +1586,7 @@ void serial_callback(uint8_t sbyte) { if (txp > 13) txp = 13; #endif #else - if (txp > 17) txp = 17; + if (txp > 20) txp = 20; #endif lora_txp = txp; @@ -2648,7 +2648,13 @@ void sleep_now() { #if BOARD_MODEL == BOARD_HELTEC32_V4 headless_led_off(); headless_led_detach_pwm(); - digitalWrite(LORA_PA_CPS, LOW); + #if LORA_PA_AUTO_DETECT + if (sx126x_modem.isKCT8103L()) { + digitalWrite(LORA_PA_CTX, LOW); + } else { + digitalWrite(LORA_PA_CPS, LOW); + } + #endif digitalWrite(LORA_PA_CSD, LOW); digitalWrite(LORA_PA_PWR_EN, LOW); digitalWrite(Vext, HIGH); diff --git a/Utilities.h b/Utilities.h index 6db06b9..a3100d8 100755 --- a/Utilities.h +++ b/Utilities.h @@ -1383,11 +1383,33 @@ int getTxPower() { } #if HAS_LORA_PA + #if BOARD_MODEL == BOARD_HELTEC32_V4 + bool pa_values_determined = false; + int tx_gain[PA_GAIN_POINTS] = {100}; + #else + bool pa_values_determined = true; const int tx_gain[PA_GAIN_POINTS] = {PA_GAIN_VALUES}; + #endif #endif +extern uint8_t lora_pa_model; +void determine_pa_values() { + #if BOARD_MODEL == BOARD_HELTEC32_V4 + if (lora_pa_model == LORA_PA_GC1109) { + for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_GC1109_VALUES[i]; } + pa_values_determined = true; + for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); } + } else if (lora_pa_model == LORA_PA_KCT8103L) { + for (int i=0; i < PA_GAIN_POINTS; i++) { tx_gain[i] = PA_KCT8103L_VALUES[i]; } + pa_values_determined = true; + for (int i=0; i < PA_GAIN_POINTS; i++) { Serial.print(" "); Serial.printf("%d", tx_gain[i]); } + } + #endif +} + int map_target_power_to_modem_output(int target_tx_power) { #if HAS_LORA_PA + if (!pa_values_determined) { determine_pa_values(); } int modem_output_dbm = -9; for (int i = 0; i < PA_GAIN_POINTS; i++) { int gain = tx_gain[i]; diff --git a/sx126x.cpp b/sx126x.cpp index 2a46876..799c9c0 100755 --- a/sx126x.cpp +++ b/sx126x.cpp @@ -275,6 +275,21 @@ void sx126x::setPacketParams(long preamble_symbols, uint8_t headermode, uint8_t buf[7] = 0x00; buf[8] = 0x00; executeOpcode(OP_PACKET_PARAMS_6X, buf, 9); + + // SX1262 errata section 15.4: IQ polarity is inverted compared to + // SX1276. The SetPacketParams command resets register 0x0736 to an + // incorrect default. For standard IQ (no inversion), bit 2 must be + // SET after every SetPacketParams call. For inverted IQ, bit 2 must + // be CLEARED. Without this fix, LoRa RX demodulation fails silently + // while TX continues to work. + uint8_t iqreg = readRegister(0x0736); + if (buf[5] == 0x00) { + // Standard IQ: set bit 2 + writeRegister(0x0736, iqreg | 0x04); + } else { + // Inverted IQ: clear bit 2 + writeRegister(0x0736, iqreg & ~0x04); + } } void sx126x::reset(void) { @@ -287,6 +302,22 @@ void sx126x::reset(void) { } } +void sx126x::setDCDCRegulator(void) { + // Documentation + // 5. Power Distribution -> 5.1 Selecting DC-DC Converter or LDO Regulation + // 13.1.11 SetRegulatorMode + + uint8_t mode_byte = MODE_STDBY_RC_6X; + executeOpcode(OP_STANDBY_6X, &mode_byte, 1); + + // Enable DC-DC regulator for high power operation + uint8_t reg_mode = 0x01; // 0x00 = LDO, 0x01 = DC-DC + executeOpcode(OP_REGULATOR_MODE_6X, ®_mode, 1); + + delay(5); + waitOnBusy(); +} + void sx126x::calibrate(void) { // Put in STDBY_RC mode before calibration uint8_t mode_byte = MODE_STDBY_RC_6X; @@ -319,10 +350,19 @@ int sx126x::begin(long frequency) { if (_rxen != -1) { pinMode(_rxen, OUTPUT); } if (_txen != -1) { pinMode(_txen, OUTPUT); } + //TODO: if it works, make it optional + //#ifdef SX1262_USE_DCDC_REGULATOR + setDCDCRegulator(); + //#endif calibrate(); calibrate_image(frequency); - enableTCXO(); + #if HAS_TCXO + enableTCXO(); + //13.1.15 SetRxTxFallbackMode to STDBY_XOSC + uint8_t fallback_mode = 0x30; // STDBY_XOSC after TX/RX + executeOpcode(OP_RX_TX_FALLBACK_MODE_6X, &fallback_mode, 1); + #endif loraMode(); standby(); @@ -339,7 +379,9 @@ int sx126x::begin(long frequency) { setFrequency(frequency); setTxPower(2); enableCrc(); - writeRegister(REG_LNA_6X, 0x96); // Set LNA boost + writeRegister(REG_LNA_6X, 0x96); // Set LNA boosted gain mode + // Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity. + writeRegister(0x08B5, readRegister(0x08B5) | 0x01); uint8_t basebuf[2] = {0}; // Set base addresses executeOpcode(OP_BUFFER_BASE_ADDR_6X, basebuf, 2); @@ -347,7 +389,22 @@ int sx126x::begin(long frequency) { setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); #if HAS_LORA_PA - #if LORA_PA_GC1109 + if (lora_pa_model == LORA_PA_UNKNOWN) { + #if BOARD_MODEL == BOARD_HELTEC32_V4 + + pinMode(LORA_PA_PWR_EN, OUTPUT); + pinMode(LORA_PA_CSD, INPUT); + digitalWrite(LORA_PA_PWR_EN, HIGH); delay(5); + if (digitalRead(LORA_PA_CSD) == HIGH) { + lora_pa_model = LORA_PA_KCT8103L; + lora_lna_gain = LORA_LNA_KCT8103L_GAIN; + } else { + lora_pa_model = LORA_PA_GC1109; + } + #endif + } + + if (lora_pa_model == LORA_PA_GC1109) { // Enable Vfem_ctl for supply to // PA power net. pinMode(LORA_PA_PWR_EN, OUTPUT); @@ -372,7 +429,26 @@ int sx126x::begin(long frequency) { // is driven by the SX1262 DIO2 // pin directly, so we do not // need to manually raise this. - #endif + + } else if (lora_pa_model == LORA_PA_KCT8103L) { + // Enable Vfem_ctl for supply to + // PA power net. + pinMode(LORA_PA_PWR_EN, OUTPUT); + digitalWrite(LORA_PA_PWR_EN, HIGH); + + // Enable KCT8103L chip + pinMode(LORA_PA_CSD, OUTPUT); + digitalWrite(LORA_PA_CSD, HIGH); + + // Enable receive LNA + pinMode(LORA_PA_CTX, OUTPUT); + digitalWrite(LORA_PA_CTX, LOW); + + // On Heltec V4.3, the PA CPS pin + // is driven by the SX1262 DIO2 + // pin directly, so we do not + // need to manually raise this. + } #endif return 1; @@ -382,14 +458,17 @@ void sx126x::end() { sleep(); SPI.end(); _preinit_done = false; } int sx126x::beginPacket(int implicitHeader) { #if HAS_LORA_PA - #if LORA_PA_GC1109 + if (lora_pa_model == LORA_PA_GC1109) { // Enable PA CPS for transmit // digitalWrite(LORA_PA_CPS, HIGH); // Disabled since we're keeping it // on permanently as long as the // radio is powered up. - #endif + } else if (lora_pa_model == LORA_PA_KCT8103L) { + digitalWrite(LORA_PA_CTX, HIGH); + } #endif + if (_txen != -1) { digitalWrite(_txen, HIGH); } //Set TXen high when transmitting standby(); @@ -405,6 +484,7 @@ int sx126x::beginPacket(int implicitHeader) { int sx126x::endPacket() { setPacketParams(_preambleLength, _implicitHeaderMode, _payloadLength, _crcMode); + if (_rxen != -1) { digitalWrite(_rxen, LOW); } //Set RXen low when transmitting uint8_t timeout[3] = {0}; // Put in single TX mode @@ -479,7 +559,7 @@ int ISR_VECT sx126x::currentRssi() { executeOpcodeRead(OP_CURRENT_RSSI_6X, &byte, 1); int rssi = -(int(byte)) / 2; #if HAS_LORA_LNA - rssi -= LORA_LNA_GAIN; + rssi -= lora_lna_gain; #endif return rssi; } @@ -495,7 +575,7 @@ int ISR_VECT sx126x::packetRssi() { executeOpcodeRead(OP_PACKET_STATUS_6X, buf, 3); int pkt_rssi = -buf[0] / 2; #if HAS_LORA_LNA - pkt_rssi -= LORA_LNA_GAIN; + pkt_rssi -= lora_lna_gain; #endif return pkt_rssi; } @@ -602,7 +682,7 @@ void sx126x::onReceive(void(*callback)(int)){ void sx126x::receive(int size) { #if HAS_LORA_PA - #if LORA_PA_GC1109 + if (lora_pa_model == LORA_PA_GC1109) { // Disable PA CPS for receive // digitalWrite(LORA_PA_CPS, LOW); // That turned out to be a bad idea. @@ -610,7 +690,9 @@ void sx126x::receive(int size) { // on and off too quickly. We'll keep // it on permanently, as long as the // radio is powered up. - #endif + } else if (lora_pa_model == LORA_PA_KCT8103L) { + digitalWrite(LORA_PA_CTX, LOW); + } #endif if (size > 0) { diff --git a/sx126x.h b/sx126x.h index 9685094..3246b04 100755 --- a/sx126x.h +++ b/sx126x.h @@ -74,6 +74,7 @@ public: void disableCrc(); void enableTCXO(); void disableTCXO(); + void setDCDCRegulator(); void rxAntEnable(); void loraMode(); @@ -97,6 +98,8 @@ public: void dumpRegisters(Stream& out); + bool isKCT8103L() { return _kct8103l; } + private: void explicitHeaderMode(); void implicitHeaderMode(); @@ -107,7 +110,8 @@ public: // Poll for deferred DIO0 interrupt (call from main loop) void pollDio0(); -private: uint8_t readRegister(uint16_t address); +private: + uint8_t readRegister(uint16_t address); void writeRegister(uint16_t address, uint8_t value); uint8_t singleTransfer(uint8_t opcode, uint16_t address, uint8_t value); @@ -142,6 +146,7 @@ private: int _fifo_rx_addr_ptr; uint8_t _packet[255]; bool _preinit_done; + bool _kct8103l; volatile bool _dio0_risen; void (*_onReceive)(int); };