//! API for Pulse-Width Modulation (PWM) //! //! The Vorago VA108xx devices use the TIM peripherals to perform PWM related tasks //! //! ## Examples //! //! - [PWM example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/pwm.rs) use core::convert::Infallible; use core::marker::PhantomData; use crate::pac; use crate::time::Hertz; use crate::timer::{TimDynRegister, TimPin, TimRegInterface, ValidTim, ValidTimAndPin}; use crate::{clock::enable_peripheral_clock, gpio::DynPinId}; const DUTY_MAX: u16 = u16::MAX; pub struct PwmCommon { sys_clk: Hertz, /// For PWMB, this is the upper limit current_duty: u16, /// For PWMA, this value will not be used current_lower_limit: u16, current_period: Hertz, current_rst_val: u32, } enum StatusSelPwm { PwmA = 3, PwmB = 4, } pub struct PwmA {} pub struct PwmB {} //================================================================================================== // Strongly typed PWM pin //================================================================================================== pub struct PwmPin { pin_and_tim: (Pin, Tim), inner: ReducedPwmPin, mode: PhantomData, } impl PwmPin where (Pin, Tim): ValidTimAndPin, { /// Create a new stronlgy typed PWM pin pub fn new( sys_cfg: &mut pac::Sysconfig, sys_clk: impl Into + Copy, pin_and_tim: (Pin, Tim), initial_period: impl Into + Copy, ) -> Self { let mut pin = PwmPin { pin_and_tim, inner: ReducedPwmPin::::new( Tim::TIM_ID, Pin::DYN, PwmCommon { current_duty: 0, current_lower_limit: 0, current_period: initial_period.into(), current_rst_val: 0, sys_clk: sys_clk.into(), }, ), //unsafe { TimAndPin::new(tim_and_pin.0, tim_and_pin.1) }, mode: PhantomData, }; enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Gpio); enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Ioconfig); sys_cfg .tim_clk_enable() .modify(|r, w| unsafe { w.bits(r.bits() | pin.pin_and_tim.1.mask_32()) }); pin.enable_pwm_a(); pin.set_period(initial_period); pin } pub fn downgrade(self) -> ReducedPwmPin { self.inner } pub fn release(self) -> (Pin, Tim) { self.pin_and_tim } #[inline] fn enable_pwm_a(&mut self) { self.inner.enable_pwm_a(); } #[inline] fn enable_pwm_b(&mut self) { self.inner.enable_pwm_b(); } #[inline] pub fn get_period(&self) -> Hertz { self.inner.get_period() } #[inline] pub fn set_period(&mut self, period: impl Into) { self.inner.set_period(period); } #[inline] pub fn disable(&mut self) { self.inner.disable(); } #[inline] pub fn enable(&mut self) { self.inner.enable(); } #[inline] pub fn period(&self) -> Hertz { self.inner.period() } #[inline(always)] pub fn duty(&self) -> u16 { self.inner.duty() } } impl From> for PwmPin where (Pin, Tim): ValidTimAndPin, { fn from(other: PwmPin) -> Self { let mut pwmb = Self { mode: PhantomData, pin_and_tim: other.pin_and_tim, inner: other.inner.into(), }; pwmb.enable_pwm_b(); pwmb } } impl From> for PwmPin where (PIN, TIM): ValidTimAndPin, { fn from(other: PwmPin) -> Self { let mut pwma = Self { mode: PhantomData, pin_and_tim: other.pin_and_tim, inner: other.inner.into(), }; pwma.enable_pwm_a(); pwma } } impl PwmPin where (Pin, Tim): ValidTimAndPin, { pub fn pwma( sys_cfg: &mut pac::Sysconfig, sys_clk: impl Into + Copy, pin_and_tim: (Pin, Tim), initial_period: impl Into + Copy, ) -> Self { let mut pin: PwmPin = Self::new(sys_cfg, sys_clk, pin_and_tim, initial_period); pin.enable_pwm_a(); pin } } impl PwmPin where (Pin, Tim): ValidTimAndPin, { pub fn pwmb( sys_cfg: &mut pac::Sysconfig, sys_clk: impl Into + Copy, pin_and_tim: (Pin, Tim), initial_period: impl Into + Copy, ) -> Self { let mut pin: PwmPin = Self::new(sys_cfg, sys_clk, pin_and_tim, initial_period); pin.enable_pwm_b(); pin } } //================================================================================================== // Reduced PWM pin //================================================================================================== /// Reduced version where type information is deleted pub struct ReducedPwmPin { dyn_reg: TimDynRegister, common: PwmCommon, mode: PhantomData, } impl ReducedPwmPin { pub(crate) fn new(tim_id: u8, pin_id: DynPinId, common: PwmCommon) -> Self { Self { dyn_reg: TimDynRegister { tim_id, pin_id }, common, mode: PhantomData, } } } impl ReducedPwmPin { #[inline] fn enable_pwm_a(&mut self) { self.dyn_reg .reg_block() .ctrl() .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) }); } #[inline] fn enable_pwm_b(&mut self) { self.dyn_reg .reg_block() .ctrl() .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) }); } #[inline] pub fn get_period(&self) -> Hertz { self.common.current_period } #[inline] pub fn set_period(&mut self, period: impl Into) { self.common.current_period = period.into(); // Avoid division by 0 if self.common.current_period.raw() == 0 { return; } self.common.current_rst_val = self.common.sys_clk.raw() / self.common.current_period.raw(); self.dyn_reg .reg_block() .rst_value() .write(|w| unsafe { w.bits(self.common.current_rst_val) }); } #[inline] pub fn disable(&mut self) { self.dyn_reg .reg_block() .ctrl() .modify(|_, w| w.enable().clear_bit()); } #[inline] pub fn enable(&mut self) { self.dyn_reg .reg_block() .ctrl() .modify(|_, w| w.enable().set_bit()); } #[inline] pub fn period(&self) -> Hertz { self.common.current_period } #[inline(always)] pub fn duty(&self) -> u16 { self.common.current_duty } } impl From> for ReducedPwmPin where (Pin, Tim): ValidTimAndPin, { fn from(value: PwmPin) -> Self { value.downgrade() } } impl From> for ReducedPwmPin where (Pin, Tim): ValidTimAndPin, { fn from(value: PwmPin) -> Self { value.downgrade() } } impl From> for ReducedPwmPin { fn from(other: ReducedPwmPin) -> Self { let mut pwmb = Self { dyn_reg: other.dyn_reg, common: other.common, mode: PhantomData, }; pwmb.enable_pwm_b(); pwmb } } impl From> for ReducedPwmPin { fn from(other: ReducedPwmPin) -> Self { let mut pwmb = Self { dyn_reg: other.dyn_reg, common: other.common, mode: PhantomData, }; pwmb.enable_pwm_a(); pwmb } } //================================================================================================== // PWMB implementations //================================================================================================== impl PwmPin where (Pin, Tim): ValidTimAndPin, { pub fn pwmb_lower_limit(&self) -> u16 { self.inner.pwmb_lower_limit() } pub fn pwmb_upper_limit(&self) -> u16 { self.inner.pwmb_upper_limit() } /// Set the lower limit for PWMB /// /// The PWM signal will be 1 as long as the current RST counter is larger than /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high /// state pub fn set_pwmb_lower_limit(&mut self, duty: u16) { self.inner.set_pwmb_lower_limit(duty); } /// Set the higher limit for PWMB /// /// The PWM signal will be 1 as long as the current RST counter is smaller than /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high /// state pub fn set_pwmb_upper_limit(&mut self, duty: u16) { self.inner.set_pwmb_upper_limit(duty); } } impl ReducedPwmPin { #[inline(always)] pub fn pwmb_lower_limit(&self) -> u16 { self.common.current_lower_limit } #[inline(always)] pub fn pwmb_upper_limit(&self) -> u16 { self.common.current_duty } /// Set the lower limit for PWMB /// /// The PWM signal will be 1 as long as the current RST counter is larger than /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high /// state #[inline(always)] pub fn set_pwmb_lower_limit(&mut self, duty: u16) { self.common.current_lower_limit = duty; let pwmb_val: u64 = (self.common.current_rst_val as u64 * self.common.current_lower_limit as u64) / DUTY_MAX as u64; self.dyn_reg .reg_block() .pwmb_value() .write(|w| unsafe { w.bits(pwmb_val as u32) }); } /// Set the higher limit for PWMB /// /// The PWM signal will be 1 as long as the current RST counter is smaller than /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high /// state pub fn set_pwmb_upper_limit(&mut self, duty: u16) { self.common.current_duty = duty; let pwma_val: u64 = (self.common.current_rst_val as u64 * self.common.current_duty as u64) / DUTY_MAX as u64; self.dyn_reg .reg_block() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); } } //================================================================================================== // Embedded HAL implementation: PWMA only //================================================================================================== impl embedded_hal::pwm::ErrorType for PwmPin { type Error = Infallible; } impl embedded_hal::pwm::ErrorType for ReducedPwmPin { type Error = Infallible; } impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin { #[inline] fn max_duty_cycle(&self) -> u16 { DUTY_MAX } #[inline] fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { self.common.current_duty = duty; let pwma_val: u64 = (self.common.current_rst_val as u64 * (DUTY_MAX as u64 - self.common.current_duty as u64)) / DUTY_MAX as u64; self.dyn_reg .reg_block() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); Ok(()) } } impl embedded_hal::pwm::SetDutyCycle for PwmPin { #[inline] fn max_duty_cycle(&self) -> u16 { DUTY_MAX } #[inline] fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { self.inner.set_duty_cycle(duty) } } /// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0. /// /// Please note that this might load a lot of floating point code because this processor does not /// have a FPU pub fn get_duty_from_percent(percent: f32) -> u16 { if percent > 1.0 { DUTY_MAX } else if percent <= 0.0 { 0 } else { (percent * DUTY_MAX as f32) as u16 } }