Skip to main content

ra_hal/
pwm.rs

1//! Pulse Width Modulation driver utilizing the General PWM Timer (`GPT`).
2//!
3//! # Notes
4//! * The `GPT` timer has both 16-bit and 32-bit timer instances.
5//!   Unlike the [`timer`](crate::timer) this driver treats all instances
6//!   as 16-bit for the sake of brevity.
7//! * `RA4M1`: The default configuration sets the CPU and GPT clocks to 48 MHz,
8//!   however setting the CPU clock to 32 MHz allows for the GPT clock
9//!   to be set to 32 MHz or 64 MHz.
10//! * Two module stop gates are shared across all `GPT` instances.  One
11//!   for 16-bit and one for 32-bit instances.  Currently they are not
12//!   disabled on `Drop` as we're not refcounting `GPT` usage and
13//!   `time-driver` uses `GPT32_0`.  Pins are returned to input state on drop.
14
15use core::marker::PhantomData;
16
17use embassy_hal_internal::{Peri, PeripheralType};
18use embassy_sync::waitqueue::AtomicWaker;
19use paste::paste;
20
21use crate::{
22    event_link::InterruptEvent,
23    gpio::{Flex, Pin, PortFunction, WithOpenDrain},
24    module_stop::ModuleStop,
25    pac::{
26        self,
27        gpt::vals::{Ccr, Gtio, Mode, Odty, Tpcs},
28    },
29};
30
31/// PWM configuration
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33#[non_exhaustive]
34pub struct Config {
35    /// Timer prescaler. §22.2.12.
36    pub divider: Divider,
37
38    /// Where to toggle the output (channel A). §22.3.3.1.
39    pub compare_a: u16,
40
41    /// Where to toggle the output (channel B). §22.3.3.1.
42    pub compare_b: u16,
43
44    /// Maximum value of `GTCNT`.  The counter will reverse direction at this point.
45    pub top: u16,
46}
47
48/// PWM clock divider
49#[cfg_attr(feature = "defmt", derive(defmt::Format))]
50pub enum Divider {
51    /// `PCLKD/1`
52    Div1,
53
54    /// `PCLKD/4`
55    Div4,
56
57    /// `PCLKD/16`
58    Div16,
59
60    /// `PCLKD/64`
61    Div64,
62
63    /// `PCLKD/256`
64    Div256,
65
66    /// `PCLKD/1024`
67    Div1024,
68}
69
70/// PWM driver
71pub struct Pwm<'d, I: Instance> {
72    _instance: PhantomData<&'d I>,
73    // These are set to WithOpenDrain because on the RA4M1 all PWM pins have both capabilities
74    channel_a: Option<Flex<'d, WithOpenDrain>>,
75    channel_b: Option<Flex<'d, WithOpenDrain>>,
76}
77
78/// Error type for `Pwm` operations.
79#[cfg_attr(feature = "defmt", derive(defmt::Format))]
80#[derive(Debug)]
81pub enum PwmError {
82    /// The duty cycle exceeds the width of the timer or the currently set period.
83    InvalidDutyCycle,
84
85    /// Any other kind of error.
86    Unknown,
87}
88
89/// PWM instance
90#[allow(private_bounds)]
91pub trait Instance: SealedInstance + ModuleStop + PeripheralType + 'static + Send {}
92
93pub(crate) trait SealedInstance {
94    #[cfg(feature = "defmt")]
95    const PERIPHERAL: &'static str;
96    #[cfg(not(feature = "defmt"))]
97    const PERIPHERAL: () = ();
98
99    const CAPTURE_COMP_A_EVENT: InterruptEvent;
100
101    #[allow(dead_code)]
102    const CAPTURE_COMP_B_EVENT: InterruptEvent;
103
104    #[allow(dead_code)]
105    const COMP_C_EVENT: InterruptEvent;
106
107    fn regs() -> pac::gpt::Gpt;
108
109    fn cmpa_waker() -> &'static AtomicWaker;
110}
111
112/// A pin configured to be used as a single PWM channel.
113#[allow(private_bounds)]
114pub trait PwmChannel: SealedPwmChannel {}
115
116pub(crate) trait SealedPwmChannel {}
117
118/// PWM output pin trait.
119#[allow(private_bounds)]
120pub trait PwmPin<I: Instance, C: PwmChannel>: SealedPwmPin<I, C> {}
121
122pub(crate) trait SealedPwmPin<I: SealedInstance, C: PwmChannel>:
123    Pin + PeripheralType
124{
125    const PERIPHERAL_FUNC: PortFunction;
126
127    #[inline(always)]
128    fn set_pfunc(&self) {
129        self.set_as_pf(Self::PERIPHERAL_FUNC);
130    }
131}
132
133trait SealedPwmChansetter {}
134impl<'d, I: Instance> SealedPwmChansetter for Pwm<'d, I> {}
135
136/// Trait that allows setting specific channels with the same function name.
137#[allow(private_bounds)]
138pub trait PwmChansetter<'d, C: PwmChannel, I: Instance>: SealedPwmChansetter {
139    /// Takes ownership of a pin and assigns it to output channel `C`.
140    fn with_channel<A: PwmPin<I, C>>(self, pin_a: Peri<'d, A>) -> Self;
141}
142
143impl<'d, I: Instance> PwmChansetter<'d, ChanA, I> for Pwm<'d, I> {
144    /// This will panic if Channel A has already been set.
145    fn with_channel<A: PwmPin<I, ChanA>>(mut self, pin_a: Peri<'d, A>) -> Self {
146        assert!(self.channel_b.is_none());
147
148        let pwm = I::regs();
149
150        pwm.gtior().modify(|w| {
151            // Start=Low, End=Low, Match=Toggle
152            w.set_gtioa(Gtio::_00111);
153            w.set_oae(true);
154        });
155        pwm.gtber().modify(|w| w.set_ccra(Ccr::SingleBuffer));
156
157        pin_a.set_pfunc();
158        let pin_a = Flex::new(pin_a);
159
160        self.channel_a = Some(pin_a);
161        self
162    }
163}
164
165impl<'d, I: Instance> PwmChansetter<'d, ChanB, I> for Pwm<'d, I> {
166    /// This will panic if Channel B has already been set.
167    fn with_channel<B: PwmPin<I, ChanB>>(mut self, pin_b: Peri<'d, B>) -> Self {
168        assert!(self.channel_b.is_none());
169
170        let pwm = I::regs();
171
172        pwm.gtior().modify(|w| {
173            // Start=Low, End=Low, Match=Toggle
174            w.set_gtiob(Gtio::_00111);
175            w.set_obe(true);
176        });
177        pwm.gtber().modify(|w| w.set_ccrb(Ccr::SingleBuffer));
178
179        pin_b.set_pfunc();
180        let pin_b = Flex::new(pin_b);
181
182        self.channel_b = Some(pin_b);
183        self
184    }
185}
186
187impl Default for Config {
188    fn default() -> Self {
189        Self {
190            divider: Divider::Div4,
191            compare_a: 0,
192            compare_b: 0,
193            top: 0xFFFF,
194        }
195    }
196}
197
198impl From<Divider> for u16 {
199    fn from(value: Divider) -> Self {
200        match value {
201            Divider::Div1 => 1,
202            Divider::Div4 => 4,
203            Divider::Div16 => 16,
204            Divider::Div64 => 64,
205            Divider::Div256 => 256,
206            Divider::Div1024 => 1024,
207        }
208    }
209}
210
211impl From<Tpcs> for Divider {
212    fn from(value: Tpcs) -> Self {
213        match value {
214            Tpcs::Div1 => Self::Div1,
215            Tpcs::Div4 => Self::Div4,
216            Tpcs::Div16 => Self::Div16,
217            Tpcs::Div64 => Self::Div64,
218            Tpcs::Div256 => Self::Div256,
219            Tpcs::Div1024 => Self::Div1024,
220            Tpcs::_RESERVED_6 => unimplemented!(),
221            Tpcs::_RESERVED_7 => unimplemented!(),
222        }
223    }
224}
225
226impl From<Divider> for Tpcs {
227    fn from(value: Divider) -> Self {
228        match value {
229            Divider::Div1 => Self::Div1,
230            Divider::Div4 => Self::Div4,
231            Divider::Div16 => Self::Div16,
232            Divider::Div64 => Self::Div64,
233            Divider::Div256 => Self::Div256,
234            Divider::Div1024 => Self::Div1024,
235        }
236    }
237}
238
239impl<'d, I: Instance> Pwm<'d, I> {
240    /// Consumes a `GPT` peripheral instance and returns a driver.
241    ///
242    /// # Arguments
243    /// * `_peri` The `GPT` peripheral e.g. `GPT16_5` or `GPT32_1`.
244    /// * `config` configuration
245    ///
246    /// # Returns
247    ///
248    /// A `PWM` driver with no output pins assigned and whose counter is initialized to `0` but has not been started.
249    pub fn new(peri: Peri<'d, I>, config: Config) -> Self {
250        let _ = peri;
251
252        I::start_module();
253
254        let pwm = I::regs();
255
256        pwm.gtcr().modify(|w| w.set_md(Mode::TrianglePwm1));
257
258        pwm.gtcnt().write_value(0);
259
260        let mut this = Self {
261            _instance: PhantomData,
262            channel_a: None,
263            channel_b: None,
264        };
265
266        pwm.gtssr().modify(|r| r.set_cstrt(true));
267
268        this.set_config(config);
269
270        this
271    }
272
273    /// Takes ownership of a pin and assigns it to output channel A.
274    pub fn with_channel_a<A: PwmPin<I, ChanA>>(self, pin_a: Peri<'d, A>) -> Self {
275        PwmChansetter::<ChanA, I>::with_channel::<A>(self, pin_a)
276    }
277
278    /// Takes ownership of a pin and assigns it to output channel B.
279    pub fn with_channel_b<B: PwmPin<I, ChanB>>(self, pin_b: Peri<'d, B>) -> Self {
280        PwmChansetter::<ChanB, I>::with_channel::<B>(self, pin_b)
281    }
282
283    /// Applies a `Config` struct.
284    /// Does not reset the counter.
285    pub fn set_config(&mut self, config: Config) {
286        let pwm = I::regs();
287        pwm.gtcr().modify(|w| w.set_tpcs(config.divider.into()));
288        pwm.gtpr().write_value(config.top as u32);
289
290        pwm.gtuddtyc().modify(|w| w.set_oadty(Odty::CompareMatch));
291        pwm.gtccra().write_value(config.compare_a as u32);
292        pwm.gtccrc().write_value(config.compare_a as u32);
293
294        pwm.gtuddtyc().modify(|w| w.set_obdty(Odty::CompareMatch));
295        pwm.gtccrb().write_value(config.compare_b as u32);
296        pwm.gtccre().write_value(config.compare_b as u32);
297    }
298
299    /// Starts the PWM counter.
300    #[inline]
301    pub fn start(&mut self) {
302        let pwm = I::regs();
303
304        pwm.gtcr().modify(|w| w.set_cst(true));
305    }
306
307    /// Stops the PWM counter.
308    #[inline]
309    pub fn stop(&mut self) {
310        let pwm = I::regs();
311
312        pwm.gtcr().modify(|w| w.set_cst(false));
313    }
314
315    /// Sets the frequency and duty cycle for both channels.
316    ///
317    /// # Arguments
318    /// * `frequency` Frequency in hertz
319    /// * `pct` Duty cycle percentage, range is `0.0..=1.0`
320    pub fn set_frequency(&mut self, frequency: u32, pct: f32) -> Result<(), PwmError> {
321        let pwm = I::regs();
322        let clocks = crate::clock::clock_status();
323        let divider: Divider = pwm.gtcr().read().tpcs().into();
324        let divider: u16 = divider.into();
325        let divider = divider as f32;
326        let pwm_clk = clocks.peripheral_d.to_Hz() as f32;
327        let period = (pwm_clk / divider) / (frequency as f32 * 2.0);
328
329        trace!(
330            "{}set_freq(freq={}, pct={}%, divider={}, clk={}, period={})",
331            I::PERIPHERAL,
332            frequency,
333            pct * 100.0,
334            divider,
335            pwm_clk,
336            period,
337        );
338
339        if period < 2.0 || period > f32::from(u16::MAX - 2) {
340            error!(
341                "{}Unable to set frequency={} Hz, max allowable={}",
342                I::PERIPHERAL,
343                frequency,
344                (pwm_clk / (divider * 4.0)) as u32,
345            );
346            return Err(PwmError::InvalidDutyCycle);
347        }
348
349        pwm.gtpr().write_value(period as u32);
350        self.set_duty_pct(pct);
351
352        Ok(())
353    }
354
355    /// Sets the duty cycle for both channels to the same value.
356    ///
357    /// # Arguments
358    /// `pct` Duty cycle percentage, range is `0.0..=1.0`
359    #[inline]
360    pub fn set_duty_pct(&mut self, pct: f32) {
361        self.set_duty_pct_a(pct);
362        self.set_duty_pct_b(pct);
363    }
364
365    /// Sets the duty cycle for channel A if it's been assigned to a pin.
366    ///
367    /// # Arguments
368    /// `pct` Duty cycle percentage, range is `0.0..=1.0`
369    pub fn set_duty_pct_a(&mut self, pct: f32) {
370        if self.channel_a.is_none() {
371            return;
372        }
373
374        let pwm = I::regs();
375        let pct = pct.clamp(0.0, 1.0);
376        let period = pwm.gtpr().read() as f32;
377        let cmp = (period * (1.0 - pct)) as u32;
378
379        if cmp == 0 {
380            pwm.gtuddtyc().modify(|w| w.set_oadty(Odty::On));
381        } else if cmp >= period as u32 {
382            pwm.gtuddtyc().modify(|w| w.set_oadty(Odty::Off));
383        } else {
384            // This will center the peak
385
386            pwm.gtuddtyc().modify(|w| w.set_oadty(Odty::CompareMatch));
387            pwm.gtccra().write_value(cmp);
388            pwm.gtccrc().write_value(cmp);
389        }
390    }
391
392    /// Sets the duty cycle for channel A if it's been assigned to a pin.
393    ///
394    /// # Arguments
395    /// `duty` Raw value, range is `0.0..=period`
396    pub fn set_duty_a(&mut self, duty: u32) -> Result<(), PwmError> {
397        if self.channel_a.is_none() {
398            return Ok(());
399        }
400
401        let pwm = I::regs();
402        let period = pwm.gtpr().read();
403
404        #[cfg(feature = "strict-assert")]
405        assert!(period <= u32::from(u16::MAX));
406        if period > u32::from(u16::MAX) {
407            Err(PwmError::InvalidDutyCycle)?;
408        }
409
410        if duty == 0 {
411            pwm.gtuddtyc().modify(|w| w.set_oadty(Odty::On));
412        } else if duty >= period {
413            pwm.gtuddtyc().modify(|w| w.set_oadty(Odty::Off));
414        } else {
415            // This will center the peak
416
417            pwm.gtuddtyc().modify(|w| w.set_oadty(Odty::CompareMatch));
418            pwm.gtccra().write_value(duty);
419            pwm.gtccrc().write_value(duty);
420        }
421
422        Ok(())
423    }
424
425    /// Sets the duty cycle for channel B if it's been assigned to a pin.
426    ///
427    /// # Arguments
428    /// `pct` Duty cycle, range is 0.0..=1.0
429    pub fn set_duty_pct_b(&mut self, pct: f32) {
430        if self.channel_b.is_none() {
431            return;
432        }
433
434        let pwm = I::regs();
435        let pct = pct.clamp(0.0, 1.0);
436        let period = pwm.gtpr().read() as f32;
437        let cmp = (period * (1.0 - pct)) as u32;
438
439        if cmp == 0 {
440            pwm.gtuddtyc().modify(|w| w.set_obdty(Odty::On));
441        } else if cmp >= period as u32 {
442            pwm.gtuddtyc().modify(|w| w.set_obdty(Odty::Off));
443        } else {
444            // This will center the peak
445
446            pwm.gtuddtyc().modify(|w| w.set_obdty(Odty::CompareMatch));
447            pwm.gtccrb().write_value(cmp);
448            pwm.gtccre().write_value(cmp);
449        }
450    }
451
452    /// Sets the duty cycle for channel B if it's been assigned to a pin.
453    ///
454    /// # Arguments
455    /// `duty` Raw value, range is `0.0..=period`
456    pub fn set_duty_b(&mut self, duty: u32) -> Result<(), PwmError> {
457        if self.channel_b.is_none() {
458            return Ok(());
459        }
460
461        let pwm = I::regs();
462        let period = pwm.gtpr().read();
463
464        #[cfg(feature = "strict-assert")]
465        assert!(period <= u32::from(u16::MAX));
466        if period > u32::from(u16::MAX) {
467            Err(PwmError::InvalidDutyCycle)?;
468        }
469
470        if duty == 0 {
471            pwm.gtuddtyc().modify(|w| w.set_obdty(Odty::On));
472        } else if duty >= period {
473            pwm.gtuddtyc().modify(|w| w.set_obdty(Odty::Off));
474        } else {
475            // This will center the peak
476
477            pwm.gtuddtyc().modify(|w| w.set_obdty(Odty::CompareMatch));
478            pwm.gtccrb().write_value(duty);
479            pwm.gtccre().write_value(duty);
480        }
481
482        Ok(())
483    }
484}
485
486impl<'d, I: Instance> Drop for Pwm<'d, I> {
487    fn drop(&mut self) {
488        self.stop();
489        I::stop_module();
490    }
491}
492
493macro_rules! declare_pwm_channel {
494    ($chan:ident) => {
495        paste! {
496            // TODO: macro expansion in rustdoc?
497            #[allow(missing_docs)]
498            pub enum [< Chan $chan >]  {}
499            impl PwmChannel for [< Chan $chan >] {}
500            impl SealedPwmChannel for [< Chan $chan >] {}
501        }
502    };
503}
504
505/// Declares a PWM pin
506///
507/// # Arguments
508/// * `$instance` GPT instance e.g. `GPT32_0`, `GPT16_2`
509/// * `$chan` channel either `ChanA` or `ChanB`
510/// * `$pin` peripheral name for the pin
511/// * `$pf` Peripheral Function
512macro_rules! pwm_pin {
513    ($instance:ident, $chan:ident, $pin:ident, $pf:ident) => {
514        impl crate::pwm::PwmPin<crate::peripherals::$instance, crate::pwm::$chan>
515            for crate::peripherals::$pin
516        {
517        }
518
519        impl crate::pwm::SealedPwmPin<crate::peripherals::$instance, crate::pwm::$chan>
520            for crate::peripherals::$pin
521        {
522            const PERIPHERAL_FUNC: crate::gpio::PortFunction = crate::gpio::PortFunction::$pf;
523        }
524    };
525}
526pub(crate) use pwm_pin;
527
528declare_pwm_channel!(A);
529declare_pwm_channel!(B);
530
531macro_rules! gpt_instance {
532    ($peripheral:ident, $ccmpa:ident, $ccmpb:ident, $cmpc:ident, $overflow:ident, $underflow:ident) => {
533        impl crate::pwm::Instance for crate::peripherals::$peripheral {}
534        impl crate::pwm::SealedInstance for crate::peripherals::$peripheral {
535            #[cfg(feature = "defmt")]
536            const PERIPHERAL: &'static str = concat!(stringify!($peripheral), ": ");
537
538            const CAPTURE_COMP_A_EVENT: crate::event_link::InterruptEvent =
539                crate::event_link::InterruptEvent::$ccmpa;
540            const CAPTURE_COMP_B_EVENT: crate::event_link::InterruptEvent =
541                crate::event_link::InterruptEvent::$ccmpb;
542            const COMP_C_EVENT: crate::event_link::InterruptEvent =
543                crate::event_link::InterruptEvent::$cmpc;
544
545            #[inline(always)]
546            fn regs() -> crate::pac::gpt::Gpt {
547                crate::pac::$peripheral
548            }
549
550            fn cmpa_waker() -> &'static embassy_sync::waitqueue::AtomicWaker {
551                static WAKER: embassy_sync::waitqueue::AtomicWaker =
552                    embassy_sync::waitqueue::AtomicWaker::new();
553                &WAKER
554            }
555        }
556    };
557}
558pub(crate) use gpt_instance;
559
560impl embedded_hal_1::pwm::Error for PwmError {
561    fn kind(&self) -> embedded_hal_1::pwm::ErrorKind {
562        embedded_hal_1::pwm::ErrorKind::Other
563    }
564}
565
566impl<'d, I: Instance> embedded_hal_1::pwm::ErrorType for Pwm<'d, I> {
567    type Error = PwmError;
568}
569
570impl<'d, I: Instance> embedded_hal_1::pwm::SetDutyCycle for Pwm<'d, I> {
571    fn max_duty_cycle(&self) -> u16 {
572        let pwm = I::regs();
573        let period = pwm.gtpr().read();
574        assert!(period <= u32::from(u16::MAX));
575        period as u16
576    }
577
578    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
579        if self.channel_a.is_some() {
580            self.set_duty_a(duty as u32)?;
581        }
582
583        if self.channel_b.is_some() {
584            self.set_duty_b(duty as u32)?;
585        }
586
587        Ok(())
588    }
589}