diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2023-04-06 21:33:17 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-04-06 21:33:17 +0000 |
| commit | da8258b7673a52a4639818f9c3f3b7ea241b4799 (patch) | |
| tree | 074787c16ce64c2f6033a4d79487ad8c0321a210 | |
| parent | 60809edf2ce8f3cb91d31398dc347e4193e2e5f2 (diff) | |
| parent | 31ef783ac1add908c4c4506d3ce4e5f6ad9bea56 (diff) | |
Merge #1330
1330: stm32/pwm: add complementary pwm r=Dirbaio a=xoviat
This implements complementary PWM with dead time on many supported targets. The specific dead-time programming functions are passed through directly to the user, which is a bit ugly but the best compromise I could reach for now.
Co-authored-by: xoviat <[email protected]>
| -rw-r--r-- | embassy-stm32/src/pwm/complementary_pwm.rs | 124 | ||||
| -rw-r--r-- | embassy-stm32/src/pwm/mod.rs | 40 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/pwm_complementary.rs | 77 |
3 files changed, 241 insertions, 0 deletions
diff --git a/embassy-stm32/src/pwm/complementary_pwm.rs b/embassy-stm32/src/pwm/complementary_pwm.rs new file mode 100644 index 000000000..13edfbaa3 --- /dev/null +++ b/embassy-stm32/src/pwm/complementary_pwm.rs | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | |||
| 3 | use embassy_hal_common::{into_ref, PeripheralRef}; | ||
| 4 | pub use stm32_metapac::timer::vals::Ckd; | ||
| 5 | |||
| 6 | use super::simple_pwm::*; | ||
| 7 | use super::*; | ||
| 8 | #[allow(unused_imports)] | ||
| 9 | use crate::gpio::sealed::{AFType, Pin}; | ||
| 10 | use crate::gpio::AnyPin; | ||
| 11 | use crate::time::Hertz; | ||
| 12 | use crate::Peripheral; | ||
| 13 | |||
| 14 | pub struct ComplementaryPwmPin<'d, Perip, Channel> { | ||
| 15 | _pin: PeripheralRef<'d, AnyPin>, | ||
| 16 | phantom: PhantomData<(Perip, Channel)>, | ||
| 17 | } | ||
| 18 | |||
| 19 | macro_rules! complementary_channel_impl { | ||
| 20 | ($new_chx:ident, $channel:ident, $pin_trait:ident, $complementary_pin_trait:ident) => { | ||
| 21 | impl<'d, Perip: CaptureCompare16bitInstance> ComplementaryPwmPin<'d, Perip, $channel> { | ||
| 22 | pub fn $new_chx(pin: impl Peripheral<P = impl $complementary_pin_trait<Perip>> + 'd) -> Self { | ||
| 23 | into_ref!(pin); | ||
| 24 | critical_section::with(|_| unsafe { | ||
| 25 | pin.set_low(); | ||
| 26 | pin.set_as_af(pin.af_num(), AFType::OutputPushPull); | ||
| 27 | #[cfg(gpio_v2)] | ||
| 28 | pin.set_speed(crate::gpio::Speed::VeryHigh); | ||
| 29 | }); | ||
| 30 | ComplementaryPwmPin { | ||
| 31 | _pin: pin.map_into(), | ||
| 32 | phantom: PhantomData, | ||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | }; | ||
| 37 | } | ||
| 38 | |||
| 39 | complementary_channel_impl!(new_ch1, Ch1, Channel1Pin, Channel1ComplementaryPin); | ||
| 40 | complementary_channel_impl!(new_ch2, Ch2, Channel2Pin, Channel2ComplementaryPin); | ||
| 41 | complementary_channel_impl!(new_ch3, Ch3, Channel3Pin, Channel3ComplementaryPin); | ||
| 42 | complementary_channel_impl!(new_ch4, Ch4, Channel4Pin, Channel4ComplementaryPin); | ||
| 43 | |||
| 44 | pub struct ComplementaryPwm<'d, T> { | ||
| 45 | inner: PeripheralRef<'d, T>, | ||
| 46 | } | ||
| 47 | |||
| 48 | impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { | ||
| 49 | pub fn new( | ||
| 50 | tim: impl Peripheral<P = T> + 'd, | ||
| 51 | _ch1: Option<PwmPin<'d, T, Ch1>>, | ||
| 52 | _ch1n: Option<ComplementaryPwmPin<'d, T, Ch1>>, | ||
| 53 | _ch2: Option<PwmPin<'d, T, Ch2>>, | ||
| 54 | _ch2n: Option<ComplementaryPwmPin<'d, T, Ch2>>, | ||
| 55 | _ch3: Option<PwmPin<'d, T, Ch3>>, | ||
| 56 | _ch3n: Option<ComplementaryPwmPin<'d, T, Ch3>>, | ||
| 57 | _ch4: Option<PwmPin<'d, T, Ch4>>, | ||
| 58 | _ch4n: Option<ComplementaryPwmPin<'d, T, Ch4>>, | ||
| 59 | freq: Hertz, | ||
| 60 | ) -> Self { | ||
| 61 | Self::new_inner(tim, freq) | ||
| 62 | } | ||
| 63 | |||
| 64 | fn new_inner(tim: impl Peripheral<P = T> + 'd, freq: Hertz) -> Self { | ||
| 65 | into_ref!(tim); | ||
| 66 | |||
| 67 | T::enable(); | ||
| 68 | <T as crate::rcc::sealed::RccPeripheral>::reset(); | ||
| 69 | |||
| 70 | let mut this = Self { inner: tim }; | ||
| 71 | |||
| 72 | this.inner.set_frequency(freq); | ||
| 73 | this.inner.start(); | ||
| 74 | |||
| 75 | unsafe { | ||
| 76 | this.inner.enable_outputs(true); | ||
| 77 | |||
| 78 | this.inner | ||
| 79 | .set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1); | ||
| 80 | this.inner | ||
| 81 | .set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1); | ||
| 82 | this.inner | ||
| 83 | .set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); | ||
| 84 | this.inner | ||
| 85 | .set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1); | ||
| 86 | } | ||
| 87 | this | ||
| 88 | } | ||
| 89 | |||
| 90 | pub fn enable(&mut self, channel: Channel) { | ||
| 91 | unsafe { | ||
| 92 | self.inner.enable_channel(channel, true); | ||
| 93 | self.inner.enable_complementary_channel(channel, true); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn disable(&mut self, channel: Channel) { | ||
| 98 | unsafe { | ||
| 99 | self.inner.enable_complementary_channel(channel, false); | ||
| 100 | self.inner.enable_channel(channel, false); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | pub fn set_freq(&mut self, freq: Hertz) { | ||
| 105 | self.inner.set_frequency(freq); | ||
| 106 | } | ||
| 107 | |||
| 108 | pub fn get_max_duty(&self) -> u16 { | ||
| 109 | unsafe { self.inner.get_max_compare_value() } | ||
| 110 | } | ||
| 111 | |||
| 112 | pub fn set_duty(&mut self, channel: Channel, duty: u16) { | ||
| 113 | assert!(duty < self.get_max_duty()); | ||
| 114 | unsafe { self.inner.set_compare_value(channel, duty) } | ||
| 115 | } | ||
| 116 | |||
| 117 | pub fn set_dead_time_clock_division(&mut self, value: Ckd) { | ||
| 118 | unsafe { self.inner.set_dead_time_clock_division(value) } | ||
| 119 | } | ||
| 120 | |||
| 121 | pub fn set_dead_time_value(&mut self, value: u8) { | ||
| 122 | unsafe { self.inner.set_dead_time_value(value) } | ||
| 123 | } | ||
| 124 | } | ||
diff --git a/embassy-stm32/src/pwm/mod.rs b/embassy-stm32/src/pwm/mod.rs index d3713391c..0bef07089 100644 --- a/embassy-stm32/src/pwm/mod.rs +++ b/embassy-stm32/src/pwm/mod.rs | |||
| @@ -1,5 +1,8 @@ | |||
| 1 | pub mod complementary_pwm; | ||
| 1 | pub mod simple_pwm; | 2 | pub mod simple_pwm; |
| 2 | 3 | ||
| 4 | use stm32_metapac::timer::vals::Ckd; | ||
| 5 | |||
| 3 | #[cfg(feature = "unstable-pac")] | 6 | #[cfg(feature = "unstable-pac")] |
| 4 | pub mod low_level { | 7 | pub mod low_level { |
| 5 | pub use super::sealed::*; | 8 | pub use super::sealed::*; |
| @@ -67,6 +70,14 @@ pub(crate) mod sealed { | |||
| 67 | unsafe fn get_max_compare_value(&self) -> u16; | 70 | unsafe fn get_max_compare_value(&self) -> u16; |
| 68 | } | 71 | } |
| 69 | 72 | ||
| 73 | pub trait ComplementaryCaptureCompare16bitInstance: CaptureCompare16bitInstance { | ||
| 74 | unsafe fn set_dead_time_clock_division(&mut self, value: Ckd); | ||
| 75 | |||
| 76 | unsafe fn set_dead_time_value(&mut self, value: u8); | ||
| 77 | |||
| 78 | unsafe fn enable_complementary_channel(&mut self, channel: Channel, enable: bool); | ||
| 79 | } | ||
| 80 | |||
| 70 | pub trait CaptureCompare32bitInstance: crate::timer::sealed::GeneralPurpose32bitInstance { | 81 | pub trait CaptureCompare32bitInstance: crate::timer::sealed::GeneralPurpose32bitInstance { |
| 71 | unsafe fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); | 82 | unsafe fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); |
| 72 | 83 | ||
| @@ -82,6 +93,12 @@ pub trait CaptureCompare16bitInstance: | |||
| 82 | sealed::CaptureCompare16bitInstance + crate::timer::GeneralPurpose16bitInstance + 'static | 93 | sealed::CaptureCompare16bitInstance + crate::timer::GeneralPurpose16bitInstance + 'static |
| 83 | { | 94 | { |
| 84 | } | 95 | } |
| 96 | |||
| 97 | pub trait ComplementaryCaptureCompare16bitInstance: | ||
| 98 | sealed::ComplementaryCaptureCompare16bitInstance + crate::timer::AdvancedControlInstance + 'static | ||
| 99 | { | ||
| 100 | } | ||
| 101 | |||
| 85 | pub trait CaptureCompare32bitInstance: | 102 | pub trait CaptureCompare32bitInstance: |
| 86 | sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + crate::timer::GeneralPurpose32bitInstance + 'static | 103 | sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + crate::timer::GeneralPurpose32bitInstance + 'static |
| 87 | { | 104 | { |
| @@ -209,6 +226,29 @@ foreach_interrupt! { | |||
| 209 | impl CaptureCompare16bitInstance for crate::peripherals::$inst { | 226 | impl CaptureCompare16bitInstance for crate::peripherals::$inst { |
| 210 | 227 | ||
| 211 | } | 228 | } |
| 229 | |||
| 230 | impl crate::pwm::sealed::ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst { | ||
| 231 | unsafe fn set_dead_time_clock_division(&mut self, value: Ckd) { | ||
| 232 | use crate::timer::sealed::AdvancedControlInstance; | ||
| 233 | Self::regs_advanced().cr1().modify(|w| w.set_ckd(value)); | ||
| 234 | } | ||
| 235 | |||
| 236 | unsafe fn set_dead_time_value(&mut self, value: u8) { | ||
| 237 | use crate::timer::sealed::AdvancedControlInstance; | ||
| 238 | Self::regs_advanced().bdtr().modify(|w| w.set_dtg(value)); | ||
| 239 | } | ||
| 240 | |||
| 241 | unsafe fn enable_complementary_channel(&mut self, channel: Channel, enable: bool) { | ||
| 242 | use crate::timer::sealed::AdvancedControlInstance; | ||
| 243 | Self::regs_advanced() | ||
| 244 | .ccer() | ||
| 245 | .modify(|w| w.set_ccne(channel.raw(), enable)); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst { | ||
| 250 | |||
| 251 | } | ||
| 212 | }; | 252 | }; |
| 213 | } | 253 | } |
| 214 | 254 | ||
diff --git a/examples/stm32f4/src/bin/pwm_complementary.rs b/examples/stm32f4/src/bin/pwm_complementary.rs new file mode 100644 index 000000000..6e17f3fd3 --- /dev/null +++ b/examples/stm32f4/src/bin/pwm_complementary.rs | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::*; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_stm32::pwm::complementary_pwm::{Ckd, ComplementaryPwm, ComplementaryPwmPin}; | ||
| 8 | use embassy_stm32::pwm::simple_pwm::PwmPin; | ||
| 9 | use embassy_stm32::pwm::Channel; | ||
| 10 | use embassy_stm32::time::khz; | ||
| 11 | use embassy_time::{Duration, Timer}; | ||
| 12 | use {defmt_rtt as _, panic_probe as _}; | ||
| 13 | |||
| 14 | #[embassy_executor::main] | ||
| 15 | async fn main(_spawner: Spawner) { | ||
| 16 | let p = embassy_stm32::init(Default::default()); | ||
| 17 | info!("Hello World!"); | ||
| 18 | |||
| 19 | let ch1 = PwmPin::new_ch1(p.PE9); | ||
| 20 | let ch1n = ComplementaryPwmPin::new_ch1(p.PA7); | ||
| 21 | let mut pwm = ComplementaryPwm::new( | ||
| 22 | p.TIM1, | ||
| 23 | Some(ch1), | ||
| 24 | Some(ch1n), | ||
| 25 | None, | ||
| 26 | None, | ||
| 27 | None, | ||
| 28 | None, | ||
| 29 | None, | ||
| 30 | None, | ||
| 31 | khz(10), | ||
| 32 | ); | ||
| 33 | |||
| 34 | /* | ||
| 35 | Dead-time = T_clk * T_dts * T_dtg | ||
| 36 | |||
| 37 | T_dts: | ||
| 38 | This bit-field indicates the division ratio between the timer clock (CK_INT) frequency and the | ||
| 39 | dead-time and sampling clock (tDTS)used by the dead-time generators and the digital filters | ||
| 40 | (ETR, TIx), | ||
| 41 | 00: tDTS=tCK_INT | ||
| 42 | 01: tDTS=2*tCK_INT | ||
| 43 | 10: tDTS=4*tCK_INT | ||
| 44 | |||
| 45 | T_dtg: | ||
| 46 | This bit-field defines the duration of the dead-time inserted between the complementary | ||
| 47 | outputs. DT correspond to this duration. | ||
| 48 | DTG[7:5]=0xx => DT=DTG[7:0]x tdtg with tdtg=tDTS. | ||
| 49 | DTG[7:5]=10x => DT=(64+DTG[5:0])xtdtg with Tdtg=2xtDTS. | ||
| 50 | DTG[7:5]=110 => DT=(32+DTG[4:0])xtdtg with Tdtg=8xtDTS. | ||
| 51 | DTG[7:5]=111 => DT=(32+DTG[4:0])xtdtg with Tdtg=16xtDTS. | ||
| 52 | Example if TDTS=125ns (8MHz), dead-time possible values are: | ||
| 53 | 0 to 15875 ns by 125 ns steps, | ||
| 54 | 16 us to 31750 ns by 250 ns steps, | ||
| 55 | 32 us to 63us by 1 us steps, | ||
| 56 | 64 us to 126 us by 2 us steps | ||
| 57 | */ | ||
| 58 | pwm.set_dead_time_clock_division(Ckd::DIV1); | ||
| 59 | pwm.set_dead_time_value(0); | ||
| 60 | |||
| 61 | let max = pwm.get_max_duty(); | ||
| 62 | pwm.enable(Channel::Ch1); | ||
| 63 | |||
| 64 | info!("PWM initialized"); | ||
| 65 | info!("PWM max duty {}", max); | ||
| 66 | |||
| 67 | loop { | ||
| 68 | pwm.set_duty(Channel::Ch1, 0); | ||
| 69 | Timer::after(Duration::from_millis(300)).await; | ||
| 70 | pwm.set_duty(Channel::Ch1, max / 4); | ||
| 71 | Timer::after(Duration::from_millis(300)).await; | ||
| 72 | pwm.set_duty(Channel::Ch1, max / 2); | ||
| 73 | Timer::after(Duration::from_millis(300)).await; | ||
| 74 | pwm.set_duty(Channel::Ch1, max - 1); | ||
| 75 | Timer::after(Duration::from_millis(300)).await; | ||
| 76 | } | ||
| 77 | } | ||
