Skip to main content

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::{PwmClockGuard, 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 corresponding PWM
165///   timer.
166/// * Each signal out of the PWM signal pair includes a specific pattern of dead time. (Not yet
167///   implemented)
168/// * Superimposes a carrier on the PWM signal, if configured to do so. (Not yet implemented)
169/// * Handles response under fault conditions. (Not yet implemented)
170pub struct Operator<'d, const OP: u8, PWM> {
171    phantom: PhantomData<&'d PWM>,
172    _guard: PeripheralGuard,
173    _pwm_clock_guard: PwmClockGuard,
174}
175
176impl<'d, const OP: u8, PWM: PwmPeripheral> Operator<'d, OP, PWM> {
177    pub(super) fn new(guard: PeripheralGuard) -> Self {
178        // Side note:
179        // It would have been nice to deselect any timer reference on peripheral
180        // initialization.
181        // However experimentation (ESP32-S3) showed that writing `3` to timersel
182        // will not disable the timer reference but instead act as though `2` was
183        // written.
184        Operator {
185            phantom: PhantomData,
186            _guard: guard,
187            _pwm_clock_guard: PwmClockGuard::new::<PWM>(),
188        }
189    }
190
191    /// Select a [`Timer`] to be the timing reference for this operator
192    ///
193    /// ### Note:
194    /// By default TIMER0 is used
195    pub fn set_timer<const TIM: u8>(&mut self, timer: &Timer<TIM, PWM>) {
196        let _ = timer;
197        // SAFETY:
198        // We only write to our OPERATORx_TIMERSEL register
199        let block = unsafe { &*PWM::block() };
200        block.operator_timersel().modify(|_, w| match OP {
201            0 => unsafe { w.operator0_timersel().bits(TIM) },
202            1 => unsafe { w.operator1_timersel().bits(TIM) },
203            2 => unsafe { w.operator2_timersel().bits(TIM) },
204            _ => {
205                unreachable!()
206            }
207        });
208    }
209
210    /// Use the A output with the given pin and configuration
211    pub fn with_pin_a(
212        self,
213        pin: impl PeripheralOutput<'d>,
214        config: PwmPinConfig<true>,
215    ) -> PwmPin<'d, PWM, OP, true> {
216        PwmPin::new(pin, config)
217    }
218
219    /// Use the B output with the given pin and configuration
220    pub fn with_pin_b(
221        self,
222        pin: impl PeripheralOutput<'d>,
223        config: PwmPinConfig<false>,
224    ) -> PwmPin<'d, PWM, OP, false> {
225        PwmPin::new(pin, config)
226    }
227
228    /// Use both the A and the B output with the given pins and configurations
229    pub fn with_pins(
230        self,
231        pin_a: impl PeripheralOutput<'d>,
232        config_a: PwmPinConfig<true>,
233        pin_b: impl PeripheralOutput<'d>,
234        config_b: PwmPinConfig<false>,
235    ) -> (PwmPin<'d, PWM, OP, true>, PwmPin<'d, PWM, OP, false>) {
236        (PwmPin::new(pin_a, config_a), PwmPin::new(pin_b, config_b))
237    }
238
239    /// Link two pins using the deadtime generator
240    ///
241    /// This is useful for complementary or mirrored signals with or without
242    /// configured deadtime
243    pub fn with_linked_pins(
244        self,
245        pin_a: impl PeripheralOutput<'d>,
246        config_a: PwmPinConfig<true>,
247        pin_b: impl PeripheralOutput<'d>,
248        config_b: PwmPinConfig<false>,
249        config_dt: DeadTimeCfg,
250    ) -> LinkedPins<'d, PWM, OP> {
251        LinkedPins::new(pin_a, config_a, pin_b, config_b, config_dt)
252    }
253}
254
255/// Configuration describing how the operator generates a signal on a connected
256/// pin
257pub struct PwmPinConfig<const IS_A: bool> {
258    actions: PwmActions<IS_A>,
259    update_method: PwmUpdateMethod,
260}
261
262impl<const IS_A: bool> PwmPinConfig<IS_A> {
263    /// A configuration using [`PwmActions::UP_ACTIVE_HIGH`] and
264    /// [`PwmUpdateMethod::SYNC_ON_ZERO`]
265    pub const UP_ACTIVE_HIGH: Self =
266        Self::new(PwmActions::UP_ACTIVE_HIGH, PwmUpdateMethod::SYNC_ON_ZERO);
267    /// A configuration using [`PwmActions::UP_DOWN_ACTIVE_HIGH`] and
268    /// [`PwmUpdateMethod::SYNC_ON_ZERO`]
269    pub const UP_DOWN_ACTIVE_HIGH: Self = Self::new(
270        PwmActions::UP_DOWN_ACTIVE_HIGH,
271        PwmUpdateMethod::SYNC_ON_ZERO,
272    );
273    /// A configuration using [`PwmActions::empty`] and
274    /// [`PwmUpdateMethod::empty`]
275    pub const EMPTY: Self = Self::new(PwmActions::empty(), PwmUpdateMethod::empty());
276
277    /// Get a configuration using the given `PwmActions` and `PwmUpdateMethod`
278    pub const fn new(actions: PwmActions<IS_A>, update_method: PwmUpdateMethod) -> Self {
279        PwmPinConfig {
280            actions,
281            update_method,
282        }
283    }
284}
285
286/// A pin driven by an MCPWM operator
287pub struct PwmPin<'d, PWM, const OP: u8, const IS_A: bool> {
288    pin: OutputSignal<'d>,
289    phantom: PhantomData<PWM>,
290    _guard: PeripheralGuard,
291}
292
293impl<'d, PWM: PwmPeripheral, const OP: u8, const IS_A: bool> PwmPin<'d, PWM, OP, IS_A> {
294    fn new(pin: impl PeripheralOutput<'d>, config: PwmPinConfig<IS_A>) -> Self {
295        let pin = pin.into();
296
297        let guard = PeripheralGuard::new(PWM::peripheral());
298
299        let mut pin = PwmPin {
300            pin,
301            phantom: PhantomData,
302            _guard: guard,
303        };
304        pin.set_actions(config.actions);
305        pin.set_update_method(config.update_method);
306
307        PWM::output_signal::<OP, IS_A>().connect_to(&pin.pin);
308        pin.pin.set_output_enable(true);
309
310        pin
311    }
312
313    /// Configure what actions should be taken on timing events
314    pub fn set_actions(&mut self, value: PwmActions<IS_A>) {
315        // SAFETY:
316        // We only write to our GENx_x register
317        let ch = unsafe { Self::ch() };
318        let bits = value.0;
319
320        // SAFETY:
321        // `bits` is a valid bit pattern
322        ch.gen_((!IS_A) as usize).write(|w| unsafe { w.bits(bits) });
323    }
324
325    /// Set how a new timestamp syncs with the timer
326    pub fn set_update_method(&mut self, update_method: PwmUpdateMethod) {
327        // SAFETY:
328        // We only write to our GENx_x_UPMETHOD register
329        let ch = unsafe { Self::ch() };
330        let bits = update_method.0;
331
332        #[cfg(esp32s3)]
333        let cfg = ch.cmpr_cfg();
334        #[cfg(any(esp32, esp32c6, esp32h2))]
335        let cfg = ch.gen_stmp_cfg();
336
337        cfg.modify(|_, w| unsafe {
338            if IS_A {
339                w.a_upmethod().bits(bits)
340            } else {
341                w.b_upmethod().bits(bits)
342            }
343        });
344    }
345
346    /// Write a new timestamp.
347    /// The written value will take effect according to the set
348    /// [`PwmUpdateMethod`].
349    pub fn set_timestamp(&mut self, value: u16) {
350        // SAFETY:
351        // We only write to our GENx_TSTMP_x register
352        let ch = unsafe { Self::ch() };
353
354        #[cfg(esp32s3)]
355        if IS_A {
356            ch.cmpr_value0().write(|w| unsafe { w.a().bits(value) });
357        } else {
358            ch.cmpr_value1().write(|w| unsafe { w.b().bits(value) });
359        }
360
361        #[cfg(any(esp32, esp32c6, esp32h2))]
362        if IS_A {
363            ch.gen_tstmp_a().write(|w| unsafe { w.a().bits(value) });
364        } else {
365            ch.gen_tstmp_b().write(|w| unsafe { w.b().bits(value) });
366        }
367    }
368
369    /// Get the old timestamp.
370    /// The value of the timestamp will take effect according to the set
371    /// [`PwmUpdateMethod`].
372    pub fn timestamp(&self) -> u16 {
373        // SAFETY:
374        // We only read to our GENx_TSTMP_x register
375        let ch = unsafe { Self::ch() };
376
377        #[cfg(esp32s3)]
378        if IS_A {
379            ch.cmpr_value0().read().a().bits()
380        } else {
381            ch.cmpr_value1().read().b().bits()
382        }
383
384        #[cfg(any(esp32, esp32c6, esp32h2))]
385        if IS_A {
386            ch.gen_tstmp_a().read().a().bits()
387        } else {
388            ch.gen_tstmp_b().read().b().bits()
389        }
390    }
391
392    /// Get the period of the timer.
393    pub fn period(&self) -> u16 {
394        // SAFETY:
395        // We only grant access to our CFG0 register with the lifetime of &mut self
396        let block = unsafe { &*PWM::block() };
397
398        let tim_select = block.operator_timersel().read();
399        let tim = match OP {
400            0 => tim_select.operator0_timersel().bits(),
401            1 => tim_select.operator1_timersel().bits(),
402            2 => tim_select.operator2_timersel().bits(),
403            _ => {
404                unreachable!()
405            }
406        };
407
408        // SAFETY:
409        // The CFG0 registers are identical for all timers so we can pretend they're
410        // TIMER0_CFG0
411        block.timer(tim as usize).cfg0().read().period().bits()
412    }
413
414    unsafe fn ch() -> &'static pac::mcpwm0::CH {
415        let block = unsafe { &*PWM::block() };
416        block.ch(OP as usize)
417    }
418}
419
420/// Implement no error type for the PwmPin because the method are infallible
421impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::ErrorType
422    for PwmPin<'_, PWM, OP, IS_A>
423{
424    type Error = core::convert::Infallible;
425}
426
427/// Implement the trait SetDutyCycle for PwmPin
428impl<PWM: PwmPeripheral, const OP: u8, const IS_A: bool> embedded_hal::pwm::SetDutyCycle
429    for PwmPin<'_, PWM, OP, IS_A>
430{
431    /// Get the max duty of the PwmPin
432    fn max_duty_cycle(&self) -> u16 {
433        self.period()
434    }
435
436    /// Set the max duty of the PwmPin
437    fn set_duty_cycle(&mut self, duty: u16) -> Result<(), core::convert::Infallible> {
438        self.set_timestamp(duty);
439        Ok(())
440    }
441}
442
443#[procmacros::doc_replace(
444    "mcpwm_clk" => {
445        cfg(not(esp32h2)) => "40",
446        cfg(esp32h2) => "32"
447    }
448)]
449/// Two pins driven by the same timer and operator
450///
451/// Useful for complementary or mirrored signals with or without
452/// configured deadtime.
453///
454/// # H-Bridge example
455///
456/// ```rust, no_run
457/// # {before_snippet}
458/// # use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
459/// # use esp_hal::mcpwm::operator::{DeadTimeCfg, PwmPinConfig, PWMStream};
460/// // active high complementary using PWMA input
461/// let bridge_active = DeadTimeCfg::new_ahc();
462///
463/// // use PWMB as input for both outputs
464/// let bridge_off = DeadTimeCfg::new_bypass().set_output_swap(PWMStream::PWMA, true);
465///
466/// let mut mcpwm = McPwm::new(
467///     peripherals.MCPWM0,
468///     PeripheralClockConfig::with_frequency(Rate::from_mhz(__mcpwm_clk__))?,
469/// );
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/// # {after_snippet}
488/// ```
489pub struct LinkedPins<'d, PWM, const OP: u8> {
490    pin_a: PwmPin<'d, PWM, OP, true>,
491    pin_b: PwmPin<'d, PWM, OP, false>,
492}
493
494impl<'d, PWM: PwmPeripheral, const OP: u8> LinkedPins<'d, PWM, OP> {
495    fn new(
496        pin_a: impl PeripheralOutput<'d>,
497        config_a: PwmPinConfig<true>,
498        pin_b: impl PeripheralOutput<'d>,
499        config_b: PwmPinConfig<false>,
500        config_dt: DeadTimeCfg,
501    ) -> Self {
502        // setup deadtime config before enabling the pins
503        #[cfg(esp32s3)]
504        let dt_cfg = unsafe { Self::ch() }.db_cfg();
505        #[cfg(not(esp32s3))]
506        let dt_cfg = unsafe { Self::ch() }.dt_cfg();
507        dt_cfg.write(|w| unsafe { w.bits(config_dt.cfg_reg) });
508
509        let pin_a = PwmPin::new(pin_a, config_a);
510        let pin_b = PwmPin::new(pin_b, config_b);
511
512        LinkedPins { pin_a, pin_b }
513    }
514
515    /// Configure what actions should be taken on timing events
516    pub fn set_actions_a(&mut self, value: PwmActions<true>) {
517        self.pin_a.set_actions(value)
518    }
519    /// Configure what actions should be taken on timing events
520    pub fn set_actions_b(&mut self, value: PwmActions<false>) {
521        self.pin_b.set_actions(value)
522    }
523
524    /// Set how a new timestamp syncs with the timer
525    pub fn set_update_method_a(&mut self, update_method: PwmUpdateMethod) {
526        self.pin_a.set_update_method(update_method)
527    }
528    /// Set how a new timestamp syncs with the timer
529    pub fn set_update_method_b(&mut self, update_method: PwmUpdateMethod) {
530        self.pin_b.set_update_method(update_method)
531    }
532
533    /// Write a new timestamp.
534    /// The written value will take effect according to the set
535    /// [`PwmUpdateMethod`].
536    pub fn set_timestamp_a(&mut self, value: u16) {
537        self.pin_a.set_timestamp(value)
538    }
539    /// Write a new timestamp.
540    /// The written value will take effect according to the set
541    /// [`PwmUpdateMethod`].
542    pub fn set_timestamp_b(&mut self, value: u16) {
543        self.pin_a.set_timestamp(value)
544    }
545
546    /// Configure the deadtime generator
547    pub fn set_deadtime_cfg(&mut self, config: DeadTimeCfg) {
548        #[cfg(esp32s3)]
549        let dt_cfg = unsafe { Self::ch() }.db_cfg();
550        #[cfg(not(esp32s3))]
551        let dt_cfg = unsafe { Self::ch() }.dt_cfg();
552        dt_cfg.write(|w| unsafe { w.bits(config.cfg_reg) });
553    }
554
555    /// Set the deadtime generator rising edge delay
556    pub fn set_rising_edge_deadtime(&mut self, dead_time: u16) {
557        #[cfg(esp32s3)]
558        let dt_red = unsafe { Self::ch() }.db_red_cfg();
559        #[cfg(not(esp32s3))]
560        let dt_red = unsafe { Self::ch() }.dt_red_cfg();
561        dt_red.write(|w| unsafe { w.red().bits(dead_time) });
562    }
563    /// Set the deadtime generator falling edge delay
564    pub fn set_falling_edge_deadtime(&mut self, dead_time: u16) {
565        #[cfg(esp32s3)]
566        let dt_fed = unsafe { Self::ch() }.db_fed_cfg();
567        #[cfg(not(esp32s3))]
568        let dt_fed = unsafe { Self::ch() }.dt_fed_cfg();
569        dt_fed.write(|w| unsafe { w.fed().bits(dead_time) });
570    }
571
572    unsafe fn ch() -> &'static pac::mcpwm0::CH {
573        let block = unsafe { &*PWM::block() };
574        block.ch(OP as usize)
575    }
576}
577
578/// An action the operator applies to an output
579#[non_exhaustive]
580#[repr(u32)]
581pub enum UpdateAction {
582    /// Clear the output by setting it to a low level.
583    SetLow  = 1,
584    /// Set the output to a high level.
585    SetHigh = 2,
586    /// Change the current output level to the opposite value.
587    /// If it is currently pulled high, pull it low, or vice versa.
588    Toggle  = 3,
589}
590
591/// Settings for what actions should be taken on timing events
592///
593/// ### Note:
594/// The hardware supports using a timestamp A event to trigger an action on
595/// output B or vice versa. For clearer ownership semantics this HAL does not
596/// support such configurations.
597pub struct PwmActions<const IS_A: bool>(u32);
598
599impl<const IS_A: bool> PwmActions<IS_A> {
600    /// Using this setting together with a timer configured with
601    /// [`PwmWorkingMode::Increase`](super::timer::PwmWorkingMode::Increase)
602    /// will set the output high for a duration proportional to the set
603    /// timestamp.
604    pub const UP_ACTIVE_HIGH: Self = Self::empty()
605        .on_up_counting_timer_equals_zero(UpdateAction::SetHigh)
606        .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
607
608    /// Using this setting together with a timer configured with
609    /// [`PwmWorkingMode::UpDown`](super::timer::PwmWorkingMode::UpDown) will
610    /// set the output high for a duration proportional to the set
611    /// timestamp.
612    pub const UP_DOWN_ACTIVE_HIGH: Self = Self::empty()
613        .on_down_counting_timer_equals_timestamp(UpdateAction::SetHigh)
614        .on_up_counting_timer_equals_timestamp(UpdateAction::SetLow);
615
616    /// `PwmActions` with no `UpdateAction`s set
617    pub const fn empty() -> Self {
618        PwmActions(0)
619    }
620
621    /// Choose an `UpdateAction` for an `UTEZ` event
622    pub const fn on_up_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
623        self.with_value_at_offset(action as u32, 0)
624    }
625
626    /// Choose an `UpdateAction` for an `UTEP` event
627    pub const fn on_up_counting_timer_equals_period(self, action: UpdateAction) -> Self {
628        self.with_value_at_offset(action as u32, 2)
629    }
630
631    /// Choose an `UpdateAction` for an `UTEA`/`UTEB` event
632    pub const fn on_up_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
633        match IS_A {
634            true => self.with_value_at_offset(action as u32, 4),
635            false => self.with_value_at_offset(action as u32, 6),
636        }
637    }
638
639    /// Choose an `UpdateAction` for an `UTEA`/`UTEB` event where you can
640    /// specify which of the A/B to use
641    pub const fn on_up_counting_timer_equals_ch_timestamp<const CH_A: bool>(
642        self,
643        action: UpdateAction,
644    ) -> Self {
645        match CH_A {
646            true => self.with_value_at_offset(action as u32, 4),
647            false => self.with_value_at_offset(action as u32, 6),
648        }
649    }
650
651    /// Choose an `UpdateAction` for an `DTEZ` event
652    pub const fn on_down_counting_timer_equals_zero(self, action: UpdateAction) -> Self {
653        self.with_value_at_offset(action as u32, 12)
654    }
655
656    /// Choose an `UpdateAction` for an `DTEP` event
657    pub const fn on_down_counting_timer_equals_period(self, action: UpdateAction) -> Self {
658        self.with_value_at_offset(action as u32, 14)
659    }
660
661    /// Choose an `UpdateAction` for an `DTEA`/`DTEB` event
662    pub const fn on_down_counting_timer_equals_timestamp(self, action: UpdateAction) -> Self {
663        match IS_A {
664            true => self.with_value_at_offset(action as u32, 16),
665            false => self.with_value_at_offset(action as u32, 18),
666        }
667    }
668
669    /// Choose an `UpdateAction` for an `DTEA`/`DTEB` event where you can
670    /// specify which of the A/B to use
671    pub const fn on_down_counting_timer_equals_ch_timestamp<const CH_A: bool>(
672        self,
673        action: UpdateAction,
674    ) -> Self {
675        match CH_A {
676            true => self.with_value_at_offset(action as u32, 16),
677            false => self.with_value_at_offset(action as u32, 18),
678        }
679    }
680
681    const fn with_value_at_offset(self, value: u32, offset: u32) -> Self {
682        let mask = !(0b11 << offset);
683        let value = (self.0 & mask) | (value << offset);
684        PwmActions(value)
685    }
686}
687
688/// Settings for when [`PwmPin::set_timestamp`] takes effect
689///
690/// Multiple syncing triggers can be set.
691pub struct PwmUpdateMethod(u8);
692
693impl PwmUpdateMethod {
694    /// New timestamp will be applied immediately
695    pub const SYNC_IMMEDIATLY: Self = Self::empty();
696    /// New timestamp will be applied when timer is equal to zero
697    pub const SYNC_ON_ZERO: Self = Self::empty().sync_on_timer_equals_zero();
698    /// New timestamp will be applied when timer is equal to period
699    pub const SYNC_ON_PERIOD: Self = Self::empty().sync_on_timer_equals_period();
700
701    /// `PwmUpdateMethod` with no sync triggers.
702    /// Corresponds to syncing immediately
703    pub const fn empty() -> Self {
704        PwmUpdateMethod(0)
705    }
706
707    /// Enable syncing new timestamp values when timer is equal to zero
708    pub const fn sync_on_timer_equals_zero(mut self) -> Self {
709        self.0 |= 0b0001;
710        self
711    }
712
713    /// Enable syncing new timestamp values when timer is equal to period
714    pub const fn sync_on_timer_equals_period(mut self) -> Self {
715        self.0 |= 0b0010;
716        self
717    }
718}