Merge pull request #3317 from GrantM11235/simplepwmchannel

embassy-stm32: Add SimplePwmChannel
This commit is contained in:
Ulf Lilleengen 2024-10-23 10:30:13 +00:00 committed by GitHub
commit 8803128707
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 269 additions and 90 deletions

View File

@ -6,6 +6,8 @@
//!
//! The available functionality depends on the timer type.
use core::mem::ManuallyDrop;
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
// Re-export useful enums
pub use stm32_metapac::timer::vals::{FilterValue, Sms as SlaveMode, Ts as TriggerSource};
@ -198,6 +200,11 @@ impl<'d, T: CoreInstance> Timer<'d, T> {
Self { tim }
}
pub(crate) unsafe fn clone_unchecked(&self) -> ManuallyDrop<Self> {
let tim = unsafe { self.tim.clone_unchecked() };
ManuallyDrop::new(Self { tim })
}
/// Get access to the virutal core 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers

View File

@ -2,6 +2,7 @@
use core::marker::PhantomData;
use embassy_hal_internal::Peripheral;
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(not(stm32l0))]
@ -66,7 +67,7 @@ impl State {
}
}
trait SealedInstance: RccPeripheral {
trait SealedInstance: RccPeripheral + Peripheral<P = Self> {
/// Async state for this timer
fn state() -> &'static State;
}

View File

@ -1,6 +1,7 @@
//! Simple PWM driver.
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use embassy_hal_internal::{into_ref, PeripheralRef};
@ -51,6 +52,111 @@ channel_impl!(new_ch2, Ch2, Channel2Pin);
channel_impl!(new_ch3, Ch3, Channel3Pin);
channel_impl!(new_ch4, Ch4, Channel4Pin);
/// A single channel of a pwm, obtained from [`SimplePwm::split`],
/// [`SimplePwm::channel`], [`SimplePwm::ch1`], etc.
///
/// It is not possible to change the pwm frequency because
/// the frequency configuration is shared with all four channels.
pub struct SimplePwmChannel<'d, T: GeneralInstance4Channel> {
timer: ManuallyDrop<Timer<'d, T>>,
channel: Channel,
}
// TODO: check for RMW races
impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> {
/// Enable the given channel.
pub fn enable(&mut self) {
self.timer.enable_channel(self.channel, true);
}
/// Disable the given channel.
pub fn disable(&mut self) {
self.timer.enable_channel(self.channel, false);
}
/// Check whether given channel is enabled
pub fn is_enabled(&self) -> bool {
self.timer.get_channel_enable_state(self.channel)
}
/// Get max duty value.
///
/// This value depends on the configured frequency and the timer's clock rate from RCC.
pub fn max_duty_cycle(&self) -> u16 {
let max = self.timer.get_max_compare_value();
assert!(max < u16::MAX as u32);
max as u16 + 1
}
/// Set the duty for a given channel.
///
/// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included.
pub fn set_duty_cycle(&mut self, duty: u16) {
assert!(duty <= (*self).max_duty_cycle());
self.timer.set_compare_value(self.channel, duty.into())
}
/// Set the duty cycle to 0%, or always inactive.
pub fn set_duty_cycle_fully_off(&mut self) {
self.set_duty_cycle(0);
}
/// Set the duty cycle to 100%, or always active.
pub fn set_duty_cycle_fully_on(&mut self) {
self.set_duty_cycle((*self).max_duty_cycle());
}
/// Set the duty cycle to `num / denom`.
///
/// The caller is responsible for ensuring that `num` is less than or equal to `denom`,
/// and that `denom` is not zero.
pub fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) {
assert!(denom != 0);
assert!(num <= denom);
let duty = u32::from(num) * u32::from(self.max_duty_cycle()) / u32::from(denom);
// This is safe because we know that `num <= denom`, so `duty <= self.max_duty_cycle()` (u16)
#[allow(clippy::cast_possible_truncation)]
self.set_duty_cycle(duty as u16);
}
/// Set the duty cycle to `percent / 100`
///
/// The caller is responsible for ensuring that `percent` is less than or equal to 100.
pub fn set_duty_cycle_percent(&mut self, percent: u8) {
self.set_duty_cycle_fraction(u16::from(percent), 100)
}
/// Get the duty for a given channel.
///
/// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included.
pub fn current_duty_cycle(&self) -> u16 {
unwrap!(self.timer.get_compare_value(self.channel).try_into())
}
/// Set the output polarity for a given channel.
pub fn set_polarity(&mut self, polarity: OutputPolarity) {
self.timer.set_output_polarity(self.channel, polarity);
}
/// Set the output compare mode for a given channel.
pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) {
self.timer.set_output_compare_mode(self.channel, mode);
}
}
/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`].
pub struct SimplePwmChannels<'d, T: GeneralInstance4Channel> {
/// Channel 1
pub ch1: SimplePwmChannel<'d, T>,
/// Channel 2
pub ch2: SimplePwmChannel<'d, T>,
/// Channel 3
pub ch3: SimplePwmChannel<'d, T>,
/// Channel 4
pub ch4: SimplePwmChannel<'d, T>,
}
/// Simple PWM driver.
pub struct SimplePwm<'d, T: GeneralInstance4Channel> {
inner: Timer<'d, T>,
@ -89,19 +195,76 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> {
this
}
/// Enable the given channel.
pub fn enable(&mut self, channel: Channel) {
self.inner.enable_channel(channel, true);
/// Get a single channel
///
/// If you need to use multiple channels, use [`Self::split`].
pub fn channel(&mut self, channel: Channel) -> SimplePwmChannel<'_, T> {
SimplePwmChannel {
timer: unsafe { self.inner.clone_unchecked() },
channel,
}
}
/// Disable the given channel.
pub fn disable(&mut self, channel: Channel) {
self.inner.enable_channel(channel, false);
/// Channel 1
///
/// This is just a convenience wrapper around [`Self::channel`].
///
/// If you need to use multiple channels, use [`Self::split`].
pub fn ch1(&mut self) -> SimplePwmChannel<'_, T> {
self.channel(Channel::Ch1)
}
/// Check whether given channel is enabled
pub fn is_enabled(&self, channel: Channel) -> bool {
self.inner.get_channel_enable_state(channel)
/// Channel 2
///
/// This is just a convenience wrapper around [`Self::channel`].
///
/// If you need to use multiple channels, use [`Self::split`].
pub fn ch2(&mut self) -> SimplePwmChannel<'_, T> {
self.channel(Channel::Ch2)
}
/// Channel 3
///
/// This is just a convenience wrapper around [`Self::channel`].
///
/// If you need to use multiple channels, use [`Self::split`].
pub fn ch3(&mut self) -> SimplePwmChannel<'_, T> {
self.channel(Channel::Ch3)
}
/// Channel 4
///
/// This is just a convenience wrapper around [`Self::channel`].
///
/// If you need to use multiple channels, use [`Self::split`].
pub fn ch4(&mut self) -> SimplePwmChannel<'_, T> {
self.channel(Channel::Ch4)
}
/// Splits a [`SimplePwm`] into four pwm channels.
///
/// This returns all four channels, including channels that
/// aren't configured with a [`PwmPin`].
// TODO: I hate the name "split"
pub fn split(self) -> SimplePwmChannels<'static, T>
where
// must be static because the timer will never be dropped/disabled
'd: 'static,
{
// without this, the timer would be disabled at the end of this function
let timer = ManuallyDrop::new(self.inner);
let ch = |channel| SimplePwmChannel {
timer: unsafe { timer.clone_unchecked() },
channel,
};
SimplePwmChannels {
ch1: ch(Channel::Ch1),
ch2: ch(Channel::Ch2),
ch3: ch(Channel::Ch3),
ch4: ch(Channel::Ch4),
}
}
/// Set PWM frequency.
@ -109,6 +272,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> {
/// Note: when you call this, the max duty value changes, so you will have to
/// call `set_duty` on all channels with the duty calculated based on the new max duty.
pub fn set_frequency(&mut self, freq: Hertz) {
// TODO: prevent ARR = u16::MAX?
let multiplier = if self.inner.get_counting_mode().is_center_aligned() {
2u8
} else {
@ -120,33 +284,10 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> {
/// Get max duty value.
///
/// This value depends on the configured frequency and the timer's clock rate from RCC.
pub fn get_max_duty(&self) -> u32 {
self.inner.get_max_compare_value() + 1
}
/// Set the duty for a given channel.
///
/// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
pub fn set_duty(&mut self, channel: Channel, duty: u32) {
assert!(duty <= self.get_max_duty());
self.inner.set_compare_value(channel, duty)
}
/// Get the duty for a given channel.
///
/// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
pub fn get_duty(&self, channel: Channel) -> u32 {
self.inner.get_compare_value(channel)
}
/// Set the output polarity for a given channel.
pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) {
self.inner.set_output_polarity(channel, polarity);
}
/// Set the output compare mode for a given channel.
pub fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode) {
self.inner.set_output_compare_mode(channel, mode);
pub fn max_duty_cycle(&self) -> u16 {
let max = self.inner.get_max_compare_value();
assert!(max < u16::MAX as u32);
max as u16 + 1
}
/// Generate a sequence of PWM waveform
@ -164,8 +305,8 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> {
#[allow(clippy::let_unit_value)] // eg. stm32f334
let req = dma.request();
let original_duty_state = self.get_duty(channel);
let original_enable_state = self.is_enabled(channel);
let original_duty_state = self.channel(channel).current_duty_cycle();
let original_enable_state = self.channel(channel).is_enabled();
let original_update_dma_state = self.inner.get_update_dma_state();
if !original_update_dma_state {
@ -173,7 +314,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> {
}
if !original_enable_state {
self.enable(channel);
self.channel(channel).enable();
}
unsafe {
@ -201,10 +342,10 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> {
// restore output compare state
if !original_enable_state {
self.disable(channel);
self.channel(channel).disable();
}
self.set_duty(channel, original_duty_state);
self.channel(channel).set_duty_cycle(original_duty_state);
// Since DMA is closed before timer update event trigger DMA is turn off,
// this can almost always trigger a DMA FIFO error.
@ -234,8 +375,8 @@ macro_rules! impl_waveform_chx {
let cc_channel = Channel::$cc_ch;
let original_duty_state = self.get_duty(cc_channel);
let original_enable_state = self.is_enabled(cc_channel);
let original_duty_state = self.channel(cc_channel).current_duty_cycle();
let original_enable_state = self.channel(cc_channel).is_enabled();
let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE;
let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel);
@ -249,7 +390,7 @@ macro_rules! impl_waveform_chx {
}
if !original_enable_state {
self.enable(cc_channel);
self.channel(cc_channel).enable();
}
unsafe {
@ -277,10 +418,10 @@ macro_rules! impl_waveform_chx {
// restore output compare state
if !original_enable_state {
self.disable(cc_channel);
self.channel(cc_channel).disable();
}
self.set_duty(cc_channel, original_duty_state);
self.channel(cc_channel).set_duty_cycle(original_duty_state);
// Since DMA is closed before timer Capture Compare Event trigger DMA is turn off,
// this can almost always trigger a DMA FIFO error.
@ -304,6 +445,41 @@ impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2);
impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3);
impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4);
impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> {
type Error = core::convert::Infallible;
}
impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::SetDutyCycle for SimplePwmChannel<'d, T> {
fn max_duty_cycle(&self) -> u16 {
self.max_duty_cycle()
}
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.set_duty_cycle(duty);
Ok(())
}
fn set_duty_cycle_fully_off(&mut self) -> Result<(), Self::Error> {
self.set_duty_cycle_fully_off();
Ok(())
}
fn set_duty_cycle_fully_on(&mut self) -> Result<(), Self::Error> {
self.set_duty_cycle_fully_on();
Ok(())
}
fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> {
self.set_duty_cycle_fraction(num, denom);
Ok(())
}
fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> {
self.set_duty_cycle_percent(percent);
Ok(())
}
}
impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> {
type Channel = Channel;
type Time = Hertz;
@ -330,7 +506,7 @@ impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> {
}
fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) {
assert!(duty <= self.get_max_duty());
assert!(duty <= self.max_duty_cycle() as u32);
self.inner.set_compare_value(channel, duty)
}

View File

@ -6,7 +6,6 @@ use embassy_executor::Spawner;
use embassy_stm32::gpio::OutputType;
use embassy_stm32::time::khz;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
use embassy_stm32::timer::Channel;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
@ -15,22 +14,22 @@ async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
info!("Hello World!");
let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default());
let max = pwm.get_max_duty();
pwm.enable(Channel::Ch1);
let ch1_pin = PwmPin::new_ch1(p.PE9, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default());
let mut ch1 = pwm.ch1();
ch1.enable();
info!("PWM initialized");
info!("PWM max duty {}", max);
info!("PWM max duty {}", ch1.max_duty_cycle());
loop {
pwm.set_duty(Channel::Ch1, 0);
ch1.set_duty_cycle_fully_off();
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max / 4);
ch1.set_duty_cycle_fraction(1, 4);
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max / 2);
ch1.set_duty_cycle_fraction(1, 2);
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max - 1);
ch1.set_duty_cycle(ch1.max_duty_cycle() - 1);
Timer::after_millis(300).await;
}
}

View File

@ -61,7 +61,7 @@ async fn main(_spawner: Spawner) {
// construct ws2812 non-return-to-zero (NRZ) code bit by bit
// ws2812 only need 24 bits for each LED, but we add one bit more to keep PWM output low
let max_duty = ws2812_pwm.get_max_duty() as u16;
let max_duty = ws2812_pwm.max_duty_cycle();
let n0 = 8 * max_duty / 25; // ws2812 Bit 0 high level timing
let n1 = 2 * n0; // ws2812 Bit 1 high level timing
@ -84,7 +84,7 @@ async fn main(_spawner: Spawner) {
let pwm_channel = Channel::Ch1;
// make sure PWM output keep low on first start
ws2812_pwm.set_duty(pwm_channel, 0);
ws2812_pwm.channel(pwm_channel).set_duty_cycle(0);
// flip color at 2 Hz
let mut ticker = Ticker::every(Duration::from_millis(500));

View File

@ -47,10 +47,10 @@ async fn main(spawner: Spawner) {
unwrap!(spawner.spawn(blinky(p.PB1)));
// Connect PB1 and PA8 with a 1k Ohm resistor
let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default());
pwm.enable(Channel::Ch1);
pwm.set_duty(Channel::Ch1, 50);
let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default());
pwm.ch1().enable();
pwm.ch1().set_duty_cycle(50);
let ch1 = CapturePin::new_ch1(p.PA0, Pull::None);
let mut ic = InputCapture::new(p.TIM2, Some(ch1), None, None, None, Irqs, khz(1000), Default::default());

View File

@ -14,7 +14,6 @@ use embassy_stm32::gpio::{Level, Output, OutputType, Pull, Speed};
use embassy_stm32::time::khz;
use embassy_stm32::timer::pwm_input::PwmInput;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
use embassy_stm32::timer::Channel;
use embassy_stm32::{bind_interrupts, peripherals, timer};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
@ -43,11 +42,10 @@ async fn main(spawner: Spawner) {
unwrap!(spawner.spawn(blinky(p.PB1)));
// Connect PA8 and PA6 with a 1k Ohm resistor
let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default());
let max = pwm.get_max_duty();
pwm.set_duty(Channel::Ch1, max / 4);
pwm.enable(Channel::Ch1);
let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default());
pwm.ch1().set_duty_cycle_fraction(1, 4);
pwm.ch1().enable();
let mut pwm_input = PwmInput::new(p.TIM2, p.PA0, Pull::None, khz(1000));
pwm_input.enable();

View File

@ -6,7 +6,6 @@ use embassy_executor::Spawner;
use embassy_stm32::gpio::OutputType;
use embassy_stm32::time::khz;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
use embassy_stm32::timer::Channel;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
@ -15,22 +14,22 @@ async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
info!("Hello World!");
let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default());
let max = pwm.get_max_duty();
pwm.enable(Channel::Ch1);
let ch1_pin = PwmPin::new_ch1(p.PC0, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default());
let mut ch1 = pwm.ch1();
ch1.enable();
info!("PWM initialized");
info!("PWM max duty {}", max);
info!("PWM max duty {}", ch1.max_duty_cycle());
loop {
pwm.set_duty(Channel::Ch1, 0);
ch1.set_duty_cycle_fully_off();
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max / 4);
ch1.set_duty_cycle_fraction(1, 4);
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max / 2);
ch1.set_duty_cycle_fraction(1, 2);
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max - 1);
ch1.set_duty_cycle(ch1.max_duty_cycle() - 1);
Timer::after_millis(300).await;
}
}

View File

@ -6,7 +6,6 @@ use embassy_executor::Spawner;
use embassy_stm32::gpio::OutputType;
use embassy_stm32::time::khz;
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
use embassy_stm32::timer::Channel;
use embassy_stm32::Config;
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
@ -37,22 +36,22 @@ async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(config);
info!("Hello World!");
let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM3, Some(ch1), None, None, None, khz(10), Default::default());
let max = pwm.get_max_duty();
pwm.enable(Channel::Ch1);
let ch1_pin = PwmPin::new_ch1(p.PA6, OutputType::PushPull);
let mut pwm = SimplePwm::new(p.TIM3, Some(ch1_pin), None, None, None, khz(10), Default::default());
let mut ch1 = pwm.ch1();
ch1.enable();
info!("PWM initialized");
info!("PWM max duty {}", max);
info!("PWM max duty {}", ch1.max_duty_cycle());
loop {
pwm.set_duty(Channel::Ch1, 0);
ch1.set_duty_cycle_fully_off();
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max / 4);
ch1.set_duty_cycle_fraction(1, 4);
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max / 2);
ch1.set_duty_cycle_fraction(1, 2);
Timer::after_millis(300).await;
pwm.set_duty(Channel::Ch1, max - 1);
ch1.set_duty_cycle(ch1.max_duty_cycle() - 1);
Timer::after_millis(300).await;
}
}