feat: add ELECROW ThinkNode-M2 board support
ESP32-S3 + SX1262 + 1.3" SH1106 OLED + CP2102 UART bridge +
1000 mAh Li-Po. Pin map verified against Meshtastic's
variants/esp32s3/ELECROW-ThinkNode-M2. DIO3 TCXO at 3.3 V (vs 1.8 V
on Heltec), VEXT_ENABLE active HIGH (opposite polarity), dedicated
LORA_POWER_EN on GPIO48 sequenced before SPI init. Reuses the
existing async SH1106 driver at I2C address 0x3C.
Refactors hal::esp32s3::{init_spi,init_i2c} and board::esp32s3::init_radio
to accept generic GPIO bounds so ESP32-S3 boards with different pin
assignments can share the helpers. init_radio now takes a
TcxoCtrlVoltage parameter instead of hardcoding Ctrl1V8.
Incidental cleanups needed to make `just check-all` green:
- Replace the core::mem::forget(output) pattern with let _foo = ...;
current esp-hal makes Output non-Drop, so forget is a no-op that
trips clippy's forget_non_drop lint.
- Switch justfile xtensa builds from `cargo +nightly` with a RUSTC
override to `cargo +esp` so the compiler and rust-src versions stay
aligned. Public nightly's rust-src had drifted two months ahead of
the esp fork and broke -Zbuild-std on core.
This commit is contained in:
14 files changed
+262
-46
No files matched your search
@@ -7,8 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- ELECROW ThinkNode-M2 board support (ESP32-S3 + SX1262 + 1.3" SH1106 OLED
|
||||
via CP2102 UART bridge, 1000 mAh Li-Po handheld)
|
||||
- Pin map verified against Meshtastic's
|
||||
`variants/esp32s3/ELECROW-ThinkNode-M2`
|
||||
- DIO3 TCXO driven at 3.3 V (not 1.8 V like Heltec)
|
||||
- Dedicated `LORA_POWER_EN` (GPIO48) sequenced HIGH before SPI init
|
||||
- `VEXT_ENABLE` (GPIO46) is active HIGH — opposite polarity from Heltec
|
||||
- Reuses the existing async SH1106 driver at I2C address 0x3C
|
||||
|
||||
### Changed
|
||||
|
||||
- `hal::esp32s3::{init_spi, init_i2c}` and `board::esp32s3::init_radio` are
|
||||
now generic over GPIO type, so ESP32-S3 boards with different pin
|
||||
assignments can share the helpers.
|
||||
- `board::esp32s3::init_radio` takes a `TcxoCtrlVoltage` parameter instead
|
||||
of hardcoding `Ctrl1V8` — needed for ThinkNode-M2's 3.3 V TCXO.
|
||||
- SSD1306 display items in `board::esp32s3` are now cfg-gated to Heltec
|
||||
boards (ThinkNode-M2 supplies its own SH1106 `create_display`).
|
||||
- `justfile` uses `cargo +esp` for Xtensa targets instead of `cargo +nightly`
|
||||
with a `RUSTC=esp` override — the self-contained esp toolchain keeps its
|
||||
cargo, rustc, and rust-src versions aligned. Public nightly's rust-src
|
||||
had drifted ahead of the esp fork, breaking `-Zbuild-std`.
|
||||
- Migrated to embassy-executor 0.10 and embassy-rp 0.10
|
||||
- ESP crates (esp-hal, esp-rtos, esp-backtrace, esp-println) patched from
|
||||
esp-rs/esp-hal main branch until esp-rtos publishes with 0.10 support
|
||||
@@ -22,6 +44,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- `core::mem::forget(output)` pattern in ESP32-S3 board files is now
|
||||
`let _vext = Output::new(...)` — current esp-hal makes `Output` non-Drop,
|
||||
which made `forget` a no-op and tripped clippy's `forget_non_drop` lint.
|
||||
- UART boards (heltec_v3_uart) display stuck on splash screen after boot.
|
||||
Host task sent `DisplayCommand::Reset` (which sets disconnected=true) instead
|
||||
of `DisplayCommand::On`. UART has no DTR, so nothing ever cleared it.
|
||||
|
||||
@@ -105,6 +105,12 @@ heltec_v4 = [
|
||||
"dep:esp-println",
|
||||
"dep:esp-rtos",
|
||||
]
|
||||
elecrow_thinknode_m2 = [
|
||||
"dep:esp-backtrace",
|
||||
"dep:esp-hal",
|
||||
"dep:esp-println",
|
||||
"dep:esp-rtos",
|
||||
]
|
||||
rak_wisblock_4631 = [
|
||||
"dep:cortex-m",
|
||||
"dep:cortex-m-rt",
|
||||
|
||||
@@ -23,6 +23,7 @@ All commands are run from the `firmware/` directory.
|
||||
| Heltec V3 | `heltec_v3` | ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
|
||||
| Heltec V3 (UART) | `heltec_v3_uart` | ESP32-S3 | SX1262 | CP2102 bridge | xtensa-esp32s3-none-elf |
|
||||
| Heltec V4 | `heltec_v4` | ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
|
||||
| ELECROW ThinkNode-M2 | `elecrow_thinknode_m2` | ESP32-S3 | SX1262 | CP2102 bridge | xtensa-esp32s3-none-elf |
|
||||
| RAK WisBlock 4631 | `rak_wisblock_4631` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
|
||||
| Waveshare RP2040-LoRa | `waveshare_rp2040_lora` | RP2040 | SX1262 | Native CDC-ACM | thumbv6m-none-eabi |
|
||||
| Wio Tracker L1 | `wio_tracker_l1` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
set shell := ["bash", "-c"]
|
||||
|
||||
# Board definitions: feature target chip
|
||||
# Xtensa targets use nightly cargo + esp rustc + -Zbuild-std=core.
|
||||
# Xtensa targets use the self-contained esp toolchain (espup-installed) with
|
||||
# `-Zbuild-std=core,alloc`. Using `+esp` rather than `+nightly` keeps the
|
||||
# compiler and rust-src versions aligned — the public nightly drifts ahead of
|
||||
# the esp fork and breaks core-stdlib compilation otherwise.
|
||||
# Tool versions are pinned in mise.toml; run `just setup` to install everything.
|
||||
heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3"
|
||||
heltec_v3_uart := "heltec_v3_uart xtensa-esp32s3-none-elf esp32s3"
|
||||
heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3"
|
||||
elecrow_thinknode_m2 := "elecrow_thinknode_m2 xtensa-esp32s3-none-elf esp32s3"
|
||||
rak_wisblock_4631 := "rak_wisblock_4631 thumbv7em-none-eabihf nRF52840_xxAA"
|
||||
wio_tracker_l1 := "wio_tracker_l1 thumbv7em-none-eabihf nRF52840_xxAA"
|
||||
waveshare_rp2040_lora := "waveshare_rp2040_lora thumbv6m-none-eabi rp2040"
|
||||
@@ -14,7 +18,7 @@ builds_dir := "builds"
|
||||
version := `sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml`
|
||||
|
||||
# All known boards
|
||||
boards := "heltec_v3 heltec_v3_uart heltec_v4 rak_wisblock_4631 waveshare_rp2040_lora wio_tracker_l1"
|
||||
boards := "heltec_v3 heltec_v3_uart heltec_v4 elecrow_thinknode_m2 rak_wisblock_4631 waveshare_rp2040_lora wio_tracker_l1"
|
||||
|
||||
# Install all required tools and toolchains
|
||||
setup:
|
||||
@@ -111,18 +115,18 @@ _ensure_tools:
|
||||
@mise trust --yes . 2>/dev/null; mise install --quiet
|
||||
|
||||
# Run a cargo command for a board.
|
||||
# Xtensa: nightly cargo + esp rustc (via RUSTC override) + -Zbuild-std=core
|
||||
# Xtensa: esp toolchain (cargo + rustc + rust-src together) + -Zbuild-std=core
|
||||
[private]
|
||||
_cargo board cmd extra_args="":
|
||||
@just _ensure_tools
|
||||
@read -r feat target chip <<< "$(just _info {{board}})"; \
|
||||
env=""; extra=""; \
|
||||
extra=""; buildstd=""; \
|
||||
case "$target" in xtensa-*) \
|
||||
just _require_esp_toolchain; \
|
||||
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
|
||||
env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly"; buildstd="-Zbuild-std=core,alloc";; \
|
||||
extra="+esp"; buildstd="-Zbuild-std=core,alloc";; \
|
||||
esac; \
|
||||
eval $env cargo $extra {{cmd}} --target "$target" --features "$feat" $buildstd {{extra_args}}
|
||||
cargo $extra {{cmd}} --target "$target" --features "$feat" $buildstd {{extra_args}}
|
||||
|
||||
# Ensure a board's toolchain is available, auto-installing if needed
|
||||
[private]
|
||||
@@ -133,17 +137,15 @@ _can_build board:
|
||||
*) rustup target list --installed | grep -q "^$target$" || rustup target add "$target" ;; \
|
||||
esac
|
||||
|
||||
# Ensure the ESP Xtensa toolchain is installed (espup installed via mise)
|
||||
# Ensure the ESP Xtensa toolchain is installed (espup installed via mise).
|
||||
# The esp toolchain ships with its own cargo, rustc, and rust-src — no extra
|
||||
# components are needed on top.
|
||||
[private]
|
||||
_require_esp_toolchain:
|
||||
@just _ensure_tools
|
||||
@if ! rustup toolchain list | grep -q "^esp"; then \
|
||||
echo "ESP toolchain not found, installing via espup (this may take a while)..." >&2; \
|
||||
espup install || { echo "error: espup install failed" >&2; exit 1; }; \
|
||||
fi; \
|
||||
if ! rustup component list --toolchain nightly --installed 2>/dev/null | grep -q "^rust-src"; then \
|
||||
echo "Installing nightly rust-src (needed for -Zbuild-std)..." >&2; \
|
||||
rustup component add rust-src --toolchain nightly || { echo "error: failed to install rust-src" >&2; exit 1; }; \
|
||||
fi
|
||||
|
||||
# Find a serial port matching any of the given VID:PID pairs (checked in order)
|
||||
@@ -211,6 +213,7 @@ _info name:
|
||||
@if [ "{{name}}" == "heltec_v3" ]; then echo "{{heltec_v3}}"; \
|
||||
elif [ "{{name}}" == "heltec_v3_uart" ]; then echo "{{heltec_v3_uart}}"; \
|
||||
elif [ "{{name}}" == "heltec_v4" ]; then echo "{{heltec_v4}}"; \
|
||||
elif [ "{{name}}" == "elecrow_thinknode_m2" ]; then echo "{{elecrow_thinknode_m2}}"; \
|
||||
elif [ "{{name}}" == "rak_wisblock_4631" ]; then echo "{{rak_wisblock_4631}}"; \
|
||||
elif [ "{{name}}" == "waveshare_rp2040_lora" ]; then echo "{{waveshare_rp2040_lora}}"; \
|
||||
elif [ "{{name}}" == "wio_tracker_l1" ]; then echo "{{wio_tracker_l1}}"; \
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
//! ELECROW ThinkNode-M2 — ESP32-S3 Meshtastic handset with SH1106 OLED.
|
||||
//!
|
||||
//! Tamagotchi-form-factor handheld: ESP32-S3-WROOM-1-N4 + SX1262 + 1.3" SH1106
|
||||
//! OLED + CP2102 UART bridge + 1000 mAh Li-Po. Sold pre-loaded with Meshtastic;
|
||||
//! this firmware turns it into a DongLoRa radio bridge.
|
||||
//!
|
||||
//! Pin map (verified from Meshtastic's variants/esp32s3/ELECROW-ThinkNode-M2
|
||||
//! and PR meshtastic/firmware#6354):
|
||||
//! LoRa SPI: SCK=12, MOSI=11, MISO=13, CS=10
|
||||
//! LoRa ctl: RESET=21, BUSY=14, DIO1=3
|
||||
//! LoRa pwr: GPIO48 (must be HIGH before any SPI traffic)
|
||||
//! OLED: I2C0 SDA=16, SCL=15, SH1106 @ 0x3C
|
||||
//! VEXT: GPIO46 (active HIGH — gates OLED supply)
|
||||
//! UART: TX=43, RX=44 via CP2102 bridge
|
||||
//! LED: GPIO6 (active HIGH indicator)
|
||||
//!
|
||||
//! Notable differences from the Heltec ESP32-S3 boards:
|
||||
//! * Display is SH1106 (not SSD1306) — uses the existing sh1106 driver.
|
||||
//! * VEXT_ENABLE polarity is reversed (active HIGH here, active LOW on Heltec).
|
||||
//! * Dedicated LoRa power enable on GPIO48 — must be sequenced before SPI init.
|
||||
//! * TCXO is driven at 3.3 V via DIO3 (Heltec uses 1.8 V).
|
||||
|
||||
use esp_hal::gpio::{Level, Output, OutputConfig};
|
||||
|
||||
use super::esp32s3;
|
||||
use super::traits::{BoardParts, LoRaBoard};
|
||||
use crate::driver::sh1106::Sh1106;
|
||||
use crate::driver::simple_led::SimpleLed;
|
||||
use crate::hal::esp32s3 as mcu;
|
||||
|
||||
pub use super::esp32s3::{DisplayI2c, RadioDriver, RadioParts};
|
||||
|
||||
// ── Concrete peripheral types ───────────────────────────────────────
|
||||
|
||||
pub type DisplayDriver = Sh1106<DisplayI2c>;
|
||||
pub type UartDriver = esp_hal::uart::Uart<'static, esp_hal::Async>;
|
||||
pub type LedDriver = SimpleLed<Output<'static>>;
|
||||
|
||||
// ── Peripheral bundles ──────────────────────────────────────────────
|
||||
|
||||
pub struct UartParts {
|
||||
pub driver: UartDriver,
|
||||
}
|
||||
|
||||
pub struct DisplayParts {
|
||||
pub i2c: DisplayI2c,
|
||||
}
|
||||
|
||||
// ── Display init ────────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
let mut display = Sh1106::new(i2c, 0x3C);
|
||||
|
||||
embassy_time::Timer::after_millis(100).await;
|
||||
if display.init().await.is_err() {
|
||||
defmt::error!("SH1106 display init failed");
|
||||
return None;
|
||||
}
|
||||
let _ = display.set_brightness(0xFF).await;
|
||||
Some(display)
|
||||
}
|
||||
|
||||
// ── Board init ──────────────────────────────────────────────────────
|
||||
|
||||
pub struct Board {
|
||||
p: esp_hal::peripherals::Peripherals,
|
||||
}
|
||||
|
||||
impl LoRaBoard for Board {
|
||||
const NAME: &'static str = "ELECROW ThinkNode-M2";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
type RadioParts = RadioParts;
|
||||
type CommParts = UartParts;
|
||||
type DisplayParts = DisplayParts;
|
||||
type DisplayDriver = DisplayDriver;
|
||||
type LedDriver = LedDriver;
|
||||
|
||||
fn init() -> Self {
|
||||
let p = esp_hal::init(esp_hal::Config::default());
|
||||
Self { p }
|
||||
}
|
||||
|
||||
fn mac_address() -> [u8; 6] {
|
||||
mcu::mac_address()
|
||||
}
|
||||
|
||||
fn into_parts(self) -> BoardParts<RadioParts, UartParts, DisplayParts, LedDriver> {
|
||||
let p = self.p;
|
||||
|
||||
mcu::start_timer(p.TIMG0, p.SW_INTERRUPT);
|
||||
|
||||
// VEXT_ENABLE: GPIO46, active HIGH — powers OLED rail (opposite of Heltec).
|
||||
// Output has no Drop impl, so the pin stays HIGH for program lifetime.
|
||||
let _vext = Output::new(p.GPIO46, Level::High, OutputConfig::default());
|
||||
esp_hal::delay::Delay::new().delay_millis(10);
|
||||
|
||||
// LORA_POWER_EN: GPIO48, active HIGH — must be HIGH before any SPI traffic.
|
||||
let _lora_pwr = Output::new(p.GPIO48, Level::High, OutputConfig::default());
|
||||
esp_hal::delay::Delay::new().delay_millis(10);
|
||||
|
||||
// SPI2 for SX1262: SCK=12, MOSI=11, MISO=13
|
||||
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO12, p.GPIO11, p.GPIO13);
|
||||
|
||||
// SX1262: CS=10, RESET=21, DIO1=3, BUSY=14, TCXO driven at 3.3 V via DIO3.
|
||||
let radio = esp32s3::init_radio(
|
||||
spi_bus,
|
||||
p.GPIO10,
|
||||
p.GPIO21,
|
||||
p.GPIO3,
|
||||
p.GPIO14,
|
||||
lora_phy::sx126x::TcxoCtrlVoltage::Ctrl3V3,
|
||||
);
|
||||
|
||||
// UART0 via CP2102 bridge: TX=43, RX=44
|
||||
use esp_hal::uart::{Config as UartConfig, Uart};
|
||||
let host = UartParts {
|
||||
driver: Uart::new(p.UART0, UartConfig::default())
|
||||
.expect("UART init")
|
||||
.with_tx(p.GPIO43)
|
||||
.with_rx(p.GPIO44)
|
||||
.into_async(),
|
||||
};
|
||||
|
||||
// I2C0 for SH1106 OLED: SDA=16, SCL=15
|
||||
let i2c = mcu::init_i2c(p.I2C0, p.GPIO16, p.GPIO15);
|
||||
let display = Some(DisplayParts { i2c });
|
||||
|
||||
// LED on GPIO6 (active HIGH)
|
||||
let led_pin = Output::new(p.GPIO6, Level::Low, OutputConfig::default());
|
||||
let led = SimpleLed(led_pin);
|
||||
|
||||
BoardParts {
|
||||
radio,
|
||||
host,
|
||||
display,
|
||||
led,
|
||||
mac: Self::mac_address(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-6
@@ -7,7 +7,7 @@
|
||||
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, Level, Output, OutputConfig, Pull};
|
||||
use esp_hal::gpio::{Input, InputConfig, InputPin, Level, Output, OutputConfig, OutputPin, Pull};
|
||||
|
||||
use lora_phy::iv::GenericSx126xInterfaceVariant;
|
||||
use lora_phy::sx126x::{self, Sx1262, Sx126x};
|
||||
@@ -32,12 +32,17 @@ pub struct RadioParts {
|
||||
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<DisplayI2c>,
|
||||
ssd1306::size::DisplaySize128x64,
|
||||
@@ -45,6 +50,7 @@ pub type DisplayDriver = ssd1306::Ssd1306Async<
|
||||
>;
|
||||
|
||||
/// 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<DisplayDriver> {
|
||||
use ssd1306::mode::DisplayConfigAsync;
|
||||
use ssd1306::prelude::{Brightness, DisplayRotation};
|
||||
@@ -67,12 +73,17 @@ pub async fn create_display(i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
// ── 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<NoopRawMutex, SpiBus>,
|
||||
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>,
|
||||
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);
|
||||
@@ -86,7 +97,7 @@ pub fn init_radio(
|
||||
|
||||
let sx_config = sx126x::Config {
|
||||
chip: Sx1262,
|
||||
tcxo_ctrl: Some(sx126x::TcxoCtrlVoltage::Ctrl1V8),
|
||||
tcxo_ctrl: Some(tcxo_voltage),
|
||||
use_dcdc: true,
|
||||
rx_boost: false,
|
||||
};
|
||||
|
||||
+13
-6
@@ -53,12 +53,19 @@ impl LoRaBoard for Board {
|
||||
|
||||
mcu::start_timer(p.TIMG0, p.SW_INTERRUPT);
|
||||
|
||||
// Vext power: GPIO36, active LOW to enable peripherals
|
||||
let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
core::mem::forget(vext);
|
||||
// Vext power: GPIO36, active LOW to enable peripherals.
|
||||
// Output has no Drop impl, so the pin stays configured for program lifetime.
|
||||
let _vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
|
||||
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11);
|
||||
let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13);
|
||||
let radio = esp32s3::init_radio(
|
||||
spi_bus,
|
||||
p.GPIO8,
|
||||
p.GPIO12,
|
||||
p.GPIO14,
|
||||
p.GPIO13,
|
||||
lora_phy::sx126x::TcxoCtrlVoltage::Ctrl1V8,
|
||||
);
|
||||
|
||||
// Note: switches internal USB PHY from Serial-JTAG to OTG.
|
||||
let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19);
|
||||
@@ -72,12 +79,12 @@ impl LoRaBoard for Board {
|
||||
),
|
||||
};
|
||||
|
||||
// SSD1306 display reset: pulse GPIO21 low->high before I2C init
|
||||
// SSD1306 display reset: pulse GPIO21 low->high before I2C init.
|
||||
// Output has no Drop impl; pin stays HIGH after this block.
|
||||
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);
|
||||
|
||||
let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
|
||||
let display = Some(DisplayParts { i2c });
|
||||
|
||||
@@ -52,12 +52,19 @@ impl LoRaBoard for Board {
|
||||
|
||||
mcu::start_timer(p.TIMG0, p.SW_INTERRUPT);
|
||||
|
||||
// Vext power: GPIO36, active LOW to enable peripherals
|
||||
let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
core::mem::forget(vext);
|
||||
// Vext power: GPIO36, active LOW to enable peripherals.
|
||||
// Output has no Drop impl, so the pin stays configured for program lifetime.
|
||||
let _vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
|
||||
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11);
|
||||
let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13);
|
||||
let radio = esp32s3::init_radio(
|
||||
spi_bus,
|
||||
p.GPIO8,
|
||||
p.GPIO12,
|
||||
p.GPIO14,
|
||||
p.GPIO13,
|
||||
lora_phy::sx126x::TcxoCtrlVoltage::Ctrl1V8,
|
||||
);
|
||||
|
||||
use esp_hal::uart::{Config as UartConfig, Uart};
|
||||
let host = UartParts {
|
||||
@@ -68,12 +75,12 @@ impl LoRaBoard for Board {
|
||||
.into_async(),
|
||||
};
|
||||
|
||||
// SSD1306 display reset: pulse GPIO21 low->high before I2C init
|
||||
// SSD1306 display reset: pulse GPIO21 low->high before I2C init.
|
||||
// Output has no Drop impl; pin stays HIGH after this block.
|
||||
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);
|
||||
|
||||
let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
|
||||
let display = Some(DisplayParts { i2c });
|
||||
|
||||
+13
-6
@@ -46,12 +46,19 @@ impl LoRaBoard for Board {
|
||||
|
||||
mcu::start_timer(p.TIMG0, p.SW_INTERRUPT);
|
||||
|
||||
// Vext power: GPIO36, active LOW to enable peripherals
|
||||
let vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
core::mem::forget(vext);
|
||||
// Vext power: GPIO36, active LOW to enable peripherals.
|
||||
// Output has no Drop impl, so the pin stays configured for program lifetime.
|
||||
let _vext = Output::new(p.GPIO36, Level::Low, OutputConfig::default());
|
||||
|
||||
let spi_bus = mcu::init_spi(p.SPI2, p.DMA_CH0, p.GPIO9, p.GPIO10, p.GPIO11);
|
||||
let radio = esp32s3::init_radio(spi_bus, p.GPIO8, p.GPIO12, p.GPIO14, p.GPIO13);
|
||||
let radio = esp32s3::init_radio(
|
||||
spi_bus,
|
||||
p.GPIO8,
|
||||
p.GPIO12,
|
||||
p.GPIO14,
|
||||
p.GPIO13,
|
||||
lora_phy::sx126x::TcxoCtrlVoltage::Ctrl1V8,
|
||||
);
|
||||
|
||||
// Note: switches internal USB PHY from Serial-JTAG to OTG.
|
||||
let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19);
|
||||
@@ -65,12 +72,12 @@ impl LoRaBoard for Board {
|
||||
),
|
||||
};
|
||||
|
||||
// SSD1315 display reset: pulse GPIO21 low->high before I2C init
|
||||
// SSD1315 display reset: pulse GPIO21 low->high before I2C init.
|
||||
// Output has no Drop impl; pin stays HIGH after this block.
|
||||
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);
|
||||
|
||||
let i2c = mcu::init_i2c(p.I2C0, p.GPIO17, p.GPIO18);
|
||||
let display = Some(DisplayParts { i2c });
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
#[cfg(feature = "wio_tracker_l1")]
|
||||
#[cfg(any(feature = "wio_tracker_l1", feature = "elecrow_thinknode_m2"))]
|
||||
pub mod sh1106;
|
||||
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "wio_tracker_l1", feature = "waveshare_rp2040_lora"))]
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "elecrow_thinknode_m2", feature = "wio_tracker_l1", feature = "waveshare_rp2040_lora"))]
|
||||
pub mod simple_led;
|
||||
+13
-5
@@ -6,6 +6,7 @@
|
||||
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use esp_hal::dma::{DmaRxBuf, DmaTxBuf};
|
||||
use esp_hal::gpio::interconnect::{PeripheralInput, PeripheralOutput};
|
||||
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;
|
||||
@@ -33,12 +34,16 @@ pub fn start_timer(
|
||||
// ── SPI bus ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Initialize SPI2 with DMA and wrap in a shared bus (StaticCell + Mutex).
|
||||
///
|
||||
/// Generic over pin type so boards with different SPI pin assignments can
|
||||
/// share this helper. Any GPIO that implements the esp-hal interconnect
|
||||
/// traits (which is every GPIO on the ESP32-S3) can be passed directly.
|
||||
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>,
|
||||
sck: impl PeripheralOutput<'static>,
|
||||
mosi: impl PeripheralOutput<'static>,
|
||||
miso: impl PeripheralInput<'static>,
|
||||
) -> &'static embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus> {
|
||||
let spi = Spi::new(
|
||||
spi2,
|
||||
@@ -66,10 +71,13 @@ pub fn init_spi(
|
||||
// ── I2C bus ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Initialize I2C0 for peripherals (display, sensors, etc.).
|
||||
///
|
||||
/// Generic over pin type. I2C lines are bidirectional (open-drain), so both
|
||||
/// `PeripheralInput` and `PeripheralOutput` bounds are required.
|
||||
pub fn init_i2c(
|
||||
i2c0: esp_hal::peripherals::I2C0<'static>,
|
||||
sda: esp_hal::peripherals::GPIO17<'static>,
|
||||
scl: esp_hal::peripherals::GPIO18<'static>,
|
||||
sda: impl PeripheralInput<'static> + PeripheralOutput<'static>,
|
||||
scl: impl PeripheralInput<'static> + PeripheralOutput<'static>,
|
||||
) -> I2cBus {
|
||||
I2c::new(i2c0, I2cConfig::default())
|
||||
.expect("I2C init")
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//! 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_v3_uart", feature = "heltec_v4"))]
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "elecrow_thinknode_m2"))]
|
||||
pub mod esp32s3;
|
||||
|
||||
#[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))]
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
pub mod framing;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "heltec_v3_uart")] {
|
||||
if #[cfg(any(feature = "heltec_v3_uart", feature = "elecrow_thinknode_m2"))] {
|
||||
mod uart;
|
||||
pub use uart::host_task;
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1", feature = "waveshare_rp2040_lora"))] {
|
||||
use defmt_rtt as _;
|
||||
use panic_probe as _;
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] {
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "elecrow_thinknode_m2"))] {
|
||||
use esp_backtrace as _;
|
||||
use esp_println as _;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ cfg_if::cfg_if! {
|
||||
async fn main(spawner: Spawner) {
|
||||
run(spawner).await;
|
||||
}
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] {
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "elecrow_thinknode_m2"))] {
|
||||
#[esp_rtos::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
run(spawner).await;
|
||||
|
||||
Reference in new issue
Block a user