diff --git a/ci/expected/esp32c3/monotonic.run b/ci/expected/esp32c3/monotonic.run new file mode 100644 index 0000000000..9bf82bd6cc --- /dev/null +++ b/ci/expected/esp32c3/monotonic.run @@ -0,0 +1,34 @@ +QEMU 8.2.0 monitor - type 'help' for more information +(qemu) q +ESP-ROM:esp32c3-api1-20210207 +Build:Feb 7 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fcd5820,len:0x1714 +load:0x403cc710,len:0x968 +load:0x403ce710,len:0x2f9c +entry 0x403cc710 +I (0) boot: ESP-IDF v5.1.2-342-gbcf1645e44 2nd stage bootloader +I (0) boot: compile time Dec 12 2023 10:50:58 +I (0) boot: chip revision: v0.3 +I (0) boot.esp32c3: SPI Speed : 40MHz +I (0) boot.esp32c3: SPI Mode : SLOW READ +I (0) boot.esp32c3: SPI Flash Size : 4MB +I (0) boot: Enabling RNG early entropy source... +I (1) boot: Partition Table: +I (1) boot: ## Label Usage Type ST Offset Length +I (1) boot: 0 nvs WiFi data 01 02 00009000 00006000 +I (1) boot: 1 phy_init RF data 01 01 0000f000 00001000 +I (1) boot: 2 factory factory app 00 00 00010000 003f0000 +I (1) boot: End of partition table +I (1) esp_image: REDACTED +I (3) esp_image: REDACTED +I (3) esp_image: REDACTED +I (8) esp_image: REDACTED +I (11) boot: Loaded app from partition at offset 0x10000 +I (11) boot: Disabling RNG early entropy source... +init +hello from bar +hello from baz +hello from foo diff --git a/examples/esp32c3/.cargo/config.toml b/examples/esp32c3/.cargo/config.toml index 9ea4ecb95c..05f0a2921e 100644 --- a/examples/esp32c3/.cargo/config.toml +++ b/examples/esp32c3/.cargo/config.toml @@ -1,6 +1,6 @@ [target.riscv32imc-unknown-none-elf] # Real hardware -#runner = "espflash flash --monitor" +# runner = "espflash flash --monitor" # QEMU emulator runner = "./runner.sh" @@ -14,6 +14,3 @@ rustflags = [ ] target = "riscv32imc-unknown-none-elf" - -[unstable] -build-std = ["core"] diff --git a/examples/esp32c3/Cargo.lock b/examples/esp32c3/Cargo.lock index 35d467e457..5aaf0e1ba2 100644 --- a/examples/esp32c3/Cargo.lock +++ b/examples/esp32c3/Cargo.lock @@ -144,6 +144,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + [[package]] name = "enum-as-inner" version = "0.4.0" @@ -285,6 +294,7 @@ dependencies = [ "esp-println", "esp32c3 0.22.0", "rtic", + "rtic-monotonics", ] [[package]] @@ -384,6 +394,30 @@ dependencies = [ "gcd", ] +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "gcd" version = "2.3.0" @@ -479,6 +513,18 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.6.0" @@ -611,6 +657,14 @@ dependencies = [ "rtic-macros", ] +[[package]] +name = "rtic-common" +version = "1.0.1" +dependencies = [ + "critical-section", + "portable-atomic", +] + [[package]] name = "rtic-core" version = "1.0.0" @@ -628,6 +682,30 @@ dependencies = [ "syn 2.0.53", ] +[[package]] +name = "rtic-monotonics" +version = "2.0.2" +dependencies = [ + "cfg-if", + "esp32c3 0.22.0", + "fugit", + "portable-atomic", + "riscv", + "rtic-time", +] + +[[package]] +name = "rtic-time" +version = "2.0.0" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", + "embedded-hal-async", + "fugit", + "futures-util", + "rtic-common", +] + [[package]] name = "rustversion" version = "1.0.14" diff --git a/examples/esp32c3/Cargo.toml b/examples/esp32c3/Cargo.toml index fa2a4e0d68..adb0ece33b 100644 --- a/examples/esp32c3/Cargo.toml +++ b/examples/esp32c3/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" [dependencies] rtic = {path = "../../rtic/"} +rtic-monotonics = {path = "../../rtic-monotonics/"} esp-hal = { version = "0.16.1", features = ["esp32c3", "direct-vectoring", "interrupt-preemption"] } esp-backtrace = { version = "0.11.0", features = [ "esp32c3", @@ -21,4 +22,4 @@ esp-println = { version = "0.9.0", features = ["esp32c3", "uart"] } [features] test-critical-section = [] -riscv-esp32c3-backend = ["rtic/riscv-esp32c3-backend"] +riscv-esp32c3-backend = ["rtic/riscv-esp32c3-backend", "rtic-monotonics/esp32c3-systimer"] diff --git a/examples/esp32c3/README.md b/examples/esp32c3/README.md index 37d0167fb1..fa382c65d2 100644 --- a/examples/esp32c3/README.md +++ b/examples/esp32c3/README.md @@ -15,12 +15,24 @@ This crate uses the most convenient option in ``cargo-espflash`` and ``espflash` ## Running the crate +Uncomment the + +```runner = "espflash flash --monitor"``` + +line in ``.cargo/config.toml`` + +and comment out (or remove) + +```runner = "./runner.sh"``` + +Now, running + ```cargo run --example sw_and_hw --features=riscv-esp32c3-backend (--release)``` -should do the trick. +in the root of this crate should do the trick. # Expected behavior -The program +The example ``sw_and_hw`` - Prints ``init`` - Enters a high prio task - During the execution of the high prio task, the button should be non-functional @@ -31,3 +43,9 @@ The program - Exits the low prio task - Prints ``idle`` +The example ``monotonic`` +- Prints ``init`` +- Spawns the ``foo``, ``bar``, ``baz`` tasks (because of hardware interrupt latency dispatch, the order here may vary). +- Each task prints ``hello from $TASK`` on entry +- The tasks wait for 1, 2, 3 seconds respectively +- Once the wait period is over, each task exits printing ``bye from $TASK`` (now in the proper order). diff --git a/examples/esp32c3/examples/monotonic.rs b/examples/esp32c3/examples/monotonic.rs new file mode 100644 index 0000000000..1c0bd77854 --- /dev/null +++ b/examples/esp32c3/examples/monotonic.rs @@ -0,0 +1,51 @@ +#![no_main] +#![no_std] +use esp_backtrace as _; +#[rtic::app(device = esp32c3, dispatchers = [])] +mod app { + use rtic_monotonics::esp32c3::prelude::*; + esp32c3_systimer_monotonic!(Mono); + use esp_hal as _; + use esp_println::println; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + println!("init"); + let timer = cx.device.SYSTIMER; + + Mono::start(timer); + + foo::spawn().unwrap(); + bar::spawn().unwrap(); + baz::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_cx: foo::Context) { + println!("hello from foo"); + Mono::delay(2_u64.secs()).await; + println!("bye from foo"); + } + + #[task] + async fn bar(_cx: bar::Context) { + println!("hello from bar"); + Mono::delay(3_u64.secs()).await; + println!("bye from bar"); + } + + #[task] + async fn baz(_cx: baz::Context) { + println!("hello from baz"); + Mono::delay(4_u64.secs()).await; + println!("bye from baz"); + } +} diff --git a/examples/esp32c3/examples/sw_and_hw.rs b/examples/esp32c3/examples/sw_and_hw.rs index 413df92b18..42dd0ecd53 100644 --- a/examples/esp32c3/examples/sw_and_hw.rs +++ b/examples/esp32c3/examples/sw_and_hw.rs @@ -1,6 +1,5 @@ #![no_main] #![no_std] - #[rtic::app(device = esp32c3, dispatchers=[FROM_CPU_INTR0, FROM_CPU_INTR1])] mod app { use esp_backtrace as _; diff --git a/examples/esp32c3/rust-toolchain.toml b/examples/esp32c3/rust-toolchain.toml index 446d6fe524..e6c625538b 100644 --- a/examples/esp32c3/rust-toolchain.toml +++ b/examples/esp32c3/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2023-11-14" +channel = "stable" components = ["rust-src"] targets = ["riscv32imc-unknown-none-elf"] diff --git a/rtic-monotonics/CHANGELOG.md b/rtic-monotonics/CHANGELOG.md index b13a3a9a44..fa185d6fd7 100644 --- a/rtic-monotonics/CHANGELOG.md +++ b/rtic-monotonics/CHANGELOG.md @@ -13,6 +13,9 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top! ## v2.0.2 - 2024-07-05 +### Added +- `SYSTIMER` based monotonic for the ESP32-C3 + ### Fixed - Fix `stm32` monotonic for timer peripherals with only two clock compare modules diff --git a/rtic-monotonics/Cargo.toml b/rtic-monotonics/Cargo.toml index 8895fcb60d..953c658734 100644 --- a/rtic-monotonics/Cargo.toml +++ b/rtic-monotonics/Cargo.toml @@ -31,6 +31,7 @@ features = [ "stm32_tim4", "stm32_tim5", "stm32_tim15", + "esp32c3-systimer", ] rustdoc-flags = ["--cfg", "docsrs"] @@ -65,6 +66,11 @@ stm32-metapac = { version = "15.0.0", optional = true } # i.MX RT imxrt-ral = { version = "0.5.3", optional = true } + +esp32c3 = {version = "0.22.0", optional = true } +riscv = {version = "0.11.1", optional = true } + + [build-dependencies] proc-macro2 = { version = "1.0.36", optional = true } quote = { version = "1.0.15", optional = true } @@ -104,6 +110,9 @@ imxrt = ["dep:cortex-m", "dep:imxrt-ral"] imxrt_gpt1 = ["imxrt"] imxrt_gpt2 = ["imxrt"] +# ESP32-C3 Timer +esp32c3-systimer = ["dep:esp32c3", "dep:riscv"] + # STM32 timers # Use as `features = ["stm32g081kb", "stm32_tim15"]` stm32_tim2 = [] diff --git a/rtic-monotonics/src/esp32c3.rs b/rtic-monotonics/src/esp32c3.rs new file mode 100644 index 0000000000..61e044e2d6 --- /dev/null +++ b/rtic-monotonics/src/esp32c3.rs @@ -0,0 +1,193 @@ +//! [`Monotonic`](rtic_time::Monotonic) implementation for ESP32C3's SYSTIMER. +//! +//! Always runs at a fixed rate of 16 MHz. +//! +//! # Example +//! +//! ``` +//! use rtic_monotonics::esp32c3::prelude::*; +//! +//! esp32c3_systimer_monotonic!(Mono); +//! +//! fn init() { +//! # // This is normally provided by the selected PAC +//! # let timer = unsafe { esp32c3::Peripherals::steal() }.SYSTIMER; +//! # +//! // Start the monotonic +//! Mono::start(timer); +//! } +//! +//! async fn usage() { +//! loop { +//! // Use the monotonic +//! let timestamp = Mono::now(); +//! Mono::delay(100.millis()).await; +//! } +//! } +//! ``` + +/// Common definitions and traits for using the RP2040 timer monotonic +pub mod prelude { + pub use crate::esp32c3_systimer_monotonic; + + pub use crate::Monotonic; + + pub use fugit::{self, ExtU64, ExtU64Ceil}; +} +use crate::TimerQueueBackend; +use esp32c3::{INTERRUPT_CORE0, SYSTIMER}; +use rtic_time::timer_queue::TimerQueue; + +/// Timer implementing [`TimerQueueBackend`]. +pub struct TimerBackend; + +impl TimerBackend { + /// Starts the monotonic timer. + /// + /// **Do not use this function directly.** + /// + /// Use the prelude macros instead. + pub fn _start(timer: SYSTIMER) { + const INTERRUPT_MAP_BASE: u32 = 0x600c2000; + let interrupt_number = 37 as isize; + let cpu_interrupt_number = 31 as isize; + unsafe { + let intr_map_base = INTERRUPT_MAP_BASE as *mut u32; + intr_map_base + .offset(interrupt_number) + .write_volatile(cpu_interrupt_number as u32); + //map peripheral interrupt to CPU interrupt + (*INTERRUPT_CORE0::ptr()) + .cpu_int_enable() + .modify(|r, w| w.bits((1 << cpu_interrupt_number) | r.bits())); //enable the CPU interupt. + let intr = INTERRUPT_CORE0::ptr(); + let intr_prio_base = (*intr).cpu_int_pri_0().as_ptr(); + + intr_prio_base + .offset(cpu_interrupt_number) + .write_volatile(15 as u32); + } + timer.conf().write(|w| w.timer_unit0_work_en().set_bit()); + timer + .conf() + .write(|w| w.timer_unit1_core0_stall_en().clear_bit()); + TIMER_QUEUE.initialize(Self {}) + } +} + +static TIMER_QUEUE: TimerQueue = TimerQueue::new(); +use esp32c3; +impl TimerQueueBackend for TimerBackend { + type Ticks = u64; + fn now() -> Self::Ticks { + let peripherals = unsafe { esp32c3::Peripherals::steal() }; + peripherals + .SYSTIMER + .unit0_op() + .write(|w| w.timer_unit0_update().set_bit()); + // this must be polled until value is valid + while { + peripherals + .SYSTIMER + .unit0_op() + .read() + .timer_unit0_value_valid() + == false + } {} + let instant: u64 = (peripherals.SYSTIMER.unit0_value_lo().read().bits() as u64) + | ((peripherals.SYSTIMER.unit0_value_hi().read().bits() as u64) << 32); + instant + } + + fn set_compare(instant: Self::Ticks) { + let systimer = unsafe { esp32c3::Peripherals::steal() }.SYSTIMER; + systimer + .target0_conf() + .write(|w| w.target0_timer_unit_sel().set_bit()); + systimer + .target0_conf() + .write(|w| w.target0_period_mode().clear_bit()); + systimer + .target0_lo() + .write(|w| unsafe { w.bits((instant & 0xFFFFFFFF).try_into().unwrap()) }); + systimer + .target0_hi() + .write(|w| unsafe { w.bits((instant >> 32).try_into().unwrap()) }); + systimer + .comp0_load() + .write(|w| w.timer_comp0_load().set_bit()); //sync period to comp register + systimer.conf().write(|w| w.target0_work_en().set_bit()); + systimer.int_ena().write(|w| w.target0().set_bit()); + } + + fn clear_compare_flag() { + unsafe { esp32c3::Peripherals::steal() } + .SYSTIMER + .int_clr() + .write(|w| w.target0().bit(true)); + } + + fn pend_interrupt() { + extern "C" { + fn cpu_int_31_handler(); + } + //run the timer interrupt handler in a critical section to emulate a max priority + //interrupt. + //since there is no hardware support for pending a timer interrupt. + riscv::interrupt::disable(); + unsafe { cpu_int_31_handler() }; + unsafe { riscv::interrupt::enable() }; + } + + fn timer_queue() -> &'static TimerQueue { + &TIMER_QUEUE + } +} + +/// Create an ESP32-C3 SysTimer based monotonic and register the necessary interrupt for it. +/// +/// See [`crate::esp32c3`] for more details. +/// +/// # Arguments +/// +/// * `name` - The name that the monotonic type will have. +#[macro_export] +macro_rules! esp32c3_systimer_monotonic { + ($name:ident) => { + /// A `Monotonic` based on the ESP32-C3 SysTimer peripheral. + pub struct $name; + + impl $name { + /// Starts the `Monotonic`. + /// + /// This method must be called only once. + pub fn start(timer: esp32c3::SYSTIMER) { + #[export_name = "cpu_int_31_handler"] + #[allow(non_snake_case)] + unsafe extern "C" fn Systimer() { + use $crate::TimerQueueBackend; + $crate::esp32c3::TimerBackend::timer_queue().on_monotonic_interrupt(); + } + + $crate::esp32c3::TimerBackend::_start(timer); + } + } + + impl $crate::TimerQueueBasedMonotonic for $name { + type Backend = $crate::esp32c3::TimerBackend; + type Instant = $crate::fugit::Instant< + ::Ticks, + 1, + 16_000_000, + >; + type Duration = $crate::fugit::Duration< + ::Ticks, + 1, + 16_000_000, + >; + } + + $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); + $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); + }; +} diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs index 757e9018d3..9fe0bbf117 100644 --- a/rtic-monotonics/src/lib.rs +++ b/rtic-monotonics/src/lib.rs @@ -39,6 +39,9 @@ pub use rtic_time::{ TimeoutError, }; +#[cfg(feature = "esp32c3-systimer")] +pub mod esp32c3; + #[cfg(feature = "cortex-m-systick")] pub mod systick;