From 47aeab152fff59e64d8244475dfbec338e6f98e5 Mon Sep 17 00:00:00 2001 From: huntc Date: Tue, 25 Jan 2022 18:06:42 +1100 Subject: [PATCH 01/18] PWM WS2812B example and per sequence config Demonstrates how to set the colour of a WS2812B to blue using PWM, and the use of multiple sequences along with their own config. This required an API change. --- embassy-nrf/src/pwm.rs | 87 ++++++++++++++------ examples/nrf/src/bin/pwm_sequence.rs | 23 ++++-- examples/nrf/src/bin/pwm_sequence_ppi.rs | 9 +- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 60 ++++++++++++++ 4 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 examples/nrf/src/bin/pwm_sequence_ws2812b.rs diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index a77cd633..21b450b1 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,12 +128,28 @@ impl<'d, T: Instance> SequencePwm<'d, T> { }) } - /// Start or restart playback + /// Start or restart playback. Takes at least one sequence along with its + /// configuration. Optionally takes a second sequence and/or its configuration. + /// In the case where no second sequence is provided then the first sequence + /// is used. In the case where no second sequence configuration is supplied, + /// the first sequence configuration is used. The sequence mode applies to both + /// sequences combined as one. #[inline(always)] - pub fn start(&mut self, sequence: &'d [u16], times: SequenceMode) -> Result<(), Error> { - slice_in_ram_or(sequence, Error::DMABufferNotInDataMemory)?; + pub fn start( + &mut self, + sequence0: &'d [u16], + sequence_config0: SequenceConfig, + sequence1: Option<&'d [u16]>, + sequence_config1: Option, + times: SequenceMode, + ) -> Result<(), Error> { + let alt_sequence = sequence1.unwrap_or(sequence0); + let alt_sequence_config = (&sequence_config1).as_ref().unwrap_or(&sequence_config0); - if sequence.len() > 32767 { + slice_in_ram_or(sequence0, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(alt_sequence, Error::DMABufferNotInDataMemory)?; + + if sequence0.len() > MAX_SEQUENCE_LEN || alt_sequence.len() > MAX_SEQUENCE_LEN { return Err(Error::SequenceTooLong); } @@ -153,19 +161,31 @@ impl<'d, T: Instance> SequencePwm<'d, T> { let r = T::regs(); + r.seq0 + .refresh + .write(|w| unsafe { w.bits(sequence_config0.refresh) }); + r.seq0 + .enddelay + .write(|w| unsafe { w.bits(sequence_config0.end_delay) }); r.seq0 .ptr - .write(|w| unsafe { w.bits(sequence.as_ptr() as u32) }); + .write(|w| unsafe { w.bits(sequence0.as_ptr() as u32) }); r.seq0 .cnt - .write(|w| unsafe { w.bits(sequence.len() as u32) }); + .write(|w| unsafe { w.bits(sequence0.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(sequence.as_ptr() as u32) }); + .write(|w| unsafe { w.bits(alt_sequence.as_ptr() as u32) }); r.seq1 .cnt - .write(|w| unsafe { w.bits(sequence.len() as u32) }); + .write(|w| unsafe { w.bits(alt_sequence.len() as u32) }); r.enable.write(|w| w.enable().enabled()); @@ -356,9 +376,8 @@ impl<'a, T: Instance> Drop for SequencePwm<'a, T> { } } -/// Configure an infinite looping sequence for `SequencePwm` #[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 +386,21 @@ 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, + } + } +} + +#[non_exhaustive] +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,10 +410,6 @@ 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, } @@ -389,7 +419,12 @@ impl Default for SequenceConfig { /// How many times to run the sequence #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SequenceMode { - /// Run sequence n Times total + /// Run sequence n Times total. + /// 1 = Run sequence 0 once + /// 2 = Run sequence 0 and then sequence 1 + /// 3 to 4 = Run sequence 0, sequence 1, sequence 0 and then sequence 1 + /// 5 to 6 = Run sequence 0, sequence 1, sequence 0, sequence 1, sequence 0 and then sequence 1 + /// i.e the when >= 2 the loop count is determined by dividing by 2 and rounding up Times(u16), /// Repeat until `stop` is called. Infinite, diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 56c865d1..2dcbc747 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -8,7 +8,7 @@ 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, SequenceMode, SequencePwm}; use embassy_nrf::Peripherals; #[embassy::main] @@ -16,26 +16,39 @@ 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 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 = Config::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); + let _ = pwm.start( + &seq_values_1, + seq_config, + None, + None, + SeqSequenceMode::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 _ = pwm.start( + &seq_values_2, + seq_config, + None, + None, + SequenceMode::Infinite, + ); // 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 f03c5716..9523671a 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -11,26 +11,27 @@ 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, SequenceMode, SequencePwm}; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { let seq_values: [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)); + let _ = pwm.start(&seq_values, seq_config, None, None, SequenceMode::Infinite); // 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 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 00000000..01afd043 --- /dev/null +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -0,0 +1,60 @@ +#![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, SequenceMode, SequencePwm, +}; +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 + +// 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) { + // Declare the bits of 24 bits + let mut blue_seq: [u16; 8 * 3] = [ + 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 + ]; + let reset_seq = [RES; 1]; + + 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, + )); + + let blue_seq_config = SequenceConfig::default(); + let mut reset_seq_config = SequenceConfig::default(); + reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES + unwrap!(pwm.start( + &blue_seq, + blue_seq_config, + Some(&reset_seq), + Some(reset_seq_config), + SequenceMode::Times(2) + )); + + Timer::after(Duration::from_millis(20000)).await; + info!("Program stopped"); +} From 12ce02457438c1dd1566f3f3c43537ae4f54f699 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 28 Jan 2022 13:38:20 +1100 Subject: [PATCH 02/18] Make the sequence a little nicer to pass around --- embassy-nrf/src/pwm.rs | 46 ++++++++++++-------- examples/nrf/src/bin/pwm_sequence.rs | 18 +++----- examples/nrf/src/bin/pwm_sequence_ppi.rs | 10 +++-- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 22 ++++------ 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 21b450b1..97c02edd 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -137,19 +137,16 @@ impl<'d, T: Instance> SequencePwm<'d, T> { #[inline(always)] pub fn start( &mut self, - sequence0: &'d [u16], - sequence_config0: SequenceConfig, - sequence1: Option<&'d [u16]>, - sequence_config1: Option, + sequence0: Sequence<'d>, + sequence1: Option>, times: SequenceMode, ) -> Result<(), Error> { - let alt_sequence = sequence1.unwrap_or(sequence0); - let alt_sequence_config = (&sequence_config1).as_ref().unwrap_or(&sequence_config0); + let alt_sequence = sequence1.as_ref().unwrap_or(&sequence0); - slice_in_ram_or(sequence0, Error::DMABufferNotInDataMemory)?; - slice_in_ram_or(alt_sequence, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(sequence0.words, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(alt_sequence.words, Error::DMABufferNotInDataMemory)?; - if sequence0.len() > MAX_SEQUENCE_LEN || alt_sequence.len() > MAX_SEQUENCE_LEN { + if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN { return Err(Error::SequenceTooLong); } @@ -163,29 +160,29 @@ impl<'d, T: Instance> SequencePwm<'d, T> { r.seq0 .refresh - .write(|w| unsafe { w.bits(sequence_config0.refresh) }); + .write(|w| unsafe { w.bits(sequence0.config.refresh) }); r.seq0 .enddelay - .write(|w| unsafe { w.bits(sequence_config0.end_delay) }); + .write(|w| unsafe { w.bits(sequence0.config.end_delay) }); r.seq0 .ptr - .write(|w| unsafe { w.bits(sequence0.as_ptr() as u32) }); + .write(|w| unsafe { w.bits(sequence0.words.as_ptr() as u32) }); r.seq0 .cnt - .write(|w| unsafe { w.bits(sequence0.len() as u32) }); + .write(|w| unsafe { w.bits(sequence0.words.len() as u32) }); r.seq1 .refresh - .write(|w| unsafe { w.bits(alt_sequence_config.refresh) }); + .write(|w| unsafe { w.bits(alt_sequence.config.refresh) }); r.seq1 .enddelay - .write(|w| unsafe { w.bits(alt_sequence_config.end_delay) }); + .write(|w| unsafe { w.bits(alt_sequence.config.end_delay) }); r.seq1 .ptr - .write(|w| unsafe { w.bits(alt_sequence.as_ptr() as u32) }); + .write(|w| unsafe { w.bits(alt_sequence.words.as_ptr() as u32) }); r.seq1 .cnt - .write(|w| unsafe { w.bits(alt_sequence.len() as u32) }); + .write(|w| unsafe { w.bits(alt_sequence.words.len() as u32) }); r.enable.write(|w| w.enable().enabled()); @@ -400,6 +397,7 @@ impl Default for Config { } #[non_exhaustive] +#[derive(Clone)] pub struct SequenceConfig { /// Number of PWM periods to delay between each sequence sample pub refresh: u32, @@ -416,6 +414,20 @@ impl Default for SequenceConfig { } } +#[non_exhaustive] +pub struct Sequence<'d> { + /// The words comprising the sequence. Must not exceed 32767 words. + pub words: &'d [u16], + /// Configuration associated with the sequence. + pub config: SequenceConfig, +} + +impl<'d> Sequence<'d> { + pub fn new(words: &'d [u16], config: SequenceConfig) -> Self { + Self { words, config } + } +} + /// How many times to run the sequence #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SequenceMode { diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 2dcbc747..78787d53 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -8,13 +8,13 @@ use defmt::*; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; -use embassy_nrf::pwm::{Config, Prescaler, SequenceMode, SequencePwm}; +use embassy_nrf::pwm::{Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm}; 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_1: [u16; 5] = [1000, 250, 100, 50, 0]; + let seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -22,7 +22,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { // 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 = Config::default(); + let mut seq_config = SequenceConfig::default(); seq_config.refresh = 624; // thus our sequence takes 5 * 5000ms or 25 seconds @@ -30,11 +30,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); let _ = pwm.start( - &seq_values_1, - seq_config, + Sequence::new(&seq_words_1, seq_config.clone()), None, - None, - SeqSequenceMode::Infinite, + SequenceMode::Infinite, ); info!("pwm started!"); @@ -43,9 +41,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { info!("pwm starting with another sequence!"); let _ = pwm.start( - &seq_values_2, - seq_config, - None, + Sequence::new(&seq_words_2, seq_config), None, SequenceMode::Infinite, ); diff --git a/examples/nrf/src/bin/pwm_sequence_ppi.rs b/examples/nrf/src/bin/pwm_sequence_ppi.rs index 9523671a..c80820f8 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -11,12 +11,12 @@ 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::{Config, Prescaler, SequenceConfig, SequenceMode, SequencePwm}; +use embassy_nrf::pwm::{Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm}; 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 = Config::default(); config.prescaler = Prescaler::Div128; @@ -31,7 +31,11 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let _ = pwm.start(&seq_values, seq_config, None, None, SequenceMode::Infinite); + let _ = pwm.start( + Sequence::new(&seq_words, seq_config), + None, + SequenceMode::Infinite, + ); // 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 diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 01afd043..2fb37015 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -9,7 +9,7 @@ use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ - Config, Prescaler, SequenceConfig, SequenceLoad, SequenceMode, SequencePwm, + Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequenceMode, SequencePwm, }; use embassy_nrf::Peripherals; @@ -29,12 +29,17 @@ const RES: u16 = 0x8000; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { // Declare the bits of 24 bits - let mut blue_seq: [u16; 8 * 3] = [ + let blue_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 ]; - let reset_seq = [RES; 1]; + let blue_seq = Sequence::new(&blue_seq_words, SequenceConfig::default()); + + let reset_seq_words = [RES; 1]; + let mut reset_seq_config = SequenceConfig::default(); + reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; + let reset_seq = Sequence::new(&reset_seq_words, reset_seq_config); let mut config = Config::default(); config.sequence_load = SequenceLoad::Common; @@ -44,16 +49,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, )); - let blue_seq_config = SequenceConfig::default(); - let mut reset_seq_config = SequenceConfig::default(); - reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES - unwrap!(pwm.start( - &blue_seq, - blue_seq_config, - Some(&reset_seq), - Some(reset_seq_config), - SequenceMode::Times(2) - )); + unwrap!(pwm.start(blue_seq, Some(reset_seq), SequenceMode::Times(2))); Timer::after(Duration::from_millis(20000)).await; info!("Program stopped"); From 8e9f4488662699877cf10335991554eb5407f940 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 28 Jan 2022 13:43:36 +1100 Subject: [PATCH 03/18] Doc tidying --- embassy-nrf/src/pwm.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 97c02edd..41dcce04 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -131,9 +131,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Start or restart playback. Takes at least one sequence along with its /// configuration. Optionally takes a second sequence and/or its configuration. /// In the case where no second sequence is provided then the first sequence - /// is used. In the case where no second sequence configuration is supplied, - /// the first sequence configuration is used. The sequence mode applies to both - /// sequences combined as one. + /// is used. The sequence mode applies to both sequences combined as one. #[inline(always)] pub fn start( &mut self, From 9ac52a768bbcd4bc8b753c64805fc23906b2c91f Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 28 Jan 2022 16:21:53 +1100 Subject: [PATCH 04/18] Now permits sequences to be mutated subsequently --- embassy-nrf/src/pwm.rs | 24 +++++++---- examples/nrf/src/bin/pwm_sequence.rs | 8 ++-- examples/nrf/src/bin/pwm_sequence_ppi.rs | 4 +- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 43 ++++++++++++++++---- 4 files changed, 58 insertions(+), 21 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 41dcce04..94dfdeda 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -31,6 +31,8 @@ pub struct SequencePwm<'d, T: Instance> { ch1: Option, ch2: Option, ch3: Option, + sequence0: Option>, + sequence1: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -125,11 +127,13 @@ impl<'d, T: Instance> SequencePwm<'d, T> { ch1: ch1.degrade_optional(), ch2: ch2.degrade_optional(), ch3: ch3.degrade_optional(), + sequence0: None, + sequence1: None, }) } /// Start or restart playback. Takes at least one sequence along with its - /// configuration. Optionally takes a second sequence and/or its configuration. + /// configuration. Optionally takes a second sequence and its configuration. /// In the case where no second sequence is provided then the first sequence /// is used. The sequence mode applies to both sequences combined as one. #[inline(always)] @@ -152,7 +156,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { return Err(Error::SequenceTimesAtLeastOne); } - self.stop(); + let _ = self.stop(); let r = T::regs(); @@ -222,6 +226,9 @@ impl<'d, T: Instance> SequencePwm<'d, T> { } } + self.sequence0 = Some(sequence0); + self.sequence1 = sequence1; + Ok(()) } @@ -326,9 +333,10 @@ impl<'d, T: Instance> SequencePwm<'d, T> { } /// Stop playback. Disables the peripheral. Does NOT clear the last duty - /// cycle from the pin. + /// cycle from the pin. Returns any sequences previously provided to + /// `start` so that they may be further mutated. #[inline(always)] - pub fn stop(&self) { + pub fn stop(&mut self) -> (Option>, Option>) { let r = T::regs(); r.shorts.reset(); @@ -339,6 +347,8 @@ impl<'d, T: Instance> SequencePwm<'d, T> { r.tasks_stop.write(|w| unsafe { w.bits(0x01) }); r.enable.write(|w| w.enable().disabled()); + + (self.sequence0.take(), self.sequence1.take()) } } @@ -346,7 +356,7 @@ impl<'a, T: Instance> Drop for SequencePwm<'a, T> { fn drop(&mut self) { let r = T::regs(); - self.stop(); + let _ = self.stop(); if let Some(pin) = &self.ch0 { pin.set_low(); @@ -415,13 +425,13 @@ impl Default for SequenceConfig { #[non_exhaustive] pub struct Sequence<'d> { /// The words comprising the sequence. Must not exceed 32767 words. - pub words: &'d [u16], + pub words: &'d mut [u16], /// Configuration associated with the sequence. pub config: SequenceConfig, } impl<'d> Sequence<'d> { - pub fn new(words: &'d [u16], config: SequenceConfig) -> Self { + pub fn new(words: &'d mut [u16], config: SequenceConfig) -> Self { Self { words, config } } } diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 78787d53..6fb861e8 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -13,8 +13,8 @@ use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let seq_words_1: [u16; 5] = [1000, 250, 100, 50, 0]; - let seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; + let mut seq_words_1: [u16; 5] = [1000, 250, 100, 50, 0]; + let mut seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -30,7 +30,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); let _ = pwm.start( - Sequence::new(&seq_words_1, seq_config.clone()), + Sequence::new(&mut seq_words_1, seq_config.clone()), None, SequenceMode::Infinite, ); @@ -41,7 +41,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { info!("pwm starting with another sequence!"); let _ = pwm.start( - Sequence::new(&seq_words_2, seq_config), + Sequence::new(&mut seq_words_2, seq_config), None, SequenceMode::Infinite, ); diff --git a/examples/nrf/src/bin/pwm_sequence_ppi.rs b/examples/nrf/src/bin/pwm_sequence_ppi.rs index c80820f8..f5d734bb 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -16,7 +16,7 @@ use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; + let mut seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -32,7 +32,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { )); let _ = pwm.start( - Sequence::new(&seq_words, seq_config), + Sequence::new(&mut seq_words, seq_config), None, SequenceMode::Infinite, ); diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 2fb37015..0ce79cbe 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -15,7 +15,9 @@ 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 +// 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. @@ -29,17 +31,17 @@ const RES: u16 = 0x8000; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { // Declare the bits of 24 bits - let blue_seq_words = [ + let mut color_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 ]; - let blue_seq = Sequence::new(&blue_seq_words, SequenceConfig::default()); + let color_seq = Sequence::new(&mut color_seq_words, SequenceConfig::default()); - let reset_seq_words = [RES; 1]; + let mut reset_seq_words = [RES; 1]; let mut reset_seq_config = SequenceConfig::default(); reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; - let reset_seq = Sequence::new(&reset_seq_words, reset_seq_config); + let reset_seq = Sequence::new(&mut reset_seq_words, reset_seq_config); let mut config = Config::default(); config.sequence_load = SequenceLoad::Common; @@ -49,8 +51,33 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, )); - unwrap!(pwm.start(blue_seq, Some(reset_seq), SequenceMode::Times(2))); + unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); - Timer::after(Duration::from_millis(20000)).await; - info!("Program stopped"); + Timer::after(Duration::from_millis(1000)).await; + + let mut color_bit = 16; + let mut bit_value = T0H; + + loop { + if let (Some(color_seq), Some(reset_seq)) = pwm.stop() { + color_seq.words[color_bit] = bit_value; + unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); + } + + 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; + } + } + } } From 482389a6911d8d3505872e6ad03d5b0af565eaf9 Mon Sep 17 00:00:00 2001 From: huntc Date: Sat, 29 Jan 2022 15:26:31 +1100 Subject: [PATCH 05/18] Own the sequence buffer This approach owns the sequence buffers which, while introducing an extra move, it eliminates the need to guard the lifetime of the sequence buffer. Given ownership, the buffer will be retained until the PWM sequence task is stopped. --- embassy-nrf/src/pwm.rs | 108 +++++++++++++------ examples/nrf/src/bin/pwm_sequence.rs | 22 ++-- examples/nrf/src/bin/pwm_sequence_ppi.rs | 13 +-- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 33 +++--- 4 files changed, 108 insertions(+), 68 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 94dfdeda..9146160c 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -25,14 +25,14 @@ pub struct SimplePwm<'d, T: Instance> { /// SequencePwm allows you to offload the updating of a sequence of duty cycles /// to up to four channels, as well as repeat that sequence n times. -pub struct SequencePwm<'d, T: Instance> { +pub struct SequencePwm<'d, T: Instance, const S0: usize, const S1: usize> { phantom: PhantomData<&'d mut T>, ch0: Option, ch1: Option, ch2: Option, ch3: Option, - sequence0: Option>, - sequence1: Option>, + sequence0: Option>, + sequence1: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -43,13 +43,17 @@ pub enum Error { SequenceTooLong, /// Min Sequence count is 1 SequenceTimesAtLeastOne, + /// Sequence 0 is required, Sequence 1 is NOT required + SequenceTimesRequireSeq0Only, + /// Sequence 0 is required, Sequence 1 is required + SequenceTimesRequireBothSeq0AndSeq1, /// EasyDMA can only read from data memory, read only buffers in flash will fail. DMABufferNotInDataMemory, } const MAX_SEQUENCE_LEN: usize = 32767; -impl<'d, T: Instance> SequencePwm<'d, T> { +impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S1> { /// Creates the interface to a `SequencePwm`. /// /// Must be started by calling `start` @@ -68,6 +72,10 @@ impl<'d, T: Instance> SequencePwm<'d, T> { ch3: impl Unborrow + 'd, config: Config, ) -> Result { + if S0 > MAX_SEQUENCE_LEN || S1 > MAX_SEQUENCE_LEN { + return Err(Error::SequenceTooLong); + } + unborrow!(ch0, ch1, ch2, ch3); let r = T::regs(); @@ -133,31 +141,49 @@ impl<'d, T: Instance> SequencePwm<'d, T> { } /// Start or restart playback. 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. The sequence mode applies to both sequences combined as one. + /// configuration. A second sequence must be provided when looping i.e. + /// when the sequence mode is anything other than Times(1). #[inline(always)] pub fn start( &mut self, - sequence0: Sequence<'d>, - sequence1: Option>, + sequence0: Sequence, + sequence1: Sequence, times: SequenceMode, ) -> Result<(), Error> { - let alt_sequence = sequence1.as_ref().unwrap_or(&sequence0); + slice_in_ram_or(&sequence0.words, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(&sequence1.words, Error::DMABufferNotInDataMemory)?; - 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 { + let seq_0_word_count = sequence0.word_count.unwrap_or(S0); + let seq_1_word_count = sequence0.word_count.unwrap_or(S1); + if seq_0_word_count > S0 || seq_1_word_count > S1 { return Err(Error::SequenceTooLong); } - if let SequenceMode::Times(0) = times { - return Err(Error::SequenceTimesAtLeastOne); + match times { + SequenceMode::Times(0) => return Err(Error::SequenceTimesAtLeastOne), + SequenceMode::Times(1) if seq_0_word_count == 0 || seq_1_word_count != 0 => { + return Err(Error::SequenceTimesRequireSeq0Only) + } + SequenceMode::Times(1) => (), + SequenceMode::Times(_) | SequenceMode::Infinite + if seq_0_word_count == 0 || seq_1_word_count == 0 => + { + return Err(Error::SequenceTimesRequireBothSeq0AndSeq1) + } + SequenceMode::Times(_) | SequenceMode::Infinite => (), } let _ = self.stop(); + // We now own these sequences and they will be moved. We want + // the peripheral to point at the right bits of memory hence + // moving the sequences early. + self.sequence0 = Some(sequence0); + self.sequence1 = Some(sequence1); + + let sequence0 = self.sequence0.as_ref().unwrap(); + let sequence1 = self.sequence1.as_ref().unwrap(); + let r = T::regs(); r.seq0 @@ -171,20 +197,20 @@ impl<'d, T: Instance> SequencePwm<'d, T> { .write(|w| unsafe { w.bits(sequence0.words.as_ptr() as u32) }); r.seq0 .cnt - .write(|w| unsafe { w.bits(sequence0.words.len() as u32) }); + .write(|w| unsafe { w.bits(seq_0_word_count as u32) }); r.seq1 .refresh - .write(|w| unsafe { w.bits(alt_sequence.config.refresh) }); + .write(|w| unsafe { w.bits(sequence1.config.refresh) }); r.seq1 .enddelay - .write(|w| unsafe { w.bits(alt_sequence.config.end_delay) }); + .write(|w| unsafe { w.bits(sequence1.config.end_delay) }); r.seq1 .ptr - .write(|w| unsafe { w.bits(alt_sequence.words.as_ptr() as u32) }); + .write(|w| unsafe { w.bits(sequence1.words.as_ptr() as u32) }); r.seq1 .cnt - .write(|w| unsafe { w.bits(alt_sequence.words.len() as u32) }); + .write(|w| unsafe { w.bits(seq_1_word_count as u32) }); r.enable.write(|w| w.enable().enabled()); @@ -226,9 +252,6 @@ impl<'d, T: Instance> SequencePwm<'d, T> { } } - self.sequence0 = Some(sequence0); - self.sequence1 = sequence1; - Ok(()) } @@ -336,7 +359,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// cycle from the pin. Returns any sequences previously provided to /// `start` so that they may be further mutated. #[inline(always)] - pub fn stop(&mut self) -> (Option>, Option>) { + pub fn stop(&mut self) -> (Option>, Option>) { let r = T::regs(); r.shorts.reset(); @@ -352,7 +375,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { } } -impl<'a, T: Instance> Drop for SequencePwm<'a, T> { +impl<'a, T: Instance, const S0: usize, const S1: usize> Drop for SequencePwm<'a, T, S0, S1> { fn drop(&mut self) { let r = T::regs(); @@ -381,6 +404,7 @@ impl<'a, T: Instance> Drop for SequencePwm<'a, T> { } } +/// Configuration for the PWM as a whole. #[non_exhaustive] pub struct Config { /// Selects up mode or up-and-down mode for the counter @@ -404,6 +428,7 @@ impl Default for Config { } } +/// Configuration per sequence #[non_exhaustive] #[derive(Clone)] pub struct SequenceConfig { @@ -422,20 +447,39 @@ impl Default for SequenceConfig { } } +/// A composition of a sequence buffer and its configuration. #[non_exhaustive] -pub struct Sequence<'d> { +#[derive(Clone)] +pub struct Sequence { /// The words comprising the sequence. Must not exceed 32767 words. - pub words: &'d mut [u16], + pub words: [u16; S], + /// The count of words to use. If None the S will be used. + pub word_count: Option, /// Configuration associated with the sequence. pub config: SequenceConfig, } -impl<'d> Sequence<'d> { - pub fn new(words: &'d mut [u16], config: SequenceConfig) -> Self { - Self { words, config } +impl Sequence { + pub const fn new(words: [u16; S], config: SequenceConfig) -> Self { + Self { + words, + word_count: None, + config, + } } } +/// Declares an empty sequence which will cause it to be disabled. +/// Note that any looping i.e. !Times(1), will require a second +/// sequence given the way the PWM peripheral works. +pub const EMPTY_SEQ: Sequence<0> = Sequence::new( + [], + SequenceConfig { + refresh: 0, + end_delay: 0, + }, +); + /// How many times to run the sequence #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SequenceMode { @@ -446,7 +490,7 @@ pub enum SequenceMode { /// 5 to 6 = Run sequence 0, sequence 1, sequence 0, sequence 1, sequence 0 and then sequence 1 /// i.e the when >= 2 the loop count is determined by dividing by 2 and rounding up Times(u16), - /// Repeat until `stop` is called. + /// Repeat until `stop` is called. Both sequences must be provided. Infinite, } diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 6fb861e8..6fe957d2 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -8,14 +8,13 @@ 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}; +use embassy_nrf::pwm::{ + Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm, EMPTY_SEQ, +}; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let mut seq_words_1: [u16; 5] = [1000, 250, 100, 50, 0]; - let mut seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; - let mut config = Config::default(); config.prescaler = Prescaler::Div128; // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us @@ -26,25 +25,20 @@ async fn main(_spawner: Spawner, p: Peripherals) { seq_config.refresh = 624; // thus our sequence takes 5 * 5000ms or 25 seconds + let seq_1 = Sequence::new([1000, 250, 100, 50, 0], seq_config.clone()); + let seq_2 = Sequence::new([0, 50, 100, 250, 1000], seq_config); + let mut pwm = unwrap!(SequencePwm::new( p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let _ = pwm.start( - Sequence::new(&mut seq_words_1, seq_config.clone()), - None, - SequenceMode::Infinite, - ); + unwrap!(pwm.start(seq_1, EMPTY_SEQ, SequenceMode::Times(1))); info!("pwm started!"); Timer::after(Duration::from_millis(20000)).await; info!("pwm starting with another sequence!"); - let _ = pwm.start( - Sequence::new(&mut seq_words_2, seq_config), - None, - SequenceMode::Infinite, - ); + unwrap!(pwm.start(seq_2, EMPTY_SEQ, SequenceMode::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 f5d734bb..4883222a 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -16,7 +16,7 @@ use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let mut seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; + let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -31,11 +31,12 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let _ = pwm.start( - Sequence::new(&mut seq_words, seq_config), - None, - SequenceMode::Infinite, - ); + // If we loop in any way i.e. not Times(1), then we must provide + // the PWM peripheral with two sequences. + let seq_0 = Sequence::new(seq_words, seq_config); + let seq_1 = seq_0.clone(); + + unwrap!(pwm.start(seq_0, seq_1, SequenceMode::Infinite)); // 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 diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 0ce79cbe..8acb209c 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -30,19 +30,6 @@ const RES: u16 = 0x8000; // line is assumed to be P1_05. #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - // Declare the bits of 24 bits - let mut color_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 - ]; - let color_seq = Sequence::new(&mut color_seq_words, SequenceConfig::default()); - - let mut reset_seq_words = [RES; 1]; - let mut reset_seq_config = SequenceConfig::default(); - reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; - let reset_seq = Sequence::new(&mut reset_seq_words, reset_seq_config); - let mut config = Config::default(); config.sequence_load = SequenceLoad::Common; config.prescaler = Prescaler::Div1; @@ -51,7 +38,21 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, )); - unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); + // Declare the bits of 24 bits + let color_seq = Sequence::new( + [ + 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 + ], + SequenceConfig::default(), + ); + + let mut reset_seq_config = SequenceConfig::default(); + reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; + let reset_seq = Sequence::new([RES], reset_seq_config); + + unwrap!(pwm.start(color_seq, reset_seq, SequenceMode::Times(2))); Timer::after(Duration::from_millis(1000)).await; @@ -59,9 +60,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut bit_value = T0H; loop { - if let (Some(color_seq), Some(reset_seq)) = pwm.stop() { + if let (Some(mut color_seq), Some(reset_seq)) = pwm.stop() { color_seq.words[color_bit] = bit_value; - unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); + unwrap!(pwm.start(color_seq, reset_seq, SequenceMode::Times(2))); } Timer::after(Duration::from_millis(50)).await; From 1c67bd46433734d9280e976da33975cf5beb773e Mon Sep 17 00:00:00 2001 From: huntc Date: Sun, 30 Jan 2022 16:21:23 +1100 Subject: [PATCH 06/18] Revert "Own the sequence buffer" This reverts commit 482389a6911d8d3505872e6ad03d5b0af565eaf9. --- embassy-nrf/src/pwm.rs | 108 ++++++------------- examples/nrf/src/bin/pwm_sequence.rs | 22 ++-- examples/nrf/src/bin/pwm_sequence_ppi.rs | 13 ++- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 33 +++--- 4 files changed, 68 insertions(+), 108 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 9146160c..94dfdeda 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -25,14 +25,14 @@ pub struct SimplePwm<'d, T: Instance> { /// SequencePwm allows you to offload the updating of a sequence of duty cycles /// to up to four channels, as well as repeat that sequence n times. -pub struct SequencePwm<'d, T: Instance, const S0: usize, const S1: usize> { +pub struct SequencePwm<'d, T: Instance> { phantom: PhantomData<&'d mut T>, ch0: Option, ch1: Option, ch2: Option, ch3: Option, - sequence0: Option>, - sequence1: Option>, + sequence0: Option>, + sequence1: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -43,17 +43,13 @@ pub enum Error { SequenceTooLong, /// Min Sequence count is 1 SequenceTimesAtLeastOne, - /// Sequence 0 is required, Sequence 1 is NOT required - SequenceTimesRequireSeq0Only, - /// Sequence 0 is required, Sequence 1 is required - SequenceTimesRequireBothSeq0AndSeq1, /// EasyDMA can only read from data memory, read only buffers in flash will fail. DMABufferNotInDataMemory, } const MAX_SEQUENCE_LEN: usize = 32767; -impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S1> { +impl<'d, T: Instance> SequencePwm<'d, T> { /// Creates the interface to a `SequencePwm`. /// /// Must be started by calling `start` @@ -72,10 +68,6 @@ impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S ch3: impl Unborrow + 'd, config: Config, ) -> Result { - if S0 > MAX_SEQUENCE_LEN || S1 > MAX_SEQUENCE_LEN { - return Err(Error::SequenceTooLong); - } - unborrow!(ch0, ch1, ch2, ch3); let r = T::regs(); @@ -141,49 +133,31 @@ impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S } /// Start or restart playback. Takes at least one sequence along with its - /// configuration. A second sequence must be provided when looping i.e. - /// when the sequence mode is anything other than Times(1). + /// configuration. Optionally takes a second sequence and its configuration. + /// In the case where no second sequence is provided then the first sequence + /// is used. The sequence mode applies to both sequences combined as one. #[inline(always)] pub fn start( &mut self, - sequence0: Sequence, - sequence1: Sequence, + sequence0: Sequence<'d>, + sequence1: Option>, times: SequenceMode, ) -> Result<(), Error> { - slice_in_ram_or(&sequence0.words, Error::DMABufferNotInDataMemory)?; - slice_in_ram_or(&sequence1.words, Error::DMABufferNotInDataMemory)?; + let alt_sequence = sequence1.as_ref().unwrap_or(&sequence0); - let seq_0_word_count = sequence0.word_count.unwrap_or(S0); - let seq_1_word_count = sequence0.word_count.unwrap_or(S1); - if seq_0_word_count > S0 || seq_1_word_count > S1 { + 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); } - match times { - SequenceMode::Times(0) => return Err(Error::SequenceTimesAtLeastOne), - SequenceMode::Times(1) if seq_0_word_count == 0 || seq_1_word_count != 0 => { - return Err(Error::SequenceTimesRequireSeq0Only) - } - SequenceMode::Times(1) => (), - SequenceMode::Times(_) | SequenceMode::Infinite - if seq_0_word_count == 0 || seq_1_word_count == 0 => - { - return Err(Error::SequenceTimesRequireBothSeq0AndSeq1) - } - SequenceMode::Times(_) | SequenceMode::Infinite => (), + if let SequenceMode::Times(0) = times { + return Err(Error::SequenceTimesAtLeastOne); } let _ = self.stop(); - // We now own these sequences and they will be moved. We want - // the peripheral to point at the right bits of memory hence - // moving the sequences early. - self.sequence0 = Some(sequence0); - self.sequence1 = Some(sequence1); - - let sequence0 = self.sequence0.as_ref().unwrap(); - let sequence1 = self.sequence1.as_ref().unwrap(); - let r = T::regs(); r.seq0 @@ -197,20 +171,20 @@ impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S .write(|w| unsafe { w.bits(sequence0.words.as_ptr() as u32) }); r.seq0 .cnt - .write(|w| unsafe { w.bits(seq_0_word_count as u32) }); + .write(|w| unsafe { w.bits(sequence0.words.len() as u32) }); r.seq1 .refresh - .write(|w| unsafe { w.bits(sequence1.config.refresh) }); + .write(|w| unsafe { w.bits(alt_sequence.config.refresh) }); r.seq1 .enddelay - .write(|w| unsafe { w.bits(sequence1.config.end_delay) }); + .write(|w| unsafe { w.bits(alt_sequence.config.end_delay) }); r.seq1 .ptr - .write(|w| unsafe { w.bits(sequence1.words.as_ptr() as u32) }); + .write(|w| unsafe { w.bits(alt_sequence.words.as_ptr() as u32) }); r.seq1 .cnt - .write(|w| unsafe { w.bits(seq_1_word_count as u32) }); + .write(|w| unsafe { w.bits(alt_sequence.words.len() as u32) }); r.enable.write(|w| w.enable().enabled()); @@ -252,6 +226,9 @@ impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S } } + self.sequence0 = Some(sequence0); + self.sequence1 = sequence1; + Ok(()) } @@ -359,7 +336,7 @@ impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S /// cycle from the pin. Returns any sequences previously provided to /// `start` so that they may be further mutated. #[inline(always)] - pub fn stop(&mut self) -> (Option>, Option>) { + pub fn stop(&mut self) -> (Option>, Option>) { let r = T::regs(); r.shorts.reset(); @@ -375,7 +352,7 @@ impl<'d, T: Instance, const S0: usize, const S1: usize> SequencePwm<'d, T, S0, S } } -impl<'a, T: Instance, const S0: usize, const S1: usize> Drop for SequencePwm<'a, T, S0, S1> { +impl<'a, T: Instance> Drop for SequencePwm<'a, T> { fn drop(&mut self) { let r = T::regs(); @@ -404,7 +381,6 @@ impl<'a, T: Instance, const S0: usize, const S1: usize> Drop for SequencePwm<'a, } } -/// Configuration for the PWM as a whole. #[non_exhaustive] pub struct Config { /// Selects up mode or up-and-down mode for the counter @@ -428,7 +404,6 @@ impl Default for Config { } } -/// Configuration per sequence #[non_exhaustive] #[derive(Clone)] pub struct SequenceConfig { @@ -447,39 +422,20 @@ impl Default for SequenceConfig { } } -/// A composition of a sequence buffer and its configuration. #[non_exhaustive] -#[derive(Clone)] -pub struct Sequence { +pub struct Sequence<'d> { /// The words comprising the sequence. Must not exceed 32767 words. - pub words: [u16; S], - /// The count of words to use. If None the S will be used. - pub word_count: Option, + pub words: &'d mut [u16], /// Configuration associated with the sequence. pub config: SequenceConfig, } -impl Sequence { - pub const fn new(words: [u16; S], config: SequenceConfig) -> Self { - Self { - words, - word_count: None, - config, - } +impl<'d> Sequence<'d> { + pub fn new(words: &'d mut [u16], config: SequenceConfig) -> Self { + Self { words, config } } } -/// Declares an empty sequence which will cause it to be disabled. -/// Note that any looping i.e. !Times(1), will require a second -/// sequence given the way the PWM peripheral works. -pub const EMPTY_SEQ: Sequence<0> = Sequence::new( - [], - SequenceConfig { - refresh: 0, - end_delay: 0, - }, -); - /// How many times to run the sequence #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SequenceMode { @@ -490,7 +446,7 @@ pub enum SequenceMode { /// 5 to 6 = Run sequence 0, sequence 1, sequence 0, sequence 1, sequence 0 and then sequence 1 /// i.e the when >= 2 the loop count is determined by dividing by 2 and rounding up Times(u16), - /// Repeat until `stop` is called. Both sequences must be provided. + /// Repeat until `stop` is called. Infinite, } diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 6fe957d2..6fb861e8 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -8,13 +8,14 @@ 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, EMPTY_SEQ, -}; +use embassy_nrf::pwm::{Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm}; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { + let mut seq_words_1: [u16; 5] = [1000, 250, 100, 50, 0]; + let mut seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; + let mut config = Config::default(); config.prescaler = Prescaler::Div128; // 1 period is 1000 * (128/16mhz = 0.000008s = 0.008ms) = 8us @@ -25,20 +26,25 @@ async fn main(_spawner: Spawner, p: Peripherals) { seq_config.refresh = 624; // thus our sequence takes 5 * 5000ms or 25 seconds - let seq_1 = Sequence::new([1000, 250, 100, 50, 0], seq_config.clone()); - let seq_2 = Sequence::new([0, 50, 100, 250, 1000], seq_config); - let mut pwm = unwrap!(SequencePwm::new( p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - unwrap!(pwm.start(seq_1, EMPTY_SEQ, SequenceMode::Times(1))); + let _ = pwm.start( + Sequence::new(&mut seq_words_1, seq_config.clone()), + None, + SequenceMode::Infinite, + ); info!("pwm started!"); Timer::after(Duration::from_millis(20000)).await; info!("pwm starting with another sequence!"); - unwrap!(pwm.start(seq_2, EMPTY_SEQ, SequenceMode::Times(1))); + let _ = pwm.start( + Sequence::new(&mut seq_words_2, seq_config), + None, + SequenceMode::Infinite, + ); // 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 4883222a..f5d734bb 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -16,7 +16,7 @@ use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; + let mut seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -31,12 +31,11 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - // If we loop in any way i.e. not Times(1), then we must provide - // the PWM peripheral with two sequences. - let seq_0 = Sequence::new(seq_words, seq_config); - let seq_1 = seq_0.clone(); - - unwrap!(pwm.start(seq_0, seq_1, SequenceMode::Infinite)); + let _ = pwm.start( + Sequence::new(&mut seq_words, seq_config), + None, + SequenceMode::Infinite, + ); // 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 diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 8acb209c..0ce79cbe 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -30,6 +30,19 @@ const RES: u16 = 0x8000; // line is assumed to be P1_05. #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { + // Declare the bits of 24 bits + let mut color_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 + ]; + let color_seq = Sequence::new(&mut color_seq_words, SequenceConfig::default()); + + let mut reset_seq_words = [RES; 1]; + let mut reset_seq_config = SequenceConfig::default(); + reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; + let reset_seq = Sequence::new(&mut reset_seq_words, reset_seq_config); + let mut config = Config::default(); config.sequence_load = SequenceLoad::Common; config.prescaler = Prescaler::Div1; @@ -38,21 +51,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, )); - // Declare the bits of 24 bits - let color_seq = Sequence::new( - [ - 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 - ], - SequenceConfig::default(), - ); - - let mut reset_seq_config = SequenceConfig::default(); - reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; - let reset_seq = Sequence::new([RES], reset_seq_config); - - unwrap!(pwm.start(color_seq, reset_seq, SequenceMode::Times(2))); + unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); Timer::after(Duration::from_millis(1000)).await; @@ -60,9 +59,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut bit_value = T0H; loop { - if let (Some(mut color_seq), Some(reset_seq)) = pwm.stop() { + if let (Some(color_seq), Some(reset_seq)) = pwm.stop() { color_seq.words[color_bit] = bit_value; - unwrap!(pwm.start(color_seq, reset_seq, SequenceMode::Times(2))); + unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); } Timer::after(Duration::from_millis(50)).await; From 986295998a3fa8c665364d7b4a5fc009d186dee9 Mon Sep 17 00:00:00 2001 From: huntc Date: Sun, 30 Jan 2022 16:26:09 +1100 Subject: [PATCH 07/18] Some more doco --- embassy-nrf/src/pwm.rs | 3 +++ examples/nrf/src/bin/pwm_sequence.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 94dfdeda..a4bc9476 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -381,6 +381,7 @@ impl<'a, T: Instance> Drop for SequencePwm<'a, T> { } } +/// Configuration for the PWM as a whole. #[non_exhaustive] pub struct Config { /// Selects up mode or up-and-down mode for the counter @@ -404,6 +405,7 @@ impl Default for Config { } } +/// Configuration per sequence #[non_exhaustive] #[derive(Clone)] pub struct SequenceConfig { @@ -422,6 +424,7 @@ impl Default for SequenceConfig { } } +/// A composition of a sequence buffer and its configuration. #[non_exhaustive] pub struct Sequence<'d> { /// The words comprising the sequence. Must not exceed 32767 words. diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 6fb861e8..d3ddf558 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -32,7 +32,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let _ = pwm.start( Sequence::new(&mut seq_words_1, seq_config.clone()), None, - SequenceMode::Infinite, + SequenceMode::Times(1), ); info!("pwm started!"); @@ -43,7 +43,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let _ = pwm.start( Sequence::new(&mut seq_words_2, seq_config), None, - SequenceMode::Infinite, + SequenceMode::Times(1), ); // we can abort a sequence if we need to before its complete with pwm.stop() From bc7266394ddba3d6128cc9de131109c2454f3d05 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 11:48:08 +1100 Subject: [PATCH 08/18] Clarify why we need the mut buffer --- embassy-nrf/src/pwm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index a4bc9476..e561f038 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -428,6 +428,8 @@ impl Default for SequenceConfig { #[non_exhaustive] pub struct Sequence<'d> { /// The words comprising the sequence. Must not exceed 32767 words. + /// The reason for this buffer to be mutable is so that stopping + /// the PWM can relinquish the sequence for subsequent modification. pub words: &'d mut [u16], /// Configuration associated with the sequence. pub config: SequenceConfig, From 1af6b23f970d80d881bbc83fe69846e14c512e1c Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 13:04:55 +1100 Subject: [PATCH 09/18] Introduces a Sequences struct --- embassy-nrf/src/pwm.rs | 274 ++++++++++--------- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 39 ++- 2 files changed, 161 insertions(+), 152 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index e561f038..b696cbe6 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -31,8 +31,6 @@ pub struct SequencePwm<'d, T: Instance> { ch1: Option, ch2: Option, ch3: Option, - sequence0: Option>, - sequence1: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -127,111 +125,9 @@ impl<'d, T: Instance> SequencePwm<'d, T> { ch1: ch1.degrade_optional(), ch2: ch2.degrade_optional(), ch3: ch3.degrade_optional(), - sequence0: None, - sequence1: None, }) } - /// Start or restart playback. 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. The sequence mode applies to both sequences combined as one. - #[inline(always)] - pub fn start( - &mut self, - sequence0: Sequence<'d>, - sequence1: Option>, - times: SequenceMode, - ) -> Result<(), Error> { - let alt_sequence = sequence1.as_ref().unwrap_or(&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::Times(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); - - 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) }); - } - } - - self.sequence0 = Some(sequence0); - self.sequence1 = sequence1; - - Ok(()) - } - /// Returns reference to `Stopped` event endpoint for PPI. #[inline(always)] pub fn event_stopped(&self) -> Event { @@ -331,33 +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. Returns any sequences previously provided to - /// `start` so that they may be further mutated. - #[inline(always)] - pub fn stop(&mut self) -> (Option>, Option>) { - 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()); - - (self.sequence0.take(), self.sequence1.take()) - } } impl<'a, T: Instance> Drop for SequencePwm<'a, T> { fn drop(&mut self) { let r = T::regs(); - let _ = self.stop(); - if let Some(pin) = &self.ch0 { pin.set_low(); pin.conf().reset(); @@ -426,21 +301,158 @@ impl Default for SequenceConfig { /// A composition of a sequence buffer and its configuration. #[non_exhaustive] -pub struct Sequence<'d> { +pub struct Sequence<'s> { /// The words comprising the sequence. Must not exceed 32767 words. - /// The reason for this buffer to be mutable is so that stopping - /// the PWM can relinquish the sequence for subsequent modification. - pub words: &'d mut [u16], + pub words: &'s [u16], /// Configuration associated with the sequence. pub config: SequenceConfig, } -impl<'d> Sequence<'d> { - pub fn new(words: &'d mut [u16], config: SequenceConfig) -> Self { +impl<'s> Sequence<'s> { + pub fn new(words: &'s [u16], config: SequenceConfig) -> Self { Self { words, config } } } +/// 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 Sequences<'d, 's, T: Instance> { + _pwm: &'s mut SequencePwm<'d, T>, + sequence0: Sequence<'s>, + sequence1: Option>, +} + +impl<'d, 's, T: Instance> Sequences<'d, 's, T> { + pub fn new( + pwm: &'s mut SequencePwm<'d, T>, + sequence0: Sequence<'s>, + sequence1: Option>, + ) -> Self { + Sequences { + _pwm: pwm, + sequence0, + sequence1, + } + } + + /// Start or restart playback. The sequence mode applies to both sequences combined as one. + #[inline(always)] + pub fn start(&self, 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::Times(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); + + 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(()) + } + + /// 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 Sequences<'d, 's, T> { + fn drop(&mut self) { + let _ = self.stop(); + } +} + /// How many times to run the sequence #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SequenceMode { diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 0ce79cbe..310842d8 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -9,7 +9,7 @@ use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ - Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequenceMode, SequencePwm, + Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequenceMode, SequencePwm, Sequences, }; use embassy_nrf::Peripherals; @@ -30,19 +30,6 @@ const RES: u16 = 0x8000; // line is assumed to be P1_05. #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - // Declare the bits of 24 bits - let mut color_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 - ]; - let color_seq = Sequence::new(&mut color_seq_words, SequenceConfig::default()); - - let mut reset_seq_words = [RES; 1]; - let mut reset_seq_config = SequenceConfig::default(); - reset_seq_config.end_delay = 799; // 50us (20 ticks * 40) - 1 tick because we've already got one RES; - let reset_seq = Sequence::new(&mut reset_seq_words, reset_seq_config); - let mut config = Config::default(); config.sequence_load = SequenceLoad::Common; config.prescaler = Prescaler::Div1; @@ -51,18 +38,24 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, )); - unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); - - Timer::after(Duration::from_millis(1000)).await; + // 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 { - if let (Some(color_seq), Some(reset_seq)) = pwm.stop() { - color_seq.words[color_bit] = bit_value; - unwrap!(pwm.start(color_seq, Some(reset_seq), SequenceMode::Times(2))); - } + let sequence0 = Sequence::new(&seq_words, seq_config.clone()); + let sequences = Sequences::new(&mut pwm, sequence0, None); + unwrap!(sequences.start(SequenceMode::Times(2))); Timer::after(Duration::from_millis(50)).await; @@ -79,5 +72,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { color_bit -= 1; } } + + drop(sequences); + + seq_words[color_bit] = bit_value; } } From 25be00878c44550a7ecbb6f6501490dba770c20e Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 15:55:04 +1100 Subject: [PATCH 10/18] Doco correction --- embassy-nrf/src/pwm.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index b696cbe6..d7433502 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -459,9 +459,9 @@ pub enum SequenceMode { /// Run sequence n Times total. /// 1 = Run sequence 0 once /// 2 = Run sequence 0 and then sequence 1 - /// 3 to 4 = Run sequence 0, sequence 1, sequence 0 and then sequence 1 - /// 5 to 6 = Run sequence 0, sequence 1, sequence 0, sequence 1, sequence 0 and then sequence 1 - /// i.e the when >= 2 the loop count is determined by dividing by 2 and rounding up + /// 3 = Run sequence 1, sequence 0, sequence 1 and then sequence 0 + /// 4 = Run sequence 0, sequence 1, sequence 0 and then sequence 1 + /// ...and so on. Times(u16), /// Repeat until `stop` is called. Infinite, From fe5501293f39307fbfa419d6f882f4a2cd10c115 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 16:26:23 +1100 Subject: [PATCH 11/18] Expose PWM --- embassy-nrf/src/pwm.rs | 4 ++-- examples/nrf/src/bin/pwm_sequence_ppi.rs | 19 ++++++++++--------- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 9 +++++++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index d7433502..55863ea5 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -321,7 +321,7 @@ impl<'s> Sequence<'s> { /// is used. #[non_exhaustive] pub struct Sequences<'d, 's, T: Instance> { - _pwm: &'s mut SequencePwm<'d, T>, + pub pwm: &'s mut SequencePwm<'d, T>, sequence0: Sequence<'s>, sequence1: Option>, } @@ -333,7 +333,7 @@ impl<'d, 's, T: Instance> Sequences<'d, 's, T> { sequence1: Option>, ) -> Self { Sequences { - _pwm: pwm, + pwm, sequence0, sequence1, } diff --git a/examples/nrf/src/bin/pwm_sequence_ppi.rs b/examples/nrf/src/bin/pwm_sequence_ppi.rs index f5d734bb..593e7590 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -11,12 +11,14 @@ 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::{Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm}; +use embassy_nrf::pwm::{ + Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm, Sequences, +}; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let mut seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; + let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -31,11 +33,10 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let _ = pwm.start( - Sequence::new(&mut seq_words, seq_config), - None, - SequenceMode::Infinite, - ); + let sequence0 = Sequence::new(&seq_words, seq_config); + let sequences = Sequences::new(&mut pwm, sequence0, None); + unwrap!(sequences.start(SequenceMode::Infinite)); + // 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 @@ -53,8 +54,8 @@ async fn main(_spawner: Spawner, p: Peripherals) { // messing with the pwm tasks is ill advised // Times::Ininite and Times even are seq0, Times odd is seq1 - let start = unsafe { pwm.task_start_seq0() }; - let stop = unsafe { pwm.task_stop() }; + let start = unsafe { sequences.pwm.task_start_seq0() }; + let stop = unsafe { sequences.pwm.task_stop() }; 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 index 310842d8..7706f91d 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -7,11 +7,12 @@ mod example_common; use defmt::*; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; +use embassy::util::Forever; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequenceMode, SequencePwm, Sequences, }; -use embassy_nrf::Peripherals; +use embassy_nrf::{peripherals, Peripherals}; // WS2812B LED light demonstration. Drives just one light. // The following reference on WS2812B may be of use: @@ -26,6 +27,8 @@ 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; +static PWM: Forever> = Forever::new(); + // Provides data to a WS2812b (Neopixel) LED and makes it go blue. The data // line is assumed to be P1_05. #[embassy::main] @@ -34,10 +37,12 @@ async fn main(_spawner: Spawner, p: Peripherals) { config.sequence_load = SequenceLoad::Common; config.prescaler = Prescaler::Div1; config.max_duty = 20; // 1.25us (1s / 16Mhz * 20) - let mut pwm = unwrap!(SequencePwm::new( + let pwm = unwrap!(SequencePwm::new( p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, )); + let mut pwm = PWM.put(pwm); + // Declare the bits of 24 bits in a buffer we'll be // mutating later. let mut seq_words = [ From 81f98c32aaec61171678ba6cbdd9c82860a1802f Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 16:34:25 +1100 Subject: [PATCH 12/18] Update another example --- examples/nrf/src/bin/pwm_sequence.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index d3ddf558..b31c12a2 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -8,13 +8,15 @@ 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}; +use embassy_nrf::pwm::{ + Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm, Sequences, +}; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let mut seq_words_1: [u16; 5] = [1000, 250, 100, 50, 0]; - let mut seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; + let seq_words_1: [u16; 5] = [1000, 250, 100, 50, 0]; + let seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -29,22 +31,21 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut pwm = unwrap!(SequencePwm::new( p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let _ = pwm.start( - Sequence::new(&mut seq_words_1, seq_config.clone()), - None, - SequenceMode::Times(1), - ); + + let sequence0 = Sequence::new(&seq_words_1, seq_config.clone()); + let sequences = Sequences::new(&mut pwm, sequence0, None); + unwrap!(sequences.start(SequenceMode::Times(1))); info!("pwm started!"); Timer::after(Duration::from_millis(20000)).await; info!("pwm starting with another sequence!"); - let _ = pwm.start( - Sequence::new(&mut seq_words_2, seq_config), - None, - SequenceMode::Times(1), - ); + drop(sequences); // This stops the previous sequence and returns pwm ownership back + + let sequence0 = Sequence::new(&seq_words_2, seq_config); + let sequences = Sequences::new(&mut pwm, sequence0, None); + unwrap!(sequences.start(SequenceMode::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 From e9e4d058d1fbb1eefa0b2d58b0a76075e0a32487 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 16:39:14 +1100 Subject: [PATCH 13/18] Revert the use of forever --- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 7706f91d..310842d8 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -7,12 +7,11 @@ mod example_common; use defmt::*; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; -use embassy::util::Forever; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequenceMode, SequencePwm, Sequences, }; -use embassy_nrf::{peripherals, Peripherals}; +use embassy_nrf::Peripherals; // WS2812B LED light demonstration. Drives just one light. // The following reference on WS2812B may be of use: @@ -27,8 +26,6 @@ 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; -static PWM: Forever> = Forever::new(); - // Provides data to a WS2812b (Neopixel) LED and makes it go blue. The data // line is assumed to be P1_05. #[embassy::main] @@ -37,12 +34,10 @@ async fn main(_spawner: Spawner, p: Peripherals) { config.sequence_load = SequenceLoad::Common; config.prescaler = Prescaler::Div1; config.max_duty = 20; // 1.25us (1s / 16Mhz * 20) - let pwm = unwrap!(SequencePwm::new( + let mut pwm = unwrap!(SequencePwm::new( p.PWM0, p.P1_05, NoPin, NoPin, NoPin, config, )); - let mut pwm = PWM.put(pwm); - // Declare the bits of 24 bits in a buffer we'll be // mutating later. let mut seq_words = [ From 9e36ede363b66c3e007d8cb0c477234b88ba0737 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 16:48:26 +1100 Subject: [PATCH 14/18] Small correction to times --- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 310842d8..c0c10373 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { loop { let sequence0 = Sequence::new(&seq_words, seq_config.clone()); let sequences = Sequences::new(&mut pwm, sequence0, None); - unwrap!(sequences.start(SequenceMode::Times(2))); + unwrap!(sequences.start(SequenceMode::Times(1))); Timer::after(Duration::from_millis(50)).await; From 965a5f2c3fba365519bed1c2a955145783d6a05b Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 19:11:15 +1100 Subject: [PATCH 15/18] Introduced the SingleSequencer and a more complex Sequencer --- embassy-nrf/src/pwm.rs | 113 ++++++++++++------- examples/nrf/src/bin/pwm_double_sequence.rs | 46 ++++++++ examples/nrf/src/bin/pwm_sequence.rs | 22 +--- examples/nrf/src/bin/pwm_sequence_ppi.rs | 14 +-- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 7 +- 5 files changed, 134 insertions(+), 68 deletions(-) create mode 100644 examples/nrf/src/bin/pwm_double_sequence.rs diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 55863ea5..c0d73bdc 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -314,26 +314,58 @@ impl<'s> Sequence<'s> { } } +/// 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>, sequence: Sequence<'s>) -> Self { + Self { + sequencer: Sequencer::new(pwm, sequence, 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) + } +} + /// 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 Sequences<'d, 's, T: Instance> { - pub pwm: &'s mut SequencePwm<'d, T>, +pub struct Sequencer<'d, 's, T: Instance> { + _pwm: &'s mut SequencePwm<'d, T>, sequence0: Sequence<'s>, sequence1: Option>, } -impl<'d, 's, T: Instance> Sequences<'d, 's, T> { +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 { - Sequences { - pwm, + Sequencer { + _pwm: pwm, sequence0, sequence1, } @@ -341,7 +373,7 @@ impl<'d, 's, T: Instance> Sequences<'d, 's, T> { /// Start or restart playback. The sequence mode applies to both sequences combined as one. #[inline(always)] - pub fn start(&self, times: SequenceMode) -> Result<(), Error> { + 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); @@ -352,7 +384,7 @@ impl<'d, 's, T: Instance> Sequences<'d, 's, T> { return Err(Error::SequenceTooLong); } - if let SequenceMode::Times(0) = times { + if let SequenceMode::Loop(0) = times { return Err(Error::SequenceTimesAtLeastOne); } @@ -391,41 +423,27 @@ impl<'d, 's, T: Instance> Sequences<'d, 's, T> { // 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::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) }); - } + 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[0].write(|w| unsafe { w.bits(0x01) }); } } + // 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(()) } @@ -447,22 +465,35 @@ impl<'d, 's, T: Instance> Sequences<'d, 's, T> { } } -impl<'d, 's, T: Instance> Drop for Sequences<'d, 's, T> { +impl<'d, 's, T: Instance> Drop for Sequencer<'d, 's, T> { fn drop(&mut self) { let _ = self.stop(); } } -/// How many times to run the sequence +/// 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. - /// 1 = Run sequence 0 once - /// 2 = Run sequence 0 and then sequence 1 - /// 3 = Run sequence 1, sequence 0, sequence 1 and then sequence 0 - /// 4 = Run sequence 0, sequence 1, sequence 0 and then sequence 1 - /// ...and so on. - 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 00000000..269015f4 --- /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 b31c12a2..761ac0f0 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -9,14 +9,13 @@ use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ - Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm, Sequences, + Config, Prescaler, Sequence, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, }; use embassy_nrf::Peripherals; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { - let seq_words_1: [u16; 5] = [1000, 250, 100, 50, 0]; - let seq_words_2: [u16; 5] = [0, 50, 100, 250, 1000]; + let seq_words: [u16; 5] = [1000, 250, 100, 50, 0]; let mut config = Config::default(); config.prescaler = Prescaler::Div128; @@ -32,20 +31,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let sequence0 = Sequence::new(&seq_words_1, seq_config.clone()); - let sequences = Sequences::new(&mut pwm, sequence0, None); - unwrap!(sequences.start(SequenceMode::Times(1))); - - info!("pwm started!"); - - Timer::after(Duration::from_millis(20000)).await; - info!("pwm starting with another sequence!"); - - drop(sequences); // This stops the previous sequence and returns pwm ownership back - - let sequence0 = Sequence::new(&seq_words_2, seq_config); - let sequences = Sequences::new(&mut pwm, sequence0, None); - unwrap!(sequences.start(SequenceMode::Times(1))); + let sequence = Sequence::new(&seq_words, seq_config.clone()); + let sequencer = SingleSequencer::new(&mut pwm, sequence); + 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 593e7590..7e58c37e 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -12,7 +12,7 @@ use embassy_nrf::gpio::{Input, NoPin, Pull}; use embassy_nrf::gpiote::{InputChannel, InputChannelPolarity}; use embassy_nrf::ppi::Ppi; use embassy_nrf::pwm::{ - Config, Prescaler, Sequence, SequenceConfig, SequenceMode, SequencePwm, Sequences, + Config, Prescaler, Sequence, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, }; use embassy_nrf::Peripherals; @@ -33,10 +33,6 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let sequence0 = Sequence::new(&seq_words, seq_config); - let sequences = Sequences::new(&mut pwm, sequence0, None); - unwrap!(sequences.start(SequenceMode::Infinite)); - // 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 @@ -54,8 +50,12 @@ async fn main(_spawner: Spawner, p: Peripherals) { // messing with the pwm tasks is ill advised // Times::Ininite and Times even are seq0, Times odd is seq1 - let start = unsafe { sequences.pwm.task_start_seq0() }; - let stop = unsafe { sequences.pwm.task_stop() }; + let start = unsafe { pwm.task_start_seq0() }; + let stop = unsafe { pwm.task_stop() }; + + let sequence = Sequence::new(&seq_words, seq_config); + let sequencer = SingleSequencer::new(&mut pwm, sequence); + 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 index c0c10373..71ddd528 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -9,7 +9,8 @@ use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ - Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequenceMode, SequencePwm, Sequences, + Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequencePwm, SingleSequenceMode, + SingleSequencer, }; use embassy_nrf::Peripherals; @@ -54,8 +55,8 @@ async fn main(_spawner: Spawner, p: Peripherals) { loop { let sequence0 = Sequence::new(&seq_words, seq_config.clone()); - let sequences = Sequences::new(&mut pwm, sequence0, None); - unwrap!(sequences.start(SequenceMode::Times(1))); + let sequences = SingleSequencer::new(&mut pwm, sequence0); + unwrap!(sequences.start(SingleSequenceMode::Times(1))); Timer::after(Duration::from_millis(50)).await; From 3b2beddc7a5397864d12c66f4b562a2391d3c57c Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 19:14:24 +1100 Subject: [PATCH 16/18] Forgot to expose the stop method --- embassy-nrf/src/pwm.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index c0d73bdc..895292bc 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -342,6 +342,14 @@ impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { }; 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. From 81d31e43ebf947ff2cd91b3a6f6af092fcb7e2b7 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 4 Feb 2022 19:18:10 +1100 Subject: [PATCH 17/18] Removed unrequired clone --- examples/nrf/src/bin/pwm_sequence.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index 761ac0f0..a76b1110 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -31,7 +31,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let sequence = Sequence::new(&seq_words, seq_config.clone()); + let sequence = Sequence::new(&seq_words, seq_config); let sequencer = SingleSequencer::new(&mut pwm, sequence); unwrap!(sequencer.start(SingleSequenceMode::Times(1))); From df5ba727f2c8bd3f2a67f51a3f43d7f47b011b1c Mon Sep 17 00:00:00 2001 From: huntc Date: Sat, 5 Feb 2022 08:05:23 +1100 Subject: [PATCH 18/18] Further API simplification for the single seq scenario --- embassy-nrf/src/pwm.rs | 4 ++-- examples/nrf/src/bin/pwm_sequence.rs | 5 ++--- examples/nrf/src/bin/pwm_sequence_ppi.rs | 5 ++--- examples/nrf/src/bin/pwm_sequence_ws2812b.rs | 5 ++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 895292bc..01b1f48d 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -323,9 +323,9 @@ pub struct SingleSequencer<'d, 's, T: Instance> { impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { /// Create a new sequencer - pub fn new(pwm: &'s mut SequencePwm<'d, T>, sequence: Sequence<'s>) -> Self { + pub fn new(pwm: &'s mut SequencePwm<'d, T>, words: &'s [u16], config: SequenceConfig) -> Self { Self { - sequencer: Sequencer::new(pwm, sequence, None), + sequencer: Sequencer::new(pwm, Sequence::new(words, config), None), } } diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf/src/bin/pwm_sequence.rs index a76b1110..f06ea0b1 100644 --- a/examples/nrf/src/bin/pwm_sequence.rs +++ b/examples/nrf/src/bin/pwm_sequence.rs @@ -9,7 +9,7 @@ use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ - Config, Prescaler, Sequence, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, + Config, Prescaler, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, }; use embassy_nrf::Peripherals; @@ -31,8 +31,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { p.PWM0, p.P0_13, NoPin, NoPin, NoPin, config, )); - let sequence = Sequence::new(&seq_words, seq_config); - let sequencer = SingleSequencer::new(&mut pwm, sequence); + 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() diff --git a/examples/nrf/src/bin/pwm_sequence_ppi.rs b/examples/nrf/src/bin/pwm_sequence_ppi.rs index 7e58c37e..c25c5e10 100644 --- a/examples/nrf/src/bin/pwm_sequence_ppi.rs +++ b/examples/nrf/src/bin/pwm_sequence_ppi.rs @@ -12,7 +12,7 @@ use embassy_nrf::gpio::{Input, NoPin, Pull}; use embassy_nrf::gpiote::{InputChannel, InputChannelPolarity}; use embassy_nrf::ppi::Ppi; use embassy_nrf::pwm::{ - Config, Prescaler, Sequence, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, + Config, Prescaler, SequenceConfig, SequencePwm, SingleSequenceMode, SingleSequencer, }; use embassy_nrf::Peripherals; @@ -53,8 +53,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let start = unsafe { pwm.task_start_seq0() }; let stop = unsafe { pwm.task_stop() }; - let sequence = Sequence::new(&seq_words, seq_config); - let sequencer = SingleSequencer::new(&mut pwm, sequence); + 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); diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs index 71ddd528..d1a027a7 100644 --- a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf/src/bin/pwm_sequence_ws2812b.rs @@ -9,7 +9,7 @@ use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy_nrf::gpio::NoPin; use embassy_nrf::pwm::{ - Config, Prescaler, Sequence, SequenceConfig, SequenceLoad, SequencePwm, SingleSequenceMode, + Config, Prescaler, SequenceConfig, SequenceLoad, SequencePwm, SingleSequenceMode, SingleSequencer, }; use embassy_nrf::Peripherals; @@ -54,8 +54,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut bit_value = T0H; loop { - let sequence0 = Sequence::new(&seq_words, seq_config.clone()); - let sequences = SingleSequencer::new(&mut pwm, sequence0); + let sequences = SingleSequencer::new(&mut pwm, &seq_words, seq_config.clone()); unwrap!(sequences.start(SingleSequenceMode::Times(1))); Timer::after(Duration::from_millis(50)).await;