aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreZio Pan <[email protected]>2023-12-28 16:23:47 +0800
committereZio Pan <[email protected]>2023-12-28 20:09:12 +0800
commit8c2a6df03b852233ef6c774896cbb00c2a15040f (patch)
treec8bd878540fafbf589055a91993b8e3b01661a09
parenteebfee189a592427423d3a3ad22132d59926a0e8 (diff)
implement PWM waveform generating with DMA
-rw-r--r--embassy-stm32/build.rs11
-rw-r--r--embassy-stm32/src/timer/mod.rs18
-rw-r--r--embassy-stm32/src/timer/simple_pwm.rs100
-rw-r--r--examples/stm32f4/src/bin/pwm.rs12
-rw-r--r--examples/stm32f4/src/bin/ws2812_pwm.rs (renamed from examples/stm32f4/src/bin/ws2812_pwm_dma.rs)75
-rw-r--r--examples/stm32g4/src/bin/pwm.rs12
-rw-r--r--examples/stm32h7/src/bin/pwm.rs13
7 files changed, 159 insertions, 82 deletions
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() {
1008 (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), 1008 (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
1009 (("dac", "CH1"), quote!(crate::dac::DacDma1)), 1009 (("dac", "CH1"), quote!(crate::dac::DacDma1)),
1010 (("dac", "CH2"), quote!(crate::dac::DacDma2)), 1010 (("dac", "CH2"), quote!(crate::dac::DacDma2)),
1011 (("timer", "UP"), quote!(crate::timer::UpDma)),
1011 ] 1012 ]
1012 .into(); 1013 .into();
1013 1014
@@ -1023,6 +1024,16 @@ fn main() {
1023 } 1024 }
1024 1025
1025 if let Some(tr) = signals.get(&(regs.kind, ch.signal)) { 1026 if let Some(tr) = signals.get(&(regs.kind, ch.signal)) {
1027 // TIM6 of stm32f334 is special, DMA channel for TIM6 depending on SYSCFG state
1028 if chip_name.starts_with("stm32f334") && p.name == "TIM6" {
1029 continue;
1030 }
1031
1032 // TIM6 of stm32f378 is special, DMA channel for TIM6 depending on SYSCFG state
1033 if chip_name.starts_with("stm32f378") && p.name == "TIM6" {
1034 continue;
1035 }
1036
1026 let peri = format_ident!("{}", p.name); 1037 let peri = format_ident!("{}", p.name);
1027 1038
1028 let channel = if let Some(channel) = &ch.channel { 1039 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 {
91 91
92 /// Enable/disable the update interrupt. 92 /// Enable/disable the update interrupt.
93 fn enable_update_interrupt(&mut self, enable: bool) { 93 fn enable_update_interrupt(&mut self, enable: bool) {
94 Self::regs().dier().write(|r| r.set_uie(enable)); 94 Self::regs().dier().modify(|r| r.set_uie(enable));
95 }
96
97 /// Enable/disable the update dma.
98 fn enable_update_dma(&mut self, enable: bool) {
99 Self::regs().dier().modify(|r| r.set_ude(enable));
95 } 100 }
96 101
97 /// Enable/disable autoreload preload. 102 /// Enable/disable autoreload preload.
@@ -288,6 +293,14 @@ pub(crate) mod sealed {
288 fn get_compare_value(&self, channel: Channel) -> u16 { 293 fn get_compare_value(&self, channel: Channel) -> u16 {
289 Self::regs_gp16().ccr(channel.index()).read().ccr() 294 Self::regs_gp16().ccr(channel.index()).read().ccr()
290 } 295 }
296
297 /// Set output compare preload.
298 fn set_output_compare_preload(&mut self, channel: Channel, preload: bool) {
299 let channel_index = channel.index();
300 Self::regs_gp16()
301 .ccmr_output(channel_index / 2)
302 .modify(|w| w.set_ocpe(channel_index % 2, preload));
303 }
291 } 304 }
292 305
293 /// Capture/Compare 16-bit timer instance with complementary pin support. 306 /// Capture/Compare 16-bit timer instance with complementary pin support.
@@ -676,3 +689,6 @@ foreach_interrupt! {
676 } 689 }
677 }; 690 };
678} 691}
692
693// Update Event trigger DMA for every timer
694dma_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);
55channel_impl!(new_ch4, Ch4, Channel4Pin); 55channel_impl!(new_ch4, Ch4, Channel4Pin);
56 56
57/// Simple PWM driver. 57/// Simple PWM driver.
58pub struct SimplePwm<'d, T> { 58pub struct SimplePwm<'d, T, Dma> {
59 inner: PeripheralRef<'d, T>, 59 inner: PeripheralRef<'d, T>,
60 dma: PeripheralRef<'d, Dma>,
60} 61}
61 62
62impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { 63impl<'d, T: CaptureCompare16bitInstance, Dma> SimplePwm<'d, T, Dma> {
63 /// Create a new simple PWM driver. 64 /// Create a new simple PWM driver.
64 pub fn new( 65 pub fn new(
65 tim: impl Peripheral<P = T> + 'd, 66 tim: impl Peripheral<P = T> + 'd,
@@ -69,16 +70,22 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
69 _ch4: Option<PwmPin<'d, T, Ch4>>, 70 _ch4: Option<PwmPin<'d, T, Ch4>>,
70 freq: Hertz, 71 freq: Hertz,
71 counting_mode: CountingMode, 72 counting_mode: CountingMode,
73 dma: impl Peripheral<P = Dma> + 'd,
72 ) -> Self { 74 ) -> Self {
73 Self::new_inner(tim, freq, counting_mode) 75 Self::new_inner(tim, freq, counting_mode, dma)
74 } 76 }
75 77
76 fn new_inner(tim: impl Peripheral<P = T> + 'd, freq: Hertz, counting_mode: CountingMode) -> Self { 78 fn new_inner(
77 into_ref!(tim); 79 tim: impl Peripheral<P = T> + 'd,
80 freq: Hertz,
81 counting_mode: CountingMode,
82 dma: impl Peripheral<P = Dma> + 'd,
83 ) -> Self {
84 into_ref!(tim, dma);
78 85
79 T::enable_and_reset(); 86 T::enable_and_reset();
80 87
81 let mut this = Self { inner: tim }; 88 let mut this = Self { inner: tim, dma };
82 89
83 this.inner.set_counting_mode(counting_mode); 90 this.inner.set_counting_mode(counting_mode);
84 this.set_frequency(freq); 91 this.set_frequency(freq);
@@ -86,14 +93,13 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
86 93
87 this.inner.enable_outputs(); 94 this.inner.enable_outputs();
88 95
89 this.inner 96 [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
90 .set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1); 97 .iter()
91 this.inner 98 .for_each(|&channel| {
92 .set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1); 99 this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
93 this.inner 100 this.inner.set_output_compare_preload(channel, true)
94 .set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); 101 });
95 this.inner 102
96 .set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1);
97 this 103 this
98 } 104 }
99 105
@@ -141,7 +147,71 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
141 } 147 }
142} 148}
143 149
144impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> { 150impl<'d, T: CaptureCompare16bitInstance + Basic16bitInstance, Dma> SimplePwm<'d, T, Dma>
151where
152 Dma: super::UpDma<T>,
153{
154 /// Generate a sequence of PWM waveform
155 pub async fn gen_waveform(&mut self, channel: Channel, duty: &[u16]) {
156 duty.iter().all(|v| v.le(&self.get_max_duty()));
157
158 self.inner.enable_update_dma(true);
159
160 #[cfg_attr(any(stm32f334, stm32f378), allow(clippy::let_unit_value))]
161 let req = self.dma.request();
162
163 self.enable(channel);
164
165 #[cfg(not(any(bdma, gpdma)))]
166 let dma_regs = self.dma.regs();
167 #[cfg(not(any(bdma, gpdma)))]
168 let isr_num = self.dma.num() / 4;
169 #[cfg(not(any(bdma, gpdma)))]
170 let isr_bit = self.dma.num() % 4;
171
172 #[cfg(not(any(bdma, gpdma)))]
173 // clean DMA FIFO error before a transfer
174 if dma_regs.isr(isr_num).read().feif(isr_bit) {
175 dma_regs.ifcr(isr_num).write(|v| v.set_feif(isr_bit, true));
176 }
177
178 unsafe {
179 #[cfg(not(any(bdma, gpdma)))]
180 use crate::dma::{Burst, FifoThreshold};
181 use crate::dma::{Transfer, TransferOptions};
182
183 let dma_transfer_option = TransferOptions {
184 #[cfg(not(any(bdma, gpdma)))]
185 fifo_threshold: Some(FifoThreshold::Full),
186 #[cfg(not(any(bdma, gpdma)))]
187 mburst: Burst::Incr8,
188 ..Default::default()
189 };
190
191 Transfer::new_write(
192 &mut self.dma,
193 req,
194 duty,
195 T::regs_gp16().ccr(channel.index()).as_ptr() as *mut _,
196 dma_transfer_option,
197 )
198 .await
199 };
200
201 self.disable(channel);
202
203 self.inner.enable_update_dma(false);
204
205 #[cfg(not(any(bdma, gpdma)))]
206 // Since DMA is closed before timer update event trigger DMA is turn off, it will almost always trigger a DMA FIFO error.
207 // Thus, we will always clean DMA FEIF after each transfer
208 if dma_regs.isr(isr_num).read().feif(isr_bit) {
209 dma_regs.ifcr(isr_num).write(|v| v.set_feif(isr_bit, true));
210 }
211 }
212}
213
214impl<'d, T: CaptureCompare16bitInstance, Dma> embedded_hal_02::Pwm for SimplePwm<'d, T, Dma> {
145 type Channel = Channel; 215 type Channel = Channel;
146 type Time = Hertz; 216 type Time = Hertz;
147 type Duty = u16; 217 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 @@
3 3
4use defmt::*; 4use defmt::*;
5use embassy_executor::Spawner; 5use embassy_executor::Spawner;
6use embassy_stm32::dma;
6use embassy_stm32::gpio::OutputType; 7use embassy_stm32::gpio::OutputType;
7use embassy_stm32::time::khz; 8use embassy_stm32::time::khz;
8use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; 9use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
@@ -16,7 +17,16 @@ async fn main(_spawner: Spawner) {
16 info!("Hello World!"); 17 info!("Hello World!");
17 18
18 let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull); 19 let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull);
19 let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); 20 let mut pwm = SimplePwm::new(
21 p.TIM1,
22 Some(ch1),
23 None,
24 None,
25 None,
26 khz(10),
27 Default::default(),
28 dma::NoDma,
29 );
20 let max = pwm.get_max_duty(); 30 let max = pwm.get_max_duty();
21 pwm.enable(Channel::Ch1); 31 pwm.enable(Channel::Ch1);
22 32
diff --git a/examples/stm32f4/src/bin/ws2812_pwm_dma.rs b/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 @@
2// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered. 2// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered.
3// 3//
4// 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. 4// 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.
5// Thus we can set TIM overflow at 800 kHz, and let TIM Update Event trigger a DMA transfer, then let DMA change CCR value, 5// Thus we can set TIM overflow at 800 kHz, and change duty ratio of TIM to meet the bit representation of ws2812.
6// such that pwm duty ratio meet the bit representation of ws2812.
7// 6//
8// You may want to modify TIM CCR with Cortex core directly, 7// you may also want to take a look at `ws2812_spi.rs` file, which make use of SPI instead.
9// but according to my test, Cortex core will need to run far more than 100 MHz to catch up with TIM.
10// Thus we need to use a DMA.
11//
12// This demo is a combination of HAL, PAC, and manually invoke `dma::Transfer`.
13// 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.
14// 8//
15// Warning: 9// Warning:
16// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn. 10// 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 @@
20 14
21use embassy_executor::Spawner; 15use embassy_executor::Spawner;
22use embassy_stm32::gpio::OutputType; 16use embassy_stm32::gpio::OutputType;
23use embassy_stm32::pac;
24use embassy_stm32::time::khz; 17use embassy_stm32::time::khz;
25use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; 18use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
26use embassy_stm32::timer::{Channel, CountingMode}; 19use embassy_stm32::timer::{Channel, CountingMode};
@@ -52,7 +45,7 @@ async fn main(_spawner: Spawner) {
52 device_config.rcc.sys = Sysclk::PLL1_P; 45 device_config.rcc.sys = Sysclk::PLL1_P;
53 } 46 }
54 47
55 let mut dp = embassy_stm32::init(device_config); 48 let dp = embassy_stm32::init(device_config);
56 49
57 let mut ws2812_pwm = SimplePwm::new( 50 let mut ws2812_pwm = SimplePwm::new(
58 dp.TIM3, 51 dp.TIM3,
@@ -62,6 +55,7 @@ async fn main(_spawner: Spawner) {
62 None, 55 None,
63 khz(800), // data rate of ws2812 56 khz(800), // data rate of ws2812
64 CountingMode::EdgeAlignedUp, 57 CountingMode::EdgeAlignedUp,
58 dp.DMA1_CH2,
65 ); 59 );
66 60
67 // construct ws2812 non-return-to-zero (NRZ) code bit by bit 61 // construct ws2812 non-return-to-zero (NRZ) code bit by bit
@@ -89,62 +83,19 @@ async fn main(_spawner: Spawner) {
89 83
90 let pwm_channel = Channel::Ch1; 84 let pwm_channel = Channel::Ch1;
91 85
92 // PAC level hacking, enable output compare preload
93 // keep output waveform integrity
94 pac::TIM3
95 .ccmr_output(pwm_channel.index())
96 .modify(|v| v.set_ocpe(0, true));
97
98 // make sure PWM output keep low on first start 86 // make sure PWM output keep low on first start
99 ws2812_pwm.set_duty(pwm_channel, 0); 87 ws2812_pwm.set_duty(pwm_channel, 0);
100 88
101 { 89 // flip color at 2 Hz
102 use embassy_stm32::dma::{Burst, FifoThreshold, Transfer, TransferOptions}; 90 let mut ticker = Ticker::every(Duration::from_millis(500));
103
104 // configure FIFO and MBURST of DMA, to minimize DMA occupation on AHB/APB
105 let mut dma_transfer_option = TransferOptions::default();
106 dma_transfer_option.fifo_threshold = Some(FifoThreshold::Full);
107 dma_transfer_option.mburst = Burst::Incr8;
108
109 // flip color at 2 Hz
110 let mut ticker = Ticker::every(Duration::from_millis(500));
111
112 loop {
113 for &color in color_list {
114 // start PWM output
115 ws2812_pwm.enable(pwm_channel);
116
117 // PAC level hacking, enable timer-update-event trigger DMA
118 pac::TIM3.dier().modify(|v| v.set_ude(true));
119
120 unsafe {
121 Transfer::new_write(
122 // with &mut, we can easily reuse same DMA channel multiple times
123 &mut dp.DMA1_CH2,
124 5,
125 color,
126 pac::TIM3.ccr(pwm_channel.index()).as_ptr() as *mut _,
127 dma_transfer_option,
128 )
129 .await;
130
131 // Turn off timer-update-event trigger DMA as soon as possible.
132 // Then clean the FIFO Error Flag if set.
133 pac::TIM3.dier().modify(|v| v.set_ude(false));
134 if pac::DMA1.isr(0).read().feif(2) {
135 pac::DMA1.ifcr(0).write(|v| v.set_feif(2, true));
136 }
137
138 // ws2812 need at least 50 us low level input to confirm the input data and change it's state
139 Timer::after_micros(50).await;
140 }
141
142 // stop PWM output for saving some energy
143 ws2812_pwm.disable(pwm_channel);
144 91
145 // wait until ticker tick 92 loop {
146 ticker.next().await; 93 for &color in color_list {
147 } 94 ws2812_pwm.gen_waveform(Channel::Ch1, color).await;
95 // ws2812 need at least 50 us low level input to confirm the input data and change it's state
96 Timer::after_micros(50).await;
97 // wait until ticker tick
98 ticker.next().await;
148 } 99 }
149 } 100 }
150} 101}
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 @@
3 3
4use defmt::*; 4use defmt::*;
5use embassy_executor::Spawner; 5use embassy_executor::Spawner;
6use embassy_stm32::dma;
6use embassy_stm32::gpio::OutputType; 7use embassy_stm32::gpio::OutputType;
7use embassy_stm32::time::khz; 8use embassy_stm32::time::khz;
8use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; 9use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
@@ -16,7 +17,16 @@ async fn main(_spawner: Spawner) {
16 info!("Hello World!"); 17 info!("Hello World!");
17 18
18 let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull); 19 let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull);
19 let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); 20 let mut pwm = SimplePwm::new(
21 p.TIM1,
22 Some(ch1),
23 None,
24 None,
25 None,
26 khz(10),
27 Default::default(),
28 dma::NoDma,
29 );
20 let max = pwm.get_max_duty(); 30 let max = pwm.get_max_duty();
21 pwm.enable(Channel::Ch1); 31 pwm.enable(Channel::Ch1);
22 32
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;
7use embassy_stm32::time::khz; 7use embassy_stm32::time::khz;
8use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; 8use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
9use embassy_stm32::timer::Channel; 9use embassy_stm32::timer::Channel;
10use embassy_stm32::Config; 10use embassy_stm32::{dma, Config};
11use embassy_time::Timer; 11use embassy_time::Timer;
12use {defmt_rtt as _, panic_probe as _}; 12use {defmt_rtt as _, panic_probe as _};
13 13
@@ -38,7 +38,16 @@ async fn main(_spawner: Spawner) {
38 info!("Hello World!"); 38 info!("Hello World!");
39 39
40 let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull); 40 let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull);
41 let mut pwm = SimplePwm::new(p.TIM3, Some(ch1), None, None, None, khz(10), Default::default()); 41 let mut pwm = SimplePwm::new(
42 p.TIM3,
43 Some(ch1),
44 None,
45 None,
46 None,
47 khz(10),
48 Default::default(),
49 dma::NoDma,
50 );
42 let max = pwm.get_max_duty(); 51 let max = pwm.get_max_duty();
43 pwm.enable(Channel::Ch1); 52 pwm.enable(Channel::Ch1);
44 53