From 3b8859653e4028c194ceed9432a4f76c3c856816 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 4 Sep 2024 14:46:35 -0400 Subject: [PATCH] stm32: Clean up the lptim driver --- embassy-stm32/src/lptim/mod.rs | 166 +++++------------------ embassy-stm32/src/lptim/pwm.rs | 122 +++++++++++++++++ embassy-stm32/src/lptim/timer.rs | 212 ++++++++++++++++++++++++++++++ embassy-stm32/src/lptim/traits.rs | 95 ------------- 4 files changed, 369 insertions(+), 226 deletions(-) create mode 100644 embassy-stm32/src/lptim/pwm.rs create mode 100644 embassy-stm32/src/lptim/timer.rs delete mode 100644 embassy-stm32/src/lptim/traits.rs diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs index 3cf95149e..baedf9fe2 100644 --- a/embassy-stm32/src/lptim/mod.rs +++ b/embassy-stm32/src/lptim/mod.rs @@ -1,145 +1,49 @@ //! Low-power timer (LPTIM) -mod traits; +pub mod pwm; +pub mod timer; -use core::marker::PhantomData; +use crate::rcc::RccPeripheral; -use embassy_hal_internal::{into_ref, PeripheralRef}; -pub use traits::Instance; - -use crate::gpio::{AfType, AnyPin, OutputType, Speed}; -// use crate::time::Hertz; -use crate::{rcc, Peripheral}; - -/// LPTIM master instance. -pub struct Master { - phantom: PhantomData, -} - -/// LPTIM channel 1 instance. -pub struct Ch1 { - phantom: PhantomData, -} - -/// LPTIM channel 2 instance. -pub struct Ch2 { - phantom: PhantomData, -} - -trait SealedChannel { - fn raw() -> usize; -} - -/// channel instance trait. -#[allow(private_bounds)] -pub trait Channel: SealedChannel {} - -/// LPTIM PWM pin. -pub struct PwmPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, - phantom: PhantomData<(T, C)>, -} - -macro_rules! channel_impl { - ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident) => { - impl<'d, T: Instance> PwmPin<'d, T, $channel> { - #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output(OutputType::PushPull, Speed::VeryHigh), - ); - }); - PwmPin { - _pin: pin.map_into(), - phantom: PhantomData, - } - } - } - - impl SealedChannel for $channel { - fn raw() -> usize { - $ch_num - } - } - impl Channel for $channel {} - }; -} - -channel_impl!(new_ch1, Ch1, 0, Channel1Pin); -channel_impl!(new_ch2, Ch2, 1, Channel2Pin); - -/// Struct used to divide a high resolution timer into multiple channels -pub struct Pwm<'d, T: Instance> { - _inner: PeripheralRef<'d, T>, - /// Master instance. - pub master: Master, +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { /// Channel 1. - pub ch_1: Ch1, + Ch1, /// Channel 2. - pub ch_2: Ch2, + Ch2, } -impl<'d, T: Instance> Pwm<'d, T> { - /// Create a new LPTIM driver. - /// - /// This splits the LPTIM into its constituent parts, which you can then use individually. - pub fn new( - tim: impl Peripheral

+ 'd, - _ch1: Option>>, - _ch2: Option>>, - ) -> Self { - Self::new_inner(tim) - } - - fn new_inner(tim: impl Peripheral

+ 'd) -> Self { - into_ref!(tim); - - rcc::enable_and_reset::(); - - T::regs().cr().modify(|w| w.set_enable(true)); - - // Set frequency. Should be configurable. - // By default, 16MHz. We want 440Hz. - // That's 36363 cycles - T::regs().arr().write_value(stm32_metapac::lptim::regs::Arr(36363)); - - // Set duty cycle. Should be configurable. Should take care of channel too (only Ch1 now) - T::regs().ccr(0).write_value(stm32_metapac::lptim::regs::Ccr(18181)); - - // Enable channel as PWM. Default state anyway. Implement later. - // T::regs().ccmr().modify(|w| { - // w.set_ccsel(0, 0); - // w.set_ccsel(1, 0); - // }) - - // Enable output on pins. Should care about the channels! - T::regs().ccmr().modify(|w| { - w.set_cce(0, true); - w.set_cce(1, true); - }); - - Self { - _inner: tim, - master: Master { phantom: PhantomData }, - ch_1: Ch1 { phantom: PhantomData }, - ch_2: Ch2 { phantom: PhantomData }, +impl Channel { + /// Get the channel index (0..1) + pub fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, } } - - /// Start - pub fn start(&mut self) { - T::regs().cr().modify(|w| w.set_cntstrt(true)); - } - - /// Stop - pub fn stop(&mut self) { - T::regs().cr().modify(|w| w.set_cntstrt(false)); - } } pin_trait!(Channel1Pin, Instance); pin_trait!(Channel2Pin, Instance); + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::lptim::LptimAdv; +} + +/// LPTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + 'static {} +foreach_interrupt! { + ($inst:ident, lptim, LPTIM_ADV, UP, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::lptim::LptimAdv { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + + } + }; +} diff --git a/embassy-stm32/src/lptim/pwm.rs b/embassy-stm32/src/lptim/pwm.rs new file mode 100644 index 000000000..725aa676e --- /dev/null +++ b/embassy-stm32/src/lptim/pwm.rs @@ -0,0 +1,122 @@ +//! PWM driver. + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::timer::{ChannelDirection, Timer}; +use super::{Channel, Channel1Pin, Channel2Pin, Instance}; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} + +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct PwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: Instance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + AfType::output(OutputType::PushPull, Speed::VeryHigh), + ); + }); + PwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_ch1, Ch1, Channel1Pin); +channel_impl!(new_ch2, Ch2, Channel2Pin); + +/// PWM driver. +pub struct Pwm<'d, T: Instance> { + inner: Timer<'d, T>, // _inner: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new PWM driver. + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1_pin: Option>, + _ch2_pin: Option>, + freq: Hertz, + ) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.enable(); + this.set_frequency(freq); + + [Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| { + this.inner.set_channel_direction(channel, ChannelDirection::OutputPwm); + }); + + this.inner.continuous_mode_start(); + + this + } + + /// Enable the given channel. + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self, channel: Channel) -> bool { + self.inner.get_channel_enable_state(channel) + } + + /// Set PWM frequency. + /// + /// 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, frequency: Hertz) { + self.inner.set_frequency(frequency); + } + + /// 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) -> u16 { + 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: u16) { + 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) -> u16 { + self.inner.get_compare_value(channel) + } +} diff --git a/embassy-stm32/src/lptim/timer.rs b/embassy-stm32/src/lptim/timer.rs new file mode 100644 index 000000000..06392b2e7 --- /dev/null +++ b/embassy-stm32/src/lptim/timer.rs @@ -0,0 +1,212 @@ +//! Low-level timer driver. + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +use super::{Channel, Instance}; +use crate::pac::lptim::vals; +use crate::rcc; +use crate::time::Hertz; + +/// Direction of a low-power timer channel +pub enum ChannelDirection { + /// Use channel as a PWM output + OutputPwm, + /// Use channel as an input capture + InputCapture, +} + +impl From for vals::Ccsel { + fn from(direction: ChannelDirection) -> Self { + match direction { + ChannelDirection::OutputPwm => vals::Ccsel::OUTPUTCOMPARE, + ChannelDirection::InputCapture => vals::Ccsel::INPUTCAPTURE, + } + } +} + +enum Prescaler { + Div1, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl From<&Prescaler> for vals::Presc { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => vals::Presc::DIV1, + Prescaler::Div2 => vals::Presc::DIV2, + Prescaler::Div4 => vals::Presc::DIV4, + Prescaler::Div8 => vals::Presc::DIV8, + Prescaler::Div16 => vals::Presc::DIV16, + Prescaler::Div32 => vals::Presc::DIV32, + Prescaler::Div64 => vals::Presc::DIV64, + Prescaler::Div128 => vals::Presc::DIV128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: vals::Presc) -> Self { + match prescaler { + vals::Presc::DIV1 => Prescaler::Div1, + vals::Presc::DIV2 => Prescaler::Div2, + vals::Presc::DIV4 => Prescaler::Div4, + vals::Presc::DIV8 => Prescaler::Div8, + vals::Presc::DIV16 => Prescaler::Div16, + vals::Presc::DIV32 => Prescaler::Div32, + vals::Presc::DIV64 => Prescaler::Div64, + vals::Presc::DIV128 => Prescaler::Div128, + } + } +} + +impl From<&Prescaler> for u32 { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => 1, + Prescaler::Div2 => 2, + Prescaler::Div4 => 4, + Prescaler::Div8 => 8, + Prescaler::Div16 => 16, + Prescaler::Div32 => 32, + Prescaler::Div64 => 64, + Prescaler::Div128 => 128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: u32) -> Self { + match prescaler { + 1 => Prescaler::Div1, + 2 => Prescaler::Div2, + 4 => Prescaler::Div4, + 8 => Prescaler::Div8, + 16 => Prescaler::Div16, + 32 => Prescaler::Div32, + 64 => Prescaler::Div64, + 128 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn from_ticks(ticks: u32) -> Self { + // We need to scale down to a 16-bit range + (ticks >> 16).next_power_of_two().into() + } + + pub fn scale_down(&self, ticks: u32) -> u16 { + (ticks / u32::from(self)).try_into().unwrap() + } + + pub fn scale_up(&self, ticks: u16) -> u32 { + u32::from(self) * ticks as u32 + } +} + +/// Low-level timer driver. +pub struct Timer<'d, T: Instance> { + _tim: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Timer<'d, T> { + /// Create a new timer driver. + pub fn new(tim: impl Peripheral

+ 'd) -> Self { + into_ref!(tim); + + rcc::enable_and_reset::(); + + Self { _tim: tim } + } + + /// Enable the timer. + pub fn enable(&self) { + T::regs().cr().modify(|w| w.set_enable(true)); + } + + /// Disable the timer. + pub fn disable(&self) { + T::regs().cr().modify(|w| w.set_enable(false)); + } + + /// Start the timer in single pulse mode. + pub fn single_mode_start(&self) { + T::regs().cr().modify(|w| w.set_sngstrt(true)); + } + + /// Start the timer in continuous mode. + pub fn continuous_mode_start(&self) { + T::regs().cr().modify(|w| w.set_cntstrt(true)); + } + + /// Set channel direction. + pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { + T::regs() + .ccmr() + .modify(|w| w.set_ccsel(channel.index(), direction.into())); + } + + /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. + pub fn set_frequency(&self, frequency: Hertz) { + let f = frequency.0; + assert!(f > 0); + + let pclk_f = T::frequency().0; + + let pclk_ticks_per_timer_period = pclk_f / f; + + let psc = Prescaler::from_ticks(pclk_ticks_per_timer_period); + let arr = psc.scale_down(pclk_ticks_per_timer_period); + + T::regs().cfgr().modify(|r| r.set_presc((&psc).into())); + T::regs().arr().modify(|r| r.set_arr(arr.into())); + } + + /// Get the timer frequency. + pub fn get_frequency(&self) -> Hertz { + let pclk_f = T::frequency(); + let arr = T::regs().arr().read().arr(); + let psc = Prescaler::from(T::regs().cfgr().read().presc()); + + pclk_f / psc.scale_up(arr) + } + + /// Get the clock frequency of the timer (before prescaler is applied). + pub fn get_clock_frequency(&self) -> Hertz { + T::frequency() + } + + /// Enable/disable a channel. + pub fn enable_channel(&self, channel: Channel, enable: bool) { + T::regs().ccmr().modify(|w| { + w.set_cce(channel.index(), enable); + }); + } + + /// Get enable/disable state of a channel + pub fn get_channel_enable_state(&self, channel: Channel) -> bool { + T::regs().ccmr().read().cce(channel.index()) + } + + /// Set compare value for a channel. + pub fn set_compare_value(&self, channel: Channel, value: u16) { + T::regs().ccr(channel.index()).modify(|w| w.set_ccr(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self, channel: Channel) -> u16 { + T::regs().ccr(channel.index()).read().ccr() + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + pub fn get_max_compare_value(&self) -> u16 { + T::regs().arr().read().arr() + } +} diff --git a/embassy-stm32/src/lptim/traits.rs b/embassy-stm32/src/lptim/traits.rs deleted file mode 100644 index a8f890a78..000000000 --- a/embassy-stm32/src/lptim/traits.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::rcc::RccPeripheral; -use crate::time::Hertz; - -#[repr(u8)] -#[derive(Clone, Copy)] -pub(crate) enum Prescaler { - Div1 = 1, - Div2 = 2, - Div4 = 4, - Div8 = 8, - Div16 = 16, - Div32 = 32, - Div64 = 64, - Div128 = 128, -} - -impl From for u8 { - fn from(val: Prescaler) -> Self { - match val { - Prescaler::Div1 => 0b000, - Prescaler::Div2 => 0b001, - Prescaler::Div4 => 0b010, - Prescaler::Div8 => 0b011, - Prescaler::Div16 => 0b100, - Prescaler::Div32 => 0b101, - Prescaler::Div64 => 0b110, - Prescaler::Div128 => 0b111, - } - } -} - -impl From for Prescaler { - fn from(val: u8) -> Self { - match val { - 0b000 => Prescaler::Div1, - 0b001 => Prescaler::Div2, - 0b010 => Prescaler::Div4, - 0b011 => Prescaler::Div8, - 0b100 => Prescaler::Div16, - 0b101 => Prescaler::Div32, - 0b110 => Prescaler::Div64, - 0b111 => Prescaler::Div128, - _ => unreachable!(), - } - } -} - -impl Prescaler { - pub fn compute_min_high_res(val: u32) -> Self { - *[ - Prescaler::Div1, - Prescaler::Div2, - Prescaler::Div4, - Prescaler::Div8, - Prescaler::Div16, - Prescaler::Div32, - Prescaler::Div64, - Prescaler::Div128, - ] - .iter() - .skip_while(|psc| **psc as u32 <= val) - .next() - .unwrap() - } - - pub fn compute_min_low_res(val: u32) -> Self { - *[Prescaler::Div32, Prescaler::Div64, Prescaler::Div128] - .iter() - .skip_while(|psc| **psc as u32 <= val) - .next() - .unwrap() - } -} - -pub(crate) trait SealedInstance: RccPeripheral { - fn regs() -> crate::pac::lptim::LptimAdv; -} - -/// LPTIM instance trait. -#[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static {} - -foreach_interrupt! { - ($inst:ident, lptim, LPTIM_ADV, UP, $irq:ident) => { - impl SealedInstance for crate::peripherals::$inst { - fn regs() -> crate::pac::lptim::LptimAdv { - crate::pac::$inst - } - } - - impl Instance for crate::peripherals::$inst { - - } - }; -}