quadrature_encoder/encoder/
indexed.rs

1//! A robust incremental encoder driver with support for multiple step-modes.
2
3use core::marker::PhantomData;
4
5use num_traits::{One, SaturatingAdd, WrappingNeg, Zero};
6use quadrature_decoder::{Change, FullStep, IndexedIncrementalDecoder, StepMode};
7
8#[allow(unused_imports)]
9use crate::{
10    mode::{Async, Blocking, Movement, OperationMode, PollMode},
11    traits::*,
12    Error, InputPinError, Linear, Rotary,
13};
14
15/// Rotary encoder.
16pub type IndexedRotaryEncoder<Clk, Dt, Steps = FullStep, T = i32, PM = Blocking> =
17    IndexedIncrementalEncoder<Rotary, Clk, Dt, Steps, T, PM>;
18/// Linear encoder.
19pub type IndexedLinearEncoder<Clk, Dt, Steps = FullStep, T = i32, PM = Blocking> =
20    IndexedIncrementalEncoder<Linear, Clk, Dt, Steps, T, PM>;
21
22/// A robust incremental encoder with support for multiple step-modes.
23#[derive(Debug)]
24pub struct IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps = FullStep, T = i32, PM = Blocking> {
25    decoder: IndexedIncrementalDecoder<Steps, T>,
26    pin_clk: Clk,
27    pin_dt: Dt,
28    pin_idx: Idx,
29    pin_clk_state: bool,
30    pin_dt_state: bool,
31    pin_idx_state: bool,
32    is_reversed: bool,
33    _mode: PhantomData<Mode>,
34    _poll_mode: PhantomData<PM>,
35}
36
37impl<Mode, Clk, Dt, Idx, Steps, T, PM> IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps, T, PM>
38where
39    Mode: OperationMode,
40    Clk: InputPin,
41    Dt: InputPin,
42    Idx: InputPin,
43    Steps: StepMode,
44    T: Zero,
45    PM: PollMode,
46{
47    /// Creates an indexed incremental encoder driver for the given pins.
48    pub fn new(mut pin_clk: Clk, mut pin_dt: Dt, mut pin_idx: Idx) -> Self
49    where
50        IndexedIncrementalDecoder<Steps, T>: Default,
51    {
52        let pin_clk_state = pin_clk.is_high().unwrap_or(false);
53        let pin_dt_state = pin_dt.is_high().unwrap_or(false);
54        let pin_idx_state = pin_idx.is_high().unwrap_or(false);
55
56        Self {
57            decoder: Default::default(),
58            pin_clk,
59            pin_dt,
60            pin_idx,
61            pin_clk_state,
62            pin_dt_state,
63            pin_idx_state,
64            is_reversed: false,
65            _mode: PhantomData,
66            _poll_mode: PhantomData,
67        }
68    }
69}
70
71impl<Mode, Clk, Dt, Idx, Steps, T, PM> IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps, T, PM>
72where
73    Mode: OperationMode,
74    Clk: InputPin,
75    Dt: InputPin,
76    Idx: InputPin,
77    Steps: StepMode,
78    T: Copy + Zero + One + SaturatingAdd + WrappingNeg + From<i8>,
79    PM: PollMode,
80{
81    /// Sets the encoder's reversed mode, making it report flipped movements and positions.
82    pub fn reversed(mut self) -> Self {
83        self.is_reversed = true;
84        self
85    }
86
87    /// Returns `true` if the encoder is reversed, otherwise `false`.
88    pub fn is_reversed(&self) -> bool {
89        self.is_reversed
90    }
91
92    /// Returns mutable borrows for the signal channel pins.
93    pub fn pins_mut(&mut self) -> (&mut Clk, &mut Dt) {
94        (&mut self.pin_clk, &mut self.pin_dt)
95    }
96
97    /// Consumes self, returning the signal channel pins.
98    pub fn release(self) -> (Clk, Dt) {
99        (self.pin_clk, self.pin_dt)
100    }
101
102    /// Updates the internal decoder state, from the latest IO readings.
103    /// This is called within poll() / poll_async()
104    fn update(&mut self) -> Result<Option<Mode::Movement>, Error> {
105        let change: Option<Change> = self
106            .decoder
107            .update(self.pin_clk_state, self.pin_dt_state, self.pin_idx_state)
108            .map_err(Error::Quadrature)?;
109        let movement: Option<Mode::Movement> = change.map(From::from);
110
111        Ok(movement.map(|movement| {
112            if self.is_reversed() {
113                movement.flipped()
114            } else {
115                movement
116            }
117        }))
118    }
119
120    /// Resets the encoder to its initial state.
121    pub fn reset(&mut self) {
122        self.decoder.reset();
123    }
124
125    /// Returns the encoder's position counter relative to its initial position in number of cycles.
126    pub fn position(&self) -> T {
127        match self.is_reversed {
128            true => self.decoder.counter().wrapping_neg(),
129            false => self.decoder.counter(),
130        }
131    }
132
133    /// Sets the encoder's position.
134    pub fn set_position(&mut self, position: T) {
135        match self.is_reversed {
136            true => self.decoder.set_counter(position.wrapping_neg()),
137            false => self.decoder.set_counter(position),
138        }
139    }
140}
141
142impl<Mode, Clk, Dt, Idx, Steps, T> IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps, T, Blocking>
143where
144    Mode: OperationMode,
145    Clk: InputPin,
146    Dt: InputPin,
147    Idx: InputPin,
148    Steps: StepMode,
149    T: Copy + Zero + One + SaturatingAdd + WrappingNeg + From<i8>,
150{
151    /// Updates the encoder's state based on the given **clock**, **data**, and **index** pins,
152    /// returning the direction if a movement was detected, `None` if no movement was detected,
153    /// or `Err(_)` if an invalid input (i.e. a positional "jump") was detected.
154    ///
155    /// Depending on whether it matters why the encoder did not detect a movement
156    /// (e.g. due to actual lack of movement or an erroneous read)
157    /// you would either call `encoder.poll()` directly, or via `encoder.poll().unwrap_or_default()`
158    /// to fall back to `None` in case of `Err(_)`.
159    pub fn poll(&mut self) -> Result<Option<Mode::Movement>, Error> {
160        self.pin_clk_state = self
161            .pin_clk
162            .is_high()
163            .map_err(|_| Error::InputPin(InputPinError::PinClk))?;
164        self.pin_dt_state = self
165            .pin_dt
166            .is_high()
167            .map_err(|_| Error::InputPin(InputPinError::PinDt))?;
168        self.pin_idx_state = self
169            .pin_idx
170            .is_high()
171            .map_err(|_| Error::InputPin(InputPinError::PinIdx))?;
172        self.update()
173    }
174}
175
176/// If async is enabled, and the pins provided satisfy the AsyncInputPin trait, the into_async() method is exposed.
177#[cfg(feature = "async")]
178impl<Mode, Clk, Dt, Idx, Steps, T> IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps, T, Blocking>
179where
180    Mode: OperationMode,
181    Clk: InputPin + Wait,
182    Dt: InputPin + Wait,
183    Idx: InputPin + Wait,
184    Steps: StepMode,
185    T: Copy + Zero + One + SaturatingAdd + WrappingNeg + From<i8>,
186{
187    /// Reconfigure the driver so that poll() is an async fn
188    pub fn into_async(self) -> IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps, T, Async>
189    where
190        IndexedIncrementalDecoder<Steps, T>: Default,
191    {
192        IndexedIncrementalEncoder::<Mode, Clk, Dt, Idx, Steps, T, Async> {
193            decoder: self.decoder,
194            pin_clk: self.pin_clk,
195            pin_dt: self.pin_dt,
196            pin_idx: self.pin_idx,
197            pin_clk_state: self.pin_clk_state,
198            pin_dt_state: self.pin_dt_state,
199            pin_idx_state: self.pin_idx_state,
200            is_reversed: self.is_reversed,
201            _mode: PhantomData,
202            _poll_mode: PhantomData,
203        }
204    }
205}
206
207#[cfg(feature = "async")]
208impl<Mode, Clk, Dt, Idx, Steps, T> IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps, T, Async>
209where
210    Mode: OperationMode,
211    Clk: InputPin + Wait,
212    Dt: InputPin + Wait,
213    Idx: InputPin + Wait,
214    Steps: StepMode,
215    T: Copy + Zero + One + SaturatingAdd + WrappingNeg + From<i8>,
216{
217    /// Updates the encoder's state based on the given **clock**, **data**, and **index** pins,
218    /// returning the direction if a movement was detected, `None` if no movement was detected,
219    /// or `Err(_)` if an invalid input (i.e. a positional "jump") was detected.
220    ///
221    /// Depending on whether it matters why the encoder did not detect a movement
222    /// (e.g. due to actual lack of movement or an erroneous read)
223    /// you would either call `encoder.poll()` directly, or via `encoder.poll().unwrap_or_default()`
224    /// to fall back to `None` in case of `Err(_)`.
225    ///
226    /// Waits asyncronously for any of the pins to change state, before returning.
227    pub async fn poll(&mut self) -> Result<Option<Mode::Movement>, Error> {
228        let clk_fut = match self.pin_clk_state {
229            true => self.pin_clk.wait_for_low().left_future(),
230            false => self.pin_clk.wait_for_high().right_future(),
231        };
232
233        let dt_fut = match self.pin_dt_state {
234            true => self.pin_dt.wait_for_low().left_future(),
235            false => self.pin_dt.wait_for_high().right_future(),
236        };
237
238        let idx_fut = match self.pin_idx_state {
239            true => self.pin_idx.wait_for_low().left_future(),
240            false => self.pin_idx.wait_for_high().right_future(),
241        };
242
243        // toggle the internal state, rather than reading the pin state directly,
244        // as the pin state has likely changed since the wait_for_low() future was resolved
245        // by the hardware interrupt behind-the-scenes.
246        match select3(clk_fut, dt_fut, idx_fut).await {
247            Either3::First(_) => {
248                self.pin_clk_state = !self.pin_clk_state;
249            }
250            Either3::Second(_) => {
251                self.pin_dt_state = !self.pin_dt_state;
252            }
253            Either3::Third(_) => {
254                self.pin_idx_state = !self.pin_idx_state;
255            }
256        };
257
258        self.update()
259    }
260
261    /// Reconfigure the driver so that poll() is a blocking function
262    pub fn into_blocking(self) -> IndexedIncrementalEncoder<Mode, Clk, Dt, Idx, Steps, T, Blocking>
263    where
264        IndexedIncrementalDecoder<Steps, T>: Default,
265    {
266        IndexedIncrementalEncoder::<Mode, Clk, Dt, Idx, Steps, T, Blocking> {
267            decoder: self.decoder,
268            pin_clk: self.pin_clk,
269            pin_dt: self.pin_dt,
270            pin_idx: self.pin_idx,
271            pin_clk_state: self.pin_clk_state,
272            pin_dt_state: self.pin_dt_state,
273            pin_idx_state: self.pin_idx_state,
274            is_reversed: self.is_reversed,
275            _mode: PhantomData,
276            _poll_mode: PhantomData,
277        }
278    }
279}