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