diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index 058b8a0fc..de03827e9 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -1008,6 +1008,7 @@ fn main() {
(("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
(("dac", "CH1"), quote!(crate::dac::DacDma1)),
(("dac", "CH2"), quote!(crate::dac::DacDma2)),
+ (("timer", "UP"), quote!(crate::timer::UpDma)),
]
.into();
@@ -1023,6 +1024,16 @@ fn main() {
}
if let Some(tr) = signals.get(&(regs.kind, ch.signal)) {
+ // TIM6 of stm32f334 is special, DMA channel for TIM6 depending on SYSCFG state
+ if chip_name.starts_with("stm32f334") && p.name == "TIM6" {
+ continue;
+ }
+
+ // TIM6 of stm32f378 is special, DMA channel for TIM6 depending on SYSCFG state
+ if chip_name.starts_with("stm32f378") && p.name == "TIM6" {
+ continue;
+ }
+
let peri = format_ident!("{}", p.name);
let channel = if let Some(channel) = &ch.channel {
diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs
index 74120adad..05a0564a3 100644
--- a/embassy-stm32/src/timer/mod.rs
+++ b/embassy-stm32/src/timer/mod.rs
@@ -91,7 +91,12 @@ pub(crate) mod sealed {
/// Enable/disable the update interrupt.
fn enable_update_interrupt(&mut self, enable: bool) {
- Self::regs().dier().write(|r| r.set_uie(enable));
+ Self::regs().dier().modify(|r| r.set_uie(enable));
+ }
+
+ /// Enable/disable the update dma.
+ fn enable_update_dma(&mut self, enable: bool) {
+ Self::regs().dier().modify(|r| r.set_ude(enable));
}
/// Enable/disable autoreload preload.
@@ -288,6 +293,14 @@ pub(crate) mod sealed {
fn get_compare_value(&self, channel: Channel) -> u16 {
Self::regs_gp16().ccr(channel.index()).read().ccr()
}
+
+ /// Set output compare preload.
+ fn set_output_compare_preload(&mut self, channel: Channel, preload: bool) {
+ let channel_index = channel.index();
+ Self::regs_gp16()
+ .ccmr_output(channel_index / 2)
+ .modify(|w| w.set_ocpe(channel_index % 2, preload));
+ }
}
/// Capture/Compare 16-bit timer instance with complementary pin support.
@@ -676,3 +689,6 @@ foreach_interrupt! {
}
};
}
+
+// Update Event trigger DMA for every timer
+dma_trait!(UpDma, Basic16bitInstance);
diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs
index e6072aa15..1819c7c55 100644
--- a/embassy-stm32/src/timer/simple_pwm.rs
+++ b/embassy-stm32/src/timer/simple_pwm.rs
@@ -55,11 +55,12 @@ channel_impl!(new_ch3, Ch3, Channel3Pin);
channel_impl!(new_ch4, Ch4, Channel4Pin);
/// Simple PWM driver.
-pub struct SimplePwm<'d, T> {
+pub struct SimplePwm<'d, T, Dma> {
inner: PeripheralRef<'d, T>,
+ dma: PeripheralRef<'d, Dma>,
}
-impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
+impl<'d, T: CaptureCompare16bitInstance, Dma> SimplePwm<'d, T, Dma> {
/// Create a new simple PWM driver.
pub fn new(
tim: impl Peripheral
+ 'd,
@@ -69,16 +70,22 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
_ch4: Option>,
freq: Hertz,
counting_mode: CountingMode,
+ dma: impl Peripheral + 'd,
) -> Self {
- Self::new_inner(tim, freq, counting_mode)
+ Self::new_inner(tim, freq, counting_mode, dma)
}
- fn new_inner(tim: impl Peripheral
+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self {
- into_ref!(tim);
+ fn new_inner(
+ tim: impl Peripheral
+ 'd,
+ freq: Hertz,
+ counting_mode: CountingMode,
+ dma: impl Peripheral
+ 'd,
+ ) -> Self {
+ into_ref!(tim, dma);
T::enable_and_reset();
- let mut this = Self { inner: tim };
+ let mut this = Self { inner: tim, dma };
this.inner.set_counting_mode(counting_mode);
this.set_frequency(freq);
@@ -86,14 +93,13 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
this.inner.enable_outputs();
- this.inner
- .set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1);
- this.inner
- .set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1);
- this.inner
- .set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1);
- this.inner
- .set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1);
+ [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
+ .iter()
+ .for_each(|&channel| {
+ this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
+ this.inner.set_output_compare_preload(channel, true)
+ });
+
this
}
@@ -141,7 +147,71 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
}
}
-impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> {
+impl<'d, T: CaptureCompare16bitInstance + Basic16bitInstance, Dma> SimplePwm<'d, T, Dma>
+where
+ Dma: super::UpDma,
+{
+ /// Generate a sequence of PWM waveform
+ pub async fn gen_waveform(&mut self, channel: Channel, duty: &[u16]) {
+ duty.iter().all(|v| v.le(&self.get_max_duty()));
+
+ self.inner.enable_update_dma(true);
+
+ #[cfg_attr(any(stm32f334, stm32f378), allow(clippy::let_unit_value))]
+ let req = self.dma.request();
+
+ self.enable(channel);
+
+ #[cfg(not(any(bdma, gpdma)))]
+ let dma_regs = self.dma.regs();
+ #[cfg(not(any(bdma, gpdma)))]
+ let isr_num = self.dma.num() / 4;
+ #[cfg(not(any(bdma, gpdma)))]
+ let isr_bit = self.dma.num() % 4;
+
+ #[cfg(not(any(bdma, gpdma)))]
+ // clean DMA FIFO error before a transfer
+ if dma_regs.isr(isr_num).read().feif(isr_bit) {
+ dma_regs.ifcr(isr_num).write(|v| v.set_feif(isr_bit, 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::Incr8,
+ ..Default::default()
+ };
+
+ Transfer::new_write(
+ &mut self.dma,
+ req,
+ duty,
+ T::regs_gp16().ccr(channel.index()).as_ptr() as *mut _,
+ dma_transfer_option,
+ )
+ .await
+ };
+
+ self.disable(channel);
+
+ self.inner.enable_update_dma(false);
+
+ #[cfg(not(any(bdma, gpdma)))]
+ // Since DMA is closed before timer update event trigger DMA is turn off, it will almost always trigger a DMA FIFO error.
+ // Thus, we will always clean DMA FEIF after each transfer
+ if dma_regs.isr(isr_num).read().feif(isr_bit) {
+ dma_regs.ifcr(isr_num).write(|v| v.set_feif(isr_bit, true));
+ }
+ }
+}
+
+impl<'d, T: CaptureCompare16bitInstance, Dma> embedded_hal_02::Pwm for SimplePwm<'d, T, Dma> {
type Channel = Channel;
type Time = Hertz;
type Duty = u16;
diff --git a/examples/stm32f4/src/bin/pwm.rs b/examples/stm32f4/src/bin/pwm.rs
index 8844a9f0e..92bc42ec8 100644
--- a/examples/stm32f4/src/bin/pwm.rs
+++ b/examples/stm32f4/src/bin/pwm.rs
@@ -3,6 +3,7 @@
use defmt::*;
use embassy_executor::Spawner;
+use embassy_stm32::dma;
use embassy_stm32::gpio::OutputType;
use embassy_stm32::time::khz;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
@@ -16,7 +17,16 @@ async fn main(_spawner: Spawner) {
info!("Hello World!");
let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull);
- let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default());
+ let mut pwm = SimplePwm::new(
+ p.TIM1,
+ Some(ch1),
+ None,
+ None,
+ None,
+ khz(10),
+ Default::default(),
+ dma::NoDma,
+ );
let max = pwm.get_max_duty();
pwm.enable(Channel::Ch1);
diff --git a/examples/stm32f4/src/bin/ws2812_pwm_dma.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs
similarity index 50%
rename from examples/stm32f4/src/bin/ws2812_pwm_dma.rs
rename to examples/stm32f4/src/bin/ws2812_pwm.rs
index 4458b643f..973743e49 100644
--- a/examples/stm32f4/src/bin/ws2812_pwm_dma.rs
+++ b/examples/stm32f4/src/bin/ws2812_pwm.rs
@@ -2,15 +2,9 @@
// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered.
//
// The idea is that the data rate of ws2812 is 800 kHz, and it use different duty ratio to represent bit 0 and bit 1.
-// Thus we can set TIM overflow at 800 kHz, and let TIM Update Event trigger a DMA transfer, then let DMA change CCR value,
-// such that pwm duty ratio meet the bit representation of ws2812.
+// Thus we can set TIM overflow at 800 kHz, and change duty ratio of TIM to meet the bit representation of ws2812.
//
-// You may want to modify TIM CCR with Cortex core directly,
-// but according to my test, Cortex core will need to run far more than 100 MHz to catch up with TIM.
-// Thus we need to use a DMA.
-//
-// This demo is a combination of HAL, PAC, and manually invoke `dma::Transfer`.
-// If you need a simpler way to control ws2812, you may want to take a look at `ws2812_spi.rs` file, which make use of SPI.
+// you may also want to take a look at `ws2812_spi.rs` file, which make use of SPI instead.
//
// Warning:
// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn.
@@ -20,7 +14,6 @@
use embassy_executor::Spawner;
use embassy_stm32::gpio::OutputType;
-use embassy_stm32::pac;
use embassy_stm32::time::khz;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
use embassy_stm32::timer::{Channel, CountingMode};
@@ -52,7 +45,7 @@ async fn main(_spawner: Spawner) {
device_config.rcc.sys = Sysclk::PLL1_P;
}
- let mut dp = embassy_stm32::init(device_config);
+ let dp = embassy_stm32::init(device_config);
let mut ws2812_pwm = SimplePwm::new(
dp.TIM3,
@@ -62,6 +55,7 @@ async fn main(_spawner: Spawner) {
None,
khz(800), // data rate of ws2812
CountingMode::EdgeAlignedUp,
+ dp.DMA1_CH2,
);
// construct ws2812 non-return-to-zero (NRZ) code bit by bit
@@ -89,62 +83,19 @@ async fn main(_spawner: Spawner) {
let pwm_channel = Channel::Ch1;
- // PAC level hacking, enable output compare preload
- // keep output waveform integrity
- pac::TIM3
- .ccmr_output(pwm_channel.index())
- .modify(|v| v.set_ocpe(0, true));
-
// make sure PWM output keep low on first start
ws2812_pwm.set_duty(pwm_channel, 0);
- {
- use embassy_stm32::dma::{Burst, FifoThreshold, Transfer, TransferOptions};
+ // flip color at 2 Hz
+ let mut ticker = Ticker::every(Duration::from_millis(500));
- // configure FIFO and MBURST of DMA, to minimize DMA occupation on AHB/APB
- let mut dma_transfer_option = TransferOptions::default();
- dma_transfer_option.fifo_threshold = Some(FifoThreshold::Full);
- dma_transfer_option.mburst = Burst::Incr8;
-
- // flip color at 2 Hz
- let mut ticker = Ticker::every(Duration::from_millis(500));
-
- loop {
- for &color in color_list {
- // start PWM output
- ws2812_pwm.enable(pwm_channel);
-
- // PAC level hacking, enable timer-update-event trigger DMA
- pac::TIM3.dier().modify(|v| v.set_ude(true));
-
- unsafe {
- Transfer::new_write(
- // with &mut, we can easily reuse same DMA channel multiple times
- &mut dp.DMA1_CH2,
- 5,
- color,
- pac::TIM3.ccr(pwm_channel.index()).as_ptr() as *mut _,
- dma_transfer_option,
- )
- .await;
-
- // Turn off timer-update-event trigger DMA as soon as possible.
- // Then clean the FIFO Error Flag if set.
- pac::TIM3.dier().modify(|v| v.set_ude(false));
- if pac::DMA1.isr(0).read().feif(2) {
- pac::DMA1.ifcr(0).write(|v| v.set_feif(2, true));
- }
-
- // ws2812 need at least 50 us low level input to confirm the input data and change it's state
- Timer::after_micros(50).await;
- }
-
- // stop PWM output for saving some energy
- ws2812_pwm.disable(pwm_channel);
-
- // wait until ticker tick
- ticker.next().await;
- }
+ loop {
+ for &color in color_list {
+ ws2812_pwm.gen_waveform(Channel::Ch1, color).await;
+ // ws2812 need at least 50 us low level input to confirm the input data and change it's state
+ Timer::after_micros(50).await;
+ // wait until ticker tick
+ ticker.next().await;
}
}
}
diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs
index d4809a481..9fa004c3e 100644
--- a/examples/stm32g4/src/bin/pwm.rs
+++ b/examples/stm32g4/src/bin/pwm.rs
@@ -3,6 +3,7 @@
use defmt::*;
use embassy_executor::Spawner;
+use embassy_stm32::dma;
use embassy_stm32::gpio::OutputType;
use embassy_stm32::time::khz;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
@@ -16,7 +17,16 @@ async fn main(_spawner: Spawner) {
info!("Hello World!");
let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull);
- let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default());
+ let mut pwm = SimplePwm::new(
+ p.TIM1,
+ Some(ch1),
+ None,
+ None,
+ None,
+ khz(10),
+ Default::default(),
+ dma::NoDma,
+ );
let max = pwm.get_max_duty();
pwm.enable(Channel::Ch1);
diff --git a/examples/stm32h7/src/bin/pwm.rs b/examples/stm32h7/src/bin/pwm.rs
index 1e48ba67b..de155fc94 100644
--- a/examples/stm32h7/src/bin/pwm.rs
+++ b/examples/stm32h7/src/bin/pwm.rs
@@ -7,7 +7,7 @@ use embassy_stm32::gpio::OutputType;
use embassy_stm32::time::khz;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
use embassy_stm32::timer::Channel;
-use embassy_stm32::Config;
+use embassy_stm32::{dma, Config};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
@@ -38,7 +38,16 @@ async fn main(_spawner: Spawner) {
info!("Hello World!");
let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull);
- let mut pwm = SimplePwm::new(p.TIM3, Some(ch1), None, None, None, khz(10), Default::default());
+ let mut pwm = SimplePwm::new(
+ p.TIM3,
+ Some(ch1),
+ None,
+ None,
+ None,
+ khz(10),
+ Default::default(),
+ dma::NoDma,
+ );
let max = pwm.get_max_duty();
pwm.enable(Channel::Ch1);