Merge remote-tracking branch 'origin/main' into nrf-pdm

This commit is contained in:
Quentin Smith
2023-07-17 21:31:43 -04:00
863 changed files with 74741 additions and 28517 deletions

View File

@ -1,66 +1,71 @@
//! Pulse Density Modulation (PDM) mirophone driver.
#![macro_use]
use core::marker::PhantomData;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_common::{into_ref, PeripheralRef};
use embassy_hal_common::drop::OnDrop;
use embassy_sync::waitqueue::AtomicWaker;
use embassy_hal_common::{into_ref, PeripheralRef};
use futures::future::poll_fn;
use pac::{pdm, PDM};
use pdm::mode::{EDGE_A, OPERATION_A};
pub use pdm::pdmclkctrl::FREQ_A as Frequency;
pub use pdm::ratio::RATIO_A as Ratio;
use fixed::types::I7F1;
use crate::interrupt::InterruptExt;
use crate::gpio::Pin as GpioPin;
use crate::{interrupt, pac, peripherals, Peripheral};
use crate::chip::EASY_DMA_SIZE;
use crate::gpio::sealed::Pin;
use crate::gpio::{AnyPin, Pin as GpioPin};
use crate::interrupt::typelevel::Interrupt;
use crate::{interrupt, Peripheral};
use crate::pac::pdm::mode::{EDGE_A, OPERATION_A};
pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency;
pub use crate::pac::pdm::ratio::RATIO_A as Ratio;
/// Interrupt handler.
pub struct InterruptHandler<T: Instance> {
_phantom: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
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();
}
}
/// PDM microphone interface
pub struct Pdm<'d, T: Instance> {
_peri: PeripheralRef<'d, T>,
}
/// PDM error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {}
/// One-shot and continuous PDM.
pub struct Pdm<'d> {
_p: PeripheralRef<'d, peripherals::PDM>,
pub enum Error {
/// Buffer is too long.
BufferTooLong,
/// Buffer is empty
BufferZeroLength,
/// PDM is not running
NotRunning,
/// PDM is already running
AlreadyRunning,
}
static WAKER: AtomicWaker = AtomicWaker::new();
/// Used to configure the PDM peripheral.
///
/// See the `Default` impl for suitable default values.
#[non_exhaustive]
pub struct Config {
/// Clock frequency
pub frequency: Frequency,
/// Clock ratio
pub ratio: Ratio,
/// Channels
pub channels: Channels,
/// Edge to sample on
pub left_edge: Edge,
/// Gain left in dB
pub gain_left: I7F1,
/// Gain right in dB
pub gain_right: I7F1,
}
impl Default for Config {
/// Default configuration for single channel sampling.
fn default() -> Self {
Self {
frequency: Frequency::DEFAULT,
ratio: Ratio::RATIO80,
channels: Channels::Stereo,
left_edge: Edge::FallingEdge,
gain_left: I7F1::ZERO,
gain_right: I7F1::ZERO,
}
}
}
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
@ -68,69 +73,66 @@ impl Default for Config {
/// 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> Pdm<'d> {
impl<'d, T: Instance> Pdm<'d, T> {
/// Create PDM driver
pub fn new(
pdm: impl Peripheral<P = peripherals::PDM> + 'd,
irq: impl Peripheral<P = interrupt::PDM> + 'd,
data: impl Peripheral<P = impl GpioPin> + 'd,
clock: impl Peripheral<P = impl GpioPin> + 'd,
pdm: impl Peripheral<P = T> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
clk: impl Peripheral<P = impl GpioPin> + 'd,
din: impl Peripheral<P = impl GpioPin> + 'd,
config: Config,
) -> Self {
into_ref!(pdm, irq, data, clock);
into_ref!(pdm, clk, din);
Self::new_inner(pdm, clk.map_into(), din.map_into(), config)
}
let r = unsafe { &*PDM::ptr() };
fn new_inner(
pdm: PeripheralRef<'d, T>,
clk: PeripheralRef<'d, AnyPin>,
din: PeripheralRef<'d, AnyPin>,
config: Config,
) -> Self {
into_ref!(pdm);
let Config { frequency, ratio, channels, left_edge, gain_left, gain_right } = config;
let r = T::regs();
// Configure channels
r.enable.write(|w| w.enable().enabled());
r.pdmclkctrl.write(|w| w.freq().variant(frequency));
r.ratio.write(|w| w.ratio().variant(ratio));
// setup gpio pins
din.conf().write(|w| w.input().set_bit());
r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) });
clk.set_low();
clk.conf().write(|w| w.dir().output());
r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) });
// configure
r.pdmclkctrl.write(|w| w.freq().variant(config.frequency));
r.ratio.write(|w| w.ratio().variant(config.ratio));
r.mode.write(|w| {
w.operation().variant(channels.into());
w.edge().variant(left_edge.into());
w.operation().variant(config.operation_mode.into());
w.edge().variant(config.edge.into());
w
});
Self::_set_gain(r, gain_left, gain_right);
r.psel.din.write(|w| unsafe { w.bits(data.psel_bits()) });
r.psel.clk.write(|w| unsafe { w.bits(clock.psel_bits()) });
Self::_set_gain(r, config.gain_left, config.gain_right);
// Disable all events interrupts
r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) });
irq.set_handler(Self::on_interrupt);
irq.unpend();
irq.enable();
// IRQ
T::Interrupt::unpend();
unsafe { T::Interrupt::enable() };
Self { _p: pdm }
r.enable.write(|w| w.enable().set_bit());
Self { _peri: pdm }
}
fn on_interrupt(_ctx: *mut ()) {
let r = Self::regs();
if r.events_end.read().bits() != 0 {
r.intenclr.write(|w| w.end().clear());
WAKER.wake();
}
if r.events_started.read().bits() != 0 {
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) {
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);
@ -138,81 +140,111 @@ impl<'d> Pdm<'d> {
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(Self::regs(), gain_left, gain_right)
Self::_set_gain(T::regs(), gain_left, gain_right)
}
fn regs() -> &'static pdm::RegisterBlock {
unsafe { &*PDM::ptr() }
/// Start sampling microphon data into a dummy buffer
/// Usefull to start the microphon and keep it active between recording samples
pub async fn start(&mut self) {
let r = T::regs();
// start dummy sampling because microphon needs some setup time
r.sample
.ptr
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
r.sample
.maxcnt
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
r.tasks_start.write(|w| unsafe { w.bits(1) });
}
/// One shot sampling. If the PDM is configured for multiple channels, the samples will be interleaved.
/// The first samples from the PDM peripheral and microphone usually contain garbage data, so the discard parameter sets the number of complete buffers to discard before returning.
pub async fn sample<const N: usize>(&mut self, mut discard: usize, buf: &mut [i16; N]) {
let r = Self::regs();
/// Stop sampling microphon data inta a dummy buffer
pub async fn stop(&mut self) {
let r = T::regs();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
r.events_started.reset();
}
// Set up the DMA
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 _) });
/// Sample data into the given buffer.
pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> {
if buffer.len() == 0 {
return Err(Error::BufferZeroLength);
}
if buffer.len() > EASY_DMA_SIZE {
return Err(Error::BufferTooLong);
}
// Reset and enable the events
r.events_end.reset();
r.events_stopped.reset();
r.intenset.write(|w| {
w.end().set();
w.stopped().set();
w
let r = T::regs();
if r.events_started.read().bits() == 0 {
return Err(Error::NotRunning);
}
let drop = OnDrop::new(move || {
r.intenclr.write(|w| w.end().clear());
r.events_stopped.reset();
// reset to dummy buffer
r.sample
.ptr
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
r.sample
.maxcnt
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
while r.events_stopped.read().bits() == 0 {}
});
// Don't reorder the start event before the previous writes. Hopefully self
// wouldn't happen anyway.
// setup user buffer
let ptr = buffer.as_ptr();
let len = buffer.len();
r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) });
r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) });
// wait till the current sample is finished and the user buffer sample is started
Self::wait_for_sample().await;
// reset the buffer back to the dummy buffer
r.sample
.ptr
.write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) });
r.sample
.maxcnt
.write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) });
// wait till the user buffer is sampled
Self::wait_for_sample().await;
drop.defuse();
Ok(())
}
async fn wait_for_sample() {
let r = T::regs();
r.events_end.reset();
r.intenset.write(|w| w.end().set());
compiler_fence(Ordering::SeqCst);
r.tasks_start.write(|w| w.tasks_start().set_bit());
let ondrop = OnDrop::new(|| {
r.tasks_stop.write(|w| w.tasks_stop().set_bit());
// 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 'end' event.
poll_fn(|cx| {
let r = Self::regs();
WAKER.register(cx.waker());
T::state().waker.register(cx.waker());
if r.events_end.read().bits() != 0 {
compiler_fence(Ordering::SeqCst);
// END means the whole buffer has been received.
r.events_end.reset();
r.intenset.write(|w| w.end().set());
if discard > 0 {
discard -= 1;
} else {
// 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 {
return Poll::Ready(());
}
Poll::Pending
})
.await;
ondrop.defuse();
compiler_fence(Ordering::SeqCst);
}
/// 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
@ -226,10 +258,14 @@ impl<'d> Pdm<'d> {
&mut self,
bufs: &mut [[i16; N]; 2],
mut sampler: S,
) where
) -> Result<(), Error> where
S: FnMut(&[i16; N]) -> SamplerState,
{
let r = Self::regs();
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 _) });
@ -255,7 +291,7 @@ impl<'d> Pdm<'d> {
let mut done = false;
let ondrop = OnDrop::new(|| {
let drop = OnDrop::new(|| {
r.tasks_stop.write(|w| w.tasks_stop().set_bit());
// N.B. It would be better if this were async, but Drop only support sync code.
while r.events_stopped.read().bits() != 0 {}
@ -263,9 +299,9 @@ impl<'d> Pdm<'d> {
// Wait for events and complete when the sampler indicates it has had enough.
poll_fn(|cx| {
let r = Self::regs();
let r = T::regs();
WAKER.register(cx.waker());
T::state().waker.register(cx.waker());
if r.events_end.read().bits() != 0 {
compiler_fence(Ordering::SeqCst);
@ -301,43 +337,130 @@ impl<'d> Pdm<'d> {
Poll::Pending
})
.await;
ondrop.defuse();
drop.defuse();
Ok(())
}
}
impl<'d> Drop for Pdm<'d> {
fn drop(&mut self) {
let r = Self::regs();
r.enable.write(|w| w.enable().disabled());
/// PDM microphone driver Config
pub struct Config {
/// Use stero or mono operation
pub operation_mode: OperationMode,
/// On which edge the left channel should be samples
pub edge: Edge,
/// Clock frequency
pub frequency: Frequency,
/// Clock ratio
pub ratio: Ratio,
/// Gain left in dB
pub gain_left: I7F1,
/// Gain right in dB
pub gain_right: I7F1,
}
impl Default for Config {
fn default() -> Self {
Self {
operation_mode: OperationMode::Mono,
edge: Edge::LeftFalling,
frequency: Frequency::DEFAULT,
ratio: Ratio::RATIO80,
gain_left: I7F1::ZERO,
gain_right: I7F1::ZERO,
}
}
}
#[derive(Clone, Copy, PartialEq)]
/// PDM operation mode.
#[derive(PartialEq)]
pub enum OperationMode {
/// Mono (1 channel)
Mono,
/// Stereo (2 channels)
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
#[derive(PartialEq)]
pub enum Edge {
FallingEdge,
RisingEdge,
/// Left edge is rising
LeftRising,
/// Left edge is falling
LeftFalling,
}
impl From<Edge> for EDGE_A {
fn from(edge: Edge) -> Self {
match edge {
Edge::FallingEdge => EDGE_A::LEFTFALLING,
Edge::RisingEdge => EDGE_A::LEFTRISING,
Edge::LeftRising => EDGE_A::LEFT_RISING,
Edge::LeftFalling => EDGE_A::LEFT_FALLING,
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Channels {
Stereo,
Mono,
impl<'d, T: Instance> Drop for Pdm<'d, T> {
fn drop(&mut self) {
let r = T::regs();
r.tasks_stop.write(|w| unsafe { w.bits(1) });
r.enable.write(|w| w.enable().disabled());
r.psel.din.reset();
r.psel.clk.reset();
}
}
impl From<Channels> for OPERATION_A {
fn from(ch: Channels) -> Self {
match ch {
Channels::Stereo => OPERATION_A::STEREO,
Channels::Mono => OPERATION_A::MONO,
pub(crate) mod sealed {
use embassy_sync::waitqueue::AtomicWaker;
/// Peripheral static state
pub struct State {
pub waker: AtomicWaker,
}
impl State {
pub const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
}
}
}
pub trait Instance {
fn regs() -> &'static crate::pac::pdm::RegisterBlock;
fn state() -> &'static State;
}
}
/// PDM peripheral instance.
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
/// Interrupt for this peripheral.
type Interrupt: interrupt::typelevel::Interrupt;
}
macro_rules! impl_pdm {
($type:ident, $pac_type:ident, $irq:ident) => {
impl crate::pdm::sealed::Instance for peripherals::$type {
fn regs() -> &'static crate::pac::pdm::RegisterBlock {
unsafe { &*pac::$pac_type::ptr() }
}
fn state() -> &'static crate::pdm::sealed::State {
static STATE: crate::pdm::sealed::State = crate::pdm::sealed::State::new();
&STATE
}
}
impl crate::pdm::Instance for peripherals::$type {
type Interrupt = crate::interrupt::typelevel::$irq;
}
};
}