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());
|
r.intenclr.write(|w| w.started().clear());
|
||||||
WAKER.wake();
|
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) {
|
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.ptr.write(|w| unsafe { w.sampleptr().bits(buf.as_mut_ptr() as u32) });
|
||||||
r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) });
|
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.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
|
// Don't reorder the start event before the previous writes. Hopefully self
|
||||||
// wouldn't happen anyway.
|
// wouldn't happen anyway.
|
||||||
compiler_fence(Ordering::SeqCst);
|
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.
|
// Wait for 'end' event.
|
||||||
poll_fn(|cx| {
|
poll_fn(|cx| {
|
||||||
@ -158,7 +168,109 @@ impl<'d> Pdm<'d> {
|
|||||||
WAKER.register(cx.waker());
|
WAKER.register(cx.waker());
|
||||||
|
|
||||||
if r.events_end.read().bits() != 0 {
|
if r.events_end.read().bits() != 0 {
|
||||||
|
// END means the whole buffer has been received.
|
||||||
r.events_end.reset();
|
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(());
|
return Poll::Ready(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ async fn main(_p: Spawner) {
|
|||||||
pdm.set_gain(gain, gain);
|
pdm.set_gain(gain, gain);
|
||||||
info!("Gain = {} dB", defmt::Debug2Format(&gain));
|
info!("Gain = {} dB", defmt::Debug2Format(&gain));
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
let mut buf = [0; 128];
|
let mut buf = [0; 1500];
|
||||||
pdm.sample(&mut buf).await;
|
pdm.sample(&mut buf).await;
|
||||||
info!(
|
info!(
|
||||||
"{} samples, min {=i16}, max {=i16}, RMS {=i16}",
|
"{} 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.iter().map(|v| i32::from(*v).pow(2)).fold(0i32, |a,b| a.saturating_add(b))
|
||||||
/ buf.len() as i32).sqrt() as i16,
|
/ buf.len() as i32).sqrt() as i16,
|
||||||
);
|
);
|
||||||
|
info!("samples = {}", &buf);
|
||||||
Timer::after(Duration::from_millis(100)).await;
|
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