From a46f33b2144df0b913b50bb8c78256e20bce84c8 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sat, 20 Aug 2022 16:37:51 -0400 Subject: [PATCH] Initial PDM driver --- embassy-nrf/src/chips/nrf52810.rs | 3 + embassy-nrf/src/chips/nrf52811.rs | 3 + embassy-nrf/src/chips/nrf52832.rs | 3 + embassy-nrf/src/chips/nrf52833.rs | 3 + embassy-nrf/src/chips/nrf52840.rs | 3 + embassy-nrf/src/lib.rs | 8 ++ embassy-nrf/src/pdm.rs | 185 ++++++++++++++++++++++++++++++ examples/nrf/Cargo.toml | 1 + examples/nrf/src/bin/pdm.rs | 34 ++++++ 9 files changed, 243 insertions(+) create mode 100644 embassy-nrf/src/pdm.rs create mode 100644 examples/nrf/src/bin/pdm.rs 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(&mut self, buf: &mut [i16; N]) { + let r = Self::regs(); + + // Set up the DMA + r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(buf.as_mut_ptr() as u32) }); + r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); + + // Reset and enable the end event + r.events_end.reset(); + r.intenset.write(|w| w.end().set()); + + // Don't reorder the start event before the previous writes. Hopefully self + // wouldn't happen anyway. + compiler_fence(Ordering::SeqCst); + + r.tasks_start.write(|w| { w.tasks_start().set_bit() }); + + // Wait for 'end' event. + poll_fn(|cx| { + let r = Self::regs(); + + WAKER.register(cx.waker()); + + if r.events_end.read().bits() != 0 { + r.events_end.reset(); + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Edge { + FallingEdge, + RisingEdge, +} + +impl From for EDGE_A { + fn from(edge: Edge) -> Self { + match edge { + Edge::FallingEdge => EDGE_A::LEFTFALLING, + Edge::RisingEdge => EDGE_A::LEFTRISING, + } + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Channels { + Stereo, + Mono, +} + +impl From for OPERATION_A { + fn from(ch: Channels) -> Self { + match ch { + Channels::Stereo => OPERATION_A::STEREO, + Channels::Mono => OPERATION_A::MONO, + } + } +} \ No newline at end of file diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 2fcc3122..673bcfc6 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -27,6 +27,7 @@ cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rand = { version = "0.8.4", default-features = false } +fixed = "1.10.0" embedded-storage = "0.3.0" usbd-hid = "0.5.2" serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf/src/bin/pdm.rs b/examples/nrf/src/bin/pdm.rs new file mode 100644 index 00000000..d5e90e27 --- /dev/null +++ b/examples/nrf/src/bin/pdm.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::interrupt; +use embassy_nrf::pdm::{Config, Channels, Pdm}; +use embassy_time::{Duration, Timer}; +use fixed::types::I7F1; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = Config::default(); + // Pins are correct for the onboard microphone on the Feather nRF52840 Sense. + config.channels = Channels::Mono; + config.gain_left = I7F1::from_bits(5); // 2.5 dB + let mut pdm = Pdm::new(p.PDM, interrupt::take!(PDM), &mut p.P0_00, &mut p.P0_01, config); + + loop { + let mut buf = [0; 128]; + pdm.sample(&mut buf).await; + info!( + "{} samples, min {=i16}, max {=i16}, mean {=i16}", + buf.len(), + buf.iter().min().unwrap(), + buf.iter().max().unwrap(), + (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16, + ); + Timer::after(Duration::from_millis(100)).await; + } +}