diff --git a/bin/config.d/femtofox/femtofox_E80-900M2213S.yaml b/bin/config.d/femtofox/femtofox_E80-900M2213S.yaml index 2f2b24603..b3f6328cd 100644 --- a/bin/config.d/femtofox/femtofox_E80-900M2213S.yaml +++ b/bin/config.d/femtofox/femtofox_E80-900M2213S.yaml @@ -1,30 +1,31 @@ --- Lora: -## Ebyte E80-900M22S -## This is a bit experimental -## -## + ## Ebyte E80-900M2213S + ## This is a bit experimental + ## + ## Module: lr1121 gpiochip: 1 # subtract 32 from the gpio numbers DIO3_TCXO_VOLTAGE: 1.8 CS: 16 #pin6 / GPIO48 1C0 - IRQ: 23 #pin17 / GPIO55 1C7 + IRQ: 23 #pin17 / GPIO55 1C7 Busy: 22 #pin16 / GPIO54 1C6 Reset: 25 #pin13 / GPIO57 1D1 - spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19) spiSpeed: 2000000 - -rfswitch_table: - pins: [DIO5, DIO6, DIO7] - MODE_STBY: [LOW, LOW, LOW] - MODE_RX: [LOW, HIGH, LOW] - MODE_TX: [HIGH, HIGH, LOW] - MODE_TX_HP: [HIGH, LOW, LOW] - MODE_TX_HF: [LOW, LOW, LOW] - MODE_GNSS: [LOW, LOW, HIGH] - MODE_WIFI: [LOW, LOW, LOW] + + # CDEbyte do not follow the Semtech reference switching: DIO5/DIO6 are swapped. + # DIO7 is wired to the GNSS path. See variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md + rfswitch_table: + pins: [DIO5, DIO6, DIO7] + MODE_STBY: [LOW, LOW, LOW] + MODE_RX: [LOW, HIGH, LOW] + MODE_TX: [HIGH, HIGH, LOW] + MODE_TX_HP: [HIGH, LOW, LOW] + MODE_TX_HF: [LOW, LOW, LOW] + MODE_GNSS: [LOW, LOW, HIGH] + MODE_WIFI: [LOW, LOW, LOW] General: MACAddressSource: eth0 diff --git a/bin/config.d/femtofox/femtofox_LR1121 generic.yaml b/bin/config.d/femtofox/femtofox_LR1121 generic.yaml index c66eebed5..a6c6b03b4 100644 --- a/bin/config.d/femtofox/femtofox_LR1121 generic.yaml +++ b/bin/config.d/femtofox/femtofox_LR1121 generic.yaml @@ -1,46 +1,28 @@ --- Lora: -## Ebyte E80-900M22S -## This is a bit experimental -## -## + ## Generic LR1121 module (Semtech reference RF switch topology) + ## This is a bit experimental Module: lr1121 gpiochip: 1 # subtract 32 from the gpio numbers DIO3_TCXO_VOLTAGE: 1.8 CS: 16 #pin6 / GPIO48 1C0 - IRQ: 23 #pin17 / GPIO55 1C7 + IRQ: 23 #pin17 / GPIO55 1C7 Busy: 22 #pin16 / GPIO54 1C6 Reset: 25 #pin13 / GPIO57 1D1 - spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19) spiSpeed: 2000000 - -rfswitch_table: - pins: - - DIO5 - - DIO6 - MODE_STBY: - - LOW - - LOW - MODE_RX: - - HIGH - - LOW - MODE_TX: - - HIGH - - HIGH - MODE_TX_HP: - - LOW - - HIGH - MODE_TX_HF: - - LOW - - LOW - MODE_GNSS: - - LOW - - LOW - MODE_WIFI: - - LOW - - LOW + + # Semtech reference switching (SWSD006): rx=RFSW0, tx=RFSW0|RFSW1, tx_hp=RFSW1. + rfswitch_table: + pins: [DIO5, DIO6] + MODE_STBY: [LOW, LOW] + MODE_RX: [HIGH, LOW] + MODE_TX: [HIGH, HIGH] + MODE_TX_HP: [LOW, HIGH] + MODE_TX_HF: [LOW, LOW] + MODE_GNSS: [LOW, LOW] + MODE_WIFI: [LOW, LOW] General: MACAddressSource: eth0 diff --git a/bin/config.d/femtofox/femtofox_WIO-LR1121.yaml b/bin/config.d/femtofox/femtofox_WIO-LR1121.yaml index c2ab76d46..fa9b4c666 100644 --- a/bin/config.d/femtofox/femtofox_WIO-LR1121.yaml +++ b/bin/config.d/femtofox/femtofox_WIO-LR1121.yaml @@ -1,30 +1,37 @@ --- Lora: -## Ebyte E80-900M22S -## This is a bit experimental -## -## + ## Seeed Wio-LR1121 + ## This is a bit experimental Module: lr1121 gpiochip: 1 # subtract 32 from the gpio numbers DIO3_TCXO_VOLTAGE: 1.8 CS: 16 #pin6 / GPIO48 1C0 - IRQ: 23 #pin17 / GPIO55 1C7 + IRQ: 23 #pin17 / GPIO55 1C7 Busy: 22 #pin16 / GPIO54 1C6 Reset: 25 #pin13 / GPIO57 1D1 - spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19) spiSpeed: 2000000 - -rfswitch_table: - pins: [DIO5, DIO6, DIO7] - MODE_STBY: [LOW, LOW, LOW] - MODE_RX: [LOW, LOW, LOW] - MODE_TX: [LOW, HIGH, LOW] - MODE_TX_HP: [HIGH, LOW, LOW] - # MODE_TX_HF: [] - # MODE_GNSS: [] - MODE_WIFI: [LOW, LOW, LOW] + + # Internal RF switch is a Skyworks SKY13373-460LF driven by DIO5 (V1) and DIO6 (V2). + # Datasheet section 4.5 "True Table of the Internal RF Switch": + # V1 V2 Status + # 0 0 Shutdown -> MODE_STBY + # 1 0 RFI_P_LF & RFI_N_LF -> MODE_RX + # 0 1 RFO_HP_LF -> MODE_TX_HP + # 1 1 RFO_LP_LF -> MODE_TX + # This matches the Semtech reference topology (SWSD006): + # rx=RFSW0, tx=RFSW0|RFSW1, tx_hp=RFSW1. + # The LR1121 has no GNSS/WiFi scanning, so those modes are unused. + rfswitch_table: + pins: [DIO5, DIO6] + MODE_STBY: [LOW, LOW] + MODE_RX: [HIGH, LOW] + MODE_TX: [HIGH, HIGH] + MODE_TX_HP: [LOW, HIGH] + MODE_TX_HF: [LOW, LOW] + MODE_GNSS: [LOW, LOW] + MODE_WIFI: [LOW, LOW] General: MACAddressSource: eth0 diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 8bd22bd35..b7a6040e3 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -47,7 +47,6 @@ static const Module::RfSwitchMode_t rfswitch_table[] = { #endif // the 2.4G part maxes at 13dBm - #if ARCH_PORTDUINO #define LR1120_MAX_POWER portduino_config.lr1120_max_power #endif @@ -55,6 +54,23 @@ static const Module::RfSwitchMode_t rfswitch_table[] = { #define LR1120_MAX_POWER 13 #endif +// Vref to assume for a board that declares a TCXO may be fitted without saying at what voltage. +// "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per +// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104 +#if defined(TCXO_OPTIONAL) +#define LR11X0_TCXO_DEFAULT_VOLTAGE 1.6f +#else +#define LR11X0_TCXO_DEFAULT_VOLTAGE 0 +#endif + +// A chip that never answers can surface either way depending on where RadioLib gave up: a bounded +// per-command BUSY wait in Module::SPItransferStream() reports SPI_CMD_TIMEOUT rather than +// SPI_CMD_FAILED, so both have to count as "the chip did not talk to us" +static inline bool lr11x0SpiFailed(int res) +{ + return res == RADIOLIB_ERR_SPI_CMD_FAILED || res == RADIOLIB_ERR_SPI_CMD_TIMEOUT; +} + template LR11x0Interface::LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) @@ -73,22 +89,26 @@ template bool LR11x0Interface::init() digitalWrite(LR11X0_POWER_EN, HIGH); #endif + // An explicit Vref always wins; TCXO_OPTIONAL only supplies a default for boards that declare a + // TCXO may be fitted without saying at what voltage. Both may appear in the same variant file. #if ARCH_PORTDUINO - float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000; -// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE + // Portduino leaves dio3_tcxo_voltage at 0 whenever the YAML omits DIO3_TCXO_VOLTAGE, which is the + // "no explicit Vref" case, so the TCXO_OPTIONAL default still has to apply there + float tcxoVoltage = + portduino_config.dio3_tcxo_voltage > 0 ? (float)portduino_config.dio3_tcxo_voltage / 1000 : LR11X0_TCXO_DEFAULT_VOLTAGE; #elif defined(LR11X0_DIO3_TCXO_VOLTAGE) float tcxoVoltage = LR11X0_DIO3_TCXO_VOLTAGE; - LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", LR11X0_DIO3_TCXO_VOLTAGE); - // (DIO3 is not free to be used as an IRQ) -#elif defined(TCXO_OPTIONAL) - float tcxoVoltage = 1.6f; // TCXO_OPTIONAL: try default 1.6 V first, fall back to XTAL on failure - LOG_DEBUG("TCXO_OPTIONAL: no LR11X0_DIO3_TCXO_VOLTAGE defined, trying default TCXO Vref 1.6 V first"); #else - float tcxoVoltage = - 0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per - // https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104 - // (DIO3 is free to be used as an IRQ) - LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage"); + float tcxoVoltage = LR11X0_TCXO_DEFAULT_VOLTAGE; +#endif + + // DIO3 is free to be used as an IRQ only while no TCXO Vref is driven on it + if (tcxoVoltage > 0) + LOG_DEBUG("LR11x0 TCXO Vref %f V on DIO3 (DIO3 unavailable as an IRQ)", tcxoVoltage); + else + LOG_DEBUG("LR11x0 no TCXO Vref, XTAL only (DIO3 free as an IRQ)"); +#if defined(TCXO_OPTIONAL) + LOG_DEBUG("TCXO_OPTIONAL: oscillator type unknown, probing XTAL first and using any TCXO Vref only as fallback"); #endif RadioLibInterface::init(); @@ -114,29 +134,48 @@ template bool LR11x0Interface::init() // Allow extra time for TCXO to stabilize after power-on delay(10); - int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); - - // Retry if we get SPI command failed - some units need extra TCXO stabilization time - if (res == RADIOLIB_ERR_SPI_CMD_FAILED) { - LOG_WARN("LR11x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res); - delay(100); - res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); - } + // Timestamped brackets so a hang inside RadioLib leaves a dangling "attempt" line in the boot log + auto tryBegin = [&](int attempt, float vref) { + uint32_t attemptStart = millis(); + LOG_INFO("LR11x0 begin() attempt %d: tcxoVoltage=%.3fV at t=%ums", attempt, vref, attemptStart); + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, vref); + LOG_INFO("LR11x0 begin() attempt %d returned %d after %ums", attempt, res, millis() - attemptStart); + return res; + }; #if defined(TCXO_OPTIONAL) - // If init failed for any reason other than chip not found, retry without TCXO (XTAL mode) + // 1. XTAL, because a TCXO-first attempt hangs RadioLib's unbounded calibration wait on a module + // with no TCXO fitted, whereas XTAL fails fast and cleanly on a module that does have one + float attemptVoltage = 0; +#else + // 1. Whatever Vref the variant configured, which it declared unconditionally + float attemptVoltage = tcxoVoltage; +#endif + int res = tryBegin(1, attemptVoltage); + +#if defined(TCXO_OPTIONAL) + // 2. XTAL failed with the chip present, so fall back to the TCXO if the variant configured one if (res != RADIOLIB_ERR_NONE && res != RADIOLIB_ERR_CHIP_NOT_FOUND && tcxoVoltage > 0) { - LOG_WARN("LR11x0 init failed with TCXO Vref %f V (err %d), retrying without TCXO", tcxoVoltage, res); - tcxoVoltage = 0; - res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + LOG_WARN("LR11x0 XTAL init failed (err %d), retrying with TCXO Vref %f V", res, tcxoVoltage); + attemptVoltage = tcxoVoltage; + res = tryBegin(2, attemptVoltage); if (res == RADIOLIB_ERR_NONE) - LOG_INFO("LR11x0 init success without TCXO (XTAL mode)"); + LOG_INFO("LR11x0 init success with TCXO Vref %f V", tcxoVoltage); } #endif + // 3. Some units need extra settling time, so give whichever oscillator we settled on one retry. + // After a step 2 fallback that is a second TCXO attempt, which is where settling actually matters. + if (lr11x0SpiFailed(res)) { + LOG_WARN("LR11x0 init failed with %d (SPI command failure), retrying after delay...", res); + delay(100); + res = tryBegin(3, attemptVoltage); + } + // \todo Display actual typename of the adapter, not just `LR11x0` LOG_INFO("LR11x0 init result %d", res); - if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) { + + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || lr11x0SpiFailed(res)) { #ifdef LR11X0_UPDATE_FIRMWARE_TO // An interrupted update leaves the radio sitting in bootloader mode, where begin() fails. Retry the // flash from here rather than giving up, otherwise the device could never recover on its own. diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md index 5d3d90c72..3c138ac55 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/readme.md @@ -42,7 +42,7 @@ Making your own node based on this design is straightforward. There are various | Waveshare | Core1262-HF | yes | Ext | | | Waveshare | LoRa Node Module | yes | Int | | | Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed | -| Seeed | Wio-LR1121 | yes | Int | LR1121, needs alternate rfswitch.h | +| Seeed | Wio-LR1121 | yes | Int | LR1121, build -D LR1121_MODULE_WIO | | AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** | | RF Solutions | RFM95 | No | Int | Untested | | Ebyte | E80-900M2213S | Yes | Int | LR1121 radio | @@ -55,7 +55,24 @@ The E80 from CDEbyte is the most obtainable module at present, and has been sele Naturally, CDEbyte have chosen to ignore the generic Semtech implementation of the RF switching logic and have supplied confusing and contradictory documentation, which is explained below. -tl;dr: The E80 is chosen as the default. **If you wish to use another module, the table in `rfswitch.h` must be adjusted accordingly.** +tl;dr: The E80 is chosen as the default. **If you wish to use another module, select it with an `LR1121_MODULE_*` build flag, or adjust the table in `rfswitch.h` accordingly.** + +`rfswitch.h` carries a matrix for each known LR1121 module, guarded by a build flag: + +| Build flag | Module | +| ------------------- | ------------------- | +| _(none)_ | Ebyte E80-900M2213S | +| `LR1121_MODULE_E80` | Ebyte E80-900M2213S | +| `LR1121_MODULE_WIO` | Seeed Wio-LR1121 | + +Add it to your environment in `platformio.ini`: + +```ini +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/diy/nrf52_promicro_diy_tcxo + -D NRF52_PROMICRO_DIY + -D LR1121_MODULE_WIO +``` ### E80 switching - the saga @@ -82,7 +99,7 @@ The Semtech default, the values are (taken from [here](https://github.com/Lora-n
< Click to expand > - + ```cpp @@ -119,4 +136,19 @@ Tests were conducted in each of the three configurations between a known-good SX The RF switching is based on the code example given. Logically, this shows the DIO5 and DIO6 are swapped compared to the reference design. -If future DIYers wish to use an alternative module, the table in `rfswitch.h` must be adjusted accordingly. +## Seeed Wio-LR1121 + +The Wio is the counter-example to the E80: it follows the Semtech reference exactly, so no swap is needed. Its internal switch is a Skyworks SKY13373-460LF, controlled by DIO5 (V1) and DIO6 (V2) only - there is no third control line, so DIO7 is left out of the pin list. Section 4.5 of the module datasheet, "True Table of the Internal RF Switch", gives: + +| V1 (DIO5) | V2 (DIO6) | Status | RadioLib mode | +| --------- | --------- | ------------------- | ------------- | +| 0 | 0 | Shutdown | `MODE_STBY` | +| 1 | 0 | RFI_P_LF & RFI_N_LF | `MODE_RX` | +| 0 | 1 | RFO_HP_LF | `MODE_TX_HP` | +| 1 | 1 | RFO_LP_LF | `MODE_TX` | + +Note that the LR1121 has no GNSS or WiFi scanning, so `MODE_GNSS` and `MODE_WIFI` are left in the shutdown state. + +Select it with `-D LR1121_MODULE_WIO`. + +If future DIYers wish to use a module not listed above, add a matrix for it to `rfswitch.h` behind a new `LR1121_MODULE_*` guard. diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/rfswitch.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/rfswitch.h index 9c9bbebd4..9e68b1775 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/rfswitch.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/rfswitch.h @@ -6,6 +6,22 @@ #define LR20x0 LR2021 #endif +// LR1121 modules differ in how their internal RF switch is wired, so the matrix must match the +// module actually fitted. The Ebyte E80-900M2213S is the default; define one of the alternatives +// below in your build_flags (or above the #include of this header) to select a different module. +// +// LR1121_MODULE_E80 Ebyte E80-900M2213S (default if nothing is defined) +// LR1121_MODULE_WIO Seeed Wio-LR1121 +#ifdef USE_LR1121 + +#if !defined(LR1121_MODULE_E80) && !defined(LR1121_MODULE_WIO) +#define LR1121_MODULE_E80 +#endif +#if defined(LR1121_MODULE_E80) && defined(LR1121_MODULE_WIO) +#error "Define only one LR1121_MODULE_* option" +#endif + +#ifdef LR1121_MODULE_E80 // This is rewritten to match the requirements of the E80-900M2213S // The E80 does not conform to the reference Semtech switches(!) and therefore needs a custom matrix. // See footnote #3 in "https://www.cdebyte.com/products/E80-900M2213S/2#Pin" @@ -13,7 +29,6 @@ // DIO5 -> RFSW0_V1 // DIO6 -> RFSW1_V2 // DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible. -#ifdef USE_LR1121 static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC}; @@ -30,7 +45,39 @@ static const Module::RfSwitchMode_t rfswitch_table[] = { END_OF_MODE_TABLE, // clang-format on }; -#endif +#endif // LR1121_MODULE_E80 + +#ifdef LR1121_MODULE_WIO +// Seeed Wio-LR1121. Unlike the E80 this module DOES follow the Semtech reference topology +// (SWSD006: rx = RFSW0, tx = RFSW0|RFSW1, tx_hp = RFSW1). +// The internal switch is a Skyworks SKY13373-460LF driven by two lines only: +// DIO5 -> V1 +// DIO6 -> V2 +// Datasheet section 4.5, "True Table of the Internal RF Switch": +// V1 V2 Status +// 0 0 Shutdown +// 1 0 RFI_P_LF & RFI_N_LF (receive) +// 0 1 RFO_HP_LF (sub-GHz high power) +// 1 1 RFO_LP_LF (sub-GHz low power) +// The LR1121 has no GNSS or WiFi scanning, so those modes are left in the shutdown state. +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // clang-format off + // mode DIO5 DIO6 + {LR11x0::MODE_STBY, {LOW, LOW}}, + {LR11x0::MODE_RX, {HIGH, LOW}}, + {LR11x0::MODE_TX, {HIGH, HIGH}}, + {LR11x0::MODE_TX_HP, {LOW, HIGH}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, + {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, + END_OF_MODE_TABLE, + // clang-format on +}; +#endif // LR1121_MODULE_WIO + +#endif // USE_LR1121 // LR2021 RF switch matrix following the standard Semtech / Seeed T1000-E reference topology. // DIO5 -> antenna path select (HIGH = sub-GHz LF) diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index 9220b9c30..323873660 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -262,7 +262,7 @@ settings. | Waveshare | Core1262-HF | yes | Ext | | | Waveshare | LoRa Node Module | yes | Int | | | Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed | -| Seeed | Wio-LR1121 | yes | Int | LR1121, needs alternate rfswitch.h | +| Seeed | Wio-LR1121 | yes | Int | LR1121, build -D LR1121_MODULE_WIO | | AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** | | RF Solutions | RFM95 | No | Int | Untested | | Ebyte | E80-900M2213S | Yes | Int | LR1121 radio |