diff options
| author | Elias Hanelt <[email protected]> | 2025-11-25 13:18:00 -0800 |
|---|---|---|
| committer | Elias Hanelt <[email protected]> | 2025-11-25 13:18:00 -0800 |
| commit | 2b219b7cb59ec3e4370edc88538ea3ea996f37b9 (patch) | |
| tree | c602a4b7c39d50ada628f2d715c6cc59f2e35c3a /embassy-stm32/src/timer | |
| parent | 576fb23faabf6df7f2c9ed2039e94d3586a3788f (diff) | |
| parent | 906eaee53f84381dd10583894edf2de67275f083 (diff) | |
Merge remote-tracking branch 'origin/main' into feature/spi-bidi
Diffstat (limited to 'embassy-stm32/src/timer')
| -rw-r--r-- | embassy-stm32/src/timer/complementary_pwm.rs | 108 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/input_capture.rs | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/low_level.rs | 181 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/mod.rs | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/pwm_input.rs | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/ringbuffered.rs | 169 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/simple_pwm.rs | 274 |
7 files changed, 451 insertions, 284 deletions
diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 9a56a41fb..77f19a37b 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs | |||
| @@ -77,8 +77,6 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { | |||
| 77 | 77 | ||
| 78 | this.inner.set_counting_mode(counting_mode); | 78 | this.inner.set_counting_mode(counting_mode); |
| 79 | this.set_frequency(freq); | 79 | this.set_frequency(freq); |
| 80 | this.inner.start(); | ||
| 81 | |||
| 82 | this.inner.enable_outputs(); | 80 | this.inner.enable_outputs(); |
| 83 | 81 | ||
| 84 | [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] | 82 | [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] |
| @@ -89,6 +87,10 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { | |||
| 89 | }); | 87 | }); |
| 90 | this.inner.set_autoreload_preload(true); | 88 | this.inner.set_autoreload_preload(true); |
| 91 | 89 | ||
| 90 | // Generate update event so pre-load registers are written to the shadow registers | ||
| 91 | this.inner.generate_update_event(); | ||
| 92 | this.inner.start(); | ||
| 93 | |||
| 92 | this | 94 | this |
| 93 | } | 95 | } |
| 94 | 96 | ||
| @@ -160,8 +162,8 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { | |||
| 160 | 162 | ||
| 161 | /// Set PWM frequency. | 163 | /// Set PWM frequency. |
| 162 | /// | 164 | /// |
| 163 | /// Note: when you call this, the max duty value changes, so you will have to | 165 | /// Note: that the frequency will not be applied in the timer until an update event |
| 164 | /// call `set_duty` on all channels with the duty calculated based on the new max duty. | 166 | /// occurs. |
| 165 | pub fn set_frequency(&mut self, freq: Hertz) { | 167 | pub fn set_frequency(&mut self, freq: Hertz) { |
| 166 | let multiplier = if self.inner.get_counting_mode().is_center_aligned() { | 168 | let multiplier = if self.inner.get_counting_mode().is_center_aligned() { |
| 167 | 2u8 | 169 | 2u8 |
| @@ -219,59 +221,53 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { | |||
| 219 | /// Note: | 221 | /// Note: |
| 220 | /// you will need to provide corresponding TIMx_UP DMA channel to use this method. | 222 | /// you will need to provide corresponding TIMx_UP DMA channel to use this method. |
| 221 | pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma<T>>, channel: Channel, duty: &[u16]) { | 223 | 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 | 224 | self.inner.enable_channel(channel, true); |
| 223 | let req = dma.request(); | 225 | self.inner.enable_update_dma(true); |
| 224 | 226 | self.inner.setup_update_dma(dma, channel, duty).await; | |
| 225 | let original_duty_state = self.inner.get_compare_value(channel); | 227 | self.inner.enable_update_dma(false); |
| 226 | let original_enable_state = self.inner.get_channel_enable_state(channel); | 228 | } |
| 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 | } | ||
| 264 | |||
| 265 | self.inner.set_compare_value(channel, original_duty_state); | ||
| 266 | 229 | ||
| 267 | // Since DMA is closed before timer update event trigger DMA is turn off, | 230 | /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. |
| 268 | // this can almost always trigger a DMA FIFO error. | 231 | /// |
| 269 | // | 232 | /// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers |
| 270 | // optional TODO: | 233 | /// in sequence on each update event (UEV). The data is written via the DMAR register using the |
| 271 | // clean FEIF after disable UDE | 234 | /// DMA base address (DBA) and burst length (DBL) configured in the DCR register. |
| 272 | if !original_update_dma_state { | 235 | /// |
| 273 | self.inner.enable_update_dma(false); | 236 | /// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row |
| 274 | } | 237 | /// represents a single update event and each column corresponds to a specific timer channel (starting |
| 238 | /// from `starting_channel` up to and including `ending_channel`). | ||
| 239 | /// | ||
| 240 | /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like: | ||
| 241 | /// | ||
| 242 | /// ```rust,ignore | ||
| 243 | /// let dma_buf: [u16; 16] = [ | ||
| 244 | /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1 | ||
| 245 | /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2 | ||
| 246 | /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3 | ||
| 247 | /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4 | ||
| 248 | /// ]; | ||
| 249 | /// ``` | ||
| 250 | /// | ||
| 251 | /// Each group of `N` values (where `N` is number of channels) is transferred on one update event, | ||
| 252 | /// updating the duty cycles of all selected channels simultaneously. | ||
| 253 | /// | ||
| 254 | /// Note: | ||
| 255 | /// You will need to provide corresponding `TIMx_UP` DMA channel to use this method. | ||
| 256 | /// Also be aware that embassy timers use one of timers internally. It is possible to | ||
| 257 | /// switch this timer by using `time-driver-timX` feature. | ||
| 258 | /// | ||
| 259 | pub async fn waveform_up_multi_channel( | ||
| 260 | &mut self, | ||
| 261 | dma: Peri<'_, impl super::UpDma<T>>, | ||
| 262 | starting_channel: Channel, | ||
| 263 | ending_channel: Channel, | ||
| 264 | duty: &[u16], | ||
| 265 | ) { | ||
| 266 | self.inner.enable_update_dma(true); | ||
| 267 | self.inner | ||
| 268 | .setup_update_dma_burst(dma, starting_channel, ending_channel, duty) | ||
| 269 | .await; | ||
| 270 | self.inner.enable_update_dma(false); | ||
| 275 | } | 271 | } |
| 276 | } | 272 | } |
| 277 | 273 | ||
diff --git a/embassy-stm32/src/timer/input_capture.rs b/embassy-stm32/src/timer/input_capture.rs index 2a4ec2db0..9cf0f8c34 100644 --- a/embassy-stm32/src/timer/input_capture.rs +++ b/embassy-stm32/src/timer/input_capture.rs | |||
| @@ -60,6 +60,7 @@ impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { | |||
| 60 | this.inner.set_counting_mode(counting_mode); | 60 | this.inner.set_counting_mode(counting_mode); |
| 61 | this.inner.set_tick_freq(freq); | 61 | this.inner.set_tick_freq(freq); |
| 62 | this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details | 62 | this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details |
| 63 | this.inner.generate_update_event(); | ||
| 63 | this.inner.start(); | 64 | this.inner.start(); |
| 64 | 65 | ||
| 65 | // enable NVIC interrupt | 66 | // enable NVIC interrupt |
diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 0122fe4f7..aba08081f 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs | |||
| @@ -13,6 +13,7 @@ use embassy_hal_internal::Peri; | |||
| 13 | pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource}; | 13 | pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource}; |
| 14 | 14 | ||
| 15 | use super::*; | 15 | use super::*; |
| 16 | use crate::dma::{Transfer, WritableRingBuffer}; | ||
| 16 | use crate::pac::timer::vals; | 17 | use crate::pac::timer::vals; |
| 17 | use crate::rcc; | 18 | use crate::rcc; |
| 18 | use crate::time::Hertz; | 19 | use crate::time::Hertz; |
| @@ -272,6 +273,17 @@ impl<'d, T: CoreInstance> Timer<'d, T> { | |||
| 272 | self.regs_core().cr1().modify(|r| r.set_cen(true)); | 273 | self.regs_core().cr1().modify(|r| r.set_cen(true)); |
| 273 | } | 274 | } |
| 274 | 275 | ||
| 276 | /// Generate timer update event from software. | ||
| 277 | /// | ||
| 278 | /// Set URS to avoid generating interrupt or DMA request. This update event is only | ||
| 279 | /// used to load value from pre-load registers. If called when the timer is running, | ||
| 280 | /// it may disrupt the output waveform. | ||
| 281 | pub fn generate_update_event(&self) { | ||
| 282 | self.regs_core().cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); | ||
| 283 | self.regs_core().egr().write(|r| r.set_ug(true)); | ||
| 284 | self.regs_core().cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); | ||
| 285 | } | ||
| 286 | |||
| 275 | /// Stop the timer. | 287 | /// Stop the timer. |
| 276 | pub fn stop(&self) { | 288 | pub fn stop(&self) { |
| 277 | self.regs_core().cr1().modify(|r| r.set_cen(false)); | 289 | self.regs_core().cr1().modify(|r| r.set_cen(false)); |
| @@ -322,10 +334,6 @@ impl<'d, T: CoreInstance> Timer<'d, T> { | |||
| 322 | let regs = self.regs_core(); | 334 | let regs = self.regs_core(); |
| 323 | regs.psc().write_value(psc); | 335 | regs.psc().write_value(psc); |
| 324 | regs.arr().write(|r| r.set_arr(arr)); | 336 | regs.arr().write(|r| r.set_arr(arr)); |
| 325 | |||
| 326 | regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); | ||
| 327 | regs.egr().write(|r| r.set_ug(true)); | ||
| 328 | regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); | ||
| 329 | } | 337 | } |
| 330 | #[cfg(not(stm32l0))] | 338 | #[cfg(not(stm32l0))] |
| 331 | TimerBits::Bits32 => { | 339 | TimerBits::Bits32 => { |
| @@ -335,10 +343,6 @@ impl<'d, T: CoreInstance> Timer<'d, T> { | |||
| 335 | let regs = self.regs_gp32_unchecked(); | 343 | let regs = self.regs_gp32_unchecked(); |
| 336 | regs.psc().write_value(psc); | 344 | regs.psc().write_value(psc); |
| 337 | regs.arr().write_value(arr); | 345 | regs.arr().write_value(arr); |
| 338 | |||
| 339 | regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); | ||
| 340 | regs.egr().write(|r| r.set_ug(true)); | ||
| 341 | regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); | ||
| 342 | } | 346 | } |
| 343 | } | 347 | } |
| 344 | } | 348 | } |
| @@ -656,6 +660,167 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { | |||
| 656 | } | 660 | } |
| 657 | } | 661 | } |
| 658 | 662 | ||
| 663 | /// Setup a ring buffer for the channel | ||
| 664 | pub fn setup_ring_buffer<'a>( | ||
| 665 | &mut self, | ||
| 666 | dma: Peri<'a, impl super::UpDma<T>>, | ||
| 667 | channel: Channel, | ||
| 668 | dma_buf: &'a mut [u16], | ||
| 669 | ) -> WritableRingBuffer<'a, u16> { | ||
| 670 | #[allow(clippy::let_unit_value)] // eg. stm32f334 | ||
| 671 | let req = dma.request(); | ||
| 672 | |||
| 673 | unsafe { | ||
| 674 | use crate::dma::TransferOptions; | ||
| 675 | #[cfg(not(any(bdma, gpdma)))] | ||
| 676 | use crate::dma::{Burst, FifoThreshold}; | ||
| 677 | |||
| 678 | let dma_transfer_option = TransferOptions { | ||
| 679 | #[cfg(not(any(bdma, gpdma)))] | ||
| 680 | fifo_threshold: Some(FifoThreshold::Full), | ||
| 681 | #[cfg(not(any(bdma, gpdma)))] | ||
| 682 | mburst: Burst::Incr8, | ||
| 683 | ..Default::default() | ||
| 684 | }; | ||
| 685 | |||
| 686 | WritableRingBuffer::new( | ||
| 687 | dma, | ||
| 688 | req, | ||
| 689 | self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, | ||
| 690 | dma_buf, | ||
| 691 | dma_transfer_option, | ||
| 692 | ) | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | /// Generate a sequence of PWM waveform | ||
| 697 | /// | ||
| 698 | /// Note: | ||
| 699 | /// you will need to provide corresponding TIMx_UP DMA channel to use this method. | ||
| 700 | pub fn setup_update_dma<'a>( | ||
| 701 | &mut self, | ||
| 702 | dma: Peri<'a, impl super::UpDma<T>>, | ||
| 703 | channel: Channel, | ||
| 704 | duty: &'a [u16], | ||
| 705 | ) -> Transfer<'a> { | ||
| 706 | #[allow(clippy::let_unit_value)] // eg. stm32f334 | ||
| 707 | let req = dma.request(); | ||
| 708 | |||
| 709 | unsafe { | ||
| 710 | #[cfg(not(any(bdma, gpdma)))] | ||
| 711 | use crate::dma::{Burst, FifoThreshold}; | ||
| 712 | use crate::dma::{Transfer, TransferOptions}; | ||
| 713 | |||
| 714 | let dma_transfer_option = TransferOptions { | ||
| 715 | #[cfg(not(any(bdma, gpdma)))] | ||
| 716 | fifo_threshold: Some(FifoThreshold::Full), | ||
| 717 | #[cfg(not(any(bdma, gpdma)))] | ||
| 718 | mburst: Burst::Incr8, | ||
| 719 | ..Default::default() | ||
| 720 | }; | ||
| 721 | |||
| 722 | match self.bits() { | ||
| 723 | TimerBits::Bits16 => Transfer::new_write( | ||
| 724 | dma, | ||
| 725 | req, | ||
| 726 | duty, | ||
| 727 | self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, | ||
| 728 | dma_transfer_option, | ||
| 729 | ), | ||
| 730 | #[cfg(not(any(stm32l0)))] | ||
| 731 | TimerBits::Bits32 => { | ||
| 732 | #[cfg(not(any(bdma, gpdma)))] | ||
| 733 | panic!("unsupported timer bits"); | ||
| 734 | |||
| 735 | #[cfg(any(bdma, gpdma))] | ||
| 736 | Transfer::new_write( | ||
| 737 | dma, | ||
| 738 | req, | ||
| 739 | duty, | ||
| 740 | self.regs_1ch().ccr(channel.index()).as_ptr() as *mut u32, | ||
| 741 | dma_transfer_option, | ||
| 742 | ) | ||
| 743 | } | ||
| 744 | } | ||
| 745 | } | ||
| 746 | } | ||
| 747 | |||
| 748 | /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. | ||
| 749 | /// | ||
| 750 | /// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers | ||
| 751 | /// in sequence on each update event (UEV). The data is written via the DMAR register using the | ||
| 752 | /// DMA base address (DBA) and burst length (DBL) configured in the DCR register. | ||
| 753 | /// | ||
| 754 | /// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row | ||
| 755 | /// represents a single update event and each column corresponds to a specific timer channel (starting | ||
| 756 | /// from `starting_channel` up to and including `ending_channel`). | ||
| 757 | /// | ||
| 758 | /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like: | ||
| 759 | /// | ||
| 760 | /// ```rust,ignore | ||
| 761 | /// let dma_buf: [u16; 16] = [ | ||
| 762 | /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1 | ||
| 763 | /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2 | ||
| 764 | /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3 | ||
| 765 | /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4 | ||
| 766 | /// ]; | ||
| 767 | /// ``` | ||
| 768 | /// | ||
| 769 | /// Each group of `N` values (where `N` is number of channels) is transferred on one update event, | ||
| 770 | /// updating the duty cycles of all selected channels simultaneously. | ||
| 771 | /// | ||
| 772 | /// Note: | ||
| 773 | /// You will need to provide corresponding `TIMx_UP` DMA channel to use this method. | ||
| 774 | /// Also be aware that embassy timers use one of timers internally. It is possible to | ||
| 775 | /// switch this timer by using `time-driver-timX` feature. | ||
| 776 | /// | ||
| 777 | pub fn setup_update_dma_burst<'a>( | ||
| 778 | &mut self, | ||
| 779 | dma: Peri<'a, impl super::UpDma<T>>, | ||
| 780 | starting_channel: Channel, | ||
| 781 | ending_channel: Channel, | ||
| 782 | duty: &'a [u16], | ||
| 783 | ) -> Transfer<'a> { | ||
| 784 | let cr1_addr = self.regs_gp16().cr1().as_ptr() as u32; | ||
| 785 | let start_ch_index = starting_channel.index(); | ||
| 786 | let end_ch_index = ending_channel.index(); | ||
| 787 | |||
| 788 | assert!(start_ch_index <= end_ch_index); | ||
| 789 | |||
| 790 | let ccrx_addr = self.regs_gp16().ccr(start_ch_index).as_ptr() as u32; | ||
| 791 | self.regs_gp16() | ||
| 792 | .dcr() | ||
| 793 | .modify(|w| w.set_dba(((ccrx_addr - cr1_addr) / 4) as u8)); | ||
| 794 | self.regs_gp16() | ||
| 795 | .dcr() | ||
| 796 | .modify(|w| w.set_dbl((end_ch_index - start_ch_index) as u8)); | ||
| 797 | |||
| 798 | #[allow(clippy::let_unit_value)] // eg. stm32f334 | ||
| 799 | let req = dma.request(); | ||
| 800 | |||
| 801 | unsafe { | ||
| 802 | #[cfg(not(any(bdma, gpdma)))] | ||
| 803 | use crate::dma::{Burst, FifoThreshold}; | ||
| 804 | use crate::dma::{Transfer, TransferOptions}; | ||
| 805 | |||
| 806 | let dma_transfer_option = TransferOptions { | ||
| 807 | #[cfg(not(any(bdma, gpdma)))] | ||
| 808 | fifo_threshold: Some(FifoThreshold::Full), | ||
| 809 | #[cfg(not(any(bdma, gpdma)))] | ||
| 810 | mburst: Burst::Incr4, | ||
| 811 | ..Default::default() | ||
| 812 | }; | ||
| 813 | |||
| 814 | Transfer::new_write( | ||
| 815 | dma, | ||
| 816 | req, | ||
| 817 | duty, | ||
| 818 | self.regs_gp16().dmar().as_ptr() as *mut u16, | ||
| 819 | dma_transfer_option, | ||
| 820 | ) | ||
| 821 | } | ||
| 822 | } | ||
| 823 | |||
| 659 | /// Get capture value for a channel. | 824 | /// Get capture value for a channel. |
| 660 | pub fn get_capture_value(&self, channel: Channel) -> u32 { | 825 | pub fn get_capture_value(&self, channel: Channel) -> u32 { |
| 661 | self.get_compare_value(channel) | 826 | self.get_compare_value(channel) |
diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 804d1ef37..3fa363881 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs | |||
| @@ -12,6 +12,7 @@ pub mod low_level; | |||
| 12 | pub mod one_pulse; | 12 | pub mod one_pulse; |
| 13 | pub mod pwm_input; | 13 | pub mod pwm_input; |
| 14 | pub mod qei; | 14 | pub mod qei; |
| 15 | pub mod ringbuffered; | ||
| 15 | pub mod simple_pwm; | 16 | pub mod simple_pwm; |
| 16 | 17 | ||
| 17 | use crate::interrupt; | 18 | use crate::interrupt; |
diff --git a/embassy-stm32/src/timer/pwm_input.rs b/embassy-stm32/src/timer/pwm_input.rs index da8a79b09..057ab011a 100644 --- a/embassy-stm32/src/timer/pwm_input.rs +++ b/embassy-stm32/src/timer/pwm_input.rs | |||
| @@ -47,6 +47,7 @@ impl<'d, T: GeneralInstance4Channel> PwmInput<'d, T> { | |||
| 47 | inner.set_counting_mode(CountingMode::EdgeAlignedUp); | 47 | inner.set_counting_mode(CountingMode::EdgeAlignedUp); |
| 48 | inner.set_tick_freq(freq); | 48 | inner.set_tick_freq(freq); |
| 49 | inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details | 49 | inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details |
| 50 | inner.generate_update_event(); | ||
| 50 | inner.start(); | 51 | inner.start(); |
| 51 | 52 | ||
| 52 | // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.6 | 53 | // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.6 |
diff --git a/embassy-stm32/src/timer/ringbuffered.rs b/embassy-stm32/src/timer/ringbuffered.rs new file mode 100644 index 000000000..e8f97bf59 --- /dev/null +++ b/embassy-stm32/src/timer/ringbuffered.rs | |||
| @@ -0,0 +1,169 @@ | |||
| 1 | //! RingBuffered PWM driver. | ||
| 2 | |||
| 3 | use core::mem::ManuallyDrop; | ||
| 4 | use core::task::Waker; | ||
| 5 | |||
| 6 | use super::low_level::Timer; | ||
| 7 | use super::{Channel, GeneralInstance4Channel}; | ||
| 8 | use crate::dma::WritableRingBuffer; | ||
| 9 | use crate::dma::ringbuffer::Error; | ||
| 10 | |||
| 11 | /// A PWM channel that uses a DMA ring buffer for continuous waveform generation. | ||
| 12 | /// | ||
| 13 | /// This allows you to continuously update PWM duty cycles via DMA without blocking the CPU. | ||
| 14 | /// The ring buffer enables smooth, uninterrupted waveform generation by automatically cycling | ||
| 15 | /// through duty cycle values stored in memory. | ||
| 16 | /// | ||
| 17 | /// You can write new duty cycle values to the ring buffer while it's running, enabling | ||
| 18 | /// dynamic waveform generation for applications like motor control, LED dimming, or audio output. | ||
| 19 | /// | ||
| 20 | /// # Example | ||
| 21 | /// ```ignore | ||
| 22 | /// let mut channel = pwm.ch1().into_ring_buffered_channel(dma_ch, &mut buffer); | ||
| 23 | /// channel.start(); // Start DMA transfer | ||
| 24 | /// channel.write(&[100, 200, 300]).ok(); // Update duty cycles | ||
| 25 | /// ``` | ||
| 26 | pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> { | ||
| 27 | timer: ManuallyDrop<Timer<'d, T>>, | ||
| 28 | ring_buf: WritableRingBuffer<'d, u16>, | ||
| 29 | channel: Channel, | ||
| 30 | } | ||
| 31 | |||
| 32 | impl<'d, T: GeneralInstance4Channel> RingBufferedPwmChannel<'d, T> { | ||
| 33 | pub(crate) fn new( | ||
| 34 | timer: ManuallyDrop<Timer<'d, T>>, | ||
| 35 | channel: Channel, | ||
| 36 | ring_buf: WritableRingBuffer<'d, u16>, | ||
| 37 | ) -> Self { | ||
| 38 | Self { | ||
| 39 | timer, | ||
| 40 | ring_buf, | ||
| 41 | channel, | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | /// Start the ring buffer operation. | ||
| 46 | /// | ||
| 47 | /// You must call this after creating it for it to work. | ||
| 48 | pub fn start(&mut self) { | ||
| 49 | self.ring_buf.start() | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Clear all data in the ring buffer. | ||
| 53 | pub fn clear(&mut self) { | ||
| 54 | self.ring_buf.clear() | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Write elements directly to the raw buffer. This can be used to fill the buffer before starting the DMA transfer. | ||
| 58 | pub fn write_immediate(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { | ||
| 59 | self.ring_buf.write_immediate(buf) | ||
| 60 | } | ||
| 61 | |||
| 62 | /// Write elements from the ring buffer | ||
| 63 | /// Return a tuple of the length written and the length remaining in the buffer | ||
| 64 | pub fn write(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { | ||
| 65 | self.ring_buf.write(buf) | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Write an exact number of elements to the ringbuffer. | ||
| 69 | pub async fn write_exact(&mut self, buffer: &[u16]) -> Result<usize, Error> { | ||
| 70 | self.ring_buf.write_exact(buffer).await | ||
| 71 | } | ||
| 72 | |||
| 73 | /// Wait for any ring buffer write error. | ||
| 74 | pub async fn wait_write_error(&mut self) -> Result<usize, Error> { | ||
| 75 | self.ring_buf.wait_write_error().await | ||
| 76 | } | ||
| 77 | |||
| 78 | /// The current length of the ringbuffer | ||
| 79 | pub fn len(&mut self) -> Result<usize, Error> { | ||
| 80 | self.ring_buf.len() | ||
| 81 | } | ||
| 82 | |||
| 83 | /// The capacity of the ringbuffer | ||
| 84 | pub const fn capacity(&self) -> usize { | ||
| 85 | self.ring_buf.capacity() | ||
| 86 | } | ||
| 87 | |||
| 88 | /// Set a waker to be woken when at least one byte is send. | ||
| 89 | pub fn set_waker(&mut self, waker: &Waker) { | ||
| 90 | self.ring_buf.set_waker(waker) | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Request the DMA to reset. The configuration for this channel will not be preserved. | ||
| 94 | /// | ||
| 95 | /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. | ||
| 96 | pub fn request_reset(&mut self) { | ||
| 97 | self.ring_buf.request_reset() | ||
| 98 | } | ||
| 99 | |||
| 100 | /// Request the transfer to pause, keeping the existing configuration for this channel. | ||
| 101 | /// To restart the transfer, call [`start`](Self::start) again. | ||
| 102 | /// | ||
| 103 | /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. | ||
| 104 | pub fn request_pause(&mut self) { | ||
| 105 | self.ring_buf.request_pause() | ||
| 106 | } | ||
| 107 | |||
| 108 | /// Return whether DMA is still running. | ||
| 109 | /// | ||
| 110 | /// If this returns false, it can be because either the transfer finished, or it was requested to stop early with request_stop. | ||
| 111 | pub fn is_running(&mut self) -> bool { | ||
| 112 | self.ring_buf.is_running() | ||
| 113 | } | ||
| 114 | |||
| 115 | /// Stop the DMA transfer and await until the buffer is empty. | ||
| 116 | /// | ||
| 117 | /// This disables the DMA transfer's circular mode so that the transfer stops when all available data has been written. | ||
| 118 | /// | ||
| 119 | /// This is designed to be used with streaming output data such as the I2S/SAI or DAC. | ||
| 120 | pub async fn stop(&mut self) { | ||
| 121 | self.ring_buf.stop().await | ||
| 122 | } | ||
| 123 | |||
| 124 | /// Enable the given channel. | ||
| 125 | pub fn enable(&mut self) { | ||
| 126 | self.timer.enable_channel(self.channel, true); | ||
| 127 | } | ||
| 128 | |||
| 129 | /// Disable the given channel. | ||
| 130 | pub fn disable(&mut self) { | ||
| 131 | self.timer.enable_channel(self.channel, false); | ||
| 132 | } | ||
| 133 | |||
| 134 | /// Check whether given channel is enabled | ||
| 135 | pub fn is_enabled(&self) -> bool { | ||
| 136 | self.timer.get_channel_enable_state(self.channel) | ||
| 137 | } | ||
| 138 | |||
| 139 | /// Get max duty value. | ||
| 140 | /// | ||
| 141 | /// This value depends on the configured frequency and the timer's clock rate from RCC. | ||
| 142 | pub fn max_duty_cycle(&self) -> u16 { | ||
| 143 | let max = self.timer.get_max_compare_value(); | ||
| 144 | assert!(max < u16::MAX as u32); | ||
| 145 | max as u16 + 1 | ||
| 146 | } | ||
| 147 | |||
| 148 | /// Set the output polarity for a given channel. | ||
| 149 | pub fn set_polarity(&mut self, polarity: super::low_level::OutputPolarity) { | ||
| 150 | self.timer.set_output_polarity(self.channel, polarity); | ||
| 151 | } | ||
| 152 | |||
| 153 | /// Set the output compare mode for a given channel. | ||
| 154 | pub fn set_output_compare_mode(&mut self, mode: super::low_level::OutputCompareMode) { | ||
| 155 | self.timer.set_output_compare_mode(self.channel, mode); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | /// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. | ||
| 160 | pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { | ||
| 161 | /// Channel 1 | ||
| 162 | pub ch1: RingBufferedPwmChannel<'d, T>, | ||
| 163 | /// Channel 2 | ||
| 164 | pub ch2: RingBufferedPwmChannel<'d, T>, | ||
| 165 | /// Channel 3 | ||
| 166 | pub ch3: RingBufferedPwmChannel<'d, T>, | ||
| 167 | /// Channel 4 | ||
| 168 | pub ch4: RingBufferedPwmChannel<'d, T>, | ||
| 169 | } | ||
diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 36303aeb4..484e9fd81 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs | |||
| @@ -4,7 +4,8 @@ use core::marker::PhantomData; | |||
| 4 | use core::mem::ManuallyDrop; | 4 | use core::mem::ManuallyDrop; |
| 5 | 5 | ||
| 6 | use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; | 6 | use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; |
| 7 | use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerBits, TimerChannel, TimerPin}; | 7 | use super::ringbuffered::RingBufferedPwmChannel; |
| 8 | use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; | ||
| 8 | use crate::Peri; | 9 | use crate::Peri; |
| 9 | #[cfg(gpio_v2)] | 10 | #[cfg(gpio_v2)] |
| 10 | use crate::gpio::Pull; | 11 | use crate::gpio::Pull; |
| @@ -158,6 +159,33 @@ impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { | |||
| 158 | pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) { | 159 | pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) { |
| 159 | self.timer.set_output_compare_mode(self.channel, mode); | 160 | self.timer.set_output_compare_mode(self.channel, mode); |
| 160 | } | 161 | } |
| 162 | |||
| 163 | /// Convert this PWM channel into a ring-buffered PWM channel. | ||
| 164 | /// | ||
| 165 | /// This allows continuous PWM waveform generation using a DMA ring buffer. | ||
| 166 | /// The ring buffer enables dynamic updates to the PWM duty cycle without blocking. | ||
| 167 | /// | ||
| 168 | /// # Arguments | ||
| 169 | /// * `tx_dma` - The DMA channel to use for transferring duty cycle values | ||
| 170 | /// * `dma_buf` - The buffer to use as a ring buffer (must be non-empty and <= 65535 elements) | ||
| 171 | /// | ||
| 172 | /// # Panics | ||
| 173 | /// Panics if `dma_buf` is empty or longer than 65535 elements. | ||
| 174 | pub fn into_ring_buffered_channel( | ||
| 175 | mut self, | ||
| 176 | tx_dma: Peri<'d, impl super::UpDma<T>>, | ||
| 177 | dma_buf: &'d mut [u16], | ||
| 178 | ) -> RingBufferedPwmChannel<'d, T> { | ||
| 179 | assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); | ||
| 180 | |||
| 181 | self.timer.enable_update_dma(true); | ||
| 182 | |||
| 183 | RingBufferedPwmChannel::new( | ||
| 184 | unsafe { self.timer.clone_unchecked() }, | ||
| 185 | self.channel, | ||
| 186 | self.timer.setup_ring_buffer(tx_dma, self.channel, dma_buf), | ||
| 187 | ) | ||
| 188 | } | ||
| 161 | } | 189 | } |
| 162 | 190 | ||
| 163 | /// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. | 191 | /// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. |
| @@ -198,7 +226,6 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { | |||
| 198 | this.inner.set_counting_mode(counting_mode); | 226 | this.inner.set_counting_mode(counting_mode); |
| 199 | this.set_frequency(freq); | 227 | this.set_frequency(freq); |
| 200 | this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details | 228 | this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details |
| 201 | this.inner.start(); | ||
| 202 | 229 | ||
| 203 | [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] | 230 | [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] |
| 204 | .iter() | 231 | .iter() |
| @@ -207,6 +234,11 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { | |||
| 207 | 234 | ||
| 208 | this.inner.set_output_compare_preload(channel, true); | 235 | this.inner.set_output_compare_preload(channel, true); |
| 209 | }); | 236 | }); |
| 237 | this.inner.set_autoreload_preload(true); | ||
| 238 | |||
| 239 | // Generate update event so pre-load registers are written to the shadow registers | ||
| 240 | this.inner.generate_update_event(); | ||
| 241 | this.inner.start(); | ||
| 210 | 242 | ||
| 211 | this | 243 | this |
| 212 | } | 244 | } |
| @@ -285,8 +317,8 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { | |||
| 285 | 317 | ||
| 286 | /// Set PWM frequency. | 318 | /// Set PWM frequency. |
| 287 | /// | 319 | /// |
| 288 | /// Note: when you call this, the max duty value changes, so you will have to | 320 | /// Note: that the frequency will not be applied in the timer until an update event |
| 289 | /// call `set_duty` on all channels with the duty calculated based on the new max duty. | 321 | /// occurs. |
| 290 | pub fn set_frequency(&mut self, freq: Hertz) { | 322 | pub fn set_frequency(&mut self, freq: Hertz) { |
| 291 | // TODO: prevent ARR = u16::MAX? | 323 | // TODO: prevent ARR = u16::MAX? |
| 292 | let multiplier = if self.inner.get_counting_mode().is_center_aligned() { | 324 | let multiplier = if self.inner.get_counting_mode().is_center_aligned() { |
| @@ -309,80 +341,14 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { | |||
| 309 | /// Generate a sequence of PWM waveform | 341 | /// Generate a sequence of PWM waveform |
| 310 | /// | 342 | /// |
| 311 | /// Note: | 343 | /// Note: |
| 312 | /// you will need to provide corresponding TIMx_UP DMA channel to use this method. | 344 | /// You will need to provide corresponding `TIMx_UP` DMA channel to use this method. |
| 345 | /// Also be aware that embassy timers use one of timers internally. It is possible to | ||
| 346 | /// switch this timer by using `time-driver-timX` feature. | ||
| 313 | pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma<T>>, channel: Channel, duty: &[u16]) { | 347 | pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma<T>>, channel: Channel, duty: &[u16]) { |
| 314 | #[allow(clippy::let_unit_value)] // eg. stm32f334 | 348 | self.inner.enable_channel(channel, true); |
| 315 | let req = dma.request(); | 349 | self.inner.enable_update_dma(true); |
| 316 | 350 | self.inner.setup_update_dma(dma, channel, duty).await; | |
| 317 | let original_duty_state = self.channel(channel).current_duty_cycle(); | 351 | self.inner.enable_update_dma(false); |
| 318 | let original_enable_state = self.channel(channel).is_enabled(); | ||
| 319 | let original_update_dma_state = self.inner.get_update_dma_state(); | ||
| 320 | |||
| 321 | if !original_update_dma_state { | ||
| 322 | self.inner.enable_update_dma(true); | ||
| 323 | } | ||
| 324 | |||
| 325 | if !original_enable_state { | ||
| 326 | self.channel(channel).enable(); | ||
| 327 | } | ||
| 328 | |||
| 329 | unsafe { | ||
| 330 | #[cfg(not(any(bdma, gpdma)))] | ||
| 331 | use crate::dma::{Burst, FifoThreshold}; | ||
| 332 | use crate::dma::{Transfer, TransferOptions}; | ||
| 333 | |||
| 334 | let dma_transfer_option = TransferOptions { | ||
| 335 | #[cfg(not(any(bdma, gpdma)))] | ||
| 336 | fifo_threshold: Some(FifoThreshold::Full), | ||
| 337 | #[cfg(not(any(bdma, gpdma)))] | ||
| 338 | mburst: Burst::Incr8, | ||
| 339 | ..Default::default() | ||
| 340 | }; | ||
| 341 | |||
| 342 | match self.inner.bits() { | ||
| 343 | TimerBits::Bits16 => { | ||
| 344 | Transfer::new_write( | ||
| 345 | dma, | ||
| 346 | req, | ||
| 347 | duty, | ||
| 348 | self.inner.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, | ||
| 349 | dma_transfer_option, | ||
| 350 | ) | ||
| 351 | .await | ||
| 352 | } | ||
| 353 | #[cfg(not(any(stm32l0)))] | ||
| 354 | TimerBits::Bits32 => { | ||
| 355 | #[cfg(not(any(bdma, gpdma)))] | ||
| 356 | panic!("unsupported timer bits"); | ||
| 357 | |||
| 358 | #[cfg(any(bdma, gpdma))] | ||
| 359 | Transfer::new_write( | ||
| 360 | dma, | ||
| 361 | req, | ||
| 362 | duty, | ||
| 363 | self.inner.regs_1ch().ccr(channel.index()).as_ptr() as *mut u32, | ||
| 364 | dma_transfer_option, | ||
| 365 | ) | ||
| 366 | .await | ||
| 367 | } | ||
| 368 | }; | ||
| 369 | }; | ||
| 370 | |||
| 371 | // restore output compare state | ||
| 372 | if !original_enable_state { | ||
| 373 | self.channel(channel).disable(); | ||
| 374 | } | ||
| 375 | |||
| 376 | self.channel(channel).set_duty_cycle(original_duty_state); | ||
| 377 | |||
| 378 | // Since DMA is closed before timer update event trigger DMA is turn off, | ||
| 379 | // this can almost always trigger a DMA FIFO error. | ||
| 380 | // | ||
| 381 | // optional TODO: | ||
| 382 | // clean FEIF after disable UDE | ||
| 383 | if !original_update_dma_state { | ||
| 384 | self.inner.enable_update_dma(false); | ||
| 385 | } | ||
| 386 | } | 352 | } |
| 387 | 353 | ||
| 388 | /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. | 354 | /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. |
| @@ -397,18 +363,23 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { | |||
| 397 | /// | 363 | /// |
| 398 | /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like: | 364 | /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like: |
| 399 | /// | 365 | /// |
| 366 | /// ```rust,ignore | ||
| 400 | /// let dma_buf: [u16; 16] = [ | 367 | /// let dma_buf: [u16; 16] = [ |
| 401 | /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1 | 368 | /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1 |
| 402 | /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2 | 369 | /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2 |
| 403 | /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3 | 370 | /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3 |
| 404 | /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4 | 371 | /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4 |
| 405 | /// ]; | 372 | /// ]; |
| 373 | /// ``` | ||
| 406 | /// | 374 | /// |
| 407 | /// Each group of N values (where N = number of channels) is transferred on one update event, | 375 | /// Each group of `N` values (where `N` is number of channels) is transferred on one update event, |
| 408 | /// updating the duty cycles of all selected channels simultaneously. | 376 | /// updating the duty cycles of all selected channels simultaneously. |
| 409 | /// | 377 | /// |
| 410 | /// Note: | 378 | /// Note: |
| 411 | /// you will need to provide corresponding TIMx_UP DMA channel to use this method. | 379 | /// You will need to provide corresponding `TIMx_UP` DMA channel to use this method. |
| 380 | /// Also be aware that embassy timers use one of timers internally. It is possible to | ||
| 381 | /// switch this timer by using `time-driver-timX` feature. | ||
| 382 | /// | ||
| 412 | pub async fn waveform_up_multi_channel( | 383 | pub async fn waveform_up_multi_channel( |
| 413 | &mut self, | 384 | &mut self, |
| 414 | dma: Peri<'_, impl super::UpDma<T>>, | 385 | dma: Peri<'_, impl super::UpDma<T>>, |
| @@ -416,148 +387,11 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { | |||
| 416 | ending_channel: Channel, | 387 | ending_channel: Channel, |
| 417 | duty: &[u16], | 388 | duty: &[u16], |
| 418 | ) { | 389 | ) { |
| 419 | let cr1_addr = self.inner.regs_gp16().cr1().as_ptr() as u32; | 390 | self.inner.enable_update_dma(true); |
| 420 | let start_ch_index = starting_channel.index(); | ||
| 421 | let end_ch_index = ending_channel.index(); | ||
| 422 | |||
| 423 | assert!(start_ch_index <= end_ch_index); | ||
| 424 | |||
| 425 | let ccrx_addr = self.inner.regs_gp16().ccr(start_ch_index).as_ptr() as u32; | ||
| 426 | self.inner | ||
| 427 | .regs_gp16() | ||
| 428 | .dcr() | ||
| 429 | .modify(|w| w.set_dba(((ccrx_addr - cr1_addr) / 4) as u8)); | ||
| 430 | self.inner | 391 | self.inner |
| 431 | .regs_gp16() | 392 | .setup_update_dma_burst(dma, starting_channel, ending_channel, duty) |
| 432 | .dcr() | 393 | .await; |
| 433 | .modify(|w| w.set_dbl((end_ch_index - start_ch_index) as u8)); | 394 | self.inner.enable_update_dma(false); |
| 434 | |||
| 435 | #[allow(clippy::let_unit_value)] // eg. stm32f334 | ||
| 436 | let req = dma.request(); | ||
| 437 | |||
| 438 | let original_update_dma_state = self.inner.get_update_dma_state(); | ||
| 439 | if !original_update_dma_state { | ||
| 440 | self.inner.enable_update_dma(true); | ||
| 441 | } | ||
| 442 | |||
| 443 | unsafe { | ||
| 444 | #[cfg(not(any(bdma, gpdma)))] | ||
| 445 | use crate::dma::{Burst, FifoThreshold}; | ||
| 446 | use crate::dma::{Transfer, TransferOptions}; | ||
| 447 | |||
| 448 | let dma_transfer_option = TransferOptions { | ||
| 449 | #[cfg(not(any(bdma, gpdma)))] | ||
| 450 | fifo_threshold: Some(FifoThreshold::Full), | ||
| 451 | #[cfg(not(any(bdma, gpdma)))] | ||
| 452 | mburst: Burst::Incr4, | ||
| 453 | ..Default::default() | ||
| 454 | }; | ||
| 455 | |||
| 456 | Transfer::new_write( | ||
| 457 | dma, | ||
| 458 | req, | ||
| 459 | duty, | ||
| 460 | self.inner.regs_gp16().dmar().as_ptr() as *mut u16, | ||
| 461 | dma_transfer_option, | ||
| 462 | ) | ||
| 463 | .await | ||
| 464 | }; | ||
| 465 | |||
| 466 | if !original_update_dma_state { | ||
| 467 | self.inner.enable_update_dma(false); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | } | ||
| 471 | |||
| 472 | impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { | ||
| 473 | /// Generate a sequence of PWM waveform | ||
| 474 | pub async fn waveform<C: TimerChannel>(&mut self, dma: Peri<'_, impl super::Dma<T, C>>, duty: &[u16]) { | ||
| 475 | use crate::pac::timer::vals::Ccds; | ||
| 476 | |||
| 477 | #[allow(clippy::let_unit_value)] // eg. stm32f334 | ||
| 478 | let req = dma.request(); | ||
| 479 | |||
| 480 | let cc_channel = C::CHANNEL; | ||
| 481 | |||
| 482 | let original_duty_state = self.channel(cc_channel).current_duty_cycle(); | ||
| 483 | let original_enable_state = self.channel(cc_channel).is_enabled(); | ||
| 484 | let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ON_UPDATE; | ||
| 485 | let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); | ||
| 486 | |||
| 487 | // redirect CC DMA request onto Update Event | ||
| 488 | if !original_cc_dma_on_update { | ||
| 489 | self.inner.set_cc_dma_selection(Ccds::ON_UPDATE) | ||
| 490 | } | ||
| 491 | |||
| 492 | if !original_cc_dma_enabled { | ||
| 493 | self.inner.set_cc_dma_enable_state(cc_channel, true); | ||
| 494 | } | ||
| 495 | |||
| 496 | if !original_enable_state { | ||
| 497 | self.channel(cc_channel).enable(); | ||
| 498 | } | ||
| 499 | |||
| 500 | unsafe { | ||
| 501 | #[cfg(not(any(bdma, gpdma)))] | ||
| 502 | use crate::dma::{Burst, FifoThreshold}; | ||
| 503 | use crate::dma::{Transfer, TransferOptions}; | ||
| 504 | |||
| 505 | let dma_transfer_option = TransferOptions { | ||
| 506 | #[cfg(not(any(bdma, gpdma)))] | ||
| 507 | fifo_threshold: Some(FifoThreshold::Full), | ||
| 508 | #[cfg(not(any(bdma, gpdma)))] | ||
| 509 | mburst: Burst::Incr8, | ||
| 510 | ..Default::default() | ||
| 511 | }; | ||
| 512 | |||
| 513 | match self.inner.bits() { | ||
| 514 | TimerBits::Bits16 => { | ||
| 515 | Transfer::new_write( | ||
| 516 | dma, | ||
| 517 | req, | ||
| 518 | duty, | ||
| 519 | self.inner.regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut u16, | ||
| 520 | dma_transfer_option, | ||
| 521 | ) | ||
| 522 | .await | ||
| 523 | } | ||
| 524 | #[cfg(not(any(stm32l0)))] | ||
| 525 | TimerBits::Bits32 => { | ||
| 526 | #[cfg(not(any(bdma, gpdma)))] | ||
| 527 | panic!("unsupported timer bits"); | ||
| 528 | |||
| 529 | #[cfg(any(bdma, gpdma))] | ||
| 530 | Transfer::new_write( | ||
| 531 | dma, | ||
| 532 | req, | ||
| 533 | duty, | ||
| 534 | self.inner.regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut u32, | ||
| 535 | dma_transfer_option, | ||
| 536 | ) | ||
| 537 | .await | ||
| 538 | } | ||
| 539 | }; | ||
| 540 | }; | ||
| 541 | |||
| 542 | // restore output compare state | ||
| 543 | if !original_enable_state { | ||
| 544 | self.channel(cc_channel).disable(); | ||
| 545 | } | ||
| 546 | |||
| 547 | self.channel(cc_channel).set_duty_cycle(original_duty_state); | ||
| 548 | |||
| 549 | // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, | ||
| 550 | // this can almost always trigger a DMA FIFO error. | ||
| 551 | // | ||
| 552 | // optional TODO: | ||
| 553 | // clean FEIF after disable UDE | ||
| 554 | if !original_cc_dma_enabled { | ||
| 555 | self.inner.set_cc_dma_enable_state(cc_channel, false); | ||
| 556 | } | ||
| 557 | |||
| 558 | if !original_cc_dma_on_update { | ||
| 559 | self.inner.set_cc_dma_selection(Ccds::ON_COMPARE) | ||
| 560 | } | ||
| 561 | } | 395 | } |
| 562 | } | 396 | } |
| 563 | 397 | ||
