From 05c511355638c3a55ab509ef9b2e30f5564b6282 Mon Sep 17 00:00:00 2001 From: RaulIQ Date: Wed, 21 May 2025 12:27:25 +0300 Subject: [PATCH 1/4] add waveform_up_multichannel using DMAR/DCR --- embassy-stm32/src/timer/simple_pwm.rs | 68 +++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 8fd7e8df4..c6fd169fc 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -381,6 +381,74 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { self.inner.enable_update_dma(false); } } + + /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. + /// + /// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers + /// in sequence on each update event (UEV). The data is written via the DMAR register using the + /// DMA base address (DBA) and burst length (DBL) configured in the DCR register. + /// + /// Note: + /// you will need to provide corresponding TIMx_UP DMA channel to use this method. + pub async fn waveform_up_multichannel( + &mut self, + dma: Peri<'_, impl super::UpDma>, + starting_channel: Channel, + ending_channel: Channel, + duty: &[u16], + ) { + let cr1_addr = self.inner.regs_gp16().cr1().as_ptr() as u32; + let start_ch_index = starting_channel.index(); + let end_ch_index = ending_channel.index(); + + assert!(start_ch_index <= end_ch_index); + + let ccrx_addr = self.inner.regs_gp16().ccr(start_ch_index).as_ptr() as u32; + self.inner + .regs_gp16() + .dcr() + .modify(|w| w.set_dba(((ccrx_addr - cr1_addr) / 4) as u8)); + self.inner + .regs_gp16() + .dcr() + .modify(|w| w.set_dbl((end_ch_index - start_ch_index) as u8)); + + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + let original_update_dma_state = self.inner.get_update_dma_state(); + if !original_update_dma_state { + self.inner.enable_update_dma(true); + } + + 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::Incr4, + ..Default::default() + }; + + Transfer::new_write( + dma, + req, + duty, + self.inner.regs_gp16().dmar().as_ptr() as *mut u16, + dma_transfer_option, + ) + .await + }; + + if !original_update_dma_state { + self.inner.enable_update_dma(false); + } + } + } macro_rules! impl_waveform_chx { From 62ffc995f179de25d3fc41b420dd0194c94df737 Mon Sep 17 00:00:00 2001 From: RaulIQ Date: Wed, 21 May 2025 16:39:41 +0300 Subject: [PATCH 2/4] improve waveform_up_multi_channel documentation --- embassy-stm32/src/timer/simple_pwm.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index c6fd169fc..972a3852c 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -388,9 +388,27 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// in sequence on each update event (UEV). The data is written via the DMAR register using the /// DMA base address (DBA) and burst length (DBL) configured in the DCR register. /// + /// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row + /// represents a single update event and each column corresponds to a specific timer channel (starting + /// from `starting_channel` up to and including `ending_channel`). + /// + /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like: + /// + /// ```rust + /// let dma_buf: [u16; 16] = [ + /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1 + /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2 + /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3 + /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4 + /// ]; + /// ``` + /// + /// Each group of N values (where N = number of channels) is transferred on one update event, + /// updating the duty cycles of all selected channels simultaneously. + /// /// Note: /// you will need to provide corresponding TIMx_UP DMA channel to use this method. - pub async fn waveform_up_multichannel( + pub async fn waveform_up_multi_channel( &mut self, dma: Peri<'_, impl super::UpDma>, starting_channel: Channel, From 3c3c7877cd31b84fb5dcf976e05d4eab66ddeaee Mon Sep 17 00:00:00 2001 From: RaulIQ Date: Thu, 22 May 2025 10:44:38 +0300 Subject: [PATCH 3/4] format simple_pwm.rs with rustfmt --- embassy-stm32/src/timer/simple_pwm.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 972a3852c..d356bb4b4 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -383,11 +383,11 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { } /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. - /// + /// /// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers /// in sequence on each update event (UEV). The data is written via the DMAR register using the /// DMA base address (DBA) and burst length (DBL) configured in the DCR register. - /// + /// /// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row /// represents a single update event and each column corresponds to a specific timer channel (starting /// from `starting_channel` up to and including `ending_channel`). @@ -405,7 +405,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// /// Each group of N values (where N = number of channels) is transferred on one update event, /// updating the duty cycles of all selected channels simultaneously. - /// + /// /// Note: /// you will need to provide corresponding TIMx_UP DMA channel to use this method. pub async fn waveform_up_multi_channel( @@ -466,7 +466,6 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { self.inner.enable_update_dma(false); } } - } macro_rules! impl_waveform_chx { From 967ae161a0dfe996635866b7c7139d02bc5882b9 Mon Sep 17 00:00:00 2001 From: RaulIQ Date: Thu, 22 May 2025 10:56:48 +0300 Subject: [PATCH 4/4] doc: update documentation to pass test --- embassy-stm32/src/timer/simple_pwm.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index d356bb4b4..f7f433154 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -394,14 +394,12 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like: /// - /// ```rust /// let dma_buf: [u16; 16] = [ /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1 /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2 /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3 /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4 /// ]; - /// ``` /// /// Each group of N values (where N = number of channels) is transferred on one update event, /// updating the duty cycles of all selected channels simultaneously.