diff options
| author | Quentin Smith <[email protected]> | 2022-08-20 16:37:51 -0400 |
|---|---|---|
| committer | Quentin Smith <[email protected]> | 2022-08-20 16:37:51 -0400 |
| commit | a46f33b2144df0b913b50bb8c78256e20bce84c8 (patch) | |
| tree | 9b1fab8cd08d36ec4f225b2f04b9786bd3100d6c | |
| parent | b7d77985cf230416e53190c4edde4030e42266ed (diff) | |
Initial PDM driver
| -rw-r--r-- | embassy-nrf/src/chips/nrf52810.rs | 3 | ||||
| -rw-r--r-- | embassy-nrf/src/chips/nrf52811.rs | 3 | ||||
| -rw-r--r-- | embassy-nrf/src/chips/nrf52832.rs | 3 | ||||
| -rw-r--r-- | embassy-nrf/src/chips/nrf52833.rs | 3 | ||||
| -rw-r--r-- | embassy-nrf/src/chips/nrf52840.rs | 3 | ||||
| -rw-r--r-- | embassy-nrf/src/lib.rs | 8 | ||||
| -rw-r--r-- | embassy-nrf/src/pdm.rs | 185 | ||||
| -rw-r--r-- | examples/nrf/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/nrf/src/bin/pdm.rs | 34 |
9 files changed, 243 insertions, 0 deletions
diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index faa52d8fb..3e500098c 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs | |||
| @@ -128,6 +128,9 @@ embassy_hal_common::peripherals! { | |||
| 128 | 128 | ||
| 129 | // QDEC | 129 | // QDEC |
| 130 | QDEC, | 130 | QDEC, |
| 131 | |||
| 132 | // PDM | ||
| 133 | PDM, | ||
| 131 | } | 134 | } |
| 132 | 135 | ||
| 133 | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | 136 | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); |
diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index bbdf1cbe5..25c7c0d91 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs | |||
| @@ -128,6 +128,9 @@ embassy_hal_common::peripherals! { | |||
| 128 | 128 | ||
| 129 | // QDEC | 129 | // QDEC |
| 130 | QDEC, | 130 | QDEC, |
| 131 | |||
| 132 | // PDM | ||
| 133 | PDM, | ||
| 131 | } | 134 | } |
| 132 | 135 | ||
| 133 | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | 136 | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); |
diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 18b8eda67..2c6276d4a 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs | |||
| @@ -138,6 +138,9 @@ embassy_hal_common::peripherals! { | |||
| 138 | 138 | ||
| 139 | // QDEC | 139 | // QDEC |
| 140 | QDEC, | 140 | QDEC, |
| 141 | |||
| 142 | // PDM | ||
| 143 | PDM, | ||
| 141 | } | 144 | } |
| 142 | 145 | ||
| 143 | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); | 146 | impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); |
diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 39a0f93f9..3b33907d2 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs | |||
| @@ -158,6 +158,9 @@ embassy_hal_common::peripherals! { | |||
| 158 | 158 | ||
| 159 | // QDEC | 159 | // QDEC |
| 160 | QDEC, | 160 | QDEC, |
| 161 | |||
| 162 | // PDM | ||
| 163 | PDM, | ||
| 161 | } | 164 | } |
| 162 | 165 | ||
| 163 | #[cfg(feature = "nightly")] | 166 | #[cfg(feature = "nightly")] |
diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index e3d8f34a1..ae59f8b25 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs | |||
| @@ -161,6 +161,9 @@ embassy_hal_common::peripherals! { | |||
| 161 | 161 | ||
| 162 | // TEMP | 162 | // TEMP |
| 163 | TEMP, | 163 | TEMP, |
| 164 | |||
| 165 | // PDM | ||
| 166 | PDM, | ||
| 164 | } | 167 | } |
| 165 | 168 | ||
| 166 | #[cfg(feature = "nightly")] | 169 | #[cfg(feature = "nightly")] |
diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index f3b3ca0ca..205891954 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs | |||
| @@ -103,6 +103,14 @@ pub mod uarte; | |||
| 103 | pub mod usb; | 103 | pub mod usb; |
| 104 | #[cfg(not(feature = "_nrf5340"))] | 104 | #[cfg(not(feature = "_nrf5340"))] |
| 105 | pub mod wdt; | 105 | pub mod wdt; |
| 106 | #[cfg(any( | ||
| 107 | feature = "nrf52810", | ||
| 108 | feature = "nrf52811", | ||
| 109 | feature = "nrf52832", | ||
| 110 | feature = "nrf52833", | ||
| 111 | feature = "nrf52840", | ||
| 112 | ))] | ||
| 113 | pub mod pdm; | ||
| 106 | 114 | ||
| 107 | // This mod MUST go last, so that it sees all the `impl_foo!` macros | 115 | // This mod MUST go last, so that it sees all the `impl_foo!` macros |
| 108 | #[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] | 116 | #[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] |
diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs new file mode 100644 index 000000000..629eab99f --- /dev/null +++ b/embassy-nrf/src/pdm.rs | |||
| @@ -0,0 +1,185 @@ | |||
| 1 | #![macro_use] | ||
| 2 | |||
| 3 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 4 | use core::task::Poll; | ||
| 5 | |||
| 6 | use embassy_hal_common::{into_ref, PeripheralRef}; | ||
| 7 | use embassy_util::waitqueue::AtomicWaker; | ||
| 8 | use futures::future::poll_fn; | ||
| 9 | use pac::{pdm, PDM}; | ||
| 10 | use pdm::mode::{EDGE_A, OPERATION_A}; | ||
| 11 | use fixed::types::I7F1; | ||
| 12 | |||
| 13 | use crate::interrupt::InterruptExt; | ||
| 14 | use crate::gpio::Pin as GpioPin; | ||
| 15 | use crate::{interrupt, pac, peripherals, Peripheral}; | ||
| 16 | |||
| 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 18 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 19 | #[non_exhaustive] | ||
| 20 | pub enum Error {} | ||
| 21 | |||
| 22 | /// One-shot and continuous PDM. | ||
| 23 | pub struct Pdm<'d> { | ||
| 24 | _p: PeripheralRef<'d, peripherals::PDM>, | ||
| 25 | } | ||
| 26 | |||
| 27 | static WAKER: AtomicWaker = AtomicWaker::new(); | ||
| 28 | |||
| 29 | /// Used to configure the PDM peripheral. | ||
| 30 | /// | ||
| 31 | /// See the `Default` impl for suitable default values. | ||
| 32 | #[non_exhaustive] | ||
| 33 | pub struct Config { | ||
| 34 | /// Clock | ||
| 35 | /// Clock ratio | ||
| 36 | /// Channels | ||
| 37 | pub channels: Channels, | ||
| 38 | /// Edge to sample on | ||
| 39 | pub left_edge: Edge, | ||
| 40 | /// Gain left in dB | ||
| 41 | pub gain_left: I7F1, | ||
| 42 | /// Gain right in dB | ||
| 43 | pub gain_right: I7F1, | ||
| 44 | } | ||
| 45 | |||
| 46 | impl Default for Config { | ||
| 47 | /// Default configuration for single channel sampling. | ||
| 48 | fn default() -> Self { | ||
| 49 | Self { | ||
| 50 | channels: Channels::Stereo, | ||
| 51 | left_edge: Edge::FallingEdge, | ||
| 52 | gain_left: I7F1::ZERO, | ||
| 53 | gain_right: I7F1::ZERO, | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | /// The state of a continuously running sampler. While it reflects | ||
| 59 | /// the progress of a sampler, it also signals what should be done | ||
| 60 | /// next. For example, if the sampler has stopped then the Pdm implementation | ||
| 61 | /// can then tear down its infrastructure. | ||
| 62 | #[derive(PartialEq)] | ||
| 63 | pub enum SamplerState { | ||
| 64 | Sampled, | ||
| 65 | Stopped, | ||
| 66 | } | ||
| 67 | |||
| 68 | impl<'d> Pdm<'d> { | ||
| 69 | pub fn new( | ||
| 70 | pdm: impl Peripheral<P = peripherals::PDM> + 'd, | ||
| 71 | irq: impl Peripheral<P = interrupt::PDM> + 'd, | ||
| 72 | data: impl Peripheral<P = impl GpioPin> + 'd, | ||
| 73 | clock: impl Peripheral<P = impl GpioPin> + 'd, | ||
| 74 | config: Config, | ||
| 75 | ) -> Self { | ||
| 76 | into_ref!(pdm, irq, data, clock); | ||
| 77 | |||
| 78 | let r = unsafe { &*PDM::ptr() }; | ||
| 79 | |||
| 80 | let Config { channels, left_edge, gain_left, gain_right } = config; | ||
| 81 | |||
| 82 | // Configure channels | ||
| 83 | r.enable.write(|w| w.enable().enabled()); | ||
| 84 | // TODO: Clock control | ||
| 85 | r.mode.write(|w| { | ||
| 86 | w.operation().variant(channels.into()); | ||
| 87 | w.edge().variant(left_edge.into()); | ||
| 88 | w | ||
| 89 | }); | ||
| 90 | |||
| 91 | r.psel.din.write(|w| unsafe { w.bits(data.psel_bits()) }); | ||
| 92 | r.psel.clk.write(|w| unsafe { w.bits(clock.psel_bits()) }); | ||
| 93 | |||
| 94 | // Disable all events interrupts | ||
| 95 | r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); | ||
| 96 | |||
| 97 | irq.set_handler(Self::on_interrupt); | ||
| 98 | irq.unpend(); | ||
| 99 | irq.enable(); | ||
| 100 | |||
| 101 | Self { _p: pdm } | ||
| 102 | } | ||
| 103 | |||
| 104 | fn on_interrupt(_ctx: *mut ()) { | ||
| 105 | let r = Self::regs(); | ||
| 106 | |||
| 107 | if r.events_end.read().bits() != 0 { | ||
| 108 | r.intenclr.write(|w| w.end().clear()); | ||
| 109 | WAKER.wake(); | ||
| 110 | } | ||
| 111 | |||
| 112 | if r.events_started.read().bits() != 0 { | ||
| 113 | r.intenclr.write(|w| w.started().clear()); | ||
| 114 | WAKER.wake(); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | fn regs() -> &'static pdm::RegisterBlock { | ||
| 119 | unsafe { &*PDM::ptr() } | ||
| 120 | } | ||
| 121 | |||
| 122 | /// One shot sampling. If the PDM is configured for multiple channels, the samples will be interleaved. | ||
| 123 | pub async fn sample<const N: usize>(&mut self, buf: &mut [i16; N]) { | ||
| 124 | let r = Self::regs(); | ||
| 125 | |||
| 126 | // Set up the DMA | ||
| 127 | r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(buf.as_mut_ptr() as u32) }); | ||
| 128 | r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); | ||
| 129 | |||
| 130 | // Reset and enable the end event | ||
| 131 | r.events_end.reset(); | ||
| 132 | r.intenset.write(|w| w.end().set()); | ||
| 133 | |||
| 134 | // Don't reorder the start event before the previous writes. Hopefully self | ||
| 135 | // wouldn't happen anyway. | ||
| 136 | compiler_fence(Ordering::SeqCst); | ||
| 137 | |||
| 138 | r.tasks_start.write(|w| { w.tasks_start().set_bit() }); | ||
| 139 | |||
| 140 | // Wait for 'end' event. | ||
| 141 | poll_fn(|cx| { | ||
| 142 | let r = Self::regs(); | ||
| 143 | |||
| 144 | WAKER.register(cx.waker()); | ||
| 145 | |||
| 146 | if r.events_end.read().bits() != 0 { | ||
| 147 | r.events_end.reset(); | ||
| 148 | return Poll::Ready(()); | ||
| 149 | } | ||
| 150 | |||
| 151 | Poll::Pending | ||
| 152 | }) | ||
| 153 | .await; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | #[derive(Clone, Copy, PartialEq)] | ||
| 158 | pub enum Edge { | ||
| 159 | FallingEdge, | ||
| 160 | RisingEdge, | ||
| 161 | } | ||
| 162 | |||
| 163 | impl From<Edge> for EDGE_A { | ||
| 164 | fn from(edge: Edge) -> Self { | ||
| 165 | match edge { | ||
| 166 | Edge::FallingEdge => EDGE_A::LEFTFALLING, | ||
| 167 | Edge::RisingEdge => EDGE_A::LEFTRISING, | ||
| 168 | } | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | #[derive(Clone, Copy, PartialEq)] | ||
| 173 | pub enum Channels { | ||
| 174 | Stereo, | ||
| 175 | Mono, | ||
| 176 | } | ||
| 177 | |||
| 178 | impl From<Channels> for OPERATION_A { | ||
| 179 | fn from(ch: Channels) -> Self { | ||
| 180 | match ch { | ||
| 181 | Channels::Stereo => OPERATION_A::STEREO, | ||
| 182 | Channels::Mono => OPERATION_A::MONO, | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } \ No newline at end of file | ||
diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 2fcc31221..673bcfc6b 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml | |||
| @@ -27,6 +27,7 @@ cortex-m-rt = "0.7.0" | |||
| 27 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 27 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 28 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | 28 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } |
| 29 | rand = { version = "0.8.4", default-features = false } | 29 | rand = { version = "0.8.4", default-features = false } |
| 30 | fixed = "1.10.0" | ||
| 30 | embedded-storage = "0.3.0" | 31 | embedded-storage = "0.3.0" |
| 31 | usbd-hid = "0.5.2" | 32 | usbd-hid = "0.5.2" |
| 32 | serde = { version = "1.0.136", default-features = false } | 33 | serde = { version = "1.0.136", default-features = false } |
diff --git a/examples/nrf/src/bin/pdm.rs b/examples/nrf/src/bin/pdm.rs new file mode 100644 index 000000000..d5e90e27a --- /dev/null +++ b/examples/nrf/src/bin/pdm.rs | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::info; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_nrf::interrupt; | ||
| 8 | use embassy_nrf::pdm::{Config, Channels, Pdm}; | ||
| 9 | use embassy_time::{Duration, Timer}; | ||
| 10 | use fixed::types::I7F1; | ||
| 11 | use {defmt_rtt as _, panic_probe as _}; | ||
| 12 | |||
| 13 | #[embassy_executor::main] | ||
| 14 | async fn main(_p: Spawner) { | ||
| 15 | let mut p = embassy_nrf::init(Default::default()); | ||
| 16 | let mut config = Config::default(); | ||
| 17 | // Pins are correct for the onboard microphone on the Feather nRF52840 Sense. | ||
| 18 | config.channels = Channels::Mono; | ||
| 19 | config.gain_left = I7F1::from_bits(5); // 2.5 dB | ||
| 20 | let mut pdm = Pdm::new(p.PDM, interrupt::take!(PDM), &mut p.P0_00, &mut p.P0_01, config); | ||
| 21 | |||
| 22 | loop { | ||
| 23 | let mut buf = [0; 128]; | ||
| 24 | pdm.sample(&mut buf).await; | ||
| 25 | info!( | ||
| 26 | "{} samples, min {=i16}, max {=i16}, mean {=i16}", | ||
| 27 | buf.len(), | ||
| 28 | buf.iter().min().unwrap(), | ||
| 29 | buf.iter().max().unwrap(), | ||
| 30 | (buf.iter().map(|v| i32::from(*v)).sum::<i32>() / buf.len() as i32) as i16, | ||
| 31 | ); | ||
| 32 | Timer::after(Duration::from_millis(100)).await; | ||
| 33 | } | ||
| 34 | } | ||
