diff --git a/.cargo/config.toml b/.cargo/config.toml index d2012bd..db670e2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,5 @@ [target.thumbv8m.main-none-eabihf] -runner = 'probe-rs run --connect-under-reset' +runner = 'probe-rs run --chip STM32H523RETx' rustflags = [ # LLD (shipped with the Rust toolchain) is used as the default linker "-C", "link-arg=-Tlink.x", diff --git a/Cargo.toml b/Cargo.toml index 92f18eb..bf2c27e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,13 @@ log = { version = "0.4.20", optional = true} futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true} stm32-usbd = "0.8.0" +## +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +nb = "1.0.0" +void = { version = "1.0.2", default-features = false } +cast = { version = "0.3.0", default-features = false } +## + [dev-dependencies] log = { version = "0.4.20"} cortex-m-rt = "0.7.3" @@ -105,6 +112,10 @@ opt-level = "s" # optimize for binary size [[example]] name = "blinky" +[[example]] +name = "blinky_timer" +required-features = ["stm32h533"] + [[example]] name = "i2c" required-features = ["stm32h503"] diff --git a/examples/blinky_timer.rs b/examples/blinky_timer.rs new file mode 100644 index 0000000..dd00db9 --- /dev/null +++ b/examples/blinky_timer.rs @@ -0,0 +1,84 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +mod utilities; + +use core::{ + cell::RefCell, + sync::atomic::{AtomicBool, Ordering}, +}; + +use cortex_m::interrupt::Mutex; +use cortex_m::peripheral::NVIC; +use cortex_m_rt::entry; + +use stm32h5xx_hal::gpio::gpioa::PA5; // LED pin +use stm32h5xx_hal::gpio::{Output, PushPull}; +use stm32h5xx_hal::{pac, pac::interrupt, prelude::*, timer}; +use utilities::logger::info; + +static LED_IS_ON: AtomicBool = AtomicBool::new(false); +static LED: Mutex>>>> = + Mutex::new(RefCell::new(None)); +static TIMER: Mutex>>> = + Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + utilities::logger::init(); + + let mut cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().unwrap(); + + let pwr = dp.PWR.constrain(); + let pwrcfg = pwr.vos0().freeze(); + + // Constrain and Freeze clock + let rcc = dp.RCC.constrain(); + let ccdr = rcc.sys_ck(250.MHz()).freeze(pwrcfg, &dp.SBS); + + let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + let mut led = gpioa.pa5.into_push_pull_output(); + led.set_low(); + + let mut timer = dp.TIM2.timer(2.Hz(), ccdr.peripheral.TIM2, &ccdr.clocks); + timer.listen(timer::Event::TimeOut); + + cortex_m::interrupt::free(|cs| { + LED.borrow(cs).replace(Some(led)); + TIMER.borrow(cs).replace(Some(timer)); + }); + + info!("Start blinking with timer..."); + // Enable TIM2 interrupt + unsafe { + cp.NVIC.set_priority(interrupt::TIM2, 1); + NVIC::unmask::(interrupt::TIM2); + } + + loop { + // do_nothing + } +} + +/// Handle timer overflow +/// +/// The interrupt should be configured at maximum priority, it won't take very long. +#[interrupt] +fn TIM2() { + cortex_m::interrupt::free(|cs| { + if let Some(timer) = TIMER.borrow(cs).borrow_mut().as_mut() { + timer.clear_irq(); + } + // Signal that the interrupt fired + let led_is_on = LED_IS_ON.fetch_not(Ordering::Relaxed); + if let Some(led) = LED.borrow(cs).borrow_mut().as_mut() { + if led_is_on { + led.set_low(); + } else { + led.set_high(); + } + } + }) +} diff --git a/src/lib.rs b/src/lib.rs index cc3c317..f732620 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,9 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![allow(non_camel_case_types)] +pub use nb; +pub use nb::block; + #[cfg(not(feature = "device-selected"))] compile_error!( "This crate requires one of the following device features enabled: @@ -84,6 +87,10 @@ pub mod usb; #[cfg(feature = "device-selected")] pub mod gpdma; +pub mod pwm; + +#[cfg(feature = "device-selected")] +pub mod timer; #[cfg(feature = "device-selected")] mod sealed { diff --git a/src/prelude.rs b/src/prelude.rs index a73e785..339b1ef 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,4 +1,5 @@ //! Prelude +pub use embedded_hal_02::prelude::*; pub use crate::delay::DelayExt as _stm32h5xx_hal_delay_DelayExt; pub use crate::dwt::DwtExt as _stm32h5xx_hal_delay_DwtExt; @@ -9,6 +10,7 @@ pub use crate::icache::ICacheExt as _stm32h5xx_hal_icache_ICacheExt; pub use crate::pwr::PwrExt as _stm32h5xx_hal_pwr_PwrExt; pub use crate::rcc::RccExt as _stm32h5xx_hal_rcc_RccExt; pub use crate::spi::SpiExt as _stm32h5xx_hal_spi_SpiExt; +pub use crate::timer::TimerExt as _stm32h5xx_hal_timer_TimerExt; pub use crate::usb::UsbExt as _stm32h5xx_hal_usb_UsbExt; pub use crate::time::U32Ext as _; diff --git a/src/pwm/h5.rs b/src/pwm/h5.rs new file mode 100644 index 0000000..eb228e5 --- /dev/null +++ b/src/pwm/h5.rs @@ -0,0 +1,938 @@ +use super::{ + BreakInput, Ch, ComplementaryDisabled, ComplementaryImpossible, + ExternalTriggerPins, FaultPins, NPins, Pins, Pwm, C1, C2, C3, C4, +}; +use crate::gpio::{self, Alternate}; +use crate::pac; + +// Pin definitions, mark which pins can be used with which timers and channels +macro_rules! pins { + // Single channel timer + ($($TIMX:ty: + CH1($COMP1:ty): [$($( #[ $pmeta1:meta ] )* $CH1:ty),*] + CH1N: [$($( #[ $pmeta2:meta ] )* $CH1N:ty),*] + BRK: [$($( #[ $pmeta3:meta ] )* $BRK:ty),*] + BRK2: [$($( #[ $pmeta4:meta ] )* $BRK2:ty),*] // remove? + ETR: [$($( #[ $pmeta5:meta ] )* $ETR:ty),*] + )+) => { + $( + $( + $( #[ $pmeta1 ] )* + impl Pins<$TIMX, Ch, $COMP1> for $CH1 { + type Channel = Pwm<$TIMX, C1, $COMP1>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + $( + $( #[ $pmeta2 ] )* + impl NPins<$TIMX, Ch> for $CH1N {} + )* + $( + $( #[ $pmeta3 ] )* + impl FaultPins<$TIMX,> for $BRK { + const INPUT: BreakInput = BreakInput::BreakIn; + } + )* + $( + $( #[ $pmeta4 ] )* + impl FaultPins<$TIMX> for $BRK2 { + const INPUT: BreakInput = BreakInput::BreakIn2; + } + )* + $( + $( #[ $pmeta5 ] )* + impl ExternalTriggerPins<$TIMX> for $ETR {} + )* + )+ + }; + // Old single channel timer + /*($($TIMX:ty: OUT: [$($OUT:ty),*])+) => { + $( + $( + impl Pins<$TIMX, Ch, ComplementaryImpossible> for $OUT { + type Channel = Pwm<$TIMX, C1, ComplementaryImpossible>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + )+ + };*/ + // Dual channel timer $pm + ($($TIMX:ty: + CH1($COMP1:ty): [$($( #[ $pmeta1:meta ] )* $CH1:ty),*] + CH2($COMP2:ty): [$($( #[ $pmeta2:meta ] )* $CH2:ty),*] + CH1N: [$($( #[ $pmeta3:meta ] )* $CH1N:ty),*] + CH2N: [$($( #[ $pmeta4:meta ] )* $CH2N:ty),*] + BRK: [$($( #[ $pmeta5:meta ] )* $BRK:ty),*] + BRK2: [$($( #[ $pmeta6:meta ] )* $BRK2:ty),*] + ETR: [$($( #[ $pmeta7:meta ] )* $ETR:ty),*] + )+) => { + $( + $( + $( #[ $pmeta1 ] )* + impl Pins<$TIMX, Ch, $COMP1> for $CH1 { + type Channel = Pwm<$TIMX, C1, $COMP1>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + $( + $( #[ $pmeta2 ] )* + impl Pins<$TIMX, Ch, $COMP2> for $CH2 { + type Channel = Pwm<$TIMX, C2, $COMP2>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + $( + $( #[ $pmeta3 ] )* + impl NPins<$TIMX, Ch> for $CH1N {} + )* + $( + $( #[ $pmeta4 ] )* + impl NPins<$TIMX, Ch> for $CH2N {} + )* + $( + $( #[ $pmeta5 ] )* + impl FaultPins<$TIMX,> for $BRK { + const INPUT: BreakInput = BreakInput::BreakIn; + } + )* + $( + $( #[ $pmeta6 ] )* + impl FaultPins<$TIMX> for $BRK2 { + const INPUT: BreakInput = BreakInput::BreakIn2; + } + )* + $( + $( #[ $pmeta7 ] )* + impl ExternalTriggerPins<$TIMX> for $ETR {} + )* + )+ + }; + // Quad channel timers + ($($TIMX:ty: + CH1($COMP1:ty): [$($( #[ $pmeta1:meta ] )* $CH1:ty),*] + CH2($COMP2:ty): [$($( #[ $pmeta2:meta ] )* $CH2:ty),*] + CH3($COMP3:ty): [$($( #[ $pmeta3:meta ] )* $CH3:ty),*] + CH4($COMP4:ty): [$($( #[ $pmeta4:meta ] )* $CH4:ty),*] + CH1N: [$($( #[ $pmeta5:meta ] )* $CH1N:ty),*] + CH2N: [$($( #[ $pmeta6:meta ] )* $CH2N:ty),*] + CH3N: [$($( #[ $pmeta7:meta ] )* $CH3N:ty),*] + CH4N: [$($( #[ $pmeta8:meta ] )* $CH4N:ty),*] + BRK: [$($( #[ $pmeta9:meta ] )* $BRK:ty),*] + BRK2: [$($( #[ $pmeta10:meta ] )* $BRK2:ty),*] + ETR: [$($( #[ $pmeta11:meta ] )* $ETR:ty),*] + )+) => { + $( + $( + $( #[ $pmeta1 ] )* + impl Pins<$TIMX, Ch, $COMP1> for $CH1 { + type Channel = Pwm<$TIMX, C1, $COMP1>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + $( + $( #[ $pmeta2 ] )* + impl Pins<$TIMX, Ch, $COMP2> for $CH2 { + type Channel = Pwm<$TIMX, C2, $COMP2>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + $( + $( #[ $pmeta3 ] )* + impl Pins<$TIMX, Ch, $COMP3> for $CH3 { + type Channel = Pwm<$TIMX, C3, $COMP3>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + $( + $( #[ $pmeta4 ] )* + impl Pins<$TIMX, Ch, $COMP4> for $CH4 { + type Channel = Pwm<$TIMX, C4, $COMP4>; + fn split() -> Self::Channel { + Pwm::new() + } + } + )* + $( + $( #[ $pmeta5 ] )* + impl NPins<$TIMX, Ch> for $CH1N {} + )* + $( + $( #[ $pmeta6 ] )* + impl NPins<$TIMX, Ch> for $CH2N {} + )* + $( + $( #[ $pmeta7 ] )* + impl NPins<$TIMX, Ch> for $CH3N {} + )* + $( + $( #[ $pmeta8 ] )* + impl NPins<$TIMX, Ch> for $CH4N {} + )* + $( + $( #[ $pmeta9 ] )* + impl FaultPins<$TIMX> for $BRK { + const INPUT: BreakInput = BreakInput::BreakIn; + } + )* + $( + $( #[ $pmeta10 ] )* + impl FaultPins<$TIMX> for $BRK2 { + const INPUT: BreakInput = BreakInput::BreakIn2; + } + )* + $( + $( #[ $pmeta11 ] )* + impl ExternalTriggerPins<$TIMX> for $ETR {} + )* + )+ + } +} + +// Quad channel timers +#[cfg(feature = "rm0492")] +pins!( + pac::TIM1: + CH1(ComplementaryDisabled): [ + gpio::PA8>, + gpio::PA13>, + gpio::PB1>, + gpio::PB7>, + gpio::PC6> + ] + CH2(ComplementaryDisabled): [ + gpio::PA9>, + gpio::PA14>, + gpio::PC7>, + gpio::PB4>, + gpio::PB6> + ] + CH3(ComplementaryDisabled): [ + gpio::PA10>, + gpio::PB5>, + gpio::PC8>, + gpio::PA1> + ] + CH4(ComplementaryDisabled): [ + gpio::PA11>, + gpio::PC9>, + gpio::PA2>, + gpio::PC12> + ] + CH1N: [ + gpio::PA7>, + gpio::PB13>, + gpio::PA3> + ] + CH2N: [ + gpio::PB0>, + gpio::PB14>, + gpio::PA4>, + gpio::PB2>, + gpio::PB7> + ] + CH3N: [ + gpio::PB1>, + gpio::PB15>, + gpio::PB6> + ] + CH4N: [ + gpio::PC5>, + gpio::PA8>, + gpio::PA14> + ] + BRK: [ + gpio::PA6>, + gpio::PB12>, + gpio::PA4>, + gpio::PB3> + ] + BRK2: [ + gpio::PB8>, + gpio::PC10>, + gpio::PC11> + ] + ETR: [ + gpio::PA12>, + gpio::PC0>, + gpio::PA13>, + gpio::PB0> + ] + pac::TIM2: + CH1(ComplementaryImpossible): [ + gpio::PA0>, + gpio::PA5>, + gpio::PA15>, + gpio::PB2> + ] + CH2(ComplementaryImpossible): [ + gpio::PA1>, + gpio::PB3>, + gpio::PC11> + ] + CH3(ComplementaryImpossible): [ + gpio::PA2>, + gpio::PB10>, + gpio::PD2>, + gpio::PA7> + ] + CH4(ComplementaryImpossible): [ + gpio::PA3>, + gpio::PC4>, + gpio::PC12>, + gpio::PA12> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PA0>, + gpio::PA5>, + gpio::PA15>, + gpio::PD2> + ] + pac::TIM3: + CH1(ComplementaryImpossible): [ + gpio::PA6>, + gpio::PB4>, + gpio::PC6>, + gpio::PA0>, + gpio::PA14> + ] + CH2(ComplementaryImpossible): [ + gpio::PA7>, + gpio::PB5>, + gpio::PC7>, + gpio::PA11> + ] + CH3(ComplementaryImpossible): [ + gpio::PB0>, + gpio::PC8>, + gpio::PA8>, + gpio::PB6> + ] + CH4(ComplementaryImpossible): [ + gpio::PB1>, + gpio::PC9>, + gpio::PA12>, + gpio::PB15> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PD2>, + gpio::PA2>, + gpio::PB7>, + gpio::PC1> + ] +); + +// Dual channel timers +#[cfg(feature = "h523_h533")] +pins!( + // TODO: TIM12 seems to be missing for 523's pac, re add once fixed + /*pac::TIM12: + // According to Table 7. Timer features in the DS14540 Rev 2 and DS14539 Rev 2 datasheets TIM12 has 1 complementary output + // but according to Table 16. Alternate functions there exists no such output + CH1(ComplementaryImpossible): [ + gpio::PB14> + ] + CH2(ComplementaryImpossible): [ + gpio::PB15> + ] + CH1N: [] + CH2N: [] + BRK: [] + BRK2: [] + ETR: []*/ + pac::TIM15: + CH1(ComplementaryDisabled): [ + gpio::PA2>, + gpio::PC12>, + gpio::PE5> + ] + CH2(ComplementaryImpossible): [ + gpio::PA3>, + gpio::PE6> + ] + CH1N: [ + gpio::PA1>, + gpio::PE4> + ] + CH2N: [] + BRK: [ + gpio::PA0>, + gpio::PD2>, + gpio::PE3> + ] + BRK2: [] + ETR: [] +); + +// Quad channel timers +#[cfg(feature = "h523_h533")] +pins!( + pac::TIM1: + // According to Table 7. Timer features in the DS14540 Rev 2 and DS14539 Rev 2 datasheets TIM8 has 3 complementary outputs + // but according to Table 16. Alternate functions there are 4 such outputs + CH1(ComplementaryDisabled): [ + gpio::PA8>, + gpio::PE9> + ] + CH2(ComplementaryDisabled): [ + gpio::PA9>, + gpio::PE11> + ] + CH3(ComplementaryDisabled): [ + gpio::PA10>, + gpio::PE13> + ] + CH4(ComplementaryDisabled): [ + gpio::PA11>, + gpio::PE14> + ] + CH1N: [ + gpio::PA7>, + gpio::PB13>, + gpio::PE8> + ] + CH2N: [ + gpio::PB0>, + gpio::PB14>, + gpio::PE10> + ] + CH3N: [ + gpio::PB1>, + gpio::PB15>, + gpio::PE12> + ] + CH4N: [ + gpio::PC5>, + gpio::PD5>, + gpio::PE15> + ] + BRK: [ + gpio::PA6>, + gpio::PB12>, + gpio::PE15> + ] + BRK2: [ + gpio::PE6>, + gpio::PG4> + ] + ETR: [ + gpio::PA12>, + gpio::PE7>, + gpio::PG5> + ] + pac::TIM2: + CH1(ComplementaryImpossible): [ + gpio::PA0>, + gpio::PA5>, + gpio::PA15> + ] + CH2(ComplementaryImpossible): [ + gpio::PA1>, + gpio::PB3> + ] + CH3(ComplementaryImpossible): [ + gpio::PA2>, + gpio::PB10> + ] + CH4(ComplementaryImpossible): [ + gpio::PA3>, + gpio::PC4> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PA0>, + gpio::PA5>, + gpio::PA15> + ] + pac::TIM3: + CH1(ComplementaryImpossible): [ + gpio::PA6>, + gpio::PB4>, + gpio::PC6> + ] + CH2(ComplementaryImpossible): [ + gpio::PA7>, + gpio::PB5>, + gpio::PC7> + ] + CH3(ComplementaryImpossible): [ + gpio::PB0>, + gpio::PC8> + ] + CH4(ComplementaryImpossible): [ + gpio::PB1>, + gpio::PC9> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PD2> + ] + pac::TIM4: + CH1(ComplementaryImpossible): [ + gpio::PB6>, + gpio::PD12> + ] + CH2(ComplementaryImpossible): [ + gpio::PB7>, + gpio::PD13> + ] + CH3(ComplementaryImpossible): [ + gpio::PB8>, + gpio::PD14> + ] + CH4(ComplementaryImpossible): [ + gpio::PB9>, + gpio::PC2>, + gpio::PD15> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PE0> + ] + pac::TIM5: + CH1(ComplementaryImpossible): [ + gpio::PA0> + ] + CH2(ComplementaryImpossible): [ + gpio::PA1> + ] + CH3(ComplementaryImpossible): [ + gpio::PA2> + ] + CH4(ComplementaryImpossible): [ + gpio::PA3> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PA4> + ] + pac::TIM8: + // According to Table 7. Timer features in the DS14540 Rev 2 and DS14539 Rev 2 datasheets TIM8 has 3 complementary outputs + // but according to Table 16. Alternate functions there are 4 such outputs + CH1(ComplementaryDisabled): [ + gpio::PB10>, + gpio::PC6> + ] + CH2(ComplementaryDisabled): [ + gpio::PB13>, + gpio::PC7> + ] + CH3(ComplementaryDisabled): [ + gpio::PB12>, + gpio::PC8> + ] + CH4(ComplementaryDisabled): [ + gpio::PC9> + ] + CH1N: [ + gpio::PA5>, + gpio::PA7> + ] + CH2N: [ + gpio::PB0>, + gpio::PB14> + ] + CH3N: [ + gpio::PB1>, + gpio::PB15> + ] + CH4N: [ + gpio::PB2>, + gpio::PD0> + ] + BRK: [ + gpio::PA6>, + gpio::PG2> + ] + BRK2: [ + gpio::PA8>, + gpio::PG3> + ] + ETR: [ + gpio::PA0>, + gpio::PG8> + ] +); + +// Single channel timers +#[cfg(feature = "h56x_h573")] +pins!( + pac::TIM13: + // According to Table 7. Timer features in the DS14121 Rev 5 and DS14258 Rev 6 datasheets TIM13 has 1 complementary output + // but according to Table 16. Alternate functions there exists no such output + CH1(ComplementaryImpossible): [ + gpio::PA6>, + gpio::PF8> + ] + CH1N: [] + BRK: [] + BRK2: [] + ETR: [] + pac::TIM14: + // According to Table 7. Timer features in the DS14121 Rev 5 and DS14258 Rev 6 datasheets TIM14 has 1 complementary output + // but according to Table 16. Alternate functions there exists no such output + CH1(ComplementaryImpossible): [ + gpio::PA7>, + gpio::PF9> + ] + CH1N: [] + BRK: [] + BRK2: [] + ETR: [] + pac::TIM16: + CH1(ComplementaryDisabled): [ + gpio::PD8>, + gpio::PF6> + ] + CH1N: [ + gpio::PB6>, + gpio::PF8> + ] + BRK: [ + gpio::PB4>, + gpio::PC0>, + gpio::PF10> + ] + BRK2: [] + ETR: [] + pac::TIM17: + CH1(ComplementaryDisabled): [ + gpio::PB9>, + gpio::PC2>, + gpio::PF7> + ] + CH1N: [ + gpio::PB7>, + gpio::PF9> + ] + BRK: [ + gpio::PB5>, + gpio::PG6> + ] + BRK2: [] + ETR: [] +); + +// Dual channel timers +#[cfg(feature = "h56x_h573")] +pins!( + pac::TIM12: + // According to Table 7. Timer features in the DS14121 Rev 5 and DS14258 Rev 6 datasheets TIM12 has 1 complementary output + // but according to Table 16. Alternate functions there exists no such output + CH1(ComplementaryImpossible): [ + gpio::PB14>, + gpio::PH6> + ] + CH2(ComplementaryImpossible): [ + gpio::PB15>, + gpio::PH9> + ] + CH1N: [] + CH2N: [] + BRK: [] + BRK2: [] + ETR: [] + + pac::TIM15: + CH1(ComplementaryDisabled): [ + gpio::PA2>, + gpio::PC12>, + gpio::PE5> + ] + CH2(ComplementaryImpossible): [ + gpio::PA3>, + gpio::PE6> + ] + CH1N: [ + gpio::PA1>, + gpio::PE4> + ] + CH2N: [] + BRK: [ + gpio::PA0>, + gpio::PD2>, + gpio::PE3> + ] + BRK2: [] + ETR: [] + +); + +// Quad channel timers +#[cfg(feature = "h56x_h573")] +pins!( + pac::TIM1: + CH1(ComplementaryDisabled): [ + gpio::PA8>, + gpio::PE9>, + gpio::PH11> + ] + CH2(ComplementaryDisabled): [ + gpio::PA9>, + gpio::PE11>, + gpio::PH9> + ] + CH3(ComplementaryDisabled): [ + gpio::PA10>, + gpio::PE13>, + gpio::PH7> + ] + CH4(ComplementaryDisabled): [ + gpio::PA11>, + gpio::PE14> + ] + CH1N: [ + gpio::PA7>, + gpio::PB13>, + gpio::PE8>, + gpio::PH10> + ] + CH2N: [ + gpio::PB0>, + gpio::PB14>, + gpio::PE10>, + gpio::PH8> + ] + CH3N: [ + gpio::PB1>, + gpio::PB15>, + gpio::PE12>, + gpio::PH6> + ] + CH4N: [ + gpio::PC5>, + gpio::PD5>, + gpio::PE15> + ] + BRK: [ + gpio::PA6>, + gpio::PB12>, + gpio::PE15>, + gpio::PH12> + ] + BRK2: [ + gpio::PE6>, + gpio::PG4> + ] + ETR: [ + gpio::PA12>, + gpio::PE7>, + gpio::PG5> + ] + pac::TIM2: + CH1(ComplementaryImpossible): [ + gpio::PA0>, + gpio::PA5>, + gpio::PA15> + ] + CH2(ComplementaryImpossible): [ + gpio::PA1>, + gpio::PB3> + ] + CH3(ComplementaryImpossible): [ + gpio::PA2>, + gpio::PB10> + ] + CH4(ComplementaryImpossible): [ + gpio::PA3>, + gpio::PC4>, + gpio::PB11> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PA0>, + gpio::PA5>, + gpio::PA15> + ] + pac::TIM3: + CH1(ComplementaryImpossible): [ + gpio::PA6>, + gpio::PB4>, + gpio::PC6> + ] + CH2(ComplementaryImpossible): [ + gpio::PA7>, + gpio::PB5>, + gpio::PC7> + ] + CH3(ComplementaryImpossible): [ + gpio::PB0>, + gpio::PC8> + ] + CH4(ComplementaryImpossible): [ + gpio::PB1>, + gpio::PC9> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PD2> + ] + pac::TIM4: + CH1(ComplementaryImpossible): [ + gpio::PB6>, + gpio::PD12> + ] + CH2(ComplementaryImpossible): [ + gpio::PB7>, + gpio::PD13> + ] + CH3(ComplementaryImpossible): [ + gpio::PB8>, + gpio::PD14> + ] + CH4(ComplementaryImpossible): [ + gpio::PB9>, + gpio::PC2>, + gpio::PD15> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PE0> + ] + pac::TIM5: + CH1(ComplementaryImpossible): [ + gpio::PA0>, + gpio::PH10> + ] + CH2(ComplementaryImpossible): [ + gpio::PA1>, + gpio::PH11> + ] + CH3(ComplementaryImpossible): [ + gpio::PA2>, + gpio::PH12> + ] + CH4(ComplementaryImpossible): [ + gpio::PA3>, + gpio::PI0> + ] + CH1N: [] + CH2N: [] + CH3N: [] + CH4N: [] + BRK: [] + BRK2: [] + ETR: [ + gpio::PA4>, + gpio::PH8> + ] + pac::TIM8: + CH1(ComplementaryDisabled): [ + gpio::PC6>, + gpio::PH6>, + gpio::PI5> + ] + CH2(ComplementaryDisabled): [ + gpio::PC7>, + gpio::PH8>, + gpio::PI6> + ] + CH3(ComplementaryDisabled): [ + gpio::PC8>, + gpio::PH10>, + gpio::PI7> + ] + CH4(ComplementaryDisabled): [ + gpio::PC9>, + gpio::PI2> + ] + CH1N: [ + gpio::PA5>, + gpio::PA7>, + gpio::PH7>, + gpio::PH13> + ] + CH2N: [ + gpio::PB0>, + gpio::PB14>, + gpio::PH9>, + gpio::PH14> + ] + CH3N: [ + gpio::PB1>, + gpio::PB15>, + gpio::PH11>, + gpio::PH15> + ] + CH4N: [ + gpio::PB2>, + gpio::PD0>, + gpio::PH12> + ] + BRK: [ + gpio::PA6>, + gpio::PG2>, + gpio::PH12>, + gpio::PI4> + ] + BRK2: [ + gpio::PA8>, + gpio::PG3>, + gpio::PI1> + ] + ETR: [ + gpio::PA0>, + gpio::PG8>, + gpio::PI3> + ] +); diff --git a/src/pwm/mod.rs b/src/pwm/mod.rs new file mode 100644 index 0000000..c9d4cdb --- /dev/null +++ b/src/pwm/mod.rs @@ -0,0 +1,1321 @@ +//! Pulse Width Modulation (PWM) +//! +//! Timers support up to 4 simultaneous PWM output channels +//! +//! ## Examples +//! +//! - [Simple example](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/pwm.rs) +//! - [Advanced example](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/pwm_advanced.rs) +//! - [LPTIM peripheral example](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/pwm_lptim.rs) +//! +//! ## Usage +//! +//! ```rust +//! let gpioa = ..; // Set up and split GPIOA +//! let pins = ( +//! gpioa.pa8.into_alternate_af1(), +//! gpioa.pa9.into_alternate_af1(), +//! gpioa.pa10.into_alternate_af1(), +//! gpioa.pa11.into_alternate_af1(), +//! ); +//! ``` +//! +//! To see which pins can be used with which timers, see your device datasheet or see which pins implement the [Pins](trait.Pins.html) trait. +//! +//! Then call the `pwm` function on the corresponding timer: +//! +//! ``` +//! let device: pac::Peripherals = ..; +//! +//! // Put the timer in PWM mode using the specified pins +//! // with a frequency of 100 Hz. +//! let (c0, c1, c2, c3) = device.TIM1.pwm( +//! pins, +//! 100.Hz(), +//! prec, +//! &clocks +//! ); +//! +//! // Set the duty cycle of channel 0 to 50% +//! c0.set_duty(c0.get_max_duty() / 2); +//! // PWM outputs are disabled by default +//! c0.enable() +//! ``` +//! +//! ## Advanced features +//! +//! Some timers support various advanced features. These features are +//! accessed by calling TIMx.[pwm_advanced](trait.PwmAdvExt.html#tymethod.pwm_advanced) to get a [PwmBuilder](struct.PwmBuilder.html) +//! and calling appropriate methods of the PwmBuilder before calling [PwmBuilder::finalize](struct.PwmBuilder.html#method.finalize) +//! to create the PWM channels and a [PwmControl](struct.PwmControl.html) struct exposing things like [FaultMonitor](trait.FaultMonitor.html) functionality. +//! +//! ```rust +//! let gpioa = ..; // Set up and split GPIOA +//! let pins = ( +//! gpioa.pa8.into_alternate_af1(), +//! gpioa.pa9.into_alternate_af1(), +//! gpioa.pa10.into_alternate_af1(), +//! gpioa.pa11.into_alternate_af1(), +//! ); +//! ``` +//! +//! Then call the `pwm_advanced` function on the corresponding timer: +//! +//! ``` +//! let device: pac::Peripherals = ..; +//! +//! // Put the timer in PWM mode using the specified pins +//! // with a frequency of 100 Hz, 2us deadtime between complementary edges, +//! // center-aligned PWM, and an active-low fault input +//! let (mut control, (c1, c2, c3, c4)) = device.TIM1 +//! .pwm_advanced( +//! pins, +//! prec, +//! &clocks +//! ) +//! .frequency(100.Hz()) +//! .center_aligned() +//! .with_break_pin(gpioe.pe15.into_alternate_af1(), Polarity::ActiveLow) +//! .finalize(); +//! ``` +//! +//! Then change some PWM channels to active-low (reversing pin polarity relative to logic polarity) +//! or enable a complementary PWM output (both only on some timer channels). +//! +//! ``` +//! // Set channel 1 to complementary with both the regular and complementary output active low +//! let mut c1 = c1 +//! .into_complementary(gpioe.pe8.into_alternate_af1()) +//! .into_active_low() +//! .into_comp_active_low(); +//! +//! // Set the duty cycle of channel 1 to 50% +//! c1.set_duty(c1.get_max_duty() / 2); +//! +//! // PWM outputs are disabled by default +//! c1.enable() +//! +//! // If a PWM fault happened, you can clear it through the control structure +//! if control.is_fault_active() { +//! control.clear_fault(); +//! } +//! ``` +//! +//! ## Fault (Break) inputs +//! +//! The [PwmBuilder::with_break_pin](struct.PwmBuilder.html#method.with_break_pin) method emables break/fault functionality as described in the reference manual. +//! +//! The [PwmControl](struct.PwmControl.html) will then implement [FaultMonitor](trait.FaultMonitor.html) which can be used to monitor and control the fault status. +//! +//! If the break input becomes active, all PWM will be stopped. +//! +//! The BKIN hardware respects deadtimes when going into the fault state while the BKIN2 hardware acts immediately. +//! +//! The fault state puts all PWM pins into high-impedance mode, so pull-ups or pull-downs should be used to set the pins to a safe state. +//! +//! Currently only one break input (BKIN or BKIN2) can be enabled, this could be changed to allow two break inputs at the same time. +//! +//! ## Complementary outputs +//! +//! Once a PWM channel has been created through TIMx.pwm(...) or TIMx.pwm_advanced(...).finalize(), it can be put into complementary mode or have its polarity changed. +//! +//! [Pwm::into_complementary](struct.Pwm.html#method.into_complementary) takes a pin that can be used as a complementary output +//! +//! For allowed pins, see the device datasheet or see which pins implement the [NPins](trait.NPins.html) trait. +//! +//! ## PWM alignment +//! +//! A timer with multiple PWM channels can have different alignments between PWM channels. +//! +//! All PWM-capable timers support left aligned PWM. In left aligned PWM, all channels go active at the start of a PWM cycle then go inactive when their duty cycles expire. +//! +//! Some timers also support right aligned and center aligned PWM. In right aligned PWM, all channels go inactive at the end of a PWM cycle and go active when their duty cycle remains until the end of the PWM cycle. +//! +//! In center aligned PWM, all channels start inactive, then go active when half their duty cycle remains until the center of the PWM period, then go inactive again once their duty cycle expires and remain inactive for the rest of the PWM cycle. +//! This produces a symmetrical PWM waveform, with increasing duty cycle moving both the inactive and active edge equally. +//! When a component is placed across multiple PWM channels with different duty cycles in center aligned mode, the component will see twice the ripple frequency as the PWM switching frequency. +//! +//! ## PWM channel polarity +//! +//! A PWM channel is active or inactive based on the duty cycle, alignment, etc. However, the actual GPIO signal level that represents active vs inactive is configurable. +//! +//! The [into_active_low](struct.Pwm.html#method.into_active_low) and [into_active_high](struct.Pwm.html#method.into_active_high) methods set the active signal level to low (VSS) or high (VDD). +//! +//! The complementary output is active when the regular output is inactive. The active signal level of the complementary output is set by the [into_comp_active_low](struct.Pwm.html#method.into_comp_active_low), and [into_comp_active_high](struct.Pwm.html#method.into_comp_active_high) methods. +//! +//! ## Deadtime +//! +//! All channels on a given timer share the same deadtime setting as set by [PwmBuilder::with_deadtime](struct.PwmBuilder.html#method.with_deadtime) +//! +//! PWM channels with complementary outputs can have deadtime added to the signal. Dead time is used to prevent cross-conduction in some power electronics topologies. +//! +//! With complementary outputs and dead time enabled on a PWM channel, when the regular output goes inactive (high or low based on into_active_high/into_active_low), the complementary output remains inactive until the deadtime passes. +//! Similarily, when the complementary output goes inactive, the regular output waits until the deadtime passes before it goes active. +//! +//! Deadtime is applied based on the logical active/inactive levels. Depending on the PWM polarity and complementary polarity, both pins can be high or low during deadtime; they will both be in the inactive state. +//! +//! The deadtime must be 4032 counts of the timer clock or less or the builder will assert/panic. For a 200MHz timer this is 20 microseconds; slower timers can have even longer deadtimes. +//! +//! ## Disabled or faulted state +//! +//! At initialization, when a PWM channel is disabled, or while a fault is active, the PWM outputs will be in a high impedance state. +//! +//! If needed, pull-up or pull-down resistors should be used to ensure that all power electronics are in a safe state while the GPIO pins are high impedance. +//! +//! Although the timers allow quite a bit of configuration here, that would require configuring the PWM pins before configuring other parts of the timer, which would be a challenge with how type states and traits are used for timer configuration. +//! +//! Additionally, the GPIO will always be high-impedance during power-up or in reset, so pull-ups or pull-downs to ensure safe state are always a good idea. + +mod h5; + +use core::marker::PhantomData; + +use crate::pac; + +use crate::rcc::{rec, CoreClocks, ResetEnable}; +use crate::time::{Hertz, NanoSeconds}; +use crate::timer::{GetClk, Event}; +use fugit::ExtU32; + +// This trait marks that a GPIO pin can be used with a specific timer channel +// TIM is the timer being used +// CHANNEL is a marker struct for the channel (or multi channels for tuples) +// Example: impl Pins for PA8> { type Channel = Pwm; } +/// Pins is a trait that marks which GPIO pins may be used as PWM channels; it should not be directly used. +/// See the device datasheet 'Pin descriptions' chapter for which pins can be used with which timer PWM channels (or look at Implementors) +pub trait Pins { + type Channel; + fn split() -> Self::Channel; +} + +/// NPins is a trait that marks which GPIO pins may be used as complementary PWM channels; it should not be directly used. +/// See the device datasheet 'Pin descriptions' chapter for which pins can be used with which timer PWM channels (or look at Implementors) +pub trait NPins {} + +/// FaultPins is a trait that marks which GPIO pins may be used as PWM fault inputs; it should not be directly used. +/// See the device datasheet 'Pin descriptions' chapter for which pins can be used with which timer PWM channels (or look at Implementors) +pub trait FaultPins { + const INPUT: BreakInput; +} + +/// ExternalTriggerPins is a trait that marks which GPIO pins may be used as external input trigger; it should not be used directly. +pub trait ExternalTriggerPins {} + +/// Channel wrapper +pub struct Ch; + +/// Marker struct for PWM channel 1 on Pins trait and Pwm struct +pub const C1: u8 = 0; +/// Marker struct for PWM channel 2 on Pins trait and Pwm struct +pub const C2: u8 = 1; +/// Marker struct for PWM channel 3 on Pins trait and Pwm struct +pub const C3: u8 = 2; +/// Marker struct for PWM channel 4 on Pins trait and Pwm struct +pub const C4: u8 = 3; + +/// Marker struct for pins and PWM channels that do not support complementary output +pub struct ComplementaryImpossible; +/// Marker struct for pins and PWM channels that support complementary output but are not using it +pub struct ComplementaryDisabled; +/// Marker struct for PWM channels that have complementary outputs enabled +pub struct ComplementaryEnabled; + +/// Enum for IO polarity +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Polarity { + ActiveHigh, + ActiveLow, +} + +/// Configuration enum to keep track of which break input corresponds with which FaultPins +#[derive(PartialEq, Eq)] +pub enum BreakInput { + BreakIn, + BreakIn2, +} + +/// Internal enum that keeps track of the count settings before PWM is finalized +enum CountSettings { + Frequency(Hertz), + Explicit { period: WIDTH, prescaler: u16 }, +} + +/// Marker struct for active high IO polarity +pub struct ActiveHigh; +/// Marker struct for active low IO polarity +pub struct ActiveLow; + +/// Whether a PWM signal is left-aligned, right-aligned, or center-aligned +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Alignment { + Left, + Right, + Center, +} + +/// Pwm represents one PWM channel; it is created by calling TIM?.pwm(...) and lets you control the channel through the PwmPin trait +pub struct Pwm { + _markers: PhantomData<(TIM, COMP)>, +} + +impl Pwm { + fn new() -> Self { + Self { + _markers: PhantomData, + } + } +} + +/// PwmBuilder is used to configure advanced PWM features +pub struct PwmBuilder { + _markers: PhantomData<(TIM, PINS, CHANNEL, FAULT, COMP)>, + alignment: Alignment, + base_freq: Hertz, + count: CountSettings, + bkin_enabled: bool, // If the FAULT type parameter is FaultEnabled, either bkin or bkin2 must be enabled + bkin2_enabled: bool, + fault_polarity: Polarity, + deadtime: NanoSeconds, +} + +/// Allows a PwmControl to monitor and control faults (break inputs) of a timer's PWM channels +pub trait FaultMonitor { + /// Returns true if a fault is preventing PWM output + fn is_fault_active(&self) -> bool; + + /// Enables PWM output, clearing fault state and immediately resuming PWM; if the break pin is still active, this can't clear the fault. + fn clear_fault(&mut self); + + /// Disables PWM output, setting fault state; this can be used to stop all PWM from a timer in software detected faults + fn set_fault(&mut self); +} + +/// Exposes timer wide advanced features, such as [FaultMonitor](trait.FaultMonitor.html) +/// or future features like trigger outputs for synchronization with ADCs and other peripherals +pub struct PwmControl { + _tim: PhantomData, + _fault: PhantomData, +} + +impl PwmControl { + fn new() -> Self { + Self { + _tim: PhantomData, + _fault: PhantomData, + } + } +} + +/// Marker struct indicating that a PwmControl is in charge of fault monitoring +pub struct FaultEnabled; +/// Marker struct indicating that a PwmControl does not handle fault monitoring +pub struct FaultDisabled; + +// automatically implement Pins trait for tuples of individual pins +macro_rules! pins_tuples { + // Tuple of two pins + ($(($CHA:ident, $CHB:ident)),*) => { + $( + impl Pins, Ch<$CHB>), (TA, TB)> for (CHA, CHB) + where + CHA: Pins, TA>, + CHB: Pins, TB>, + { + type Channel = (Pwm, Pwm); + fn split() -> Self::Channel { + (Pwm::new(), Pwm::new()) + } + } + )* + }; + // Tuple of three pins + ($(($CHA:ident, $CHB:ident, $CHC:ident)),*) => { + $( + pins_tuples! { + PERM3: ($CHA, $CHB, $CHC), + PERM3: ($CHA, $CHC, $CHB), + PERM3: ($CHB, $CHA, $CHC), + PERM3: ($CHB, $CHC, $CHA), + PERM3: ($CHC, $CHA, $CHB), + PERM3: ($CHC, $CHB, $CHA) + } + )* + }; + // Permutate tuple of three pins + ($(PERM3: ($CHA:ident, $CHB:ident, $CHC:ident)),*) => { + $( + impl Pins, Ch<$CHB>, Ch<$CHC>), (TA, TB, TC)> for (CHA, CHB, CHC) + where + CHA: Pins, TA>, + CHB: Pins, TB>, + CHC: Pins, TC>, + { + type Channel = (Pwm, Pwm, Pwm); + fn split() -> Self::Channel { + (Pwm::new(), Pwm::new(), Pwm::new()) + } + } + )* + }; + // Tuple of four pins (permutates the last 3, leaves 4th in place) + ($(($CHD:ident, $CHA:ident, $CHB:ident, $CHC:ident)),*) => { + $( + pins_tuples! { + PERM4: ($CHD, $CHA, $CHB, $CHC), + PERM4: ($CHD, $CHA, $CHC, $CHB), + PERM4: ($CHD, $CHB, $CHA, $CHC), + PERM4: ($CHD, $CHB, $CHC, $CHA), + PERM4: ($CHD, $CHC, $CHA, $CHB), + PERM4: ($CHD, $CHC, $CHB, $CHA) + } + )* + }; + // Tuple of four pins (permutates the last 3, leaves 1st in place) + ($(PERM4: ($CHA:ident, $CHB:ident, $CHC:ident, $CHD:ident)),*) => { + $( + impl Pins, Ch<$CHB>, Ch<$CHC>, Ch<$CHD>), (TA, TB, TC, TD)> for (CHA, CHB, CHC, CHD) + where + CHA: Pins, TA>, + CHB: Pins, TB>, + CHC: Pins, TC>, + CHD: Pins, TD>, + { + type Channel = (Pwm, Pwm, Pwm, Pwm); + fn split() -> Self::Channel { + (Pwm::new(), Pwm::new(), Pwm::new(), Pwm::new()) + } + } + )* + } +} + +pins_tuples! { + (C1, C2), + (C2, C1), + (C1, C3), + (C3, C1), + (C1, C4), + (C4, C1), + (C2, C3), + (C3, C2), + (C2, C4), + (C4, C2), + (C3, C4), + (C4, C3) +} + +pins_tuples! { + (C1, C2, C3), + (C1, C2, C4), + (C1, C3, C4), + (C2, C3, C4) +} + +pins_tuples! { + (C1, C2, C3, C4), + (C2, C1, C3, C4), + (C3, C1, C2, C4), + (C4, C1, C2, C3) +} + +// Period and prescaler calculator for 32-bit timers +// Returns (arr, psc) +fn calculate_frequency_32bit( + base_freq: Hertz, + freq: Hertz, + alignment: Alignment, +) -> (u32, u16) { + let divisor = if let Alignment::Center = alignment { + freq.raw() * 2 + } else { + freq.raw() + }; + + // Round to the nearest period + let arr = (base_freq.raw() + (divisor >> 1)) / divisor - 1; + + (arr, 0) +} + +// Period and prescaler calculator for 16-bit timers +// Returns (arr, psc) +// Returns as (u32, u16) to be compatible but arr will always be a valid u16 +fn calculate_frequency_16bit( + base_freq: Hertz, + freq: Hertz, + alignment: Alignment, +) -> (u32, u16) { + let ideal_period = + calculate_frequency_32bit(base_freq, freq, alignment).0 + 1; + + // Division factor is (PSC + 1) + let prescale = (ideal_period - 1) / (1 << 16); + + // This will always fit in a 16-bit value because u32::MAX / (1 << 16) fits in a 16 bit + + // Round to the nearest period + let period = (ideal_period + (prescale >> 1)) / (prescale + 1) - 1; + + // It should be impossible to fail these asserts + assert!(period <= 0xFFFF); + assert!(prescale <= 0xFFFF); + + (period, prescale as u16) +} + +// Deadtime calculator helper function +// Returns (BDTR.DTG, CR1.CKD) +fn calculate_deadtime(base_freq: Hertz, deadtime: NanoSeconds) -> (u8, u8) { + // tDTS is based on tCK_INT which is before the prescaler + // It uses its own separate prescaler CR1.CKD + + // ticks = ns * GHz = ns * Hz / 1e9 + // Cortex-M7 has 32x32->64 multiply but no 64-bit divide + // Divide by 100000 then 10000 by multiplying and shifting + // This can't overflow because both values being multiplied are u32 + let deadtime_ticks = deadtime.ticks() as u64 * base_freq.raw() as u64; + // Make sure we won't overflow when multiplying; DTG is max 1008 ticks and CKD is max prescaler of 4 + // so deadtimes over 4032 ticks are impossible (4032*10^9 before dividing) + assert!(deadtime_ticks <= 4_032_000_000_000u64); + let deadtime_ticks = deadtime_ticks * 42950; + let deadtime_ticks = (deadtime_ticks >> 32) as u32; + let deadtime_ticks = deadtime_ticks as u64 * 429497; + let deadtime_ticks = (deadtime_ticks >> 32) as u32; + + // Choose CR1 CKD divider of 1, 2, or 4 to determine tDTS + let (deadtime_ticks, ckd) = match deadtime_ticks { + t if t <= 1008 => (deadtime_ticks, 1), + t if t <= 2016 => (deadtime_ticks / 2, 2), + t if t <= 4032 => (deadtime_ticks / 4, 4), + _ => { + panic!("Deadtime must be less than 4032 ticks of timer base clock.") + } + }; + + // Choose BDTR DTG bits to match deadtime_ticks + // We want the smallest value of DTG that gives a deadtime >= the requested deadtime + for dtg in 0..=255 { + let actual_deadtime: u32 = match dtg { + d if d < 128 => d, + d if d < 192 => 2 * (64 + (d & 0x3F)), + d if d < 224 => 8 * (32 + (d & 0x1F)), + _ => 16 * (32 + (dtg & 0x1F)), + }; + + if actual_deadtime >= deadtime_ticks { + return (dtg as u8, ckd); + } + } + + panic!("This should be unreachable."); +} + +// PwmExt trait +/// Allows the pwm() method to be added to the peripheral register structs from the device crate +pub trait PwmExt: Sized { + type Rec: ResetEnable; + + /// The requested frequency will be rounded to the nearest achievable frequency; the actual frequency may be higher or lower than requested. + fn pwm( + self, + _pins: PINS, + frequency: Hertz, + prec: Self::Rec, + clocks: &CoreClocks, + ) -> PINS::Channel + where + PINS: Pins; +} + +pub trait PwmAdvExt: Sized { + type Rec: ResetEnable; + + fn pwm_advanced( + self, + _pins: PINS, + prec: Self::Rec, + clocks: &CoreClocks, + ) -> PwmBuilder + where + PINS: Pins; +} + +// Implement PwmExt trait for timer +macro_rules! pwm_ext_hal { + ($TIMX:ty: $timX:ident, $Rec:ident) => { + impl PwmExt for $TIMX { + type Rec = rec::$Rec; + + fn pwm( + self, + pins: PINS, + frequency: Hertz, + prec: rec::$Rec, + clocks: &CoreClocks, + ) -> PINS::Channel + where + PINS: Pins, + { + $timX(self, pins, frequency, prec, clocks) + } + } + }; +} + +// Implement PWM configuration for timer +macro_rules! tim_hal { + ($($TIMX:ty: ($timX:ident, $Rec:ident, $typ:ty, $bits:expr + $(, DIR: $cms:ident)? + $(, BDTR: $bdtr:ident, $moe_set:ident, $af1:ident, $bkinp_setting:ident + $(, $bk2inp_setting:ident)? + )? + ),)+) => { + $( + pwm_ext_hal!($TIMX: $timX, $Rec); + + /// Configures PWM + fn $timX( + tim: $TIMX, + _pins: PINS, + freq: Hertz, + prec: rec::$Rec, + clocks: &CoreClocks, + ) -> PINS::Channel + where + PINS: Pins<$TIMX, T, U>, + { + let _ = prec.enable().reset(); // drop + + let clk = <$TIMX>::get_clk(clocks) + .expect(concat!(stringify!($TIMX), ": Input clock not running!")); + + let (period, prescale) = match $bits { + 16 => calculate_frequency_16bit(clk, freq, Alignment::Left), + _ => calculate_frequency_32bit(clk, freq, Alignment::Left), + }; + + // Write prescale + tim.psc().write(|w| { w.psc().set(prescale as u16) }); + + // Write period + tim.arr().write(|w| { w.arr().set(period as u32) }); // TODO Some timers are supposed to have 16 bit register fields here + + // BDTR: Advanced-control timers + $( + // Set CCxP = OCxREF / CCxNP = !OCxREF + // Refer to RM0433 Rev 6 - Table 324. + tim.$bdtr().write(|w| w.moe().$moe_set()); + )? + + tim.cr1().write(|w| w.cen().enabled()); + + PINS::split() + } + + impl PwmAdvExt<$typ> for $TIMX { + type Rec = rec::$Rec; + + fn pwm_advanced( + self, + _pins: PINS, + prec: Self::Rec, + clocks: &CoreClocks, + ) -> PwmBuilder + where + PINS: Pins + { + let _ = prec.enable().reset(); // drop + + let clk = <$TIMX>::get_clk(clocks) + .expect(concat!(stringify!($TIMX), ": Input clock not running!")); + + PwmBuilder { + _markers: PhantomData, + alignment: Alignment::Left, + base_freq: clk, + count: CountSettings::Explicit { period: 65535, prescaler: 0, }, + bkin_enabled: false, + bkin2_enabled: false, + fault_polarity: Polarity::ActiveLow, + deadtime: 0.nanos(), + } + } + } + + impl + PwmBuilder<$TIMX, PINS, CHANNEL, FAULT, COMP, $typ> + where + PINS: Pins<$TIMX, CHANNEL, COMP>, + { + pub fn finalize(self) -> (PwmControl<$TIMX, FAULT>, PINS::Channel) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + let (period, prescaler) = match self.count { + CountSettings::Explicit { period, prescaler } => (period as u32, prescaler), + CountSettings::Frequency( freq ) => { + match $bits { + 16 => calculate_frequency_16bit(self.base_freq, freq, self.alignment), + _ => calculate_frequency_32bit(self.base_freq, freq, self.alignment), + } + }, + }; + + // Write prescaler + tim.psc().write(|w| w.psc().set(prescaler as u16)); + + // Write period + tim.arr().write(|w| w.arr().set(period as u32)); // TODO: Supposed to be 16 bit for some timers + + $( + let (dtg, ckd) = calculate_deadtime(self.base_freq, self.deadtime); + + match ckd { + 1 => tim.cr1().modify(|_, w| w.ckd().div1()), + 2 => tim.cr1().modify(|_, w| w.ckd().div2()), + 4 => tim.cr1().modify(|_, w| w.ckd().div4()), + _ => panic!("Should be unreachable, invalid deadtime prescaler"), + }; + + let bkp = match self.fault_polarity { + Polarity::ActiveLow => false, + Polarity::ActiveHigh => true, + }; + + if self.bkin_enabled { + // BDTR: + // BKF = 1 -> break pin filtering of 2 cycles of CK_INT (peripheral source clock) + // AOE = 0 -> after a fault, master output enable MOE can only be set by software, not automatically + // BKE = 1 -> break is enabled + // BKP = 0 for active low, 1 for active high + // Safety: bkf is set to a constant value (1) that is a valid value for the field per the reference manual + unsafe { tim.$bdtr().write(|w| w.dtg().bits(dtg).bkf().bits(1).aoe().clear_bit().bke().set_bit().bkp().bit(bkp).moe().$moe_set()); } + + // AF1: + // BKINE = 1 -> break input enabled + // BKINP should make input active high (BDTR BKP will set polarity), bit value varies timer to timer + tim.$af1().write(|w| w.bkine().set_bit().bkinp().$bkinp_setting()); + } + $( + // Not all timers that have break inputs have break2 inputs + else if self.bkin2_enabled { + // BDTR: + // BK2F = 1 -> break pin filtering of 2 cycles of CK_INT (peripheral source clock) + // AOE = 0 -> after a fault, master output enable MOE can only be set by software, not automatically + // BK2E = 1 -> break is enabled + // BK2P = 0 for active low, 1 for active high + // Safety: bkf is set to a constant value (1) that is a valid value for the field per the reference manual + unsafe { tim.$bdtr().write(|w| w.dtg().bits(dtg).bk2f().bits(1).aoe().clear_bit().bk2e().set_bit().bk2p().bit(bkp).moe().$moe_set()); } + + // AF1: + // BKINE = 1 -> break input enabled + // BKINP should make input active high (BDTR BKP will set polarity), bit value varies timer to timer + tim.af2().write(|w| w.bk2ine().set_bit().bk2inp().$bk2inp_setting()); + } + )? + else { + // Safety: the DTG field of BDTR allows any 8-bit deadtime value and the dtg variable is u8 + unsafe { + tim.$bdtr().write(|w| w.dtg().bits(dtg).aoe().clear_bit().moe().$moe_set()); + } + } + + // BDTR: Advanced-control timers + // Set CCxP = OCxREF / CCxNP = !OCxREF + // Refer to RM0433 Rev 6 - Table 324. + tim.$bdtr().modify(|_, w| w.moe().$moe_set()); + )? + + + $( + match self.alignment { + Alignment::Left => { }, + Alignment::Right => { tim.cr1().modify(|_, w| w.dir().down()); }, + Alignment::Center => { tim.cr1().modify(|_, w| w.$cms().center_aligned3()); } + } + )? + + tim.cr1().modify(|_, w| w.cen().enabled()); + + (PwmControl::new(), PINS::split()) + } + + /// Set the PWM frequency; will overwrite the previous prescaler + /// and period The requested frequency will be rounded to the + /// nearest achievable frequency; the actual frequency may be + /// higher or lower than requested. + #[must_use] + pub fn frequency(mut self, freq: Hertz) -> Self { + self.count = CountSettings::Frequency( freq ); + + self + } + + /// Set the prescaler; PWM count runs at base_frequency/(prescaler+1) + #[must_use] + pub fn prescaler(mut self, prescaler: u16) -> Self { + let period = match self.count { + CountSettings::Frequency(_) => 65535, + CountSettings::Explicit { period, prescaler: _ } => period, + }; + + self.count = CountSettings::Explicit { period, prescaler }; + + self + } + + /// Set the period; PWM count runs from 0 to period, repeating every (period+1) counts + #[must_use] + pub fn period(mut self, period: $typ) -> Self { + let prescaler = match self.count { + CountSettings::Frequency(_) => 0, + CountSettings::Explicit { period: _, prescaler } => prescaler, + }; + + self.count = CountSettings::Explicit { period, prescaler }; + + self + } + + + // Timers with complementary and deadtime and faults + $( + /// Set the deadtime for complementary PWM channels of this timer + #[must_use] + pub fn with_deadtime>(mut self, deadtime: T) -> Self { + // $bdtr is an Ident that only exists for timers with deadtime, so we can use it as a variable name to + // only implement this method for timers that support deadtime. + let $bdtr = deadtime.into(); + + self.deadtime = $bdtr; + + self + } + )? + + #[must_use] + pub fn left_aligned(mut self) -> Self { + self.alignment = Alignment::Left; + + self + } + + // Timers with advanced counting options, including center aligned and right aligned PWM + $( + #[must_use] + pub fn center_aligned(mut self) -> Self { + // $cms is an Ident that only exists for timers with center/right aligned PWM, so we can use it as a variable name to + // only implement this method for timers that support center/right aligned PWM. + let $cms = Alignment::Center; + + self.alignment = $cms; + + self + } + + #[must_use] + pub fn right_aligned(mut self) -> Self { + self.alignment = Alignment::Right; + + self + } + )? + } + + impl PwmControl<$TIMX, F> { + /// Start listening for `event` + pub fn listen(&mut self, event: Event) { + match event { + Event::TimeOut => { + let tim = unsafe { <$TIMX>::steal() }; + // Enable update event interrupt + tim.dier().write(|w| w.uie().set_bit()); + } + } + } + + /// Stop listening for `event` + pub fn unlisten(&mut self, event: Event) { + match event { + Event::TimeOut => { + let tim = unsafe { <$TIMX>::steal() }; + // Disable update event interrupt + tim.dier().write(|w| w.uie().clear_bit()); + let _ = tim.dier().read(); + let _ = tim.dier().read(); // Delay 2 peripheral clocks + } + } + } + + /// Check if Update Interrupt flag is cleared + pub fn is_irq_clear(&mut self) -> bool { + let tim = unsafe { <$TIMX>::steal() }; + tim.sr().read().uif().bit_is_clear() + } + + /// Clears interrupt flag + pub fn clear_irq(&mut self) { + let tim = unsafe { <$TIMX>::steal() }; + tim.sr().modify(|_, w| { + // Clears timeout event + w.uif().clear_bit() + }); + let _ = tim.sr().read(); + let _ = tim.sr().read(); // Delay 2 peripheral clocks + } + } + + // Timers with break/fault, dead time, and complimentary capabilities + $( + impl PwmBuilder<$TIMX, PINS, CHANNEL, FaultDisabled, COMP, $typ> { + /// Configure a break pin that will disable PWM when activated (active level based on polarity argument) + /// Note: not all timers have fault inputs; `FaultPins` is only implemented for valid pins/timers. + pub fn with_break_pin>(self, _pin: P, polarity: Polarity) -> PwmBuilder<$TIMX, PINS, CHANNEL, FaultEnabled, COMP, $typ> { + PwmBuilder { + _markers: PhantomData, + alignment: self.alignment, + base_freq: self.base_freq, + count: self.count, + bkin_enabled: self.bkin_enabled || P::INPUT == BreakInput::BreakIn, + bkin2_enabled: self.bkin2_enabled || P::INPUT == BreakInput::BreakIn2, + fault_polarity: polarity, + deadtime: self.deadtime, + } + } + } + + impl FaultMonitor for PwmControl<$TIMX, FaultEnabled> { + fn is_fault_active(&self) -> bool { + let tim = unsafe { &*<$TIMX>::ptr() }; + + !tim.$bdtr().read().moe().bit() + } + + fn clear_fault(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.$bdtr().modify(|_, w| w.moe().set_bit()); + } + + fn set_fault(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.$bdtr().modify(|_, w| w.moe().clear_bit()); + } + } + )? + )+ + } +} + +tim_hal! { + pac::TIM1: (tim1, Tim1, u16, 16, DIR: cms, BDTR: bdtr, enabled, af1, clear_bit, clear_bit), + pac::TIM2: (tim2, Tim2, u32, 32, DIR: cms), + pac::TIM3: (tim3, Tim3, u16, 16, DIR: cms), + pac::TIM6: (tim6, Tim6, u16, 16), + pac::TIM7: (tim7, Tim7, u16, 16), +} + +#[cfg(feature = "rm0481")] +tim_hal! { + pac::TIM4: (tim4, Tim4, u16, 16, DIR: cms), + pac::TIM5: (tim5, Tim5, u32, 32, DIR: cms), + pac::TIM8: (tim8, Tim8, u16, 16, DIR: cms, BDTR: bdtr, enabled, af1, clear_bit, clear_bit), + //pac::TIM12: (tim12, Tim12, u16, 16), + pac::TIM15: (tim15, Tim15, u16, 16, BDTR: bdtr, set_bit, af1, set_bit), +} // TODO: TIM12 seems to be missing for 523's pac, re add once fixed + +#[cfg(feature = "h56x_h573")] +tim_hal! { + pac::TIM13: (tim13, Tim13, u16, 16), + pac::TIM14: (tim14, Tim14, u16, 16), + pac::TIM16: (tim16, Tim16, u16, 16, BDTR: bdtr, set_bit, af1, set_bit), + pac::TIM17: (tim17, Tim17, u16, 16, BDTR: bdtr, set_bit, af1, set_bit), +} + +pub trait PwmPinEnable { + fn ccer_enable(&mut self); + fn ccer_disable(&mut self); +} + +// Implement PwmPin for timer channels +macro_rules! tim_pin_hal { + // Standard pins (no complementary functionality) + ($TIMX:ty, $typ:ty $(, $ccne:ident)*: $( + ($CH:ident, $ccmrx_output:ident, $ocxpe:ident, $ocxm:ident),)+ + ) => { + $( + impl embedded_hal_02::PwmPin for Pwm<$TIMX, $CH, COMP> + where Pwm<$TIMX, $CH, COMP>: PwmPinEnable { + type Duty = $typ; + + // You may not access self in the following methods! + // See unsafe above + + fn disable(&mut self) { + self.ccer_disable(); + } + + fn enable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.$ccmrx_output().modify(|_, w| + w.$ocxpe() + .enabled() // Enable preload + .$ocxm() + .pwm_mode1() // PWM Mode + ); + + self.ccer_enable(); + } + + fn get_duty(&self) -> Self::Duty { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccr($CH as usize).read().ccr().bits() as $typ + } + + fn get_max_duty(&self) -> Self::Duty { + let tim = unsafe { &*<$TIMX>::ptr() }; + + let arr = tim.arr().read().arr().bits() as $typ; + + // One PWM cycle is ARR+1 counts long + // Valid PWM duty cycles are 0 to ARR+1 + // However, if ARR is 65535 on a 16-bit timer, we can't add 1 + // In that case, 100% duty cycle is not possible, only 65535/65536 + if arr == Self::Duty::MAX { + arr + } + else { + arr + 1 + } + } + + fn set_duty(&mut self, duty: Self::Duty) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccr($CH as usize).write(|w| w.ccr().set(duty as u32)); + } + } + + )+ + + // Enable implementation for ComplementaryImpossible + impl PwmPinEnable for Pwm<$TIMX, C, ComplementaryImpossible> { + fn ccer_enable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| w.cce(C).enabled()); + } + fn ccer_disable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| w.cce(C).disabled()); + } + } + + + impl Pwm<$TIMX, C, COMP> { + pub fn set_polarity(&mut self, pol: Polarity) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| match pol { + Polarity::ActiveLow => w.ccp(C).set_bit(), + Polarity::ActiveHigh => w.ccp(C).clear_bit(), + }); + } + } + + impl Pwm<$TIMX, C, COMP> { + pub fn into_active_low(mut self) -> Pwm<$TIMX, C, COMP> { + self.set_polarity(Polarity::ActiveLow); + self + } + } + + impl Pwm<$TIMX, C, COMP> { + pub fn into_active_high(mut self) -> Pwm<$TIMX, C, COMP> { + self.set_polarity(Polarity::ActiveHigh); + self + } + } + + // Complementary channels + + // Enable implementation for ComplementaryDisabled + impl PwmPinEnable for Pwm<$TIMX, C, ComplementaryDisabled> { + fn ccer_enable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| w.cce(C).enabled()); + } + fn ccer_disable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| w.cce(C).disabled()); + } + } + + $( + // Enable implementation for ComplementaryEnabled + impl PwmPinEnable for Pwm<$TIMX, C, ComplementaryEnabled> { + fn ccer_enable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| w.cce(C).enabled().$ccne(C).enabled()); + } + fn ccer_disable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| w.cce(C).disabled().$ccne(C).disabled()); + } + } + )* + + impl Pwm<$TIMX, C, ComplementaryDisabled> { + pub fn into_complementary(self, _npin: NPIN) -> Pwm<$TIMX, C, ComplementaryEnabled> + where NPIN: NPins<$TIMX, Ch> { + // Make sure we aren't switching to complementary after we enable the channel + let tim = unsafe { &*<$TIMX>::ptr() }; + + let enabled = tim.ccer().read().cce(C).is_enabled(); + + assert!(!enabled); + + Pwm::new() + } + } + + impl Pwm<$TIMX, C, ComplementaryEnabled> { + pub fn set_comp_polarity(&mut self, pol: Polarity) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.ccer().modify(|_, w| match pol { + Polarity::ActiveLow => w.ccnp(C).set_bit(), + Polarity::ActiveHigh => w.ccnp(C).clear_bit(), + }); + } + } + + impl Pwm<$TIMX, C, ComplementaryEnabled> { + pub fn into_comp_active_low(mut self) -> Pwm<$TIMX, C, ComplementaryEnabled> { + self.set_comp_polarity(Polarity::ActiveLow); + self + } + } + + impl Pwm<$TIMX, C, ComplementaryEnabled> { + pub fn into_comp_active_high(mut self) -> Pwm<$TIMX, C, ComplementaryEnabled> { + self.set_comp_polarity(Polarity::ActiveHigh); + self + } + } + }; +} + +fn foo(tim: pac::TIM1) { + const C: u8 = 0; + let value = 3; + tim.psc().modify(|_, w| w.psc().set(value)); + + tim.ccer().modify(|_, w| w.ccp(C).falling_edge()); + tim.cr1().modify(|_, w| w.dir().down()); + tim.cr1().modify(|_, w| w.cms().center_aligned3()); +} + +// Dual channel timers +/*#[cfg(feature="rm0481")] +tim_pin_hal! { + pac::TIM12, u16: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), +}*/ // TODO: TIM12 seems to be missing for 523's pac, re add once fixed +#[cfg(feature = "rm0481")] +tim_pin_hal! { + pac::TIM15, u16, ccne: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), +} + +// Single channel timers +#[cfg(feature = "h56x_h573")] +tim_pin_hal! { + pac::TIM13, u16: (C1, ccmr1_output, oc1pe, oc1m), +} +#[cfg(feature = "h56x_h573")] +tim_pin_hal! { + pac::TIM14, u16: (C1, ccmr1_output, oc1pe, oc1m), +} +#[cfg(feature = "h56x_h573")] +tim_pin_hal! { + pac::TIM16, u16: (C1, ccmr1_output, oc1pe, oc1m), +} +#[cfg(feature = "h56x_h573")] +tim_pin_hal! { + pac::TIM17, u16: (C1, ccmr1_output, oc1pe, oc1m), +} + +// Quad channel timers +tim_pin_hal! { + pac::TIM1, u16, ccne: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), + (C3, ccmr2_output, oc3pe, oc3m), + (C4, ccmr2_output, oc4pe, oc4m), +} +tim_pin_hal! { + pac::TIM2, u32: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), + (C3, ccmr2_output, oc3pe, oc3m), + (C4, ccmr2_output, oc4pe, oc4m), +} +tim_pin_hal! { + pac::TIM3, u16: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), + (C3, ccmr2_output, oc3pe, oc3m), + (C4, ccmr2_output, oc4pe, oc4m), +} +#[cfg(feature = "rm0481")] +tim_pin_hal! { + pac::TIM4, u16: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), + (C3, ccmr2_output, oc3pe, oc3m), + (C4, ccmr2_output, oc4pe, oc4m), +} +#[cfg(feature = "rm0481")] +tim_pin_hal! { + pac::TIM5, u32: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), + (C3, ccmr2_output, oc3pe, oc3m), + (C4, ccmr2_output, oc4pe, oc4m), +} +#[cfg(feature = "rm0481")] +tim_pin_hal! { + pac::TIM8, u16, ccne: + (C1, ccmr1_output, oc1pe, oc1m), + (C2, ccmr1_output, oc2pe, oc2m), + (C3, ccmr2_output, oc3pe, oc3m), + (C4, ccmr2_output, oc4pe, oc4m), +} +/* +// Low-power timers +macro_rules! lptim_hal { + ($($TIMX:ty: ($timX:ident, $Rec:ident),)+) => { + $( + pwm_ext_hal!($TIMX: $timX, $Rec); + + /// Configures PWM signal on the LPTIM OUT pin. + fn $timX( + tim: $TIMX, + _pins: PINS, + freq: Hertz, + prec: rec::$Rec, + clocks: &CoreClocks, + ) -> PINS::Channel + where + PINS: Pins<$TIMX, T, U>, + { + let _ = prec.enable().reset(); // drop + + let clk = <$TIMX>::get_clk(clocks) + .expect(concat!(stringify!($TIMX), ": Input clock not running!")); + let reload = clk / freq; + assert!(reload < 128 * (1 << 16)); + + // Calculate prescaler + use pac::$timX::cfgr::PRESC_A; + let (prescale, prescale_div) = match reload / (1 << 16) { + 0 => (PRESC_A::Div1, 1), + 1 => (PRESC_A::Div2, 2), + 2..=3 => (PRESC_A::Div4, 4), + 4..=7 => (PRESC_A::Div8, 8), + 8..=15 => (PRESC_A::Div16, 16), + 16..=31 => (PRESC_A::Div32, 32), + 32..=63 => (PRESC_A::Div64, 64), + _ => (PRESC_A::Div128, 128), + }; + + // Calcuate reload + let arr = reload / prescale_div; + assert!(arr <= 0xFFFF); + assert!(arr > 0); + + // CFGR + tim.cfgr().modify(|_, w| w.presc().variant(prescale)); + + // Enable + tim.cr().modify(|_, w| w.enable().enabled()); + + // Write ARR: LPTIM must be enabled + tim.arr().write(|w| w.arr().bits(arr as u16)); + while !tim.isr().read().arrok().is_set() {} + tim.icr().write(|w| w.arrokcf().clear()); + + // PWM output is disabled by default, disable the + // entire timer + tim.cr().modify(|_, w| w.enable().disabled()); + + PINS::split() + } + + impl embedded_hal_02::PwmPin for Pwm<$TIMX, C1, ComplementaryImpossible> { + type Duty = u16; + + // You may not access self in the following methods! + // See unsafe above + + fn disable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + // LPTIM only has one output, so we disable the + // entire timer + tim.cr().modify(|_, w| w.enable().disabled()); + } + + fn enable(&mut self) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.cr().modify(|_, w| w.enable().enabled()); + tim.cr().modify(|_, w| w.cntstrt().start()); + } + + fn get_duty(&self) -> u16 { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.cmp().read().cmp().bits() + } + + fn get_max_duty(&self) -> u16 { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.arr().read().arr().bits() + } + + fn set_duty(&mut self, duty: u16) { + let tim = unsafe { &*<$TIMX>::ptr() }; + + tim.cmp().write(|w| w.cmp().bits(duty)); + while !tim.isr().read().cmpok().is_set() {} + tim.icr().write(|w| w.cmpokcf().clear()); + } + } + )+ + } +} + +lptim_hal! { + pac::LPTIM1: (lptim1, Lptim1), + pac::LPTIM2: (lptim2, Lptim2), + pac::LPTIM3: (lptim3, Lptim3), +} +#[cfg(not(feature = "rm0455"))] +lptim_hal! { + pac::LPTIM4: (lptim4, Lptim4), + pac::LPTIM5: (lptim5, Lptim5), +}*/ diff --git a/src/timer.rs b/src/timer.rs new file mode 100644 index 0000000..058dd21 --- /dev/null +++ b/src/timer.rs @@ -0,0 +1,448 @@ +//! Timers +//! +//! # Examples +//! +//! - [Blinky using a Timer](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/blinky_timer.rs) +//! - [64 bit microsecond timer](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/tick_timer.rs) + +// TODO: on the h7x3 at least, only TIM2, TIM3, TIM4, TIM5 can support 32 bits. +// TIM1 is 16 bit. + +use core::marker::PhantomData; + +use crate::stm32::{TIM1, TIM2, TIM3, TIM6, TIM7}; +#[cfg(feature = "h56x_h573")] +use crate::stm32::{TIM13, TIM14, TIM16, TIM17}; +#[cfg(feature = "rm0481")] +use crate::stm32::{/*TIM12,*/ TIM15, TIM4, TIM5, TIM8}; // TODO: TIM12 seems to be missing for 523's pac, re add once fixed +use cast::{u16, u32}; +use void::Void; + +use crate::rcc::{rec, CoreClocks, ResetEnable}; +use crate::time::Hertz; + +/// Associate clocks with timers +pub trait GetClk { + fn get_clk(clocks: &CoreClocks) -> Option; +} + +/// Timers with CK_INT derived from rcc_tim[xy]_ker_ck +macro_rules! impl_tim_ker_ck { + ($($ckX:ident: $($TIMX:ident),+)+) => { + $( + $( + impl GetClk for $TIMX { + fn get_clk(clocks: &CoreClocks) -> Option { + Some(clocks.$ckX()) + } + } + )+ + )+ + } +} +impl_tim_ker_ck! { + timx_ker_ck: TIM2, TIM3, TIM6, TIM7 + timy_ker_ck: TIM1 +} + +#[cfg(feature = "rm0481")] +impl_tim_ker_ck! { + timx_ker_ck: TIM4, TIM5 /*TIM12,*/ // TODO: TIM12 seems to be missing for 523's pac, re add once fixed + timy_ker_ck: TIM8, TIM15 +} + +#[cfg(feature = "h56x_h573")] +impl_tim_ker_ck! { + timx_ker_ck: TIM13, TIM14 + timy_ker_ck: TIM16, TIM17 +} + +/// External trait for hardware timers +pub trait TimerExt { + type Rec: ResetEnable; + + /// Configures a periodic timer + /// + /// Generates an overflow event at the `timeout` frequency. + fn timer(self, timeout: Hertz, prec: Self::Rec, clocks: &CoreClocks) + -> TIM; + + /// Configures the timer to count up at the given frequency + /// + /// Counts from 0 to the counter's maximum value, then repeats. + /// Because this only uses the timer prescaler, the frequency + /// is rounded to a multiple of the timer's kernel clock. + /// + /// For example, calling `.tick_timer(1.MHz(), ..)` for a 16-bit timer will + /// result in a timers that increments every microsecond and overflows every + /// ~65 milliseconds + fn tick_timer( + self, + frequency: Hertz, + prec: Self::Rec, + clocks: &CoreClocks, + ) -> TIM; +} + +/// Hardware timers +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Timer { + clk: u32, + tim: TIM, +} + +/// Timer Events +/// +/// Each event is a possible interrupt source, if enabled +pub enum Event { + /// Timer timed out / count down ended + TimeOut, +} + +macro_rules! hal { + ($($TIMX:ident: ($timX:ident, $Rec:ident, $cntType:ty),)+) => { + $( + impl embedded_hal_02::timer::Periodic for Timer<$TIMX> {} + + impl embedded_hal_02::timer::CountDown for Timer<$TIMX> { + type Time = Hertz; + + fn start(&mut self, timeout: T) + where + T: Into, + { + // Pause + self.pause(); + + // Reset counter + self.reset_counter(); + + // UEV event occours on next overflow + self.urs_counter_only(); + self.clear_irq(); + + // Set PSC and ARR + self.set_freq(timeout.into()); + + // Generate an update event to force an update of the ARR register. This ensures + // the first timer cycle is of the specified duration. + self.apply_freq(); + + // Start counter + self.resume() + } + + fn wait(&mut self) -> nb::Result<(), Void> { + if self.is_irq_clear() { + Err(nb::Error::WouldBlock) + } else { + self.clear_irq(); + Ok(()) + } + } + } + + impl TimerExt> for $TIMX { + type Rec = rec::$Rec; + + fn timer(self, timeout: Hertz, + prec: Self::Rec, clocks: &CoreClocks + ) -> Timer<$TIMX> { + use embedded_hal_02::timer::CountDown; + + let mut timer = Timer::$timX(self, prec, clocks); + timer.start(timeout); + timer + } + + fn tick_timer(self, frequency: Hertz, + prec: Self::Rec, clocks: &CoreClocks + ) -> Timer<$TIMX> { + let mut timer = Timer::$timX(self, prec, clocks); + + timer.pause(); + + // UEV event occours on next overflow + timer.urs_counter_only(); + timer.clear_irq(); + + // Set PSC and ARR + timer.set_tick_freq(frequency); + + // Generate an update event to force an update of the ARR + // register. This ensures the first timer cycle is of the + // specified duration. + timer.apply_freq(); + + // Start counter + timer.resume(); + + timer + } + } + + impl Timer<$TIMX> { + /// Configures a TIM peripheral as a periodic count down timer, + /// without starting it + pub fn $timX(tim: $TIMX, prec: rec::$Rec, clocks: &CoreClocks) -> Self + { + // enable and reset peripheral to a clean state + let _ = prec.enable().reset(); // drop, can be recreated by free method + + let clk = $TIMX::get_clk(clocks) + .expect(concat!(stringify!($TIMX), ": Input clock not running!")).raw(); + + Timer { + clk, + tim, + } + } + + /// Configures the timer's frequency and counter reload value + /// so that it underflows at the timeout's frequency + pub fn set_freq(&mut self, timeout: Hertz) { + let ticks = self.clk / timeout.raw(); + + self.set_timeout_ticks(ticks); + } + + /// Sets the timer period from a time duration + /// + /// ``` + /// use stm32h7xx_hal::time::MilliSeconds; + /// + /// // Set timeout to 100ms + /// let timeout = MilliSeconds::from_ticks(100).into_rate(); + /// timer.set_timeout(timeout); + /// ``` + /// + /// Alternatively, the duration can be set using the + /// core::time::Duration type + /// + /// ``` + /// let duration = core::time::Duration::from_nanos(2_500); + /// + /// // Set timeout to 2.5µs + /// timer.set_timeout(duration); + /// ``` + pub fn set_timeout(&mut self, timeout: T) + where + T: Into + { + const NANOS_PER_SECOND: u64 = 1_000_000_000; + let timeout = timeout.into(); + + let clk = self.clk as u64; + let ticks = u32::try_from( + clk * timeout.as_secs() + + clk * u64::from(timeout.subsec_nanos()) / NANOS_PER_SECOND, + ) + .unwrap_or(u32::MAX); + + self.set_timeout_ticks(ticks.max(1)); + } + + /// Sets the timer's prescaler and auto reload register so that the timer will reach + /// the ARR after `ticks - 1` amount of timer clock ticks. + /// + /// ``` + /// // Set auto reload register to 50000 and prescaler to divide by 2. + /// timer.set_timeout_ticks(100000); + /// ``` + /// + /// This function will round down if the prescaler is used to extend the range: + /// ``` + /// // Set auto reload register to 50000 and prescaler to divide by 2. + /// timer.set_timeout_ticks(100001); + /// ``` + fn set_timeout_ticks(&mut self, ticks: u32) { + let (psc, arr) = calculate_timeout_ticks_register_values(ticks); + unsafe { + self.tim.psc().write(|w| w.psc().bits(psc)); + } + #[allow(unused_unsafe)] // method is safe for some timers + self.tim.arr().write(|w| unsafe { w.bits(u32(arr)) }); + } + + /// Configures the timer to count up at the given frequency + /// + /// Counts from 0 to the counter's maximum value, then repeats. + /// Because this only uses the timer prescaler, the frequency + /// is rounded to a multiple of the timer's kernel clock. + pub fn set_tick_freq(&mut self, frequency: Hertz) { + let div = self.clk / frequency.raw(); + + let psc = u16(div - 1).unwrap(); + unsafe { + self.tim.psc().write(|w| w.psc().bits(psc)); + } + + let counter_max = u32(<$cntType>::MAX); + #[allow(unused_unsafe)] // method is safe for some timers + self.tim.arr().write(|w| unsafe { w.bits(counter_max) }); + } + + /// Applies frequency/timeout changes immediately + /// + /// The timer will normally update its prescaler and auto-reload + /// value when its counter overflows. This function causes + /// those changes to happen immediately. Also clears the counter. + pub fn apply_freq(&mut self) { + self.tim.egr().write(|w| w.ug().set_bit()); + } + + /// Pauses the TIM peripheral + pub fn pause(&mut self) { + self.tim.cr1().modify(|_, w| w.cen().clear_bit()); + } + + /// Resume (unpause) the TIM peripheral + pub fn resume(&mut self) { + self.tim.cr1().modify(|_, w| w.cen().set_bit()); + } + + /// Set Update Request Source to counter overflow/underflow only + pub fn urs_counter_only(&mut self) { + self.tim.cr1().modify(|_, w| w.urs().counter_only()); + } + + /// Reset the counter of the TIM peripheral + pub fn reset_counter(&mut self) { + self.tim.cnt().reset(); + } + + /// Read the counter of the TIM peripheral + pub fn counter(&self) -> u32 { + self.tim.cnt().read().cnt().bits().into() + } + + /// Start listening for `event` + pub fn listen(&mut self, event: Event) { + match event { + Event::TimeOut => { + // Enable update event interrupt + self.tim.dier().write(|w| w.uie().set_bit()); + } + } + } + + /// Stop listening for `event` + pub fn unlisten(&mut self, event: Event) { + match event { + Event::TimeOut => { + // Disable update event interrupt + self.tim.dier().write(|w| w.uie().clear_bit()); + let _ = self.tim.dier().read(); + let _ = self.tim.dier().read(); // Delay 2 peripheral clocks + } + } + } + + /// Check if Update Interrupt flag is cleared + pub fn is_irq_clear(&mut self) -> bool { + self.tim.sr().read().uif().bit_is_clear() + } + + /// Clears interrupt flag + pub fn clear_irq(&mut self) { + self.tim.sr().modify(|_, w| { + // Clears timeout event + w.uif().clear_bit() + }); + let _ = self.tim.sr().read(); + let _ = self.tim.sr().read(); // Delay 2 peripheral clocks + } + + /// Releases the TIM peripheral + pub fn free(mut self) -> ($TIMX, rec::$Rec) { + // pause counter + self.pause(); + + (self.tim, rec::$Rec { _marker: PhantomData }) + } + + /// Returns a reference to the inner peripheral + pub fn inner(&self) -> &$TIMX { + &self.tim + } + + /// Returns a mutable reference to the inner peripheral + pub fn inner_mut(&mut self) -> &mut $TIMX { + &mut self.tim + } + } + )+ + } +} + +/// We want to have `ticks` amount of timer ticks before it reloads. +/// But `ticks` may have a higher value than what the timer can hold directly. +/// So we'll use the prescaler to extend the range. +/// +/// To know how many times we would overflow with a prescaler of 1, we divide `ticks` by 2^16 (the max amount of ticks per overflow). +/// If the result is e.g. 3, then we need to increase our range by 4 times to fit all the ticks. +/// We can increase the range enough by setting the prescaler to 3 (which will divide the clock freq by 4). +/// Because every tick is now 4x as long, we need to divide `ticks` by 4 to keep the same timeout. +/// +/// This function returns the prescaler register value and auto reload register value. +fn calculate_timeout_ticks_register_values(ticks: u32) -> (u16, u16) { + // Note (unwrap): Never panics because 32-bit value is shifted right by 16 bits, + // resulting in a value that always fits in 16 bits. + let psc = u16(ticks / (1 << 16)).unwrap(); + // Note (unwrap): Never panics because the divisor is always such that the result fits in 16 bits. + // Also note that the timer counts `0..=arr`, so subtract 1 to get the correct period. + let arr = u16(ticks / (u32(psc) + 1)).unwrap().saturating_sub(1); + (psc, arr) +} + +hal! { + // Advanced-control + TIM1: (tim1, Tim1, u16), + + // General-purpose + TIM2: (tim2, Tim2, u32), + TIM3: (tim3, Tim3, u16), + + // Basic + TIM6: (tim6, Tim6, u16), + TIM7: (tim7, Tim7, u16), +} + +#[cfg(feature = "rm0481")] +hal! { + // Advanced-control + TIM8: (tim8, Tim8, u16), + + // General-purpose + TIM4: (tim4, Tim4, u16), + TIM5: (tim5, Tim5, u32), + + // General-purpose + //TIM12: (tim12, Tim12, u16), // TODO: TIM12 seems to be missing for 523's pac, re add once fixed + + // General-purpose + TIM15: (tim15, Tim15, u16), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn timeout_ticks_register_values() { + assert_eq!(calculate_timeout_ticks_register_values(0), (0, 0)); + assert_eq!(calculate_timeout_ticks_register_values(50000), (0, 49999)); + assert_eq!(calculate_timeout_ticks_register_values(100000), (1, 49999)); + assert_eq!(calculate_timeout_ticks_register_values(65535), (0, 65534)); + assert_eq!(calculate_timeout_ticks_register_values(65536), (1, 32767)); + assert_eq!( + calculate_timeout_ticks_register_values(1000000), + (15, 62499) + ); + assert_eq!( + calculate_timeout_ticks_register_values(u32::MAX), + (u16::MAX, u16::MAX - 1) + ); + } +}