vorago_shared_hal/
pwm.rs

1use core::convert::Infallible;
2use core::marker::PhantomData;
3
4use crate::gpio::IoPeriphPin;
5use crate::timer::enable_tim_clk;
6use crate::timer::regs::{EnableControl, StatusSelect};
7use crate::{PeripheralSelect, enable_peripheral_clock};
8
9use crate::time::Hertz;
10use crate::timer::{self, TimId, TimInstance, TimPin};
11
12const DUTY_MAX: u16 = u16::MAX;
13
14#[derive(Debug)]
15#[cfg_attr(feature = "defmt", derive(defmt::Format))]
16pub enum PwmA {}
17#[derive(Debug)]
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19pub enum PwmB {}
20
21#[derive(Debug, thiserror::Error)]
22#[cfg_attr(feature = "defmt", derive(defmt::Format))]
23#[error("pin tim ID {pin_tim:?} and timer tim id {tim_id:?} do not match")]
24pub struct TimMissmatchError {
25    pin_tim: TimId,
26    tim_id: TimId,
27}
28
29//==================================================================================================
30// PWM pin
31//==================================================================================================
32
33/// Reduced version where type information is deleted
34pub struct PwmPin<Mode = PwmA> {
35    tim_id: TimId,
36    regs: timer::regs::MmioTimer<'static>,
37    ref_clk: Hertz,
38    /// For PWMB, this is the upper limit
39    current_duty: u16,
40    /// For PWMA, this value will not be used
41    current_lower_limit: u16,
42    current_period: Hertz,
43    current_rst_val: u32,
44    mode: PhantomData<Mode>,
45}
46
47impl<Mode> PwmPin<Mode> {
48    /// Create a new PWM pin
49    pub fn new<Pin: TimPin, Tim: TimInstance>(
50        _pin: Pin,
51        _tim: Tim,
52        #[cfg(feature = "vor1x")] sys_clk: Hertz,
53        #[cfg(feature = "vor4x")] clks: &crate::clock::Clocks,
54        initial_frequency: Hertz,
55    ) -> Result<Self, TimMissmatchError> {
56        if Pin::TIM_ID != Tim::ID {
57            return Err(TimMissmatchError {
58                pin_tim: Pin::TIM_ID,
59                tim_id: Tim::ID,
60            });
61        }
62        IoPeriphPin::new(Pin::PIN_ID, Pin::FUN_SEL, None);
63        let mut pin = PwmPin {
64            tim_id: Tim::ID,
65            regs: timer::regs::Timer::new_mmio(Tim::ID),
66            current_duty: 0,
67            current_lower_limit: 0,
68            current_period: initial_frequency,
69            current_rst_val: 0,
70            #[cfg(feature = "vor1x")]
71            ref_clk: sys_clk,
72            #[cfg(feature = "vor4x")]
73            ref_clk: clks.apb1(),
74            mode: PhantomData,
75        };
76        // For Vorago 4x, the presence of the pin structure ensures that its respective peripheral
77        // clock was already enabled.
78        #[cfg(feature = "vor1x")]
79        enable_peripheral_clock(PeripheralSelect::Gpio);
80        enable_peripheral_clock(PeripheralSelect::IoConfig);
81        enable_tim_clk(Tim::ID);
82        pin.enable_pwm_a();
83        pin.set_period(initial_frequency);
84        Ok(pin)
85    }
86
87    #[inline]
88    fn enable_pwm_a(&mut self) {
89        self.regs.modify_control(|mut value| {
90            value.set_status_sel(StatusSelect::PwmaOutput);
91            value
92        });
93    }
94
95    #[inline]
96    fn enable_pwm_b(&mut self) {
97        self.regs.modify_control(|mut value| {
98            value.set_status_sel(StatusSelect::PwmbOutput);
99            value
100        });
101    }
102
103    #[inline]
104    pub fn get_period(&self) -> Hertz {
105        self.current_period
106    }
107
108    #[inline]
109    pub fn set_period(&mut self, period: impl Into<Hertz>) {
110        self.current_period = period.into();
111        // Avoid division by 0
112        if self.current_period.raw() == 0 {
113            return;
114        }
115        self.current_rst_val = self.ref_clk.raw() / self.current_period.raw();
116        self.regs.write_reset_value(self.current_rst_val);
117    }
118
119    #[inline]
120    pub fn disable(&mut self) {
121        self.regs.write_enable_control(EnableControl::new_disable());
122    }
123
124    #[inline]
125    pub fn enable(&mut self) {
126        self.regs.write_enable_control(EnableControl::new_enable());
127    }
128
129    #[inline]
130    pub fn period(&self) -> Hertz {
131        self.current_period
132    }
133
134    #[inline(always)]
135    pub fn duty(&self) -> u16 {
136        self.current_duty
137    }
138}
139
140impl From<PwmPin<PwmA>> for PwmPin<PwmB> {
141    fn from(other: PwmPin<PwmA>) -> Self {
142        let mut pwmb = Self {
143            mode: PhantomData,
144            regs: other.regs,
145            tim_id: other.tim_id,
146            ref_clk: other.ref_clk,
147            current_duty: other.current_duty,
148            current_lower_limit: other.current_lower_limit,
149            current_period: other.current_period,
150            current_rst_val: other.current_rst_val,
151        };
152        pwmb.enable_pwm_b();
153        pwmb
154    }
155}
156
157impl From<PwmPin<PwmB>> for PwmPin<PwmA> {
158    fn from(other: PwmPin<PwmB>) -> Self {
159        let mut pwmb = Self {
160            mode: PhantomData,
161            tim_id: other.tim_id,
162            regs: other.regs,
163            ref_clk: other.ref_clk,
164            current_duty: other.current_duty,
165            current_lower_limit: other.current_lower_limit,
166            current_period: other.current_period,
167            current_rst_val: other.current_rst_val,
168        };
169        pwmb.enable_pwm_a();
170        pwmb
171    }
172}
173
174//==================================================================================================
175// PWMB implementations
176//==================================================================================================
177
178impl PwmPin<PwmB> {
179    #[inline(always)]
180    pub fn pwmb_lower_limit(&self) -> u16 {
181        self.current_lower_limit
182    }
183
184    #[inline(always)]
185    pub fn pwmb_upper_limit(&self) -> u16 {
186        self.current_duty
187    }
188
189    /// Set the lower limit for PWMB
190    ///
191    /// The PWM signal will be 1 as long as the current RST counter is larger than
192    /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
193    /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
194    /// state
195    #[inline(always)]
196    pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
197        self.current_lower_limit = duty;
198        let pwmb_val: u64 =
199            (self.current_rst_val as u64 * self.current_lower_limit as u64) / DUTY_MAX as u64;
200        self.regs.write_pwmb_value(pwmb_val as u32);
201    }
202
203    /// Set the higher limit for PWMB
204    ///
205    /// The PWM signal will be 1 as long as the current RST counter is smaller than
206    /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
207    /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
208    /// state
209    pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
210        self.current_duty = duty;
211        let pwma_val: u64 =
212            (self.current_rst_val as u64 * self.current_duty as u64) / DUTY_MAX as u64;
213        self.regs.write_pwma_value(pwma_val as u32);
214    }
215}
216
217//==================================================================================================
218// Embedded HAL implementation: PWMA only
219//==================================================================================================
220
221impl embedded_hal::pwm::ErrorType for PwmPin {
222    type Error = Infallible;
223}
224
225impl embedded_hal::pwm::SetDutyCycle for PwmPin {
226    #[inline]
227    fn max_duty_cycle(&self) -> u16 {
228        DUTY_MAX
229    }
230
231    #[inline]
232    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
233        self.current_duty = duty;
234        let pwma_val: u64 = (self.current_rst_val as u64
235            * (DUTY_MAX as u64 - self.current_duty as u64))
236            / DUTY_MAX as u64;
237        self.regs.write_pwma_value(pwma_val as u32);
238        Ok(())
239    }
240}
241
242/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
243///
244/// Please note that this might load a lot of floating point code because this processor does not
245/// have a FPU
246pub fn get_duty_from_percent(percent: f32) -> u16 {
247    if percent > 1.0 {
248        DUTY_MAX
249    } else if percent <= 0.0 {
250        0
251    } else {
252        (percent * DUTY_MAX as f32) as u16
253    }
254}