Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async: GPIO #333

Merged
merged 12 commits into from
Jan 27, 2023
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:
run: cd esp32-hal/ && cargo check --examples --features=eh1,smartled,ufmt
- name: check esp32-hal (async)
run: cd esp32-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0
- name: check esp32-hal (async, gpio)
run: cd esp32-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async

esp32c2-hal:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -75,6 +77,8 @@ jobs:
run: cd esp32c2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick
- name: check esp32c2-hal (async, timg0)
run: cd esp32c2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0
- name: check esp32c2-hal (async, gpio)
run: cd esp32c2-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-systick,async

esp32c3-hal:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -109,6 +113,8 @@ jobs:
run: cargo check --manifest-path=esp32c3-hal/Cargo.toml --target=riscv32imc-unknown-none-elf --example=embassy_hello_world --features=embassy,embassy-time-systick
- name: check esp32c3-hal (async, timg0)
run: cargo check --manifest-path=esp32c3-hal/Cargo.toml --target=riscv32imc-unknown-none-elf --example=embassy_hello_world --features=embassy,embassy-time-timg0
- name: check esp32c3-hal (async, gpio)
run: cd esp32c3-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-systick,async

esp32s2-hal:
runs-on: ubuntu-latest
Expand All @@ -135,6 +141,8 @@ jobs:
# run: cd esp32s2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick
- name: check esp32s2-hal (async, timg0)
run: cd esp32s2-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0
- name: check esp32s2-hal (async, gpio)
run: cd esp32s2-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async

esp32s3-hal:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -164,7 +172,8 @@ jobs:
run: cd esp32s3-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-systick
- name: check esp32s3-hal (async, timg0)
run: cd esp32s3-hal/ && cargo check --example=embassy_hello_world --features=embassy,embassy-time-timg0

- name: check esp32s3-hal (async, gpio)
run: cd esp32s3-hal/ && cargo check --example=embassy_wait --features=embassy,embassy-time-timg0,async
# --------------------------------------------------------------------------
# MSRV

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ The **M**inimum **S**upported **R**ust **V**ersions are:

- `1.65.0` for RISC-V devices (**ESP32-C2**, **ESP32-C3**)
- `1.65.0` for Xtensa devices (**ESP32**, **ESP32-S2**, **ESP32-S3**)
- `1.67.0` for all `async` examples (`embassy_hello_world`, `embassy_wait`, etc.)

Note that targeting the Xtensa ISA currently requires the use of the [esp-rs/rust] compiler fork. The [esp-rs/rust-build] repository has pre-compiled release artifacts for most common platforms, and provides installation scripts to aid you in the process.

Expand Down
2 changes: 1 addition & 1 deletion esp-hal-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void = { version = "1.0.2", default-features = false }
usb-device = { version = "0.2.9", optional = true }

# async
embedded-hal-async = { version = "0.1.0-alpha.3", optional = true }
embedded-hal-async = { version = "0.2.0-alpha.0", optional = true }
embassy-sync = { version = "0.1.0", optional = true }
embassy-time = { version = "0.1.0", features = ["nightly"], optional = true }

Expand Down
134 changes: 127 additions & 7 deletions esp-hal-common/src/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ pub trait Pin {
self.listen_with_options(event, true, false, false)
}

fn is_listening(&self) -> bool;

fn listen_with_options(
&mut self,
event: Event,
Expand Down Expand Up @@ -402,13 +404,7 @@ pub trait BankGpioRegisterAccess {
gpio.func_in_sel_cfg[signal as usize].modify(|_, w| w.sel().clear_bit());
}

fn set_int_enable(
&self,
gpio_num: u8,
int_ena: u32,
int_type: u8,
wake_up_from_light_sleep: bool,
) {
fn set_int_enable(gpio_num: u8, int_ena: u32, int_type: u8, wake_up_from_light_sleep: bool) {
let gpio = unsafe { &*crate::peripherals::GPIO::PTR };
gpio.pin[gpio_num as usize].modify(|_, w| unsafe {
w.int_ena()
Expand Down Expand Up @@ -837,6 +833,14 @@ where
}
}

fn is_listening(&self) -> bool {
let bits = unsafe { &*GPIO::PTR }.pin[GPIONUM as usize]
.read()
.int_ena()
.bits();
bits != 0
}

fn unlisten(&mut self) {
unsafe {
(&*GPIO::PTR).pin[GPIONUM as usize]
Expand Down Expand Up @@ -1642,3 +1646,119 @@ pub(crate) use gpio;

pub use self::types::{InputSignal, OutputSignal};
use self::types::{ONE_INPUT, ZERO_INPUT};

#[cfg(feature = "async")]
mod asynch {
use core::task::{Context, Poll};

use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_async::digital::Wait;

use super::*;
use crate::prelude::*;

#[allow(clippy::declare_interior_mutable_const)]
const NEW_AW: AtomicWaker = AtomicWaker::new();
static PIN_WAKERS: [AtomicWaker; NUM_PINS] = [NEW_AW; NUM_PINS];

impl<MODE, RA, IRA, PINTYPE, SIG, const GPIONUM: u8> Wait
for GpioPin<Input<MODE>, RA, IRA, PINTYPE, SIG, GPIONUM>
where
RA: BankGpioRegisterAccess,
PINTYPE: IsOutputPin,
IRA: InteruptStatusRegisterAccess,
SIG: GpioSignal,
{
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
PinFuture::new(self, Event::HighLevel).await
}

async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
PinFuture::new(self, Event::LowLevel).await
}

async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
PinFuture::new(self, Event::RisingEdge).await
}

async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
PinFuture::new(self, Event::FallingEdge).await
}

async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
PinFuture::new(self, Event::AnyEdge).await
}
}

pub struct PinFuture<'a, P> {
pin: &'a mut P,
}

impl<'a, P> PinFuture<'a, P>
where
P: crate::gpio::Pin + embedded_hal_1::digital::ErrorType,
{
pub fn new(pin: &'a mut P, event: Event) -> Self {
pin.listen(event);
Self { pin }
}
}

impl<'a, P> core::future::Future for PinFuture<'a, P>
where
P: crate::gpio::Pin + embedded_hal_1::digital::ErrorType,
{
type Output = Result<(), P::Error>;

fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
PIN_WAKERS[self.pin.number() as usize].register(cx.waker());

// if pin is no longer listening its been triggered
// therefore the future has resolved
if !self.pin.is_listening() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
}

#[interrupt]
unsafe fn GPIO() {
// TODO how to handle dual core reg access
// we need to check which core the interrupt is currently firing on
// and only fire interrupts registered for that core
type Bank0 = SingleCoreInteruptStatusRegisterAccessBank0;
#[cfg(any(esp32, esp32s2, esp32s3))]
type Bank1 = SingleCoreInteruptStatusRegisterAccessBank1;

let mut intrs = Bank0::pro_cpu_interrupt_status_read() as u64;

#[cfg(any(esp32, esp32s2, esp32s3))]
{
intrs |= (Bank1::pro_cpu_interrupt_status_read() as u64) << 32;
}

// clear interrupts
Bank0GpioRegisterAccess::write_interrupt_status_clear(!0);
Copy link
Contributor

@bugadani bugadani May 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MabezDev I'm not entirely sure but I think that set_int_enable does not implicitly clear interrupt status bits, it is possible for an edge-triggered listener to wake a task twice (in this call, and at a later GPIO interrupt request).

I think it is also possible that we lose GPIO interrupt requests here, because read and clear are not atomic. Probably only those bits should be cleared here that are set in intrs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand your first point, but I do agree that we should only clear the interrupts read from the status inside the GPIO interrupt handler.

Copy link
Contributor

@bugadani bugadani May 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in the process of opening an issue with more data that confirms my suspicions: level-triggered interrupts fire multiple times, it looks like. But I might just have incorrect assumptions here, we'll see :)

#[cfg(any(esp32, esp32s2, esp32s3))]
Bank1GpioRegisterAccess::write_interrupt_status_clear(!0);

while intrs != 0 {
let pin_nr = intrs.trailing_zeros();
cfg_if::cfg_if! {
if #[cfg(any(esp32, esp32s2, esp32s3))] {
if pin_nr < 32 {
Bank0GpioRegisterAccess::set_int_enable(pin_nr as u8, 0, 0, false);
} else {
Bank1GpioRegisterAccess::set_int_enable(pin_nr as u8, 0, 0, false);
}
} else {
Bank0GpioRegisterAccess::set_int_enable(pin_nr as u8, 0, 0, false);
}
}
PIN_WAKERS[pin_nr as usize].wake(); // wake task
intrs &= !(1 << pin_nr);
}
}
}
2 changes: 2 additions & 0 deletions esp-hal-common/src/gpio/esp32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::{
Unknown,
};

pub const NUM_PINS: usize = 39;

pub type OutputSignalType = u16;
pub const OUTPUT_SIGNAL_MAX: u16 = 548;
pub const INPUT_SIGNAL_MAX: u16 = 539;
Expand Down
2 changes: 2 additions & 0 deletions esp-hal-common/src/gpio/esp32c2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use crate::{
Unknown,
};

pub const NUM_PINS: usize = 20;

pub type OutputSignalType = u8;
pub const OUTPUT_SIGNAL_MAX: u8 = 128;
pub const INPUT_SIGNAL_MAX: u8 = 100;
Expand Down
2 changes: 2 additions & 0 deletions esp-hal-common/src/gpio/esp32c3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use crate::{
Unknown,
};

pub const NUM_PINS: usize = 21;

pub type OutputSignalType = u8;
pub const OUTPUT_SIGNAL_MAX: u8 = 128;
pub const INPUT_SIGNAL_MAX: u8 = 100;
Expand Down
2 changes: 2 additions & 0 deletions esp-hal-common/src/gpio/esp32s2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::{
Unknown,
};

pub const NUM_PINS: usize = 46;

pub type OutputSignalType = u16;
pub const OUTPUT_SIGNAL_MAX: u16 = 256;
pub const INPUT_SIGNAL_MAX: u16 = 204;
Expand Down
2 changes: 2 additions & 0 deletions esp-hal-common/src/gpio/esp32s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::{
Unknown,
};

pub const NUM_PINS: usize = 48;

pub type OutputSignalType = u16;
pub const OUTPUT_SIGNAL_MAX: u16 = 256;
pub const INPUT_SIGNAL_MAX: u16 = 189;
Expand Down
2 changes: 2 additions & 0 deletions esp-hal-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#![no_std]
#![cfg_attr(xtensa, feature(asm_experimental_arch))]
#![cfg_attr(feature = "async", allow(incomplete_features))]
#![cfg_attr(feature = "async", feature(async_fn_in_trait))]

#[cfg_attr(esp32, path = "peripherals/esp32.rs")]
#[cfg_attr(esp32c3, path = "peripherals/esp32c3.rs")]
Expand Down
6 changes: 5 additions & 1 deletion esp32-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ categories = [
embassy-time = { version = "0.1.0", features = ["nightly"], optional = true }
embedded-hal = { version = "0.2.7", features = ["unproven"] }
embedded-hal-1 = { version = "=1.0.0-alpha.9", optional = true, package = "embedded-hal" }
embedded-hal-async = { version = "0.1.0-alpha.3", optional = true }
embedded-hal-async = { version = "0.2.0-alpha.0", optional = true }
embedded-hal-nb = { version = "=1.0.0-alpha.1", optional = true }
esp-hal-common = { version = "0.4.0", features = ["esp32"], path = "../esp-hal-common" }
xtensa-lx = { version = "0.7.0", features = ["esp32"] }
Expand Down Expand Up @@ -73,3 +73,7 @@ required-features = ["eh1"]
[[example]]
name = "embassy_hello_world"
required-features = ["embassy"]

[[example]]
name = "embassy_wait"
required-features = ["embassy", "async"]
5 changes: 5 additions & 0 deletions esp32-hal/examples/embassy_hello_world.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! embassy hello world
//!
//! This is an example of running the embassy executor with multiple tasks
//! concurrently.

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
Expand Down
73 changes: 73 additions & 0 deletions esp32-hal/examples/embassy_wait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! embassy wait
//!
//! This is an example of asynchronously `Wait`ing for a pin state to change.

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use embassy_executor::Executor;
use embassy_time::{Duration, Timer};
use embedded_hal_async::digital::Wait;
use esp32_hal::{
clock::ClockControl,
embassy,
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Rtc,
IO,
};
use esp_backtrace as _;
use esp_hal_common::{Gpio0, Input, PullDown};
use static_cell::StaticCell;

#[embassy_executor::task]
async fn ping(mut pin: Gpio0<Input<PullDown>>) {
loop {
esp_println::println!("Waiting...");
pin.wait_for_rising_edge().await.unwrap();
esp_println::println!("Ping!");
Timer::after(Duration::from_millis(100)).await;
}
}

static EXECUTOR: StaticCell<Executor> = StaticCell::new();

#[xtensa_lx_rt::entry]
fn main() -> ! {
esp_println::println!("Init!");
let peripherals = Peripherals::take();
let system = peripherals.DPORT.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;

// Disable watchdog timers
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();

#[cfg(feature = "embassy-time-timg0")]
embassy::init(&clocks, timer_group0.timer0);

let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// GPIO 0 as input
let input = io.pins.gpio0.into_pull_down_input();

// Async requires the GPIO interrupt to wake futures
esp32_hal::interrupt::enable(
esp32_hal::peripherals::Interrupt::GPIO,
esp32_hal::interrupt::Priority::Priority1,
)
.unwrap();

let executor = EXECUTOR.init(Executor::new());
executor.run(|spawner| {
spawner.spawn(ping(input)).ok();
});
}
6 changes: 5 additions & 1 deletion esp32c2-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ categories = [
embassy-time = { version = "0.1.0", features = ["nightly"], optional = true }
embedded-hal = { version = "0.2.7", features = ["unproven"] }
embedded-hal-1 = { version = "=1.0.0-alpha.9", optional = true, package = "embedded-hal" }
embedded-hal-async = { version = "0.1.0-alpha.3", optional = true }
embedded-hal-async = { version = "0.2.0-alpha.0", optional = true }
embedded-hal-nb = { version = "=1.0.0-alpha.1", optional = true }
esp-hal-common = { version = "0.4.0", features = ["esp32c2"], path = "../esp-hal-common" }
r0 = "1.0.0"
Expand Down Expand Up @@ -70,3 +70,7 @@ required-features = ["eh1"]
[[example]]
name = "embassy_hello_world"
required-features = ["embassy"]

[[example]]
name = "embassy_wait"
required-features = ["embassy", "async"]
Loading