Bug Report: TXEN Pin Logic Inverted for E22/PiMesh-1W Modules - Causes 4x Power Consumption #11

Closed
opened 2026-10-27 09:28:25 +01:00 by adam · 4 comments
Owner

Summary

The LoRaRF SX126x driver in pymc_core sets TXEN HIGH during RX mode, which is inverted logic for E22-900M30S based modules (like PiMesh-1W). This causes the 1W Power Amplifier to remain constantly ON, resulting in ~2.1A draw instead of ~0.5A - a 4x increase in power consumption that drastically reduces battery life.

Environment

  • Hardware: Raspberry Pi Zero 2 W + PiMesh-1W HAT (E22-900M30S module)
  • Software: pymc_core 1.0.7 (installed from dev branch)
  • OS: Raspberry Pi OS (Debian Trixie)
  • Python: 3.13

Expected Behavior

For E22/PiMesh-1W modules with external PA:

  • RX/Idle mode: TXEN = LOW (PA off), RXEN = HIGH (LNA on)
  • TX mode: TXEN = HIGH (PA on), RXEN = LOW (LNA off)

Expected idle current: ~500-600 mA

Actual Behavior

The SX126x.py driver sets TXEN HIGH when entering RX mode:

  • RX/Idle mode: TXEN = HIGH (PA on!) ❌
  • TX mode: TXEN = HIGH (PA on) ✓

Actual idle current: ~2,100 mA (PA constantly drawing ~1.6A extra)

Impact

Metric Expected Actual Impact
Idle current 500 mA 2,100 mA 4x higher
Idle power 2.0W 8.3W 4x higher
Runtime (14Ah battery) 25 hours 6 hours 75% reduction

For solar-powered remote repeaters, this bug can cause unexpected shutdowns during cloudy periods.

Root Cause

In /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py:

Line 955-958 (request method - enters RX mode):

# save current txen pin state and set txen pin to high
if self._txen != -1:
    self._txState = _get_output(self._txen).read()
    _get_output(self._txen).write(True)  # BUG: Should be False for RX

Line 980-983 (listen method):

# save current txen pin state and set txen pin to high
if self._txen != -1:
    self._txState = _get_output(self._txen).read()
    _get_output(self._txen).write(True)  # BUG: Should be False for RX

Lines 1054, 1059, 1154, 1164 (post-TX restoration):

_get_output(self._txen).write(self._txState)  # BUG: Restores previous (wrong) state

The comments say "set txen pin to high" for RX mode, suggesting this was intentional but incorrect for E22 modules with external PA.

Workaround / Fix

Patch SX126x.py to set TXEN LOW for RX mode and after TX:

# Backup original
sudo cp /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py \
       /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py.bak

# Fix RX mode (line 958)
sudo sed -i '958s/_get_output(self._txen).write(True)/_get_output(self._txen).write(False)/' \
    /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py

# Fix listen mode (line 983)
sudo sed -i '983s/_get_output(self._txen).write(True)/_get_output(self._txen).write(False)/' \
    /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py

# Fix post-TX restoration (lines 1054, 1059, 1154, 1164)
sudo sed -i 's/_get_output(self._txen).write(self._txState)/_get_output(self._txen).write(False)/g' \
    /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py

# Restart service
sudo systemctl restart pymc-repeater

Suggested Proper Fix

The correct fix should make TXEN behavior configurable, as different modules may have different logic:

Option 1: Add txen_active_high_for_tx config option

# In sx1262_wrapper.py initialization
self.txen_active_high_for_tx = config.get("txen_active_high_for_tx", True)

# In _control_tx_rx_pins method
def _control_tx_rx_pins(self, tx_mode: bool) -> None:
    if tx_mode:
        txen_state = self.txen_active_high_for_tx
    else:
        txen_state = not self.txen_active_high_for_tx
    
    if self.txen_pin != -1:
        if txen_state:
            self._gpio_manager.set_pin_high(self.txen_pin)
        else:
            self._gpio_manager.set_pin_low(self.txen_pin)

Option 2: Use the wrapper's _control_tx_rx_pins exclusively

The sx1262_wrapper.py already has correct logic in _control_tx_rx_pins() (lines 872-887). The issue is that SX126x.py also controls TXEN directly, bypassing the wrapper.

Either:

  1. Remove TXEN control from SX126x.py entirely and let the wrapper handle it
  2. Or pass -1 to SX126x for txen_pin and handle it only in the wrapper

Verification Data

Before Fix (TXEN stuck HIGH)

Monitoring for 30 seconds...
Time    Voltage    Current (mA)    Power (W)
0s      3.91V      -2167 mA        8.5W
1s      3.92V      -2149 mA        8.4W
...
29s     3.93V      -2112 mA        8.3W

After Fix (TXEN LOW during RX)

60-second stability test...
Time    Voltage    Current    Power    Note
0s      4.00V      916 mA     3.7W     RX/Idle
...
49s     3.93V      2252 mA    8.8W     TX        <- Brief TX spike (normal)
50s     4.01V      544 mA     2.2W     RX/Idle   <- Returns to low power
...
============================================================
SUMMARY:
  Idle/RX average: 566 mA
  TX events: 1
  Estimated runtime (14Ah): 24.7 hours

Affected Hardware

This bug likely affects all modules using external PA controlled by TXEN:

  • PiMesh-1W HAT (E22-900M30S)
  • Ebyte E22-900M30S
  • Any SX1262 + external PA with TXEN control
## Summary The LoRaRF SX126x driver in pymc_core sets TXEN HIGH during RX mode, which is inverted logic for E22-900M30S based modules (like PiMesh-1W). This causes the 1W Power Amplifier to remain constantly ON, resulting in ~2.1A draw instead of ~0.5A - a 4x increase in power consumption that drastically reduces battery life. ## Environment - **Hardware:** Raspberry Pi Zero 2 W + PiMesh-1W HAT (E22-900M30S module) - **Software:** pymc_core 1.0.7 (installed from dev branch) - **OS:** Raspberry Pi OS (Debian Trixie) - **Python:** 3.13 ## Expected Behavior For E22/PiMesh-1W modules with external PA: - **RX/Idle mode:** TXEN = LOW (PA off), RXEN = HIGH (LNA on) - **TX mode:** TXEN = HIGH (PA on), RXEN = LOW (LNA off) Expected idle current: ~500-600 mA ## Actual Behavior The SX126x.py driver sets TXEN HIGH when entering RX mode: - **RX/Idle mode:** TXEN = HIGH (PA on!) ❌ - **TX mode:** TXEN = HIGH (PA on) ✓ Actual idle current: ~2,100 mA (PA constantly drawing ~1.6A extra) ## Impact | Metric | Expected | Actual | Impact | |--------|----------|--------|--------| | Idle current | 500 mA | 2,100 mA | 4x higher | | Idle power | 2.0W | 8.3W | 4x higher | | Runtime (14Ah battery) | 25 hours | 6 hours | 75% reduction | For solar-powered remote repeaters, this bug can cause unexpected shutdowns during cloudy periods. ## Root Cause In `/usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py`: **Line 955-958 (request method - enters RX mode):** ```python # save current txen pin state and set txen pin to high if self._txen != -1: self._txState = _get_output(self._txen).read() _get_output(self._txen).write(True) # BUG: Should be False for RX ``` **Line 980-983 (listen method):** ```python # save current txen pin state and set txen pin to high if self._txen != -1: self._txState = _get_output(self._txen).read() _get_output(self._txen).write(True) # BUG: Should be False for RX ``` **Lines 1054, 1059, 1154, 1164 (post-TX restoration):** ```python _get_output(self._txen).write(self._txState) # BUG: Restores previous (wrong) state ``` The comments say "set txen pin to high" for RX mode, suggesting this was intentional but incorrect for E22 modules with external PA. ## Workaround / Fix Patch SX126x.py to set TXEN LOW for RX mode and after TX: ```bash # Backup original sudo cp /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py \ /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py.bak # Fix RX mode (line 958) sudo sed -i '958s/_get_output(self._txen).write(True)/_get_output(self._txen).write(False)/' \ /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py # Fix listen mode (line 983) sudo sed -i '983s/_get_output(self._txen).write(True)/_get_output(self._txen).write(False)/' \ /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py # Fix post-TX restoration (lines 1054, 1059, 1154, 1164) sudo sed -i 's/_get_output(self._txen).write(self._txState)/_get_output(self._txen).write(False)/g' \ /usr/local/lib/python3.13/dist-packages/pymc_core/hardware/lora/LoRaRF/SX126x.py # Restart service sudo systemctl restart pymc-repeater ``` ## Suggested Proper Fix The correct fix should make TXEN behavior configurable, as different modules may have different logic: ### Option 1: Add `txen_active_high_for_tx` config option ```python # In sx1262_wrapper.py initialization self.txen_active_high_for_tx = config.get("txen_active_high_for_tx", True) # In _control_tx_rx_pins method def _control_tx_rx_pins(self, tx_mode: bool) -> None: if tx_mode: txen_state = self.txen_active_high_for_tx else: txen_state = not self.txen_active_high_for_tx if self.txen_pin != -1: if txen_state: self._gpio_manager.set_pin_high(self.txen_pin) else: self._gpio_manager.set_pin_low(self.txen_pin) ``` ### Option 2: Use the wrapper's _control_tx_rx_pins exclusively The `sx1262_wrapper.py` already has correct logic in `_control_tx_rx_pins()` (lines 872-887). The issue is that `SX126x.py` also controls TXEN directly, bypassing the wrapper. Either: 1. Remove TXEN control from SX126x.py entirely and let the wrapper handle it 2. Or pass `-1` to SX126x for txen_pin and handle it only in the wrapper ## Verification Data ### Before Fix (TXEN stuck HIGH) ``` Monitoring for 30 seconds... Time Voltage Current (mA) Power (W) 0s 3.91V -2167 mA 8.5W 1s 3.92V -2149 mA 8.4W ... 29s 3.93V -2112 mA 8.3W ``` ### After Fix (TXEN LOW during RX) ``` 60-second stability test... Time Voltage Current Power Note 0s 4.00V 916 mA 3.7W RX/Idle ... 49s 3.93V 2252 mA 8.8W TX <- Brief TX spike (normal) 50s 4.01V 544 mA 2.2W RX/Idle <- Returns to low power ... ============================================================ SUMMARY: Idle/RX average: 566 mA TX events: 1 Estimated runtime (14Ah): 24.7 hours ``` ## Affected Hardware This bug likely affects all modules using external PA controlled by TXEN: - PiMesh-1W HAT (E22-900M30S) - Ebyte E22-900M30S - Any SX1262 + external PA with TXEN control <!-- github-sync-id:3769005208 -->
adam closed this issue 2026-10-27 09:28:25 +01:00
Author
Owner

Let me review these changes thank you.

Let me review these changes thank you. <!-- github-comment-id:3700812644 -->
Author
Owner

Thank you and great find!, I have applied this fix to the underlying PYMC_CORE lib (in dev branch)

Thank you and great find!, I have applied this fix to the underlying PYMC_CORE lib (in dev branch) <!-- github-comment-id:3704214752 -->
Author
Owner

Appreciate the quick response! I'm really enjoying pyMC_Repeater. Thanks for all the work you've put into it.

Appreciate the quick response! I'm really enjoying pyMC_Repeater. Thanks for all the work you've put into it. <!-- github-comment-id:3704230885 -->
Author
Owner

Thank you — I’m really glad you’re enjoying it!! Great find!
Please keep sharing more little beauties like that — the more PRs, the better 👍

P.S. If you’re not already there, feel free to join us on Discord:
https://discord.com/channels/1343693475589263471/1431414286974189639

Thank you — I’m really glad you’re enjoying it!! Great find! Please keep sharing more little beauties like that — the more PRs, the better 👍 P.S. If you’re not already there, feel free to join us on Discord: https://discord.com/channels/1343693475589263471/1431414286974189639 <!-- github-comment-id:3704236144 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/pyMC_Repeater#11