avr_hal_generic/
simple_pwm.rs

1//! PWM Implementation
2
3use core::marker::PhantomData;
4use embedded_hal::pwm;
5use embedded_hal::pwm::{ErrorKind, ErrorType, SetDutyCycle};
6
7use crate::port::mode;
8use crate::port::Pin;
9
10/// Clock prescaler for PWM
11///
12/// The prescaler dictates the PWM frequency, together with the IO clock.  The formula is as
13/// follows:
14///
15/// ```text
16/// F_pwm = CLK_io / (Prescaler * 256);
17/// ```
18///
19/// | Prescaler | 16 MHz Clock | 8 MHz Clock |
20/// | --- | --- | ---|
21/// | `Direct` | 62.5 kHz | 31.3 kHz |
22/// | `Prescale8` | 7.81 kHz | 3.91 kHz |
23/// | `Prescale64` | 977 Hz | 488 Hz |
24/// | `Prescale256` | 244 Hz | 122 Hz |
25/// | `Prescale1024` | 61.0 Hz | 30.5 Hz |
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27pub enum Prescaler {
28    /// No prescaling, the IO clock drives the timer directly.
29    Direct,
30    /// Divide the IO clock by 8.
31    Prescale8,
32    /// Divide the IO clock by 64.
33    Prescale64,
34    /// Divide the IO clock by 256.
35    Prescale256,
36    /// Divide the IO clock by 1024.
37    Prescale1024,
38}
39
40/// Implement traits and types for PWM timers
41pub trait PwmPinOps<TC> {
42    type Duty;
43
44    fn enable(&mut self);
45    fn disable(&mut self);
46    fn get_duty(&self) -> Self::Duty;
47    fn get_max_duty(&self) -> Self::Duty;
48
49    fn set_duty(&mut self, value: u8);
50}
51
52pub trait IntoPwmPin<TC, PIN> {
53    fn into_pwm(self, timer: &TC) -> Pin<mode::PwmOutput<TC>, PIN>;
54}
55
56impl<TC, PIN: PwmPinOps<TC>> IntoPwmPin<TC, PIN> for Pin<mode::Output, PIN> {
57    fn into_pwm(self, _timer: &TC) -> Pin<mode::PwmOutput<TC>, PIN> {
58        Pin {
59            pin: self.pin,
60            _mode: PhantomData,
61        }
62    }
63}
64
65impl<TC, PIN: PwmPinOps<TC>> Pin<mode::PwmOutput<TC>, PIN> {
66    pub fn enable(&mut self) {
67        self.pin.enable();
68    }
69
70    pub fn disable(&mut self) {
71        self.pin.disable();
72    }
73
74    pub fn get_duty(&self) -> <PIN as PwmPinOps<TC>>::Duty {
75        self.pin.get_duty()
76    }
77
78    pub fn get_max_duty(&self) -> <PIN as PwmPinOps<TC>>::Duty {
79        self.pin.get_max_duty()
80    }
81
82    pub fn set_duty(&mut self, duty: u8) {
83        self.pin.set_duty(duty);
84    }
85}
86
87#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
88pub enum PwmError {
89    /// `embedded-hal` supports duty cycles up to `u16`, however `avr` devices only support up to `u8`.
90    /// Passing a duty cycle larger than [`u8::MAX`] will result in this error.
91    DutyCycleTooLarge,
92}
93
94impl pwm::Error for PwmError {
95    fn kind(&self) -> ErrorKind {
96        ErrorKind::Other
97    }
98}
99
100impl<TC, PIN: PwmPinOps<TC>> ErrorType for Pin<mode::PwmOutput<TC>, PIN> {
101    type Error = PwmError;
102}
103
104impl<TC, PIN: PwmPinOps<TC, Duty = u8>> SetDutyCycle for Pin<mode::PwmOutput<TC>, PIN> {
105    fn max_duty_cycle(&self) -> u16 {
106        self.get_max_duty() as u16
107    }
108
109    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
110        if duty > u8::MAX as u16 {
111            return Err(PwmError::DutyCycleTooLarge);
112        }
113        self.set_duty(duty as u8);
114        Ok(())
115    }
116}
117
118#[macro_export]
119macro_rules! impl_simple_pwm {
120    (
121        $(#[$timer_pwm_attr:meta])*
122        pub struct $TimerPwm:ident {
123            timer: $TIMER:ty,
124            init: |$init_timer:ident, $prescaler:ident| $init_block:block,
125            pins: {$(
126                $PXi:ident: {
127                    ocr: $ocr:ident,
128                    $into_pwm:ident: |$pin_timer:ident| if enable
129                        $pin_enable_block:block else $pin_disable_block:block,
130                },
131            )+},
132        }
133    ) => {
134        $(#[$timer_pwm_attr])*
135        pub struct $TimerPwm {
136            timer: $TIMER,
137        }
138
139        impl $TimerPwm {
140            pub fn new(timer: $TIMER, prescaler: $crate::simple_pwm::Prescaler) -> $TimerPwm {
141                let mut t = $TimerPwm { timer };
142
143                {
144                    let $init_timer = &mut t.timer;
145                    let $prescaler = prescaler;
146                    $init_block
147                }
148
149                t
150            }
151        }
152
153        $(
154            impl avr_hal_generic::simple_pwm::PwmPinOps<$TimerPwm> for $PXi {
155                type Duty = u8;
156
157                fn enable(&mut self) {
158                    // SAFETY: This block will usually result in a read-modify-write sequence which
159                    // is not concurrency safe.  Thus, it is wrapped in a critical section which
160                    // ensures we will never hit a race-condition here.
161                    $crate::avr_device::interrupt::free(|_| {
162                        let $pin_timer = unsafe { &*<$TIMER>::ptr() };
163                        $pin_enable_block
164                    });
165                }
166
167                fn disable(&mut self) {
168                    // SAFETY: This block will usually result in a read-modify-write sequence which
169                    // is not concurrency safe.  Thus, it is wrapped in a critical section which
170                    // ensures we will never hit a race-condition here.
171                    $crate::avr_device::interrupt::free(|_| {
172                        let $pin_timer = unsafe { &*<$TIMER>::ptr() };
173                        $pin_disable_block
174                    });
175                }
176
177                fn get_duty(&self) -> Self::Duty {
178                    unsafe { (&*<$TIMER>::ptr()) }.$ocr.read().bits() as Self::Duty
179                }
180
181                fn get_max_duty(&self) -> Self::Duty {
182                    u8::MAX
183                }
184
185                fn set_duty(&mut self, duty: Self::Duty) {
186                    // SAFETY: This register is exclusively used here so there are no concurrency
187                    // issues.
188                    unsafe { (&*<$TIMER>::ptr()).$ocr.write(|w| w.bits(duty.into())); };
189                }
190            }
191        )+
192    }
193}