From 1b06976c69396e6fd94a9417fef694282d6aab97 Mon Sep 17 00:00:00 2001
From: Augustin Godinot <agodinot@hotmail.fr>
Date: Sun, 26 Sep 2021 17:59:53 +0200
Subject: [PATCH 1/2] Add initial PDM peripheral HAL

---
 nrf-hal-common/src/lib.rs |   2 +
 nrf-hal-common/src/pdm.rs | 134 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 136 insertions(+)
 create mode 100644 nrf-hal-common/src/pdm.rs

diff --git a/nrf-hal-common/src/lib.rs b/nrf-hal-common/src/lib.rs
index e1423ef7..97d3f510 100644
--- a/nrf-hal-common/src/lib.rs
+++ b/nrf-hal-common/src/lib.rs
@@ -85,6 +85,8 @@ pub mod uicr;
 #[cfg(feature = "nrf-usbd")]
 pub mod usbd;
 pub mod wdt;
+#[cfg(feature = "52840")]
+pub mod pdm;
 
 pub mod prelude {
     pub use crate::hal::digital::v2::*;
diff --git a/nrf-hal-common/src/pdm.rs b/nrf-hal-common/src/pdm.rs
new file mode 100644
index 00000000..354d3cf2
--- /dev/null
+++ b/nrf-hal-common/src/pdm.rs
@@ -0,0 +1,134 @@
+//! HAL interface to the PDM peripheral
+//!
+//! The PDM (Pulse Density Modulation) peripheral enables the sampling of pulse
+//! density signals.
+
+use crate::{
+    hal::digital::v2::OutputPin,
+    gpio::{Floating, Input, Output, Pin, PushPull},
+    pac::PDM,
+};
+// Publicly re-export configuration enums for convenience
+pub use crate::pac::pdm::{
+    gainl::GAINL_A as GainL, 
+    gainr::GAINR_A as GainR, 
+    mode::EDGE_A as Sampling,
+    mode::OPERATION_A as Channel, 
+    pdmclkctrl::FREQ_A as Frequency, 
+    ratio::RATIO_A as Ratio,
+};
+
+pub struct Pdm {
+    pdm: PDM,
+    clk: Pin<Output<PushPull>>,
+    din: Pin<Input<Floating>>,
+}
+
+impl Pdm {
+    /// Create the `Pdm` instance, initialize the raw peripheral and enable it.
+    pub fn new(pdm: PDM, mut clk: Pin<Output<PushPull>>, din: Pin<Input<Floating>>) -> Self {
+        // Set the CLK pin low as requested by the docs
+        clk.set_low().unwrap();
+
+        // Configure the pins
+        pdm.psel.clk.write(|w| {
+            unsafe { w.bits(clk.psel_bits()) };
+            w.connect().connected()
+        });
+        pdm.psel.din.write(|w| {
+            unsafe { w.bits(din.psel_bits()) };
+            w.connect().connected()
+        });
+
+        Self { pdm, clk, din }
+    }
+
+    /// Set clock frequency
+    pub fn frequency(&self, frequency: Frequency) -> &Self {
+        self.pdm.pdmclkctrl.write(|w| w.freq().variant(frequency));
+
+        self
+    }
+
+    /// Set the hardware decimation filter gain for the left channel (this is
+    /// also the gain used in mono mode)
+    pub fn left_gain(&self, gain: GainL) -> &Self {
+        self.pdm.gainl.write(|w| w.gainl().variant(gain));
+
+        self
+    }
+
+    /// Set the hardware decimation filter gain for the left channel (this is
+    /// also the gain used in mono mode)
+    pub fn right_gain(&self, gain: GainR) -> &Self {
+        self.pdm.gainr.write(|w| w.gainr().variant(gain));
+
+        self
+    }
+
+    /// Set the ratio clock frequency/sample rate (sample rate = clock frequency / ratio)
+    pub fn ratio(&self, ratio: Ratio) -> &Self {
+        self.pdm.ratio.write(|w| w.ratio().variant(ratio));
+        
+        self
+    }
+
+    /// Set whether the left (or mono) samples are taken on a clock rise or fall.
+    pub fn sampling(&self, sampling: Sampling) -> &Self {
+        self.pdm.mode.write(|w| w.edge().variant(sampling));
+
+        self
+    }
+
+    /// Set the channel mode : mono or stereo
+    pub fn channel(&self, channel: Channel) -> &Self {
+        self.pdm.mode.write(|w| w.operation().variant(channel));
+
+        self
+    }
+
+    /// Enable the peripheral
+    pub fn enable(&self) {
+        self.pdm.enable.write(|w| w.enable().enabled());
+    } 
+
+    /// Return ownership of underlying pins and peripheral
+    pub fn free(self) -> (PDM, Pin<Output<PushPull>>, Pin<Input<Floating>>) {
+        (self.pdm, self.clk, self.din)
+    }
+
+    /// Perform one blocking acquisition, filling the given buffer with samples.
+    ///
+    /// The buffer length must not exceed 2^16 - 1
+    pub fn read(&self, buffer: &mut [i16]) {
+        // Setup the buffer address and the number of samples to acquire
+        self.pdm.sample
+            .ptr
+            .write(|w| unsafe { w.sampleptr().bits(buffer.as_ptr() as u32) });
+        self.pdm.sample
+            .maxcnt
+            .write(|w| unsafe { w.buffsize().bits(buffer.len() as u16) });
+        
+        // Start the acquisition
+        self.pdm.tasks_start.write(|w| w.tasks_start().set_bit());
+
+        // Wait for the acquisition to start then prevent it from restarting
+        // after
+        while !self.pdm.events_started.read().events_started().bit_is_set() {}
+        self.pdm.sample
+            .maxcnt
+            .write(|w| unsafe { w.buffsize().bits(0) });
+        
+        // Wait for the acquisition to finish
+        while !self.pdm.events_end.read().events_end().bit_is_set() {}
+
+        self.clear_events();
+    }
+
+    /// Clear all events
+    fn clear_events(&self) {
+        self.pdm.events_started.write(|w| w.events_started().clear_bit());
+        self.pdm.events_stopped.write(|w| w.events_stopped().clear_bit());
+        self.pdm.events_end.write(|w| w.events_end().clear_bit());
+    }
+}

From 373ef6fe3e734ebe55ffecbd77c9f1df7b5a3d33 Mon Sep 17 00:00:00 2001
From: Augustin Godinot <agodinot@hotmail.fr>
Date: Sun, 26 Sep 2021 18:00:08 +0200
Subject: [PATCH 2/2] Add PDM example

---
 examples/pdm-demo/Embed.toml  | 63 +++++++++++++++++++++++++++++
 examples/pdm-demo/README.md   | 12 ++++++
 examples/pdm-demo/src/main.rs | 75 +++++++++++++++++++++++++++++++++++
 3 files changed, 150 insertions(+)
 create mode 100755 examples/pdm-demo/Embed.toml
 create mode 100644 examples/pdm-demo/README.md
 create mode 100755 examples/pdm-demo/src/main.rs

diff --git a/examples/pdm-demo/Embed.toml b/examples/pdm-demo/Embed.toml
new file mode 100755
index 00000000..fda29b6b
--- /dev/null
+++ b/examples/pdm-demo/Embed.toml
@@ -0,0 +1,63 @@
+[default.probe]
+# USB vendor ID
+# usb_vid = "1337"
+# USB product ID
+# usb_pid = "1337"
+# Serial number
+# serial = "12345678"
+# The protocol to be used for communicating with the target.
+protocol = "Swd"
+# The speed in kHz of the data link to the target.
+# speed = 1337
+
+[default.flashing]
+# Whether or not the target should be flashed.
+enabled = true
+# Whether or not the target should be halted after reset.
+# DEPRECATED, moved to reset section
+halt_afterwards = false
+# Whether or not bytes erased but not rewritten with data from the ELF
+# should be restored with their contents before erasing.
+restore_unwritten_bytes = false
+# The path where an SVG of the assembled flash layout should be written to.
+# flash_layout_output_path = "out.svg"
+
+[default.reset]
+# Whether or not the target should be reset.
+# When flashing is enabled as well, the target will be reset after flashing.
+enabled = true
+# Whether or not the target should be halted after reset.
+halt_afterwards = false
+
+[default.general]
+# The chip name of the chip to be debugged.
+chip = "nRF52840"
+# A list of chip descriptions to be loaded during runtime.
+chip_descriptions = []
+# The default log level to be used. Possible values are one of:
+#   "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" 
+log_level = "WARN"
+
+[default.rtt]
+# Whether or not an RTTUI should be opened after flashing.
+# This is exclusive and cannot be used with GDB at the moment.
+enabled = true
+# A list of channel associations to be displayed. If left empty, all channels are displayed.
+channels = [
+    # { up = 0, down = 0, name = "name", format = "String" }
+]
+# The duration in ms for which the logger should retry to attach to RTT.
+timeout = 3000
+# Whether timestamps in the RTTUI are enabled
+show_timestamps = true
+# Whether to save rtt history buffer on exit.
+log_enabled = false
+# Where to save rtt history buffer relative to manifest path.
+log_path = "./logs"
+
+[default.gdb]
+# Whether or not a GDB server should be opened after flashing.
+# This is exclusive and cannot be used with RTT at the moment.
+enabled = false
+# The connection string in host:port format wher the GDB server will open a socket.
+# gdb_connection_string
diff --git a/examples/pdm-demo/README.md b/examples/pdm-demo/README.md
new file mode 100644
index 00000000..fa88800c
--- /dev/null
+++ b/examples/pdm-demo/README.md
@@ -0,0 +1,12 @@
+# Pulse Density Modulation demo
+
+This example showcases how to use the PDM peripheral to acquire microphone samples on an Arduino Nano 33 BLE board.
+
+
+## How to run
+
+If using `cargo-embed`, just run
+
+```console
+$ cargo embed --release --target=thumbv7em-none-eabihf
+```
\ No newline at end of file
diff --git a/examples/pdm-demo/src/main.rs b/examples/pdm-demo/src/main.rs
new file mode 100755
index 00000000..c7e8d1f6
--- /dev/null
+++ b/examples/pdm-demo/src/main.rs
@@ -0,0 +1,75 @@
+#![no_main]
+#![no_std]
+
+use core::{
+    panic::PanicInfo,
+    sync::atomic::{compiler_fence, Ordering},
+};
+
+use nrf52840_hal as hal;
+use hal::{
+    pdm::{self, Pdm},
+    clocks::Clocks,
+    gpio::Level,
+};
+use rtt_target::{rprintln, rtt_init_print};
+
+// The lenght of the buffer allocated to the pdm samples (in mono mode it is
+// exactly the number of samples per read)
+const PDM_BUFFER_LEN: usize = 8192;
+
+#[cortex_m_rt::entry]
+fn main() -> ! {
+    rtt_init_print!();
+    rprintln!("Hello, world!");
+
+    let peripherals = hal::pac::Peripherals::take().unwrap();
+    let port0 = hal::gpio::p0::Parts::new(peripherals.P0);
+
+    // Enable the high frequency oscillator if not already enabled
+    let _clocks = Clocks::new(peripherals.CLOCK).enable_ext_hfosc();
+
+    // Retreive the right pins used in the Arduino Nano 33 BLE Sense board
+    let _mic_vcc = port0.p0_17.into_push_pull_output(Level::High);
+    let mic_clk = port0.p0_26.into_push_pull_output(Level::Low).degrade();
+    let mic_din = port0.p0_25.into_floating_input().degrade();
+    
+    let pdm = Pdm::new(peripherals.PDM, mic_clk, mic_din);
+    pdm.sampling(pdm::Sampling::LEFTFALLING)
+        .channel(pdm::Channel::MONO)
+        .frequency(pdm::Frequency::_1280K)
+        .left_gain(pdm::GainL::MAXGAIN)
+        .enable();
+
+    // Allocate the buffer
+    let mut buffer = [0; PDM_BUFFER_LEN];
+    
+    // Skip a few samples as suggested by the nrf-52840 docs
+    for i in 0..10 {
+        rprintln!("{}", i);
+        pdm.read(&mut buffer);
+    }
+
+    
+    // Output the power of the received signal
+    loop {
+        // Ask the pdm peripheral to fill our buffer with samples
+        pdm.read(&mut buffer);
+
+        let square_sum = buffer.iter().fold(0i64, |sum, &item| {
+            sum + (item as i64).pow(2)
+        });
+        rprintln!("Energy : {}", square_sum as f32 / PDM_BUFFER_LEN as f32);
+
+        for _ in 0..10_000 {}
+    }
+}
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+    cortex_m::interrupt::disable();
+    rprintln!("{}", info);
+    loop {
+        compiler_fence(Ordering::SeqCst);
+    }
+}
\ No newline at end of file