esp_hal/mcpwm/
operator.rs

1//! # MCPWM Operator Module
2//!
3//! ## Overview
4//! The `operator` is responsible for generating `PWM (Pulse Width Modulation)`
5//! signals and handling various aspects related to `PWM` signal generation.
6//!
7//! ## Configuration
8//! This module provides flexibility in configuring the PWM outputs. Its
9//! implementation allows for motor control and other applications that demand
10//! accurate pulse timing and sophisticated modulation techniques.
11
12use core::marker::PhantomData;
13
14use super::PeripheralGuard;
15use crate::{
16    gpio::interconnect::{OutputConnection, PeripheralOutput},
17    mcpwm::{timer::Timer, PwmPeripheral},
18    pac,
19    peripheral::{Peripheral, PeripheralRef},
20};
21
22/// Input/Output Stream descriptor for each channel
23#[derive(Copy, Clone)]
24#[allow(clippy::upper_case_acronyms, reason = "peripheral is unstable")]
25pub enum PWMStream {
26    /// PWM Stream A
27    PWMA,
28    /// PWM Stream B
29    PWMB,
30}
31
32/// Configuration for MCPWM Operator DeadTime
33/// It's recommended to reference the technical manual for configuration
34#[derive(Copy, Clone)]
35pub struct DeadTimeCfg {
36    cfg_reg: u32,
37}
38
39#[allow(clippy::unusual_byte_groupings)]
40impl DeadTimeCfg {
41    // NOTE: it's a bit difficult to make this typestate
42    // due to the different interconnections (FED/RED vs PWMxA/PWMxB) and
43    // the many modes of operation
44
45    /// B_OUTBYPASS
46    const S0: u32 = 0b01_0000_0000_0000_0000;
47    /// A_OUTBYPASS
48    const S1: u32 = 0b00_1000_0000_0000_0000;
49    /// RED_OUTINVERT
50    const S2: u32 = 0b00_0010_0000_0000_0000;
51    /// FED_OUTINVERT
52    const S3: u32 = 0b00_0100_0000_0000_0000;
53    /// RED_INSEL
54    const S4: u32 = 0b00_0000_1000_0000_0000;
55    /// FED_INSEL
56    const S5: u32 = 0b00_0001_0000_0000_0000;
57    /// A_OUTSWAP
58    const S6: u32 = 0b00_0000_0010_0000_0000;
59    /// B_OUTSWAP
60    const S7: u32 = 0b00_0000_0100_0000_0000;
61    /// DEB_MODE
62    const _S8: u32 = 0b00_0000_0001_0000_0000;
63    /// Use PT_clk instead of PWM_clk
64    const CLK_SEL: u32 = 0b10_0000_0000_0000_0000;
65
66    /// Uses the following configuration:
67    /// * Clock: PWM_clk
68    /// * Bypass: A & B
69    /// * Inputs: A->A, B->B (InSel)
70    /// * Outputs: A->A, B->B (OutSwap)
71    /// * No Dual-edge B
72    /// * No Invert
73    /// * FED/RED update mode = immediate
74    pub const fn new_bypass() -> DeadTimeCfg {
75        DeadTimeCfg {
76            cfg_reg: Self::S0 | Self::S1,
77        }
78    }
79
80    /// Active High Complementary (AHC) from Technical Reference manual
81    ///
82    /// Will generate a PWM from input PWMA, such that output PWMA & PWMB are
83    /// each others complement except during a transition in which they will
84    /// be both off (as deadtime) such that they should never overlap, useful
85    /// for H-Bridge type scenarios
86    pub const fn new_ahc() -> DeadTimeCfg {
87        DeadTimeCfg { cfg_reg: Self::S3 }
88    }
89    // TODO: Add some common configurations ~AHC~,ALC,AH,AC
90
91    #[must_use]
92    const fn set_flag(mut self, flag: u32, val: bool) -> Self {
93        if val {
94            self.cfg_reg |= flag;
95        } else {
96            self.cfg_reg &= !flag;
97        }
98        self
99    }
100
101    /// Sets FED/RED output inverter
102    /// Inverts the output of the FED/RED module (excl DEB mode feedback)
103    #[must_use]
104    pub const fn invert_output(self, fed: bool, red: bool) -> Self {
105        self.set_flag(Self::S3, fed).set_flag(Self::S2, red)
106    }
107
108    /// Swaps the output of a PWM Stream
109    /// i.e. If both streams have output_swap enabled, the output of the module
110    /// is swapped, while if only one is enabled that one 'copies' from the
111    /// other stream
112    #[must_use]
113    pub const fn set_output_swap(self, stream: PWMStream, swap: bool) -> Self {
114        self.set_flag(
115            match stream {
116                PWMStream::PWMA => Self::S6,
117                PWMStream::PWMB => Self::S7,
118            },
119            swap,
120        )
121    }
122
123    /// Set PWMA/PWMB stream to bypass everything except output_swap
124    /// This means no deadtime is applied when enabled
125    #[must_use]
126    pub const fn set_bypass(self, stream: PWMStream, enable: bool) -> Self {
127        self.set_flag(
128            match stream {
129                PWMStream::PWMA => Self::S1,
130                PWMStream::PWMB => Self::S0,
131            },
132            enable,
133        )
134    }
135
136    /// Select Between PWMClk & PT_Clk
137    #[must_use]
138    pub const fn select_clock(self, pwm_clock: bool) -> Self {
139        self.set_flag(Self::CLK_SEL, pwm_clock)
140    }
141
142    /// Select which stream is used for the input of FED/RED
143    #[must_use]
144    pub const fn select_input(self, fed: PWMStream, red: PWMStream) -> Self {
145        self.set_flag(
146            Self::S5,
147            match fed {
148                PWMStream::PWMA => false,
149                PWMStream::PWMB => true,
150            },
151        )
152        .set_flag(
153            Self::S4,
154            match red {
155                PWMStream::PWMA => false,
156                PWMStream::PWMB => true,
157            },
158        )
159    }
160}
161
162/// A MCPWM operator
163///
164/// The PWM Operator submodule has the following functions:
165/// * Generates a PWM signal pair, based on timing references obtained from the
166///   corresponding PWM timer.
167/// * Each signal out of the PWM signal pair includes a specific pattern of dead
168///   time. (Not yet implemented)
169/// * Superimposes a carrier on the PWM signal, if configured to do so. (Not yet
170///   implemented)
171/// * Handles response under fault conditions. (Not yet implemented)
172pub struct Operator<'d, const OP: u8, PWM> {
173    phantom: PhantomData<&'d PWM>,
174    _guard: PeripheralGuard,
175}
176
177impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> {
178    pub(super) fn new() -> Self {
179        let guard = PeripheralGuard::new(PWM::peripheral());
180
181        // Side note:
182        // It would have been nice to deselect any timer reference on peripheral
183        // initialization.
184        // However experimentation (ESP32-S3) showed that writing `3` to timersel
185        // will not disable the timer reference but instead act as though `2` was
186        // written.
187        Operator {
188            phantom: PhantomData,
189            _guard: guard,
190        }
191    }
192
193    /// Select a [`Timer`] to be the timing reference for this operator
194    ///
195    /// ### Note:
196    /// By default TIMER0 is used
197    pub fn set_timer<const TIM: u8>(&mut self, timer: &Timer<TIM, PWM>) {
198        let _ = timer;
199        // SAFETY:
200        // We only write to our OPERATORx_TIMERSEL register
201        let block = unsafe { &*PWM::block() };
202        block.operator_timersel().modify(|_, w| match OP {
203            0 => unsafe { w.operator0_timersel().bits(TIM) },
204            1 => unsafe { w.operator1_timersel().bits(TIM) },
205            2 => unsafe { w.operator2_timersel().bits(TIM) },
206            _ => {
207                unreachable!()
208            }
209        });
210    }
211
212    /// Use the A output with the given pin and configuration
213    pub fn with_pin_a(
214        self,
215        pin: impl Peripheral<P = impl PeripheralOutput> + 'd,
216        config: PwmPinConfig<true>,
217    ) -> PwmPin<'d, PWM, OP, true> {
218        PwmPin::new(pin, config)
219    }
220
221    /// Use the B output with the given pin and configuration
222    pub fn with_pin_b(
223        self,
224        pin: impl Peripheral<P = impl PeripheralOutput> + 'd,
225        config: PwmPinConfig<false>,
226    ) -> PwmPin<'d, PWM, OP, false> {
227        PwmPin::new(pin, config)
228    }
229
230    /// Use both the A and the B output with the given pins and configurations
231    pub fn with_pins(
232        self,
233        pin_a: impl Peripheral<P = impl PeripheralOutput> + 'd,
234        config_a: PwmPinConfig<true>,
235        pin_b: impl Peripheral<P = impl PeripheralOutput> + 'd,
236        config_b: PwmPinConfig<false>,
237    ) -> (PwmPin<'d, PWM, OP, true>, PwmPin<'d, PWM, OP, false>) {
238        (PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b))
239    }
240
241    /// Link two pins using the deadtime generator
242    ///
243    /// This is useful for complementary or mirrored signals with or without
244    /// configured deadtime
245    pub fn with_linked_pins(
246        self,
247        pin_a: impl Peripheral<P = impl PeripheralOutput> + 'd,
248        config_a: PwmPinConfig<true>,
249        pin_b: impl Peripheral<P = impl PeripheralOutput> + 'd,
250        config_b: PwmPinConfig<false>,
251        config_dt: DeadTimeCfg,
252    ) -> LinkedPins<'d, PWM, OP> {
253        LinkedPins::new(pin_a, config_a, pin_b, config_b, config_dt)
254    }
255}
256
257/// Configuration describing how the operator generates a signal on a connected
258/// pin
259pub struct PwmPinConfig<const IS_A: bool> {
260    actions: PwmActions<IS_A>,
261    update_method: PwmUpdateMethod,
262}
263
264impl<const IS_A: bool> PwmPinConfig<IS_A> {
265    /// A configuration using [`PwmActions::UP_ACTIVE_HIGH`] and
266    /// [`PwmUpdateMethod::SYNC_ON_ZERO`]
267    pub const UP_ACTIVE_HIGH: Self =
268        Self::new(PwmActions::UP_ACTIVE_HIGH, PwmUpdateMethod::SYNC_ON_ZERO);
269    /// A configuration using [`PwmActions::UP_DOWN_ACTIVE_HIGH`] and
270    /// [`PwmUpdateMethod::SYNC_ON_ZERO`]
271    pub const UP_DOWN_ACTIVE_HIGH: Self = Self::new(
272        PwmActions::UP_DOWN_ACTIVE_HIGH,
273        PwmUpdateMethod::SYNC_ON_ZERO,
274    );
275    /// A configuration using [`PwmActions::empty`] and
276    /// [`PwmUpdateMethod::empty`]
277    pub const EMPTY: Self = Self::new(PwmActions::empty(), PwmUpdateMethod::empty());
278
279    /// Get a configuration using the given `PwmActions` and `PwmUpdateMethod`
280    pub const fn new(actions: PwmActions<IS_A>, update_method: PwmUpdateMethod) -> Self {
281        PwmPinConfig {
282            actions,
283            update_method,
284        }
285    }
286}
287
288/// A pin driven by an MCPWM operator
289pub struct PwmPin<'d, PWM, const OP: u8, const IS_A: bool> {
290    pin: PeripheralRef<'d, OutputConnection>,
291    phantom: PhantomData<PWM>,
292    _guard: PeripheralGuard,
293}
294
295impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, IS_A> {
296    fn new(
297        pin: impl Peripheral<P = impl PeripheralOutput> + 'd,
298        config: PwmPinConfig<IS_A>,
299    ) -> Self {
300        crate::into_mapped_ref!(pin);
301
302        let guard = PeripheralGuard::new(PWM::peripheral());
303
304        let mut pin = PwmPin {
305            pin,
306            phantom: PhantomData,
307            _guard: guard,
308        };
309        pin.set_actions(config.actions);
310        pin.set_update_method(config.update_method);
311
312        PWM::output_signal::<OP, IS_A>().connect_to(&mut pin.pin);
313        pin.pin.enable_output(true);
314
315        pin
316    }
317
318    /// Configure what actions should be taken on timing events
319    pub fn set_actions(&mut self, value: PwmActions<IS_A>) {
320        // SAFETY:
321        // We only write to our GENx_x register
322        let ch = unsafe { Self::ch() };
323        let bits = value.0;
324
325        // SAFETY:
326        // `bits` is a valid bit pattern
327        ch.gen((!IS_A) as usize).write(|w| unsafe { w.bits(bits) });
328    }
329
330    /// Set how a new timestamp syncs with the timer
331    pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) {
332        // SAFETY:
333        // We only write to our GENx_x_UPMETHOD register
334        let ch = unsafe { Self::ch() };
335        let bits = update_method.0;
336
337        #[cfg(esp32s3)]
338        let cfg = ch.cmpr_cfg();
339        #[cfg(any(esp32, esp32c6, esp32h2))]
340        let cfg = ch.gen_stmp_cfg();
341
342        cfg.modify(|_, w| unsafe {
343            if IS_A {
344                w.a_upmethod().bits(bits)
345            } else {
346                w.b_upmethod().bits(bits)
347            }
348        });
349    }
350
351    /// Write a new timestamp.
352    /// The written value will take effect according to the set
353    /// [`PwmUpdateMethod`].
354    pub fn set_timestamp(&mut self, value: u16) {
355        // SAFETY:
356        // We only write to our GENx_TSTMP_x register
357        let ch = unsafe { Self::ch() };
358
359        #[cfg(esp32s3)]
360        if IS_A {
361            ch.cmpr_value0().write(|w| unsafe { w.a().bits(value) });
362        } else {
363            ch.cmpr_value1().write(|w| unsafe { w.b().bits(value) });
364        }
365
366        #[cfg(any(esp32, esp32c6, esp32h2))]
367        if IS_A {
368            ch.gen_tstmp_a().write(|w| unsafe { w.a().bits(value) });
369        } else {
370            ch.gen_tstmp_b().write(|w| unsafe { w.b().bits(value) });
371        }
372    }
373
374    /// Get the old timestamp.
375    /// The value of the timestamp will take effect according to the set
376    /// [`PwmUpdateMethod`].
377    pub fn timestamp(&self) -> u16 {
378        // SAFETY:
379        // We only read to our GENx_TSTMP_x register
380        let ch = unsafe { Self::ch() };
381
382        #[cfg(esp32s3)]
383        if IS_A {
384            ch.cmpr_value0().read().a().bits()
385        } else {
386            ch.cmpr_value1().read().b().bits()
387        }
388
389        #[cfg(any(esp32, esp32c6, esp32h2))]
390        if IS_A {
391            ch.gen_tstmp_a().read().a().bits()
392        } else {
393            ch.gen_tstmp_b().read().b().bits()
394        }
395    }
396
397    /// Get the period of the timer.
398    pub fn period(&self) -> u16 {
399        // SAFETY:
400        // We only grant access to our CFG0 register with the lifetime of &mut self
401        let block = unsafe { &*PWM::block() };
402
403        let tim_select = block.operator_timersel().read();
404        let tim = match OP {
405            0 => tim_select.operator0_timersel().bits(),
406            1 => tim_select.operator1_timersel().bits(),
407            2 => tim_select.operator2_timersel().bits(),
408            _ => {
409                unreachable!()
410            }
411        };
412
413        // SAFETY:
414        // The CFG0 registers are identical for all timers so we can pretend they're
415        // TIMER0_CFG0
416        block.timer(tim as usize).cfg0().read().period().bits()
417    }
418
419    unsafe fn ch() -> &'static pac::mcpwm0::CH {
420        let block = unsafe { &*PWM::block() };
421        block.ch(OP as usize)
422    }
423}
424
425/// Implement no error type for the PwmPin because the method are infallible
426impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::ErrorType
427    for PwmPin<'_, PWM, OP, IS_A>
428{
429    type Error = core::convert::Infallible;
430}
431
432/// Implement the trait SetDutyCycle for PwmPin
433impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::SetDutyCycle
434    for PwmPin<'_, PWM, OP, IS_A>
435{
436    /// Get the max duty of the PwmPin
437    fn max_duty_cycle(&self) -> u16 {
438        self.period()
439    }
440
441    /// Set the max duty of the PwmPin
442    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), core::convert::Infallible> {
443        self.set_timestamp(duty);
444        Ok(())
445    }
446}
447
448/// Two pins driven by the same timer and operator
449///
450/// Useful for complementary or mirrored signals with or without
451/// configured deadtime.
452///
453/// # H-Bridge example
454///
455/// ```rust, no_run
456#[doc = crate::before_snippet!()]
457/// # use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
458/// # use esp_hal::mcpwm::operator::{DeadTimeCfg, PwmPinConfig, PWMStream};
459/// // active high complementary using PWMA input
460/// let bridge_active = DeadTimeCfg::new_ahc();
461/// // use PWMB as input for both outputs
462/// let bridge_off = DeadTimeCfg::new_bypass().set_output_swap(PWMStream::PWMA,
463/// true);
464#[cfg_attr(
465    esp32h2,
466    doc = "let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(40))?;"
467)]
468#[cfg_attr(
469    not(esp32h2),
470    doc = "let clock_cfg = PeripheralClockConfig::with_frequency(Rate::from_mhz(32))?;"
471)]
472/// let mut mcpwm = McPwm::new(peripherals.MCPWM0, clock_cfg);
473///
474/// let mut pins = mcpwm.operator0.with_linked_pins(
475///     peripherals.GPIO0,
476///     PwmPinConfig::UP_DOWN_ACTIVE_HIGH, // use PWMA as our main input
477///     peripherals.GPIO1,
478///     PwmPinConfig::EMPTY, // keep PWMB "low"
479///     bridge_off,
480/// );
481///
482/// pins.set_falling_edge_deadtime(5);
483/// pins.set_rising_edge_deadtime(5);
484/// // pin_a: ________________________________________
485/// // pin_b: ________________________________________
486/// pins.set_timestamp_a(40); // 40% duty cycle if period configured to 100
487/// pins.set_deadtime_cfg(bridge_active);
488/// // pin_a: _______-------_____________-------______
489/// // pin_b: ------_________-----------_________-----
490/// # Ok(())
491/// # }
492/// ```
493pub struct LinkedPins<'d, PWM, const OP: u8> {
494    pin_a: PwmPin<'d, PWM, OP, true>,
495    pin_b: PwmPin<'d, PWM, OP, false>,
496}
497
498impl<'d, PWM: PwmPeripheral, const OP: u8> LinkedPins<'d, PWM, OP> {
499    fn new(
500        pin_a: impl Peripheral<P = impl PeripheralOutput> + 'd,
501        config_a: PwmPinConfig<true>,
502        pin_b: impl Peripheral<P = impl PeripheralOutput> + 'd,
503        config_b: PwmPinConfig<false>,
504        config_dt: DeadTimeCfg,
505    ) -> Self {
506        // setup deadtime config before enabling the pins
507        #[cfg(esp32s3)]
508        let dt_cfg = unsafe { Self::ch() }.db_cfg();
509        #[cfg(not(esp32s3))]
510        let dt_cfg = unsafe { Self::ch() }.dt_cfg();
511        dt_cfg.write(|w| unsafe { w.bits(config_dt.cfg_reg) });
512
513        let pin_a = PwmPin::new(pin_a, config_a);
514        let pin_b = PwmPin::new(pin_b, config_b);
515
516        LinkedPins { pin_a, pin_b }
517    }
518
519    /// Configure what actions should be taken on timing events
520    pub fn set_actions_a(&mut self, value: PwmActions<true>) {
521        self.pin_a.set_actions(value)
522    }
523    /// Configure what actions should be taken on timing events
524    pub fn set_actions_b(&mut self, value: PwmActions<false>) {
525        self.pin_b.set_actions(value)
526    }
527
528    /// Set how a new timestamp syncs with the timer
529    pub fn set_update_method_a(&mut self, update_method: PwmUpdateMethod) {
530        self.pin_a.set_update_method(update_method)
531    }
532    /// Set how a new timestamp syncs with the timer
533    pub fn set_update_method_b(&mut self, update_method: PwmUpdateMethod) {
534        self.pin_b.set_update_method(update_method)
535    }
536
537    /// Write a new timestamp.
538    /// The written value will take effect according to the set
539    /// [`PwmUpdateMethod`].
540    pub fn set_timestamp_a(&mut self, value: u16) {
541        self.pin_a.set_timestamp(value)
542    }
543    /// Write a new timestamp.
544    /// The written value will take effect according to the set
545    /// [`PwmUpdateMethod`].
546    pub fn set_timestamp_b(&mut self, value: u16) {
547        self.pin_a.set_timestamp(value)
548    }
549
550    /// Configure the deadtime generator
551    pub fn set_deadtime_cfg(&mut self, config: DeadTimeCfg) {
552        #[cfg(esp32s3)]
553        let dt_cfg = unsafe { Self::ch() }.db_cfg();
554        #[cfg(not(esp32s3))]
555        let dt_cfg = unsafe { Self::ch() }.dt_cfg();
556        dt_cfg.write(|w| unsafe { w.bits(config.cfg_reg) });
557    }
558
559    /// Set the deadtime generator rising edge delay
560    pub fn set_rising_edge_deadtime(&mut self, dead_time: u16) {
561        #[cfg(esp32s3)]
562        let dt_red = unsafe { Self::ch() }.db_red_cfg();
563        #[cfg(not(esp32s3))]
564        let dt_red = unsafe { Self::ch() }.dt_red_cfg();
565        dt_red.write(|w| unsafe { w.red().bits(dead_time) });
566    }
567    /// Set the deadtime generator falling edge delay
568    pub fn set_falling_edge_deadtime(&mut self, dead_time: u16) {
569        #[cfg(esp32s3)]
570        let dt_fed = unsafe { Self::ch() }.db_fed_cfg();
571        #[cfg(not(esp32s3))]
572        let dt_fed = unsafe { Self::ch() }.dt_fed_cfg();
573        dt_fed.write(|w| unsafe { w.fed().bits(dead_time) });
574    }
575
576    unsafe fn ch() -> &'static pac::mcpwm0::CH {
577        let block = unsafe { &*PWM::block() };
578        block.ch(OP as usize)
579    }
580}
581
582/// An action the operator applies to an output
583#[non_exhaustive]
584#[repr(u32)]
585pub enum UpdateAction {
586    /// Clear the output by setting it to a low level.
587    SetLow  = 1,
588    /// Set the output to a high level.
589    SetHigh = 2,
590    /// Change the current output level to the opposite value.
591    /// If it is currently pulled high, pull it low, or vice versa.
592    Toggle  = 3,
593}
594
595/// Settings for what actions should be taken on timing events
596///
597/// ### Note:
598/// The hardware supports using a timestamp A event to trigger an action on
599/// output B or vice versa. For clearer ownership semantics this HAL does not
600/// support such configurations.
601pub struct PwmActions<const IS_A: bool>(u32);
602
603impl<const IS_A: bool> PwmActions<IS_A> {
604    /// Using this setting together with a timer configured with
605    /// [`PwmWorkingMode::Increase`](super::timer::PwmWorkingMode::Increase)
606    /// will set the output high for a duration proportional to the set
607    /// timestamp.
608    pub const UP_ACTIVE_HIGH: Self = Self::empty()
609        .on_up_counting_timer_equals_zero(UpdateAction::SetHigh)
610        .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
611
612    /// Using this setting together with a timer configured with
613    /// [`PwmWorkingMode::UpDown`](super::timer::PwmWorkingMode::UpDown) will
614    /// set the output high for a duration proportional to the set
615    /// timestamp.
616    pub const UP_DOWN_ACTIVE_HIGH: Self = Self::empty()
617        .on_down_counting_timer_equals_timestamp(UpdateAction::SetHigh)
618        .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
619
620    /// `PwmActions` with no `UpdateAction`s set
621    pub const fn empty() -> Self {
622        PwmActions(0)
623    }
624
625    /// Choose an `UpdateAction` for an `UTEZ` event
626    pub const fn on_up_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
627        self.with_value_at_offset(action as u32, 0)
628    }
629
630    /// Choose an `UpdateAction` for an `UTEP` event
631    pub const fn on_up_counting_timer_equals_period(self, action: UpdateAction) -> Self {
632        self.with_value_at_offset(action as u32, 2)
633    }
634
635    /// Choose an `UpdateAction` for an `UTEA`/`UTEB` event
636    pub const fn on_up_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
637        match IS_A {
638            true => self.with_value_at_offset(action as u32, 4),
639            false => self.with_value_at_offset(action as u32, 6),
640        }
641    }
642
643    /// Choose an `UpdateAction` for an `UTEA`/`UTEB` event where you can
644    /// specify which of the A/B to use
645    pub const fn on_up_counting_timer_equals_ch_timestamp<const CH_A: bool>(
646        self,
647        action: UpdateAction,
648    ) -> Self {
649        match CH_A {
650            true => self.with_value_at_offset(action as u32, 4),
651            false => self.with_value_at_offset(action as u32, 6),
652        }
653    }
654
655    /// Choose an `UpdateAction` for an `DTEZ` event
656    pub const fn on_down_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
657        self.with_value_at_offset(action as u32, 12)
658    }
659
660    /// Choose an `UpdateAction` for an `DTEP` event
661    pub const fn on_down_counting_timer_equals_period(self, action: UpdateAction) -> Self {
662        self.with_value_at_offset(action as u32, 14)
663    }
664
665    /// Choose an `UpdateAction` for an `DTEA`/`DTEB` event
666    pub const fn on_down_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
667        match IS_A {
668            true => self.with_value_at_offset(action as u32, 16),
669            false => self.with_value_at_offset(action as u32, 18),
670        }
671    }
672
673    /// Choose an `UpdateAction` for an `DTEA`/`DTEB` event where you can
674    /// specify which of the A/B to use
675    pub const fn on_down_counting_timer_equals_ch_timestamp<const CH_A: bool>(
676        self,
677        action: UpdateAction,
678    ) -> Self {
679        match CH_A {
680            true => self.with_value_at_offset(action as u32, 16),
681            false => self.with_value_at_offset(action as u32, 18),
682        }
683    }
684
685    const fn with_value_at_offset(self, value: u32, offset: u32) -> Self {
686        let mask = !(0b11 << offset);
687        let value = (self.0 & mask) | (value << offset);
688        PwmActions(value)
689    }
690}
691
692/// Settings for when [`PwmPin::set_timestamp`] takes effect
693///
694/// Multiple syncing triggers can be set.
695pub struct PwmUpdateMethod(u8);
696
697impl PwmUpdateMethod {
698    /// New timestamp will be applied immediately
699    pub const SYNC_IMMEDIATLY: Self = Self::empty();
700    /// New timestamp will be applied when timer is equal to zero
701    pub const SYNC_ON_ZERO: Self = Self::empty().sync_on_timer_equals_zero();
702    /// New timestamp will be applied when timer is equal to period
703    pub const SYNC_ON_PERIOD: Self = Self::empty().sync_on_timer_equals_period();
704
705    /// `PwmUpdateMethod` with no sync triggers.
706    /// Corresponds to syncing immediately
707    pub const fn empty() -> Self {
708        PwmUpdateMethod(0)
709    }
710
711    /// Enable syncing new timestamp values when timer is equal to zero
712    pub const fn sync_on_timer_equals_zero(mut self) -> Self {
713        self.0 |= 0b0001;
714        self
715    }
716
717    /// Enable syncing new timestamp values when timer is equal to period
718    pub const fn sync_on_timer_equals_period(mut self) -> Self {
719        self.0 |= 0b0010;
720        self
721    }
722}