From d45376583386272bc49fecc7eed8951067f84ac8 Mon Sep 17 00:00:00 2001 From: Eicke Hecht Date: Mon, 24 Nov 2025 21:42:54 +0100 Subject: feat: Implement basic ring buffered PWM channel --- embassy-stm32/src/timer/low_level.rs | 2 - embassy-stm32/src/timer/ringbuffered.rs | 151 +++++++++++++++++++++++++++----- embassy-stm32/src/timer/simple_pwm.rs | 35 ++++++++ 3 files changed, 163 insertions(+), 25 deletions(-) diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 307d614bf..2cee5f1f5 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -813,8 +813,6 @@ impl<'d, T: GeneralInstance4Channel> Timer<'d, T> { pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { - #[cfg(any(bdma, gpdma))] - panic!("unsupported DMA"); use crate::pac::timer::vals::Ccds; diff --git a/embassy-stm32/src/timer/ringbuffered.rs b/embassy-stm32/src/timer/ringbuffered.rs index d20c5d532..bb602f8a7 100644 --- a/embassy-stm32/src/timer/ringbuffered.rs +++ b/embassy-stm32/src/timer/ringbuffered.rs @@ -1,19 +1,142 @@ //! RingBuffered PWM driver. use core::mem::ManuallyDrop; +use core::task::Waker; use super::low_level::Timer; -use super::{Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; -use crate::Peri; -use crate::dma::ringbuffer::WritableDmaRingBuffer; -use super::simple_pwm::SimplePwm; +use super::{Channel, GeneralInstance4Channel}; +use crate::dma::ringbuffer::Error; +use crate::dma::WritableRingBuffer; pub struct RingBufferedPwmChannel<'d, T: GeneralInstance4Channel> { timer: ManuallyDrop>, - ring_buf: WritableDmaRingBuffer<'d, u8>, + ring_buf: WritableRingBuffer<'d, u16>, channel: Channel, } +impl<'d, T: GeneralInstance4Channel> RingBufferedPwmChannel<'d, T> { + pub(crate) fn new(timer: ManuallyDrop>, channel: Channel, ring_buf: WritableRingBuffer<'d, u16>) -> Self { + Self { + timer, + ring_buf, + channel, + } + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.ring_buf.start() + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ring_buf.clear() + } + + /// Write elements directly to the raw buffer. This can be used to fill the buffer before starting the DMA transfer. + pub fn write_immediate(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { + self.ring_buf.write_immediate(buf) + } + + /// Write elements from the ring buffer + /// Return a tuple of the length written and the length remaining in the buffer + pub fn write(&mut self, buf: &[u16]) -> Result<(usize, usize), Error> { + self.ring_buf.write(buf) + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, buffer: &[u16]) -> Result { + self.ring_buf.write_exact(buffer).await + } + + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self) -> Result { + self.ring_buf.wait_write_error().await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + self.ring_buf.len() + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ring_buf.capacity() + } + + /// Set a waker to be woken when at least one byte is send. + pub fn set_waker(&mut self, waker: &Waker) { + self.ring_buf.set_waker(waker) + } + + /// Request the DMA to reset. The configuration for this channel will not be preserved. + /// + /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. + pub fn request_reset(&mut self) { + self.ring_buf.request_reset() + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until is_running returns false. + pub fn request_pause(&mut self) { + self.ring_buf.request_pause() + } + + /// Return whether DMA is still running. + /// + /// If this returns false, it can be because either the transfer finished, or it was requested to stop early with request_stop. + pub fn is_running(&mut self) -> bool { + self.ring_buf.is_running() + } + + /// Stop the DMA transfer and await until the buffer is empty. + /// + /// This disables the DMA transfer's circular mode so that the transfer stops when all available data has been written. + /// + /// This is designed to be used with streaming output data such as the I2S/SAI or DAC. + pub async fn stop(&mut self) { + self.ring_buf.stop().await + } + + /// Enable the given channel. + pub fn enable(&mut self) { + self.timer.enable_channel(self.channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.timer.enable_channel(self.channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.timer.get_channel_enable_state(self.channel) + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn max_duty_cycle(&self) -> u16 { + let max = self.timer.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 + } + + /// Set the output polarity for a given channel. + pub fn set_polarity(&mut self, polarity: super::low_level::OutputPolarity) { + self.timer.set_output_polarity(self.channel, polarity); + } + + /// Set the output compare mode for a given channel. + pub fn set_output_compare_mode(&mut self, mode: super::low_level::OutputCompareMode) { + self.timer.set_output_compare_mode(self.channel, mode); + } +} + /// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { /// Channel 1 @@ -26,22 +149,4 @@ pub struct RingBufferedPwmChannels<'d, T: GeneralInstance4Channel> { pub ch4: RingBufferedPwmChannel<'d, T>, } -/// Simple PWM driver. -pub struct RingBufferedPwm<'d, T: GeneralInstance4Channel> { - inner: Timer<'d, T>, -} - -impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { - pub fn into_ring_buffered_channel(mut self, tx_dma: Peri<'_, impl super::Dma>, dma_buf: &'d mut [u8]) -> RingBufferedPwmChannel<'d> { - assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); - let ring_buf = WritableDmaRingBuffer::new(dma_buf); - let channel = C::CHANNEL; - RingBufferedPwmChannel { - timer: unsafe { self.inner.clone_unchecked() }, - channel, - ring_buf - } - // let ring_buf = WriteableRingBuffer::new(); - } -} diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 56d00ea59..53e0345ea 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -4,8 +4,13 @@ use core::marker::PhantomData; use core::mem::ManuallyDrop; use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; +use super::ringbuffered::RingBufferedPwmChannel; use super::{Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, TimerChannel, TimerPin}; +use crate::dma::WritableRingBuffer; use crate::Peri; +#[cfg(not(any(bdma, gpdma)))] +use crate::dma::{Burst, FifoThreshold}; +use crate::dma::TransferOptions; #[cfg(gpio_v2)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; @@ -374,6 +379,36 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { pub async fn waveform_continuous(&mut self, dma: Peri<'_, impl super::Dma>, duty: &[u16]) { self.inner.waveform_continuous(dma, duty).await; } + pub fn into_ring_buffered_channel(self, tx_dma: Peri<'d, impl super::Dma>, dma_buf: &'d mut [u16]) -> RingBufferedPwmChannel<'d, T> { + assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); + + let channel = C::CHANNEL; + let request = tx_dma.request(); + + let opts = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; + + let ring_buf = unsafe { + WritableRingBuffer::new( + tx_dma, + request, + self.inner.regs_gp16().ccr(channel.index()).as_ptr() as *mut u16, + dma_buf, + opts, + ) + }; + + RingBufferedPwmChannel::new( + unsafe { self.inner.clone_unchecked() }, + channel, + ring_buf + ) + } } impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { -- cgit