esp_hal/ledc/
channel.rs

1//! # LEDC channel
2//!
3//! ## Overview
4//! The LEDC Channel module  provides a high-level interface to
5//! configure and control individual PWM channels of the LEDC peripheral.
6//!
7//! ## Configuration
8//! The module allows precise and flexible control over LED lighting and other
9//! `Pulse-Width Modulation (PWM)` applications by offering configurable duty
10//! cycles and frequencies.
11
12use super::timer::{TimerIFace, TimerSpeed};
13use crate::{
14    gpio::{
15        interconnect::{OutputConnection, PeripheralOutput},
16        OutputSignal,
17    },
18    pac::ledc::RegisterBlock,
19    peripheral::{Peripheral, PeripheralRef},
20    peripherals::LEDC,
21};
22
23/// Fade parameter sub-errors
24#[derive(Debug, Clone, Copy, PartialEq)]
25#[cfg_attr(feature = "defmt", derive(defmt::Format))]
26pub enum FadeError {
27    /// Start duty % out of range
28    StartDuty,
29    /// End duty % out of range
30    EndDuty,
31    /// Duty % change from start to end is out of range
32    DutyRange,
33    /// Duration too long for timer frequency and duty resolution
34    Duration,
35}
36
37/// Channel errors
38#[derive(Debug, Clone, Copy, PartialEq)]
39#[cfg_attr(feature = "defmt", derive(defmt::Format))]
40pub enum Error {
41    /// Invalid duty % value
42    Duty,
43    /// Timer not configured
44    Timer,
45    /// Channel not configured
46    Channel,
47    /// Fade parameters invalid
48    Fade(FadeError),
49}
50
51/// Channel number
52#[derive(PartialEq, Eq, Copy, Clone, Debug)]
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
54pub enum Number {
55    /// Channel 0
56    Channel0 = 0,
57    /// Channel 1
58    Channel1 = 1,
59    /// Channel 2
60    Channel2 = 2,
61    /// Channel 3
62    Channel3 = 3,
63    /// Channel 4
64    Channel4 = 4,
65    /// Channel 5
66    Channel5 = 5,
67    #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))]
68    /// Channel 6
69    Channel6 = 6,
70    #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))]
71    /// Channel 7
72    Channel7 = 7,
73}
74
75/// Channel configuration
76pub mod config {
77    use crate::ledc::timer::{TimerIFace, TimerSpeed};
78
79    #[derive(Debug, Clone, Copy, PartialEq)]
80    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
81    /// Pin configuration for the LEDC channel.
82    pub enum PinConfig {
83        /// Push-pull pin configuration.
84        PushPull,
85        /// Open-drain pin configuration.
86        OpenDrain,
87    }
88
89    /// Channel configuration
90    #[derive(Copy, Clone)]
91    pub struct Config<'a, S: TimerSpeed> {
92        /// A reference to the timer associated with this channel.
93        pub timer: &'a dyn TimerIFace<S>,
94        /// The duty cycle percentage (0-100).
95        pub duty_pct: u8,
96        /// The pin configuration (PushPull or OpenDrain).
97        pub pin_config: PinConfig,
98    }
99}
100
101/// Channel interface
102pub trait ChannelIFace<'a, S: TimerSpeed + 'a>
103where
104    Channel<'a, S>: ChannelHW,
105{
106    /// Configure channel
107    fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error>;
108
109    /// Set channel duty HW
110    fn set_duty(&self, duty_pct: u8) -> Result<(), Error>;
111
112    /// Start a duty-cycle fade
113    fn start_duty_fade(
114        &self,
115        start_duty_pct: u8,
116        end_duty_pct: u8,
117        duration_ms: u16,
118    ) -> Result<(), Error>;
119
120    /// Check whether a duty-cycle fade is running
121    fn is_duty_fade_running(&self) -> bool;
122}
123
124/// Channel HW interface
125pub trait ChannelHW {
126    /// Configure Channel HW except for the duty which is set via
127    /// [`Self::set_duty_hw`].
128    fn configure_hw(&mut self) -> Result<(), Error>;
129    /// Configure the hardware for the channel with a specific pin
130    /// configuration.
131    fn configure_hw_with_pin_config(&mut self, cfg: config::PinConfig) -> Result<(), Error>;
132
133    /// Set channel duty HW
134    fn set_duty_hw(&self, duty: u32);
135
136    /// Start a duty-cycle fade HW
137    fn start_duty_fade_hw(
138        &self,
139        start_duty: u32,
140        duty_inc: bool,
141        duty_steps: u16,
142        cycles_per_step: u16,
143        duty_per_cycle: u16,
144    );
145
146    /// Check whether a duty-cycle fade is running HW
147    fn is_duty_fade_running_hw(&self) -> bool;
148}
149
150/// Channel struct
151pub struct Channel<'a, S: TimerSpeed> {
152    ledc: &'a RegisterBlock,
153    timer: Option<&'a dyn TimerIFace<S>>,
154    number: Number,
155    output_pin: PeripheralRef<'a, OutputConnection>,
156}
157
158impl<'a, S: TimerSpeed> Channel<'a, S> {
159    /// Return a new channel
160    pub fn new(
161        number: Number,
162        output_pin: impl Peripheral<P = impl PeripheralOutput> + 'a,
163    ) -> Self {
164        crate::into_mapped_ref!(output_pin);
165        let ledc = LEDC::regs();
166        Channel {
167            ledc,
168            timer: None,
169            number,
170            output_pin,
171        }
172    }
173}
174
175impl<'a, S: TimerSpeed> ChannelIFace<'a, S> for Channel<'a, S>
176where
177    Channel<'a, S>: ChannelHW,
178{
179    /// Configure channel
180    fn configure(&mut self, config: config::Config<'a, S>) -> Result<(), Error> {
181        self.timer = Some(config.timer);
182
183        self.set_duty(config.duty_pct)?;
184        self.configure_hw_with_pin_config(config.pin_config)?;
185
186        Ok(())
187    }
188
189    /// Set duty % of channel
190    fn set_duty(&self, duty_pct: u8) -> Result<(), Error> {
191        let duty_exp;
192        if let Some(timer) = self.timer {
193            if let Some(timer_duty) = timer.duty() {
194                duty_exp = timer_duty as u32;
195            } else {
196                return Err(Error::Timer);
197            }
198        } else {
199            return Err(Error::Channel);
200        }
201
202        let duty_range = 2u32.pow(duty_exp);
203        let duty_value = (duty_range * duty_pct as u32) / 100;
204
205        if duty_pct > 100u8 {
206            // duty_pct greater than 100%
207            return Err(Error::Duty);
208        }
209
210        self.set_duty_hw(duty_value);
211
212        Ok(())
213    }
214
215    /// Start a duty fade from one % to another.
216    ///
217    /// There's a constraint on the combination of timer frequency, timer PWM
218    /// duty resolution (the bit count), the fade "range" (abs(start-end)), and
219    /// the duration:
220    ///
221    /// frequency * duration / ((1<<bit_count) * abs(start-end)) < 1024
222    ///
223    /// Small percentage changes, long durations, coarse PWM resolutions (that
224    /// is, low bit counts), and high timer frequencies will all be more likely
225    /// to fail this requirement.  If it does fail, this function will return
226    /// an error Result.
227    fn start_duty_fade(
228        &self,
229        start_duty_pct: u8,
230        end_duty_pct: u8,
231        duration_ms: u16,
232    ) -> Result<(), Error> {
233        let duty_exp;
234        let frequency;
235        if start_duty_pct > 100u8 {
236            return Err(Error::Fade(FadeError::StartDuty));
237        }
238        if end_duty_pct > 100u8 {
239            return Err(Error::Fade(FadeError::EndDuty));
240        }
241        if let Some(timer) = self.timer {
242            if let Some(timer_duty) = timer.duty() {
243                if timer.frequency() > 0 {
244                    duty_exp = timer_duty as u32;
245                    frequency = timer.frequency();
246                } else {
247                    return Err(Error::Timer);
248                }
249            } else {
250                return Err(Error::Timer);
251            }
252        } else {
253            return Err(Error::Channel);
254        }
255
256        let duty_range = (1u32 << duty_exp) - 1;
257        let start_duty_value = (duty_range * start_duty_pct as u32) / 100;
258        let end_duty_value = (duty_range * end_duty_pct as u32) / 100;
259
260        // NB: since we do the multiplication first here, there's no loss of
261        // precision from using milliseconds instead of (e.g.) nanoseconds.
262        let pwm_cycles = (duration_ms as u32) * frequency / 1000;
263
264        let abs_duty_diff = end_duty_value.abs_diff(start_duty_value);
265        let duty_steps: u32 = u16::try_from(abs_duty_diff).unwrap_or(65535).into();
266        // This conversion may fail if duration_ms is too big, and if either
267        // duty_steps gets truncated, or the fade is over a short range of duty
268        // percentages, so it's too small.  Returning an Err in either case is
269        // fine: shortening the duration_ms will sort things out.
270        let cycles_per_step: u16 = (pwm_cycles / duty_steps)
271            .try_into()
272            .map_err(|_| Error::Fade(FadeError::Duration))
273            .and_then(|res| {
274                if res > 1023 {
275                    Err(Error::Fade(FadeError::Duration))
276                } else {
277                    Ok(res)
278                }
279            })?;
280        // This can't fail unless abs_duty_diff is bigger than 65536*65535-1,
281        // and so duty_steps gets truncated.  But that requires duty_exp to be
282        // at least 32, and the hardware only supports up to 20.  Still, handle
283        // it in case something changes in the future.
284        let duty_per_cycle: u16 = (abs_duty_diff / duty_steps)
285            .try_into()
286            .map_err(|_| Error::Fade(FadeError::DutyRange))?;
287
288        self.start_duty_fade_hw(
289            start_duty_value,
290            end_duty_value > start_duty_value,
291            duty_steps.try_into().unwrap(),
292            cycles_per_step,
293            duty_per_cycle,
294        );
295
296        Ok(())
297    }
298
299    fn is_duty_fade_running(&self) -> bool {
300        self.is_duty_fade_running_hw()
301    }
302}
303
304mod ehal1 {
305    use embedded_hal::pwm::{self, ErrorKind, ErrorType, SetDutyCycle};
306
307    use super::{Channel, ChannelHW, Error};
308    use crate::ledc::timer::TimerSpeed;
309
310    impl pwm::Error for Error {
311        fn kind(&self) -> pwm::ErrorKind {
312            ErrorKind::Other
313        }
314    }
315
316    impl<S: TimerSpeed> ErrorType for Channel<'_, S> {
317        type Error = Error;
318    }
319
320    impl<'a, S: TimerSpeed> SetDutyCycle for Channel<'a, S>
321    where
322        Channel<'a, S>: ChannelHW,
323    {
324        fn max_duty_cycle(&self) -> u16 {
325            let duty_exp;
326
327            if let Some(timer_duty) = self.timer.and_then(|timer| timer.duty()) {
328                duty_exp = timer_duty as u32;
329            } else {
330                return 0;
331            }
332
333            let duty_range = 2u32.pow(duty_exp);
334
335            duty_range as u16
336        }
337
338        fn set_duty_cycle(&mut self, mut duty: u16) -> Result<(), Self::Error> {
339            let max = self.max_duty_cycle();
340            duty = if duty > max { max } else { duty };
341            self.set_duty_hw(duty.into());
342            Ok(())
343        }
344    }
345}
346
347impl<S: crate::ledc::timer::TimerSpeed> Channel<'_, S> {
348    #[cfg(esp32)]
349    fn set_channel(&mut self, timer_number: u8) {
350        if S::IS_HS {
351            let ch = self.ledc.hsch(self.number as usize);
352            ch.hpoint().write(|w| unsafe { w.hpoint().bits(0x0) });
353            ch.conf0()
354                .modify(|_, w| unsafe { w.sig_out_en().set_bit().timer_sel().bits(timer_number) });
355        } else {
356            let ch = self.ledc.lsch(self.number as usize);
357            ch.hpoint().write(|w| unsafe { w.hpoint().bits(0x0) });
358            ch.conf0()
359                .modify(|_, w| unsafe { w.sig_out_en().set_bit().timer_sel().bits(timer_number) });
360        }
361        self.start_duty_without_fading();
362    }
363    #[cfg(not(esp32))]
364    fn set_channel(&mut self, timer_number: u8) {
365        {
366            let ch = self.ledc.ch(self.number as usize);
367            ch.hpoint().write(|w| unsafe { w.hpoint().bits(0x0) });
368            ch.conf0().modify(|_, w| {
369                w.sig_out_en().set_bit();
370                unsafe { w.timer_sel().bits(timer_number) }
371            });
372        }
373
374        // this is needed to make low duty-resolutions / high frequencies work
375        #[cfg(any(esp32h2, esp32c6))]
376        self.ledc
377            .ch_gamma_wr_addr(self.number as usize)
378            .write(|w| unsafe { w.bits(0) });
379
380        self.start_duty_without_fading();
381    }
382
383    #[cfg(esp32)]
384    fn start_duty_without_fading(&self) {
385        if S::IS_HS {
386            self.ledc
387                .hsch(self.number as usize)
388                .conf1()
389                .write(|w| unsafe {
390                    w.duty_start().set_bit();
391                    w.duty_inc().set_bit();
392                    w.duty_num().bits(0x1);
393                    w.duty_cycle().bits(0x1);
394                    w.duty_scale().bits(0x0)
395                });
396        } else {
397            self.ledc
398                .lsch(self.number as usize)
399                .conf1()
400                .write(|w| unsafe {
401                    w.duty_start().set_bit();
402                    w.duty_inc().set_bit();
403                    w.duty_num().bits(0x1);
404                    w.duty_cycle().bits(0x1);
405                    w.duty_scale().bits(0x0)
406                });
407        }
408    }
409    #[cfg(any(esp32c6, esp32h2))]
410    fn start_duty_without_fading(&self) {
411        let cnum = self.number as usize;
412        self.ledc
413            .ch(cnum)
414            .conf1()
415            .write(|w| w.duty_start().set_bit());
416        self.ledc.ch_gamma_wr(cnum).write(|w| {
417            w.ch_gamma_duty_inc().set_bit();
418            unsafe {
419                w.ch_gamma_duty_num().bits(0x1);
420                w.ch_gamma_duty_cycle().bits(0x1);
421                w.ch_gamma_scale().bits(0x0)
422            }
423        });
424    }
425    #[cfg(not(any(esp32, esp32c6, esp32h2)))]
426    fn start_duty_without_fading(&self) {
427        self.ledc.ch(self.number as usize).conf1().write(|w| {
428            w.duty_start().set_bit();
429            w.duty_inc().set_bit();
430            unsafe {
431                w.duty_num().bits(0x1);
432                w.duty_cycle().bits(0x1);
433                w.duty_scale().bits(0x0)
434            }
435        });
436    }
437
438    #[cfg(esp32)]
439    fn start_duty_fade_inner(
440        &self,
441        duty_inc: bool,
442        duty_steps: u16,
443        cycles_per_step: u16,
444        duty_per_cycle: u16,
445    ) {
446        if S::IS_HS {
447            self.ledc
448                .hsch(self.number as usize)
449                .conf1()
450                .write(|w| unsafe {
451                    w.duty_start()
452                        .set_bit()
453                        .duty_inc()
454                        .variant(duty_inc)
455                        .duty_num() // count of incs before stopping
456                        .bits(duty_steps)
457                        .duty_cycle() // overflows between incs
458                        .bits(cycles_per_step)
459                        .duty_scale()
460                        .bits(duty_per_cycle)
461                });
462        } else {
463            self.ledc
464                .lsch(self.number as usize)
465                .conf1()
466                .write(|w| unsafe {
467                    w.duty_start()
468                        .set_bit()
469                        .duty_inc()
470                        .variant(duty_inc)
471                        .duty_num() // count of incs before stopping
472                        .bits(duty_steps)
473                        .duty_cycle() // overflows between incs
474                        .bits(cycles_per_step)
475                        .duty_scale()
476                        .bits(duty_per_cycle)
477                });
478        }
479    }
480
481    #[cfg(any(esp32c6, esp32h2))]
482    fn start_duty_fade_inner(
483        &self,
484        duty_inc: bool,
485        duty_steps: u16,
486        cycles_per_step: u16,
487        duty_per_cycle: u16,
488    ) {
489        let cnum = self.number as usize;
490        self.ledc
491            .ch(cnum)
492            .conf1()
493            .write(|w| w.duty_start().set_bit());
494        self.ledc.ch_gamma_wr(cnum).write(|w| unsafe {
495            w.ch_gamma_duty_inc()
496                .variant(duty_inc)
497                .ch_gamma_duty_num() // count of incs before stopping
498                .bits(duty_steps)
499                .ch_gamma_duty_cycle() // overflows between incs
500                .bits(cycles_per_step)
501                .ch_gamma_scale()
502                .bits(duty_per_cycle)
503        });
504        self.ledc
505            .ch_gamma_wr_addr(cnum)
506            .write(|w| unsafe { w.ch_gamma_wr_addr().bits(0) });
507        self.ledc
508            .ch_gamma_conf(cnum)
509            .write(|w| unsafe { w.ch_gamma_entry_num().bits(0x1) });
510    }
511
512    #[cfg(not(any(esp32, esp32c6, esp32h2)))]
513    fn start_duty_fade_inner(
514        &self,
515        duty_inc: bool,
516        duty_steps: u16,
517        cycles_per_step: u16,
518        duty_per_cycle: u16,
519    ) {
520        self.ledc
521            .ch(self.number as usize)
522            .conf1()
523            .write(|w| unsafe {
524                w.duty_start().set_bit();
525                w.duty_inc().variant(duty_inc);
526                // count of incs before stopping
527                w.duty_num().bits(duty_steps);
528                // overflows between incs
529                w.duty_cycle().bits(cycles_per_step);
530                w.duty_scale().bits(duty_per_cycle)
531            });
532    }
533
534    #[cfg(esp32)]
535    fn update_channel(&self) {
536        if !S::IS_HS {
537            self.ledc
538                .lsch(self.number as usize)
539                .conf0()
540                .modify(|_, w| w.para_up().set_bit());
541        }
542    }
543    #[cfg(not(esp32))]
544    fn update_channel(&self) {
545        self.ledc
546            .ch(self.number as usize)
547            .conf0()
548            .modify(|_, w| w.para_up().set_bit());
549    }
550}
551
552impl<S> ChannelHW for Channel<'_, S>
553where
554    S: crate::ledc::timer::TimerSpeed,
555{
556    /// Configure Channel HW
557    fn configure_hw(&mut self) -> Result<(), Error> {
558        self.configure_hw_with_pin_config(config::PinConfig::PushPull)
559    }
560    fn configure_hw_with_pin_config(&mut self, cfg: config::PinConfig) -> Result<(), Error> {
561        if let Some(timer) = self.timer {
562            if !timer.is_configured() {
563                return Err(Error::Timer);
564            }
565
566            match cfg {
567                config::PinConfig::PushPull => self.output_pin.set_to_push_pull_output(),
568                config::PinConfig::OpenDrain => self.output_pin.set_to_open_drain_output(),
569            };
570
571            let timer_number = timer.number() as u8;
572
573            self.set_channel(timer_number);
574            self.update_channel();
575
576            #[cfg(esp32)]
577            let signal = if S::IS_HS {
578                #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))]
579                match self.number {
580                    Number::Channel0 => OutputSignal::LEDC_HS_SIG0,
581                    Number::Channel1 => OutputSignal::LEDC_HS_SIG1,
582                    Number::Channel2 => OutputSignal::LEDC_HS_SIG2,
583                    Number::Channel3 => OutputSignal::LEDC_HS_SIG3,
584                    Number::Channel4 => OutputSignal::LEDC_HS_SIG4,
585                    Number::Channel5 => OutputSignal::LEDC_HS_SIG5,
586                    Number::Channel6 => OutputSignal::LEDC_HS_SIG6,
587                    Number::Channel7 => OutputSignal::LEDC_HS_SIG7,
588                }
589            } else {
590                match self.number {
591                    Number::Channel0 => OutputSignal::LEDC_LS_SIG0,
592                    Number::Channel1 => OutputSignal::LEDC_LS_SIG1,
593                    Number::Channel2 => OutputSignal::LEDC_LS_SIG2,
594                    Number::Channel3 => OutputSignal::LEDC_LS_SIG3,
595                    Number::Channel4 => OutputSignal::LEDC_LS_SIG4,
596                    Number::Channel5 => OutputSignal::LEDC_LS_SIG5,
597                    #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))]
598                    Number::Channel6 => OutputSignal::LEDC_LS_SIG6,
599                    #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))]
600                    Number::Channel7 => OutputSignal::LEDC_LS_SIG7,
601                }
602            };
603            #[cfg(not(esp32))]
604            let signal = match self.number {
605                Number::Channel0 => OutputSignal::LEDC_LS_SIG0,
606                Number::Channel1 => OutputSignal::LEDC_LS_SIG1,
607                Number::Channel2 => OutputSignal::LEDC_LS_SIG2,
608                Number::Channel3 => OutputSignal::LEDC_LS_SIG3,
609                Number::Channel4 => OutputSignal::LEDC_LS_SIG4,
610                Number::Channel5 => OutputSignal::LEDC_LS_SIG5,
611                #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))]
612                Number::Channel6 => OutputSignal::LEDC_LS_SIG6,
613                #[cfg(not(any(esp32c2, esp32c3, esp32c6, esp32h2)))]
614                Number::Channel7 => OutputSignal::LEDC_LS_SIG7,
615            };
616
617            signal.connect_to(&mut self.output_pin);
618        } else {
619            return Err(Error::Timer);
620        }
621
622        Ok(())
623    }
624
625    /// Set duty in channel HW
626    #[cfg(esp32)]
627    fn set_duty_hw(&self, duty: u32) {
628        if S::IS_HS {
629            self.ledc
630                .hsch(self.number as usize)
631                .duty()
632                .write(|w| unsafe { w.duty().bits(duty << 4) });
633        } else {
634            self.ledc
635                .lsch(self.number as usize)
636                .duty()
637                .write(|w| unsafe { w.duty().bits(duty << 4) });
638        }
639        self.start_duty_without_fading();
640        self.update_channel();
641    }
642
643    /// Set duty in channel HW
644    #[cfg(not(esp32))]
645    fn set_duty_hw(&self, duty: u32) {
646        self.ledc
647            .ch(self.number as usize)
648            .duty()
649            .write(|w| unsafe { w.duty().bits(duty << 4) });
650        self.start_duty_without_fading();
651        self.update_channel();
652    }
653
654    /// Start a duty-cycle fade HW
655    #[cfg(esp32)]
656    fn start_duty_fade_hw(
657        &self,
658        start_duty: u32,
659        duty_inc: bool,
660        duty_steps: u16,
661        cycles_per_step: u16,
662        duty_per_cycle: u16,
663    ) {
664        if S::IS_HS {
665            self.ledc
666                .hsch(self.number as usize)
667                .duty()
668                .write(|w| unsafe { w.duty().bits(start_duty << 4) });
669            self.ledc
670                .int_clr()
671                .write(|w| w.duty_chng_end_hsch(self.number as u8).clear_bit_by_one());
672        } else {
673            self.ledc
674                .lsch(self.number as usize)
675                .duty()
676                .write(|w| unsafe { w.duty().bits(start_duty << 4) });
677            self.ledc
678                .int_clr()
679                .write(|w| w.duty_chng_end_lsch(self.number as u8).clear_bit_by_one());
680        }
681        self.start_duty_fade_inner(duty_inc, duty_steps, cycles_per_step, duty_per_cycle);
682        self.update_channel();
683    }
684
685    /// Start a duty-cycle fade HW
686    #[cfg(not(esp32))]
687    fn start_duty_fade_hw(
688        &self,
689        start_duty: u32,
690        duty_inc: bool,
691        duty_steps: u16,
692        cycles_per_step: u16,
693        duty_per_cycle: u16,
694    ) {
695        self.ledc
696            .ch(self.number as usize)
697            .duty()
698            .write(|w| unsafe { w.duty().bits(start_duty << 4) });
699        self.ledc
700            .int_clr()
701            .write(|w| w.duty_chng_end_ch(self.number as u8).clear_bit_by_one());
702        self.start_duty_fade_inner(duty_inc, duty_steps, cycles_per_step, duty_per_cycle);
703        self.update_channel();
704    }
705
706    #[cfg(esp32)]
707    fn is_duty_fade_running_hw(&self) -> bool {
708        let reg = self.ledc.int_raw().read();
709        if S::IS_HS {
710            reg.duty_chng_end_hsch(self.number as u8).bit_is_clear()
711        } else {
712            reg.duty_chng_end_lsch(self.number as u8).bit_is_clear()
713        }
714    }
715
716    #[cfg(not(esp32))]
717    fn is_duty_fade_running_hw(&self) -> bool {
718        self.ledc
719            .int_raw()
720            .read()
721            .duty_chng_end_ch(self.number as u8)
722            .bit_is_clear()
723    }
724}