Merge pull request #1667 from quentinmit/nrf-pdm
nrf/pdm: Add continuous sampling API
This commit is contained in:
		
						commit
						3382ca1a54
					
				| @ -8,12 +8,22 @@ use core::task::Poll; | |||||||
| 
 | 
 | ||||||
| use embassy_hal_common::drop::OnDrop; | use embassy_hal_common::drop::OnDrop; | ||||||
| use embassy_hal_common::{into_ref, PeripheralRef}; | use embassy_hal_common::{into_ref, PeripheralRef}; | ||||||
|  | use fixed::types::I7F1; | ||||||
| use futures::future::poll_fn; | use futures::future::poll_fn; | ||||||
| 
 | 
 | ||||||
| use crate::chip::EASY_DMA_SIZE; | use crate::chip::EASY_DMA_SIZE; | ||||||
| use crate::gpio::sealed::Pin; | use crate::gpio::sealed::Pin; | ||||||
| use crate::gpio::{AnyPin, Pin as GpioPin}; | use crate::gpio::{AnyPin, Pin as GpioPin}; | ||||||
| use crate::interrupt::typelevel::Interrupt; | use crate::interrupt::typelevel::Interrupt; | ||||||
|  | use crate::pac::pdm::mode::{EDGE_A, OPERATION_A}; | ||||||
|  | pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; | ||||||
|  | #[cfg(any(
 | ||||||
|  |     feature = "nrf52840", | ||||||
|  |     feature = "nrf52833", | ||||||
|  |     feature = "_nrf5340-app", | ||||||
|  |     feature = "_nrf9160", | ||||||
|  | ))] | ||||||
|  | pub use crate::pac::pdm::ratio::RATIO_A as Ratio; | ||||||
| use crate::{interrupt, Peripheral}; | use crate::{interrupt, Peripheral}; | ||||||
| 
 | 
 | ||||||
| /// Interrupt handler.
 | /// Interrupt handler.
 | ||||||
| @ -23,7 +33,20 @@ pub struct InterruptHandler<T: Instance> { | |||||||
| 
 | 
 | ||||||
| impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | ||||||
|     unsafe fn on_interrupt() { |     unsafe fn on_interrupt() { | ||||||
|         T::regs().intenclr.write(|w| w.end().clear()); |         let r = T::regs(); | ||||||
|  | 
 | ||||||
|  |         if r.events_end.read().bits() != 0 { | ||||||
|  |             r.intenclr.write(|w| w.end().clear()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if r.events_started.read().bits() != 0 { | ||||||
|  |             r.intenclr.write(|w| w.started().clear()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if r.events_stopped.read().bits() != 0 { | ||||||
|  |             r.intenclr.write(|w| w.stopped().clear()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         T::state().waker.wake(); |         T::state().waker.wake(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -44,10 +67,24 @@ pub enum Error { | |||||||
|     BufferZeroLength, |     BufferZeroLength, | ||||||
|     /// PDM is not running
 |     /// PDM is not running
 | ||||||
|     NotRunning, |     NotRunning, | ||||||
|  |     /// PDM is already running
 | ||||||
|  |     AlreadyRunning, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static DUMMY_BUFFER: [i16; 1] = [0; 1]; | static DUMMY_BUFFER: [i16; 1] = [0; 1]; | ||||||
| 
 | 
 | ||||||
|  | /// 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 { | ||||||
|  |     /// The sampler processed the samples and is ready for more.
 | ||||||
|  |     Sampled, | ||||||
|  |     /// The sampler is done processing samples.
 | ||||||
|  |     Stopped, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl<'d, T: Instance> Pdm<'d, T> { | impl<'d, T: Instance> Pdm<'d, T> { | ||||||
|     /// Create PDM driver
 |     /// Create PDM driver
 | ||||||
|     pub fn new( |     pub fn new( | ||||||
| @ -79,18 +116,24 @@ impl<'d, T: Instance> Pdm<'d, T> { | |||||||
|         r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) }); |         r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) }); | ||||||
| 
 | 
 | ||||||
|         // configure
 |         // configure
 | ||||||
|         // use default for
 |         r.pdmclkctrl.write(|w| w.freq().variant(config.frequency)); | ||||||
|         // - gain right
 |         #[cfg(any(
 | ||||||
|         // - gain left
 |             feature = "nrf52840", | ||||||
|         // - clk
 |             feature = "nrf52833", | ||||||
|         // - ratio
 |             feature = "_nrf5340-app", | ||||||
|  |             feature = "_nrf9160", | ||||||
|  |         ))] | ||||||
|  |         r.ratio.write(|w| w.ratio().variant(config.ratio)); | ||||||
|         r.mode.write(|w| { |         r.mode.write(|w| { | ||||||
|             w.edge().bit(config.edge == Edge::LeftRising); |             w.operation().variant(config.operation_mode.into()); | ||||||
|             w.operation().bit(config.operation_mode == OperationMode::Mono); |             w.edge().variant(config.edge.into()); | ||||||
|             w |             w | ||||||
|         }); |         }); | ||||||
|         r.gainl.write(|w| w.gainl().default_gain()); | 
 | ||||||
|         r.gainr.write(|w| w.gainr().default_gain()); |         Self::_set_gain(r, config.gain_left, config.gain_right); | ||||||
|  | 
 | ||||||
|  |         // Disable all events interrupts
 | ||||||
|  |         r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); | ||||||
| 
 | 
 | ||||||
|         // IRQ
 |         // IRQ
 | ||||||
|         T::Interrupt::unpend(); |         T::Interrupt::unpend(); | ||||||
| @ -101,6 +144,25 @@ impl<'d, T: Instance> Pdm<'d, T> { | |||||||
|         Self { _peri: pdm } |         Self { _peri: pdm } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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::<u8>() | ||||||
|  |             .clamp(0, 0x50); | ||||||
|  |         let gain_right = gain_right | ||||||
|  |             .saturating_add(I7F1::from_bits(40)) | ||||||
|  |             .saturating_to_num::<u8>() | ||||||
|  |             .clamp(0, 0x50); | ||||||
|  | 
 | ||||||
|  |         r.gainl.write(|w| unsafe { w.gainl().bits(gain_left) }); | ||||||
|  |         r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Adjust the gain of the PDM microphone on the fly
 | ||||||
|  |     pub fn set_gain(&mut self, gain_left: I7F1, gain_right: I7F1) { | ||||||
|  |         Self::_set_gain(T::regs(), gain_left, gain_right) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Start sampling microphon data into a dummy buffer
 |     /// Start sampling microphon data into a dummy buffer
 | ||||||
|     /// Usefull to start the microphon and keep it active between recording samples
 |     /// Usefull to start the microphon and keep it active between recording samples
 | ||||||
|     pub async fn start(&mut self) { |     pub async fn start(&mut self) { | ||||||
| @ -198,6 +260,108 @@ impl<'d, T: Instance> Pdm<'d, T> { | |||||||
| 
 | 
 | ||||||
|         compiler_fence(Ordering::SeqCst); |         compiler_fence(Ordering::SeqCst); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /// Continuous sampling with double buffers.
 | ||||||
|  |     ///
 | ||||||
|  |     /// 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<S, const N: usize>( | ||||||
|  |         &mut self, | ||||||
|  |         bufs: &mut [[i16; N]; 2], | ||||||
|  |         mut sampler: S, | ||||||
|  |     ) -> Result<(), Error> | ||||||
|  |     where | ||||||
|  |         S: FnMut(&[i16; N]) -> SamplerState, | ||||||
|  |     { | ||||||
|  |         let r = T::regs(); | ||||||
|  | 
 | ||||||
|  |         if r.events_started.read().bits() != 0 { | ||||||
|  |             return Err(Error::AlreadyRunning); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         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| unsafe { w.bits(1) }); | ||||||
|  | 
 | ||||||
|  |         let mut current_buffer = 0; | ||||||
|  | 
 | ||||||
|  |         let mut done = false; | ||||||
|  | 
 | ||||||
|  |         let drop = OnDrop::new(|| { | ||||||
|  |             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 {} | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Wait for events and complete when the sampler indicates it has had enough.
 | ||||||
|  |         poll_fn(|cx| { | ||||||
|  |             let r = T::regs(); | ||||||
|  | 
 | ||||||
|  |             T::state().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| unsafe { w.bits(1) }); | ||||||
|  |                         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 { | ||||||
|  |                 return Poll::Ready(()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Poll::Pending | ||||||
|  |         }) | ||||||
|  |         .await; | ||||||
|  |         drop.defuse(); | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// PDM microphone driver Config
 | /// PDM microphone driver Config
 | ||||||
| @ -206,6 +370,20 @@ pub struct Config { | |||||||
|     pub operation_mode: OperationMode, |     pub operation_mode: OperationMode, | ||||||
|     /// On which edge the left channel should be samples
 |     /// On which edge the left channel should be samples
 | ||||||
|     pub edge: Edge, |     pub edge: Edge, | ||||||
|  |     /// 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, | ||||||
|  |     /// Gain right in dB
 | ||||||
|  |     pub gain_right: I7F1, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for Config { | impl Default for Config { | ||||||
| @ -213,6 +391,16 @@ impl Default for Config { | |||||||
|         Self { |         Self { | ||||||
|             operation_mode: OperationMode::Mono, |             operation_mode: OperationMode::Mono, | ||||||
|             edge: Edge::LeftFalling, |             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, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -226,6 +414,15 @@ pub enum OperationMode { | |||||||
|     Stereo, |     Stereo, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl From<OperationMode> for OPERATION_A { | ||||||
|  |     fn from(mode: OperationMode) -> Self { | ||||||
|  |         match mode { | ||||||
|  |             OperationMode::Mono => OPERATION_A::MONO, | ||||||
|  |             OperationMode::Stereo => OPERATION_A::STEREO, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// PDM edge polarity
 | /// PDM edge polarity
 | ||||||
| #[derive(PartialEq)] | #[derive(PartialEq)] | ||||||
| pub enum Edge { | pub enum Edge { | ||||||
| @ -235,6 +432,15 @@ pub enum Edge { | |||||||
|     LeftFalling, |     LeftFalling, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl From<Edge> for EDGE_A { | ||||||
|  |     fn from(edge: Edge) -> Self { | ||||||
|  |         match edge { | ||||||
|  |             Edge::LeftRising => EDGE_A::LEFT_RISING, | ||||||
|  |             Edge::LeftFalling => EDGE_A::LEFT_FALLING, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl<'d, T: Instance> Drop for Pdm<'d, T> { | impl<'d, T: Instance> Drop for Pdm<'d, T> { | ||||||
|     fn drop(&mut self) { |     fn drop(&mut self) { | ||||||
|         let r = T::regs(); |         let r = T::regs(); | ||||||
|  | |||||||
| @ -43,6 +43,7 @@ embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-host | |||||||
| defmt = "0.3" | defmt = "0.3" | ||||||
| defmt-rtt = "0.4" | defmt-rtt = "0.4" | ||||||
| 
 | 
 | ||||||
|  | fixed = "1.10.0" | ||||||
| static_cell = "1.1" | static_cell = "1.1" | ||||||
| cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | ||||||
| cortex-m-rt = "0.7.0" | cortex-m-rt = "0.7.0" | ||||||
| @ -53,6 +54,8 @@ embedded-storage = "0.3.0" | |||||||
| usbd-hid = "0.6.0" | usbd-hid = "0.6.0" | ||||||
| serde = { version = "1.0.136", default-features = false } | serde = { version = "1.0.136", default-features = false } | ||||||
| embedded-hal-async = { version = "0.2.0-alpha.2", optional = true } | embedded-hal-async = { version = "0.2.0-alpha.2", optional = true } | ||||||
|  | num-integer = { version = "0.1.45", default-features = false } | ||||||
|  | microfft = "0.5.0" | ||||||
| 
 | 
 | ||||||
| [patch.crates-io] | [patch.crates-io] | ||||||
| lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } | lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } | ||||||
|  | |||||||
| @ -7,6 +7,8 @@ use embassy_executor::Spawner; | |||||||
| use embassy_nrf::pdm::{self, Config, Pdm}; | use embassy_nrf::pdm::{self, Config, Pdm}; | ||||||
| use embassy_nrf::{bind_interrupts, peripherals}; | use embassy_nrf::{bind_interrupts, peripherals}; | ||||||
| use embassy_time::{Duration, Timer}; | use embassy_time::{Duration, Timer}; | ||||||
|  | use fixed::types::I7F1; | ||||||
|  | use num_integer::Roots; | ||||||
| use {defmt_rtt as _, panic_probe as _}; | use {defmt_rtt as _, panic_probe as _}; | ||||||
| 
 | 
 | ||||||
| bind_interrupts!(struct Irqs { | bind_interrupts!(struct Irqs { | ||||||
| @ -20,6 +22,9 @@ async fn main(_p: Spawner) { | |||||||
|     let mut pdm = Pdm::new(p.PDM, Irqs, p.P0_01, p.P0_00, config); |     let mut pdm = Pdm::new(p.PDM, Irqs, p.P0_01, p.P0_00, config); | ||||||
| 
 | 
 | ||||||
|     loop { |     loop { | ||||||
|  |         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)); | ||||||
|             pdm.start().await; |             pdm.start().await; | ||||||
| 
 | 
 | ||||||
|             // wait some time till the microphon settled
 |             // wait some time till the microphon settled
 | ||||||
| @ -29,9 +34,24 @@ async fn main(_p: Spawner) { | |||||||
|             let mut buf = [0i16; SAMPLES]; |             let mut buf = [0i16; SAMPLES]; | ||||||
|             pdm.sample(&mut buf).await.unwrap(); |             pdm.sample(&mut buf).await.unwrap(); | ||||||
| 
 | 
 | ||||||
|  |             let mean = (buf.iter().map(|v| i32::from(*v)).sum::<i32>() / buf.len() as i32) as i16; | ||||||
|  |             info!( | ||||||
|  |                 "{} 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 - mean).pow(2)) | ||||||
|  |                     .fold(0i32, |a, b| a.saturating_add(b)) | ||||||
|  |                     / buf.len() as i32) | ||||||
|  |                     .sqrt() as i16, | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|             info!("samples: {:?}", &buf); |             info!("samples: {:?}", &buf); | ||||||
| 
 | 
 | ||||||
|             pdm.stop().await; |             pdm.stop().await; | ||||||
|             Timer::after(Duration::from_millis(100)).await; |             Timer::after(Duration::from_millis(100)).await; | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										81
									
								
								examples/nrf52840/src/bin/pdm_continuous.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								examples/nrf52840/src/bin/pdm_continuous.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | |||||||
|  | #![no_std] | ||||||
|  | #![no_main] | ||||||
|  | #![feature(type_alias_impl_trait)] | ||||||
|  | 
 | ||||||
|  | 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 fixed::types::I7F1; | ||||||
|  | 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
 | ||||||
|  | 
 | ||||||
|  | bind_interrupts!(struct Irqs { | ||||||
|  |     PDM => pdm::InterruptHandler<peripherals::PDM>; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | #[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.frequency = Frequency::_1280K; // 16 kHz sample rate
 | ||||||
|  |     config.ratio = Ratio::RATIO80; | ||||||
|  |     config.operation_mode = OperationMode::Mono; | ||||||
|  |     config.gain_left = I7F1::from_bits(5); // 2.5 dB
 | ||||||
|  |     let mut pdm = Pdm::new(p.PDM, Irqs, &mut p.P0_00, &mut p.P0_01, config); | ||||||
|  | 
 | ||||||
|  |     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::<i32>() / 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) { | ||||||
|  |     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()) | ||||||
|  |         .enumerate() | ||||||
|  |         .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal)) | ||||||
|  |         .map(|(i, v)| (i, ((v * 32768.0) as u32).sqrt())) | ||||||
|  |         .unwrap() | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user