Skip to content

[Bug]: limitPower() not called during reconfigure() - PA gain compensation lost after non-LoRa config changes #10022

@InspiringCode

Description

@InspiringCode

Category

Other

Hardware

DIY

Is this bug report about any UI component firmware like InkHUD or Meshtatic UI (MUI)?

  • Meshtastic UI aka MUI colorTFT
  • InkHUD ePaper
  • OLED slide UI on any display

Firmware Version

2.7.20.6658ec2

Description

Background/motivation

When testing my new WashTastic board I noticed something very strange. Although I have set the power to 22dBm, I got TX SNRs that were roughly 6dB better than with my SX126x nodes and roughly in the range of a Station G2 configured for 27dBm-30dBm, which didn't seem plausible to me. I immediately checked the source code to see whether it applies a PA_GAIN mapping, which seemed so at first glance, which made it even more strange. After doing quite some investigation, I think I have found the bug that is outlined here. To confirm the bug, I set back the power to 22dBm and did a clean reboot. After that (withoug changing any other setting), my measurements seemed to roughly match the ones from the standard SX126x nodes.

Summary

On boards with an external PA (TX_GAIN_LORA > 0), the PA gain subtraction applied by limitPower() is lost whenever reconfigure() is triggered without a reboot. The SX1262 is programmed with the full user-configured power, bypassing PA gain compensation, resulting in output power exceeding the intended (and potentially regulatory) limit by TX_GAIN_LORA dB.

Hardware

env:WashTastic (ProMicro nRF52840 + EBYTE E22-900M30S)

Affects all boards with non-zero TX_GAIN_LORA or multi-point PA gain arrays (see table below).

Root Cause

Two interacting problems:

1. applyModemConfig() writes power back to loraConfig.tx_power before limitPower() runs

In RadioInterface.cpp lines 828–839:

power = loraConfig.tx_power;                 // power = 22 (user-configured value)
// ... regional clamping ...
loraConfig.tx_power = (int8_t)power;          // writes 22 back — BEFORE limitPower() ever runs

limitPower() is only called later, from the subclass init() method (e.g. SX126xInterface.cpp line 89). It reduces the member variable power (e.g. 22 - 7 = 15), but never writes the corrected value back to loraConfig.tx_power, which remains 22.

2. reconfigure() does not call limitPower()

All radio interface reconfigure() methods call RadioInterface::reconfigure()applyModemConfig() (which reloads the uncorrected loraConfig.tx_power) but skip calling limitPower() before passing power to setOutputPower().

Compare the two code paths:

init() (in SX126xInterface.cpp lines 88–94):

RadioLibInterface::init();          // → applyModemConfig() → power = loraConfig.tx_power = 22
limitPower(SX126X_MAX_POWER);       // ← CALLED → power = 22 - 7 = 15
lora.begin(..., power, ...);        // SX1262 set to 15 dBm ✅

reconfigure() (in SX126xInterface.cpp lines 208–253):

RadioInterface::reconfigure();      // → applyModemConfig() → power = loraConfig.tx_power = 22
                                    // ← limitPower() NEVER CALLED
lora.setOutputPower(power);         // SX1262 set to 22 dBm ❌

Execution Trace (WashTastic, user sets 22 dBm, US region)

Boot (init() path) — CORRECT

SX126xInterface::init()
  → RadioLibInterface::init()
    → RadioInterface::init()
      → applyModemConfig()
          power = loraConfig.tx_power             → power = 22
          loraConfig.tx_power = (int8_t)power     → loraConfig.tx_power = 22 (written back here)
  → limitPower(SX126X_MAX_POWER=22)                ← CALLED
      EBYTE_E22_900M30S: tx_gain[0] = 7
      power -= tx_gain[0]                         → power = 15
      (loraConfig.tx_power is NOT updated — remains 22)
  → lora.begin(..., power=15, ...)

Result: SX1262 at 15 dBm + ~7 dB PA gain ≈ 22 dBm at antenna ✅

After non-LoRa config change (reconfigure() path) — BUG

User changes any non-LoRa setting (display, power, Bluetooth, etc.)
  → AdminModule::saveChanges()
    → MeshService::reloadConfig()
      → configChanged.notifyObservers()
        → RadioInterface::reloadConfig()
          → SX126xInterface::reconfigure()
            → RadioInterface::reconfigure()
              → applyModemConfig()
                  power = loraConfig.tx_power     → power = 22 (reloaded from uncorrected config!)
                  loraConfig.tx_power = 22        → (unchanged)
                                                  ← limitPower() NEVER CALLED
            → lora.setOutputPower(power=22)

Result: SX1262 at 22 dBm + ~7 dB PA gain ≈ 29 dBm at antenna ❌ (~800 mW instead of ~160 mW)

Steps to Reproduce

  1. Flash a board with non-zero TX_GAIN_LORA (e.g. env:WashTastic which defines EBYTE_E22_900M30S)
  2. Configure region and set TX power to 22 dBm via the Android/iOS app
  3. Node reboots — power is correct (SX1262 = 15 dBm, antenna ≈ 22 dBm)
  4. Change any non-LoRa config setting from the app that does not trigger a reboot (e.g. display screen-on timeout without changing flip/oled/displaymode, or any setting via the on-device menu that calls service->reloadConfig())
  5. reconfigure() runs, SX1262 is now set to 22 dBm — PA gain compensation is lost
  6. Node is now transmitting ≈ 29 dBm until next reboot

Verification: Compare remote-reported SNR/RSSI before and after step 4 against a reference node at a fixed distance. After step 4 the signal will be ~7 dB stronger than intended.

Impact

  • Regulatory compliance: Output power exceeds the intended limit by the full TX_GAIN_LORA value
  • Hardware risk: On high-gain PA modules, the overcurrent can be extreme (see table)
  • Persistence: The excess power persists until the next full reboot; any subsequent non-LoRa config change re-triggers it
  • Scope: Affects all radio interfacesSX126xInterface, LR11x0Interface, SX128xInterface, RF95Interface all have identical reconfigure() patterns that omit limitPower()

Affected Boards (non-exhaustive)

Board / Define TX_GAIN_LORA SX126X_MAX_POWER Intended max output Actual after bug
EBYTE_E22_900M30S 7 22 22 dBm (160 mW) 29 dBm (800 mW)
EBYTE_E22_900M33S 25 8 8 dBm (6 mW) 33 dBm (2 W)
NICERF_MINIF27 7 22 22 dBm 29 dBm
NICERF_F30_HF 8 22 22 dBm 30 dBm
NICERF_F30_LF 10 22 22 dBm 32 dBm
USE_GC1109_PA 7–11 (array) 22 varies +7–11 dB over limit
USE_KCT8103L_PA 7–14 (array) 22 varies +7–14 dB over limit
T-Beam 1W (variant.h) 10 22 varies +10 dB over limit
RAK13302 7–9 (array) 22 varies +7–9 dB over limit

Relevant log output

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions