diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index faa52d8f..3e500098 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -128,6 +128,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // PDM + PDM, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index bbdf1cbe..25c7c0d9 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -128,6 +128,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // PDM + PDM, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 18b8eda6..2c6276d4 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -138,6 +138,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // PDM + PDM, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 39a0f93f..3b33907d 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -158,6 +158,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + + // PDM + PDM, } #[cfg(feature = "nightly")] diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index e3d8f34a..ae59f8b2 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -161,6 +161,9 @@ embassy_hal_common::peripherals! { // TEMP TEMP, + + // PDM + PDM, } #[cfg(feature = "nightly")] diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index f3b3ca0c..20589195 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -103,6 +103,14 @@ pub mod uarte; pub mod usb; #[cfg(not(feature = "_nrf5340"))] pub mod wdt; +#[cfg(any( + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", +))] +pub mod pdm; // This mod MUST go last, so that it sees all the `impl_foo!` macros #[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs new file mode 100644 index 00000000..629eab99 --- /dev/null +++ b/embassy-nrf/src/pdm.rs @@ -0,0 +1,185 @@ +#![macro_use] + +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_util::waitqueue::AtomicWaker; +use futures::future::poll_fn; +use pac::{pdm, PDM}; +use pdm::mode::{EDGE_A, OPERATION_A}; +use fixed::types::I7F1; + +use crate::interrupt::InterruptExt; +use crate::gpio::Pin as GpioPin; +use crate::{interrupt, pac, peripherals, Peripheral}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error {} + +/// One-shot and continuous PDM. +pub struct Pdm<'d> { + _p: PeripheralRef<'d, peripherals::PDM>, +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// Used to configure the PDM peripheral. +/// +/// See the `Default` impl for suitable default values. +#[non_exhaustive] +pub struct Config { + /// Clock + /// Clock ratio + /// Channels + pub channels: Channels, + /// Edge to sample on + pub left_edge: Edge, + /// Gain left in dB + pub gain_left: I7F1, + /// Gain right in dB + pub gain_right: I7F1, +} + +impl Default for Config { + /// Default configuration for single channel sampling. + fn default() -> Self { + Self { + channels: Channels::Stereo, + left_edge: Edge::FallingEdge, + gain_left: I7F1::ZERO, + gain_right: I7F1::ZERO, + } + } +} + +/// The state of a continuously running sampler. While it reflects +/// the progress of a sampler, it also signals what should be done +/// next. For example, if the sampler has stopped then the Pdm implementation +/// can then tear down its infrastructure. +#[derive(PartialEq)] +pub enum SamplerState { + Sampled, + Stopped, +} + +impl<'d> Pdm<'d> { + pub fn new( + pdm: impl Peripheral
+ 'd, + irq: impl Peripheral
+ 'd, + data: impl Peripheral
+ 'd, + clock: impl Peripheral
+ 'd,
+ config: Config,
+ ) -> Self {
+ into_ref!(pdm, irq, data, clock);
+
+ let r = unsafe { &*PDM::ptr() };
+
+ let Config { channels, left_edge, gain_left, gain_right } = config;
+
+ // Configure channels
+ r.enable.write(|w| w.enable().enabled());
+ // TODO: Clock control
+ r.mode.write(|w| {
+ w.operation().variant(channels.into());
+ w.edge().variant(left_edge.into());
+ w
+ });
+
+ r.psel.din.write(|w| unsafe { w.bits(data.psel_bits()) });
+ r.psel.clk.write(|w| unsafe { w.bits(clock.psel_bits()) });
+
+ // Disable all events interrupts
+ r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) });
+
+ irq.set_handler(Self::on_interrupt);
+ irq.unpend();
+ irq.enable();
+
+ Self { _p: pdm }
+ }
+
+ fn on_interrupt(_ctx: *mut ()) {
+ let r = Self::regs();
+
+ if r.events_end.read().bits() != 0 {
+ r.intenclr.write(|w| w.end().clear());
+ WAKER.wake();
+ }
+
+ if r.events_started.read().bits() != 0 {
+ r.intenclr.write(|w| w.started().clear());
+ WAKER.wake();
+ }
+ }
+
+ fn regs() -> &'static pdm::RegisterBlock {
+ unsafe { &*PDM::ptr() }
+ }
+
+ /// One shot sampling. If the PDM is configured for multiple channels, the samples will be interleaved.
+ pub async fn sample