-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
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 runslimitPower() 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
- Flash a board with non-zero
TX_GAIN_LORA(e.g.env:WashTasticwhich definesEBYTE_E22_900M30S) - Configure region and set TX power to 22 dBm via the Android/iOS app
- Node reboots — power is correct (SX1262 = 15 dBm, antenna ≈ 22 dBm)
- 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()) reconfigure()runs, SX1262 is now set to 22 dBm — PA gain compensation is lost- 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_LORAvalue - 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 interfaces —
SX126xInterface,LR11x0Interface,SX128xInterface,RF95Interfaceall have identicalreconfigure()patterns that omitlimitPower()
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 |