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