pub const ENC_CW: &str = "ENC_CW";
pub const ENC_CCW: &str = "ENC_CCW";
pub const ENC_SW: &str = "ENC_SW";
pub const STEPS_PER_DETENT: i8 = 4;
const REST: u8 = 0b11;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Rotation {
Cw,
Ccw,
}
impl Rotation {
pub fn label(self) -> &'static str {
match self {
Rotation::Cw => ENC_CW,
Rotation::Ccw => ENC_CCW,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Channel {
A,
B,
}
fn code_of(a: bool, b: bool) -> u8 {
(u8::from(a) << 1) | u8::from(b)
}
fn quarter_step(from: u8, to: u8) -> Option<i8> {
match (from, to) {
_ if from == to => Some(0),
(0b11, 0b01) | (0b01, 0b00) | (0b00, 0b10) | (0b10, 0b11) => Some(1),
(0b11, 0b10) | (0b10, 0b00) | (0b00, 0b01) | (0b01, 0b11) => Some(-1),
_ => None,
}
}
#[derive(Debug, Clone)]
pub struct QuadratureDecoder {
code: u8,
steps: i8,
rejected: u64,
}
impl QuadratureDecoder {
pub fn new(a: bool, b: bool) -> Self {
Self {
code: code_of(a, b),
steps: 0,
rejected: 0,
}
}
pub fn on_edge(&mut self, channel: Channel, high: bool) -> Option<Rotation> {
let a = self.code & 0b10 != 0;
let b = self.code & 0b01 != 0;
match channel {
Channel::A => self.on_levels(high, b),
Channel::B => self.on_levels(a, high),
}
}
pub fn on_levels(&mut self, a: bool, b: bool) -> Option<Rotation> {
let code = code_of(a, b);
match quarter_step(self.code, code) {
Some(0) => return None,
Some(step) => self.steps = self.steps.saturating_add(step),
None => {
self.rejected += 1;
self.steps = 0;
self.code = code;
return None;
}
}
self.code = code;
if code != REST {
return None;
}
match std::mem::take(&mut self.steps) {
STEPS_PER_DETENT => Some(Rotation::Cw),
s if s == -STEPS_PER_DETENT => Some(Rotation::Ccw),
_ => None,
}
}
pub fn rejected(&self) -> u64 {
self.rejected
}
}
#[cfg(test)]
mod tests {
use super::*;
fn feed(d: &mut QuadratureDecoder, levels: &[(u8, u8)]) -> Vec<Rotation> {
levels
.iter()
.filter_map(|&(a, b)| d.on_levels(a == 1, b == 1))
.collect()
}
const CW: [(u8, u8); 4] = [(0, 1), (0, 0), (1, 0), (1, 1)];
const CCW: [(u8, u8); 4] = [(1, 0), (0, 0), (0, 1), (1, 1)];
#[test]
fn a_full_clockwise_cycle_is_one_cw_detent() {
let mut d = QuadratureDecoder::new(true, true);
assert_eq!(feed(&mut d, &CW), vec![Rotation::Cw]);
assert_eq!(feed(&mut d, &CW), vec![Rotation::Cw]);
assert_eq!(d.rejected(), 0);
}
#[test]
fn a_full_counter_clockwise_cycle_is_one_ccw_detent() {
let mut d = QuadratureDecoder::new(true, true);
assert_eq!(feed(&mut d, &CCW), vec![Rotation::Ccw]);
assert_eq!(feed(&mut d, &CCW), vec![Rotation::Ccw]);
}
#[test]
fn a_detent_fires_only_on_the_return_to_rest() {
let mut d = QuadratureDecoder::new(true, true);
assert!(feed(&mut d, &CW[..3]).is_empty());
assert_eq!(d.on_levels(true, true), Some(Rotation::Cw));
}
#[test]
fn edges_on_each_channel_decode_the_same_as_levels() {
let mut d = QuadratureDecoder::new(true, true);
assert_eq!(d.on_edge(Channel::A, false), None);
assert_eq!(d.on_edge(Channel::B, false), None);
assert_eq!(d.on_edge(Channel::A, true), None);
assert_eq!(d.on_edge(Channel::B, true), Some(Rotation::Cw));
assert_eq!(d.on_edge(Channel::B, false), None);
assert_eq!(d.on_edge(Channel::A, false), None);
assert_eq!(d.on_edge(Channel::B, true), None);
assert_eq!(d.on_edge(Channel::A, true), Some(Rotation::Ccw));
}
#[test]
fn a_repeated_level_is_not_a_step() {
let mut d = QuadratureDecoder::new(true, true);
assert_eq!(d.on_edge(Channel::A, false), None);
assert_eq!(d.on_edge(Channel::A, false), None);
assert_eq!(feed(&mut d, &CW[1..]), vec![Rotation::Cw]);
}
#[test]
fn contact_bounce_cancels_out_instead_of_counting() {
let mut d = QuadratureDecoder::new(true, true);
let chatter = [(0, 1), (1, 1), (0, 1), (1, 1), (0, 1)];
assert!(feed(&mut d, &chatter).is_empty());
let rest = [(0, 0), (0, 1), (0, 0), (1, 0), (1, 1)];
assert_eq!(feed(&mut d, &rest), vec![Rotation::Cw]);
assert_eq!(d.rejected(), 0);
}
#[test]
fn a_half_turn_and_back_is_no_detent() {
let mut d = QuadratureDecoder::new(true, true);
let wobble = [(0, 1), (0, 0), (0, 1), (1, 1)];
assert!(feed(&mut d, &wobble).is_empty());
assert_eq!(feed(&mut d, &CCW), vec![Rotation::Ccw]);
}
#[test]
fn a_both_lines_transition_is_rejected_and_drops_the_partial_detent() {
let mut d = QuadratureDecoder::new(true, true);
assert!(feed(&mut d, &[(0, 1), (1, 0)]).is_empty());
assert_eq!(d.rejected(), 1);
assert_eq!(d.on_levels(true, true), None);
assert_eq!(d.on_levels(false, false), None);
assert_eq!(d.rejected(), 2);
assert_eq!(d.on_levels(true, true), None);
assert_eq!(d.rejected(), 3);
assert_eq!(feed(&mut d, &CW), vec![Rotation::Cw]);
}
#[test]
fn a_decoder_started_mid_detent_never_reports_a_false_detent() {
let mut d = QuadratureDecoder::new(false, false);
assert!(feed(&mut d, &[(1, 0), (1, 1)]).is_empty());
assert_eq!(feed(&mut d, &CW), vec![Rotation::Cw]);
}
#[test]
fn detents_carry_the_encoder_labels() {
assert_eq!(Rotation::Cw.label(), "ENC_CW");
assert_eq!(Rotation::Ccw.label(), "ENC_CCW");
assert_eq!(ENC_SW, "ENC_SW");
}
}