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