refactor: extract hal/ and driver/ modules

- Created hal/esp32s3.rs with MCU primitives (SPI, I2C, USB, UART, timer, MAC)
- Created hal/nrf52840.rs with shared nRF boilerplate (interrupts, bus wrapping, VBUS, MAC)
- Moved SH1106 OLED driver from display/ to driver/ (clear separation from app code)
- Board esp32s3.rs now delegates MCU init to hal, keeps only peripheral wiring (RadioParts)
- nRF board files use hal for shared StaticCell patterns, mac_address, interrupt bindings
- Board files still own pin assignments and peripheral construction
This commit is contained in:
swaits committed 2026-04-07 09:38:56 -06:00
1 parent 8bdaa72bc3
commit fb334118fd
13 files changed
+300 -252

No files matched your search

+121
View File
@@ -0,0 +1,121 @@
//! ESP32-S3 MCU initialization primitives.
//!
//! Provides low-level bus and peripheral init for ESP32-S3 boards.
//! Board files call these with specific pins and construct higher-level
//! drivers (Sx126x, SSD1306, etc.) themselves.
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use esp_hal::dma::{DmaRxBuf, DmaTxBuf};
use esp_hal::i2c::master::{Config as I2cConfig, I2c};
use esp_hal::spi::master::{Config as SpiConfig, Spi, SpiDmaBus};
use esp_hal::spi::Mode as SpiMode;
use esp_hal::time::Rate;
use esp_hal::timer::timg::TimerGroup;
use static_cell::StaticCell;
// ── MCU-level types ─────────────────────────────────────────────────
pub type SpiBus = SpiDmaBus<'static, esp_hal::Async>;
pub type I2cBus = I2c<'static, esp_hal::Async>;
pub type UsbOtgDriver = esp_hal::otg_fs::asynch::Driver<'static>;
pub type UartDriver = esp_hal::uart::Uart<'static, esp_hal::Async>;
// ── Timer ───────────────────────────────────────────────────────────
/// Start the ESP-RTOS scheduler and Embassy time driver.
pub fn start_timer(timg0: esp_hal::peripherals::TIMG0<'static>) {
let timg0 = TimerGroup::new(timg0);
esp_rtos::start(timg0.timer0);
}
// ── SPI bus ─────────────────────────────────────────────────────────
/// Initialize SPI2 with DMA and wrap in a shared bus (StaticCell + Mutex).
///
/// Returns a reference to the shared SPI bus suitable for `SpiDevice::new()`.
pub fn init_spi(
spi2: esp_hal::peripherals::SPI2<'static>,
dma_ch0: esp_hal::peripherals::DMA_CH0<'static>,
sck: esp_hal::peripherals::GPIO9<'static>,
mosi: esp_hal::peripherals::GPIO10<'static>,
miso: esp_hal::peripherals::GPIO11<'static>,
) -> &'static embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus> {
let spi = Spi::new(
spi2,
SpiConfig::default()
.with_frequency(Rate::from_mhz(1))
.with_mode(SpiMode::_0),
)
.expect("SPI init")
.with_sck(sck)
.with_mosi(mosi)
.with_miso(miso)
.with_dma(dma_ch0);
let (rx_buf, rx_desc, tx_buf, tx_desc) = esp_hal::dma_buffers!(256);
let dma_rx = DmaRxBuf::new(rx_desc, rx_buf).expect("DMA RX buf");
let dma_tx = DmaTxBuf::new(tx_desc, tx_buf).expect("DMA TX buf");
let spi = spi.with_buffers(dma_rx, dma_tx).into_async();
static SPI_BUS: StaticCell<embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus>> =
StaticCell::new();
SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi))
}
// ── I2C bus ─────────────────────────────────────────────────────────
/// Initialize I2C0 for peripherals (display, sensors, etc.).
pub fn init_i2c(
i2c0: esp_hal::peripherals::I2C0<'static>,
sda: esp_hal::peripherals::GPIO17<'static>,
scl: esp_hal::peripherals::GPIO18<'static>,
) -> I2cBus {
I2c::new(i2c0, I2cConfig::default())
.expect("I2C init")
.with_sda(sda)
.with_scl(scl)
.into_async()
}
// ── USB OTG ─────────────────────────────────────────────────────────
/// Initialize USB OTG FS driver for CDC-ACM.
pub fn init_usb(
usb0: esp_hal::peripherals::USB0<'static>,
dp: esp_hal::peripherals::GPIO20<'static>,
dm: esp_hal::peripherals::GPIO19<'static>,
) -> UsbOtgDriver {
let usb_inst = esp_hal::otg_fs::Usb::new(usb0, dp, dm);
static EP_OUT_BUF: StaticCell<[u8; 1024]> = StaticCell::new();
let ep_out_buf = EP_OUT_BUF.init([0u8; 1024]);
esp_hal::otg_fs::asynch::Driver::new(
usb_inst,
ep_out_buf,
esp_hal::otg_fs::asynch::Config::default(),
)
}
// ── UART ────────────────────────────────────────────────────────────
/// Initialize UART0 for boards with USB-UART bridge chips (e.g. CP2102).
pub fn init_uart(
uart0: esp_hal::peripherals::UART0<'static>,
tx: esp_hal::peripherals::GPIO43<'static>,
rx: esp_hal::peripherals::GPIO44<'static>,
) -> UartDriver {
use esp_hal::uart::{Config as UartConfig, Uart};
Uart::new(uart0, UartConfig::default())
.expect("UART init")
.with_tx(tx)
.with_rx(rx)
.into_async()
}
// ── MAC address ─────────────────────────────────────────────────────
/// Read the factory-programmed MAC address from eFuse.
pub fn mac_address() -> [u8; 6] {
esp_hal::efuse::Efuse::mac_address()
}
+10
View File
@@ -0,0 +1,10 @@
//! MCU family initialization primitives.
//!
//! Each submodule provides low-level bus and peripheral init for one MCU family.
//! Board files call these with specific pins and construct higher-level drivers.
#[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))]
pub mod esp32s3;
#[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))]
pub mod nrf52840;
+82
View File
@@ -0,0 +1,82 @@
//! nRF52840 MCU initialization primitives.
//!
//! Provides shared boilerplate for nRF52840 boards: interrupt bindings,
//! type aliases, StaticCell bus wrapping, USB init, and MAC address reading.
use embassy_nrf::bind_interrupts;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use static_cell::StaticCell;
// ── Interrupts (shared by all nRF52840 boards) ─────────────────────
bind_interrupts!(pub struct Irqs {
USBD => embassy_nrf::usb::InterruptHandler<embassy_nrf::peripherals::USBD>;
SPIM3 => embassy_nrf::spim::InterruptHandler<embassy_nrf::peripherals::SPI3>;
TWISPI0 => embassy_nrf::twim::InterruptHandler<embassy_nrf::peripherals::TWISPI0>;
});
// ── MCU-level types ─────────────────────────────────────────────────
pub type SpiBus = embassy_nrf::spim::Spim<'static>;
pub type I2cBus = embassy_nrf::twim::Twim<'static>;
pub type UsbNrfDriver =
embassy_nrf::usb::Driver<'static, &'static embassy_nrf::usb::vbus_detect::SoftwareVbusDetect>;
// ── SPI bus sharing ─────────────────────────────────────────────────
/// Wrap an initialized SPI peripheral in a shared bus (StaticCell + Mutex).
pub fn share_spi_bus(
spi: SpiBus,
) -> &'static embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus> {
static SPI_BUS: StaticCell<embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus>> =
StaticCell::new();
SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi))
}
// ── I2C DMA buffer ──────────────────────────────────────────────────
/// Allocate a DMA-safe buffer for Twim I2C.
///
/// Every nRF52840 Twim user needs a `&'static mut [u8]` buffer.
/// This provides one via StaticCell so board files don't repeat the pattern.
pub fn alloc_i2c_buffer() -> &'static mut [u8; 256] {
static TWIM_BUF: StaticCell<[u8; 256]> = StaticCell::new();
TWIM_BUF.init([0u8; 256])
}
// ── USB VBUS detection ──────────────────────────────────────────────
/// Allocate and initialize software VBUS detection.
pub fn alloc_vbus_detect(
vbus_detect: bool,
self_powered: bool,
) -> &'static embassy_nrf::usb::vbus_detect::SoftwareVbusDetect {
static VBUS: StaticCell<embassy_nrf::usb::vbus_detect::SoftwareVbusDetect> = StaticCell::new();
VBUS.init(embassy_nrf::usb::vbus_detect::SoftwareVbusDetect::new(
vbus_detect,
self_powered,
))
}
// ── MAC address ─────────────────────────────────────────────────────
/// Read the factory-programmed device address from FICR registers.
pub fn mac_address() -> [u8; 6] {
// SAFETY: 0x10000000 is the nRF52840 FICR (Factory Information Configuration
// Registers) base address. Offsets 0x0A4 and 0x0A8 are the DEVICEADDR[0] and
// DEVICEADDR[1] registers containing the factory-programmed device address.
// read_volatile is correct for memory-mapped hardware registers.
unsafe {
let ficr = 0x10000000 as *const u32;
let addr0 = core::ptr::read_volatile(ficr.byte_add(0x0A4));
let addr1 = core::ptr::read_volatile(ficr.byte_add(0x0A8));
[
addr0 as u8,
(addr0 >> 8) as u8,
(addr0 >> 16) as u8,
(addr0 >> 24) as u8,
addr1 as u8,
(addr1 >> 8) as u8,
]
}
}