From a1c9a2e8bd83852d774255c82e71d2054c7c02e4 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Thu, 29 Aug 2024 16:18:43 -0400 Subject: [PATCH] First working draft of a lptim driver This driver is able to PWM a pin --- embassy-stm32/build.rs | 2 + embassy-stm32/src/lib.rs | 2 + embassy-stm32/src/lptim/mod.rs | 145 ++++++++++++++++++++++++++++++ embassy-stm32/src/lptim/traits.rs | 95 ++++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 embassy-stm32/src/lptim/mod.rs create mode 100644 embassy-stm32/src/lptim/traits.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 1984a1420..33985a3f4 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1015,6 +1015,8 @@ fn main() { (("hrtim", "CHE2"), quote!(crate::hrtim::ChannelEComplementaryPin)), (("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)), (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), + (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), + (("lptim", "CH2"), quote!(crate::lptim::Channel1Pin)), (("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)), (("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)), (("sdmmc", "D0"), quote!(crate::sdmmc::D0Pin)), diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 98695e738..451f595e0 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -89,6 +89,8 @@ pub mod i2s; pub mod ipcc; #[cfg(feature = "low-power")] pub mod low_power; +#[cfg(lptim)] +pub mod lptim; #[cfg(ltdc)] pub mod ltdc; #[cfg(opamp)] diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs new file mode 100644 index 000000000..3cf95149e --- /dev/null +++ b/embassy-stm32/src/lptim/mod.rs @@ -0,0 +1,145 @@ +//! Low-power timer (LPTIM) + +mod traits; + +use core::marker::PhantomData; + +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, + /// Channel 1. + pub ch_1: Ch1, + /// Channel 2. + pub ch_2: 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 }, + } + } + + /// 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); diff --git a/embassy-stm32/src/lptim/traits.rs b/embassy-stm32/src/lptim/traits.rs new file mode 100644 index 000000000..a8f890a78 --- /dev/null +++ b/embassy-stm32/src/lptim/traits.rs @@ -0,0 +1,95 @@ +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 { + + } + }; +}