From a46f33b2144df0b913b50bb8c78256e20bce84c8 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sat, 20 Aug 2022 16:37:51 -0400 Subject: [PATCH 01/13] 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; + } +} From 530f192acceb5a10c416e1823dc27a749e68b7dc Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sat, 20 Aug 2022 17:08:29 -0400 Subject: [PATCH 02/13] Set gain at runtime --- embassy-nrf/src/pdm.rs | 21 +++++++++++++++++++++ examples/nrf/Cargo.toml | 1 + examples/nrf/src/bin/pdm.rs | 29 +++++++++++++++++++---------- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index 629eab99..b3cc8760 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -88,6 +88,8 @@ impl<'d> Pdm<'d> { w }); + Self::_set_gain(r, gain_left, gain_right); + r.psel.din.write(|w| unsafe { w.bits(data.psel_bits()) }); r.psel.clk.write(|w| unsafe { w.bits(clock.psel_bits()) }); @@ -115,6 +117,18 @@ impl<'d> Pdm<'d> { } } + fn _set_gain(r: &pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) { + let gain_left = gain_left.saturating_add(I7F1::from_bits(40)).saturating_to_num::().clamp(0, 0x50); + let gain_right = gain_right.saturating_add(I7F1::from_bits(40)).saturating_to_num::().clamp(0, 0x50); + + r.gainl.write(|w| unsafe { w.gainl().bits(gain_left) }); + r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) }); + } + + pub fn set_gain(&mut self, gain_left: I7F1, gain_right: I7F1) { + Self::_set_gain(Self::regs(), gain_left, gain_right) + } + fn regs() -> &'static pdm::RegisterBlock { unsafe { &*PDM::ptr() } } @@ -154,6 +168,13 @@ impl<'d> Pdm<'d> { } } +impl<'d> Drop for Pdm<'d> { + fn drop(&mut self) { + let r = Self::regs(); + r.enable.write(|w| w.enable().disabled()); + } +} + #[derive(Clone, Copy, PartialEq)] pub enum Edge { FallingEdge, diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 673bcfc6..d0567ba8 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -31,3 +31,4 @@ fixed = "1.10.0" embedded-storage = "0.3.0" usbd-hid = "0.5.2" serde = { version = "1.0.136", default-features = false } +num-integer = { version = "0.1.45", default-features = false } diff --git a/examples/nrf/src/bin/pdm.rs b/examples/nrf/src/bin/pdm.rs index d5e90e27..a73d01fb 100644 --- a/examples/nrf/src/bin/pdm.rs +++ b/examples/nrf/src/bin/pdm.rs @@ -8,6 +8,7 @@ use embassy_nrf::interrupt; use embassy_nrf::pdm::{Config, Channels, Pdm}; use embassy_time::{Duration, Timer}; use fixed::types::I7F1; +use num_integer::Roots; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -20,15 +21,23 @@ async fn main(_p: Spawner) { 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; + for gain in [I7F1::from_num(-20), I7F1::from_num(0), I7F1::from_num(20)] { + pdm.set_gain(gain, gain); + info!("Gain = {} dB", defmt::Debug2Format(&gain)); + for _ in 0..10 { + let mut buf = [0; 128]; + pdm.sample(&mut buf).await; + info!( + "{} samples, min {=i16}, max {=i16}, RMS {=i16}", + buf.len(), + buf.iter().min().unwrap(), + buf.iter().max().unwrap(), + ( + buf.iter().map(|v| i32::from(*v).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) + / buf.len() as i32).sqrt() as i16, + ); + Timer::after(Duration::from_millis(100)).await; + } + } } } From 0963b5f92c9588ab00f556a6c521fad059eac72e Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sat, 20 Aug 2022 17:58:54 -0400 Subject: [PATCH 03/13] Add continuous PDM sampling with example --- embassy-nrf/src/pdm.rs | 118 ++++++++++++++++++++++++- examples/nrf/src/bin/pdm.rs | 3 +- examples/nrf/src/bin/pdm_continuous.rs | 50 +++++++++++ 3 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 examples/nrf/src/bin/pdm_continuous.rs diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index b3cc8760..db4c74af 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -115,6 +115,11 @@ impl<'d> Pdm<'d> { r.intenclr.write(|w| w.started().clear()); WAKER.wake(); } + + if r.events_stopped.read().bits() != 0 { + r.intenclr.write(|w| w.stopped().clear()); + WAKER.wake(); + } } fn _set_gain(r: &pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) { @@ -141,15 +146,20 @@ impl<'d> Pdm<'d> { 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 + // Reset and enable the events r.events_end.reset(); - r.intenset.write(|w| w.end().set()); + r.events_stopped.reset(); + r.intenset.write(|w| { + w.end().set(); + w.stopped().set(); + w + }); // 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() }); + r.tasks_start.write(|w| w.tasks_start().set_bit()); // Wait for 'end' event. poll_fn(|cx| { @@ -158,7 +168,109 @@ impl<'d> Pdm<'d> { WAKER.register(cx.waker()); if r.events_end.read().bits() != 0 { + // END means the whole buffer has been received. r.events_end.reset(); + // Note that the beginning of the buffer might be overwritten before the task fully stops :( + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + } + + if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// Continuous sampling with double buffers. + /// + /// A TIMER and two PPI peripherals are passed in so that precise sampling + /// can be attained. The sampling interval is expressed by selecting a + /// timer clock frequency to use along with a counter threshold to be reached. + /// For example, 1KHz can be achieved using a frequency of 1MHz and a counter + /// threshold of 1000. + /// + /// A sampler closure is provided that receives the buffer of samples, noting + /// that the size of this buffer can be less than the original buffer's size. + /// A command is return from the closure that indicates whether the sampling + /// should continue or stop. + /// + /// NOTE: The time spent within the callback supplied should not exceed the time + /// taken to acquire the samples into a single buffer. You should measure the + /// time taken by the callback and set the sample buffer size accordingly. + /// Exceeding this time can lead to samples becoming dropped. + pub async fn run_task_sampler( + &mut self, + bufs: &mut [[i16; N]; 2], + mut sampler: S, + ) where + S: FnMut(&[i16; N]) -> SamplerState, + { + let r = Self::regs(); + + r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) }); + r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); + + // Reset and enable the events + r.events_end.reset(); + r.events_started.reset(); + r.events_stopped.reset(); + r.intenset.write(|w| { + w.end().set(); + w.started().set(); + w.stopped().set(); + w + }); + + // 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() }); + + let mut current_buffer = 0; + + let mut done = false; + + // Wait for events and complete when the sampler indicates it has had enough. + poll_fn(|cx| { + let r = Self::regs(); + + WAKER.register(cx.waker()); + + if r.events_end.read().bits() != 0 { + compiler_fence(Ordering::SeqCst); + + r.events_end.reset(); + r.intenset.write(|w| w.end().set()); + + if !done { // Discard the last buffer after the user requested a stop. + if sampler(&bufs[current_buffer]) == SamplerState::Sampled { + let next_buffer = 1 - current_buffer; + current_buffer = next_buffer; + } else { + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + done = true; + }; + }; + } + + if r.events_started.read().bits() != 0 { + r.events_started.reset(); + r.intenset.write(|w| w.started().set()); + + let next_buffer = 1 - current_buffer; + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(bufs[next_buffer].as_mut_ptr() as u32) }); + } + + if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + r.intenset.write(|w| w.stopped().set()); + return Poll::Ready(()); } diff --git a/examples/nrf/src/bin/pdm.rs b/examples/nrf/src/bin/pdm.rs index a73d01fb..85a59a52 100644 --- a/examples/nrf/src/bin/pdm.rs +++ b/examples/nrf/src/bin/pdm.rs @@ -25,7 +25,7 @@ async fn main(_p: Spawner) { pdm.set_gain(gain, gain); info!("Gain = {} dB", defmt::Debug2Format(&gain)); for _ in 0..10 { - let mut buf = [0; 128]; + let mut buf = [0; 1500]; pdm.sample(&mut buf).await; info!( "{} samples, min {=i16}, max {=i16}, RMS {=i16}", @@ -36,6 +36,7 @@ async fn main(_p: Spawner) { buf.iter().map(|v| i32::from(*v).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) / buf.len() as i32).sqrt() as i16, ); + info!("samples = {}", &buf); Timer::after(Duration::from_millis(100)).await; } } diff --git a/examples/nrf/src/bin/pdm_continuous.rs b/examples/nrf/src/bin/pdm_continuous.rs new file mode 100644 index 00000000..e7d1806b --- /dev/null +++ b/examples/nrf/src/bin/pdm_continuous.rs @@ -0,0 +1,50 @@ +#![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, SamplerState}; +use embassy_nrf::timer::Frequency; +use fixed::types::I7F1; +use num_integer::Roots; +use {defmt_rtt as _, panic_probe as _}; + +// Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer + +#[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); + + let mut bufs = [[0; 500]; 2]; + + pdm + .run_task_sampler( + &mut bufs, + move |buf| { + // NOTE: It is important that the time spent within this callback + // does not exceed the time taken to acquire the 1500 samples we + // have in this example, which would be 10us + 2us per + // sample * 1500 = 18ms. You need to measure the time taken here + // and set the sample buffer size accordingly. Exceeding this + // time can lead to the peripheral re-writing the other buffer. + info!( + "{} samples, min {=i16}, max {=i16}, RMS {=i16}", + buf.len(), + buf.iter().min().unwrap(), + buf.iter().max().unwrap(), + ( + buf.iter().map(|v| i32::from(*v).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) + / buf.len() as i32).sqrt() as i16, + ); + SamplerState::Sampled + }, + ) + .await; +} From 3d26573c6b36c670a170279db49e7adc5e37466b Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sun, 21 Aug 2022 01:44:04 -0400 Subject: [PATCH 04/13] Discard the first N samples due to transients --- embassy-nrf/src/pdm.rs | 22 +++++++++++++++++++--- examples/nrf/src/bin/pdm.rs | 8 +++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index db4c74af..c8375b1e 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -139,7 +139,8 @@ impl<'d> Pdm<'d> { } /// 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]) { + /// The first samples from the PDM peripheral and microphone usually contain garbage data, so the discard parameter sets the number of complete buffers to discard before returning. + pub async fn sample(&mut self, mut discard: usize, buf: &mut [i16; N]) { let r = Self::regs(); // Set up the DMA @@ -148,9 +149,11 @@ impl<'d> Pdm<'d> { // Reset and enable the events r.events_end.reset(); + r.events_started.reset(); r.events_stopped.reset(); r.intenset.write(|w| { w.end().set(); + w.started().set(); w.stopped().set(); w }); @@ -168,13 +171,26 @@ impl<'d> Pdm<'d> { WAKER.register(cx.waker()); if r.events_end.read().bits() != 0 { + compiler_fence(Ordering::SeqCst); // END means the whole buffer has been received. r.events_end.reset(); - // Note that the beginning of the buffer might be overwritten before the task fully stops :( - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + r.intenset.write(|w| w.end().set()); + + if discard > 0 { + discard -= 1; + } else { + // Note that the beginning of the buffer might be overwritten before the task fully stops :( + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + } + } + + if r.events_started.read().bits() != 0 { + compiler_fence(Ordering::SeqCst); + r.events_started.reset(); } if r.events_stopped.read().bits() != 0 { + compiler_fence(Ordering::SeqCst); r.events_stopped.reset(); return Poll::Ready(()); } diff --git a/examples/nrf/src/bin/pdm.rs b/examples/nrf/src/bin/pdm.rs index 85a59a52..605eca59 100644 --- a/examples/nrf/src/bin/pdm.rs +++ b/examples/nrf/src/bin/pdm.rs @@ -26,14 +26,16 @@ async fn main(_p: Spawner) { info!("Gain = {} dB", defmt::Debug2Format(&gain)); for _ in 0..10 { let mut buf = [0; 1500]; - pdm.sample(&mut buf).await; + pdm.sample(5, &mut buf).await; + let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; info!( - "{} samples, min {=i16}, max {=i16}, RMS {=i16}", + "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}", buf.len(), buf.iter().min().unwrap(), buf.iter().max().unwrap(), + mean, ( - buf.iter().map(|v| i32::from(*v).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) + buf.iter().map(|v| i32::from(*v - mean).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) / buf.len() as i32).sqrt() as i16, ); info!("samples = {}", &buf); From 029713eca0e6c4a17283891de77c69f27bbf1a54 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sun, 21 Aug 2022 02:04:11 -0400 Subject: [PATCH 05/13] Stop PDM sampling when future is dropped --- embassy-nrf/src/pdm.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index c8375b1e..6a070e7b 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -4,6 +4,7 @@ use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_hal_common::drop::OnDrop; use embassy_util::waitqueue::AtomicWaker; use futures::future::poll_fn; use pac::{pdm, PDM}; @@ -149,11 +150,9 @@ impl<'d> Pdm<'d> { // Reset and enable the events r.events_end.reset(); - r.events_started.reset(); r.events_stopped.reset(); r.intenset.write(|w| { w.end().set(); - w.started().set(); w.stopped().set(); w }); @@ -164,6 +163,12 @@ impl<'d> Pdm<'d> { r.tasks_start.write(|w| w.tasks_start().set_bit()); + let ondrop = OnDrop::new(|| { + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + // N.B. It would be better if this were async, but Drop only support sync code. + while r.events_stopped.read().bits() != 0 {} + }); + // Wait for 'end' event. poll_fn(|cx| { let r = Self::regs(); @@ -183,21 +188,14 @@ impl<'d> Pdm<'d> { r.tasks_stop.write(|w| w.tasks_stop().set_bit()); } } - - if r.events_started.read().bits() != 0 { - compiler_fence(Ordering::SeqCst); - r.events_started.reset(); - } - if r.events_stopped.read().bits() != 0 { - compiler_fence(Ordering::SeqCst); - r.events_stopped.reset(); return Poll::Ready(()); } Poll::Pending }) .await; + ondrop.defuse(); } /// Continuous sampling with double buffers. @@ -250,6 +248,12 @@ impl<'d> Pdm<'d> { let mut done = false; + let ondrop = OnDrop::new(|| { + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + // N.B. It would be better if this were async, but Drop only support sync code. + while r.events_stopped.read().bits() != 0 {} + }); + // Wait for events and complete when the sampler indicates it has had enough. poll_fn(|cx| { let r = Self::regs(); @@ -284,15 +288,13 @@ impl<'d> Pdm<'d> { } if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); - r.intenset.write(|w| w.stopped().set()); - return Poll::Ready(()); } Poll::Pending }) .await; + ondrop.defuse(); } } From ed97e61dbecc636c3cc9f67778d4b7eb48cff893 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sun, 21 Aug 2022 02:16:26 -0400 Subject: [PATCH 06/13] PDM clock frequency control --- embassy-nrf/src/pdm.rs | 13 ++++++++++--- examples/nrf/src/bin/pdm_continuous.rs | 13 ++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index 6a070e7b..b58a0e97 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -9,6 +9,8 @@ use embassy_util::waitqueue::AtomicWaker; use futures::future::poll_fn; use pac::{pdm, PDM}; use pdm::mode::{EDGE_A, OPERATION_A}; +pub use pdm::pdmclkctrl::FREQ_A as Frequency; +pub use pdm::ratio::RATIO_A as Ratio; use fixed::types::I7F1; use crate::interrupt::InterruptExt; @@ -32,8 +34,10 @@ static WAKER: AtomicWaker = AtomicWaker::new(); /// See the `Default` impl for suitable default values. #[non_exhaustive] pub struct Config { - /// Clock + /// Clock frequency + pub frequency: Frequency, /// Clock ratio + pub ratio: Ratio, /// Channels pub channels: Channels, /// Edge to sample on @@ -48,6 +52,8 @@ impl Default for Config { /// Default configuration for single channel sampling. fn default() -> Self { Self { + frequency: Frequency::DEFAULT, + ratio: Ratio::RATIO80, channels: Channels::Stereo, left_edge: Edge::FallingEdge, gain_left: I7F1::ZERO, @@ -78,11 +84,12 @@ impl<'d> Pdm<'d> { let r = unsafe { &*PDM::ptr() }; - let Config { channels, left_edge, gain_left, gain_right } = config; + let Config { frequency, ratio, channels, left_edge, gain_left, gain_right } = config; // Configure channels r.enable.write(|w| w.enable().enabled()); - // TODO: Clock control + r.pdmclkctrl.write(|w| w.freq().variant(frequency)); + r.ratio.write(|w| w.ratio().variant(ratio)); r.mode.write(|w| { w.operation().variant(channels.into()); w.edge().variant(left_edge.into()); diff --git a/examples/nrf/src/bin/pdm_continuous.rs b/examples/nrf/src/bin/pdm_continuous.rs index e7d1806b..33ba1e27 100644 --- a/examples/nrf/src/bin/pdm_continuous.rs +++ b/examples/nrf/src/bin/pdm_continuous.rs @@ -5,8 +5,7 @@ use defmt::info; use embassy_executor::Spawner; use embassy_nrf::interrupt; -use embassy_nrf::pdm::{Config, Channels, Pdm, SamplerState}; -use embassy_nrf::timer::Frequency; +use embassy_nrf::pdm::{Config, Channels, Pdm, SamplerState, Frequency, Ratio}; use fixed::types::I7F1; use num_integer::Roots; use {defmt_rtt as _, panic_probe as _}; @@ -18,11 +17,13 @@ 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.frequency = Frequency::_1280K; // 16 kHz sample rate + config.ratio = Ratio::RATIO80; 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); - let mut bufs = [[0; 500]; 2]; + let mut bufs = [[0; 1024]; 2]; pdm .run_task_sampler( @@ -34,13 +35,15 @@ async fn main(_p: Spawner) { // sample * 1500 = 18ms. You need to measure the time taken here // and set the sample buffer size accordingly. Exceeding this // time can lead to the peripheral re-writing the other buffer. + let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; info!( - "{} samples, min {=i16}, max {=i16}, RMS {=i16}", + "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}", buf.len(), buf.iter().min().unwrap(), buf.iter().max().unwrap(), + mean, ( - buf.iter().map(|v| i32::from(*v).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) + buf.iter().map(|v| i32::from(*v - mean).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) / buf.len() as i32).sqrt() as i16, ); SamplerState::Sampled From 64154fec8cd7497992ef0d93f319b98215b8a84e Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sun, 21 Aug 2022 02:43:13 -0400 Subject: [PATCH 07/13] Demonstrate FFT in example --- examples/nrf/Cargo.toml | 1 + examples/nrf/src/bin/pdm_continuous.rs | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index d0567ba8..876dcf73 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -32,3 +32,4 @@ embedded-storage = "0.3.0" usbd-hid = "0.5.2" serde = { version = "1.0.136", default-features = false } num-integer = { version = "0.1.45", default-features = false } +microfft = "0.5.0" diff --git a/examples/nrf/src/bin/pdm_continuous.rs b/examples/nrf/src/bin/pdm_continuous.rs index 33ba1e27..e78bc40d 100644 --- a/examples/nrf/src/bin/pdm_continuous.rs +++ b/examples/nrf/src/bin/pdm_continuous.rs @@ -3,11 +3,13 @@ #![feature(type_alias_impl_trait)] use defmt::info; +use core::cmp::Ordering; use embassy_executor::Spawner; use embassy_nrf::interrupt; use embassy_nrf::pdm::{Config, Channels, Pdm, SamplerState, Frequency, Ratio}; use fixed::types::I7F1; use num_integer::Roots; +use microfft::real::rfft_1024; use {defmt_rtt as _, panic_probe as _}; // Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer @@ -36,8 +38,10 @@ async fn main(_p: Spawner) { // and set the sample buffer size accordingly. Exceeding this // time can lead to the peripheral re-writing the other buffer. let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; + let (peak_freq_index, peak_mag) = fft_peak_freq(&buf); + let peak_freq = peak_freq_index * 16000 / buf.len(); info!( - "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}", + "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}, peak {} @ {} Hz", buf.len(), buf.iter().min().unwrap(), buf.iter().max().unwrap(), @@ -45,9 +49,27 @@ async fn main(_p: Spawner) { ( buf.iter().map(|v| i32::from(*v - mean).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) / buf.len() as i32).sqrt() as i16, + peak_mag, peak_freq, ); SamplerState::Sampled }, ) .await; } + +fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) { + let mut f = [0f32; 1024]; + for i in 0..input.len() { + f[i] = (input[i] as f32) / 32768.0; + } + // N.B. rfft_1024 does the FFT in-place so result is actually also a reference to f. + let result = rfft_1024(&mut f); + result[0].im = 0.0; + + result + .iter() + .map(|c| ((c.norm_sqr()*32768.0) as u32).sqrt()) + .enumerate() + .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal)) + .unwrap() +} \ No newline at end of file From 14eae9ca06f63a69ccc29d5fd9e1dec3848a3e98 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Sun, 21 Aug 2022 12:40:51 -0400 Subject: [PATCH 08/13] Optimize pdm_continuous example --- examples/nrf/src/bin/pdm_continuous.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/nrf/src/bin/pdm_continuous.rs b/examples/nrf/src/bin/pdm_continuous.rs index e78bc40d..284a68af 100644 --- a/examples/nrf/src/bin/pdm_continuous.rs +++ b/examples/nrf/src/bin/pdm_continuous.rs @@ -68,8 +68,9 @@ fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) { result .iter() - .map(|c| ((c.norm_sqr()*32768.0) as u32).sqrt()) + .map(|c| c.norm_sqr()) .enumerate() .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal)) + .map(|(i, v)| (i, ((v*32768.0) as u32).sqrt())) .unwrap() -} \ No newline at end of file +} From d896f80405aa8963877049ed999e4aba25d6e2bb Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Tue, 23 Aug 2022 23:02:48 -0400 Subject: [PATCH 09/13] util -> sync rename --- embassy-nrf/src/pdm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index b58a0e97..c2c54fba 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -5,7 +5,7 @@ use core::task::Poll; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_hal_common::drop::OnDrop; -use embassy_util::waitqueue::AtomicWaker; +use embassy_sync::waitqueue::AtomicWaker; use futures::future::poll_fn; use pac::{pdm, PDM}; use pdm::mode::{EDGE_A, OPERATION_A}; From c333d855fca0743de1d58ef14e3c2f6389f73145 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Tue, 18 Jul 2023 17:13:43 -0400 Subject: [PATCH 10/13] Remove merge error --- embassy-nrf/src/pdm.rs | 2 +- examples/nrf/Cargo.toml | 37 ------------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 examples/nrf/Cargo.toml diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index 1fc717fd..f2675bb7 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -463,4 +463,4 @@ macro_rules! impl_pdm { type Interrupt = crate::interrupt::typelevel::$irq; } }; -} \ No newline at end of file +} diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml deleted file mode 100644 index 8309dda0..00000000 --- a/examples/nrf/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -edition = "2021" -name = "embassy-nrf-examples" -version = "0.1.0" - -[features] -default = ["nightly"] -nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid", "embassy-usb-ncm", "embedded-io/async", "embassy-net"] - -[dependencies] -embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } -embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true } -embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true } -embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"], optional = true } -embedded-io = "0.3.0" - -defmt = "0.3" -defmt-rtt = "0.3" - -static_cell = "1.0" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } -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 } -num-integer = { version = "0.1.45", default-features = false } -microfft = "0.5.0" From 2c01f277c27bc6ca4f3d41ac57aa1ea24868cfc1 Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Tue, 18 Jul 2023 17:17:04 -0400 Subject: [PATCH 11/13] cargo fmt --- embassy-nrf/src/pdm.rs | 26 ++++++--- examples/nrf52840/src/bin/pdm.rs | 8 ++- examples/nrf52840/src/bin/pdm_continuous.rs | 65 +++++++++++---------- 3 files changed, 56 insertions(+), 43 deletions(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index f2675bb7..48668c7f 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -8,17 +8,17 @@ use core::task::Poll; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -use futures::future::poll_fn; use fixed::types::I7F1; +use futures::future::poll_fn; use crate::chip::EASY_DMA_SIZE; use crate::gpio::sealed::Pin; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::typelevel::Interrupt; -use crate::{interrupt, Peripheral}; use crate::pac::pdm::mode::{EDGE_A, OPERATION_A}; pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; pub use crate::pac::pdm::ratio::RATIO_A as Ratio; +use crate::{interrupt, Peripheral}; /// Interrupt handler. pub struct InterruptHandler { @@ -133,8 +133,14 @@ impl<'d, T: Instance> Pdm<'d, T> { } fn _set_gain(r: &crate::pac::pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) { - let gain_left = gain_left.saturating_add(I7F1::from_bits(40)).saturating_to_num::().clamp(0, 0x50); - let gain_right = gain_right.saturating_add(I7F1::from_bits(40)).saturating_to_num::().clamp(0, 0x50); + let gain_left = gain_left + .saturating_add(I7F1::from_bits(40)) + .saturating_to_num::() + .clamp(0, 0x50); + let gain_right = gain_right + .saturating_add(I7F1::from_bits(40)) + .saturating_to_num::() + .clamp(0, 0x50); r.gainl.write(|w| unsafe { w.gainl().bits(gain_left) }); r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) }); @@ -258,7 +264,8 @@ impl<'d, T: Instance> Pdm<'d, T> { &mut self, bufs: &mut [[i16; N]; 2], mut sampler: S, - ) -> Result<(), Error> where + ) -> Result<(), Error> + where S: FnMut(&[i16; N]) -> SamplerState, { let r = T::regs(); @@ -267,7 +274,9 @@ impl<'d, T: Instance> Pdm<'d, T> { return Err(Error::AlreadyRunning); } - r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) }); + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) }); r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); // Reset and enable the events @@ -285,7 +294,7 @@ impl<'d, T: Instance> Pdm<'d, T> { // wouldn't happen anyway. compiler_fence(Ordering::SeqCst); - r.tasks_start.write(|w| { w.tasks_start().set_bit() }); + r.tasks_start.write(|w| w.tasks_start().set_bit()); let mut current_buffer = 0; @@ -309,7 +318,8 @@ impl<'d, T: Instance> Pdm<'d, T> { r.events_end.reset(); r.intenset.write(|w| w.end().set()); - if !done { // Discard the last buffer after the user requested a stop. + if !done { + // Discard the last buffer after the user requested a stop. if sampler(&bufs[current_buffer]) == SamplerState::Sampled { let next_buffer = 1 - current_buffer; current_buffer = next_buffer; diff --git a/examples/nrf52840/src/bin/pdm.rs b/examples/nrf52840/src/bin/pdm.rs index 47fe6773..444b9137 100644 --- a/examples/nrf52840/src/bin/pdm.rs +++ b/examples/nrf52840/src/bin/pdm.rs @@ -41,9 +41,11 @@ async fn main(_p: Spawner) { buf.iter().min().unwrap(), buf.iter().max().unwrap(), mean, - ( - buf.iter().map(|v| i32::from(*v - mean).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) - / buf.len() as i32).sqrt() as i16, + (buf.iter() + .map(|v| i32::from(*v - mean).pow(2)) + .fold(0i32, |a, b| a.saturating_add(b)) + / buf.len() as i32) + .sqrt() as i16, ); info!("samples: {:?}", &buf); diff --git a/examples/nrf52840/src/bin/pdm_continuous.rs b/examples/nrf52840/src/bin/pdm_continuous.rs index 9eaf3071..7d853147 100644 --- a/examples/nrf52840/src/bin/pdm_continuous.rs +++ b/examples/nrf52840/src/bin/pdm_continuous.rs @@ -2,14 +2,15 @@ #![no_main] #![feature(type_alias_impl_trait)] -use defmt::info; use core::cmp::Ordering; + +use defmt::info; use embassy_executor::Spawner; +use embassy_nrf::pdm::{self, Config, Frequency, OperationMode, Pdm, Ratio, SamplerState}; use embassy_nrf::{bind_interrupts, peripherals}; -use embassy_nrf::pdm::{self, Config, OperationMode, Pdm, SamplerState, Frequency, Ratio}; use fixed::types::I7F1; -use num_integer::Roots; use microfft::real::rfft_1024; +use num_integer::Roots; use {defmt_rtt as _, panic_probe as _}; // Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer @@ -31,34 +32,34 @@ async fn main(_p: Spawner) { let mut bufs = [[0; 1024]; 2]; - pdm - .run_task_sampler( - &mut bufs, - move |buf| { - // NOTE: It is important that the time spent within this callback - // does not exceed the time taken to acquire the 1500 samples we - // have in this example, which would be 10us + 2us per - // sample * 1500 = 18ms. You need to measure the time taken here - // and set the sample buffer size accordingly. Exceeding this - // time can lead to the peripheral re-writing the other buffer. - let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; - let (peak_freq_index, peak_mag) = fft_peak_freq(&buf); - let peak_freq = peak_freq_index * 16000 / buf.len(); - info!( - "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}, peak {} @ {} Hz", - buf.len(), - buf.iter().min().unwrap(), - buf.iter().max().unwrap(), - mean, - ( - buf.iter().map(|v| i32::from(*v - mean).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) - / buf.len() as i32).sqrt() as i16, - peak_mag, peak_freq, - ); - SamplerState::Sampled - }, - ) - .await.unwrap(); + pdm.run_task_sampler(&mut bufs, move |buf| { + // NOTE: It is important that the time spent within this callback + // does not exceed the time taken to acquire the 1500 samples we + // have in this example, which would be 10us + 2us per + // sample * 1500 = 18ms. You need to measure the time taken here + // and set the sample buffer size accordingly. Exceeding this + // time can lead to the peripheral re-writing the other buffer. + let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; + let (peak_freq_index, peak_mag) = fft_peak_freq(&buf); + let peak_freq = peak_freq_index * 16000 / buf.len(); + info!( + "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}, peak {} @ {} Hz", + buf.len(), + buf.iter().min().unwrap(), + buf.iter().max().unwrap(), + mean, + (buf.iter() + .map(|v| i32::from(*v - mean).pow(2)) + .fold(0i32, |a, b| a.saturating_add(b)) + / buf.len() as i32) + .sqrt() as i16, + peak_mag, + peak_freq, + ); + SamplerState::Sampled + }) + .await + .unwrap(); } fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) { @@ -75,6 +76,6 @@ fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) { .map(|c| c.norm_sqr()) .enumerate() .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal)) - .map(|(i, v)| (i, ((v*32768.0) as u32).sqrt())) + .map(|(i, v)| (i, ((v * 32768.0) as u32).sqrt())) .unwrap() } From be7fbe50d7afe78988509be485d461494d77381a Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Tue, 18 Jul 2023 18:31:32 -0400 Subject: [PATCH 12/13] Update pdm driver to build with all the PACs --- embassy-nrf/src/pdm.rs | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index 48668c7f..4cee566c 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -17,9 +17,16 @@ use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::typelevel::Interrupt; use crate::pac::pdm::mode::{EDGE_A, OPERATION_A}; pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; -pub use crate::pac::pdm::ratio::RATIO_A as Ratio; use crate::{interrupt, Peripheral}; +#[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf9160", +))] +pub use crate::pac::pdm::ratio::RATIO_A as Ratio; + /// Interrupt handler. pub struct InterruptHandler { _phantom: PhantomData, @@ -111,6 +118,12 @@ impl<'d, T: Instance> Pdm<'d, T> { // configure r.pdmclkctrl.write(|w| w.freq().variant(config.frequency)); + #[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf9160", + ))] r.ratio.write(|w| w.ratio().variant(config.ratio)); r.mode.write(|w| { w.operation().variant(config.operation_mode.into()); @@ -294,14 +307,14 @@ impl<'d, T: Instance> Pdm<'d, T> { // wouldn't happen anyway. compiler_fence(Ordering::SeqCst); - r.tasks_start.write(|w| w.tasks_start().set_bit()); + r.tasks_start.write(|w| unsafe { w.bits(1) }); let mut current_buffer = 0; let mut done = false; let drop = OnDrop::new(|| { - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); // N.B. It would be better if this were async, but Drop only support sync code. while r.events_stopped.read().bits() != 0 {} }); @@ -324,7 +337,7 @@ impl<'d, T: Instance> Pdm<'d, T> { let next_buffer = 1 - current_buffer; current_buffer = next_buffer; } else { - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); done = true; }; }; @@ -361,6 +374,12 @@ pub struct Config { /// Clock frequency pub frequency: Frequency, /// Clock ratio + #[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf9160", + ))] pub ratio: Ratio, /// Gain left in dB pub gain_left: I7F1, @@ -374,6 +393,12 @@ impl Default for Config { operation_mode: OperationMode::Mono, edge: Edge::LeftFalling, frequency: Frequency::DEFAULT, + #[cfg(any( + feature = "nrf52840", + feature = "nrf52833", + feature = "_nrf5340-app", + feature = "_nrf9160", + ))] ratio: Ratio::RATIO80, gain_left: I7F1::ZERO, gain_right: I7F1::ZERO, From 7555a1e3025a45a145734026c0f841d7c6c2625f Mon Sep 17 00:00:00 2001 From: Quentin Smith Date: Tue, 18 Jul 2023 18:32:19 -0400 Subject: [PATCH 13/13] cargo fmt --- embassy-nrf/src/pdm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index 4cee566c..217884d1 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -17,8 +17,6 @@ use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::interrupt::typelevel::Interrupt; use crate::pac::pdm::mode::{EDGE_A, OPERATION_A}; pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; -use crate::{interrupt, Peripheral}; - #[cfg(any( feature = "nrf52840", feature = "nrf52833", @@ -26,6 +24,7 @@ use crate::{interrupt, Peripheral}; feature = "_nrf9160", ))] pub use crate::pac::pdm::ratio::RATIO_A as Ratio; +use crate::{interrupt, Peripheral}; /// Interrupt handler. pub struct InterruptHandler {