diff --git a/Cargo.toml b/Cargo.toml index 89a5a9b..b75ea89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,5 @@ Inflector = "0.11.4" clap = { version = "4.4.11", features = ["derive"] } indicatif = "0.17.7" open = "5.0.1" -probe-rs = "0.21.1" +probe-rs = "0.24.0" termimad = "0.29.1" diff --git a/README.md b/README.md index 4f3db28..94d30b1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Get up and running with Embassy in seconds. # Features -- Supports STM32* and NRF* +- Supports STM32*, NRF*, and ESP32(C3/S3) - Generates project structure - Toolchain - Probing @@ -57,3 +57,8 @@ cargo embassy init my_project --chip nrf52840 ```sh cargo embassy init my_project --chip nrf52832_xxAA --softdevice s132 ``` + +**Create a new Embassy project for the ESP32S3** +```sh +cargo embassy init my_project --chip esp32s3 +``` diff --git a/src/chip.rs b/src/chip.rs index a2fc418..99a8a24 100644 --- a/src/chip.rs +++ b/src/chip.rs @@ -1,6 +1,8 @@ pub mod family; pub mod target; +use family::esp::Variant; + use crate::error::{Error, InvalidChip}; use std::str::FromStr; @@ -29,25 +31,28 @@ impl FromStr for Chip { ("nrf52840", (NRF(MemRegion::NRF52840), Thumbv7f)), // TODO: nrf53x and nrf91x // STM - ("stm32c0", (STM32, Thumbv6)), - ("stm32f0", (STM32, Thumbv6)), - ("stm32f1", (STM32, Thumbv7)), - ("stm32f2", (STM32, Thumbv7)), - ("stm32f3", (STM32, Thumbv7e)), - ("stm32f4", (STM32, Thumbv7e)), - ("stm32f7", (STM32, Thumbv7e)), - ("stm32g0", (STM32, Thumbv6)), - ("stm32g4", (STM32, Thumbv7e)), - ("stm32h5", (STM32, Thumbv8)), - ("stm32h7", (STM32, Thumbv7e)), - ("stm32l0", (STM32, Thumbv6)), - ("stm32l1", (STM32, Thumbv7)), - ("stm32l4", (STM32, Thumbv7e)), - ("stm32l5", (STM32, Thumbv8)), - ("stm32u5", (STM32, Thumbv8)), - ("stm32wb", (STM32, Thumbv7e)), - ("stm32wba", (STM32, Thumbv8)), - ("stm32wl", (STM32, Thumbv7e)), + ("stm32c0", (STM, Thumbv6)), + ("stm32f0", (STM, Thumbv6)), + ("stm32f1", (STM, Thumbv7)), + ("stm32f2", (STM, Thumbv7)), + ("stm32f3", (STM, Thumbv7e)), + ("stm32f4", (STM, Thumbv7e)), + ("stm32f7", (STM, Thumbv7e)), + ("stm32g0", (STM, Thumbv6)), + ("stm32g4", (STM, Thumbv7e)), + ("stm32h5", (STM, Thumbv8)), + ("stm32h7", (STM, Thumbv7e)), + ("stm32l0", (STM, Thumbv6)), + ("stm32l1", (STM, Thumbv7)), + ("stm32l4", (STM, Thumbv7e)), + ("stm32l5", (STM, Thumbv8)), + ("stm32u5", (STM, Thumbv8)), + ("stm32wb", (STM, Thumbv7e)), + ("stm32wba", (STM, Thumbv8)), + ("stm32wl", (STM, Thumbv7e)), + // ESP32 + ("esp32c3", (ESP(Variant::C3), Risc32Imc)), + ("esp32s3", (ESP(Variant::S3), XTensaS3)), ]; let (family, target) = chips @@ -59,10 +64,11 @@ impl FromStr for Chip { })?; Ok(Self { - name: match family { - STM32 => chip.to_string(), + name: match &family { + STM => chip.to_string(), // FRAGILE: "_" is used to coerce probe-rs chip search NRF(_) => chip.split('_').next().unwrap().to_string(), + ESP(variant) => variant.to_string(), }, family, target, diff --git a/src/chip/family.rs b/src/chip/family.rs index 5f5d390..212521d 100644 --- a/src/chip/family.rs +++ b/src/chip/family.rs @@ -1,20 +1,24 @@ +pub mod esp; pub mod mem_region; +use esp::Variant; use mem_region::MemRegion; use std::fmt::Display; #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone)] pub enum Family { - STM32, + STM, NRF(MemRegion), + ESP(Variant), } impl Display for Family { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { - Self::STM32 => "stm32", + Self::STM => "stm32", Self::NRF(_) => "nrf", + Self::ESP(_) => "esp", }) } } diff --git a/src/chip/family/esp.rs b/src/chip/family/esp.rs new file mode 100644 index 0000000..3a3f996 --- /dev/null +++ b/src/chip/family/esp.rs @@ -0,0 +1,16 @@ +use std::fmt::Display; + +#[derive(Clone, Debug)] +pub enum Variant { + C3, + S3, +} + +impl Display for Variant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::C3 => "esp32c3", + Self::S3 => "esp32s3", + }) + } +} diff --git a/src/chip/target.rs b/src/chip/target.rs index b4ade8c..1943914 100644 --- a/src/chip/target.rs +++ b/src/chip/target.rs @@ -1,14 +1,14 @@ -use clap::ValueEnum; use std::fmt::Display; -#[derive(Debug, Clone, ValueEnum)] -#[value()] +#[derive(Debug, Clone)] pub enum Target { Thumbv6, Thumbv7, Thumbv7e, Thumbv7f, Thumbv8, + XTensaS3, + Risc32Imc, } impl Display for Target { @@ -19,6 +19,8 @@ impl Display for Target { Self::Thumbv7e => "thumbv7em-none-eabi", Self::Thumbv7f => "thumbv7em-none-eabihf", Self::Thumbv8 => "thumbv8m.main-none-eabihf", + Self::XTensaS3 => "xtensa-esp32s3-none-elf", + Self::Risc32Imc => "riscv32imc-unknown-none-elf", }) } } diff --git a/src/cli/init_args/panic_handler.rs b/src/cli/init_args/panic_handler.rs index 36e9451..45be452 100644 --- a/src/cli/init_args/panic_handler.rs +++ b/src/cli/init_args/panic_handler.rs @@ -1,6 +1,6 @@ use clap::ValueEnum; -#[derive(Debug, Clone, Default, ValueEnum)] +#[derive(Debug, Clone, Default, PartialEq, ValueEnum)] #[value()] pub enum PanicHandler { #[default] diff --git a/src/error.rs b/src/error.rs index 61eb427..4e76cfd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,7 @@ pub enum Error { CreateFile(String), CreateFolder(String), ErroneousSoftdevice, + ErroneousPanicHandler, InvalidChip(InvalidChip), } diff --git a/src/init.rs b/src/init.rs index 6567454..2dc463e 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,7 +1,6 @@ use crate::{ chip::{ - family::{mem_region::MemRegion, Family}, - target::Target, + family::{esp::Variant, mem_region::MemRegion, Family}, Chip, }, cli::init_args::{panic_handler::PanicHandler, soft_device::Softdevice, InitArgs}, @@ -53,9 +52,11 @@ impl Init { self.create_project(&args.name)?; - self.init_config(&chip.target, &probe_target_name)?; - self.init_toolchain(&chip.target)?; - self.init_embed(&probe_target_name)?; + self.init_config(&chip, &probe_target_name)?; + self.init_toolchain(&chip)?; + if !matches!(&chip.family, Family::ESP(_)) { + self.init_embed(&probe_target_name)?; + } self.init_build(&chip.family)?; self.init_manifest( &args.name, @@ -63,7 +64,9 @@ impl Init { &args.panic_handler, args.softdevice.as_ref(), )?; - self.init_fmt()?; + if !matches!(&chip.family, Family::ESP(_)) { + self.init_fmt()?; + } self.init_main(&chip.family, &args.panic_handler, args.softdevice.as_ref())?; if let Family::NRF(mem_reg) = chip.family { @@ -100,26 +103,39 @@ impl Init { } } - fn init_config(&self, target: &Target, chip: &str) -> Result<(), Error> { + fn init_config(&self, chip: &Chip, name: &str) -> Result<(), Error> { fs::create_dir_all(".cargo").map_err(|_| Error::CreateFolder(".cargo".into()))?; self.create_file( ".cargo/config.toml", - &format!( - include_str!("templates/config.toml.template"), - target = target, - chip = chip - ), + &match &chip.family { + Family::ESP(variant) => format!( + include_str!("templates/config.toml.esp.template"), + target = chip.target, + rustflags = match variant { + Variant::C3 => "rustflags = [\n\"-C\", \"force-frame-pointers\",\n]", + Variant::S3 => "rustflags = [\n\"-C\", \"link-arg=-nostartfiles\",\n]", + } + ), + _ => format!( + include_str!("templates/config.toml.template"), + target = chip.target, + chip = name + ), + }, ) } - fn init_toolchain(&self, target: &Target) -> Result<(), Error> { + fn init_toolchain(&self, chip: &Chip) -> Result<(), Error> { self.create_file( "rust-toolchain.toml", - &format!( - include_str!("templates/rust-toolchain.toml.template"), - target = target - ), + &match chip.family { + Family::ESP(_) => include_str!("templates/rust-toolchain.toml.esp.template").into(), + _ => format!( + include_str!("templates/rust-toolchain.toml.template"), + target = chip.target + ), + }, ) } @@ -132,8 +148,9 @@ impl Init { fn init_build(&self, family: &Family) -> Result<(), Error> { let template = match family { - Family::STM32 => include_str!("templates/build.rs.stm32.template"), + Family::STM => include_str!("templates/build.rs.stm.template"), Family::NRF(_) => include_str!("templates/build.rs.nrf.template"), + Family::ESP(_) => include_str!("templates/build.rs.esp.template"), }; self.create_file("build.rs", template) @@ -154,31 +171,64 @@ impl Init { // NOTE: should be threaded proably self.cargo_add( "embassy-executor", - Some(&["arch-cortex-m", "executor-thread", "integrated-timers"]), + match &chip.family { + Family::ESP(_) => Some(&["executor-thread"]), + _ => Some(&["arch-cortex-m", "executor-thread", "integrated-timers"]), + }, false, )?; self.cargo_add("embassy-sync", None, false)?; self.cargo_add("embassy-futures", None, false)?; - self.cargo_add("embassy-time", Some(&["tick-hz-32_768"]), false)?; + self.cargo_add( + "embassy-time", + match &chip.family { + Family::ESP(_) => None, + _ => Some(&["tick-hz-32_768"]), + }, + false, + )?; - match chip.family { - Family::STM32 => self.cargo_add( - "embassy-stm32", - Some(&[ - "memory-x", - chip.name.as_str(), - "time-driver-any", - "exti", - "unstable-pac", - ]), - false, - ), - Family::NRF(_) => self.cargo_add( - "embassy-nrf", - Some(&[chip.name.as_str(), "gpiote", "time-driver-rtc1"]), - false, - ), - }?; + match &chip.family { + Family::STM => { + self.cargo_add( + "embassy-stm32", + Some(&[ + "memory-x", + chip.name.as_str(), + "time-driver-any", + "exti", + "unstable-pac", + ]), + false, + )?; + } + Family::NRF(_) => { + self.cargo_add( + "embassy-nrf", + Some(&[chip.name.as_str(), "gpiote", "time-driver-rtc1"]), + false, + )?; + } + Family::ESP(variant) => { + let name = variant.to_string(); + + self.cargo_add("embassy-time-driver", None, false)?; + self.cargo_add( + "esp-backtrace", + Some(&[&name, "exception-handler", "panic-handler", "println"]), + false, + )?; + self.cargo_add("esp-hal", Some(&[&name]), false)?; + self.cargo_add( + "esp-hal-embassy", + Some(&[&name, "integrated-timers"]), + false, + )?; + self.cargo_add("esp-println", Some(&[&name, "log"]), false)?; + self.cargo_add("log", None, false)?; + self.cargo_add("static_cell", None, false)?; + } + }; if let Some(softdevice) = softdevice { self.cargo_add( @@ -195,48 +245,57 @@ impl Init { self.cargo_add(&format!("nrf-softdevice-{}", softdevice.str()), None, false)?; } - self.cargo_add( - "cortex-m", - Some(if softdevice.is_some() { - &["inline-asm"] - } else { - &["inline-asm", "critical-section-single-core"] - }), - false, - )?; - self.cargo_add("cortex-m-rt", None, false)?; - self.cargo_add("defmt", None, true)?; - self.cargo_add("defmt-rtt", None, true)?; - self.cargo_add("panic-probe", Some(&["print-defmt"]), true)?; - self.cargo_add(panic_handler.str(), None, false)?; - - let mut file = fs::OpenOptions::new() - .read(true) - .append(true) - .open("Cargo.toml") - .map_err(|_| Error::CreateFile("Cargo.toml".into()))?; - - // really gross patch for cargo version discontinuity - // somewhere between cargo 1.72 and 1.76 the behavior of "cargo add" changed - let mut buf = String::new(); - file.read_to_string(&mut buf).unwrap(); - if !buf.contains("[features]") { - file.write_all(include_str!("templates/Cargo.toml.feature-patch.template").as_bytes()) + if let Family::ESP(_) = &chip.family { + println!("[NOTICE] ESP32s have their own panic handler system."); + if panic_handler.ne(&PanicHandler::default()) { + Err(Error::ErroneousPanicHandler)? + } + } else { + self.cargo_add( + "cortex-m", + Some(if softdevice.is_some() { + &["inline-asm"] + } else { + &["inline-asm", "critical-section-single-core"] + }), + false, + )?; + self.cargo_add("cortex-m-rt", None, false)?; + self.cargo_add("defmt", None, true)?; + self.cargo_add("defmt-rtt", None, true)?; + self.cargo_add("panic-probe", Some(&["print-defmt"]), true)?; + self.cargo_add(panic_handler.str(), None, false)?; + + let mut file = fs::OpenOptions::new() + .read(true) + .append(true) + .open("Cargo.toml") .map_err(|_| Error::CreateFile("Cargo.toml".into()))?; - } - file.write_all( - if softdevice.is_some() { - include_str!("templates/Cargo.toml.sd.append").into() - } else { - format!( - include_str!("templates/Cargo.toml.append"), - family = chip.family + // really gross patch for cargo version discontinuity + // somewhere between cargo 1.72 and 1.76 the behavior of "cargo add" changed + let mut buf = String::new(); + file.read_to_string(&mut buf).unwrap(); + if !buf.contains("[features]") { + file.write_all( + include_str!("templates/Cargo.toml.feature-patch.template").as_bytes(), ) + .map_err(|_| Error::CreateFile("Cargo.toml".into()))?; } - .as_bytes(), - ) - .map_err(|_| Error::CreateFile("Cargo.toml".into()))?; + + file.write_all( + if softdevice.is_some() { + include_str!("templates/Cargo.toml.sd.append").into() + } else { + format!( + include_str!("templates/Cargo.toml.append"), + family = chip.family + ) + } + .as_bytes(), + ) + .map_err(|_| Error::CreateFile("Cargo.toml".into()))?; + } Ok(()) } @@ -256,8 +315,8 @@ impl Init { self.create_file( "src/main.rs", &match (family, softdevice) { - (Family::STM32, _) => format!( - include_str!("templates/main.rs.stm32.template"), + (Family::STM, _) => format!( + include_str!("templates/main.rs.stm.template"), panic_handler = panic_handler ), (Family::NRF(_), Some(_)) => { @@ -272,6 +331,7 @@ impl Init { panic_handler = panic_handler ) } + (Family::ESP(_), _) => include_str!("templates/main.rs.esp.template").into(), }, ) } diff --git a/src/templates/build.rs.esp.template b/src/templates/build.rs.esp.template new file mode 100644 index 0000000..5efe9c9 --- /dev/null +++ b/src/templates/build.rs.esp.template @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=-Tlinkall.x"); +} diff --git a/src/templates/build.rs.stm32.template b/src/templates/build.rs.stm.template similarity index 100% rename from src/templates/build.rs.stm32.template rename to src/templates/build.rs.stm.template diff --git a/src/templates/config.toml.esp.template b/src/templates/config.toml.esp.template new file mode 100644 index 0000000..f0fbd45 --- /dev/null +++ b/src/templates/config.toml.esp.template @@ -0,0 +1,16 @@ +# This file was automatically generated. + +[target.{target}] +runner = "espflash flash --monitor" + + +[env] +ESP_LOGLEVEL="INFO" + +[build] +{rustflags} + +target = "{target}" + +[unstable] +build-std = ["core"] diff --git a/src/templates/main.rs.esp.template b/src/templates/main.rs.esp.template new file mode 100644 index 0000000..4114f53 --- /dev/null +++ b/src/templates/main.rs.esp.template @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use esp_backtrace as _; +use esp_hal::{ + clock::ClockControl, + gpio::{Io, Level, Output}, + peripherals::Peripherals, + prelude::*, + system::SystemControl, + timer::{timg::TimerGroup, ErasedTimer, OneShotTimer}, +}; +use esp_println::println; +use static_cell::StaticCell; + +#[main] +async fn main(_spawner: Spawner) { + let peripherals = Peripherals::take(); + let system = SystemControl::new(peripherals.SYSTEM); + + let clocks = ClockControl::max(system.clock_control).freeze(); + + esp_println::logger::init_logger_from_env(); + + let timg0 = TimerGroup::new(peripherals.TIMG0, &clocks, None); + + let timer = { + static TIMER: StaticCell<[OneShotTimer; 1]> = StaticCell::new(); + + TIMER.init([OneShotTimer::new(timg0.timer0.into())]) + }; + + esp_hal_embassy::init(&clocks, timer); + let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); + + let mut led = Output::new(io.pins.gpio17, Level::Low); + + loop { + println!("Hello, World!"); + led.toggle(); + Timer::after_millis(1_000).await; + } +} diff --git a/src/templates/main.rs.stm32.template b/src/templates/main.rs.stm.template similarity index 100% rename from src/templates/main.rs.stm32.template rename to src/templates/main.rs.stm.template diff --git a/src/templates/rust-toolchain.toml.esp.template b/src/templates/rust-toolchain.toml.esp.template new file mode 100644 index 0000000..2158949 --- /dev/null +++ b/src/templates/rust-toolchain.toml.esp.template @@ -0,0 +1,4 @@ +# This file was automatically generated. + +[toolchain] +channel = "esp" diff --git a/src/templates/rust-toolchain.toml.template b/src/templates/rust-toolchain.toml.template index ae4ff90..c338292 100644 --- a/src/templates/rust-toolchain.toml.template +++ b/src/templates/rust-toolchain.toml.template @@ -2,5 +2,5 @@ [toolchain] channel = "1.80" -components = [ "rust-src", "rustfmt" ] +components = ["rust-src", "rustfmt"] targets = ["{target}"]