Skip to content

Instantly share code, notes, and snippets.

@timokroeger
Created January 22, 2021 21:01
Show Gist options
  • Save timokroeger/3b1483d7c9b93c3091163e7fe09ea837 to your computer and use it in GitHub Desktop.
Save timokroeger/3b1483d7c9b93c3091163e7fe09ea837 to your computer and use it in GitHub Desktop.
Experiments with async on embedded and critical sections
//! 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