Add continuous PDM sampling with example
This commit is contained in:
parent
530f192acc
commit
0963b5f92c
@ -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<S, const N: usize>(
|
||||
&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(());
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
50
examples/nrf/src/bin/pdm_continuous.rs
Normal file
50
examples/nrf/src/bin/pdm_continuous.rs
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user