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 <xoviat@users.noreply.github.com>
This commit is contained in:
bors[bot] 2023-04-06 21:33:17 +00:00 committed by GitHub
commit da8258b767
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 241 additions and 0 deletions

View file

@ -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<P = impl $complementary_pin_trait<Perip>> + '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<P = T> + 'd,
_ch1: Option<PwmPin<'d, T, Ch1>>,
_ch1n: Option<ComplementaryPwmPin<'d, T, Ch1>>,
_ch2: Option<PwmPin<'d, T, Ch2>>,
_ch2n: Option<ComplementaryPwmPin<'d, T, Ch2>>,
_ch3: Option<PwmPin<'d, T, Ch3>>,
_ch3n: Option<ComplementaryPwmPin<'d, T, Ch3>>,
_ch4: Option<PwmPin<'d, T, Ch4>>,
_ch4n: Option<ComplementaryPwmPin<'d, T, Ch4>>,
freq: Hertz,
) -> Self {
Self::new_inner(tim, freq)
}
fn new_inner(tim: impl Peripheral<P = T> + 'd, freq: Hertz) -> Self {
into_ref!(tim);
T::enable();
<T as crate::rcc::sealed::RccPeripheral>::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) }
}
}

View file

@ -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 {
}
};
}

View file

@ -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;
}
}