Files
firmware/src/driver/mod.rs
T
swaits aca4c3cffd feat(t114): add Heltec Mesh Node T114 board support
Adds nRF52840 + SX1262 + 1.14" 240×135 ST7789 color TFT support. First
color display board in the project; introduces a BoardDisplay trait so
each board renders to its native pixel format and resolution without
aliasing or color-quantization round-trips.

Architecture:
- New BoardDisplay trait (src/display.rs) bundles render_*, present(),
  set_bright/set_dim. Replaces the old DrawTarget<Color=BinaryColor>
  + DisplayBrightness combo as the LoRaBoard::DisplayDriver bound.
- Mono OLED drivers (Sh1106, Ssd1306Async) get adapter impls that
  delegate to the existing display::render module — no behavior change
  for any existing board.
- T114 ST7789 driver (src/driver/st7789.rs) wraps lcd-async (async fork
  of mipidsi) with a 64,800-byte raw byte framebuffer in .bss. Renders
  via lcd_async::raw_framebuf::RawFrameBuf<Rgb565, _>, presents via a
  single async DMA blit (panel.show_raw_data(...).await) — the await
  yields the executor for the ~65 ms SPI transfer so the radio task
  isn't starved during repaints.
- PWM backlight on PWM0 / P0.15 (active-LOW, inverted polarity) for
  smooth dim/bright transitions instead of GPIO toggle.
- Native 240×135 Rgb565 renderer (src/display/render_color.rs) with
  splash + dashboard + sparkline.
- Shared QR-code blit (src/display/qr.rs), generic over PixelColor,
  re-centers the 64×64 bitmap which has cells in cols 0..50 and ~14 px
  of right-side padding — both mono and color splash now show the QR
  with 7 px quiet zones on all four sides (was visibly left-shifted).
- Shared content types (BoardInfo, DashboardCtx) decouple what to draw
  from how each board renders it.
- create_display() now takes the board's full DisplayParts bundle
  (was just the I2C bus) so non-I2C displays can carry SPI + control
  pins through.

Hardware:
- VEXT_ENABLE on P0.21 driven HIGH at boot — powers the SX1262 + TFT
  rail. Without this, lora-phy init succeeds at the SPI level but
  TX_DONE never fires.
- VTFT_CTRL on P0.03, active LOW; backlight on P0.15, active LOW.
- TFT SPI2 (SCK P1.08 / MOSI P1.09) at 8 MHz over EasyDMA. Async
  ExclusiveDevice from embedded-hal-bus (with the 'async' feature).
- lcd-async builder at display_size(135, 240), display_offset(52, 40),
  Deg90 rotation, ColorInversion::Inverted — standard 1.14" ST7789
  die mapping.
- Linker: shared ld/nrf52840-memory.x (S140 SoftDevice preserved at
  0x01000-0x26000 per project policy).
2026-04-30 03:11:36 -06:00

89 lines
3.0 KiB
Rust

#[cfg(any(
feature = "wio_tracker_l1",
feature = "elecrow_thinknode_m2",
feature = "lilygo_tbeam_supreme",
feature = "rak_wisblock_4631"
))]
pub mod sh1106;
#[cfg(any(feature = "lilygo_tbeam_supreme", feature = "lilygo_tbeam"))]
pub mod axp;
#[cfg(any(feature = "lilygo_tbeam", feature = "heltec_mesh_node_t114"))]
pub mod inverted_pin;
#[cfg(feature = "heltec_mesh_node_t114")]
pub mod st7789;
#[cfg(any(
feature = "heltec_mesh_node_t114",
feature = "heltec_v3",
feature = "heltec_v3_uart",
feature = "heltec_v4",
feature = "elecrow_thinknode_m2",
feature = "lilygo_tbeam_supreme",
feature = "lilygo_tbeam",
feature = "rak_wisblock_4631",
feature = "wio_tracker_l1",
feature = "waveshare_rp2040_lora"
))]
pub mod simple_led;
// ── Display brightness ────────────────────────────────────────────────
//
// The display statechart dims the OLED a few seconds after the last
// activity. Our two driver families (`ssd1306` crate and our in-tree
// `sh1106`) expose incompatible brightness APIs, so we wrap them in a
// single async trait that the state-machine actions can call uniformly.
/// Two-level backlight control used by the display dashboard.
pub trait DisplayBrightness {
fn set_bright(&mut self) -> impl core::future::Future<Output = ()>;
fn set_dim(&mut self) -> impl core::future::Future<Output = ()>;
}
// Contrast values for the two brightness levels, picked to be common across
// both driver families. `0xFF` is the SSD1306/SH1106 maximum; `0x08` is ~3%
// of max — visible in a dim room, barely noticeable in daylight. Tune here.
const CONTRAST_BRIGHT: u8 = 0xFF;
const CONTRAST_DIM: u8 = 0x08;
impl<DI, SIZE, MODE> DisplayBrightness for ssd1306::Ssd1306Async<DI, SIZE, MODE>
where
DI: display_interface::AsyncWriteOnlyDataCommand,
SIZE: ssd1306::size::DisplaySizeAsync,
{
async fn set_bright(&mut self) {
// `Brightness::custom(precharge, contrast)`. Matches the shape of
// the crate's BRIGHTEST preset (precharge 0x2) at max contrast.
let _ = self
.set_brightness(ssd1306::prelude::Brightness::custom(0x2, CONTRAST_BRIGHT))
.await;
}
async fn set_dim(&mut self) {
// Low precharge pairs with low contrast — same combination the
// crate's DIMMEST preset uses.
let _ = self
.set_brightness(ssd1306::prelude::Brightness::custom(0x1, CONTRAST_DIM))
.await;
}
}
#[cfg(any(
feature = "wio_tracker_l1",
feature = "elecrow_thinknode_m2",
feature = "lilygo_tbeam_supreme",
feature = "rak_wisblock_4631"
))]
impl<I> DisplayBrightness for sh1106::Sh1106<I>
where
I: embedded_hal_async::i2c::I2c,
{
async fn set_bright(&mut self) {
let _ = self.set_brightness(CONTRAST_BRIGHT).await;
}
async fn set_dim(&mut self) {
let _ = self.set_brightness(CONTRAST_DIM).await;
}
}