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 <xoviat@users.noreply.github.com>
This commit is contained in:
commit
da8258b767
124
embassy-stm32/src/pwm/complementary_pwm.rs
Normal file
124
embassy-stm32/src/pwm/complementary_pwm.rs
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
|
pub mod complementary_pwm;
|
||||||
pub mod simple_pwm;
|
pub mod simple_pwm;
|
||||||
|
|
||||||
|
use stm32_metapac::timer::vals::Ckd;
|
||||||
|
|
||||||
#[cfg(feature = "unstable-pac")]
|
#[cfg(feature = "unstable-pac")]
|
||||||
pub mod low_level {
|
pub mod low_level {
|
||||||
pub use super::sealed::*;
|
pub use super::sealed::*;
|
||||||
@ -67,6 +70,14 @@ pub(crate) mod sealed {
|
|||||||
unsafe fn get_max_compare_value(&self) -> u16;
|
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 {
|
pub trait CaptureCompare32bitInstance: crate::timer::sealed::GeneralPurpose32bitInstance {
|
||||||
unsafe fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode);
|
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
|
sealed::CaptureCompare16bitInstance + crate::timer::GeneralPurpose16bitInstance + 'static
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ComplementaryCaptureCompare16bitInstance:
|
||||||
|
sealed::ComplementaryCaptureCompare16bitInstance + crate::timer::AdvancedControlInstance + 'static
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
pub trait CaptureCompare32bitInstance:
|
pub trait CaptureCompare32bitInstance:
|
||||||
sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + crate::timer::GeneralPurpose32bitInstance + 'static
|
sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + crate::timer::GeneralPurpose32bitInstance + 'static
|
||||||
{
|
{
|
||||||
@ -209,6 +226,29 @@ foreach_interrupt! {
|
|||||||
impl CaptureCompare16bitInstance for crate::peripherals::$inst {
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
77
examples/stm32f4/src/bin/pwm_complementary.rs
Normal file
77
examples/stm32f4/src/bin/pwm_complementary.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user