From ebd576b4e23eef64b78a6ceddec41bcead528201 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 23 Apr 2026 17:58:20 -0600 Subject: [PATCH] fix(boards): invert LilyGo T-Beam (classic) LED polarity via InvertedPin wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The classic T-Beam's red LED on GPIO4 is wired active-low (anode to V+, cathode to GPIO). Release v1.3.0 drove it as active-high, so the LED sat lit at idle and dimmed briefly on TX/RX instead of flashing on activity. Express the polarity where it physically lives — at the pin — via a new generic InvertedPin

wrapper that swaps set_high/set_low. SimpleLed stays polarity-agnostic and every other board's code path is byte-for-byte unchanged from v1.3.0. Empirically verified against hardware on both T-Beam boards: classic red LED is now dark at idle and flashes on TX/RX; Supreme blue LED behaves unchanged. --- src/board/lilygo_tbeam.rs | 13 ++++++++----- src/driver/inverted_pin.rs | 30 ++++++++++++++++++++++++++++++ src/driver/mod.rs | 3 +++ 3 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 src/driver/inverted_pin.rs diff --git a/src/board/lilygo_tbeam.rs b/src/board/lilygo_tbeam.rs index 4be647d..7c6cb42 100644 --- a/src/board/lilygo_tbeam.rs +++ b/src/board/lilygo_tbeam.rs @@ -13,7 +13,8 @@ //! LoRa ctl: RESET=23, DIO0=26 (primary IRQ — SX127x has no BUSY) //! OLED I²C: SDA=21, SCL=22, SSD1306 @ 0x3C (shared with AXP192 @ 0x34) //! UART host: TX=1, RX=3 via CP2102 bridge (USB 10c4:ea60) -//! LED: GPIO4 (active HIGH — reuses MeshCore's P_LORA_TX_LED) +//! LED: GPIO4 (active LOW — red LED under the OLED, wrapped in +//! InvertedPin so SimpleLed sees normal polarity) //! //! GPS / battery / IMU / PCF8563 RTC are populated on the board but unused //! by this firmware. @@ -24,6 +25,7 @@ use esp_hal::i2c::master::{Config as I2cConfig, I2c}; use super::esp32; use super::traits::{BoardParts, LoRaBoard}; use crate::driver::axp; +use crate::driver::inverted_pin::InvertedPin; use crate::driver::simple_led::SimpleLed; use crate::hal::esp32 as mcu; use crate::protocol::RadioChipId; @@ -33,7 +35,7 @@ pub use super::esp32::{create_display, DisplayDriver, DisplayParts, RadioDriver, // ── Concrete peripheral types ─────────────────────────────────────── pub type UartDriver = esp_hal::uart::Uart<'static, esp_hal::Async>; -pub type LedDriver = SimpleLed>; +pub type LedDriver = SimpleLed>>; // ── Peripheral bundles ────────────────────────────────────────────── @@ -122,9 +124,10 @@ impl LoRaBoard for Board { i2c: i2c_blocking.into_async(), }); - // TX activity LED on GPIO4 (active HIGH). - let led_pin = Output::new(p.GPIO4, Level::Low, OutputConfig::default()); - let led = SimpleLed(led_pin); + // TX activity LED on GPIO4 (active LOW — pin idles HIGH = LED off). + // InvertedPin flips set_high/set_low so SimpleLed stays polarity-agnostic. + let led_pin = Output::new(p.GPIO4, Level::High, OutputConfig::default()); + let led = SimpleLed(InvertedPin(led_pin)); BoardParts { radio, diff --git a/src/driver/inverted_pin.rs b/src/driver/inverted_pin.rs new file mode 100644 index 0000000..3bbd618 --- /dev/null +++ b/src/driver/inverted_pin.rs @@ -0,0 +1,30 @@ +//! Generic `OutputPin` wrapper that inverts logic levels. +//! +//! When a peripheral is wired active-low (LED anode to V+, cathode to GPIO; +//! reset line idle-high, etc.) the physical polarity is a property of the +//! board, not of the driver. Wrapping the pin in `InvertedPin` lets drivers +//! keep "set_high = assert" semantics while the board file expresses the +//! wiring reality at construction time. +//! +//! ```ignore +//! let pin = Output::new(p.GPIO4, Level::High, OutputConfig::default()); // off +//! let led = SimpleLed(InvertedPin(pin)); // driver sees normal polarity +//! ``` + +use embedded_hal::digital::{ErrorType, OutputPin}; + +pub struct InvertedPin

(pub P); + +impl ErrorType for InvertedPin

{ + type Error = P::Error; +} + +impl OutputPin for InvertedPin

{ + fn set_low(&mut self) -> Result<(), Self::Error> { + self.0.set_high() + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.0.set_low() + } +} diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 276ab15..fffb049 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -8,6 +8,9 @@ pub mod sh1106; #[cfg(any(feature = "lilygo_tbeam_supreme", feature = "lilygo_tbeam"))] pub mod axp; +#[cfg(feature = "lilygo_tbeam")] +pub mod inverted_pin; + #[cfg(any( feature = "heltec_v3", feature = "heltec_v3_uart",