Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial PDM support #361

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions examples/pdm-demo/Embed.toml
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions examples/pdm-demo/README.md
Original file line number Diff line number Diff line change
@@ -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
```
75 changes: 75 additions & 0 deletions examples/pdm-demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 2 additions & 0 deletions nrf-hal-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
134 changes: 134 additions & 0 deletions nrf-hal-common/src/pdm.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}