embassy-rs / embassy

Modern embedded framework, using Rust and async.
https://embassy.dev
Apache License 2.0
5.66k stars 790 forks source link

RP2040 timer interrupt for application use? #3156

Open grtwje opened 4 months ago

grtwje commented 4 months ago

I have a basic Raspberry Pi Pico project that uses embassy-time for most timing, but I would like to have access to a periodic timer interrupt for a non-embassy chunk of code. Does the embassy-rp HAL offer another timer that can be used when the TIMER peripheral is being used by embassy-time? I haven't found anything in the documentation along these lines, which may mean I'm misunderstanding something fundamental. Thanks.

Meigs2 commented 4 months ago

Can you post a sample of your code or what you're trying to do?

Meigs2 commented 4 months ago

Because it depends on what you mean by non-embassy, if this "non-embassy" needs to not take a dependency on embassy-rs's timer, you'll probably have to expose some sort of trait from that code to be implemented by your embassy code. Or expose a function or something that you can invoke from the non-embassy code.

grtwje commented 4 months ago

Thank you for responding.

It is not easy to post example code. But a little background may clarify things.

There is an existing embedded application running on custom hardware. It is C based and runs on a preemptive RTOS somewhat similar to Xinu. The ultimate goal would be to port it all to Rust/Embassy.

However, before committing to that effort, several proof of concepts need to be done. The first was rewriting the bare bones of the RTOS in Rust. This was done on QEMU x86-64. Here it uses the periodic interrupt timer to drive its preemptive scheduler.

The next step that I'm trying is to demo this running this on real hardware, a Raspberry Pi Pico. Using the Embassy-rp HAL could save a lot of driver work for me. I would also like to show Embassy async code running side by side with the legacy RTOS code. Maybe this can be done by running the Embassy async code in one of the RTOS managed threads. I have not got that far yet.

The RTOS needs some sort of periodic interrupt. The TIMER is an option, but I don't immediately see how to share it with the embassy-time option enabled. That is, somehow provide a callback from the RTOS that the timer driver can call in addition to what does for embassy-time.

Is there some other interrupt source the RTOS could use?

Another option is to not enable embassy-time, but I prefer not to do that if at all possible.

Meigs2 commented 4 months ago

Is the ultimate goal to rewrite the application or rewrite the RTOS and application?

If your goal is to rewrite the RTOS, it sounds like your RTOS needs to expose a Timer trait that you implement using using embassy, something like Timer::after_xxx(duration). Pass in a callback into this timer function to be invoked after the delay is over. Again, hard to give advice unless there's some sort of details regarding how you expect this callback to be invoke.

If your goal is to rewrite the application... do you even need this RTOS? If "The ultimate goal would be to port it all to Rust/Embassy" then why even write an RTOS, you don't need an RTOS with embassy, it replaces the need for one.. If you need to do some form of scheduling for the application, then you can do something like channels which lets you basically enqueue work to be consumed by listeners. I'm using this in my project to schedule periodic work to do things like take readouts from sensors and send messages back to the main program via usb without polling.

grtwje commented 4 months ago

Yes, ultimately the RTOS will hopefully go away. But there will be a period of time where both will need to coexist.

If I understand correctly, I should take the "time-driver" option off of embassy-rp. Then have the RTOS implement its own time-driver and expose that to embassy. Basically copy what embassy-rp does?

grtwje commented 4 months ago

Well, an unexpected hang on my first attempt. Learning is fun, often only after the fact. :)

For testing I simplified things, but maybe too simple? Here's main.rs (san 'use,' 'extern', etc.):

#![no_std]
#![no_main]

#[entry]
fn main() -> ! {
   let p = embassy_rp::init(Default::default());
   unsafe {
       mos_time_driver::init();
   }
   loop {}
}

It is spinning inside embassy_rp::init(), at the call to Peripherals::take(). It never makes it to my new mos_time_driver::init() call.

compiler_fence (\rustc\129f3b9964af4d4a709d1383930ade12dfe7c081\library\core\src\sync:3726)
__cpsie #[inline] (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\cortex-m-0.7.7\asm\inline.rs:52)
enable (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\cortex-m-0.7.7\src\call_asm.rs:11)
acquire (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\embassy-rp-0.1.0\src\critical_section_impl.rs:66)
acquire (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\embassy-rp-0.1.0\src\critical_section_impl.rs:29)
_critical_section_1_0_acquire (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\critical-section-1.1.2\src\lib.rs:283)
acquire #[inline] (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\critical-section-1.1.2\src\lib.rs:183)
with<embassy_rp::Peripherals, fn(critical_section::CriticalSection) -> embassy_rp::Peripherals> (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\critical-section-1.1.2\src\lib.rs:226)
take (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\embassy-hal-internal-0.1.0\src\macros.rs:54)
init (d:\packages\cargo\registry\src\index.crates.io-6f17d22bba15001f\embassy-rp-0.1.0\src\lib.rs:342)
__cortex_m_rt_main (d:\Projects\mos\src\main.rs:46)
__cortex_m_rt_main (d:\Projects\mos\src\main.rs:42)

mos_time_driver.rs is embassy-rp/src/time_driver.rs with TimerDriver changed to MosTimeDriver. No code added yet to drive the RTOS.

I had to add a few things to the toml file. And there's stuff I don't need yet for the simplified code.

[package]
name = "mos"
version = "0.1.0"
edition = "2021"

[dependencies]
embassy-rp = { version = "0.1.0", features = [
    "defmt",
    "unstable-pac",
    "critical-section-impl",
] }

embassy-time-driver = { version = "0.1", features = ["tick-hz-100"] }
embassy-sync = "0.6"
atomic-polyfill = "1.0.1"
critical-section = "1.1"

embassy-time = { version = "0.3.1", features = [
    "defmt",
    "defmt-timestamp-uptime",
] }

defmt = "0.3"
defmt-rtt = "0.4"
cortex-m = { version = "0.7.6", features = ["inline-asm"] }
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }

[profile.release]
debug = 2
grtwje commented 4 months ago

Looks like I was hitting #1736. I changed "embassy-rp::critical-section-impl" to "cortex-m::critical-section-single-core" and it no longer hangs.

grtwje commented 4 months ago

Looks like the sys tick may be better option to drive the RTOS timer. It does not look like it is being used by other embassy features.

    let core_periph = Peripherals::take().unwrap();
    let mut syst = core_periph.SYST;
    syst.set_reload(SYST::get_ticks_per_10ms());
    syst.clear_current();
    syst.enable_counter();
    syst.enable_interrupt();
...

#[exception]
fn SysTick() {
    println!("SysTick\r");
}