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).
This commit is contained in:
22 files changed
+1246
-135
No files matched your search
@@ -70,8 +70,10 @@ impl LoRaBoard for Board {
|
||||
### 5. Add display init
|
||||
|
||||
```rust
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
// Construct and initialize your display driver (SSD1306, SH1106, etc.)
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
// Construct and initialize your display driver (SSD1306, SH1106,
|
||||
// ST7789, etc.) using whatever pins/buses you bundled into
|
||||
// DisplayParts in `into_parts()`.
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ pub struct DisplayParts {
|
||||
|
||||
// ── Display init ────────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
let mut display = Sh1106::new(i2c, 0x3C);
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
let mut display = Sh1106::new(parts.i2c, 0x3C);
|
||||
|
||||
embassy_time::Timer::after_millis(100).await;
|
||||
if display.init().await.is_err() {
|
||||
|
||||
+2
-2
@@ -47,13 +47,13 @@ pub type DisplayDriver = ssd1306::Ssd1306Async<
|
||||
>;
|
||||
|
||||
/// Construct and initialize an SSD1306 display from raw I²C.
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
use ssd1306::mode::DisplayConfigAsync;
|
||||
use ssd1306::prelude::{Brightness, DisplayRotation};
|
||||
use ssd1306::size::DisplaySize128x64;
|
||||
use ssd1306::{I2CDisplayInterface, Ssd1306Async};
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let interface = I2CDisplayInterface::new(parts.i2c);
|
||||
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
|
||||
@@ -63,13 +63,13 @@ pub type DisplayDriver = ssd1306::Ssd1306Async<
|
||||
feature = "heltec_v3_uart",
|
||||
feature = "heltec_v4"
|
||||
))]
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
use ssd1306::mode::DisplayConfigAsync;
|
||||
use ssd1306::prelude::{Brightness, DisplayRotation};
|
||||
use ssd1306::size::DisplaySize128x64;
|
||||
use ssd1306::{I2CDisplayInterface, Ssd1306Async};
|
||||
|
||||
let interface = I2CDisplayInterface::new(i2c);
|
||||
let interface = I2CDisplayInterface::new(parts.i2c);
|
||||
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||
.into_buffered_graphics_mode();
|
||||
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
//! Heltec Mesh Node T114 (nRF52840 + SX1262 + 1.14" ST7789 TFT).
|
||||
//!
|
||||
//! Hardware reference: cross-verified across MeshCore, Meshtastic, and
|
||||
//! RNode firmwares. Pin maps quoted from
|
||||
//! `meshcore-spec/references/MeshCore/variants/heltec_t114/variant.h` and
|
||||
//! `meshtastic/firmware/variants/nrf52840/heltec_mesh_node_t114/variant.h`.
|
||||
//!
|
||||
//! Stage 2: panel comes up with static colors via the `St7789Color`
|
||||
//! driver. Stage 3 swaps in the proper color renderer module.
|
||||
|
||||
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
|
||||
use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull};
|
||||
use embassy_nrf::pwm::{Prescaler, SimpleConfig, SimplePwm};
|
||||
use embassy_nrf::spim::{self, Spim};
|
||||
use embassy_nrf::usb::Driver;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_time::{Delay, Timer};
|
||||
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use lcd_async::interface::SpiInterface;
|
||||
use lcd_async::models::ST7789;
|
||||
use lcd_async::options::{ColorInversion, Orientation, Rotation};
|
||||
use lora_phy::iv::GenericSx126xInterfaceVariant;
|
||||
use lora_phy::sx126x::{self, Sx1262, Sx126x};
|
||||
use static_cell::ConstStaticCell;
|
||||
|
||||
use super::traits::{BoardParts, LoRaBoard};
|
||||
use crate::driver::inverted_pin::InvertedPin;
|
||||
use crate::driver::simple_led::SimpleLed;
|
||||
use crate::driver::st7789::{St7789Color, FB_BYTES};
|
||||
use crate::hal::nrf52840 as mcu;
|
||||
|
||||
// ── Concrete peripheral types ───────────────────────────────────────
|
||||
|
||||
type Nss = Output<'static>;
|
||||
type Iv = GenericSx126xInterfaceVariant<Output<'static>, Input<'static>>;
|
||||
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, mcu::SpiBus, Nss>;
|
||||
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
||||
pub type UsbDriver = mcu::UsbNrfDriver;
|
||||
// Green LED on P1.03, active LOW.
|
||||
pub type LedDriver = SimpleLed<InvertedPin<Output<'static>>>;
|
||||
|
||||
// ── TFT bus and driver type aliases ─────────────────────────────────
|
||||
|
||||
type TftSpim = Spim<'static>;
|
||||
type TftSpiDevice = ExclusiveDevice<TftSpim, Output<'static>, Delay>;
|
||||
type TftInterface = SpiInterface<TftSpiDevice, Output<'static>>;
|
||||
pub type DisplayDriver = St7789Color<TftInterface, Output<'static>>;
|
||||
|
||||
// ── Peripheral bundles ──────────────────────────────────────────────
|
||||
|
||||
pub struct RadioParts {
|
||||
pub driver: RadioDriver,
|
||||
pub delay: Delay,
|
||||
}
|
||||
|
||||
pub struct UsbParts {
|
||||
pub driver: UsbDriver,
|
||||
}
|
||||
|
||||
/// Assembled-but-not-yet-initialized TFT plumbing. Real initialization
|
||||
/// (power gate, RST cycle, mipidsi init) happens in `create_display`.
|
||||
pub struct DisplayParts {
|
||||
interface: TftInterface,
|
||||
rst_pin: Output<'static>,
|
||||
vdd_en: Output<'static>,
|
||||
backlight: SimplePwm<'static>,
|
||||
}
|
||||
|
||||
// ── Display init ────────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
let DisplayParts {
|
||||
interface,
|
||||
rst_pin,
|
||||
mut vdd_en,
|
||||
mut backlight,
|
||||
} = parts;
|
||||
|
||||
// 1. Drive TFT VDD enable LOW (active LOW per Meshtastic) and let
|
||||
// the rail settle. Heltec's schematic has the panel power gated
|
||||
// by P0.03 — touching SPI before VDD is stable produces snow.
|
||||
vdd_en.set_low();
|
||||
Timer::after_millis(50).await;
|
||||
|
||||
// 2. Build the lcd-async display.
|
||||
// The T114 panel is wired natively portrait (135w × 240h) at
|
||||
// chip framebuffer offset (52, 40) — the standard 1.14" 135×240
|
||||
// ST7789 die mapping. Deg90 rotation rotates the user-facing
|
||||
// surface to landscape (240w × 135h). Meshtastic's variant.h
|
||||
// sets TFT_OFFSET_X=0/Y=0 because their fork of TFT_eSPI applies
|
||||
// these offsets internally; lcd-async expects them in display_offset.
|
||||
// If the image comes up mirrored or upside-down, swap
|
||||
// Deg90 ↔ Deg270 or set `mirrored = true`.
|
||||
let mut delay = Delay;
|
||||
let panel = match lcd_async::Builder::new(ST7789, interface)
|
||||
.reset_pin(rst_pin)
|
||||
.display_size(135, 240)
|
||||
.display_offset(52, 40)
|
||||
.orientation(Orientation::new().rotate(Rotation::Deg90))
|
||||
.invert_colors(ColorInversion::Inverted)
|
||||
.init(&mut delay)
|
||||
.await
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
defmt::error!("ST7789 init failed");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
defmt::info!("ST7789 init OK");
|
||||
|
||||
// 3. Bring the backlight up at full brightness. Stage 2 uses the
|
||||
// full-on duty; the splash dimming logic handled by the display
|
||||
// statechart will hand off to set_dim/set_bright shortly.
|
||||
backlight.set_duty(0, embassy_nrf::pwm::DutyCycle::inverted(0));
|
||||
|
||||
// Forget vdd_en so it stays driven for the lifetime of the program.
|
||||
// We took it by move so we could call set_low() during init.
|
||||
core::mem::forget(vdd_en);
|
||||
|
||||
// Take the static framebuffer (240×135 × 2 bytes Rgb565 BE
|
||||
// = 64,800 bytes zero-initialized in .bss). Stored as raw bytes
|
||||
// so present() can DMA-blit the slice straight to the panel
|
||||
// without an iterator-conversion pass — the lcd-async
|
||||
// `RawFrameBuf<Rgb565, _>` wrapper handles BE byte packing on
|
||||
// each pixel write.
|
||||
static FRAMEBUFFER: ConstStaticCell<[u8; FB_BYTES]> =
|
||||
ConstStaticCell::new([0u8; FB_BYTES]);
|
||||
let fb = FRAMEBUFFER.take();
|
||||
|
||||
Some(St7789Color::new(panel, backlight, fb))
|
||||
}
|
||||
|
||||
// ── Board init ──────────────────────────────────────────────────────
|
||||
|
||||
pub struct Board {
|
||||
p: embassy_nrf::Peripherals,
|
||||
}
|
||||
|
||||
impl LoRaBoard for Board {
|
||||
const NAME: &'static str = "Heltec Mesh Node T114";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
type RadioParts = RadioParts;
|
||||
type CommParts = UsbParts;
|
||||
type DisplayParts = DisplayParts;
|
||||
type DisplayDriver = DisplayDriver;
|
||||
type LedDriver = LedDriver;
|
||||
|
||||
fn init() -> Self {
|
||||
// USB requires the 32 MHz HFXO (±50 ppm); the RC oscillator is out of spec.
|
||||
let mut config = embassy_nrf::config::Config::default();
|
||||
config.hfclk_source = embassy_nrf::config::HfclkSource::ExternalXtal;
|
||||
config.lfclk_source = embassy_nrf::config::LfclkSource::ExternalXtal;
|
||||
let p = embassy_nrf::init(config);
|
||||
Self { p }
|
||||
}
|
||||
|
||||
fn mac_address() -> [u8; 6] {
|
||||
mcu::mac_address()
|
||||
}
|
||||
|
||||
fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts, LedDriver> {
|
||||
let p = self.p;
|
||||
|
||||
// ── VEXT_ENABLE (P0.21, active HIGH) ────────────────────
|
||||
// Powers the SX1262 *and* the TFT/peripheral 3V3 rail. Must be
|
||||
// driven HIGH before any SPI traffic to the radio or panel —
|
||||
// without this, lora-phy init can succeed (SPI bus works
|
||||
// electrically) but TX_DONE never fires because the PA is
|
||||
// unpowered. Held forever via core::mem::forget. Mirrors
|
||||
// Meshtastic's `VEXT_ENABLE = P0.21, VEXT_ON_VALUE = HIGH`.
|
||||
let vext_en = Output::new(p.P0_21, Level::High, OutputDrive::Standard);
|
||||
core::mem::forget(vext_en);
|
||||
|
||||
// ── SPI3 bus for SX1262 ─────────────────────────────────
|
||||
// Per Heltec T114 schematic + Meshtastic variant.h:
|
||||
// SCK=P0.19, MOSI=P0.22, MISO=P0.23
|
||||
// NSS=P0.24, RST=P0.25, BUSY=P0.17, DIO1=P0.20
|
||||
// No external RXEN/TXEN — antenna switch is driven internally
|
||||
// by SX1262 DIO2. TCXO is on DIO3 at 1.8 V.
|
||||
let mut spi_cfg = spim::Config::default();
|
||||
spi_cfg.frequency = spim::Frequency::M1;
|
||||
let spi = Spim::new(p.SPI3, mcu::Irqs, p.P0_19, p.P0_23, p.P0_22, spi_cfg);
|
||||
let spi_bus = mcu::share_spi_bus(spi);
|
||||
|
||||
let nss = Output::new(p.P0_24, Level::High, OutputDrive::Standard);
|
||||
let spi_device = SpiDevice::new(spi_bus, nss);
|
||||
|
||||
let reset = Output::new(p.P0_25, Level::High, OutputDrive::Standard);
|
||||
let dio1 = Input::new(p.P0_20, Pull::Down);
|
||||
// BUSY has an internal 20 kΩ pull-up on the SX1262 die (datasheet
|
||||
// p. 53); an MCU-side Pull fights it. See lora-rs/lora-rs#260.
|
||||
let busy = Input::new(p.P0_17, Pull::None);
|
||||
|
||||
let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, None, None)
|
||||
.expect("SX1262 interface init");
|
||||
|
||||
let sx_config = sx126x::Config {
|
||||
chip: Sx1262,
|
||||
tcxo_ctrl: Some(sx126x::TcxoCtrlVoltage::Ctrl1V8),
|
||||
use_dcdc: true,
|
||||
rx_boost: false,
|
||||
};
|
||||
|
||||
let radio = RadioParts {
|
||||
driver: Sx126x::new(spi_device, iv, sx_config),
|
||||
delay: Delay,
|
||||
};
|
||||
|
||||
// ── USB ─────────────────────────────────────────────────
|
||||
let vbus = mcu::alloc_vbus_detect(true, true);
|
||||
let host = UsbParts {
|
||||
driver: Driver::new(p.USBD, mcu::Irqs, vbus),
|
||||
};
|
||||
|
||||
// Battery divider control (P0.06) LOW disables the divider —
|
||||
// saves a few tens of µA. Battery sense (P0.04 / AIN2) deferred
|
||||
// until the protocol gets a battery field.
|
||||
let bat_ctl = Output::new(p.P0_06, Level::Low, OutputDrive::Standard);
|
||||
core::mem::forget(bat_ctl);
|
||||
|
||||
// ── SPI2 bus for ST7789 TFT ─────────────────────────────
|
||||
// Per Meshtastic variant.h:
|
||||
// SCK=P1.08, MOSI=P1.09, CS=P0.11, DC=P0.12, RST=P0.02
|
||||
// VDD enable=P0.03 (active HIGH), Backlight=P0.15 (active LOW PWM)
|
||||
// Write-only bus — embassy_nrf::Spim still requires a MISO slot,
|
||||
// so we wire a free unused pin (P1.06) as a no-op input.
|
||||
let mut tft_spi_cfg = spim::Config::default();
|
||||
// 8 MHz lets us repaint the 240×135 panel comfortably. Higher
|
||||
// rates work with mipidsi but are platform-dependent (board
|
||||
// routing, pull strengths). 8 MHz is the safe default.
|
||||
tft_spi_cfg.frequency = spim::Frequency::M8;
|
||||
let tft_spi = Spim::new(p.SPI2, mcu::Irqs, p.P1_08, p.P1_06, p.P1_09, tft_spi_cfg);
|
||||
|
||||
let tft_cs = Output::new(p.P0_11, Level::High, OutputDrive::Standard);
|
||||
let tft_dc = Output::new(p.P0_12, Level::Low, OutputDrive::Standard);
|
||||
let tft_rst = Output::new(p.P0_02, Level::High, OutputDrive::Standard);
|
||||
// VTFT_CTRL is active LOW per Meshtastic
|
||||
// (`#define TFT_BACKLIGHT_ON LOW`, `digitalWrite(VTFT_CTRL, LOW)`
|
||||
// at boot). Start HIGH (panel rail off); create_display drives
|
||||
// LOW to enable the panel.
|
||||
let tft_vdd = Output::new(p.P0_03, Level::High, OutputDrive::Standard);
|
||||
|
||||
let tft_spi_device = ExclusiveDevice::new(tft_spi, tft_cs, Delay)
|
||||
.expect("TFT ExclusiveDevice init");
|
||||
|
||||
// lcd-async's SpiInterface streams pixel data directly from
|
||||
// the caller's slice — no scratch buffer required (mipidsi's
|
||||
// sync version needed one for batching).
|
||||
let tft_interface = SpiInterface::new(tft_spi_device, tft_dc);
|
||||
|
||||
// ── PWM backlight (active-LOW on P0.15) ─────────────────
|
||||
// Default SimpleConfig is fine — countertop=1000 → ~1 kHz
|
||||
// (well above flicker threshold), prescaler default. Channel 0
|
||||
// idle level matches the active-LOW wiring.
|
||||
let mut bl_cfg = SimpleConfig::default();
|
||||
bl_cfg.max_duty = crate::driver::st7789::BL_MAX;
|
||||
bl_cfg.prescaler = Prescaler::Div128;
|
||||
bl_cfg.ch0_idle_level = Level::High;
|
||||
let backlight = SimplePwm::new_1ch(p.PWM0, p.P0_15, &bl_cfg);
|
||||
|
||||
let display_parts = DisplayParts {
|
||||
interface: tft_interface,
|
||||
rst_pin: tft_rst,
|
||||
vdd_en: tft_vdd,
|
||||
backlight,
|
||||
};
|
||||
|
||||
// ── Green LED (P1.03, active LOW) ───────────────────────
|
||||
// Idle level HIGH = LED off. InvertedPin flips polarity so
|
||||
// SimpleLed can use normal "set_high = on" semantics.
|
||||
let led_pin = Output::new(p.P1_03, Level::High, OutputDrive::Standard);
|
||||
let led = SimpleLed(InvertedPin(led_pin));
|
||||
|
||||
BoardParts {
|
||||
radio,
|
||||
host,
|
||||
display: Some(display_parts),
|
||||
led,
|
||||
mac: Self::mac_address(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,8 @@ pub struct DisplayParts {
|
||||
|
||||
// ── Display init ────────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
let mut display = Sh1106::new(i2c, 0x3C);
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
let mut display = Sh1106::new(parts.i2c, 0x3C);
|
||||
|
||||
embassy_time::Timer::after_millis(100).await;
|
||||
if display.init().await.is_err() {
|
||||
|
||||
@@ -52,8 +52,8 @@ pub struct DisplayParts {
|
||||
|
||||
// ── Display init ────────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
let mut display = crate::driver::sh1106::Sh1106::new(i2c, 0x3C);
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
let mut display = crate::driver::sh1106::Sh1106::new(parts.i2c, 0x3C);
|
||||
|
||||
// Retry 3× with 100ms backoff. The SH1106's first transaction
|
||||
// after power-on can return Err if the chip's internal voltage
|
||||
|
||||
+5
-7
@@ -6,10 +6,7 @@
|
||||
//!
|
||||
//! See `src/board/PORTING.md` for a step-by-step guide to adding a new board.
|
||||
|
||||
use embedded_graphics::draw_target::DrawTarget;
|
||||
use embedded_graphics::pixelcolor::BinaryColor;
|
||||
|
||||
use crate::driver::DisplayBrightness;
|
||||
use crate::display::BoardDisplay;
|
||||
|
||||
/// Async RGB LED control. Boards without an LED use `()` which is a no-op.
|
||||
pub trait RgbLed {
|
||||
@@ -37,9 +34,10 @@ pub trait LoRaBoard: Sized {
|
||||
/// Display peripheral bundle (I2C bus for display init).
|
||||
type DisplayParts;
|
||||
|
||||
/// Concrete display driver type (must implement DrawTarget for rendering
|
||||
/// and DisplayBrightness for the two-level dim control).
|
||||
type DisplayDriver: DrawTarget<Color = BinaryColor> + DisplayBrightness;
|
||||
/// Concrete display driver type. Each board's driver implements
|
||||
/// [`BoardDisplay`] to render the splash + dashboard content into its
|
||||
/// native pixel format and resolution and to flush / dim the panel.
|
||||
type DisplayDriver: BoardDisplay;
|
||||
|
||||
/// RGB LED driver. Boards without an LED use `()`.
|
||||
type LedDriver: RgbLed;
|
||||
|
||||
@@ -41,12 +41,16 @@ pub struct UsbParts {
|
||||
}
|
||||
|
||||
pub struct DisplayParts {
|
||||
// No display on this board, but the trait still wants concrete
|
||||
// peripheral types. The i2c bus type is reserved for the future
|
||||
// possibility of an external OLED add-on.
|
||||
#[allow(dead_code)]
|
||||
pub i2c: DisplayI2c,
|
||||
}
|
||||
|
||||
// ── Display init ───────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(_i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
pub async fn create_display(_parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ pub struct DisplayParts {
|
||||
|
||||
// ── Display init ────────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
let mut display = crate::driver::sh1106::Sh1106::new(i2c, 0x3D);
|
||||
pub async fn create_display(parts: DisplayParts) -> Option<DisplayDriver> {
|
||||
let mut display = crate::driver::sh1106::Sh1106::new(parts.i2c, 0x3D);
|
||||
|
||||
embassy_time::Timer::after_millis(100).await;
|
||||
if display.init().await.is_err() {
|
||||
|
||||
Reference in new issue
Block a user