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).
3.0 KiB
3.0 KiB
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 fromboard/esp32s3.rsandhal/esp32s3.rs) - nRF52840 boards: copy
rak_wisblock_4631.rs(uses shared helpers fromhal/nrf52840.rs) - RP2040 boards: copy
waveshare_rp2040_lora.rs(uses shared helpers fromhal/rp2040.rs) - New MCU family: create a new
hal/<mcu>.rswith MCU primitives first
build.rs auto-discovers your board file — any .rs file in src/board/ that
implements LoRaBoard for Board is automatically detected as a board. Helper
modules (like esp32s3.rs) are auto-discovered via use super::<helper> imports
in board files. No template editing or exclusion lists needed.
2. Define concrete types
Your board must export these type aliases:
pub type RadioDriver = ...; // Must work with lora_phy::LoRa<RadioDriver, Delay>
pub type UsbDriver = ...; // Must implement embassy_usb_driver::Driver<'static>
// (or UartDriver for UART boards)
pub type DisplayI2c = ...; // Must implement embedded_hal_async::i2c::I2c
pub type DisplayDriver = ...; // Must implement DrawTarget<Color = BinaryColor>
3. Define peripheral bundles
pub struct RadioParts {
pub driver: RadioDriver,
pub delay: embassy_time::Delay,
}
pub struct UsbParts { // or UartParts for UART boards
pub driver: UsbDriver,
}
pub struct DisplayParts {
pub i2c: DisplayI2c,
}
4. Implement the LoRaBoard trait
use super::traits::{BoardParts, LoRaBoard};
impl LoRaBoard for Board {
const NAME: &'static str = "Your Board Name";
const TX_POWER_RANGE: (i8, i8) = (-9, 22); // check your radio + PA
type RadioParts = RadioParts;
type CommParts = UsbParts; // or UartParts
type DisplayParts = DisplayParts;
type DisplayDriver = DisplayDriver;
fn init() -> Self { ... }
fn mac_address() -> [u8; 6] { ... } // read from efuse, FICR, etc.
fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts> {
// Initialize buses via hal::, construct drivers, return BoardParts
BoardParts { radio, host, display: Some(display_parts), mac: Self::mac_address() }
}
}
5. Add display init
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()`.
}
6. Add Cargo feature
In firmware/Cargo.toml, add a feature with your board's HAL dependencies:
[features]
your_board = ["dep:your-hal", ...]
7. Add to justfile
Add your board's feature/target/chip to the board definitions at the top of justfile.
8. Build and test
just check your_board # Must compile
just clippy your_board # Must be clean
just build your_board # Produces firmware