diff options
| author | Dario Nieuwenhuis <[email protected]> | 2023-07-19 10:15:39 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-07-19 10:15:39 +0000 |
| commit | 3382ca1a5429504c56c360e99ec09f4f641948a2 (patch) | |
| tree | c0fdf5e0f91636e8178e3a7691b6f6d0ceb02546 | |
| parent | 07a9a4ffd8fd8ab3b5ff8cc845f23dcc375080be (diff) | |
| parent | 7555a1e3025a45a145734026c0f841d7c6c2625f (diff) | |
Merge pull request #1667 from quentinmit/nrf-pdm
nrf/pdm: Add continuous sampling API
| -rw-r--r-- | embassy-nrf/src/pdm.rs | 226 | ||||
| -rw-r--r-- | examples/nrf52840/Cargo.toml | 3 | ||||
| -rw-r--r-- | examples/nrf52840/src/bin/pdm.rs | 46 | ||||
| -rw-r--r-- | examples/nrf52840/src/bin/pdm_continuous.rs | 81 |
4 files changed, 333 insertions, 23 deletions
diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index efa1fbccc..217884d1c 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs | |||
| @@ -8,12 +8,22 @@ use core::task::Poll; | |||
| 8 | 8 | ||
| 9 | use embassy_hal_common::drop::OnDrop; | 9 | use embassy_hal_common::drop::OnDrop; |
| 10 | use embassy_hal_common::{into_ref, PeripheralRef}; | 10 | use embassy_hal_common::{into_ref, PeripheralRef}; |
| 11 | use fixed::types::I7F1; | ||
| 11 | use futures::future::poll_fn; | 12 | use futures::future::poll_fn; |
| 12 | 13 | ||
| 13 | use crate::chip::EASY_DMA_SIZE; | 14 | use crate::chip::EASY_DMA_SIZE; |
| 14 | use crate::gpio::sealed::Pin; | 15 | use crate::gpio::sealed::Pin; |
| 15 | use crate::gpio::{AnyPin, Pin as GpioPin}; | 16 | use crate::gpio::{AnyPin, Pin as GpioPin}; |
| 16 | use crate::interrupt::typelevel::Interrupt; | 17 | use crate::interrupt::typelevel::Interrupt; |
| 18 | use crate::pac::pdm::mode::{EDGE_A, OPERATION_A}; | ||
| 19 | pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; | ||
| 20 | #[cfg(any( | ||
| 21 | feature = "nrf52840", | ||
| 22 | feature = "nrf52833", | ||
| 23 | feature = "_nrf5340-app", | ||
| 24 | feature = "_nrf9160", | ||
| 25 | ))] | ||
| 26 | pub use crate::pac::pdm::ratio::RATIO_A as Ratio; | ||
| 17 | use crate::{interrupt, Peripheral}; | 27 | use crate::{interrupt, Peripheral}; |
| 18 | 28 | ||
| 19 | /// Interrupt handler. | 29 | /// Interrupt handler. |
| @@ -23,7 +33,20 @@ pub struct InterruptHandler<T: Instance> { | |||
| 23 | 33 | ||
| 24 | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { | 34 | impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { |
| 25 | unsafe fn on_interrupt() { | 35 | unsafe fn on_interrupt() { |
| 26 | T::regs().intenclr.write(|w| w.end().clear()); | 36 | let r = T::regs(); |
| 37 | |||
| 38 | if r.events_end.read().bits() != 0 { | ||
| 39 | r.intenclr.write(|w| w.end().clear()); | ||
| 40 | } | ||
| 41 | |||
| 42 | if r.events_started.read().bits() != 0 { | ||
| 43 | r.intenclr.write(|w| w.started().clear()); | ||
| 44 | } | ||
| 45 | |||
| 46 | if r.events_stopped.read().bits() != 0 { | ||
| 47 | r.intenclr.write(|w| w.stopped().clear()); | ||
| 48 | } | ||
| 49 | |||
| 27 | T::state().waker.wake(); | 50 | T::state().waker.wake(); |
| 28 | } | 51 | } |
| 29 | } | 52 | } |
| @@ -44,10 +67,24 @@ pub enum Error { | |||
| 44 | BufferZeroLength, | 67 | BufferZeroLength, |
| 45 | /// PDM is not running | 68 | /// PDM is not running |
| 46 | NotRunning, | 69 | NotRunning, |
| 70 | /// PDM is already running | ||
| 71 | AlreadyRunning, | ||
| 47 | } | 72 | } |
| 48 | 73 | ||
| 49 | static DUMMY_BUFFER: [i16; 1] = [0; 1]; | 74 | static DUMMY_BUFFER: [i16; 1] = [0; 1]; |
| 50 | 75 | ||
| 76 | /// The state of a continuously running sampler. While it reflects | ||
| 77 | /// the progress of a sampler, it also signals what should be done | ||
| 78 | /// next. For example, if the sampler has stopped then the Pdm implementation | ||
| 79 | /// can then tear down its infrastructure. | ||
| 80 | #[derive(PartialEq)] | ||
| 81 | pub enum SamplerState { | ||
| 82 | /// The sampler processed the samples and is ready for more. | ||
| 83 | Sampled, | ||
| 84 | /// The sampler is done processing samples. | ||
| 85 | Stopped, | ||
| 86 | } | ||
| 87 | |||
| 51 | impl<'d, T: Instance> Pdm<'d, T> { | 88 | impl<'d, T: Instance> Pdm<'d, T> { |
| 52 | /// Create PDM driver | 89 | /// Create PDM driver |
| 53 | pub fn new( | 90 | pub fn new( |
| @@ -79,18 +116,24 @@ impl<'d, T: Instance> Pdm<'d, T> { | |||
| 79 | r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) }); | 116 | r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) }); |
| 80 | 117 | ||
| 81 | // configure | 118 | // configure |
| 82 | // use default for | 119 | r.pdmclkctrl.write(|w| w.freq().variant(config.frequency)); |
| 83 | // - gain right | 120 | #[cfg(any( |
| 84 | // - gain left | 121 | feature = "nrf52840", |
| 85 | // - clk | 122 | feature = "nrf52833", |
| 86 | // - ratio | 123 | feature = "_nrf5340-app", |
| 124 | feature = "_nrf9160", | ||
| 125 | ))] | ||
| 126 | r.ratio.write(|w| w.ratio().variant(config.ratio)); | ||
| 87 | r.mode.write(|w| { | 127 | r.mode.write(|w| { |
| 88 | w.edge().bit(config.edge == Edge::LeftRising); | 128 | w.operation().variant(config.operation_mode.into()); |
| 89 | w.operation().bit(config.operation_mode == OperationMode::Mono); | 129 | w.edge().variant(config.edge.into()); |
| 90 | w | 130 | w |
| 91 | }); | 131 | }); |
| 92 | r.gainl.write(|w| w.gainl().default_gain()); | 132 | |
| 93 | r.gainr.write(|w| w.gainr().default_gain()); | 133 | Self::_set_gain(r, config.gain_left, config.gain_right); |
| 134 | |||
| 135 | // Disable all events interrupts | ||
| 136 | r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); | ||
| 94 | 137 | ||
| 95 | // IRQ | 138 | // IRQ |
| 96 | T::Interrupt::unpend(); | 139 | T::Interrupt::unpend(); |
| @@ -101,6 +144,25 @@ impl<'d, T: Instance> Pdm<'d, T> { | |||
| 101 | Self { _peri: pdm } | 144 | Self { _peri: pdm } |
| 102 | } | 145 | } |
| 103 | 146 | ||
| 147 | fn _set_gain(r: &crate::pac::pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) { | ||
| 148 | let gain_left = gain_left | ||
| 149 | .saturating_add(I7F1::from_bits(40)) | ||
| 150 | .saturating_to_num::<u8>() | ||
| 151 | .clamp(0, 0x50); | ||
| 152 | let gain_right = gain_right | ||
| 153 | .saturating_add(I7F1::from_bits(40)) | ||
| 154 | .saturating_to_num::<u8>() | ||
| 155 | .clamp(0, 0x50); | ||
| 156 | |||
| 157 | r.gainl.write(|w| unsafe { w.gainl().bits(gain_left) }); | ||
| 158 | r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) }); | ||
| 159 | } | ||
| 160 | |||
| 161 | /// Adjust the gain of the PDM microphone on the fly | ||
| 162 | pub fn set_gain(&mut self, gain_left: I7F1, gain_right: I7F1) { | ||
| 163 | Self::_set_gain(T::regs(), gain_left, gain_right) | ||
| 164 | } | ||
| 165 | |||
| 104 | /// Start sampling microphon data into a dummy buffer | 166 | /// Start sampling microphon data into a dummy buffer |
| 105 | /// Usefull to start the microphon and keep it active between recording samples | 167 | /// Usefull to start the microphon and keep it active between recording samples |
| 106 | pub async fn start(&mut self) { | 168 | pub async fn start(&mut self) { |
| @@ -198,6 +260,108 @@ impl<'d, T: Instance> Pdm<'d, T> { | |||
| 198 | 260 | ||
| 199 | compiler_fence(Ordering::SeqCst); | 261 | compiler_fence(Ordering::SeqCst); |
| 200 | } | 262 | } |
| 263 | |||
| 264 | /// Continuous sampling with double buffers. | ||
| 265 | /// | ||
| 266 | /// A sampler closure is provided that receives the buffer of samples, noting | ||
| 267 | /// that the size of this buffer can be less than the original buffer's size. | ||
| 268 | /// A command is return from the closure that indicates whether the sampling | ||
| 269 | /// should continue or stop. | ||
| 270 | /// | ||
| 271 | /// NOTE: The time spent within the callback supplied should not exceed the time | ||
| 272 | /// taken to acquire the samples into a single buffer. You should measure the | ||
| 273 | /// time taken by the callback and set the sample buffer size accordingly. | ||
| 274 | /// Exceeding this time can lead to samples becoming dropped. | ||
| 275 | pub async fn run_task_sampler<S, const N: usize>( | ||
| 276 | &mut self, | ||
| 277 | bufs: &mut [[i16; N]; 2], | ||
| 278 | mut sampler: S, | ||
| 279 | ) -> Result<(), Error> | ||
| 280 | where | ||
| 281 | S: FnMut(&[i16; N]) -> SamplerState, | ||
| 282 | { | ||
| 283 | let r = T::regs(); | ||
| 284 | |||
| 285 | if r.events_started.read().bits() != 0 { | ||
| 286 | return Err(Error::AlreadyRunning); | ||
| 287 | } | ||
| 288 | |||
| 289 | r.sample | ||
| 290 | .ptr | ||
| 291 | .write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) }); | ||
| 292 | r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); | ||
| 293 | |||
| 294 | // Reset and enable the events | ||
| 295 | r.events_end.reset(); | ||
| 296 | r.events_started.reset(); | ||
| 297 | r.events_stopped.reset(); | ||
| 298 | r.intenset.write(|w| { | ||
| 299 | w.end().set(); | ||
| 300 | w.started().set(); | ||
| 301 | w.stopped().set(); | ||
| 302 | w | ||
| 303 | }); | ||
| 304 | |||
| 305 | // Don't reorder the start event before the previous writes. Hopefully self | ||
| 306 | // wouldn't happen anyway. | ||
| 307 | compiler_fence(Ordering::SeqCst); | ||
| 308 | |||
| 309 | r.tasks_start.write(|w| unsafe { w.bits(1) }); | ||
| 310 | |||
| 311 | let mut current_buffer = 0; | ||
| 312 | |||
| 313 | let mut done = false; | ||
| 314 | |||
| 315 | let drop = OnDrop::new(|| { | ||
| 316 | r.tasks_stop.write(|w| unsafe { w.bits(1) }); | ||
| 317 | // N.B. It would be better if this were async, but Drop only support sync code. | ||
| 318 | while r.events_stopped.read().bits() != 0 {} | ||
| 319 | }); | ||
| 320 | |||
| 321 | // Wait for events and complete when the sampler indicates it has had enough. | ||
| 322 | poll_fn(|cx| { | ||
| 323 | let r = T::regs(); | ||
| 324 | |||
| 325 | T::state().waker.register(cx.waker()); | ||
| 326 | |||
| 327 | if r.events_end.read().bits() != 0 { | ||
| 328 | compiler_fence(Ordering::SeqCst); | ||
| 329 | |||
| 330 | r.events_end.reset(); | ||
| 331 | r.intenset.write(|w| w.end().set()); | ||
| 332 | |||
| 333 | if !done { | ||
| 334 | // Discard the last buffer after the user requested a stop. | ||
| 335 | if sampler(&bufs[current_buffer]) == SamplerState::Sampled { | ||
| 336 | let next_buffer = 1 - current_buffer; | ||
| 337 | current_buffer = next_buffer; | ||
| 338 | } else { | ||
| 339 | r.tasks_stop.write(|w| unsafe { w.bits(1) }); | ||
| 340 | done = true; | ||
| 341 | }; | ||
| 342 | }; | ||
| 343 | } | ||
| 344 | |||
| 345 | if r.events_started.read().bits() != 0 { | ||
| 346 | r.events_started.reset(); | ||
| 347 | r.intenset.write(|w| w.started().set()); | ||
| 348 | |||
| 349 | let next_buffer = 1 - current_buffer; | ||
| 350 | r.sample | ||
| 351 | .ptr | ||
| 352 | .write(|w| unsafe { w.sampleptr().bits(bufs[next_buffer].as_mut_ptr() as u32) }); | ||
| 353 | } | ||
| 354 | |||
| 355 | if r.events_stopped.read().bits() != 0 { | ||
| 356 | return Poll::Ready(()); | ||
| 357 | } | ||
| 358 | |||
| 359 | Poll::Pending | ||
| 360 | }) | ||
| 361 | .await; | ||
| 362 | drop.defuse(); | ||
| 363 | Ok(()) | ||
| 364 | } | ||
| 201 | } | 365 | } |
| 202 | 366 | ||
| 203 | /// PDM microphone driver Config | 367 | /// PDM microphone driver Config |
| @@ -206,6 +370,20 @@ pub struct Config { | |||
| 206 | pub operation_mode: OperationMode, | 370 | pub operation_mode: OperationMode, |
| 207 | /// On which edge the left channel should be samples | 371 | /// On which edge the left channel should be samples |
| 208 | pub edge: Edge, | 372 | pub edge: Edge, |
| 373 | /// Clock frequency | ||
| 374 | pub frequency: Frequency, | ||
| 375 | /// Clock ratio | ||
| 376 | #[cfg(any( | ||
| 377 | feature = "nrf52840", | ||
| 378 | feature = "nrf52833", | ||
| 379 | feature = "_nrf5340-app", | ||
| 380 | feature = "_nrf9160", | ||
| 381 | ))] | ||
| 382 | pub ratio: Ratio, | ||
| 383 | /// Gain left in dB | ||
| 384 | pub gain_left: I7F1, | ||
| 385 | /// Gain right in dB | ||
| 386 | pub gain_right: I7F1, | ||
| 209 | } | 387 | } |
| 210 | 388 | ||
| 211 | impl Default for Config { | 389 | impl Default for Config { |
| @@ -213,6 +391,16 @@ impl Default for Config { | |||
| 213 | Self { | 391 | Self { |
| 214 | operation_mode: OperationMode::Mono, | 392 | operation_mode: OperationMode::Mono, |
| 215 | edge: Edge::LeftFalling, | 393 | edge: Edge::LeftFalling, |
| 394 | frequency: Frequency::DEFAULT, | ||
| 395 | #[cfg(any( | ||
| 396 | feature = "nrf52840", | ||
| 397 | feature = "nrf52833", | ||
| 398 | feature = "_nrf5340-app", | ||
| 399 | feature = "_nrf9160", | ||
| 400 | ))] | ||
| 401 | ratio: Ratio::RATIO80, | ||
| 402 | gain_left: I7F1::ZERO, | ||
| 403 | gain_right: I7F1::ZERO, | ||
| 216 | } | 404 | } |
| 217 | } | 405 | } |
| 218 | } | 406 | } |
| @@ -226,6 +414,15 @@ pub enum OperationMode { | |||
| 226 | Stereo, | 414 | Stereo, |
| 227 | } | 415 | } |
| 228 | 416 | ||
| 417 | impl From<OperationMode> for OPERATION_A { | ||
| 418 | fn from(mode: OperationMode) -> Self { | ||
| 419 | match mode { | ||
| 420 | OperationMode::Mono => OPERATION_A::MONO, | ||
| 421 | OperationMode::Stereo => OPERATION_A::STEREO, | ||
| 422 | } | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 229 | /// PDM edge polarity | 426 | /// PDM edge polarity |
| 230 | #[derive(PartialEq)] | 427 | #[derive(PartialEq)] |
| 231 | pub enum Edge { | 428 | pub enum Edge { |
| @@ -235,6 +432,15 @@ pub enum Edge { | |||
| 235 | LeftFalling, | 432 | LeftFalling, |
| 236 | } | 433 | } |
| 237 | 434 | ||
| 435 | impl From<Edge> for EDGE_A { | ||
| 436 | fn from(edge: Edge) -> Self { | ||
| 437 | match edge { | ||
| 438 | Edge::LeftRising => EDGE_A::LEFT_RISING, | ||
| 439 | Edge::LeftFalling => EDGE_A::LEFT_FALLING, | ||
| 440 | } | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 238 | impl<'d, T: Instance> Drop for Pdm<'d, T> { | 444 | impl<'d, T: Instance> Drop for Pdm<'d, T> { |
| 239 | fn drop(&mut self) { | 445 | fn drop(&mut self) { |
| 240 | let r = T::regs(); | 446 | let r = T::regs(); |
diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 7b9c371bb..9b41ec5ab 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml | |||
| @@ -43,6 +43,7 @@ embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-host | |||
| 43 | defmt = "0.3" | 43 | defmt = "0.3" |
| 44 | defmt-rtt = "0.4" | 44 | defmt-rtt = "0.4" |
| 45 | 45 | ||
| 46 | fixed = "1.10.0" | ||
| 46 | static_cell = "1.1" | 47 | static_cell = "1.1" |
| 47 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | 48 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } |
| 48 | cortex-m-rt = "0.7.0" | 49 | cortex-m-rt = "0.7.0" |
| @@ -53,6 +54,8 @@ embedded-storage = "0.3.0" | |||
| 53 | usbd-hid = "0.6.0" | 54 | usbd-hid = "0.6.0" |
| 54 | serde = { version = "1.0.136", default-features = false } | 55 | serde = { version = "1.0.136", default-features = false } |
| 55 | embedded-hal-async = { version = "0.2.0-alpha.2", optional = true } | 56 | embedded-hal-async = { version = "0.2.0-alpha.2", optional = true } |
| 57 | num-integer = { version = "0.1.45", default-features = false } | ||
| 58 | microfft = "0.5.0" | ||
| 56 | 59 | ||
| 57 | [patch.crates-io] | 60 | [patch.crates-io] |
| 58 | lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } | 61 | lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } |
diff --git a/examples/nrf52840/src/bin/pdm.rs b/examples/nrf52840/src/bin/pdm.rs index 6b41320ca..444b9137f 100644 --- a/examples/nrf52840/src/bin/pdm.rs +++ b/examples/nrf52840/src/bin/pdm.rs | |||
| @@ -7,6 +7,8 @@ use embassy_executor::Spawner; | |||
| 7 | use embassy_nrf::pdm::{self, Config, Pdm}; | 7 | use embassy_nrf::pdm::{self, Config, Pdm}; |
| 8 | use embassy_nrf::{bind_interrupts, peripherals}; | 8 | use embassy_nrf::{bind_interrupts, peripherals}; |
| 9 | use embassy_time::{Duration, Timer}; | 9 | use embassy_time::{Duration, Timer}; |
| 10 | use fixed::types::I7F1; | ||
| 11 | use num_integer::Roots; | ||
| 10 | use {defmt_rtt as _, panic_probe as _}; | 12 | use {defmt_rtt as _, panic_probe as _}; |
| 11 | 13 | ||
| 12 | bind_interrupts!(struct Irqs { | 14 | bind_interrupts!(struct Irqs { |
| @@ -20,18 +22,36 @@ async fn main(_p: Spawner) { | |||
| 20 | let mut pdm = Pdm::new(p.PDM, Irqs, p.P0_01, p.P0_00, config); | 22 | let mut pdm = Pdm::new(p.PDM, Irqs, p.P0_01, p.P0_00, config); |
| 21 | 23 | ||
| 22 | loop { | 24 | loop { |
| 23 | pdm.start().await; | 25 | for gain in [I7F1::from_num(-20), I7F1::from_num(0), I7F1::from_num(20)] { |
| 24 | 26 | pdm.set_gain(gain, gain); | |
| 25 | // wait some time till the microphon settled | 27 | info!("Gain = {} dB", defmt::Debug2Format(&gain)); |
| 26 | Timer::after(Duration::from_millis(1000)).await; | 28 | pdm.start().await; |
| 27 | 29 | ||
| 28 | const SAMPLES: usize = 2048; | 30 | // wait some time till the microphon settled |
| 29 | let mut buf = [0i16; SAMPLES]; | 31 | Timer::after(Duration::from_millis(1000)).await; |
| 30 | pdm.sample(&mut buf).await.unwrap(); | 32 | |
| 31 | 33 | const SAMPLES: usize = 2048; | |
| 32 | info!("samples: {:?}", &buf); | 34 | let mut buf = [0i16; SAMPLES]; |
| 33 | 35 | pdm.sample(&mut buf).await.unwrap(); | |
| 34 | pdm.stop().await; | 36 | |
| 35 | Timer::after(Duration::from_millis(100)).await; | 37 | let mean = (buf.iter().map(|v| i32::from(*v)).sum::<i32>() / buf.len() as i32) as i16; |
| 38 | info!( | ||
| 39 | "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}", | ||
| 40 | buf.len(), | ||
| 41 | buf.iter().min().unwrap(), | ||
| 42 | buf.iter().max().unwrap(), | ||
| 43 | mean, | ||
| 44 | (buf.iter() | ||
| 45 | .map(|v| i32::from(*v - mean).pow(2)) | ||
| 46 | .fold(0i32, |a, b| a.saturating_add(b)) | ||
| 47 | / buf.len() as i32) | ||
| 48 | .sqrt() as i16, | ||
| 49 | ); | ||
| 50 | |||
| 51 | info!("samples: {:?}", &buf); | ||
| 52 | |||
| 53 | pdm.stop().await; | ||
| 54 | Timer::after(Duration::from_millis(100)).await; | ||
| 55 | } | ||
| 36 | } | 56 | } |
| 37 | } | 57 | } |
diff --git a/examples/nrf52840/src/bin/pdm_continuous.rs b/examples/nrf52840/src/bin/pdm_continuous.rs new file mode 100644 index 000000000..7d8531475 --- /dev/null +++ b/examples/nrf52840/src/bin/pdm_continuous.rs | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use core::cmp::Ordering; | ||
| 6 | |||
| 7 | use defmt::info; | ||
| 8 | use embassy_executor::Spawner; | ||
| 9 | use embassy_nrf::pdm::{self, Config, Frequency, OperationMode, Pdm, Ratio, SamplerState}; | ||
| 10 | use embassy_nrf::{bind_interrupts, peripherals}; | ||
| 11 | use fixed::types::I7F1; | ||
| 12 | use microfft::real::rfft_1024; | ||
| 13 | use num_integer::Roots; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | // Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer | ||
| 17 | |||
| 18 | bind_interrupts!(struct Irqs { | ||
| 19 | PDM => pdm::InterruptHandler<peripherals::PDM>; | ||
| 20 | }); | ||
| 21 | |||
| 22 | #[embassy_executor::main] | ||
| 23 | async fn main(_p: Spawner) { | ||
| 24 | let mut p = embassy_nrf::init(Default::default()); | ||
| 25 | let mut config = Config::default(); | ||
| 26 | // Pins are correct for the onboard microphone on the Feather nRF52840 Sense. | ||
| 27 | config.frequency = Frequency::_1280K; // 16 kHz sample rate | ||
| 28 | config.ratio = Ratio::RATIO80; | ||
| 29 | config.operation_mode = OperationMode::Mono; | ||
| 30 | config.gain_left = I7F1::from_bits(5); // 2.5 dB | ||
| 31 | let mut pdm = Pdm::new(p.PDM, Irqs, &mut p.P0_00, &mut p.P0_01, config); | ||
| 32 | |||
| 33 | let mut bufs = [[0; 1024]; 2]; | ||
| 34 | |||
| 35 | pdm.run_task_sampler(&mut bufs, move |buf| { | ||
| 36 | // NOTE: It is important that the time spent within this callback | ||
| 37 | // does not exceed the time taken to acquire the 1500 samples we | ||
| 38 | // have in this example, which would be 10us + 2us per | ||
| 39 | // sample * 1500 = 18ms. You need to measure the time taken here | ||
| 40 | // and set the sample buffer size accordingly. Exceeding this | ||
| 41 | // time can lead to the peripheral re-writing the other buffer. | ||
| 42 | let mean = (buf.iter().map(|v| i32::from(*v)).sum::<i32>() / buf.len() as i32) as i16; | ||
| 43 | let (peak_freq_index, peak_mag) = fft_peak_freq(&buf); | ||
| 44 | let peak_freq = peak_freq_index * 16000 / buf.len(); | ||
| 45 | info!( | ||
| 46 | "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}, peak {} @ {} Hz", | ||
| 47 | buf.len(), | ||
| 48 | buf.iter().min().unwrap(), | ||
| 49 | buf.iter().max().unwrap(), | ||
| 50 | mean, | ||
| 51 | (buf.iter() | ||
| 52 | .map(|v| i32::from(*v - mean).pow(2)) | ||
| 53 | .fold(0i32, |a, b| a.saturating_add(b)) | ||
| 54 | / buf.len() as i32) | ||
| 55 | .sqrt() as i16, | ||
| 56 | peak_mag, | ||
| 57 | peak_freq, | ||
| 58 | ); | ||
| 59 | SamplerState::Sampled | ||
| 60 | }) | ||
| 61 | .await | ||
| 62 | .unwrap(); | ||
| 63 | } | ||
| 64 | |||
| 65 | fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) { | ||
| 66 | let mut f = [0f32; 1024]; | ||
| 67 | for i in 0..input.len() { | ||
| 68 | f[i] = (input[i] as f32) / 32768.0; | ||
| 69 | } | ||
| 70 | // N.B. rfft_1024 does the FFT in-place so result is actually also a reference to f. | ||
| 71 | let result = rfft_1024(&mut f); | ||
| 72 | result[0].im = 0.0; | ||
| 73 | |||
| 74 | result | ||
| 75 | .iter() | ||
| 76 | .map(|c| c.norm_sqr()) | ||
| 77 | .enumerate() | ||
| 78 | .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal)) | ||
| 79 | .map(|(i, v)| (i, ((v * 32768.0) as u32).sqrt())) | ||
| 80 | .unwrap() | ||
| 81 | } | ||
