aboutsummaryrefslogtreecommitdiff
path: root/embassy-stm32
diff options
context:
space:
mode:
authorxoviat <[email protected]>2025-11-25 16:39:02 +0000
committerGitHub <[email protected]>2025-11-25 16:39:02 +0000
commit906eaee53f84381dd10583894edf2de67275f083 (patch)
treeae32ee612c0e92ad56af7189f267b1adb7f25c7e /embassy-stm32
parentc0c142d7c8c2a695cccf3bca5e153692fe9ec7cf (diff)
parent309ee44c7484c4d11adc3fbd527536027eac8a94 (diff)
Merge pull request #4946 from xoviat/pwm
add ring buffered channel
Diffstat (limited to 'embassy-stm32')
-rw-r--r--embassy-stm32/CHANGELOG.md1
-rw-r--r--embassy-stm32/src/timer/low_level.rs35
-rw-r--r--embassy-stm32/src/timer/mod.rs1
-rw-r--r--embassy-stm32/src/timer/ringbuffered.rs169
-rw-r--r--embassy-stm32/src/timer/simple_pwm.rs28
5 files changed, 233 insertions, 1 deletions
diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md
index 949ea03b5..d3e5ba48d 100644
--- a/embassy-stm32/CHANGELOG.md
+++ b/embassy-stm32/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7 7
8## Unreleased - ReleaseDate 8## Unreleased - ReleaseDate
9 9
10- feat: Add continuous waveform method to SimplePWM
10- change: remove waveform timer method 11- change: remove waveform timer method
11- change: low power: store stop mode for dma channels 12- change: low power: store stop mode for dma channels
12- fix: Fixed ADC4 enable() for WBA 13- fix: Fixed ADC4 enable() for WBA
diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs
index f986c8dab..aba08081f 100644
--- a/embassy-stm32/src/timer/low_level.rs
+++ b/embassy-stm32/src/timer/low_level.rs
@@ -13,7 +13,7 @@ use embassy_hal_internal::Peri;
13pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource}; 13pub use stm32_metapac::timer::vals::{FilterValue, Mms as MasterMode, Sms as SlaveMode, Ts as TriggerSource};
14 14
15use super::*; 15use super::*;
16use crate::dma::Transfer; 16use crate::dma::{Transfer, WritableRingBuffer};
17use crate::pac::timer::vals; 17use crate::pac::timer::vals;
18use crate::rcc; 18use crate::rcc;
19use crate::time::Hertz; 19use crate::time::Hertz;
@@ -660,6 +660,39 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> {
660 } 660 }
661 } 661 }
662 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
663 /// Generate a sequence of PWM waveform 696 /// Generate a sequence of PWM waveform
664 /// 697 ///
665 /// Note: 698 /// Note:
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;
12pub mod one_pulse; 12pub mod one_pulse;
13pub mod pwm_input; 13pub mod pwm_input;
14pub mod qei; 14pub mod qei;
15pub mod ringbuffered;
15pub mod simple_pwm; 16pub mod simple_pwm;
16 17
17use crate::interrupt; 18use crate::interrupt;
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
3use core::mem::ManuallyDrop;
4use core::task::Waker;
5
6use super::low_level::Timer;
7use super::{Channel, GeneralInstance4Channel};
8use crate::dma::WritableRingBuffer;
9use 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/// ```
26pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> {
27 timer: ManuallyDrop<Timer<'d, T>>,
28 ring_buf: WritableRingBuffer<'d, u16>,
29 channel: Channel,
30}
31
32impl<'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`].
160pub 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 eb1b66358..484e9fd81 100644
--- a/embassy-stm32/src/timer/simple_pwm.rs
+++ b/embassy-stm32/src/timer/simple_pwm.rs
@@ -4,6 +4,7 @@ use core::marker::PhantomData;
4use core::mem::ManuallyDrop; 4use core::mem::ManuallyDrop;
5 5
6use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; 6use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer};
7use super::ringbuffered::RingBufferedPwmChannel;
7use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; 8use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin};
8use crate::Peri; 9use crate::Peri;
9#[cfg(gpio_v2)] 10#[cfg(gpio_v2)]
@@ -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`].