diff --git a/bins/esp/.cargo/config.toml b/bins/esp/.cargo/config.toml new file mode 100644 index 0000000..3fbec81 --- /dev/null +++ b/bins/esp/.cargo/config.toml @@ -0,0 +1,14 @@ +[target.xtensa-esp32s3-none-elf] +runner = "probe-rs run --chip=esp32s3 --preverify --always-print-stacktrace --no-location --catch-hardfault" + +[env] + +[build] +rustflags = [ + "-C", "link-arg=-nostartfiles", +] + +target = "xtensa-esp32s3-none-elf" + +[unstable] +build-std = ["alloc", "core"] diff --git a/bins/esp/.gitignore b/bins/esp/.gitignore new file mode 100644 index 0000000..c2c6725 --- /dev/null +++ b/bins/esp/.gitignore @@ -0,0 +1,22 @@ +# will have compiled files and executables +debug/ +target/ + +# Editor configuration +.vscode/ +.zed/ +.helix/ +.nvim.lua + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/bins/esp/Cargo.toml b/bins/esp/Cargo.toml new file mode 100644 index 0000000..fe974cf --- /dev/null +++ b/bins/esp/Cargo.toml @@ -0,0 +1,72 @@ +[package] +edition = "2024" +name = "picocraft-esp32s3" +rust-version = "1.88" +version = "0.1.0" + +[[bin]] +name = "esp32s3" +path = "./src/bin/esp32s3.rs" +test = false + +[[test]] +harness = false +name = "hello_test" + +[lib] +test = false + +[dependencies] +esp-hal = { version = "1.0.0", features = ["esp32s3", "unstable"] } + +esp-rtos = { version = "0.2.0", features = [ + "embassy", + "esp-alloc", + "esp-radio", + "esp32s3", +] } + +esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32s3"] } + +embassy-net = { version = "0.8.0", features = [ + "dhcpv4", + "medium-ethernet", + "tcp", + "udp", +] } +embedded-io = "0.7.1" +embedded-io-async = "0.7.0" +esp-alloc = "0.9.0" +rtt-target = "0.6.2" +# for more networking protocol support see https://crates.io/crates/edge-net +embassy-executor = { version = "0.9.1", features = [] } +embassy-time = "0.5.0" +esp-radio = { version = "0.17.0", features = [ + "esp-alloc", + "esp32s3", + "smoltcp", + "unstable", + "wifi", +] } +smoltcp = { version = "0.12.0", default-features = false, features = [ + "medium-ethernet", + "multicast", + "proto-dhcpv4", + "proto-dns", + "proto-ipv4", + "socket-dns", + "socket-icmp", + "socket-raw", + "socket-tcp", + "socket-udp", +] } + +critical-section = "1.2.0" +static_cell = "2.1.1" + +[dev-dependencies] +embedded-test = { version = "0.7.0", features = [ + "embassy", + "external-executor", + "xtensa-semihosting", +] } diff --git a/bins/esp/build.rs b/bins/esp/build.rs new file mode 100644 index 0000000..236d53d --- /dev/null +++ b/bins/esp/build.rs @@ -0,0 +1,61 @@ +fn main() { + linker_be_nice(); + println!("cargo:rustc-link-arg-tests=-Tembedded-test.x"); + // make sure linkall.x is the last linker script (otherwise might cause problems + // with flip-link) + println!("cargo:rustc-link-arg=-Tlinkall.x"); +} + +fn linker_be_nice() { + let args: Vec = std::env::args().collect(); + if args.len() > 1 { + let kind = &args[1]; + let what = &args[2]; + + match kind.as_str() { + "undefined-symbol" => match what.as_str() { + "_defmt_timestamp" => { + eprintln!(); + eprintln!( + "💡 `defmt` not found - make sure `defmt.x` is added as a linker script \ + and you have included `use defmt_rtt as _;`" + ); + eprintln!(); + } + "_stack_start" => { + eprintln!(); + eprintln!("💡 Is the linker script `linkall.x` missing?"); + eprintln!(); + } + "esp_rtos_initialized" | "esp_rtos_yield_task" | "esp_rtos_task_create" => { + eprintln!(); + eprintln!( + "💡 `esp-radio` has no scheduler enabled. Make sure you have initialized \ + `esp-rtos` or provided an external scheduler." + ); + eprintln!(); + } + "embedded_test_linker_file_not_added_to_rustflags" => { + eprintln!(); + eprintln!( + "💡 `embedded-test` not found - make sure `embedded-test.x` is added as a \ + linker script for tests" + ); + eprintln!(); + } + _ => (), + }, + // we don't have anything helpful for "missing-lib" yet + _ => { + std::process::exit(1); + } + } + + std::process::exit(0); + } + + println!( + "cargo:rustc-link-arg=-Wl,--error-handling-script={}", + std::env::current_exe().unwrap().display() + ); +} diff --git a/bins/esp/rust-toolchain.toml b/bins/esp/rust-toolchain.toml new file mode 100644 index 0000000..a2f5ab5 --- /dev/null +++ b/bins/esp/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "esp" diff --git a/bins/esp/src/bin/esp32s3.rs b/bins/esp/src/bin/esp32s3.rs new file mode 100644 index 0000000..0e2d573 --- /dev/null +++ b/bins/esp/src/bin/esp32s3.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] +#![deny( + clippy::mem_forget, + reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ + holding buffers for the duration of a data transfer." +)] + +use embassy_executor::Spawner; +use embassy_time::{Duration, Timer}; +use esp_hal::clock::CpuClock; +use esp_hal::timer::timg::TimerGroup; +use rtt_target::rprintln; + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +extern crate alloc; + +// This creates a default app-descriptor required by the esp-idf bootloader. +// For more information see: +esp_bootloader_esp_idf::esp_app_desc!(); + +#[esp_rtos::main] +async fn main(spawner: Spawner) -> ! { + // generator version: 1.0.1 + + rtt_target::rtt_init_print!(); + + let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); + let peripherals = esp_hal::init(config); + + esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 73744); + + let timg0 = TimerGroup::new(peripherals.TIMG0); + esp_rtos::start(timg0.timer0); + + rprintln!("Embassy initialized!"); + + let radio_init = esp_radio::init().expect("Failed to initialize Wi-Fi/BLE controller"); + let (mut _wifi_controller, _interfaces) = + esp_radio::wifi::new(&radio_init, peripherals.WIFI, Default::default()) + .expect("Failed to initialize Wi-Fi controller"); + + // TODO: Spawn some tasks + let _ = spawner; + + loop { + rprintln!("Hello world!"); + Timer::after(Duration::from_secs(1)).await; + } + + // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples/src/bin +} diff --git a/bins/esp/src/lib.rs b/bins/esp/src/lib.rs new file mode 100644 index 0000000..0c9ac1a --- /dev/null +++ b/bins/esp/src/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/bins/esp/tests/hello_test.rs b/bins/esp/tests/hello_test.rs new file mode 100644 index 0000000..b35c521 --- /dev/null +++ b/bins/esp/tests/hello_test.rs @@ -0,0 +1,27 @@ +//! Demo test suite using embedded-test +//! +//! You can run this using `cargo test` as usual. + +#![no_std] +#![no_main] + +esp_bootloader_esp_idf::esp_app_desc!(); + +#[cfg(test)] +#[embedded_test::tests(executor = esp_rtos::embassy::Executor::new())] +mod tests { + + #[init] + fn init() { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let timg1 = esp_hal::timer::timg::TimerGroup::new(peripherals.TIMG1); + esp_rtos::start(timg1.timer0); + } + + #[test] + async fn hello_test() { + embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await; + assert_eq!(1 + 1, 2); + } +}