//! 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-hal/src/branch/main/examples/pwm.rs) use core::marker::PhantomData; use crate::{clock::enable_peripheral_clock, gpio::DynPinId}; pub use crate::{gpio::PinId, prelude::*, time::Hertz, timer::*}; use va108xx::SYSCONFIG; const DUTY_MAX: u16 = u16::MAX; pub struct PwmBase { 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 {} //================================================================================================== // Common //================================================================================================== macro_rules! pwm_common_func { () => { #[inline] fn enable_pwm_a(&mut self) { self.reg .reg() .ctrl .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) }); } #[inline] fn enable_pwm_b(&mut self) { self.reg .reg() .ctrl .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) }); } #[inline] pub fn get_period(&self) -> Hertz { self.pwm_base.current_period } #[inline] pub fn set_period(&mut self, period: impl Into) { self.pwm_base.current_period = period.into(); // Avoid division by 0 if self.pwm_base.current_period.0 == 0 { return; } self.pwm_base.current_rst_val = self.pwm_base.sys_clk.0 / self.pwm_base.current_period.0; self.reg .reg() .rst_value .write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) }); } }; } macro_rules! pwmb_func { () => { pub fn pwmb_lower_limit(&self) -> u16 { self.pwm_base.current_lower_limit } pub fn pwmb_upper_limit(&self) -> u16 { self.pwm_base.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 pub fn set_pwmb_lower_limit(&mut self, duty: u16) { self.pwm_base.current_lower_limit = duty; let pwmb_val: u64 = (self.pwm_base.current_rst_val as u64 * self.pwm_base.current_lower_limit as u64) / DUTY_MAX as u64; self.reg .reg() .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.pwm_base.current_duty = duty; let pwma_val: u64 = (self.pwm_base.current_rst_val as u64 * self.pwm_base.current_duty as u64) / DUTY_MAX as u64; self.reg .reg() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); } }; } //================================================================================================== // Strongly typed PWM pin //================================================================================================== pub struct PwmPin { reg: TimAndPinRegister, pwm_base: PwmBase, _mode: PhantomData, } impl PwmPin where (PIN, TIM): ValidTimAndPin, { /// Create a new stronlgy typed PWM pin pub fn new( vtp: (PIN, TIM), sys_clk: impl Into + Copy, sys_cfg: &mut SYSCONFIG, initial_period: impl Into + Copy, ) -> Self { let mut pin = PwmPin { pwm_base: PwmBase { current_duty: 0, current_lower_limit: 0, current_period: initial_period.into(), current_rst_val: 0, sys_clk: sys_clk.into(), }, reg: unsafe { TimAndPinRegister::new(vtp.0, vtp.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.reg.mask_32()) }); pin.enable_pwm_a(); pin.set_period(initial_period); pin } pub fn release(self) -> (PIN, TIM) { self.reg.release() } pwm_common_func!(); } impl From> for PwmPin where (PIN, TIM): ValidTimAndPin, { fn from(other: PwmPin) -> Self { let mut pwmb = Self { reg: other.reg, pwm_base: other.pwm_base, _mode: PhantomData, }; pwmb.enable_pwm_b(); pwmb } } impl From> for PwmPin where (PIN, TIM): ValidTimAndPin, { fn from(other: PwmPin) -> Self { let mut pwmb = Self { reg: other.reg, pwm_base: other.pwm_base, _mode: PhantomData, }; pwmb.enable_pwm_a(); pwmb } } impl PwmPin where (PIN, TIM): ValidTimAndPin, { pub fn pwma( vtp: (PIN, TIM), sys_clk: impl Into + Copy, sys_cfg: &mut SYSCONFIG, initial_period: impl Into + Copy, ) -> Self { let mut pin: PwmPin = Self::new(vtp, sys_clk, sys_cfg, initial_period); pin.enable_pwm_a(); pin } } impl PwmPin where (PIN, TIM): ValidTimAndPin, { pub fn pwmb( vtp: (PIN, TIM), sys_clk: impl Into + Copy, sys_cfg: &mut SYSCONFIG, initial_period: impl Into + Copy, ) -> Self { let mut pin: PwmPin = Self::new(vtp, sys_clk, sys_cfg, initial_period); pin.enable_pwm_b(); pin } } //================================================================================================== // Reduced PWM pin //================================================================================================== /// Reduced version where type information is deleted pub struct ReducedPwmPin { reg: TimDynRegister, pwm_base: PwmBase, _pin_id: DynPinId, _mode: PhantomData, } impl From> for ReducedPwmPin { fn from(pwm_pin: PwmPin) -> Self { ReducedPwmPin { reg: TimDynRegister::from(pwm_pin.reg), pwm_base: pwm_pin.pwm_base, _pin_id: PIN::DYN, _mode: PhantomData, } } } impl ReducedPwmPin { pwm_common_func!(); } impl From> for ReducedPwmPin { fn from(other: ReducedPwmPin) -> Self { let mut pwmb = Self { reg: other.reg, pwm_base: other.pwm_base, _pin_id: other._pin_id, _mode: PhantomData, }; pwmb.enable_pwm_b(); pwmb } } impl From> for ReducedPwmPin { fn from(other: ReducedPwmPin) -> Self { let mut pwmb = Self { reg: other.reg, pwm_base: other.pwm_base, _pin_id: other._pin_id, _mode: PhantomData, }; pwmb.enable_pwm_a(); pwmb } } //================================================================================================== // PWMB implementations //================================================================================================== impl PwmPin where (PIN, TIM): ValidTimAndPin, { pwmb_func!(); } impl ReducedPwmPin { pwmb_func!(); } //================================================================================================== // Embedded HAL implementation: PWMA only //================================================================================================== macro_rules! pwm_pin_impl { () => { #[inline] fn disable(&mut self) { self.reg.reg().ctrl.modify(|_, w| w.enable().clear_bit()); } #[inline] fn enable(&mut self) { self.reg.reg().ctrl.modify(|_, w| w.enable().set_bit()); } #[inline] fn set_duty(&mut self, duty: Self::Duty) { self.pwm_base.current_duty = duty; let pwma_val: u64 = (self.pwm_base.current_rst_val as u64 * (DUTY_MAX as u64 - self.pwm_base.current_duty as u64)) / DUTY_MAX as u64; self.reg .reg() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); } #[inline] fn get_duty(&self) -> Self::Duty { self.pwm_base.current_duty } #[inline] fn get_max_duty(&self) -> Self::Duty { DUTY_MAX } }; } macro_rules! pwm_impl { () => { #[inline] fn disable(&mut self, _channel: Self::Channel) { self.reg.reg().ctrl.modify(|_, w| w.enable().clear_bit()); } #[inline] fn enable(&mut self, _channel: Self::Channel) { self.reg.reg().ctrl.modify(|_, w| w.enable().set_bit()); } #[inline] fn get_period(&self) -> Self::Time { self.pwm_base.current_period } #[inline] fn set_duty(&mut self, _channel: Self::Channel, duty: Self::Duty) { self.pwm_base.current_duty = duty; let pwma_val: u64 = (self.pwm_base.current_rst_val as u64 * (DUTY_MAX as u64 - self.pwm_base.current_duty as u64)) / DUTY_MAX as u64; self.reg .reg() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); } #[inline] fn set_period

(&mut self, period: P) where P: Into, { self.pwm_base.current_period = period.into(); // Avoid division by 0 if self.pwm_base.current_period.0 == 0 { return; } self.pwm_base.current_rst_val = self.pwm_base.sys_clk.0 / self.pwm_base.current_period.0; let reg_block = self.reg.reg(); reg_block .rst_value .write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) }); reg_block .cnt_value .write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) }); } #[inline(always)] fn get_duty(&self, _channel: Self::Channel) -> Self::Duty { self.pwm_base.current_duty } #[inline(always)] fn get_max_duty(&self) -> Self::Duty { DUTY_MAX } }; } impl embedded_hal::Pwm for PwmPin { type Channel = (); type Duty = u16; type Time = Hertz; pwm_impl!(); } impl embedded_hal::Pwm for ReducedPwmPin { type Channel = (); type Duty = u16; type Time = Hertz; pwm_impl!(); } impl embedded_hal::PwmPin for PwmPin { type Duty = u16; pwm_pin_impl!(); } impl embedded_hal::PwmPin for ReducedPwmPin { type Duty = u16; pwm_pin_impl!(); } /// 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 } }