From 424ddaf3d95417bcfe8b46475c8135aead3792d2 Mon Sep 17 00:00:00 2001 From: eZio Pan Date: Sat, 6 Jan 2024 22:22:38 +0800 Subject: [PATCH 1/3] impl waveform with TIM Channel --- embassy-stm32/build.rs | 14 ++-- embassy-stm32/src/timer/mod.rs | 25 +++++++ embassy-stm32/src/timer/simple_pwm.rs | 90 +++++++++++++++++++++++++- examples/stm32f4/src/bin/ws2812_pwm.rs | 2 +- 4 files changed, 119 insertions(+), 12 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index ef152acd1..7860ee2ff 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1009,6 +1009,10 @@ fn main() { (("dac", "CH1"), quote!(crate::dac::DacDma1)), (("dac", "CH2"), quote!(crate::dac::DacDma2)), (("timer", "UP"), quote!(crate::timer::UpDma)), + (("timer", "CH1"), quote!(crate::timer::Ch1Dma)), + (("timer", "CH2"), quote!(crate::timer::Ch2Dma)), + (("timer", "CH3"), quote!(crate::timer::Ch3Dma)), + (("timer", "CH4"), quote!(crate::timer::Ch4Dma)), ] .into(); @@ -1024,16 +1028,6 @@ fn main() { } if let Some(tr) = signals.get(&(regs.kind, ch.signal)) { - // TIM6 of stm32f334 is special, DMA channel for TIM6 depending on SYSCFG state - if chip_name.starts_with("stm32f334") && p.name == "TIM6" { - continue; - } - - // TIM6 of stm32f378 is special, DMA channel for TIM6 depending on SYSCFG state - if chip_name.starts_with("stm32f378") && p.name == "TIM6" { - continue; - } - let peri = format_ident!("{}", p.name); let channel = if let Some(channel) = &ch.channel { diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index d07fd2776..957098cde 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -311,6 +311,26 @@ pub(crate) mod sealed { .ccmr_output(channel_index / 2) .modify(|w| w.set_ocpe(channel_index % 2, preload)); } + + /// Get capture compare DMA selection + fn get_cc_dma_selection(&self) -> super::vals::Ccds { + Self::regs_gp16().cr2().read().ccds() + } + + /// Set capture compare DMA selection + fn set_cc_dma_selection(&mut self, ccds: super::vals::Ccds) { + Self::regs_gp16().cr2().modify(|w| w.set_ccds(ccds)) + } + + /// Get capture compare DMA enable state + fn get_cc_dma_enable_state(&self, channel: Channel) -> bool { + Self::regs_gp16().dier().read().ccde(channel.index()) + } + + /// Set capture compare DMA enable state + fn set_cc_dma_enable_state(&mut self, channel: Channel, ccde: bool) { + Self::regs_gp16().dier().modify(|w| w.set_ccde(channel.index(), ccde)) + } } /// Capture/Compare 16-bit timer instance with complementary pin support. @@ -705,3 +725,8 @@ foreach_interrupt! { // Update Event trigger DMA for every timer dma_trait!(UpDma, Basic16bitInstance); + +dma_trait!(Ch1Dma, CaptureCompare16bitInstance); +dma_trait!(Ch2Dma, CaptureCompare16bitInstance); +dma_trait!(Ch3Dma, CaptureCompare16bitInstance); +dma_trait!(Ch4Dma, CaptureCompare16bitInstance); diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 80f10424c..665557354 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -155,7 +155,7 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { /// /// Note: /// you will need to provide corresponding TIMx_UP DMA channel to use this method. - pub async fn gen_waveform( + pub async fn waveform_up( &mut self, dma: impl Peripheral

>, channel: Channel, @@ -221,6 +221,94 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { } } +macro_rules! impl_waveform_chx { + ($fn_name:ident, $dma_ch:ident, $cc_ch:ident) => { + impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { + /// Generate a sequence of PWM waveform + /// + /// Note: + /// you will need to provide corresponding TIMx_CHy DMA channel to use this method. + pub async fn $fn_name(&mut self, dma: impl Peripheral

>, duty: &[u16]) { + use super::vals::Ccds; + + assert!(duty.iter().all(|v| *v <= self.get_max_duty())); + + into_ref!(dma); + + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + let cc_channel = super::Channel::$cc_ch; + + let original_duty_state = self.get_duty(cc_channel); + let original_enable_state = self.is_enabled(cc_channel); + let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; + let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); + + if original_cc_dma_on_update { + self.inner.set_cc_dma_selection(Ccds::ONUPDATE) + } + + if !original_cc_dma_enabled { + self.inner.set_cc_dma_enable_state(cc_channel, true); + } + + if !original_enable_state { + self.enable(cc_channel); + } + + unsafe { + #[cfg(not(any(bdma, gpdma)))] + use crate::dma::{Burst, FifoThreshold}; + use crate::dma::{Transfer, TransferOptions}; + + let dma_transfer_option = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; + + Transfer::new_write( + &mut dma, + req, + duty, + T::regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut _, + dma_transfer_option, + ) + .await + }; + + // restore output compare state + if !original_enable_state { + self.disable(cc_channel); + } + + self.set_duty(cc_channel, original_duty_state); + + // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, + // this can almost always trigger a DMA FIFO error. + // + // optional TODO: + // clean FEIF after disable UDE + if !original_cc_dma_enabled { + self.inner.set_cc_dma_enable_state(cc_channel, false); + } + + if !original_cc_dma_on_update { + self.inner.set_cc_dma_selection(Ccds::ONCOMPARE) + } + } + } + }; +} + +impl_waveform_chx!(waveform_ch1, Ch1Dma, Ch1); +impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2); +impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3); +impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4); + impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> { type Channel = Channel; type Time = Hertz; diff --git a/examples/stm32f4/src/bin/ws2812_pwm.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs index 239709253..6122cea2d 100644 --- a/examples/stm32f4/src/bin/ws2812_pwm.rs +++ b/examples/stm32f4/src/bin/ws2812_pwm.rs @@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) { loop { for &color in color_list { // with &mut, we can easily reuse same DMA channel multiple times - ws2812_pwm.gen_waveform(&mut dp.DMA1_CH2, pwm_channel, color).await; + ws2812_pwm.waveform_up(&mut dp.DMA1_CH2, pwm_channel, color).await; // ws2812 need at least 50 us low level input to confirm the input data and change it's state Timer::after_micros(50).await; // wait until ticker tick From 890a1269d05297993ffa7b537739926eb30d5436 Mon Sep 17 00:00:00 2001 From: eZio Pan Date: Sat, 6 Jan 2024 22:48:21 +0800 Subject: [PATCH 2/3] refactor with clippy --- embassy-stm32/build.rs | 7 ++++--- embassy-stm32/src/timer/complementary_pwm.rs | 3 ++- embassy-stm32/src/timer/mod.rs | 15 ++++++--------- embassy-stm32/src/timer/simple_pwm.rs | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 7860ee2ff..fc876afcc 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -936,9 +936,9 @@ fn main() { } else if pin.signal.starts_with("INN") { // TODO handle in the future when embassy supports differential measurements None - } else if pin.signal.starts_with("IN") && pin.signal.ends_with("b") { + } else if pin.signal.starts_with("IN") && pin.signal.ends_with('b') { // we number STM32L1 ADC bank 1 as 0..=31, bank 2 as 32..=63 - let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix("b").unwrap(); + let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix('b').unwrap(); Some(32u8 + signal.parse::().unwrap()) } else if pin.signal.starts_with("IN") { Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) @@ -1186,7 +1186,7 @@ fn main() { ADC3 and higher are assigned to the adc34 clock in the table The adc3_common cfg directive is added if ADC3_COMMON exists */ - let has_adc3 = METADATA.peripherals.iter().find(|p| p.name == "ADC3_COMMON").is_some(); + let has_adc3 = METADATA.peripherals.iter().any(|p| p.name == "ADC3_COMMON"); let set_adc345 = HashSet::from(["ADC3", "ADC4", "ADC5"]); for m in METADATA @@ -1370,6 +1370,7 @@ fn main() { // ======= // ADC3_COMMON is present + #[allow(clippy::print_literal)] if has_adc3 { println!("cargo:rustc-cfg={}", "adc3_common"); } diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 71d7110b5..eddce0404 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -54,6 +54,7 @@ pub struct ComplementaryPwm<'d, T> { impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { /// Create a new complementary PWM driver. + #[allow(clippy::too_many_arguments)] pub fn new( tim: impl Peripheral

+ 'd, _ch1: Option>, @@ -165,7 +166,7 @@ impl<'d, T: ComplementaryCaptureCompare16bitInstance> embedded_hal_02::Pwm for C } fn get_period(&self) -> Self::Time { - self.inner.get_frequency().into() + self.inner.get_frequency() } fn get_duty(&self, channel: Self::Channel) -> Self::Duty { diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 957098cde..210bf7153 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -470,20 +470,17 @@ pub enum CountingMode { impl CountingMode { /// Return whether this mode is edge-aligned (up or down). pub fn is_edge_aligned(&self) -> bool { - match self { - CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown => true, - _ => false, - } + matches!(self, CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown) } /// Return whether this mode is center-aligned. pub fn is_center_aligned(&self) -> bool { - match self { + matches!( + self, CountingMode::CenterAlignedDownInterrupts - | CountingMode::CenterAlignedUpInterrupts - | CountingMode::CenterAlignedBothInterrupts => true, - _ => false, - } + | CountingMode::CenterAlignedUpInterrupts + | CountingMode::CenterAlignedBothInterrupts + ) } } diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 665557354..709278c0c 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -323,7 +323,7 @@ impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, } fn get_period(&self) -> Self::Time { - self.inner.get_frequency().into() + self.inner.get_frequency() } fn get_duty(&self, channel: Self::Channel) -> Self::Duty { From b16cc04036da8bcbfe273cd796c092e8e2a074c9 Mon Sep 17 00:00:00 2001 From: eZio Pan Date: Mon, 8 Jan 2024 19:18:24 +0800 Subject: [PATCH 3/3] bug fix --- embassy-stm32/src/timer/simple_pwm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 709278c0c..ba089ac8b 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -245,7 +245,8 @@ macro_rules! impl_waveform_chx { let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); - if original_cc_dma_on_update { + // redirect CC DMA request onto Update Event + if !original_cc_dma_on_update { self.inner.set_cc_dma_selection(Ccds::ONUPDATE) }