chore: massive repo reorganization
This commit is contained in:
commit
bb588c93ce
26 files changed
+5321
No files matched your search
@@ -0,0 +1,92 @@
|
||||
# Porting DongLoRa to a New Board
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Create the board file
|
||||
|
||||
Create `src/board/your_board.rs`. Use an existing board as a template:
|
||||
- ESP32-S3 boards: copy `heltec_v4.rs` (uses shared helpers from `esp32s3.rs`)
|
||||
- nRF52840 boards: copy `rak_wisblock_4631.rs`
|
||||
|
||||
ESP32-S3 boards share common type aliases, peripheral bundles, and init helpers
|
||||
via `src/board/esp32s3.rs`. Your board file only needs the trait impl and
|
||||
board-specific init (Vext GPIO, display reset). If you add a new ESP32-S3 board,
|
||||
add its feature to the `#[cfg(any(...))]` gate on `mod esp32s3` in
|
||||
`src/board/mod.rs.j2`.
|
||||
|
||||
### 2. Define concrete types
|
||||
|
||||
Your board must export these type aliases:
|
||||
|
||||
```rust
|
||||
pub type RadioDriver = ...; // Must work with lora_phy::LoRa<RadioDriver, Delay>
|
||||
pub type UsbDriver = ...; // Must implement embassy_usb_driver::Driver<'static>
|
||||
pub type DisplayI2c = ...; // Must implement embedded_hal_async::i2c::I2c
|
||||
```
|
||||
|
||||
### 3. Define peripheral bundles
|
||||
|
||||
```rust
|
||||
pub struct RadioParts {
|
||||
pub driver: RadioDriver,
|
||||
pub delay: embassy_time::Delay,
|
||||
}
|
||||
|
||||
pub struct UsbParts {
|
||||
pub driver: UsbDriver,
|
||||
}
|
||||
|
||||
pub struct DisplayParts {
|
||||
pub i2c: DisplayI2c,
|
||||
pub mac: [u8; 6],
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Implement the LoRaBoard trait
|
||||
|
||||
```rust
|
||||
use super::traits::LoRaBoard;
|
||||
|
||||
impl LoRaBoard for Board {
|
||||
const NAME: &'static str = "Your Board Name";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22); // check your radio + PA
|
||||
fn init() -> Self { ... }
|
||||
fn mac_address() -> [u8; 6] { ... } // read from efuse, FICR, etc.
|
||||
}
|
||||
```
|
||||
|
||||
The compiler will refuse to build if any of these are missing.
|
||||
|
||||
### 5. Implement `Board::into_parts()`
|
||||
|
||||
```rust
|
||||
impl Board {
|
||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||
// Initialize SPI, DMA, radio, USB, I2C
|
||||
// Return peripheral bundles
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Add Cargo feature
|
||||
|
||||
In `firmware/Cargo.toml`, add a feature with your board's HAL dependencies:
|
||||
|
||||
```toml
|
||||
[features]
|
||||
your_board = ["dep:your-hal", ...]
|
||||
```
|
||||
|
||||
### 7. Add to justfile
|
||||
|
||||
Add your board's feature/target/chip to the `boards` list at the top of `justfile`.
|
||||
|
||||
### 8. Build and test
|
||||
|
||||
```sh
|
||||
just check your_board # Must compile
|
||||
just clippy your_board # Must be clean
|
||||
just build your_board # Produces firmware
|
||||
```
|
||||
|
||||
`build.rs` auto-discovers your `.rs` file — no template editing needed.
|
||||
@@ -0,0 +1,150 @@
|
||||
//! Shared types and initialization helpers for ESP32-S3 boards.
|
||||
//!
|
||||
//! All ESP32-S3 boards in this project share the same SPI/DMA, USB OTG,
|
||||
//! and I2C peripheral wiring. This module provides the common type aliases,
|
||||
//! peripheral bundle structs, and init functions. Each board file re-exports
|
||||
//! these types and calls the helpers from its own `Board::into_parts()`.
|
||||
|
||||
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_time::Delay;
|
||||
use esp_hal::dma::{DmaRxBuf, DmaTxBuf};
|
||||
use esp_hal::gpio::{Input, InputConfig, Level, Output, OutputConfig, Pull};
|
||||
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;
|
||||
|
||||
use lora_phy::iv::GenericSx126xInterfaceVariant;
|
||||
use lora_phy::sx126x::{self, Sx1262, Sx126x};
|
||||
|
||||
// ── Concrete peripheral types ────────────────────────────────────────
|
||||
|
||||
type SpiBus = SpiDmaBus<'static, esp_hal::Async>;
|
||||
type Nss = Output<'static>;
|
||||
type Iv = GenericSx126xInterfaceVariant<Output<'static>, Input<'static>>;
|
||||
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>;
|
||||
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
||||
|
||||
pub type UsbDriver = esp_hal::otg_fs::asynch::Driver<'static>;
|
||||
|
||||
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
||||
|
||||
// ── Peripheral bundles ───────────────────────────────────────────────
|
||||
|
||||
pub struct RadioParts {
|
||||
pub driver: RadioDriver,
|
||||
pub delay: Delay,
|
||||
}
|
||||
|
||||
pub struct UsbParts {
|
||||
pub driver: UsbDriver,
|
||||
}
|
||||
|
||||
pub struct DisplayParts {
|
||||
pub i2c: DisplayI2c,
|
||||
pub mac: [u8; 6],
|
||||
}
|
||||
|
||||
// ── Shared initialization helpers ───────────────────────────────────
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Initialize SPI + DMA + SX1262 radio.
|
||||
///
|
||||
/// All current ESP32-S3 boards use the same SPI/radio pin mapping:
|
||||
/// SCK=GPIO9, MOSI=GPIO10, MISO=GPIO11, NSS=GPIO8,
|
||||
/// Reset=GPIO12, DIO1=GPIO14, BUSY=GPIO13.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn init_radio(
|
||||
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>,
|
||||
nss_pin: esp_hal::peripherals::GPIO8<'static>,
|
||||
reset_pin: esp_hal::peripherals::GPIO12<'static>,
|
||||
dio1_pin: esp_hal::peripherals::GPIO14<'static>,
|
||||
busy_pin: esp_hal::peripherals::GPIO13<'static>,
|
||||
) -> RadioParts {
|
||||
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();
|
||||
let spi_bus = SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi));
|
||||
|
||||
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(sx126x::TcxoCtrlVoltage::Ctrl1V7),
|
||||
use_dcdc: true,
|
||||
rx_boost: false,
|
||||
};
|
||||
|
||||
RadioParts {
|
||||
driver: Sx126x::new(spi_device, iv, sx_config),
|
||||
delay: Delay,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize USB OTG CDC-ACM driver.
|
||||
pub fn init_usb(
|
||||
usb0: esp_hal::peripherals::USB0<'static>,
|
||||
dp: esp_hal::peripherals::GPIO20<'static>,
|
||||
dm: esp_hal::peripherals::GPIO19<'static>,
|
||||
) -> UsbParts {
|
||||
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]);
|
||||
UsbParts {
|
||||
driver: esp_hal::otg_fs::asynch::Driver::new(
|
||||
usb_inst,
|
||||
ep_out_buf,
|
||||
esp_hal::otg_fs::asynch::Config::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize I2C bus for display.
|
||||
pub fn init_display_i2c(
|
||||
i2c0: esp_hal::peripherals::I2C0<'static>,
|
||||
sda: esp_hal::peripherals::GPIO17<'static>,
|
||||
scl: esp_hal::peripherals::GPIO18<'static>,
|
||||
) -> DisplayI2c {
|
||||
I2c::new(i2c0, I2cConfig::default())
|
||||
.expect("I2C init")
|
||||
.with_sda(sda)
|
||||
.with_scl(scl)
|
||||
.into_async()
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use esp_hal::gpio::{Level, Output, OutputConfig};
|
||||
|
||||
use super::esp32s3;
|
||||
use super::traits::LoRaBoard;
|
||||
|
||||
#[allow(unused_imports)] // Re-exported for other modules (display, radio, usb tasks)
|
||||
pub use super::esp32s3::{DisplayI2c, DisplayParts, RadioDriver, RadioParts, UsbDriver, UsbParts};
|
||||
|
||||
// ── Board init ───────────────────────────────────────────────────────
|
||||
|
||||
pub struct Board {
|
||||
p: esp_hal::peripherals::Peripherals,
|
||||
}
|
||||
|
||||
impl LoRaBoard for Board {
|
||||
const NAME: &'static str = "Heltec V3";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
fn init() -> Self {
|
||||
let p = esp_hal::init(esp_hal::Config::default());
|
||||
Self { p }
|
||||
}
|
||||
|
||||
fn mac_address() -> [u8; 6] {
|
||||
esp_hal::efuse::Efuse::mac_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||
let p = self.p;
|
||||
|
||||
esp32s3::start_timer(p.TIMG0);
|
||||
|
||||
// Vext power: GPIO21, active LOW to enable OLED
|
||||
let vext = Output::new(p.GPIO21, Level::Low, OutputConfig::default());
|
||||
core::mem::forget(vext); // hold pin low permanently; drop would reset it
|
||||
|
||||
let radio = esp32s3::init_radio(
|
||||
p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11, p.GPIO8, p.GPIO12, p.GPIO14,
|
||||
p.GPIO13,
|
||||
);
|
||||
let usb = esp32s3::init_usb(p.USB0, p.GPIO20, p.GPIO19);
|
||||
let i2c = esp32s3::init_display_i2c(p.I2C0, p.GPIO17, p.GPIO18);
|
||||
let mac = Self::mac_address();
|
||||
let display = Some(DisplayParts { i2c, mac });
|
||||
|
||||
(radio, usb, display)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use esp_hal::gpio::{Level, Output, OutputConfig};
|
||||
|
||||
use super::esp32s3;
|
||||
use super::traits::LoRaBoard;
|
||||
|
||||
#[allow(unused_imports)] // Re-exported for other modules (display, radio, usb tasks)
|
||||
pub use super::esp32s3::{DisplayI2c, DisplayParts, RadioDriver, RadioParts, UsbDriver, UsbParts};
|
||||
|
||||
// ── Board implementation ────────────────────────────────────────────
|
||||
|
||||
pub struct Board {
|
||||
p: esp_hal::peripherals::Peripherals,
|
||||
}
|
||||
|
||||
impl LoRaBoard for Board {
|
||||
const NAME: &'static str = "Heltec V4";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
fn init() -> Self {
|
||||
let p = esp_hal::init(esp_hal::Config::default());
|
||||
Self { p }
|
||||
}
|
||||
|
||||
fn mac_address() -> [u8; 6] {
|
||||
esp_hal::efuse::Efuse::mac_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||
let p = self.p;
|
||||
|
||||
esp32s3::start_timer(p.TIMG0);
|
||||
|
||||
// Vext power: GPIO36, active LOW to enable peripherals
|
||||
let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
core::mem::forget(vext); // hold pin low permanently; drop would reset it
|
||||
|
||||
let radio = esp32s3::init_radio(
|
||||
p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11, p.GPIO8, p.GPIO12, p.GPIO14,
|
||||
p.GPIO13,
|
||||
);
|
||||
|
||||
// Note: switches internal USB PHY from Serial-JTAG to OTG.
|
||||
// espflash --monitor will stop working after this point.
|
||||
let usb = esp32s3::init_usb(p.USB0, p.GPIO20, p.GPIO19);
|
||||
|
||||
// SSD1315 display reset: pulse GPIO21 low->high before I2C init
|
||||
let mut display_rst = Output::new(p.GPIO21, Level::Low, OutputConfig::default());
|
||||
esp_hal::delay::Delay::new().delay_millis(10);
|
||||
display_rst.set_high();
|
||||
esp_hal::delay::Delay::new().delay_millis(10);
|
||||
core::mem::forget(display_rst); // hold reset high permanently
|
||||
|
||||
let i2c = esp32s3::init_display_i2c(p.I2C0, p.GPIO17, p.GPIO18);
|
||||
let mac = Self::mac_address();
|
||||
let display = Some(DisplayParts { i2c, mac });
|
||||
|
||||
(radio, usb, display)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// @generated by build.rs — DO NOT EDIT
|
||||
pub mod traits;
|
||||
pub use traits::*;
|
||||
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))]
|
||||
mod esp32s3;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
{%- for board in boards %}
|
||||
{% if loop.first %}if{% else %}else if{% endif %} #[cfg(feature = "{{ board }}")] {
|
||||
mod {{ board }};
|
||||
pub use {{ board }}::*;
|
||||
}
|
||||
{%- endfor %}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
|
||||
use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull};
|
||||
use embassy_nrf::spim::{self, Spim};
|
||||
use embassy_nrf::twim::{self, Twim};
|
||||
use embassy_nrf::usb::Driver;
|
||||
use embassy_nrf::{bind_interrupts, Peripherals};
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_time::Delay;
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use lora_phy::iv::GenericSx126xInterfaceVariant;
|
||||
use lora_phy::sx126x::{self, Sx1262, Sx126x};
|
||||
|
||||
use super::traits::LoRaBoard;
|
||||
|
||||
bind_interrupts!(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>;
|
||||
});
|
||||
|
||||
// ── Concrete peripheral types ────────────────────────────────────────
|
||||
|
||||
type SpiBus = Spim<'static>;
|
||||
type Nss = Output<'static>;
|
||||
type Iv = GenericSx126xInterfaceVariant<Output<'static>, Input<'static>>;
|
||||
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>;
|
||||
/// SX126x driver (pre-LoRa construction — LoRa::new is async).
|
||||
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
||||
|
||||
/// USB driver for CDC-ACM.
|
||||
pub type UsbDriver = Driver<'static, &'static embassy_nrf::usb::vbus_detect::SoftwareVbusDetect>;
|
||||
|
||||
/// I2C bus for an optional SSD1306 OLED.
|
||||
pub type DisplayI2c = Twim<'static>;
|
||||
|
||||
// ── Peripheral bundles ───────────────────────────────────────────────
|
||||
|
||||
pub struct RadioParts {
|
||||
pub driver: RadioDriver,
|
||||
pub delay: Delay,
|
||||
}
|
||||
|
||||
pub struct UsbParts {
|
||||
pub driver: UsbDriver,
|
||||
}
|
||||
|
||||
pub struct DisplayParts {
|
||||
pub i2c: DisplayI2c,
|
||||
pub mac: [u8; 6],
|
||||
}
|
||||
|
||||
// ── Board init ───────────────────────────────────────────────────────
|
||||
|
||||
pub struct Board {
|
||||
p: Peripherals,
|
||||
}
|
||||
|
||||
impl super::traits::LoRaBoard for Board {
|
||||
const NAME: &'static str = "RAK WisBlock 4631";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
fn init() -> Self {
|
||||
let p = embassy_nrf::init(Default::default());
|
||||
Self { p }
|
||||
}
|
||||
|
||||
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,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Board {
|
||||
|
||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||
let p = self.p;
|
||||
|
||||
// ── SPI bus for SX1262 ───────────────────────────────────
|
||||
let mut spi_cfg = spim::Config::default();
|
||||
spi_cfg.frequency = spim::Frequency::M1;
|
||||
let spi = Spim::new(p.SPI3, Irqs, p.P1_11, p.P1_13, p.P1_12, spi_cfg);
|
||||
|
||||
static SPI_BUS: StaticCell<embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus>> =
|
||||
StaticCell::new();
|
||||
let spi_bus = SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi));
|
||||
|
||||
let nss = Output::new(p.P1_10, Level::High, OutputDrive::Standard);
|
||||
let spi_device = SpiDevice::new(spi_bus, nss);
|
||||
|
||||
// ── SX1262 control pins ──────────────────────────────────
|
||||
let reset = Output::new(p.P1_06, Level::High, OutputDrive::Standard);
|
||||
let dio1 = Input::new(p.P1_15, Pull::Down);
|
||||
let busy = Input::new(p.P1_14, 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(sx126x::TcxoCtrlVoltage::Ctrl1V7),
|
||||
use_dcdc: true,
|
||||
rx_boost: false,
|
||||
};
|
||||
|
||||
let radio = RadioParts {
|
||||
driver: Sx126x::new(spi_device, iv, sx_config),
|
||||
delay: Delay,
|
||||
};
|
||||
|
||||
// ── USB ──────────────────────────────────────────────────
|
||||
static VBUS: StaticCell<embassy_nrf::usb::vbus_detect::SoftwareVbusDetect> =
|
||||
StaticCell::new();
|
||||
let vbus = VBUS.init(embassy_nrf::usb::vbus_detect::SoftwareVbusDetect::new(true, false));
|
||||
let usb = UsbParts {
|
||||
driver: Driver::new(p.USBD, Irqs, vbus),
|
||||
};
|
||||
|
||||
// ── Display (optional RAK1921 SSD1306 OLED on I2C) ──────
|
||||
// Always provide the I2C bus; display_task detects presence
|
||||
// via SSD1306 init (fails gracefully if no display attached).
|
||||
static TWIM_BUF: StaticCell<[u8; 256]> = StaticCell::new();
|
||||
let twim_buf = TWIM_BUF.init([0u8; 256]);
|
||||
let i2c_cfg = twim::Config::default();
|
||||
let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_13, p.P0_14, i2c_cfg, twim_buf);
|
||||
let mac = Self::mac_address();
|
||||
let display = Some(DisplayParts { i2c, mac });
|
||||
|
||||
(radio, usb, display)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//! Board abstraction trait.
|
||||
//!
|
||||
//! Every board must implement [`LoRaBoard`]. The compiler enforces completeness —
|
||||
//! missing constants or methods are compile errors, not runtime surprises.
|
||||
//!
|
||||
//! Board modules also export concrete types (`RadioParts`, `UsbParts`,
|
||||
//! `DisplayParts`, `RadioDriver`, `UsbDriver`, `DisplayI2c`) that the tasks
|
||||
//! use directly. The trait verifies metadata and initialization; the types
|
||||
//! verify hardware compatibility.
|
||||
//!
|
||||
//! See `src/board/PORTING.md` for a step-by-step guide to adding a new board.
|
||||
|
||||
/// Compile-time contract for a DongLoRa board.
|
||||
///
|
||||
/// Every board's `Board` struct must implement this trait. It verifies that
|
||||
/// the board provides all required metadata and initialization methods.
|
||||
/// The tasks use the board's concrete types directly (not the trait's
|
||||
/// associated types) because Embassy tasks must be concrete.
|
||||
pub trait LoRaBoard {
|
||||
/// Human-readable board name (shown on display splash screen).
|
||||
const NAME: &'static str;
|
||||
|
||||
/// TX power range in dBm (min, max) for this board's radio + PA.
|
||||
const TX_POWER_RANGE: (i8, i8);
|
||||
|
||||
/// Initialize the board hardware.
|
||||
fn init() -> Self;
|
||||
|
||||
/// Read the board's unique hardware address (MAC, device ID, etc.).
|
||||
fn mac_address() -> [u8; 6];
|
||||
}
|
||||
Reference in new issue
Block a user