Created
January 22, 2021 21:01
-
-
Save timokroeger/3b1483d7c9b93c3091163e7fe09ea837 to your computer and use it in GitHub Desktop.
Experiments with async on embedded and critical sections
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Experiments with async on embedded. This code runs on nRF52840-DK. | |
//! | |
//! On ARM Cortex-M cores interrupt requests can wake up the processor even when | |
//! interrupts are disabled by a critical section. The executor runs in a critical | |
//! section and puts the processor to sleep when no futures is ready. | |
//! A pending interrupt request wakes up the processor (without jumping to the ISR) | |
//! and the executor re-polls the futures for completion. | |
//! | |
//! Only supports a single task called `main_async` which can never be | |
//! interrupted (bus faults and other exceptions disregarded). | |
//! The sigle threaded nature reduces the complexity of async peripheral | |
//! abstractions: No need to get data into and out of the ISR context. | |
#![no_main] | |
#![no_std] | |
#![feature(future_poll_fn)] | |
use defmt_rtt as _; | |
use panic_probe as _; | |
use cortex_m::interrupt::CriticalSection; | |
#[cortex_m_rt::entry] | |
fn main() -> ! { | |
cortex_m::interrupt::free(|cs| { | |
// Simple executor that runs the `main_async` task in a loop. | |
// Insert a `WFI` instruction in the loop to enter low power mode. | |
direct_executor::run(main_async(cs), cortex_m::asm::wfi) | |
//direct_executor::run(main_async(cs), cortex_m::asm::nop) | |
// `WFE` with the `SEVONPEND` set can be used as an alternative. | |
// The processor then wakes up for any pending interrupt request, even if | |
// masked by the NVIC. But only when the pending flag changes from 0 to 1. | |
// The peripherals must be adapted to clear the NVIC pending flag after | |
// an interrupt request was triggered. | |
}) | |
} | |
async fn main_async(cs: &CriticalSection) -> ! { | |
let dp = nrf52840_pac::Peripherals::take().unwrap(); | |
let gpio0 = dp.P0; | |
let button1 = 11; // P0.11 | |
// Configure as input with pull-up | |
gpio0.pin_cnf[button1 as usize].write(|w| { | |
w.dir().input(); | |
w.pull().pullup(); | |
w.input().connect(); | |
w | |
}); | |
let mut gpiote = gpiote::Gpiote::new(dp.GPIOTE, &cs); | |
loop { | |
gpiote.falling_edge(button1).await; | |
defmt::info!("button pressed"); | |
gpiote.rising_edge(button1).await; | |
defmt::info!("button released"); | |
} | |
} | |
/// Async wrapper for the GPIO Tasks and Events (GPIOTE) peripheral. | |
/// Uses channel 0 for state change detection of a single pin. | |
/// Properly cleans up resources on drop. | |
mod gpiote { | |
use core::{future::Future, marker::PhantomData, task::Poll}; | |
use cortex_m::interrupt::CriticalSection; | |
use nrf52840_pac::{gpiote, Interrupt, NVIC}; | |
pub struct Gpiote<'a> { | |
inner: nrf52840_pac::GPIOTE, | |
cs: PhantomData<&'a CriticalSection>, | |
} | |
impl Gpiote<'_> { | |
pub fn new(gpiote: nrf52840_pac::GPIOTE, _cs: &CriticalSection) -> Gpiote<'_> { | |
// The critical section ensures that interrupts are disabled globally. | |
// Pending interrupts still wake up the processor from `WFI` sleep but | |
// no ISR will be executed. | |
// Enable our interrupt request line. | |
unsafe { NVIC::unmask(Interrupt::GPIOTE) }; | |
Gpiote { | |
inner: gpiote, | |
cs: PhantomData, | |
} | |
} | |
pub async fn falling_edge(&mut self, pin: u8) { | |
GpioteEventFuture::new(&self.inner, pin, gpiote::config::POLARITY_A::HITOLO).await | |
} | |
pub async fn rising_edge(&mut self, pin: u8) { | |
GpioteEventFuture::new(&self.inner, pin, gpiote::config::POLARITY_A::LOTOHI).await | |
} | |
pub async fn any_edge(&mut self, pin: u8) { | |
GpioteEventFuture::new(&self.inner, pin, gpiote::config::POLARITY_A::TOGGLE).await | |
} | |
} | |
impl Drop for Gpiote<'_> { | |
fn drop(&mut self) { | |
NVIC::mask(Interrupt::GPIOTE); | |
} | |
} | |
struct GpioteEventFuture<'a> { | |
gpiote: &'a nrf52840_pac::GPIOTE, | |
} | |
impl GpioteEventFuture<'_> { | |
fn new( | |
gpiote: &nrf52840_pac::GPIOTE, | |
pin: u8, | |
polarity: gpiote::config::POLARITY_A, | |
) -> GpioteEventFuture { | |
// Select pin and polarity but do not enable the detection yet. | |
gpiote.config[0].write(|w| { | |
unsafe { w.psel().bits(pin) }; | |
w.polarity().variant(polarity); | |
w | |
}); | |
// Enable the interrupt to wake up the processor from sleep when an | |
// edge was detected. | |
gpiote.intenset.write(|w| w.in0().set()); | |
GpioteEventFuture { gpiote } | |
} | |
fn reset(&self) { | |
self.gpiote.config[0].reset(); // Disable edge detection event. | |
self.gpiote.events_in[0].reset(); // Clear interrupt flag. | |
self.gpiote.intenclr.write(|w| w.in0().clear()); // Disable event interrupt. | |
} | |
} | |
impl Future for GpioteEventFuture<'_> { | |
type Output = (); | |
fn poll( | |
self: core::pin::Pin<&mut Self>, | |
_: &mut core::task::Context<'_>, | |
) -> Poll<Self::Output> { | |
// Start the event detection on first poll. | |
self.gpiote.config[0].modify(|_, w| w.mode().event()); | |
if self.gpiote.events_in[0].read().bits() != 0 { | |
self.reset(); | |
Poll::Ready(()) | |
} else { | |
Poll::Pending | |
} | |
} | |
} | |
impl Drop for GpioteEventFuture<'_> { | |
fn drop(&mut self) { | |
self.reset() | |
} | |
} | |
} | |
// defmt boilerplate | |
use core::sync::atomic::{AtomicUsize, Ordering}; | |
static COUNT: AtomicUsize = AtomicUsize::new(0); | |
defmt::timestamp!("{=usize}", COUNT.fetch_add(1, Ordering::Relaxed)); | |
#[defmt::panic_handler] | |
fn panic() -> ! { | |
cortex_m::asm::udf() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment