fix: resolve ESP32-S3 runtime crash, match official esp-hal examples
Root causes found and fixed: - USB OTG init stole the PHY from USB-Serial-JTAG (the only USB on V4) - executor-thread feature conflicted with esp-hal-embassy's executor - Linker flags needed -Wl, prefix for GCC-to-LD passthrough - Missing exception-handler feature on esp-backtrace - Missing alloc in build-std - Missing LTO and codegen-units=1 in release profile - Vext on V4 is active LOW (datasheet correct, Arduino lib was wrong) Now matches the official esp-hal v0.22.0 examples exactly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
8 files changed
+58
-70
No files matched your search
+6
-3
@@ -1,6 +1,9 @@
|
|||||||
[target.xtensa-esp32s3-none-elf]
|
[target.'cfg(target_arch = "xtensa")']
|
||||||
rustflags = [
|
rustflags = [
|
||||||
|
"-C", "link-arg=-Wl,-Tlinkall.x",
|
||||||
|
"-C", "link-arg=-Wl,-Tdefmt.x",
|
||||||
"-C", "link-arg=-nostartfiles",
|
"-C", "link-arg=-nostartfiles",
|
||||||
"-C", "link-arg=-Tlinkall.x",
|
|
||||||
"-C", "link-arg=-Tdefmt.x",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
build-std = ["alloc", "core"]
|
||||||
Generated
-2
@@ -359,8 +359,6 @@ dependencies = [
|
|||||||
"critical-section",
|
"critical-section",
|
||||||
"document-features",
|
"document-features",
|
||||||
"embassy-executor-macros",
|
"embassy-executor-macros",
|
||||||
"embassy-time-driver",
|
|
||||||
"embassy-time-queue-driver",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+10
-4
@@ -9,7 +9,7 @@ cfg-if = "1.0"
|
|||||||
critical-section = "1.2"
|
critical-section = "1.2"
|
||||||
defmt = "0.3"
|
defmt = "0.3"
|
||||||
embassy-embedded-hal = "0.2"
|
embassy-embedded-hal = "0.2"
|
||||||
embassy-executor = { version = "0.6", features = ["task-arena-size-12288", "executor-thread"] }
|
embassy-executor = { version = "0.6", features = ["task-arena-size-32768"] }
|
||||||
embassy-sync = "0.6"
|
embassy-sync = "0.6"
|
||||||
embassy-time = "0.3"
|
embassy-time = "0.3"
|
||||||
embassy-usb = { version = "0.3", features = ["defmt"] }
|
embassy-usb = { version = "0.3", features = ["defmt"] }
|
||||||
@@ -27,7 +27,7 @@ ssd1306 = { version = "0.10", features = ["async"] }
|
|||||||
embassy-futures = "0.1"
|
embassy-futures = "0.1"
|
||||||
|
|
||||||
# ESP32-S3 (Heltec V3) — requires esp toolchain (espup)
|
# ESP32-S3 (Heltec V3) — requires esp toolchain (espup)
|
||||||
esp-backtrace = { version = "0.14", optional = true, features = ["esp32s3", "panic-handler", "defmt"] }
|
esp-backtrace = { version = "0.14", optional = true, features = ["esp32s3", "panic-handler", "exception-handler", "defmt"] }
|
||||||
esp-hal = { version = "0.22", optional = true, features = ["esp32s3"] }
|
esp-hal = { version = "0.22", optional = true, features = ["esp32s3"] }
|
||||||
esp-hal-embassy = { version = "0.5", optional = true, features = ["esp32s3"] }
|
esp-hal-embassy = { version = "0.5", optional = true, features = ["esp32s3"] }
|
||||||
esp-println = { version = "0.12", optional = true, features = ["esp32s3", "defmt-espflash"] }
|
esp-println = { version = "0.12", optional = true, features = ["esp32s3", "defmt-espflash"] }
|
||||||
@@ -47,18 +47,24 @@ heltec_v3 = [
|
|||||||
"dep:esp-hal",
|
"dep:esp-hal",
|
||||||
"dep:esp-hal-embassy",
|
"dep:esp-hal-embassy",
|
||||||
"dep:esp-println",
|
"dep:esp-println",
|
||||||
"embassy-executor/integrated-timers",
|
"embassy-time/generic-queue-8",
|
||||||
]
|
]
|
||||||
heltec_v4 = [
|
heltec_v4 = [
|
||||||
"dep:esp-backtrace",
|
"dep:esp-backtrace",
|
||||||
"dep:esp-hal",
|
"dep:esp-hal",
|
||||||
"dep:esp-hal-embassy",
|
"dep:esp-hal-embassy",
|
||||||
"dep:esp-println",
|
"dep:esp-println",
|
||||||
"embassy-executor/integrated-timers",
|
"embassy-time/generic-queue-8",
|
||||||
]
|
]
|
||||||
rak_wisblock_4631 = [
|
rak_wisblock_4631 = [
|
||||||
"dep:cortex-m-rt",
|
"dep:cortex-m-rt",
|
||||||
"dep:embassy-nrf",
|
"dep:embassy-nrf",
|
||||||
"dep:panic-probe",
|
"dep:panic-probe",
|
||||||
"embassy-executor/arch-cortex-m",
|
"embassy-executor/arch-cortex-m",
|
||||||
|
"embassy-executor/executor-thread",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
lto = "fat"
|
||||||
|
opt-level = 3
|
||||||
@@ -47,7 +47,7 @@ clippy board:
|
|||||||
case "$target" in xtensa-*) \
|
case "$target" in xtensa-*) \
|
||||||
just _require_esp_toolchain; \
|
just _require_esp_toolchain; \
|
||||||
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
|
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
|
||||||
env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly -Zbuild-std=core";; \
|
env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly";; \
|
||||||
esac; \
|
esac; \
|
||||||
eval $env cargo $extra clippy --target $target --features $feat -- -D warnings
|
eval $env cargo $extra clippy --target $target --features $feat -- -D warnings
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ _cargo board cmd:
|
|||||||
case "$target" in xtensa-*) \
|
case "$target" in xtensa-*) \
|
||||||
just _require_esp_toolchain; \
|
just _require_esp_toolchain; \
|
||||||
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
|
[ -f "$HOME/export-esp.sh" ] && . "$HOME/export-esp.sh"; \
|
||||||
env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly -Zbuild-std=core";; \
|
env="RUSTC=$(rustup which rustc --toolchain esp)"; extra="+nightly";; \
|
||||||
esac; \
|
esac; \
|
||||||
eval $env cargo $extra {{cmd}} --target $target --features $feat
|
eval $env cargo $extra {{cmd}} --target $target --features $feat
|
||||||
|
|
||||||
|
|||||||
+12
-23
@@ -21,7 +21,8 @@ type Iv = GenericSx126xInterfaceVariant<Output<'static>, Input<'static>>;
|
|||||||
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>;
|
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>;
|
||||||
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
||||||
|
|
||||||
pub type UsbDriver = esp_hal::otg_fs::asynch::Driver<'static>;
|
// TODO: V3 has CP2102 on UART0 — USB OTG on GPIO19/20 goes nowhere
|
||||||
|
pub type UsbDriver = ();
|
||||||
|
|
||||||
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
||||||
|
|
||||||
@@ -48,22 +49,20 @@ pub struct Board {
|
|||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub fn init() -> Self {
|
pub fn init() -> Self {
|
||||||
let mut p = esp_hal::init(esp_hal::Config::default());
|
let p = esp_hal::init(esp_hal::Config::default());
|
||||||
|
|
||||||
let timg0 = TimerGroup::new(core::mem::replace(
|
|
||||||
&mut p.TIMG0,
|
|
||||||
unsafe { esp_hal::peripherals::TIMG0::steal() },
|
|
||||||
));
|
|
||||||
esp_hal_embassy::init(timg0.timer0);
|
|
||||||
|
|
||||||
Self { p }
|
Self { p }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||||
let p = self.p;
|
let p = self.p;
|
||||||
|
|
||||||
|
// ── Embassy time driver ────────────────────────────────────
|
||||||
|
let timg0 = TimerGroup::new(p.TIMG0);
|
||||||
|
esp_hal_embassy::init(timg0.timer0);
|
||||||
|
|
||||||
// ── Vext power: GPIO21, active LOW to enable OLED ──────────
|
// ── Vext power: GPIO21, active LOW to enable OLED ──────────
|
||||||
let _vext = Output::new(p.GPIO21, Level::Low);
|
let vext = Output::new(p.GPIO21, Level::Low);
|
||||||
|
core::mem::forget(vext);
|
||||||
|
|
||||||
// ── DMA for SPI ────────────────────────────────────────────
|
// ── DMA for SPI ────────────────────────────────────────────
|
||||||
let dma = Dma::new(p.DMA);
|
let dma = Dma::new(p.DMA);
|
||||||
@@ -112,20 +111,10 @@ impl Board {
|
|||||||
delay: Delay,
|
delay: Delay,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── USB (native ESP32-S3 OTG) ─────────────────────────────
|
// ── USB (placeholder — V3 uses CP2102 on UART0) ───────────
|
||||||
let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19);
|
let usb = UsbParts { driver: () };
|
||||||
static EP_OUT_BUF: StaticCell<[u8; 1024]> = StaticCell::new();
|
|
||||||
let ep_out_buf = EP_OUT_BUF.init([0u8; 1024]);
|
|
||||||
let usb = UsbParts {
|
|
||||||
driver: esp_hal::otg_fs::asynch::Driver::new(
|
|
||||||
usb_inst,
|
|
||||||
ep_out_buf,
|
|
||||||
esp_hal::otg_fs::asynch::Config::default(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Display (optional SSD1306 OLED on I2C, 0x3C) ──────────
|
// ── Display (SSD1306 OLED on I2C, 0x3C) ───────────────────
|
||||||
// Always provide I2C; display_task detects presence via SSD1306 init.
|
|
||||||
let i2c = I2c::new(p.I2C0, I2cConfig::default())
|
let i2c = I2c::new(p.I2C0, I2cConfig::default())
|
||||||
.with_sda(p.GPIO17)
|
.with_sda(p.GPIO17)
|
||||||
.with_scl(p.GPIO18)
|
.with_scl(p.GPIO18)
|
||||||
|
|||||||
+18
-26
@@ -21,7 +21,8 @@ type Iv = GenericSx126xInterfaceVariant<Output<'static>, Input<'static>>;
|
|||||||
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>;
|
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>;
|
||||||
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
||||||
|
|
||||||
pub type UsbDriver = esp_hal::otg_fs::asynch::Driver<'static>;
|
// TODO: USB CDC-ACM (UsbSerialJtag for dev, or full OTG stack for production)
|
||||||
|
pub type UsbDriver = ();
|
||||||
|
|
||||||
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
pub type DisplayI2c = I2c<'static, esp_hal::Async>;
|
||||||
|
|
||||||
@@ -48,23 +49,21 @@ pub struct Board {
|
|||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub fn init() -> Self {
|
pub fn init() -> Self {
|
||||||
let mut p = esp_hal::init(esp_hal::Config::default());
|
let p = esp_hal::init(esp_hal::Config::default());
|
||||||
|
|
||||||
// Init embassy time driver before consuming peripherals
|
|
||||||
let timg0 = TimerGroup::new(core::mem::replace(
|
|
||||||
&mut p.TIMG0,
|
|
||||||
unsafe { esp_hal::peripherals::TIMG0::steal() },
|
|
||||||
));
|
|
||||||
esp_hal_embassy::init(timg0.timer0);
|
|
||||||
|
|
||||||
Self { p }
|
Self { p }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
pub fn into_parts(self) -> (RadioParts, UsbParts, Option<DisplayParts>) {
|
||||||
let p = self.p;
|
let p = self.p;
|
||||||
|
|
||||||
// ── Vext power: GPIO36, active HIGH to enable OLED ─────────
|
// ── Embassy time driver ────────────────────────────────────
|
||||||
let _vext = Output::new(p.GPIO36, Level::High);
|
let timg0 = TimerGroup::new(p.TIMG0);
|
||||||
|
esp_hal_embassy::init(timg0.timer0);
|
||||||
|
|
||||||
|
// ── Vext power: GPIO36 ─────────────────────────────────────
|
||||||
|
// V4 datasheet says "pull low to enable Vext"
|
||||||
|
let vext = Output::new(p.GPIO36, Level::Low);
|
||||||
|
core::mem::forget(vext);
|
||||||
|
|
||||||
// ── DMA for SPI ────────────────────────────────────────────
|
// ── DMA for SPI ────────────────────────────────────────────
|
||||||
let dma = Dma::new(p.DMA);
|
let dma = Dma::new(p.DMA);
|
||||||
@@ -113,24 +112,17 @@ impl Board {
|
|||||||
delay: Delay,
|
delay: Delay,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── USB (native ESP32-S3 OTG) ─────────────────────────────
|
// ── USB (placeholder) ──────────────────────────────────────
|
||||||
let usb_inst = esp_hal::otg_fs::Usb::new(p.USB0, p.GPIO20, p.GPIO19);
|
let usb = UsbParts { driver: () };
|
||||||
static EP_OUT_BUF: StaticCell<[u8; 1024]> = StaticCell::new();
|
|
||||||
let ep_out_buf = EP_OUT_BUF.init([0u8; 1024]);
|
|
||||||
let usb = UsbParts {
|
|
||||||
driver: esp_hal::otg_fs::asynch::Driver::new(
|
|
||||||
usb_inst,
|
|
||||||
ep_out_buf,
|
|
||||||
esp_hal::otg_fs::asynch::Config::default(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Display (optional SSD1315 OLED on I2C, 0x3C) ──────────
|
// ── Display (SSD1315 OLED on I2C, 0x3C) ───────────────────
|
||||||
// Reset display controller via GPIO21
|
// Reset display controller
|
||||||
let mut display_rst = Output::new(p.GPIO21, Level::Low);
|
let mut display_rst = Output::new(p.GPIO21, Level::Low);
|
||||||
|
esp_hal::delay::Delay::new().delay_millis(10);
|
||||||
display_rst.set_high();
|
display_rst.set_high();
|
||||||
|
esp_hal::delay::Delay::new().delay_millis(10);
|
||||||
|
core::mem::forget(display_rst);
|
||||||
|
|
||||||
// Always provide I2C; display_task detects presence via SSD1306 init.
|
|
||||||
let i2c = I2c::new(p.I2C0, I2cConfig::default())
|
let i2c = I2c::new(p.I2C0, I2cConfig::default())
|
||||||
.with_sda(p.GPIO17)
|
.with_sda(p.GPIO17)
|
||||||
.with_scl(p.GPIO18)
|
.with_scl(p.GPIO18)
|
||||||
|
|||||||
+9
-8
@@ -1,10 +1,10 @@
|
|||||||
use embassy_executor::task;
|
use embassy_executor::task;
|
||||||
use embassy_futures::select::{select, Either};
|
use embassy_futures::select::{select, Either};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use ssd1306::mode::DisplayConfig;
|
use ssd1306::mode::DisplayConfigAsync;
|
||||||
use ssd1306::prelude::DisplayRotation;
|
use ssd1306::prelude::DisplayRotation;
|
||||||
use ssd1306::size::DisplaySize128x64;
|
use ssd1306::size::DisplaySize128x64;
|
||||||
use ssd1306::{I2CDisplayInterface, Ssd1306};
|
use ssd1306::{I2CDisplayInterface, Ssd1306Async};
|
||||||
|
|
||||||
use crate::board::DisplayParts;
|
use crate::board::DisplayParts;
|
||||||
use crate::channel::{DisplayCommand, DisplayCommandChannel, StatusWatch};
|
use crate::channel::{DisplayCommand, DisplayCommandChannel, StatusWatch};
|
||||||
@@ -52,17 +52,18 @@ pub async fn display_task(
|
|||||||
display_commands: &'static DisplayCommandChannel,
|
display_commands: &'static DisplayCommandChannel,
|
||||||
) {
|
) {
|
||||||
let interface = I2CDisplayInterface::new(parts.i2c);
|
let interface = I2CDisplayInterface::new(parts.i2c);
|
||||||
let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
|
||||||
.into_buffered_graphics_mode();
|
.into_buffered_graphics_mode();
|
||||||
|
|
||||||
if display.init().is_err() {
|
if display.init().await.is_err() {
|
||||||
defmt::error!("SSD1306 init failed");
|
defmt::error!("SSD1306 init failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let _ = display.set_brightness(ssd1306::prelude::Brightness::BRIGHTEST).await;
|
||||||
|
|
||||||
// Splash screen
|
// Splash screen
|
||||||
render::splash(&mut display, BOARD_NAME, env!("CARGO_PKG_VERSION"));
|
render::splash(&mut display, BOARD_NAME, env!("CARGO_PKG_VERSION"));
|
||||||
let _ = display.flush();
|
let _ = display.flush().await;
|
||||||
Timer::after_millis(1500).await;
|
Timer::after_millis(1500).await;
|
||||||
|
|
||||||
// Dashboard
|
// Dashboard
|
||||||
@@ -76,7 +77,7 @@ pub async fn display_task(
|
|||||||
&state.rssi_history,
|
&state.rssi_history,
|
||||||
state.rssi_count,
|
state.rssi_count,
|
||||||
);
|
);
|
||||||
let _ = display.flush();
|
let _ = display.flush().await;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match select(receiver.changed(), display_commands.receive()).await {
|
match select(receiver.changed(), display_commands.receive()).await {
|
||||||
@@ -99,13 +100,13 @@ pub async fn display_task(
|
|||||||
&state.rssi_history,
|
&state.rssi_history,
|
||||||
state.rssi_count,
|
state.rssi_count,
|
||||||
);
|
);
|
||||||
let _ = display.flush();
|
let _ = display.flush().await;
|
||||||
}
|
}
|
||||||
Either::Second(cmd) => match cmd {
|
Either::Second(cmd) => match cmd {
|
||||||
DisplayCommand::Off => {
|
DisplayCommand::Off => {
|
||||||
state.display_on = false;
|
state.display_on = false;
|
||||||
render::blank(&mut display);
|
render::blank(&mut display);
|
||||||
let _ = display.flush();
|
let _ = display.flush().await;
|
||||||
}
|
}
|
||||||
DisplayCommand::On => {
|
DisplayCommand::On => {
|
||||||
state.display_on = true;
|
state.display_on = true;
|
||||||
|
|||||||
+1
-2
@@ -17,7 +17,7 @@ cfg_if::cfg_if! {
|
|||||||
use panic_probe as _;
|
use panic_probe as _;
|
||||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))] {
|
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v4"))] {
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
use esp_println as _; // provides defmt global_logger
|
use esp_println as _;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@ static RESPONSES: ResponseChannel = ResponseChannel::new();
|
|||||||
static STATUS: StatusWatch = StatusWatch::new();
|
static STATUS: StatusWatch = StatusWatch::new();
|
||||||
static DISPLAY_COMMANDS: DisplayCommandChannel = DisplayCommandChannel::new();
|
static DISPLAY_COMMANDS: DisplayCommandChannel = DisplayCommandChannel::new();
|
||||||
|
|
||||||
// Entry point: each platform needs its own executor macro.
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(feature = "rak_wisblock_4631")] {
|
if #[cfg(feature = "rak_wisblock_4631")] {
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
|
|||||||
Reference in new issue
Block a user