From eb0bf1fd7a33330425a12420e5d948ca6e88d74f Mon Sep 17 00:00:00 2001 From: Jacob Rosenthal Date: Tue, 26 Oct 2021 00:37:52 -0700 Subject: [PATCH] simple_playback api from nrf sdk --- embassy-nrf/src/pwm.rs | 167 ++++++++++++++++++++++++++- embassy-nrf/src/util.rs | 4 +- examples/nrf/src/bin/pwm_sequence.rs | 41 +++++++ 3 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 examples/nrf/src/bin/pwm_sequence.rs diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 5e996e88..ccab6f48 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -9,7 +9,8 @@ use embassy_hal_common::unborrow; use crate::gpio::sealed::Pin as _; use crate::gpio::OptionalPin as GpioOptionalPin; use crate::interrupt::Interrupt; -use crate::pac; +use crate::util::slice_in_ram_or; +use crate::{pac, EASY_DMA_SIZE}; #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Prescaler { @@ -23,11 +24,57 @@ pub enum Prescaler { Div128, } -/// Interface to the UARTE peripheral +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SequenceLoad { + Common, + Grouped, + Individual, + Waveform, +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum CounterMode { + Up, + UpAndDown, +} + +/// Interface to the PWM peripheral pub struct Pwm<'d, T: Instance> { phantom: PhantomData<&'d mut T>, } +// Configure an infinite looping sequence for `simple_playback` +pub struct LoopingConfig<'a> { + /// Selects up mode or up-and-down mode for the counter + pub counter_mode: CounterMode, + // top value to be compared against buffer values + pub top: u16, + /// Configuration for PWM_CLK + pub prescaler: Prescaler, + /// In ram buffer to be played back + pub sequence: &'a [u16], + /// Common Mode means seq in buffer will be used across all channels + /// Individual Mode buffer holds [ch0_0, ch1_0, ch2_0, ch3_0, ch0_1, ch1_1, + /// ch2_1, ch3_1 ... ch0_n, ch1_n, ch2_n, ch3_n] + pub sequence_load: SequenceLoad, + /// will instruct a new RAM stored pulse width value on every (N+1)th PWM + /// period. Setting the register to zero will result in a new duty cycle + /// update every PWM period as long as the minimum PWM period is observed. + pub repeats: u32, + /// enddelay PWM period delays between last period on sequence 0 before repeating + pub enddelay: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + Seq0BufferTooLong, + Seq1BufferTooLong, + /// EasyDMA can only read from data memory, read only buffers in flash will fail. + DMABufferNotInDataMemory, +} + impl<'d, T: Instance> Pwm<'d, T> { /// Creates the interface to a UARTE instance. /// Sets the baud rate, parity and assigns the pins to the UARTE peripheral. @@ -99,6 +146,120 @@ impl<'d, T: Instance> Pwm<'d, T> { } } + pub fn simple_playback( + _pwm: impl Unborrow + 'd, + ch0: impl Unborrow + 'd, + ch1: impl Unborrow + 'd, + ch2: impl Unborrow + 'd, + ch3: impl Unborrow + 'd, + config: LoopingConfig, + count: u16, + ) -> Result { + slice_in_ram_or(config.sequence, Error::DMABufferNotInDataMemory)?; + + if config.sequence.len() > EASY_DMA_SIZE { + return Err(Error::Seq0BufferTooLong); + } + + unborrow!(ch0, ch1, ch2, ch3); + + let odd: bool = count & 1 == 1; + + let r = T::regs(); + + if let Some(pin) = ch0.pin_mut() { + pin.set_low(); + pin.conf().write(|w| w.dir().output()); + r.psel.out[0].write(|w| unsafe { w.bits(ch0.psel_bits()) }); + } + if let Some(pin) = ch1.pin_mut() { + pin.set_low(); + pin.conf().write(|w| w.dir().output()); + r.psel.out[1].write(|w| unsafe { w.bits(ch1.psel_bits()) }); + } + if let Some(pin) = ch2.pin_mut() { + pin.set_low(); + pin.conf().write(|w| w.dir().output()); + r.psel.out[2].write(|w| unsafe { w.bits(ch2.psel_bits()) }); + } + if let Some(pin) = ch3.pin_mut() { + pin.set_low(); + pin.conf().write(|w| w.dir().output()); + r.psel.out[3].write(|w| unsafe { w.bits(ch3.psel_bits()) }); + } + + r.enable.write(|w| w.enable().enabled()); + + r.mode + .write(|w| unsafe { w.bits(config.counter_mode as u32) }); + r.prescaler + .write(|w| w.prescaler().bits(config.prescaler as u8)); + r.countertop + .write(|w| unsafe { w.countertop().bits(config.top) }); + + r.decoder.write(|w| { + w.load().bits(config.sequence_load as u8); + w.mode().refresh_count() + }); + + r.seq0 + .ptr + .write(|w| unsafe { w.bits(config.sequence.as_ptr() as u32) }); + r.seq0 + .cnt + .write(|w| unsafe { w.bits(config.sequence.len() as u32) }); + r.seq0.refresh.write(|w| unsafe { w.bits(config.repeats) }); + r.seq0 + .enddelay + .write(|w| unsafe { w.bits(config.enddelay) }); + + r.seq1 + .ptr + .write(|w| unsafe { w.bits(config.sequence.as_ptr() as u32) }); + r.seq1 + .cnt + .write(|w| unsafe { w.bits(config.sequence.len() as u32) }); + r.seq1.refresh.write(|w| unsafe { w.bits(config.repeats) }); + r.seq1 + .enddelay + .write(|w| unsafe { w.bits(config.enddelay) }); + + let mut loop_: u16 = count / 2; + if odd { + loop_ += 1; + } + + r.loop_.write(|w| unsafe { w.cnt().bits(loop_) }); + + if odd { + r.shorts.write(|w| w.loopsdone_seqstart1().set_bit()); + } else { + r.shorts.write(|w| w.loopsdone_seqstart0().set_bit()); + } + + // tasks_seqstart doesnt exist in all svds so write its bit instead + if odd { + r.tasks_seqstart[1].write(|w| unsafe { w.bits(0x01) }); + } else { + r.tasks_seqstart[0].write(|w| unsafe { w.bits(0x01) }); + } + + Ok(Self { + phantom: PhantomData, + }) + } + + /// Stop playback + #[inline(always)] + pub fn stop(&self) { + let r = T::regs(); + + r.shorts.write(|w| unsafe { w.bits(0x0) }); + + // tasks_stop doesnt exist in all svds so write its bit instead + r.tasks_stop.write(|w| unsafe { w.bits(0x01) }); + } + /// Enables the PWM generator. #[inline(always)] pub fn enable(&self) { @@ -128,7 +289,7 @@ impl<'d, T: Instance> Pwm<'d, T> { T::regs().prescaler.write(|w| w.prescaler().bits(div as u8)); } - /// Sets the PWM clock prescaler. + /// Gets the PWM clock prescaler. #[inline(always)] pub fn prescaler(&self) -> Prescaler { match T::regs().prescaler.read().prescaler().bits() { diff --git a/embassy-nrf/src/util.rs b/embassy-nrf/src/util.rs index 344fb01f..d17ec503 100644 --- a/embassy-nrf/src/util.rs +++ b/embassy-nrf/src/util.rs @@ -2,14 +2,14 @@ const SRAM_LOWER: usize = 0x2000_0000; const SRAM_UPPER: usize = 0x3000_0000; /// Does this slice reside entirely within RAM? -pub(crate) fn slice_in_ram(slice: &[u8]) -> bool { +pub(crate) fn slice_in_ram(slice: &[T]) -> bool { let ptr = slice.as_ptr() as usize; ptr >= SRAM_LOWER && (ptr + slice.len()) < SRAM_UPPER } /// Return an error if slice is not in RAM. #[cfg(not(feature = "nrf51"))] -pub(crate) fn slice_in_ram_or(slice: &[u8], err: T) -> Result<(), T> { +pub(crate) fn slice_in_ram_or(slice: &[T], err: E) -> Result<(), E> { if slice.len() == 0 || slice_in_ram(slice) { Ok(()) } else { diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs new file mode 100644 index 00000000..93ee9f5b --- /dev/null +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; +use defmt::*; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy_nrf::gpio::NoPin; +use embassy_nrf::pwm::{CounterMode, LoopingConfig, Prescaler, Pwm, SequenceLoad}; +use embassy_nrf::Peripherals; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let seq_values: [u16; 2] = [0, 0x8000]; + + let config = LoopingConfig { + counter_mode: CounterMode::Up, + top: 31250, + prescaler: Prescaler::Div128, + sequence: &seq_values, + sequence_load: SequenceLoad::Common, + repeats: 1, + enddelay: 0, + }; + + let pwm = unwrap!(Pwm::simple_playback( + p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, 1 + )); + info!("pwm started!"); + + Timer::after(Duration::from_millis(10000)).await; + + pwm.stop(); + info!("pwm stopped!"); + + loop { + Timer::after(Duration::from_millis(1000)).await; + } +}