//! Shared types and peripheral wiring for ESP32-S3 boards. //! //! Delegates MCU-level init to `hal::esp32s3` and defines peripheral-level //! types (RadioDriver, display) used by ESP32-S3 board files. //! Each board defines its own comm types (UsbParts/UartParts) directly. use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_time::Delay; use esp_hal::gpio::{Input, InputConfig, InputPin, Level, Output, OutputConfig, OutputPin, Pull}; use lora_phy::iv::GenericSx126xInterfaceVariant; use lora_phy::sx126x::{self, Sx1262, Sx126x}; use crate::hal::esp32s3 as mcu; // ── Re-export MCU types ───────────────────────────────────────────── pub use mcu::{I2cBus as DisplayI2c, SpiBus}; // ── Concrete peripheral types ─────────────────────────────────────── type Nss = Output<'static>; type Iv = GenericSx126xInterfaceVariant, Input<'static>>; type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>; pub type RadioDriver = Sx126x; // ── Peripheral bundles ────────────────────────────────────────────── pub struct RadioParts { pub driver: RadioDriver, pub delay: Delay, } // SSD1306 display support is Heltec-specific. ESP32-S3 boards that use a // different controller (e.g. ThinkNode-M2 uses SH1106) provide their own // DisplayParts / DisplayDriver / create_display directly. #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] pub struct DisplayParts { pub i2c: DisplayI2c, } // ── Display driver ────────────────────────────────────────────────── #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] pub type DisplayDriver = ssd1306::Ssd1306Async< ssd1306::prelude::I2CInterface, ssd1306::size::DisplaySize128x64, ssd1306::mode::BufferedGraphicsModeAsync, >; /// Construct and initialize an SSD1306 display from raw I2C. #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] pub async fn create_display(i2c: DisplayI2c) -> Option { use ssd1306::mode::DisplayConfigAsync; use ssd1306::prelude::{Brightness, DisplayRotation}; use ssd1306::size::DisplaySize128x64; use ssd1306::{I2CDisplayInterface, Ssd1306Async}; let interface = I2CDisplayInterface::new(i2c); let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) .into_buffered_graphics_mode(); embassy_time::Timer::after_millis(100).await; if display.init().await.is_err() { defmt::error!("SSD1306 display init failed"); return None; } let _ = display.set_brightness(Brightness::BRIGHTEST).await; Some(display) } // ── Shared peripheral init helpers ────────────────────────────────── /// Construct SX1262 radio from an initialized SPI bus. /// /// Generic over GPIO type so boards with different radio pin assignments can /// share this helper. `tcxo_voltage` selects the DIO3-driven TCXO supply: /// Heltec boards use `Ctrl1V8`, the ELECROW ThinkNode-M2 uses `Ctrl3V3`, etc. pub fn init_radio( spi_bus: &'static embassy_sync::mutex::Mutex, nss_pin: impl OutputPin + 'static, reset_pin: impl OutputPin + 'static, dio1_pin: impl InputPin + 'static, busy_pin: impl InputPin + 'static, tcxo_voltage: sx126x::TcxoCtrlVoltage, ) -> RadioParts { let nss = Output::new(nss_pin, Level::High, OutputConfig::default()); let spi_device = SpiDevice::new(spi_bus, nss); let reset = Output::new(reset_pin, Level::High, OutputConfig::default()); let dio1 = Input::new(dio1_pin, InputConfig::default().with_pull(Pull::Down)); let busy = Input::new(busy_pin, InputConfig::default().with_pull(Pull::Down)); let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, None, None) .expect("SX1262 interface init"); let sx_config = sx126x::Config { chip: Sx1262, tcxo_ctrl: Some(tcxo_voltage), use_dcdc: true, rx_boost: false, }; RadioParts { driver: Sx126x::new(spi_device, iv, sx_config), delay: Delay, } }