sb_rotary_encoder/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(test), no_std)]
3#![warn(missing_docs)]
4
5/// Map containing values for the transitions between input states.
6/// - 0: invalid transition
7/// - 1: clockwise movement
8/// - -1: counter-clockwise movement
9#[rustfmt::skip]
10const STATE_MAP: [[i8; 4]; 4] = [
11    [0, -1, 1, 0],
12    [1, 0, 0, -1],
13    [-1, 0, 0, 1],
14    [0, 1, -1, 0]
15];
16
17/// Encoder struct with internal states.
18#[derive(Debug, Default)]
19pub struct RotaryEncoder {
20    /// Last state of input signals, range is 0..3.
21    inputstate: u8,
22
23    /// Initial input state, used to resync the switching point.
24    initial_inputstate: Option<u8>,
25
26    /// Current raw value independent of the pulse divider.
27    raw_value: i32,
28
29    /// Nominal value respecting the pulse divider.
30    value: i32,
31
32    /// Tick value of last event.
33    tick: Option<u64>,
34}
35
36impl RotaryEncoder {
37    /// Creates a new instance.
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Updates the processor and returns an event if a movement is detected.
43    /// - `input_a` and `input_b` are the signals of the encoder pins.
44    /// - `tick` is a monotonic value from the system clock used for velocity calculation.
45    /// - `pulse_divider` determines how many pulses must occur before an event is generated.
46    pub fn update<T: Into<bool>>(
47        &mut self,
48        input_a: T,
49        input_b: T,
50        tick: Option<u64>,
51        pulse_divider: i32,
52    ) -> Option<RotaryEncoderEvent> {
53        let inputstate = ((input_a.into() as u8) << 1) | (input_b.into() as u8);
54
55        if inputstate == self.inputstate {
56            // Input state has not changed.
57            return None;
58        }
59
60        if self.initial_inputstate.is_none() {
61            // Store the input stage on first call. This state is regarded as rest position
62            // and will be used later to resync the switching point.
63            self.initial_inputstate = Some(inputstate);
64        }
65
66        let state_value = STATE_MAP[self.inputstate as usize][inputstate as usize];
67        self.inputstate = inputstate;
68
69        if state_value == 0 {
70            // No valid transition detected.
71            return None;
72        }
73
74        self.raw_value += state_value as i32;
75
76        if self.raw_value % pulse_divider != 0 {
77            // Number of steps not reached yet.
78            return None;
79        }
80
81        let value = self.raw_value / pulse_divider;
82
83        if pulse_divider > 1 {
84            // Realign the raw value to the pulse divider to ensure everything stays in sync.
85            let aligned_raw_value =
86                ((self.raw_value + (pulse_divider - 1)) / pulse_divider) * pulse_divider;
87            if self.raw_value != aligned_raw_value {
88                self.raw_value = aligned_raw_value;
89                self.value = self.raw_value / pulse_divider;
90            }
91            self.inputstate = self.initial_inputstate.unwrap_or_default();
92        }
93
94        let timedelta = if let Some(timestamp) = tick {
95            Some(timestamp - self.tick.unwrap_or_default())
96        } else {
97            None
98        };
99
100        let event = Some(RotaryEncoderEvent {
101            value,
102            direction: if state_value > 0 {
103                Direction::Clockwise
104            } else {
105                Direction::CounterClockwise
106            },
107            timedelta,
108        });
109
110        self.value = value;
111        self.tick = tick;
112
113        event
114    }
115
116    /// Resets all internal states to initial values.
117    pub fn reset(&mut self) {
118        *self = Self::default();
119    }
120
121    /// Returns the current value.
122    pub fn value(&self) -> i32 {
123        self.value
124    }
125
126    /// Sets the current value.
127    pub fn set_value(&mut self, value: i32) {
128        self.value = value;
129    }
130}
131
132/// Event generated by the `process()` function.
133#[derive(Copy, Clone, Debug, PartialEq, Eq)]
134pub struct RotaryEncoderEvent {
135    /// Absolute position value.
136    value: i32,
137
138    /// Direction of movement.
139    direction: Direction,
140
141    /// Optional time units elapsed since last event.
142    timedelta: Option<u64>,
143}
144
145impl RotaryEncoderEvent {
146    /// Returns the current value.
147    pub fn value(&self) -> i32 {
148        self.value
149    }
150
151    /// Returns the direction of movement.
152    pub fn direction(&self) -> Direction {
153        self.direction
154    }
155
156    /// Returns the step value. +1 for clockwise, -1 for counter-clockwise.
157    pub fn step(&self) -> i32 {
158        match self.direction {
159            Direction::Clockwise => 1,
160            Direction::CounterClockwise => -1,
161        }
162    }
163
164    /// Returns optional time units since last event.
165    pub fn timedelta(&self) -> Option<u64> {
166        self.timedelta
167    }
168
169    /// Returns the optional velocity in respect of the timebase.
170    /// E.g. if `process()` is called in 1ms intervall, the timebase is 1000. Calling this
171    /// function with that value will return the number of events per second.
172    pub fn velocity(&self, update_freq: i32) -> Option<i32> {
173        if let Some(timedelta) = self.timedelta {
174            if timedelta > 0 {
175                Some((update_freq as u64 / timedelta) as i32)
176            } else {
177                Some(0)
178            }
179        } else {
180            None
181        }
182    }
183}
184
185/// Direction of encoder movement.
186#[derive(Copy, Clone, Debug, PartialEq, Eq)]
187pub enum Direction {
188    /// Clockwise movement.
189    Clockwise,
190
191    /// Counter-clockwise movement.
192    CounterClockwise,
193}
194
195#[cfg(test)]
196mod tests;