Merge #756
756: Add async quadrature decoder for embassy-nrf r=Dirbaio a=kalkyl Thsi PR adds an async interface to the QDEC peripheral to embassy-nrf, that can be used for rotary encoders for example. Co-authored-by: Henrik Alsér <henrik@mindbite.se> Co-authored-by: Henrik Alsér <henrik.alser@me.com> Co-authored-by: Henrik Alsér <henrik.alser@me.com>
This commit is contained in:
commit
45c0f1ab88
@ -122,6 +122,9 @@ embassy_hal_common::peripherals! {
|
||||
|
||||
// TEMP
|
||||
TEMP,
|
||||
|
||||
// QDEC
|
||||
QDEC,
|
||||
}
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
|
||||
|
@ -125,6 +125,9 @@ embassy_hal_common::peripherals! {
|
||||
|
||||
// TEMP
|
||||
TEMP,
|
||||
|
||||
// QDEC
|
||||
QDEC,
|
||||
}
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
|
||||
|
@ -125,6 +125,9 @@ embassy_hal_common::peripherals! {
|
||||
|
||||
// TEMP
|
||||
TEMP,
|
||||
|
||||
// QDEC
|
||||
QDEC,
|
||||
}
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
|
||||
|
@ -123,6 +123,9 @@ embassy_hal_common::peripherals! {
|
||||
|
||||
// TEMP
|
||||
TEMP,
|
||||
|
||||
// QDEC
|
||||
QDEC,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
|
@ -135,6 +135,9 @@ embassy_hal_common::peripherals! {
|
||||
|
||||
// TEMP
|
||||
TEMP,
|
||||
|
||||
// QDEC
|
||||
QDEC,
|
||||
}
|
||||
|
||||
impl_uarte!(UARTE0, UARTE0, UARTE0_UART0);
|
||||
|
@ -155,6 +155,9 @@ embassy_hal_common::peripherals! {
|
||||
|
||||
// TEMP
|
||||
TEMP,
|
||||
|
||||
// QDEC
|
||||
QDEC,
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
|
@ -27,6 +27,9 @@ embassy_hal_common::peripherals! {
|
||||
// QSPI
|
||||
QSPI,
|
||||
|
||||
// QDEC
|
||||
QDEC,
|
||||
|
||||
// UARTE
|
||||
UARTE0,
|
||||
UARTE1,
|
||||
|
@ -74,6 +74,8 @@ pub mod nvmc;
|
||||
pub mod ppi;
|
||||
#[cfg(not(any(feature = "nrf52805", feature = "nrf52820", feature = "_nrf5340-net")))]
|
||||
pub mod pwm;
|
||||
#[cfg(not(any(feature = "nrf51", feature = "_nrf9160", feature = "_nrf5340")))]
|
||||
pub mod qdec;
|
||||
#[cfg(feature = "nrf52840")]
|
||||
pub mod qspi;
|
||||
#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))]
|
||||
|
216
embassy-nrf/src/qdec.rs
Normal file
216
embassy-nrf/src/qdec.rs
Normal file
@ -0,0 +1,216 @@
|
||||
//! Quadrature decoder interface
|
||||
|
||||
use crate::gpio::sealed::Pin as _;
|
||||
use crate::gpio::{AnyPin, Pin as GpioPin};
|
||||
use crate::interrupt;
|
||||
use crate::pac;
|
||||
use crate::peripherals::QDEC;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::task::Poll;
|
||||
use embassy::interrupt::InterruptExt;
|
||||
use embassy::util::Unborrow;
|
||||
use embassy::waitqueue::AtomicWaker;
|
||||
use embassy_hal_common::unborrow;
|
||||
use futures::future::poll_fn;
|
||||
|
||||
/// Quadrature decoder
|
||||
pub struct Qdec<'d> {
|
||||
phantom: PhantomData<&'d QDEC>,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
pub struct Config {
|
||||
pub num_samples: NumSamples,
|
||||
pub period: SamplePeriod,
|
||||
pub led_polarity: LedPolarity,
|
||||
pub debounce: bool,
|
||||
pub led_pre_usecs: u16,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
num_samples: NumSamples::_1smpl,
|
||||
period: SamplePeriod::_256us,
|
||||
led_polarity: LedPolarity::ActiveHigh,
|
||||
debounce: true,
|
||||
led_pre_usecs: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||
|
||||
impl<'d> Qdec<'d> {
|
||||
pub fn new(
|
||||
qdec: impl Unborrow<Target = QDEC> + 'd,
|
||||
irq: impl Unborrow<Target = interrupt::QDEC> + 'd,
|
||||
a: impl Unborrow<Target = impl GpioPin> + 'd,
|
||||
b: impl Unborrow<Target = impl GpioPin> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
unborrow!(a, b);
|
||||
Self::new_inner(qdec, irq, a.degrade(), b.degrade(), None, config)
|
||||
}
|
||||
|
||||
pub fn new_with_led(
|
||||
qdec: impl Unborrow<Target = QDEC> + 'd,
|
||||
irq: impl Unborrow<Target = interrupt::QDEC> + 'd,
|
||||
a: impl Unborrow<Target = impl GpioPin> + 'd,
|
||||
b: impl Unborrow<Target = impl GpioPin> + 'd,
|
||||
led: impl Unborrow<Target = impl GpioPin> + 'd,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
unborrow!(a, b, led);
|
||||
Self::new_inner(
|
||||
qdec,
|
||||
irq,
|
||||
a.degrade(),
|
||||
b.degrade(),
|
||||
Some(led.degrade()),
|
||||
config,
|
||||
)
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
_t: impl Unborrow<Target = QDEC> + 'd,
|
||||
irq: impl Unborrow<Target = interrupt::QDEC> + 'd,
|
||||
a: AnyPin,
|
||||
b: AnyPin,
|
||||
led: Option<AnyPin>,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
unborrow!(irq);
|
||||
let r = Self::regs();
|
||||
|
||||
// Select pins.
|
||||
a.conf().write(|w| w.input().connect().pull().pullup());
|
||||
b.conf().write(|w| w.input().connect().pull().pullup());
|
||||
r.psel.a.write(|w| unsafe { w.bits(a.psel_bits()) });
|
||||
r.psel.b.write(|w| unsafe { w.bits(b.psel_bits()) });
|
||||
if let Some(led_pin) = &led {
|
||||
led_pin.conf().write(|w| w.dir().output());
|
||||
r.psel.led.write(|w| unsafe { w.bits(led_pin.psel_bits()) });
|
||||
}
|
||||
|
||||
// Enables/disable input debounce filters
|
||||
r.dbfen.write(|w| match config.debounce {
|
||||
true => w.dbfen().enabled(),
|
||||
false => w.dbfen().disabled(),
|
||||
});
|
||||
|
||||
// Set LED output pin polarity
|
||||
r.ledpol.write(|w| match config.led_polarity {
|
||||
LedPolarity::ActiveHigh => w.ledpol().active_high(),
|
||||
LedPolarity::ActiveLow => w.ledpol().active_low(),
|
||||
});
|
||||
|
||||
// Set time period the LED is switched ON prior to sampling (0..511 us).
|
||||
r.ledpre
|
||||
.write(|w| unsafe { w.ledpre().bits(config.led_pre_usecs.min(511)) });
|
||||
|
||||
// Set sample period
|
||||
r.sampleper.write(|w| match config.period {
|
||||
SamplePeriod::_128us => w.sampleper()._128us(),
|
||||
SamplePeriod::_256us => w.sampleper()._256us(),
|
||||
SamplePeriod::_512us => w.sampleper()._512us(),
|
||||
SamplePeriod::_1024us => w.sampleper()._1024us(),
|
||||
SamplePeriod::_2048us => w.sampleper()._2048us(),
|
||||
SamplePeriod::_4096us => w.sampleper()._4096us(),
|
||||
SamplePeriod::_8192us => w.sampleper()._8192us(),
|
||||
SamplePeriod::_16384us => w.sampleper()._16384us(),
|
||||
SamplePeriod::_32ms => w.sampleper()._32ms(),
|
||||
SamplePeriod::_65ms => w.sampleper()._65ms(),
|
||||
SamplePeriod::_131ms => w.sampleper()._131ms(),
|
||||
});
|
||||
|
||||
// Enable peripheral
|
||||
r.enable.write(|w| w.enable().set_bit());
|
||||
|
||||
// Start sampling
|
||||
unsafe { r.tasks_start.write(|w| w.bits(1)) };
|
||||
|
||||
irq.disable();
|
||||
irq.set_handler(|_| {
|
||||
let r = Self::regs();
|
||||
r.intenclr.write(|w| w.reportrdy().clear());
|
||||
WAKER.wake();
|
||||
});
|
||||
irq.enable();
|
||||
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform an asynchronous read of the decoder.
|
||||
/// The returned future can be awaited to obtain the number of steps.
|
||||
///
|
||||
/// If the future is dropped, the read is cancelled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// let irq = interrupt::take!(QDEC);
|
||||
/// let config = qdec::Config::default();
|
||||
/// let mut q = Qdec::new(p.QDEC, p.P0_31, p.P0_30, config);
|
||||
/// let delta = q.read().await;
|
||||
/// ```
|
||||
pub async fn read(&mut self) -> i16 {
|
||||
let t = Self::regs();
|
||||
t.intenset.write(|w| w.reportrdy().set());
|
||||
unsafe { t.tasks_readclracc.write(|w| w.bits(1)) };
|
||||
|
||||
let value = poll_fn(|cx| {
|
||||
WAKER.register(cx.waker());
|
||||
if t.events_reportrdy.read().bits() == 0 {
|
||||
return Poll::Pending;
|
||||
} else {
|
||||
t.events_reportrdy.reset();
|
||||
let acc = t.accread.read().bits();
|
||||
Poll::Ready(acc as i16)
|
||||
}
|
||||
})
|
||||
.await;
|
||||
value
|
||||
}
|
||||
|
||||
fn regs() -> &'static pac::qdec::RegisterBlock {
|
||||
unsafe { &*pac::QDEC::ptr() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum SamplePeriod {
|
||||
_128us,
|
||||
_256us,
|
||||
_512us,
|
||||
_1024us,
|
||||
_2048us,
|
||||
_4096us,
|
||||
_8192us,
|
||||
_16384us,
|
||||
_32ms,
|
||||
_65ms,
|
||||
_131ms,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum NumSamples {
|
||||
_10smpl,
|
||||
_40smpl,
|
||||
_80smpl,
|
||||
_120smpl,
|
||||
_160smpl,
|
||||
_200smpl,
|
||||
_240smpl,
|
||||
_280smpl,
|
||||
_1smpl,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum LedPolarity {
|
||||
ActiveHigh,
|
||||
ActiveLow,
|
||||
}
|
28
examples/nrf/src/bin/qdec.rs
Normal file
28
examples/nrf/src/bin/qdec.rs
Normal file
@ -0,0 +1,28 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
use defmt::info;
|
||||
use embassy::executor::Spawner;
|
||||
use embassy_nrf::{
|
||||
interrupt,
|
||||
qdec::{self, Qdec},
|
||||
Peripherals,
|
||||
};
|
||||
|
||||
use defmt_rtt as _; // global logger
|
||||
use panic_probe as _;
|
||||
|
||||
#[embassy::main]
|
||||
async fn main(_spawner: Spawner, p: Peripherals) {
|
||||
let irq = interrupt::take!(QDEC);
|
||||
let config = qdec::Config::default();
|
||||
let mut rotary_enc = Qdec::new(p.QDEC, irq, p.P0_31, p.P0_30, config);
|
||||
|
||||
info!("Turn rotary encoder!");
|
||||
let mut value = 0;
|
||||
loop {
|
||||
value += rotary_enc.read().await;
|
||||
info!("Value: {}", value);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user