rotary_encoder_embedded/
quadrature.rs1use embedded_hal::digital::InputPin;
2
3use crate::{Direction, RotaryEncoder};
4
5const QUAD_TABLE: [i8; 16] = [
9 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0, ];
26
27impl<DT, CLK> RotaryEncoder<QuadratureTableMode, DT, CLK>
28where
29 DT: InputPin,
30 CLK: InputPin,
31{
32 pub fn update(&mut self) -> Direction {
34 self.mode.update(
35 self.pin_dt.is_high().unwrap_or_default(),
36 self.pin_clk.is_high().unwrap_or_default(),
37 )
38 }
39}
40
41impl<LOGIC, DT, CLK> RotaryEncoder<LOGIC, DT, CLK>
42where
43 DT: InputPin,
44 CLK: InputPin,
45{
46 pub fn into_quadrature_table_mode(
48 self,
49 threshold: u8,
50 ) -> RotaryEncoder<QuadratureTableMode, DT, CLK> {
51 RotaryEncoder {
52 pin_dt: self.pin_dt,
53 pin_clk: self.pin_clk,
54 mode: QuadratureTableMode::new(threshold),
55 }
56 }
57}
58
59impl Default for QuadratureTableMode {
60 fn default() -> Self {
61 Self::new(1)
62 }
63}
64
65pub struct QuadratureTableMode {
68 prev_state: u8, threshold: u8, count: i8, }
72
73impl QuadratureTableMode {
74 pub fn new(threshold: u8) -> Self {
77 Self {
78 prev_state: 0,
79 count: 0,
80 threshold,
81 }
82 }
83
84 pub fn update(&mut self, dt: bool, clk: bool) -> Direction {
87 let curr = (dt as u8) | ((clk as u8) << 1);
88 let idx = ((self.prev_state << 2) | curr) as usize;
89 let delta = QUAD_TABLE[idx];
90 self.prev_state = curr;
91 self.count += delta;
92 if self.count.unsigned_abs() >= self.threshold {
93 let dir = if self.count > 0 {
94 Direction::Clockwise
95 } else {
96 Direction::Anticlockwise
97 };
98 self.count = 0; return dir;
100 }
101 Direction::None
102 }
103}
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::Direction;
108
109 fn drive_sequence(mode: &mut QuadratureTableMode, seq: &[(bool, bool)]) -> Vec<Direction> {
111 seq.iter().map(|&(dt, clk)| mode.update(dt, clk)).collect()
112 }
113
114 #[test]
115 fn single_cw_step_threshold_1() {
116 let mut mode = QuadratureTableMode::new(1);
117 assert_eq!(mode.update(true, false), Direction::Clockwise);
119 }
120
121 #[test]
122 fn single_ccw_step_threshold_1() {
123 let mut mode = QuadratureTableMode::new(1);
124 assert_eq!(mode.update(false, true), Direction::Anticlockwise);
126 }
127
128 #[test]
129 fn aggregation_threshold_2_requires_two_valid_pulses() {
130 let mut mode = QuadratureTableMode::new(2);
132
133 assert_eq!(mode.update(true, false), Direction::None);
135
136 assert_eq!(mode.update(true, true), Direction::Clockwise);
138
139 assert_eq!(mode.update(false, true), Direction::None);
141 }
142
143 #[test]
144 fn no_movement_on_constant_state() {
145 let mut mode = QuadratureTableMode::new(1);
146 for _ in 0..5 {
148 assert_eq!(mode.update(false, false), Direction::None);
149 }
150 }
151
152 #[test]
153 fn invalid_transition_skipped_state() {
154 let mut mode = QuadratureTableMode::new(1);
155 assert_eq!(mode.update(true, true), Direction::None);
157 assert_eq!(mode.update(false, false), Direction::None);
159 }
160
161 #[test]
162 fn full_cw_cycle_threshold_1() {
163 let mut mode = QuadratureTableMode::new(1);
164 let seq = [
166 (false, false), (true, false), (true, true), (false, true), (false, false), ];
172 let results = drive_sequence(&mut mode, &seq);
173 assert_eq!(
174 results,
175 vec![
176 Direction::None,
177 Direction::Clockwise,
178 Direction::Clockwise,
179 Direction::Clockwise,
180 Direction::Clockwise,
181 ]
182 );
183 }
184}