Skip to main content

esp_idf_hal/rmt/
pulse.rs

1use core::time::Duration;
2
3use esp_idf_sys::*;
4
5use crate::units::Hertz;
6
7// Might not always be available in the generated `esp-idf-sys` bindings
8const ERR_EOVERFLOW: esp_err_t = 139;
9
10/// A `Low` (0) or `High` (1) state for a pin.
11#[derive(Debug, Copy, Clone, Eq, PartialEq)]
12pub enum PinState {
13    Low,
14    High,
15}
16
17impl From<bool> for PinState {
18    fn from(state: bool) -> Self {
19        (state as u32).into()
20    }
21}
22
23impl From<u32> for PinState {
24    fn from(state: u32) -> Self {
25        if state == 0 {
26            Self::Low
27        } else {
28            Self::High
29        }
30    }
31}
32
33/// A [`Pulse`] defines for how long the output pin should be high or low.
34///
35/// The duration is defined through the [`PulseTicks`] type.
36///
37/// <div class="warning">The conversion from
38/// ticks to real time depends on the selected resolution of the RMT channel.</div>
39///
40/// # Example
41///
42/// ```rust
43/// # use esp_idf_hal::rmt::{PulseTicks, Pulse, PinState};
44/// let pulse = Pulse::new(PinState::High, PulseTicks::new(32));
45/// ```
46///
47/// You can create a [`Pulse`] with a [`Duration`] by using [`Pulse::new_with_duration`]:
48/// ```rust
49/// # use esp_idf_sys::EspError;
50/// # use core::time::Duration;
51/// # fn example() -> Result<(), EspError> {
52/// use esp_idf_hal::rmt::{Pulse, PinState, PulseTicks};
53/// use esp_idf_hal::units::FromValueType;
54///
55/// let ticks = 1_000_000.Hz(); // 1 MHz
56/// let pulse = Pulse::new_with_duration(1_000_000.Hz(), PinState::High, Duration::from_nanos(300))?;
57/// # Ok(())
58/// # }
59/// ```
60/// [`Duration`]: core::time::Duration
61#[derive(Debug, Copy, Clone, Eq, PartialEq)]
62pub struct Pulse {
63    pub ticks: PulseTicks,
64    pub pin_state: PinState,
65}
66
67impl Pulse {
68    /// Returns a low `Pulse` with zero ticks.
69    pub const fn zero() -> Self {
70        Self::new(PinState::Low, PulseTicks::zero())
71    }
72
73    /// Create a [`Pulse`] using a pin state and a tick count.
74    pub const fn new(pin_state: PinState, ticks: PulseTicks) -> Self {
75        Self { pin_state, ticks }
76    }
77
78    /// Create a [`Pulse`] using a [`Duration`].
79    ///
80    /// To convert the duration into ticks, the resolution (clock ticks) set for the
81    /// RMT channel must be provided ([`TxChannelConfig::resolution`](crate::rmt::config::TxChannelConfig::resolution)).
82    ///
83    /// # Errors
84    ///
85    /// If the duration is too long to be represented as ticks with the given resolution,
86    /// an error with the code [`ERR_EOVERFLOW`] or [`ESP_ERR_INVALID_ARG`] will be returned.
87    pub const fn new_with_duration(
88        resolution: Hertz,
89        pin_state: PinState,
90        duration: Duration,
91    ) -> Result<Self, EspError> {
92        match PulseTicks::new_with_duration(resolution, duration) {
93            Ok(ticks) => Ok(Self::new(pin_state, ticks)),
94            Err(error) => Err(error),
95        }
96    }
97}
98
99impl Default for Pulse {
100    fn default() -> Self {
101        Self::zero()
102    }
103}
104
105/// Number of ticks, restricting the range to 0 to 32,767.
106#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)]
107pub struct PulseTicks(u16);
108
109impl PulseTicks {
110    const MAX: u16 = 32767; // = 2^15 - 1
111
112    /// Use zero ticks.
113    pub const fn zero() -> Self {
114        Self(0)
115    }
116
117    /// Use the maximum value of 32767.
118    pub const fn max() -> Self {
119        Self(Self::MAX)
120    }
121
122    /// Create a `PulseTicks` from the given number of ticks.
123    ///
124    /// # Errors
125    ///
126    /// If the given number of ticks is larger than [`PulseTicks::max()`], an error with the code
127    /// [`ESP_ERR_INVALID_ARG`] will be returned.
128    pub const fn new(ticks: u16) -> Result<Self, EspError> {
129        if ticks > Self::MAX {
130            Err(EspError::from_infallible::<ESP_ERR_INVALID_ARG>())
131        } else {
132            Ok(Self(ticks))
133        }
134    }
135
136    /// Constructs a [`PulseTicks`] from a [`Duration`].
137    ///
138    /// # Errors
139    ///
140    /// If the duration is too long to be represented as ticks with the given resolution,
141    /// an error with the code [`ERR_EOVERFLOW`] or [`ESP_ERR_INVALID_ARG`] will be returned.
142    pub const fn new_with_duration(
143        resolution: Hertz,
144        duration: Duration,
145    ) -> Result<Self, EspError> {
146        match duration_to_ticks(resolution, duration) {
147            Ok(ticks) => Self::new(ticks),
148            Err(error) => Err(error),
149        }
150    }
151
152    /// Returns the number of ticks.
153    pub const fn ticks(&self) -> u16 {
154        self.0
155    }
156
157    /// Returns the duration it takes for the number of ticks, depending on the given clock ticks.
158    ///
159    /// # Panics
160    ///
161    /// This function panics if the conversion from ticks to duration overflows.
162    pub const fn duration(&self, resolution: Hertz) -> Duration {
163        match ticks_to_duration(resolution, self.ticks()) {
164            Ok(duration) => duration,
165            Err(_) => panic!("Overflow while converting ticks to duration"),
166        }
167    }
168}
169
170const ONE_SECOND_IN_NANOS: u128 = Duration::from_secs(1).as_nanos();
171
172/// A utility to convert a duration into ticks, depending on the clock ticks.
173pub const fn duration_to_ticks(resolution: Hertz, duration: Duration) -> Result<u16, EspError> {
174    let Some(ticks) = duration.as_nanos().checked_mul(resolution.0 as u128) else {
175        return Err(EspError::from_infallible::<ERR_EOVERFLOW>());
176    };
177
178    // To get the result we calculate:
179    // duration (s) * resolution (ticks/s) = ticks
180    //
181    // The above calculates with the duration in nanoseconds,
182    // which is why it has to be divided by 1_000_000_000 (= 1s)
183    // to get the correct result.
184
185    let ticks = ticks / ONE_SECOND_IN_NANOS;
186
187    if ticks > u16::MAX as u128 {
188        return Err(EspError::from_infallible::<ERR_EOVERFLOW>());
189    }
190
191    Ok(ticks as u16)
192}
193
194/// A utility to convert ticks into duration, depending on the clock ticks.
195pub const fn ticks_to_duration(resolution: Hertz, ticks: u16) -> Result<Duration, EspError> {
196    let Some(duration) = ONE_SECOND_IN_NANOS.checked_mul(ticks as u128) else {
197        return Err(EspError::from_infallible::<ERR_EOVERFLOW>());
198    };
199
200    let duration = duration / resolution.0 as u128;
201
202    if duration > u64::MAX as u128 {
203        return Err(EspError::from_infallible::<ERR_EOVERFLOW>());
204    }
205
206    Ok(Duration::from_nanos(duration as u64))
207}