diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index a77cd6332..01b1f48d9 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -45,6 +45,8 @@ pub enum Error { DMABufferNotInDataMemory, } +const MAX_SEQUENCE_LEN: usize = 32767; + impl<'d, T: Instance> SequencePwm<'d, T> { /// Creates the interface to a `SequencePwm`. /// @@ -62,7 +64,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { ch1: impl Unborrow + 'd, ch2: impl Unborrow + 'd, ch3: impl Unborrow + 'd, - config: SequenceConfig, + config: Config, ) -> Result { unborrow!(ch0, ch1, ch2, ch3); @@ -117,16 +119,6 @@ impl<'d, T: Instance> SequencePwm<'d, T> { r.countertop .write(|w| unsafe { w.countertop().bits(config.max_duty) }); - r.seq0.refresh.write(|w| unsafe { w.bits(config.refresh) }); - r.seq0 - .enddelay - .write(|w| unsafe { w.bits(config.end_delay) }); - - r.seq1.refresh.write(|w| unsafe { w.bits(config.refresh) }); - r.seq1 - .enddelay - .write(|w| unsafe { w.bits(config.end_delay) }); - Ok(Self { phantom: PhantomData, ch0: ch0.degrade_optional(), @@ -136,80 +128,6 @@ impl<'d, T: Instance> SequencePwm<'d, T> { }) } - /// Start or restart playback - #[inline(always)] - pub fn start(&mut self, sequence: &'d [u16], times: SequenceMode) -> Result<(), Error> { - slice_in_ram_or(sequence, Error::DMABufferNotInDataMemory)?; - - if sequence.len() > 32767 { - return Err(Error::SequenceTooLong); - } - - if let SequenceMode::Times(0) = times { - return Err(Error::SequenceTimesAtLeastOne); - } - - self.stop(); - - let r = T::regs(); - - r.seq0 - .ptr - .write(|w| unsafe { w.bits(sequence.as_ptr() as u32) }); - r.seq0 - .cnt - .write(|w| unsafe { w.bits(sequence.len() as u32) }); - - r.seq1 - .ptr - .write(|w| unsafe { w.bits(sequence.as_ptr() as u32) }); - r.seq1 - .cnt - .write(|w| unsafe { w.bits(sequence.len() as u32) }); - - r.enable.write(|w| w.enable().enabled()); - - // defensive before seqstart - compiler_fence(Ordering::SeqCst); - - match times { - // just the one time, no loop count - SequenceMode::Times(1) => { - r.loop_.write(|w| w.cnt().disabled()); - // tasks_seqstart() doesn't exist in all svds so write its bit instead - r.tasks_seqstart[0].write(|w| unsafe { w.bits(0x01) }); - } - // loop count is how many times to play BOTH sequences - // 2 total (1 x 2) - // 3 total, (2 x 2) - 1 - SequenceMode::Times(n) => { - let odd = n & 1 == 1; - let times = if odd { (n / 2) + 1 } else { n / 2 }; - - r.loop_.write(|w| unsafe { w.cnt().bits(times) }); - - // we can subtract 1 by starting at seq1 instead of seq0 - if odd { - // tasks_seqstart() doesn't exist in all svds so write its bit instead - r.tasks_seqstart[1].write(|w| unsafe { w.bits(0x01) }); - } else { - // tasks_seqstart() doesn't exist in all svds so write its bit instead - r.tasks_seqstart[0].write(|w| unsafe { w.bits(0x01) }); - } - } - // to play infinitely, repeat the sequence one time, then have loops done self trigger seq0 again - SequenceMode::Infinite => { - r.loop_.write(|w| unsafe { w.cnt().bits(0x1) }); - r.shorts.write(|w| w.loopsdone_seqstart0().enabled()); - - // tasks_seqstart() doesn't exist in all svds so write its bit instead - r.tasks_seqstart[0].write(|w| unsafe { w.bits(0x01) }); - } - } - - Ok(()) - } - /// Returns reference to `Stopped` event endpoint for PPI. #[inline(always)] pub fn event_stopped(&self) -> Event { @@ -309,30 +227,12 @@ impl<'d, T: Instance> SequencePwm<'d, T> { Task::from_reg(&r.tasks_stop) } - - /// Stop playback. Disables the peripheral. Does NOT clear the last duty - /// cycle from the pin. - #[inline(always)] - pub fn stop(&self) { - let r = T::regs(); - - r.shorts.reset(); - - compiler_fence(Ordering::SeqCst); - - // tasks_stop() doesn't exist in all svds so write its bit instead - r.tasks_stop.write(|w| unsafe { w.bits(0x01) }); - - r.enable.write(|w| w.enable().disabled()); - } } impl<'a, T: Instance> Drop for SequencePwm<'a, T> { fn drop(&mut self) { let r = T::regs(); - self.stop(); - if let Some(pin) = &self.ch0 { pin.set_low(); pin.conf().reset(); @@ -356,9 +256,9 @@ impl<'a, T: Instance> Drop for SequencePwm<'a, T> { } } -/// Configure an infinite looping sequence for `SequencePwm` +/// Configuration for the PWM as a whole. #[non_exhaustive] -pub struct SequenceConfig { +pub struct Config { /// Selects up mode or up-and-down mode for the counter pub counter_mode: CounterMode, /// Top value to be compared against buffer values @@ -367,6 +267,23 @@ pub struct SequenceConfig { pub prescaler: Prescaler, /// How a sequence is read from RAM and is spread to the compare register pub sequence_load: SequenceLoad, +} + +impl Default for Config { + fn default() -> Config { + Config { + counter_mode: CounterMode::Up, + max_duty: 1000, + prescaler: Prescaler::Div16, + sequence_load: SequenceLoad::Common, + } + } +} + +/// Configuration per sequence +#[non_exhaustive] +#[derive(Clone)] +pub struct SequenceConfig { /// Number of PWM periods to delay between each sequence sample pub refresh: u32, /// Number of PWM periods after the sequence ends before starting the next sequence @@ -376,21 +293,215 @@ pub struct SequenceConfig { impl Default for SequenceConfig { fn default() -> SequenceConfig { SequenceConfig { - counter_mode: CounterMode::Up, - max_duty: 1000, - prescaler: Prescaler::Div16, - sequence_load: SequenceLoad::Common, refresh: 0, end_delay: 0, } } } -/// How many times to run the sequence +/// A composition of a sequence buffer and its configuration. +#[non_exhaustive] +pub struct Sequence<'s> { + /// The words comprising the sequence. Must not exceed 32767 words. + pub words: &'s [u16], + /// Configuration associated with the sequence. + pub config: SequenceConfig, +} + +impl<'s> Sequence<'s> { + pub fn new(words: &'s [u16], config: SequenceConfig) -> Self { + Self { words, config } + } +} + +/// A single sequence that can be started and stopped. +/// Takes at one sequence along with its configuration. +#[non_exhaustive] +pub struct SingleSequencer<'d, 's, T: Instance> { + pub sequencer: Sequencer<'d, 's, T>, +} + +impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { + /// Create a new sequencer + pub fn new(pwm: &'s mut SequencePwm<'d, T>, words: &'s [u16], config: SequenceConfig) -> Self { + Self { + sequencer: Sequencer::new(pwm, Sequence::new(words, config), None), + } + } + + /// Start or restart playback. + #[inline(always)] + pub fn start(&self, times: SingleSequenceMode) -> Result<(), Error> { + let (start_seq, times) = match times { + SingleSequenceMode::Times(n) if n == 1 => (StartSequence::One, SequenceMode::Loop(1)), + SingleSequenceMode::Times(n) if n & 1 == 1 => { + (StartSequence::One, SequenceMode::Loop((n / 2) + 1)) + } + SingleSequenceMode::Times(n) => (StartSequence::Zero, SequenceMode::Loop(n / 2)), + SingleSequenceMode::Infinite => (StartSequence::Zero, SequenceMode::Infinite), + }; + self.sequencer.start(start_seq, times) + } + + /// Stop playback. Disables the peripheral. Does NOT clear the last duty + /// cycle from the pin. Returns any sequences previously provided to + /// `start` so that they may be further mutated. + #[inline(always)] + pub fn stop(&self) { + self.sequencer.stop(); + } +} + +/// A composition of sequences that can be started and stopped. +/// Takes at least one sequence along with its configuration. +/// Optionally takes a second sequence and its configuration. +/// In the case where no second sequence is provided then the first sequence +/// is used. +#[non_exhaustive] +pub struct Sequencer<'d, 's, T: Instance> { + _pwm: &'s mut SequencePwm<'d, T>, + sequence0: Sequence<'s>, + sequence1: Option>, +} + +impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { + /// Create a new double sequence. In the absence of sequence 1, sequence 0 + /// will be used twice in the one loop. + pub fn new( + pwm: &'s mut SequencePwm<'d, T>, + sequence0: Sequence<'s>, + sequence1: Option>, + ) -> Self { + Sequencer { + _pwm: pwm, + sequence0, + sequence1, + } + } + + /// Start or restart playback. The sequence mode applies to both sequences combined as one. + #[inline(always)] + pub fn start(&self, start_seq: StartSequence, times: SequenceMode) -> Result<(), Error> { + let sequence0 = &self.sequence0; + let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0); + + slice_in_ram_or(sequence0.words, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(alt_sequence.words, Error::DMABufferNotInDataMemory)?; + + if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN { + return Err(Error::SequenceTooLong); + } + + if let SequenceMode::Loop(0) = times { + return Err(Error::SequenceTimesAtLeastOne); + } + + let _ = self.stop(); + + let r = T::regs(); + + r.seq0 + .refresh + .write(|w| unsafe { w.bits(sequence0.config.refresh) }); + r.seq0 + .enddelay + .write(|w| unsafe { w.bits(sequence0.config.end_delay) }); + r.seq0 + .ptr + .write(|w| unsafe { w.bits(sequence0.words.as_ptr() as u32) }); + r.seq0 + .cnt + .write(|w| unsafe { w.bits(sequence0.words.len() as u32) }); + + r.seq1 + .refresh + .write(|w| unsafe { w.bits(alt_sequence.config.refresh) }); + r.seq1 + .enddelay + .write(|w| unsafe { w.bits(alt_sequence.config.end_delay) }); + r.seq1 + .ptr + .write(|w| unsafe { w.bits(alt_sequence.words.as_ptr() as u32) }); + r.seq1 + .cnt + .write(|w| unsafe { w.bits(alt_sequence.words.len() as u32) }); + + r.enable.write(|w| w.enable().enabled()); + + // defensive before seqstart + compiler_fence(Ordering::SeqCst); + + let seqstart_index = if start_seq == StartSequence::One { + 1 + } else { + 0 + }; + + match times { + // just the one time, no loop count + SequenceMode::Loop(n) => { + r.loop_.write(|w| unsafe { w.cnt().bits(n) }); + } + // to play infinitely, repeat the sequence one time, then have loops done self trigger seq0 again + SequenceMode::Infinite => { + r.loop_.write(|w| unsafe { w.cnt().bits(0x1) }); + r.shorts.write(|w| w.loopsdone_seqstart0().enabled()); + } + } + + // tasks_seqstart() doesn't exist in all svds so write its bit instead + r.tasks_seqstart[seqstart_index].write(|w| unsafe { w.bits(0x01) }); + + Ok(()) + } + + /// Stop playback. Disables the peripheral. Does NOT clear the last duty + /// cycle from the pin. Returns any sequences previously provided to + /// `start` so that they may be further mutated. + #[inline(always)] + pub fn stop(&self) { + let r = T::regs(); + + r.shorts.reset(); + + compiler_fence(Ordering::SeqCst); + + // tasks_stop() doesn't exist in all svds so write its bit instead + r.tasks_stop.write(|w| unsafe { w.bits(0x01) }); + + r.enable.write(|w| w.enable().disabled()); + } +} + +impl<'d, 's, T: Instance> Drop for Sequencer<'d, 's, T> { + fn drop(&mut self) { + let _ = self.stop(); + } +} + +/// How many times to run a single sequence +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SingleSequenceMode { + /// Run a single sequence n Times total. + Times(u16), + /// Repeat until `stop` is called. + Infinite, +} + +/// Which sequence to start a loop with +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum StartSequence { + /// Start with Sequence 0 + Zero, + /// Start with Sequence 1 + One, +} + +/// How many loops to run two sequences #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SequenceMode { - /// Run sequence n Times total - Times(u16), + /// Run two sequences n loops i.e. (n * (seq0 + seq1.unwrap_or(seq0))) + Loop(u16), /// Repeat until `stop` is called. Infinite, } diff --git a/examples/nrf/src/bin/pwm_double_sequence.rs b/examples/nrf/src/bin/pwm_double_sequence.rs new file mode 100644 index 000000000..269015f4a --- /dev/null +++ b/examples/nrf/src/bin/pwm_double_sequence.rs @@ -0,0 +1,46 @@ +#![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::{ + Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm, Sequencer, + StartSequence, +}; +use embassy_nrf::Peripherals; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let seq_words_0: [u16; 5] = [1000, 250, 100, 50, 0]; + let seq_words_1: [u16; 4] = [50, 100, 250, 1000]; + + let mut config = Config::default(); + config.prescaler = Prescaler::Div128; + // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us + // but say we want to hold the value for 5000ms + // so we want to repeat our value as many times as necessary until 5000ms passes + // want 5000/8 = 625 periods total to occur, so 624 (we get the one period for free remember) + let mut seq_config = SequenceConfig::default(); + seq_config.refresh = 624; + // thus our sequence takes 5 * 5000ms or 25 seconds + + let mut pwm = unwrap!(SequencePwm::new( + p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, + )); + + let sequence_0 = Sequence::new(&seq_words_0, seq_config.clone()); + let sequence_1 = Sequence::new(&seq_words_1, seq_config); + let sequencer = Sequencer::new(&mut pwm, sequence_0, Some(sequence_1)); + unwrap!(sequencer.start(StartSequence::Zero, SequenceMode::Loop(1))); + + // we can abort a sequence if we need to before its complete with pwm.stop() + // or stop is also implicitly called when the pwm peripheral is dropped + // when it goes out of scope + Timer::after(Duration::from_millis(40000)).await; + info!("pwm stopped early!"); +} diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 56c865d1c..f06ea0b19 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -8,34 +8,31 @@ use defmt::*; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; -use embassy_nrf::pwm::{Prescaler, SequenceConfig, SequenceMode, SequencePwm}; +use embassy_nrf::pwm::{ + Config, Prescaler, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, +}; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let seq_values_1: [u16; 5] = [1000, 250, 100, 50, 0]; - let seq_values_2: [u16; 5] = [0, 50, 100, 250, 1000]; + let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; - let mut config = SequenceConfig::default(); + let mut config = Config::default(); config.prescaler = Prescaler::Div128; // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us // but say we want to hold the value for 5000ms // so we want to repeat our value as many times as necessary until 5000ms passes // want 5000/8 = 625 periods total to occur, so 624 (we get the one period for free remember) - config.refresh = 624; + let mut seq_config = SequenceConfig::default(); + seq_config.refresh = 624; // thus our sequence takes 5 * 5000ms or 25 seconds let mut pwm = unwrap!(SequencePwm::new( p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let _ = pwm.start(&seq_values_1, SequenceMode::Infinite); - info!("pwm started!"); - - Timer::after(Duration::from_millis(20000)).await; - info!("pwm starting with another sequence!"); - - let _ = pwm.start(&seq_values_2, SequenceMode::Infinite); + let sequencer = SingleSequencer::new(&mut pwm, &seq_words, seq_config); + unwrap!(sequencer.start(SingleSequenceMode::Times(1))); // we can abort a sequence if we need to before its complete with pwm.stop() // or stop is also implicitly called when the pwm peripheral is dropped diff --git a/examples/nrf/src/bin/pwm_sequence_ppi.rs b/examples/nrf/src/bin/pwm_sequence_ppi.rs index f03c5716a..c25c5e10d 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -11,26 +11,28 @@ use embassy::executor::Spawner; use embassy_nrf::gpio::{Input, NoPin, Pull}; use embassy_nrf::gpiote::{InputChannel, InputChannelPolarity}; use embassy_nrf::ppi::Ppi; -use embassy_nrf::pwm::{Prescaler, SequenceConfig, SequenceMode, SequencePwm}; +use embassy_nrf::pwm::{ + Config, Prescaler, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, +}; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let seq_values: [u16; 5] = [1000, 250, 100, 50, 0]; + let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; - let mut config = SequenceConfig::default(); + let mut config = Config::default(); config.prescaler = Prescaler::Div128; // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us // but say we want to hold the value for 250ms 250ms/8 = 31.25 periods // so round to 31 - 1 (we get the one period for free remember) // thus our sequence takes 5 * 250ms or 1.25 seconds - config.refresh = 30; + let mut seq_config = SequenceConfig::default(); + seq_config.refresh = 30; let mut pwm = unwrap!(SequencePwm::new( p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let _ = pwm.start(&seq_values, SequenceMode::Times(1)); // pwm.stop() deconfigures pins, and then the task_start_seq0 task cant work // so its going to have to start running in order load the configuration @@ -51,6 +53,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { let start = unsafe { pwm.task_start_seq0() }; let stop = unsafe { pwm.task_stop() }; + let sequencer = SingleSequencer::new(&mut pwm, &seq_words, seq_config); + unwrap!(sequencer.start(SingleSequenceMode::Infinite)); + let mut ppi = Ppi::new_one_to_one(p.PPI_CH1, button1.event_in(), start); ppi.enable(); diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs new file mode 100644 index 000000000..d1a027a7e --- /dev/null +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -0,0 +1,80 @@ +#![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::{ + Config, Prescaler, SequenceConfig, SequenceLoad, SequencePwm, SingleSequenceMode, + SingleSequencer, +}; +use embassy_nrf::Peripherals; + +// WS2812B LED light demonstration. Drives just one light. +// The following reference on WS2812B may be of use: +// https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf. +// This demo lights up a single LED in blue. It then proceeds +// to pulsate the LED rapidly. + +// In the following declarations, setting the high bit tells the PWM +// to reverse polarity, which is what the WS2812B expects. + +const T1H: u16 = 0x8000 | 13; // Duty = 13/20 ticks (0.8us/1.25us) for a 1 +const T0H: u16 = 0x8000 | 7; // Duty 7/20 ticks (0.4us/1.25us) for a 0 +const RES: u16 = 0x8000; + +// Provides data to a WS2812b (Neopixel) LED and makes it go blue. The data +// line is assumed to be P1_05. +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let mut config = Config::default(); + config.sequence_load = SequenceLoad::Common; + config.prescaler = Prescaler::Div1; + config.max_duty = 20; // 1.25us (1s / 16Mhz * 20) + let mut pwm = unwrap!(SequencePwm::new( + p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, + )); + + // Declare the bits of 24 bits in a buffer we'll be + // mutating later. + let mut seq_words = [ + T0H, T0H, T0H, T0H, T0H, T0H, T0H, T0H, // G + T0H, T0H, T0H, T0H, T0H, T0H, T0H, T0H, // R + T1H, T1H, T1H, T1H, T1H, T1H, T1H, T1H, // B + RES, + ]; + let mut seq_config = SequenceConfig::default(); + seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; + + let mut color_bit = 16; + let mut bit_value = T0H; + + loop { + let sequences = SingleSequencer::new(&mut pwm, &seq_words, seq_config.clone()); + unwrap!(sequences.start(SingleSequenceMode::Times(1))); + + Timer::after(Duration::from_millis(50)).await; + + if bit_value == T0H { + if color_bit == 20 { + bit_value = T1H; + } else { + color_bit += 1; + } + } else { + if color_bit == 16 { + bit_value = T0H; + } else { + color_bit -= 1; + } + } + + drop(sequences); + + seq_words[color_bit] = bit_value; + } +}