aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-07-19 10:15:39 +0000
committerGitHub <[email protected]>2023-07-19 10:15:39 +0000
commit3382ca1a5429504c56c360e99ec09f4f641948a2 (patch)
treec0fdf5e0f91636e8178e3a7691b6f6d0ceb02546
parent07a9a4ffd8fd8ab3b5ff8cc845f23dcc375080be (diff)
parent7555a1e3025a45a145734026c0f841d7c6c2625f (diff)
Merge pull request #1667 from quentinmit/nrf-pdm
nrf/pdm: Add continuous sampling API
-rw-r--r--embassy-nrf/src/pdm.rs226
-rw-r--r--examples/nrf52840/Cargo.toml3
-rw-r--r--examples/nrf52840/src/bin/pdm.rs46
-rw-r--r--examples/nrf52840/src/bin/pdm_continuous.rs81
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
9use embassy_hal_common::drop::OnDrop; 9use embassy_hal_common::drop::OnDrop;
10use embassy_hal_common::{into_ref, PeripheralRef}; 10use embassy_hal_common::{into_ref, PeripheralRef};
11use fixed::types::I7F1;
11use futures::future::poll_fn; 12use futures::future::poll_fn;
12 13
13use crate::chip::EASY_DMA_SIZE; 14use crate::chip::EASY_DMA_SIZE;
14use crate::gpio::sealed::Pin; 15use crate::gpio::sealed::Pin;
15use crate::gpio::{AnyPin, Pin as GpioPin}; 16use crate::gpio::{AnyPin, Pin as GpioPin};
16use crate::interrupt::typelevel::Interrupt; 17use crate::interrupt::typelevel::Interrupt;
18use crate::pac::pdm::mode::{EDGE_A, OPERATION_A};
19pub 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))]
26pub use crate::pac::pdm::ratio::RATIO_A as Ratio;
17use crate::{interrupt, Peripheral}; 27use crate::{interrupt, Peripheral};
18 28
19/// Interrupt handler. 29/// Interrupt handler.
@@ -23,7 +33,20 @@ pub struct InterruptHandler<T: Instance> {
23 33
24impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { 34impl<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
49static DUMMY_BUFFER: [i16; 1] = [0; 1]; 74static 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)]
81pub 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
51impl<'d, T: Instance> Pdm<'d, T> { 88impl<'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
211impl Default for Config { 389impl 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
417impl 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)]
231pub enum Edge { 428pub enum Edge {
@@ -235,6 +432,15 @@ pub enum Edge {
235 LeftFalling, 432 LeftFalling,
236} 433}
237 434
435impl 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
238impl<'d, T: Instance> Drop for Pdm<'d, T> { 444impl<'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
43defmt = "0.3" 43defmt = "0.3"
44defmt-rtt = "0.4" 44defmt-rtt = "0.4"
45 45
46fixed = "1.10.0"
46static_cell = "1.1" 47static_cell = "1.1"
47cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } 48cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
48cortex-m-rt = "0.7.0" 49cortex-m-rt = "0.7.0"
@@ -53,6 +54,8 @@ embedded-storage = "0.3.0"
53usbd-hid = "0.6.0" 54usbd-hid = "0.6.0"
54serde = { version = "1.0.136", default-features = false } 55serde = { version = "1.0.136", default-features = false }
55embedded-hal-async = { version = "0.2.0-alpha.2", optional = true } 56embedded-hal-async = { version = "0.2.0-alpha.2", optional = true }
57num-integer = { version = "0.1.45", default-features = false }
58microfft = "0.5.0"
56 59
57[patch.crates-io] 60[patch.crates-io]
58lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } 61lora-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;
7use embassy_nrf::pdm::{self, Config, Pdm}; 7use embassy_nrf::pdm::{self, Config, Pdm};
8use embassy_nrf::{bind_interrupts, peripherals}; 8use embassy_nrf::{bind_interrupts, peripherals};
9use embassy_time::{Duration, Timer}; 9use embassy_time::{Duration, Timer};
10use fixed::types::I7F1;
11use num_integer::Roots;
10use {defmt_rtt as _, panic_probe as _}; 12use {defmt_rtt as _, panic_probe as _};
11 13
12bind_interrupts!(struct Irqs { 14bind_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
5use core::cmp::Ordering;
6
7use defmt::info;
8use embassy_executor::Spawner;
9use embassy_nrf::pdm::{self, Config, Frequency, OperationMode, Pdm, Ratio, SamplerState};
10use embassy_nrf::{bind_interrupts, peripherals};
11use fixed::types::I7F1;
12use microfft::real::rfft_1024;
13use num_integer::Roots;
14use {defmt_rtt as _, panic_probe as _};
15
16// Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer
17
18bind_interrupts!(struct Irqs {
19 PDM => pdm::InterruptHandler<peripherals::PDM>;
20});
21
22#[embassy_executor::main]
23async 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
65fn 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}