diff --git a/embassy-stm32/src/pwm/complementary_pwm.rs b/embassy-stm32/src/pwm/complementary_pwm.rs
new file mode 100644
index 00000000..13edfbaa
--- /dev/null
+++ b/embassy-stm32/src/pwm/complementary_pwm.rs
@@ -0,0 +1,124 @@
+use core::marker::PhantomData;
+
+use embassy_hal_common::{into_ref, PeripheralRef};
+pub use stm32_metapac::timer::vals::Ckd;
+
+use super::simple_pwm::*;
+use super::*;
+#[allow(unused_imports)]
+use crate::gpio::sealed::{AFType, Pin};
+use crate::gpio::AnyPin;
+use crate::time::Hertz;
+use crate::Peripheral;
+
+pub struct ComplementaryPwmPin<'d, Perip, Channel> {
+ _pin: PeripheralRef<'d, AnyPin>,
+ phantom: PhantomData<(Perip, Channel)>,
+}
+
+macro_rules! complementary_channel_impl {
+ ($new_chx:ident, $channel:ident, $pin_trait:ident, $complementary_pin_trait:ident) => {
+ impl<'d, Perip: CaptureCompare16bitInstance> ComplementaryPwmPin<'d, Perip, $channel> {
+ pub fn $new_chx(pin: impl Peripheral
> + 'd) -> Self {
+ into_ref!(pin);
+ critical_section::with(|_| unsafe {
+ pin.set_low();
+ pin.set_as_af(pin.af_num(), AFType::OutputPushPull);
+ #[cfg(gpio_v2)]
+ pin.set_speed(crate::gpio::Speed::VeryHigh);
+ });
+ ComplementaryPwmPin {
+ _pin: pin.map_into(),
+ phantom: PhantomData,
+ }
+ }
+ }
+ };
+}
+
+complementary_channel_impl!(new_ch1, Ch1, Channel1Pin, Channel1ComplementaryPin);
+complementary_channel_impl!(new_ch2, Ch2, Channel2Pin, Channel2ComplementaryPin);
+complementary_channel_impl!(new_ch3, Ch3, Channel3Pin, Channel3ComplementaryPin);
+complementary_channel_impl!(new_ch4, Ch4, Channel4Pin, Channel4ComplementaryPin);
+
+pub struct ComplementaryPwm<'d, T> {
+ inner: PeripheralRef<'d, T>,
+}
+
+impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> {
+ pub fn new(
+ tim: impl Peripheral
+ 'd,
+ _ch1: Option>,
+ _ch1n: Option>,
+ _ch2: Option>,
+ _ch2n: Option>,
+ _ch3: Option>,
+ _ch3n: Option>,
+ _ch4: Option>,
+ _ch4n: Option>,
+ freq: Hertz,
+ ) -> Self {
+ Self::new_inner(tim, freq)
+ }
+
+ fn new_inner(tim: impl Peripheral + 'd, freq: Hertz) -> Self {
+ into_ref!(tim);
+
+ T::enable();
+ ::reset();
+
+ let mut this = Self { inner: tim };
+
+ this.inner.set_frequency(freq);
+ this.inner.start();
+
+ unsafe {
+ this.inner.enable_outputs(true);
+
+ 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);
+ }
+ this
+ }
+
+ pub fn enable(&mut self, channel: Channel) {
+ unsafe {
+ self.inner.enable_channel(channel, true);
+ self.inner.enable_complementary_channel(channel, true);
+ }
+ }
+
+ pub fn disable(&mut self, channel: Channel) {
+ unsafe {
+ self.inner.enable_complementary_channel(channel, false);
+ self.inner.enable_channel(channel, false);
+ }
+ }
+
+ pub fn set_freq(&mut self, freq: Hertz) {
+ self.inner.set_frequency(freq);
+ }
+
+ pub fn get_max_duty(&self) -> u16 {
+ unsafe { self.inner.get_max_compare_value() }
+ }
+
+ pub fn set_duty(&mut self, channel: Channel, duty: u16) {
+ assert!(duty < self.get_max_duty());
+ unsafe { self.inner.set_compare_value(channel, duty) }
+ }
+
+ pub fn set_dead_time_clock_division(&mut self, value: Ckd) {
+ unsafe { self.inner.set_dead_time_clock_division(value) }
+ }
+
+ pub fn set_dead_time_value(&mut self, value: u8) {
+ unsafe { self.inner.set_dead_time_value(value) }
+ }
+}
diff --git a/embassy-stm32/src/pwm/mod.rs b/embassy-stm32/src/pwm/mod.rs
index d3713391..0bef0708 100644
--- a/embassy-stm32/src/pwm/mod.rs
+++ b/embassy-stm32/src/pwm/mod.rs
@@ -1,5 +1,8 @@
+pub mod complementary_pwm;
pub mod simple_pwm;
+use stm32_metapac::timer::vals::Ckd;
+
#[cfg(feature = "unstable-pac")]
pub mod low_level {
pub use super::sealed::*;
@@ -67,6 +70,14 @@ pub(crate) mod sealed {
unsafe fn get_max_compare_value(&self) -> u16;
}
+ pub trait ComplementaryCaptureCompare16bitInstance: CaptureCompare16bitInstance {
+ unsafe fn set_dead_time_clock_division(&mut self, value: Ckd);
+
+ unsafe fn set_dead_time_value(&mut self, value: u8);
+
+ unsafe fn enable_complementary_channel(&mut self, channel: Channel, enable: bool);
+ }
+
pub trait CaptureCompare32bitInstance: crate::timer::sealed::GeneralPurpose32bitInstance {
unsafe fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode);
@@ -82,6 +93,12 @@ pub trait CaptureCompare16bitInstance:
sealed::CaptureCompare16bitInstance + crate::timer::GeneralPurpose16bitInstance + 'static
{
}
+
+pub trait ComplementaryCaptureCompare16bitInstance:
+ sealed::ComplementaryCaptureCompare16bitInstance + crate::timer::AdvancedControlInstance + 'static
+{
+}
+
pub trait CaptureCompare32bitInstance:
sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + crate::timer::GeneralPurpose32bitInstance + 'static
{
@@ -209,6 +226,29 @@ foreach_interrupt! {
impl CaptureCompare16bitInstance for crate::peripherals::$inst {
}
+
+ impl crate::pwm::sealed::ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {
+ unsafe fn set_dead_time_clock_division(&mut self, value: Ckd) {
+ use crate::timer::sealed::AdvancedControlInstance;
+ Self::regs_advanced().cr1().modify(|w| w.set_ckd(value));
+ }
+
+ unsafe fn set_dead_time_value(&mut self, value: u8) {
+ use crate::timer::sealed::AdvancedControlInstance;
+ Self::regs_advanced().bdtr().modify(|w| w.set_dtg(value));
+ }
+
+ unsafe fn enable_complementary_channel(&mut self, channel: Channel, enable: bool) {
+ use crate::timer::sealed::AdvancedControlInstance;
+ Self::regs_advanced()
+ .ccer()
+ .modify(|w| w.set_ccne(channel.raw(), enable));
+ }
+ }
+
+ impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {
+
+ }
};
}
diff --git a/examples/stm32f4/src/bin/pwm_complementary.rs b/examples/stm32f4/src/bin/pwm_complementary.rs
new file mode 100644
index 00000000..6e17f3fd
--- /dev/null
+++ b/examples/stm32f4/src/bin/pwm_complementary.rs
@@ -0,0 +1,77 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::pwm::complementary_pwm::{Ckd, ComplementaryPwm, ComplementaryPwmPin};
+use embassy_stm32::pwm::simple_pwm::PwmPin;
+use embassy_stm32::pwm::Channel;
+use embassy_stm32::time::khz;
+use embassy_time::{Duration, Timer};
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ let p = embassy_stm32::init(Default::default());
+ info!("Hello World!");
+
+ let ch1 = PwmPin::new_ch1(p.PE9);
+ let ch1n = ComplementaryPwmPin::new_ch1(p.PA7);
+ let mut pwm = ComplementaryPwm::new(
+ p.TIM1,
+ Some(ch1),
+ Some(ch1n),
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ khz(10),
+ );
+
+ /*
+ Dead-time = T_clk * T_dts * T_dtg
+
+ T_dts:
+ This bit-field indicates the division ratio between the timer clock (CK_INT) frequency and the
+ dead-time and sampling clock (tDTS)used by the dead-time generators and the digital filters
+ (ETR, TIx),
+ 00: tDTS=tCK_INT
+ 01: tDTS=2*tCK_INT
+ 10: tDTS=4*tCK_INT
+
+ T_dtg:
+ This bit-field defines the duration of the dead-time inserted between the complementary
+ outputs. DT correspond to this duration.
+ DTG[7:5]=0xx => DT=DTG[7:0]x tdtg with tdtg=tDTS.
+ DTG[7:5]=10x => DT=(64+DTG[5:0])xtdtg with Tdtg=2xtDTS.
+ DTG[7:5]=110 => DT=(32+DTG[4:0])xtdtg with Tdtg=8xtDTS.
+ DTG[7:5]=111 => DT=(32+DTG[4:0])xtdtg with Tdtg=16xtDTS.
+ Example if TDTS=125ns (8MHz), dead-time possible values are:
+ 0 to 15875 ns by 125 ns steps,
+ 16 us to 31750 ns by 250 ns steps,
+ 32 us to 63us by 1 us steps,
+ 64 us to 126 us by 2 us steps
+ */
+ pwm.set_dead_time_clock_division(Ckd::DIV1);
+ pwm.set_dead_time_value(0);
+
+ let max = pwm.get_max_duty();
+ pwm.enable(Channel::Ch1);
+
+ info!("PWM initialized");
+ info!("PWM max duty {}", max);
+
+ loop {
+ pwm.set_duty(Channel::Ch1, 0);
+ Timer::after(Duration::from_millis(300)).await;
+ pwm.set_duty(Channel::Ch1, max / 4);
+ Timer::after(Duration::from_millis(300)).await;
+ pwm.set_duty(Channel::Ch1, max / 2);
+ Timer::after(Duration::from_millis(300)).await;
+ pwm.set_duty(Channel::Ch1, max - 1);
+ Timer::after(Duration::from_millis(300)).await;
+ }
+}