Skip to content

Commit cf5ac49

Browse files
authored
Merge pull request #4892 from Dectron-AB/cleanup-timer
stm32: Cleanup timers
2 parents 933e27d + cf7a0ea commit cf5ac49

File tree

4 files changed

+271
-262
lines changed

4 files changed

+271
-262
lines changed

embassy-stm32/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased - ReleaseDate
99

10+
- feat: Add waveform methods to ComplementaryPwm
1011
- chore: cleanup low-power add time
1112
- fix: Allow setting SAI peripheral `frame_length` to `256`
1213
- fix: flash erase on dual-bank STM32Gxxx

embassy-stm32/src/timer/complementary_pwm.rs

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -218,60 +218,57 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> {
218218
///
219219
/// Note:
220220
/// you will need to provide corresponding TIMx_UP DMA channel to use this method.
221+
#[inline(always)]
221222
pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma<T>>, channel: Channel, duty: &[u16]) {
222-
#[allow(clippy::let_unit_value)] // eg. stm32f334
223-
let req = dma.request();
224-
225-
let original_duty_state = self.inner.get_compare_value(channel);
226-
let original_enable_state = self.inner.get_channel_enable_state(channel);
227-
let original_update_dma_state = self.inner.get_update_dma_state();
228-
229-
if !original_update_dma_state {
230-
self.inner.enable_update_dma(true);
231-
}
232-
233-
if !original_enable_state {
234-
self.inner.enable_channel(channel, true);
235-
}
236-
237-
unsafe {
238-
#[cfg(not(any(bdma, gpdma)))]
239-
use crate::dma::{Burst, FifoThreshold};
240-
use crate::dma::{Transfer, TransferOptions};
241-
242-
let dma_transfer_option = TransferOptions {
243-
#[cfg(not(any(bdma, gpdma)))]
244-
fifo_threshold: Some(FifoThreshold::Full),
245-
#[cfg(not(any(bdma, gpdma)))]
246-
mburst: Burst::Incr8,
247-
..Default::default()
248-
};
249-
250-
Transfer::new_write(
251-
dma,
252-
req,
253-
duty,
254-
self.inner.regs_gp16().ccr(channel.index()).as_ptr() as *mut u16,
255-
dma_transfer_option,
256-
)
257-
.await
258-
};
259-
260-
// restore output compare state
261-
if !original_enable_state {
262-
self.inner.enable_channel(channel, false);
263-
}
223+
self.inner.waveform_up(dma, channel, duty).await
224+
}
264225

265-
self.inner.set_compare_value(channel, original_duty_state);
226+
/// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events.
227+
///
228+
/// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers
229+
/// in sequence on each update event (UEV). The data is written via the DMAR register using the
230+
/// DMA base address (DBA) and burst length (DBL) configured in the DCR register.
231+
///
232+
/// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row
233+
/// represents a single update event and each column corresponds to a specific timer channel (starting
234+
/// from `starting_channel` up to and including `ending_channel`).
235+
///
236+
/// For example, if using channels 1 through 4, a buffer of 4 update steps might look like:
237+
///
238+
/// ```rust,ignore
239+
/// let dma_buf: [u16; 16] = [
240+
/// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1
241+
/// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2
242+
/// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3
243+
/// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4
244+
/// ];
245+
/// ```
246+
///
247+
/// Each group of `N` values (where `N` is number of channels) is transferred on one update event,
248+
/// updating the duty cycles of all selected channels simultaneously.
249+
///
250+
/// Note:
251+
/// You will need to provide corresponding `TIMx_UP` DMA channel to use this method.
252+
/// Also be aware that embassy timers use one of timers internally. It is possible to
253+
/// switch this timer by using `time-driver-timX` feature.
254+
///
255+
#[inline(always)]
256+
pub async fn waveform_up_multi_channel(
257+
&mut self,
258+
dma: Peri<'_, impl super::UpDma<T>>,
259+
starting_channel: Channel,
260+
ending_channel: Channel,
261+
duty: &[u16],
262+
) {
263+
self.inner
264+
.waveform_up_multi_channel(dma, starting_channel, ending_channel, duty)
265+
.await;
266+
}
266267

267-
// Since DMA is closed before timer update event trigger DMA is turn off,
268-
// this can almost always trigger a DMA FIFO error.
269-
//
270-
// optional TODO:
271-
// clean FEIF after disable UDE
272-
if !original_update_dma_state {
273-
self.inner.enable_update_dma(false);
274-
}
268+
/// Generate a sequence of PWM waveform
269+
#[inline(always)]
270+
pub async fn waveform<C: TimerChannel>(&mut self, dma: Peri<'_, impl super::Dma<T, C>>, duty: &[u16]) {
271+
self.inner.waveform(dma, duty).await;
275272
}
276273
}
277274

embassy-stm32/src/timer/low_level.rs

Lines changed: 214 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as Slav
1414

1515
use super::*;
1616
use crate::pac::timer::vals;
17-
use crate::rcc;
1817
use crate::time::Hertz;
18+
use crate::{dma, rcc};
1919

2020
/// Input capture mode.
2121
#[derive(Clone, Copy)]
@@ -656,6 +656,219 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> {
656656
}
657657
}
658658

659+
/// Generate a sequence of PWM waveform
660+
///
661+
/// Note:
662+
/// you will need to provide corresponding TIMx_UP DMA channel to use this method.
663+
pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma<T>>, channel: Channel, duty: &[u16]) {
664+
#[allow(clippy::let_unit_value)] // eg. stm32f334
665+
let req = dma.request();
666+
667+
let original_update_dma_state = self.get_update_dma_state();
668+
669+
if !original_update_dma_state {
670+
self.enable_update_dma(true);
671+
}
672+
673+
self.waveform_helper(dma, req, channel, duty).await;
674+
675+
// Since DMA is closed before timer update event trigger DMA is turn off,
676+
// this can almost always trigger a DMA FIFO error.
677+
//
678+
// optional TODO:
679+
// clean FEIF after disable UDE
680+
if !original_update_dma_state {
681+
self.enable_update_dma(false);
682+
}
683+
}
684+
685+
/// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events.
686+
///
687+
/// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers
688+
/// in sequence on each update event (UEV). The data is written via the DMAR register using the
689+
/// DMA base address (DBA) and burst length (DBL) configured in the DCR register.
690+
///
691+
/// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row
692+
/// represents a single update event and each column corresponds to a specific timer channel (starting
693+
/// from `starting_channel` up to and including `ending_channel`).
694+
///
695+
/// For example, if using channels 1 through 4, a buffer of 4 update steps might look like:
696+
///
697+
/// ```rust,ignore
698+
/// let dma_buf: [u16; 16] = [
699+
/// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1
700+
/// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2
701+
/// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3
702+
/// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4
703+
/// ];
704+
/// ```
705+
///
706+
/// Each group of `N` values (where `N` is number of channels) is transferred on one update event,
707+
/// updating the duty cycles of all selected channels simultaneously.
708+
///
709+
/// Note:
710+
/// You will need to provide corresponding `TIMx_UP` DMA channel to use this method.
711+
/// Also be aware that embassy timers use one of timers internally. It is possible to
712+
/// switch this timer by using `time-driver-timX` feature.
713+
///
714+
pub async fn waveform_up_multi_channel(
715+
&mut self,
716+
dma: Peri<'_, impl super::UpDma<T>>,
717+
starting_channel: Channel,
718+
ending_channel: Channel,
719+
duty: &[u16],
720+
) {
721+
let cr1_addr = self.regs_gp16().cr1().as_ptr() as u32;
722+
let start_ch_index = starting_channel.index();
723+
let end_ch_index = ending_channel.index();
724+
725+
assert!(start_ch_index <= end_ch_index);
726+
727+
let ccrx_addr = self.regs_gp16().ccr(start_ch_index).as_ptr() as u32;
728+
self.regs_gp16()
729+
.dcr()
730+
.modify(|w| w.set_dba(((ccrx_addr - cr1_addr) / 4) as u8));
731+
self.regs_gp16()
732+
.dcr()
733+
.modify(|w| w.set_dbl((end_ch_index - start_ch_index) as u8));
734+
735+
#[allow(clippy::let_unit_value)] // eg. stm32f334
736+
let req = dma.request();
737+
738+
let original_update_dma_state = self.get_update_dma_state();
739+
if !original_update_dma_state {
740+
self.enable_update_dma(true);
741+
}
742+
743+
unsafe {
744+
#[cfg(not(any(bdma, gpdma)))]
745+
use crate::dma::{Burst, FifoThreshold};
746+
use crate::dma::{Transfer, TransferOptions};
747+
748+
let dma_transfer_option = TransferOptions {
749+
#[cfg(not(any(bdma, gpdma)))]
750+
fifo_threshold: Some(FifoThreshold::Full),
751+
#[cfg(not(any(bdma, gpdma)))]
752+
mburst: Burst::Incr4,
753+
..Default::default()
754+
};
755+
756+
Transfer::new_write(
757+
dma,
758+
req,
759+
duty,
760+
self.regs_gp16().dmar().as_ptr() as *mut u16,
761+
dma_transfer_option,
762+
)
763+
.await
764+
};
765+
766+
if !original_update_dma_state {
767+
self.enable_update_dma(false);
768+
}
769+
}
770+
771+
/// Generate a sequence of PWM waveform
772+
pub async fn waveform<C: TimerChannel>(&mut self, dma: Peri<'_, impl super::Dma<T, C>>, duty: &[u16]) {
773+
use crate::pac::timer::vals::Ccds;
774+
775+
#[allow(clippy::let_unit_value)] // eg. stm32f334
776+
let req = dma.request();
777+
778+
let cc_channel = C::CHANNEL;
779+
780+
let original_cc_dma_on_update = self.get_cc_dma_selection() == Ccds::ON_UPDATE;
781+
let original_cc_dma_enabled = self.get_cc_dma_enable_state(cc_channel);
782+
783+
// redirect CC DMA request onto Update Event
784+
if !original_cc_dma_on_update {
785+
self.set_cc_dma_selection(Ccds::ON_UPDATE)
786+
}
787+
788+
if !original_cc_dma_enabled {
789+
self.set_cc_dma_enable_state(cc_channel, true);
790+
}
791+
792+
self.waveform_helper(dma, req, cc_channel, duty).await;
793+
794+
// Since DMA is closed before timer Capture Compare Event trigger DMA is turn off,
795+
// this can almost always trigger a DMA FIFO error.
796+
//
797+
// optional TODO:
798+
// clean FEIF after disable UDE
799+
if !original_cc_dma_enabled {
800+
self.set_cc_dma_enable_state(cc_channel, false);
801+
}
802+
803+
if !original_cc_dma_on_update {
804+
self.set_cc_dma_selection(Ccds::ON_COMPARE)
805+
}
806+
}
807+
808+
async fn waveform_helper(
809+
&mut self,
810+
dma: Peri<'_, impl dma::Channel>,
811+
req: dma::Request,
812+
channel: Channel,
813+
duty: &[u16],
814+
) {
815+
let original_duty_state = self.get_compare_value(channel);
816+
let original_enable_state = self.get_channel_enable_state(channel);
817+
818+
if !original_enable_state {
819+
self.enable_channel(channel, true);
820+
}
821+
822+
unsafe {
823+
#[cfg(not(any(bdma, gpdma)))]
824+
use crate::dma::{Burst, FifoThreshold};
825+
use crate::dma::{Transfer, TransferOptions};
826+
827+
let dma_transfer_option = TransferOptions {
828+
#[cfg(not(any(bdma, gpdma)))]
829+
fifo_threshold: Some(FifoThreshold::Full),
830+
#[cfg(not(any(bdma, gpdma)))]
831+
mburst: Burst::Incr8,
832+
..Default::default()
833+
};
834+
835+
match self.bits() {
836+
TimerBits::Bits16 => {
837+
Transfer::new_write(
838+
dma,
839+
req,
840+
duty,
841+
self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16,
842+
dma_transfer_option,
843+
)
844+
.await
845+
}
846+
#[cfg(not(any(stm32l0)))]
847+
TimerBits::Bits32 => {
848+
#[cfg(not(any(bdma, gpdma)))]
849+
panic!("unsupported timer bits");
850+
851+
#[cfg(any(bdma, gpdma))]
852+
Transfer::new_write(
853+
dma,
854+
req,
855+
duty,
856+
self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u32,
857+
dma_transfer_option,
858+
)
859+
.await
860+
}
861+
};
862+
};
863+
864+
// restore output compare state
865+
if !original_enable_state {
866+
self.enable_channel(channel, false);
867+
}
868+
869+
self.set_compare_value(channel, original_duty_state);
870+
}
871+
659872
/// Get capture value for a channel.
660873
pub fn get_capture_value(&self, channel: Channel) -> u32 {
661874
self.get_compare_value(channel)

0 commit comments

Comments
 (0)