Initial PDM driver
This commit is contained in:
		
							parent
							
								
									b7d77985cf
								
							
						
					
					
						commit
						a46f33b214
					
				| @ -128,6 +128,9 @@ embassy_hal_common::peripherals! { | |||||||
| 
 | 
 | ||||||
|     // QDEC
 |     // QDEC
 | ||||||
|     QDEC, |     QDEC, | ||||||
|  | 
 | ||||||
|  |     // PDM
 | ||||||
|  |     PDM, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | ||||||
|  | |||||||
| @ -128,6 +128,9 @@ embassy_hal_common::peripherals! { | |||||||
| 
 | 
 | ||||||
|     // QDEC
 |     // QDEC
 | ||||||
|     QDEC, |     QDEC, | ||||||
|  | 
 | ||||||
|  |     // PDM
 | ||||||
|  |     PDM, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | ||||||
|  | |||||||
| @ -138,6 +138,9 @@ embassy_hal_common::peripherals! { | |||||||
| 
 | 
 | ||||||
|     // QDEC
 |     // QDEC
 | ||||||
|     QDEC, |     QDEC, | ||||||
|  | 
 | ||||||
|  |     // PDM
 | ||||||
|  |     PDM, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | ||||||
|  | |||||||
| @ -158,6 +158,9 @@ embassy_hal_common::peripherals! { | |||||||
| 
 | 
 | ||||||
|     // QDEC
 |     // QDEC
 | ||||||
|     QDEC, |     QDEC, | ||||||
|  | 
 | ||||||
|  |     // PDM
 | ||||||
|  |     PDM, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(feature = "nightly")] | #[cfg(feature = "nightly")] | ||||||
|  | |||||||
| @ -161,6 +161,9 @@ embassy_hal_common::peripherals! { | |||||||
| 
 | 
 | ||||||
|     // TEMP
 |     // TEMP
 | ||||||
|     TEMP, |     TEMP, | ||||||
|  | 
 | ||||||
|  |     // PDM
 | ||||||
|  |     PDM, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(feature = "nightly")] | #[cfg(feature = "nightly")] | ||||||
|  | |||||||
| @ -103,6 +103,14 @@ pub mod uarte; | |||||||
| pub mod usb; | pub mod usb; | ||||||
| #[cfg(not(feature = "_nrf5340"))] | #[cfg(not(feature = "_nrf5340"))] | ||||||
| pub mod wdt; | pub mod wdt; | ||||||
|  | #[cfg(any(
 | ||||||
|  |     feature = "nrf52810", | ||||||
|  |     feature = "nrf52811", | ||||||
|  |     feature = "nrf52832", | ||||||
|  |     feature = "nrf52833", | ||||||
|  |     feature = "nrf52840", | ||||||
|  | ))] | ||||||
|  | pub mod pdm; | ||||||
| 
 | 
 | ||||||
| // This mod MUST go last, so that it sees all the `impl_foo!` macros
 | // This mod MUST go last, so that it sees all the `impl_foo!` macros
 | ||||||
| #[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] | #[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] | ||||||
|  | |||||||
							
								
								
									
										185
									
								
								embassy-nrf/src/pdm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								embassy-nrf/src/pdm.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | |||||||
|  | #![macro_use] | ||||||
|  | 
 | ||||||
|  | use core::sync::atomic::{compiler_fence, Ordering}; | ||||||
|  | use core::task::Poll; | ||||||
|  | 
 | ||||||
|  | use embassy_hal_common::{into_ref, PeripheralRef}; | ||||||
|  | use embassy_util::waitqueue::AtomicWaker; | ||||||
|  | use futures::future::poll_fn; | ||||||
|  | use pac::{pdm, PDM}; | ||||||
|  | use pdm::mode::{EDGE_A, OPERATION_A}; | ||||||
|  | use fixed::types::I7F1; | ||||||
|  | 
 | ||||||
|  | use crate::interrupt::InterruptExt; | ||||||
|  | use crate::gpio::Pin as GpioPin; | ||||||
|  | use crate::{interrupt, pac, peripherals, Peripheral}; | ||||||
|  | 
 | ||||||
|  | #[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>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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
 | ||||||
|  |     /// Clock 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 { | ||||||
|  |             channels: Channels::Stereo, | ||||||
|  |             left_edge: Edge::FallingEdge, | ||||||
|  |             gain_left: I7F1::ZERO, | ||||||
|  |             gain_right: I7F1::ZERO, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// The state of a continuously running sampler. While it reflects
 | ||||||
|  | /// the progress of a sampler, it also signals what should be done
 | ||||||
|  | /// next. For example, if the sampler has stopped then the Pdm implementation
 | ||||||
|  | /// can then tear down its infrastructure.
 | ||||||
|  | #[derive(PartialEq)] | ||||||
|  | pub enum SamplerState { | ||||||
|  |     Sampled, | ||||||
|  |     Stopped, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<'d> Pdm<'d> { | ||||||
|  |     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, | ||||||
|  |         config: Config, | ||||||
|  |     ) -> Self { | ||||||
|  |         into_ref!(pdm, irq, data, clock); | ||||||
|  | 
 | ||||||
|  |         let r = unsafe { &*PDM::ptr() }; | ||||||
|  | 
 | ||||||
|  |         let Config { channels, left_edge, gain_left, gain_right } = config; | ||||||
|  | 
 | ||||||
|  |         // Configure channels
 | ||||||
|  |         r.enable.write(|w| w.enable().enabled()); | ||||||
|  |         // TODO: Clock control
 | ||||||
|  |         r.mode.write(|w| { | ||||||
|  |             w.operation().variant(channels.into()); | ||||||
|  |             w.edge().variant(left_edge.into()); | ||||||
|  |             w | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         r.psel.din.write(|w| unsafe { w.bits(data.psel_bits()) }); | ||||||
|  |         r.psel.clk.write(|w| unsafe { w.bits(clock.psel_bits()) }); | ||||||
|  | 
 | ||||||
|  |         // Disable all events interrupts
 | ||||||
|  |         r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); | ||||||
|  | 
 | ||||||
|  |         irq.set_handler(Self::on_interrupt); | ||||||
|  |         irq.unpend(); | ||||||
|  |         irq.enable(); | ||||||
|  | 
 | ||||||
|  |         Self { _p: 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(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn regs() -> &'static pdm::RegisterBlock { | ||||||
|  |         unsafe { &*PDM::ptr() } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// One shot sampling. If the PDM is configured for multiple channels, the samples will be interleaved.
 | ||||||
|  |     pub async fn sample<const N: usize>(&mut self, buf: &mut [i16; N]) { | ||||||
|  |         let r = Self::regs(); | ||||||
|  | 
 | ||||||
|  |         // 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 _) }); | ||||||
|  | 
 | ||||||
|  |         // Reset and enable the end event
 | ||||||
|  |         r.events_end.reset(); | ||||||
|  |         r.intenset.write(|w| w.end().set()); | ||||||
|  | 
 | ||||||
|  |         // 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() }); | ||||||
|  | 
 | ||||||
|  |         // Wait for 'end' event.
 | ||||||
|  |         poll_fn(|cx| { | ||||||
|  |             let r = Self::regs(); | ||||||
|  | 
 | ||||||
|  |             WAKER.register(cx.waker()); | ||||||
|  | 
 | ||||||
|  |             if r.events_end.read().bits() != 0 { | ||||||
|  |                 r.events_end.reset(); | ||||||
|  |                 return Poll::Ready(()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Poll::Pending | ||||||
|  |         }) | ||||||
|  |         .await; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Copy, PartialEq)] | ||||||
|  | pub enum Edge { | ||||||
|  |     FallingEdge, | ||||||
|  |     RisingEdge, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Edge> for EDGE_A { | ||||||
|  |     fn from(edge: Edge) -> Self { | ||||||
|  |         match edge { | ||||||
|  |             Edge::FallingEdge => EDGE_A::LEFTFALLING, | ||||||
|  |             Edge::RisingEdge => EDGE_A::LEFTRISING, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Copy, PartialEq)] | ||||||
|  | pub enum Channels { | ||||||
|  |     Stereo, | ||||||
|  |     Mono, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Channels> for OPERATION_A { | ||||||
|  |     fn from(ch: Channels) -> Self { | ||||||
|  |         match ch { | ||||||
|  |             Channels::Stereo => OPERATION_A::STEREO, | ||||||
|  |             Channels::Mono => OPERATION_A::MONO, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -27,6 +27,7 @@ cortex-m-rt = "0.7.0" | |||||||
| panic-probe = { version = "0.3", features = ["print-defmt"] } | panic-probe = { version = "0.3", features = ["print-defmt"] } | ||||||
| futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | ||||||
| rand = { version = "0.8.4", default-features = false } | rand = { version = "0.8.4", default-features = false } | ||||||
|  | fixed = "1.10.0" | ||||||
| embedded-storage = "0.3.0" | embedded-storage = "0.3.0" | ||||||
| usbd-hid = "0.5.2" | usbd-hid = "0.5.2" | ||||||
| serde = { version = "1.0.136", default-features = false } | serde = { version = "1.0.136", default-features = false } | ||||||
|  | |||||||
							
								
								
									
										34
									
								
								examples/nrf/src/bin/pdm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								examples/nrf/src/bin/pdm.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | #![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}; | ||||||
|  | use embassy_time::{Duration, Timer}; | ||||||
|  | use fixed::types::I7F1; | ||||||
|  | use {defmt_rtt as _, panic_probe as _}; | ||||||
|  | 
 | ||||||
|  | #[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); | ||||||
|  | 
 | ||||||
|  |     loop { | ||||||
|  |         let mut buf = [0; 128]; | ||||||
|  |         pdm.sample(&mut buf).await; | ||||||
|  |         info!( | ||||||
|  |             "{} samples, min {=i16}, max {=i16}, mean {=i16}", | ||||||
|  |             buf.len(), | ||||||
|  |             buf.iter().min().unwrap(), | ||||||
|  |             buf.iter().max().unwrap(), | ||||||
|  |             (buf.iter().map(|v| i32::from(*v)).sum::<i32>() / buf.len() as i32) as i16, | ||||||
|  |         ); | ||||||
|  |         Timer::after(Duration::from_millis(100)).await; | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user