diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-02-01 01:02:01 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-02-01 01:02:01 +0000 |
| commit | 7e02389995379f6cc6057530cc5e2c8c9d5fb0b0 (patch) | |
| tree | b38c4604daf076dc08cc8b6e6ca116df83862f7e | |
| parent | 2f7c8faf51617ca76f967bfafd51d9cd079e266a (diff) | |
| parent | b16cc04036da8bcbfe273cd796c092e8e2a074c9 (diff) | |
Merge pull request #2410 from eZioPan/waveform-on-CHx
impl waveform with TIM OC Channel DMA
| -rw-r--r-- | embassy-stm32/build.rs | 21 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/complementary_pwm.rs | 3 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/mod.rs | 40 | ||||
| -rw-r--r-- | embassy-stm32/src/timer/simple_pwm.rs | 93 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/ws2812_pwm.rs | 2 |
5 files changed, 133 insertions, 26 deletions
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index bd4195619..69848762a 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs | |||
| @@ -949,9 +949,9 @@ fn main() { | |||
| 949 | } else if pin.signal.starts_with("INN") { | 949 | } else if pin.signal.starts_with("INN") { |
| 950 | // TODO handle in the future when embassy supports differential measurements | 950 | // TODO handle in the future when embassy supports differential measurements |
| 951 | None | 951 | None |
| 952 | } else if pin.signal.starts_with("IN") && pin.signal.ends_with("b") { | 952 | } else if pin.signal.starts_with("IN") && pin.signal.ends_with('b') { |
| 953 | // we number STM32L1 ADC bank 1 as 0..=31, bank 2 as 32..=63 | 953 | // we number STM32L1 ADC bank 1 as 0..=31, bank 2 as 32..=63 |
| 954 | let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix("b").unwrap(); | 954 | let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix('b').unwrap(); |
| 955 | Some(32u8 + signal.parse::<u8>().unwrap()) | 955 | Some(32u8 + signal.parse::<u8>().unwrap()) |
| 956 | } else if pin.signal.starts_with("IN") { | 956 | } else if pin.signal.starts_with("IN") { |
| 957 | Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) | 957 | Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) |
| @@ -1022,6 +1022,10 @@ fn main() { | |||
| 1022 | (("dac", "CH1"), quote!(crate::dac::DacDma1)), | 1022 | (("dac", "CH1"), quote!(crate::dac::DacDma1)), |
| 1023 | (("dac", "CH2"), quote!(crate::dac::DacDma2)), | 1023 | (("dac", "CH2"), quote!(crate::dac::DacDma2)), |
| 1024 | (("timer", "UP"), quote!(crate::timer::UpDma)), | 1024 | (("timer", "UP"), quote!(crate::timer::UpDma)), |
| 1025 | (("timer", "CH1"), quote!(crate::timer::Ch1Dma)), | ||
| 1026 | (("timer", "CH2"), quote!(crate::timer::Ch2Dma)), | ||
| 1027 | (("timer", "CH3"), quote!(crate::timer::Ch3Dma)), | ||
| 1028 | (("timer", "CH4"), quote!(crate::timer::Ch4Dma)), | ||
| 1025 | ] | 1029 | ] |
| 1026 | .into(); | 1030 | .into(); |
| 1027 | 1031 | ||
| @@ -1037,16 +1041,6 @@ fn main() { | |||
| 1037 | } | 1041 | } |
| 1038 | 1042 | ||
| 1039 | if let Some(tr) = signals.get(&(regs.kind, ch.signal)) { | 1043 | if let Some(tr) = signals.get(&(regs.kind, ch.signal)) { |
| 1040 | // TIM6 of stm32f334 is special, DMA channel for TIM6 depending on SYSCFG state | ||
| 1041 | if chip_name.starts_with("stm32f334") && p.name == "TIM6" { | ||
| 1042 | continue; | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | // TIM6 of stm32f378 is special, DMA channel for TIM6 depending on SYSCFG state | ||
| 1046 | if chip_name.starts_with("stm32f378") && p.name == "TIM6" { | ||
| 1047 | continue; | ||
| 1048 | } | ||
| 1049 | |||
| 1050 | let peri = format_ident!("{}", p.name); | 1044 | let peri = format_ident!("{}", p.name); |
| 1051 | 1045 | ||
| 1052 | let channel = if let Some(channel) = &ch.channel { | 1046 | let channel = if let Some(channel) = &ch.channel { |
| @@ -1205,7 +1199,7 @@ fn main() { | |||
| 1205 | ADC3 and higher are assigned to the adc34 clock in the table | 1199 | ADC3 and higher are assigned to the adc34 clock in the table |
| 1206 | The adc3_common cfg directive is added if ADC3_COMMON exists | 1200 | The adc3_common cfg directive is added if ADC3_COMMON exists |
| 1207 | */ | 1201 | */ |
| 1208 | let has_adc3 = METADATA.peripherals.iter().find(|p| p.name == "ADC3_COMMON").is_some(); | 1202 | let has_adc3 = METADATA.peripherals.iter().any(|p| p.name == "ADC3_COMMON"); |
| 1209 | let set_adc345 = HashSet::from(["ADC3", "ADC4", "ADC5"]); | 1203 | let set_adc345 = HashSet::from(["ADC3", "ADC4", "ADC5"]); |
| 1210 | 1204 | ||
| 1211 | for m in METADATA | 1205 | for m in METADATA |
| @@ -1389,6 +1383,7 @@ fn main() { | |||
| 1389 | 1383 | ||
| 1390 | // ======= | 1384 | // ======= |
| 1391 | // ADC3_COMMON is present | 1385 | // ADC3_COMMON is present |
| 1386 | #[allow(clippy::print_literal)] | ||
| 1392 | if has_adc3 { | 1387 | if has_adc3 { |
| 1393 | println!("cargo:rustc-cfg={}", "adc3_common"); | 1388 | println!("cargo:rustc-cfg={}", "adc3_common"); |
| 1394 | } | 1389 | } |
diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 71d7110b5..eddce0404 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs | |||
| @@ -54,6 +54,7 @@ pub struct ComplementaryPwm<'d, T> { | |||
| 54 | 54 | ||
| 55 | impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { | 55 | impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { |
| 56 | /// Create a new complementary PWM driver. | 56 | /// Create a new complementary PWM driver. |
| 57 | #[allow(clippy::too_many_arguments)] | ||
| 57 | pub fn new( | 58 | pub fn new( |
| 58 | tim: impl Peripheral<P = T> + 'd, | 59 | tim: impl Peripheral<P = T> + 'd, |
| 59 | _ch1: Option<PwmPin<'d, T, Ch1>>, | 60 | _ch1: Option<PwmPin<'d, T, Ch1>>, |
| @@ -165,7 +166,7 @@ impl<'d, T: ComplementaryCaptureCompare16bitInstance> embedded_hal_02::Pwm for C | |||
| 165 | } | 166 | } |
| 166 | 167 | ||
| 167 | fn get_period(&self) -> Self::Time { | 168 | fn get_period(&self) -> Self::Time { |
| 168 | self.inner.get_frequency().into() | 169 | self.inner.get_frequency() |
| 169 | } | 170 | } |
| 170 | 171 | ||
| 171 | fn get_duty(&self, channel: Self::Channel) -> Self::Duty { | 172 | fn get_duty(&self, channel: Self::Channel) -> Self::Duty { |
diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index d07fd2776..210bf7153 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs | |||
| @@ -311,6 +311,26 @@ pub(crate) mod sealed { | |||
| 311 | .ccmr_output(channel_index / 2) | 311 | .ccmr_output(channel_index / 2) |
| 312 | .modify(|w| w.set_ocpe(channel_index % 2, preload)); | 312 | .modify(|w| w.set_ocpe(channel_index % 2, preload)); |
| 313 | } | 313 | } |
| 314 | |||
| 315 | /// Get capture compare DMA selection | ||
| 316 | fn get_cc_dma_selection(&self) -> super::vals::Ccds { | ||
| 317 | Self::regs_gp16().cr2().read().ccds() | ||
| 318 | } | ||
| 319 | |||
| 320 | /// Set capture compare DMA selection | ||
| 321 | fn set_cc_dma_selection(&mut self, ccds: super::vals::Ccds) { | ||
| 322 | Self::regs_gp16().cr2().modify(|w| w.set_ccds(ccds)) | ||
| 323 | } | ||
| 324 | |||
| 325 | /// Get capture compare DMA enable state | ||
| 326 | fn get_cc_dma_enable_state(&self, channel: Channel) -> bool { | ||
| 327 | Self::regs_gp16().dier().read().ccde(channel.index()) | ||
| 328 | } | ||
| 329 | |||
| 330 | /// Set capture compare DMA enable state | ||
| 331 | fn set_cc_dma_enable_state(&mut self, channel: Channel, ccde: bool) { | ||
| 332 | Self::regs_gp16().dier().modify(|w| w.set_ccde(channel.index(), ccde)) | ||
| 333 | } | ||
| 314 | } | 334 | } |
| 315 | 335 | ||
| 316 | /// Capture/Compare 16-bit timer instance with complementary pin support. | 336 | /// Capture/Compare 16-bit timer instance with complementary pin support. |
| @@ -450,20 +470,17 @@ pub enum CountingMode { | |||
| 450 | impl CountingMode { | 470 | impl CountingMode { |
| 451 | /// Return whether this mode is edge-aligned (up or down). | 471 | /// Return whether this mode is edge-aligned (up or down). |
| 452 | pub fn is_edge_aligned(&self) -> bool { | 472 | pub fn is_edge_aligned(&self) -> bool { |
| 453 | match self { | 473 | matches!(self, CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown) |
| 454 | CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown => true, | ||
| 455 | _ => false, | ||
| 456 | } | ||
| 457 | } | 474 | } |
| 458 | 475 | ||
| 459 | /// Return whether this mode is center-aligned. | 476 | /// Return whether this mode is center-aligned. |
| 460 | pub fn is_center_aligned(&self) -> bool { | 477 | pub fn is_center_aligned(&self) -> bool { |
| 461 | match self { | 478 | matches!( |
| 479 | self, | ||
| 462 | CountingMode::CenterAlignedDownInterrupts | 480 | CountingMode::CenterAlignedDownInterrupts |
| 463 | | CountingMode::CenterAlignedUpInterrupts | 481 | | CountingMode::CenterAlignedUpInterrupts |
| 464 | | CountingMode::CenterAlignedBothInterrupts => true, | 482 | | CountingMode::CenterAlignedBothInterrupts |
| 465 | _ => false, | 483 | ) |
| 466 | } | ||
| 467 | } | 484 | } |
| 468 | } | 485 | } |
| 469 | 486 | ||
| @@ -705,3 +722,8 @@ foreach_interrupt! { | |||
| 705 | 722 | ||
| 706 | // Update Event trigger DMA for every timer | 723 | // Update Event trigger DMA for every timer |
| 707 | dma_trait!(UpDma, Basic16bitInstance); | 724 | dma_trait!(UpDma, Basic16bitInstance); |
| 725 | |||
| 726 | dma_trait!(Ch1Dma, CaptureCompare16bitInstance); | ||
| 727 | dma_trait!(Ch2Dma, CaptureCompare16bitInstance); | ||
| 728 | dma_trait!(Ch3Dma, CaptureCompare16bitInstance); | ||
| 729 | dma_trait!(Ch4Dma, CaptureCompare16bitInstance); | ||
diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 83a3e9291..0b4c1225f 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs | |||
| @@ -160,7 +160,7 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { | |||
| 160 | /// | 160 | /// |
| 161 | /// Note: | 161 | /// Note: |
| 162 | /// you will need to provide corresponding TIMx_UP DMA channel to use this method. | 162 | /// you will need to provide corresponding TIMx_UP DMA channel to use this method. |
| 163 | pub async fn gen_waveform( | 163 | pub async fn waveform_up( |
| 164 | &mut self, | 164 | &mut self, |
| 165 | dma: impl Peripheral<P = impl super::UpDma<T>>, | 165 | dma: impl Peripheral<P = impl super::UpDma<T>>, |
| 166 | channel: Channel, | 166 | channel: Channel, |
| @@ -226,6 +226,95 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { | |||
| 226 | } | 226 | } |
| 227 | } | 227 | } |
| 228 | 228 | ||
| 229 | macro_rules! impl_waveform_chx { | ||
| 230 | ($fn_name:ident, $dma_ch:ident, $cc_ch:ident) => { | ||
| 231 | impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { | ||
| 232 | /// Generate a sequence of PWM waveform | ||
| 233 | /// | ||
| 234 | /// Note: | ||
| 235 | /// you will need to provide corresponding TIMx_CHy DMA channel to use this method. | ||
| 236 | pub async fn $fn_name(&mut self, dma: impl Peripheral<P = impl super::$dma_ch<T>>, duty: &[u16]) { | ||
| 237 | use super::vals::Ccds; | ||
| 238 | |||
| 239 | assert!(duty.iter().all(|v| *v <= self.get_max_duty())); | ||
| 240 | |||
| 241 | into_ref!(dma); | ||
| 242 | |||
| 243 | #[allow(clippy::let_unit_value)] // eg. stm32f334 | ||
| 244 | let req = dma.request(); | ||
| 245 | |||
| 246 | let cc_channel = super::Channel::$cc_ch; | ||
| 247 | |||
| 248 | let original_duty_state = self.get_duty(cc_channel); | ||
| 249 | let original_enable_state = self.is_enabled(cc_channel); | ||
| 250 | let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; | ||
| 251 | let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); | ||
| 252 | |||
| 253 | // redirect CC DMA request onto Update Event | ||
| 254 | if !original_cc_dma_on_update { | ||
| 255 | self.inner.set_cc_dma_selection(Ccds::ONUPDATE) | ||
| 256 | } | ||
| 257 | |||
| 258 | if !original_cc_dma_enabled { | ||
| 259 | self.inner.set_cc_dma_enable_state(cc_channel, true); | ||
| 260 | } | ||
| 261 | |||
| 262 | if !original_enable_state { | ||
| 263 | self.enable(cc_channel); | ||
| 264 | } | ||
| 265 | |||
| 266 | unsafe { | ||
| 267 | #[cfg(not(any(bdma, gpdma)))] | ||
| 268 | use crate::dma::{Burst, FifoThreshold}; | ||
| 269 | use crate::dma::{Transfer, TransferOptions}; | ||
| 270 | |||
| 271 | let dma_transfer_option = TransferOptions { | ||
| 272 | #[cfg(not(any(bdma, gpdma)))] | ||
| 273 | fifo_threshold: Some(FifoThreshold::Full), | ||
| 274 | #[cfg(not(any(bdma, gpdma)))] | ||
| 275 | mburst: Burst::Incr8, | ||
| 276 | ..Default::default() | ||
| 277 | }; | ||
| 278 | |||
| 279 | Transfer::new_write( | ||
| 280 | &mut dma, | ||
| 281 | req, | ||
| 282 | duty, | ||
| 283 | T::regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut _, | ||
| 284 | dma_transfer_option, | ||
| 285 | ) | ||
| 286 | .await | ||
| 287 | }; | ||
| 288 | |||
| 289 | // restore output compare state | ||
| 290 | if !original_enable_state { | ||
| 291 | self.disable(cc_channel); | ||
| 292 | } | ||
| 293 | |||
| 294 | self.set_duty(cc_channel, original_duty_state); | ||
| 295 | |||
| 296 | // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, | ||
| 297 | // this can almost always trigger a DMA FIFO error. | ||
| 298 | // | ||
| 299 | // optional TODO: | ||
| 300 | // clean FEIF after disable UDE | ||
| 301 | if !original_cc_dma_enabled { | ||
| 302 | self.inner.set_cc_dma_enable_state(cc_channel, false); | ||
| 303 | } | ||
| 304 | |||
| 305 | if !original_cc_dma_on_update { | ||
| 306 | self.inner.set_cc_dma_selection(Ccds::ONCOMPARE) | ||
| 307 | } | ||
| 308 | } | ||
| 309 | } | ||
| 310 | }; | ||
| 311 | } | ||
| 312 | |||
| 313 | impl_waveform_chx!(waveform_ch1, Ch1Dma, Ch1); | ||
| 314 | impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2); | ||
| 315 | impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3); | ||
| 316 | impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4); | ||
| 317 | |||
| 229 | impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> { | 318 | impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> { |
| 230 | type Channel = Channel; | 319 | type Channel = Channel; |
| 231 | type Time = Hertz; | 320 | type Time = Hertz; |
| @@ -240,7 +329,7 @@ impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, | |||
| 240 | } | 329 | } |
| 241 | 330 | ||
| 242 | fn get_period(&self) -> Self::Time { | 331 | fn get_period(&self) -> Self::Time { |
| 243 | self.inner.get_frequency().into() | 332 | self.inner.get_frequency() |
| 244 | } | 333 | } |
| 245 | 334 | ||
| 246 | fn get_duty(&self, channel: Self::Channel) -> Self::Duty { | 335 | fn get_duty(&self, channel: Self::Channel) -> Self::Duty { |
diff --git a/examples/stm32f4/src/bin/ws2812_pwm.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs index 239709253..6122cea2d 100644 --- a/examples/stm32f4/src/bin/ws2812_pwm.rs +++ b/examples/stm32f4/src/bin/ws2812_pwm.rs | |||
| @@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) { | |||
| 91 | loop { | 91 | loop { |
| 92 | for &color in color_list { | 92 | for &color in color_list { |
| 93 | // with &mut, we can easily reuse same DMA channel multiple times | 93 | // with &mut, we can easily reuse same DMA channel multiple times |
| 94 | ws2812_pwm.gen_waveform(&mut dp.DMA1_CH2, pwm_channel, color).await; | 94 | ws2812_pwm.waveform_up(&mut dp.DMA1_CH2, pwm_channel, color).await; |
| 95 | // ws2812 need at least 50 us low level input to confirm the input data and change it's state | 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; | 96 | Timer::after_micros(50).await; |
| 97 | // wait until ticker tick | 97 | // wait until ticker tick |
