aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/build.rs11
-rw-r--r--embassy-stm32/src/timer/mod.rs39
-rw-r--r--embassy-stm32/src/timer/simple_pwm.rs96
-rw-r--r--examples/stm32f4/src/bin/ws2812_pwm.rs (renamed from examples/stm32f4/src/bin/ws2812_pwm_dma.rs)75
4 files changed, 146 insertions, 75 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..d07fd2776 100644
--- a/embassy-stm32/src/timer/mod.rs
+++ b/embassy-stm32/src/timer/mod.rs
@@ -91,7 +91,17 @@ 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));
100 }
101
102 /// Get the update dma enable/disable state.
103 fn get_update_dma_state(&self) -> bool {
104 Self::regs().dier().read().ude()
95 } 105 }
96 106
97 /// Enable/disable autoreload preload. 107 /// Enable/disable autoreload preload.
@@ -269,6 +279,11 @@ pub(crate) mod sealed {
269 Self::regs_gp16().ccer().modify(|w| w.set_cce(channel.index(), enable)); 279 Self::regs_gp16().ccer().modify(|w| w.set_cce(channel.index(), enable));
270 } 280 }
271 281
282 /// Get enable/disable state of a channel
283 fn get_channel_enable_state(&self, channel: Channel) -> bool {
284 Self::regs_gp16().ccer().read().cce(channel.index())
285 }
286
272 /// Set compare value for a channel. 287 /// Set compare value for a channel.
273 fn set_compare_value(&mut self, channel: Channel, value: u16) { 288 fn set_compare_value(&mut self, channel: Channel, value: u16) {
274 Self::regs_gp16().ccr(channel.index()).modify(|w| w.set_ccr(value)); 289 Self::regs_gp16().ccr(channel.index()).modify(|w| w.set_ccr(value));
@@ -288,6 +303,14 @@ pub(crate) mod sealed {
288 fn get_compare_value(&self, channel: Channel) -> u16 { 303 fn get_compare_value(&self, channel: Channel) -> u16 {
289 Self::regs_gp16().ccr(channel.index()).read().ccr() 304 Self::regs_gp16().ccr(channel.index()).read().ccr()
290 } 305 }
306
307 /// Set output compare preload.
308 fn set_output_compare_preload(&mut self, channel: Channel, preload: bool) {
309 let channel_index = channel.index();
310 Self::regs_gp16()
311 .ccmr_output(channel_index / 2)
312 .modify(|w| w.set_ocpe(channel_index % 2, preload));
313 }
291 } 314 }
292 315
293 /// Capture/Compare 16-bit timer instance with complementary pin support. 316 /// Capture/Compare 16-bit timer instance with complementary pin support.
@@ -535,13 +558,16 @@ impl From<OutputPolarity> for bool {
535pub trait Basic16bitInstance: sealed::Basic16bitInstance + 'static {} 558pub trait Basic16bitInstance: sealed::Basic16bitInstance + 'static {}
536 559
537/// Gneral-purpose 16-bit timer instance. 560/// Gneral-purpose 16-bit timer instance.
538pub trait GeneralPurpose16bitInstance: sealed::GeneralPurpose16bitInstance + 'static {} 561pub trait GeneralPurpose16bitInstance: sealed::GeneralPurpose16bitInstance + Basic16bitInstance + 'static {}
539 562
540/// Gneral-purpose 32-bit timer instance. 563/// Gneral-purpose 32-bit timer instance.
541pub trait GeneralPurpose32bitInstance: sealed::GeneralPurpose32bitInstance + 'static {} 564pub trait GeneralPurpose32bitInstance:
565 sealed::GeneralPurpose32bitInstance + GeneralPurpose16bitInstance + 'static
566{
567}
542 568
543/// Advanced control timer instance. 569/// Advanced control timer instance.
544pub trait AdvancedControlInstance: sealed::AdvancedControlInstance + 'static {} 570pub trait AdvancedControlInstance: sealed::AdvancedControlInstance + GeneralPurpose16bitInstance + 'static {}
545 571
546/// Capture/Compare 16-bit timer instance. 572/// Capture/Compare 16-bit timer instance.
547pub trait CaptureCompare16bitInstance: 573pub trait CaptureCompare16bitInstance:
@@ -551,7 +577,7 @@ pub trait CaptureCompare16bitInstance:
551 577
552/// Capture/Compare 16-bit timer instance with complementary pin support. 578/// Capture/Compare 16-bit timer instance with complementary pin support.
553pub trait ComplementaryCaptureCompare16bitInstance: 579pub trait ComplementaryCaptureCompare16bitInstance:
554 sealed::ComplementaryCaptureCompare16bitInstance + AdvancedControlInstance + 'static 580 sealed::ComplementaryCaptureCompare16bitInstance + CaptureCompare16bitInstance + AdvancedControlInstance + 'static
555{ 581{
556} 582}
557 583
@@ -676,3 +702,6 @@ foreach_interrupt! {
676 } 702 }
677 }; 703 };
678} 704}
705
706// Update Event trigger DMA for every timer
707dma_trait!(UpDma, Basic16bitInstance);
diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs
index e6072aa15..80f10424c 100644
--- a/embassy-stm32/src/timer/simple_pwm.rs
+++ b/embassy-stm32/src/timer/simple_pwm.rs
@@ -86,14 +86,13 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
86 86
87 this.inner.enable_outputs(); 87 this.inner.enable_outputs();
88 88
89 this.inner 89 [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
90 .set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1); 90 .iter()
91 this.inner 91 .for_each(|&channel| {
92 .set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1); 92 this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
93 this.inner 93 this.inner.set_output_compare_preload(channel, true)
94 .set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); 94 });
95 this.inner 95
96 .set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1);
97 this 96 this
98 } 97 }
99 98
@@ -107,6 +106,11 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
107 self.inner.enable_channel(channel, false); 106 self.inner.enable_channel(channel, false);
108 } 107 }
109 108
109 /// Check whether given channel is enabled
110 pub fn is_enabled(&self, channel: Channel) -> bool {
111 self.inner.get_channel_enable_state(channel)
112 }
113
110 /// Set PWM frequency. 114 /// Set PWM frequency.
111 /// 115 ///
112 /// Note: when you call this, the max duty value changes, so you will have to 116 /// Note: when you call this, the max duty value changes, so you will have to
@@ -135,10 +139,86 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
135 self.inner.set_compare_value(channel, duty) 139 self.inner.set_compare_value(channel, duty)
136 } 140 }
137 141
142 /// Get the duty for a given channel.
143 ///
144 /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
145 pub fn get_duty(&self, channel: Channel) -> u16 {
146 self.inner.get_compare_value(channel)
147 }
148
138 /// Set the output polarity for a given channel. 149 /// Set the output polarity for a given channel.
139 pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { 150 pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) {
140 self.inner.set_output_polarity(channel, polarity); 151 self.inner.set_output_polarity(channel, polarity);
141 } 152 }
153
154 /// Generate a sequence of PWM waveform
155 ///
156 /// Note:
157 /// you will need to provide corresponding TIMx_UP DMA channel to use this method.
158 pub async fn gen_waveform(
159 &mut self,
160 dma: impl Peripheral<P = impl super::UpDma<T>>,
161 channel: Channel,
162 duty: &[u16],
163 ) {
164 assert!(duty.iter().all(|v| *v <= self.get_max_duty()));
165
166 into_ref!(dma);
167
168 #[allow(clippy::let_unit_value)] // eg. stm32f334
169 let req = dma.request();
170
171 let original_duty_state = self.get_duty(channel);
172 let original_enable_state = self.is_enabled(channel);
173 let original_update_dma_state = self.inner.get_update_dma_state();
174
175 if !original_update_dma_state {
176 self.inner.enable_update_dma(true);
177 }
178
179 if !original_enable_state {
180 self.enable(channel);
181 }
182
183 unsafe {
184 #[cfg(not(any(bdma, gpdma)))]
185 use crate::dma::{Burst, FifoThreshold};
186 use crate::dma::{Transfer, TransferOptions};
187
188 let dma_transfer_option = TransferOptions {
189 #[cfg(not(any(bdma, gpdma)))]
190 fifo_threshold: Some(FifoThreshold::Full),
191 #[cfg(not(any(bdma, gpdma)))]
192 mburst: Burst::Incr8,
193 ..Default::default()
194 };
195
196 Transfer::new_write(
197 &mut dma,
198 req,
199 duty,
200 T::regs_gp16().ccr(channel.index()).as_ptr() as *mut _,
201 dma_transfer_option,
202 )
203 .await
204 };
205
206 // restore output compare state
207 if !original_enable_state {
208 self.disable(channel);
209 }
210
211 self.set_duty(channel, original_duty_state);
212
213 // Since DMA is closed before timer update event trigger DMA is turn off,
214 // this can almost always trigger a DMA FIFO error.
215 //
216 // optional TODO:
217 // clean FEIF after disable UDE
218 if !original_update_dma_state {
219 self.inner.enable_update_dma(false);
220 }
221 }
142} 222}
143 223
144impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> { 224impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> {
diff --git a/examples/stm32f4/src/bin/ws2812_pwm_dma.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs
index 4458b643f..239709253 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};
@@ -89,62 +82,20 @@ async fn main(_spawner: Spawner) {
89 82
90 let pwm_channel = Channel::Ch1; 83 let pwm_channel = Channel::Ch1;
91 84
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 85 // make sure PWM output keep low on first start
99 ws2812_pwm.set_duty(pwm_channel, 0); 86 ws2812_pwm.set_duty(pwm_channel, 0);
100 87
101 { 88 // flip color at 2 Hz
102 use embassy_stm32::dma::{Burst, FifoThreshold, Transfer, TransferOptions}; 89 let mut ticker = Ticker::every(Duration::from_millis(500));
103 90
104 // configure FIFO and MBURST of DMA, to minimize DMA occupation on AHB/APB 91 loop {
105 let mut dma_transfer_option = TransferOptions::default(); 92 for &color in color_list {
106 dma_transfer_option.fifo_threshold = Some(FifoThreshold::Full); 93 // with &mut, we can easily reuse same DMA channel multiple times
107 dma_transfer_option.mburst = Burst::Incr8; 94 ws2812_pwm.gen_waveform(&mut dp.DMA1_CH2, pwm_channel, color).await;
108 95 // ws2812 need at least 50 us low level input to confirm the input data and change it's state
109 // flip color at 2 Hz 96 Timer::after_micros(50).await;
110 let mut ticker = Ticker::every(Duration::from_millis(500)); 97 // wait until ticker tick
111 98 ticker.next().await;
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
145 // wait until ticker tick
146 ticker.next().await;
147 }
148 } 99 }
149 } 100 }
150} 101}