Skip to content

Instantly share code, notes, and snippets.

@bagbag
Created March 26, 2025 16:24
Show Gist options
  • Select an option

  • Save bagbag/b210d759f9775d4898b2be1464f4a288 to your computer and use it in GitHub Desktop.

Select an option

Save bagbag/b210d759f9775d4898b2be1464f4a288 to your computer and use it in GitHub Desktop.
stepper decoder
use embassy_futures::select::{Either, select, select_array};
use embassy_time::Timer;
use esp_hal::{
gpio::{Input, InputConfig, InputPin, Pull},
peripheral::Peripheral,
};
const STATE_A: u8 = 0b1000;
const STATE_AB: u8 = 0b1100;
const STATE_B: u8 = 0b0100;
const STATE_BC: u8 = 0b0110;
const STATE_C: u8 = 0b0010;
const STATE_CD: u8 = 0b0011;
const STATE_D: u8 = 0b0001;
const STATE_DA: u8 = 0b1001;
const INVALID_STATE: u8 = 0b1111;
const VALID_STATES: [u8; 8] = [STATE_A, STATE_AB, STATE_B, STATE_BC, STATE_C, STATE_CD, STATE_D, STATE_DA];
const INVALID_TRANSITION: i8 = i8::MIN;
// --- Lookup Table for State Transitions ---
// LUT[last_state][current_state] -> step_change
// Uses i8::MIN (-128) to indicate an invalid/unexpected transition.
const TRANSITION_LUT: [[i8; 16]; 16] = {
let mut lut = [[INVALID_TRANSITION; 16]; 16];
lut[STATE_A as usize][STATE_A as usize] = 0;
lut[STATE_A as usize][STATE_AB as usize] = 1;
lut[STATE_A as usize][STATE_DA as usize] = -1;
lut[STATE_A as usize][STATE_B as usize] = 2;
lut[STATE_A as usize][STATE_D as usize] = -2;
lut[STATE_AB as usize][STATE_AB as usize] = 0;
lut[STATE_AB as usize][STATE_B as usize] = 1;
lut[STATE_AB as usize][STATE_A as usize] = -1;
lut[STATE_AB as usize][STATE_BC as usize] = 2;
lut[STATE_AB as usize][STATE_DA as usize] = -2;
lut[STATE_B as usize][STATE_B as usize] = 0;
lut[STATE_B as usize][STATE_BC as usize] = 1;
lut[STATE_B as usize][STATE_AB as usize] = -1;
lut[STATE_B as usize][STATE_C as usize] = 2;
lut[STATE_B as usize][STATE_A as usize] = -2;
lut[STATE_BC as usize][STATE_BC as usize] = 0;
lut[STATE_BC as usize][STATE_C as usize] = 1;
lut[STATE_BC as usize][STATE_B as usize] = -1;
lut[STATE_BC as usize][STATE_CD as usize] = 2;
lut[STATE_BC as usize][STATE_AB as usize] = -2;
lut[STATE_C as usize][STATE_C as usize] = 0;
lut[STATE_C as usize][STATE_CD as usize] = 1;
lut[STATE_C as usize][STATE_BC as usize] = -1;
lut[STATE_C as usize][STATE_D as usize] = 2;
lut[STATE_C as usize][STATE_B as usize] = -2;
lut[STATE_CD as usize][STATE_CD as usize] = 0;
lut[STATE_CD as usize][STATE_D as usize] = 1;
lut[STATE_CD as usize][STATE_C as usize] = -1;
lut[STATE_CD as usize][STATE_DA as usize] = 2;
lut[STATE_CD as usize][STATE_BC as usize] = -2;
lut[STATE_D as usize][STATE_D as usize] = 0;
lut[STATE_D as usize][STATE_DA as usize] = 1;
lut[STATE_D as usize][STATE_CD as usize] = -1;
lut[STATE_D as usize][STATE_A as usize] = 2;
lut[STATE_D as usize][STATE_C as usize] = -2;
lut[STATE_DA as usize][STATE_DA as usize] = 0;
lut[STATE_DA as usize][STATE_A as usize] = 1;
lut[STATE_DA as usize][STATE_D as usize] = -1;
lut[STATE_DA as usize][STATE_AB as usize] = 2;
lut[STATE_DA as usize][STATE_CD as usize] = -2;
lut
};
pub struct StepperDecoder<'a> {
inputs: [Input<'a>; 4],
last_state: u8,
half_steps_delta: i32,
total_half_steps: u32,
invalid_states: u32,
invalid_transistions: u32,
}
impl<'a> StepperDecoder<'a> {
pub fn get_steps_delta(self: &Self) -> f32 {
return self.half_steps_delta as f32 / 2.0;
}
pub fn get_total_steps(self: &Self) -> f32 {
return self.total_half_steps as f32 / 2.0;
}
pub fn new<A: InputPin, B: InputPin, C: InputPin, D: InputPin>(
pin_a: impl Peripheral<P = A> + 'a,
pin_b: impl Peripheral<P = B> + 'a,
pin_c: impl Peripheral<P = C> + 'a,
pin_d: impl Peripheral<P = D> + 'a,
) -> Self {
let input_config = InputConfig::default().with_pull(Pull::Down);
return StepperDecoder {
inputs: [
Input::new(pin_a, input_config),
Input::new(pin_b, input_config),
Input::new(pin_c, input_config),
Input::new(pin_d, input_config),
],
last_state: INVALID_STATE,
half_steps_delta: 0,
total_half_steps: 0,
invalid_states: 0,
invalid_transistions: 0,
};
}
pub async fn run(self: &mut Self) {
loop {
self.wait_for_changes_debounced(10).await;
let current_state = self.read_state();
let delta = TRANSITION_LUT[self.last_state as usize][current_state as usize];
if delta != INVALID_TRANSITION {
self.half_steps_delta += delta as i32;
self.total_half_steps += delta.abs() as u32;
} else if self.last_state != INVALID_STATE {
self.invalid_transistions += 1;
// log ?
}
// resync the state, even if transistion is invalid
if is_valid_state(current_state) {
self.last_state = current_state;
} else {
self.invalid_states += 1;
// log ?
}
}
}
async fn wait_for_input_changes(self: &mut Self) {
let futures = self.inputs.each_mut().map(|p| p.wait_for_any_edge());
select_array(futures).await;
}
async fn wait_for_changes_debounced(self: &mut Self, millis: u64) {
loop {
let res = select(self.wait_for_input_changes(), Timer::after_millis(millis)).await;
if let Either::Second(_) = res {
break;
}
}
}
#[rustfmt::skip]
fn read_state(self: &Self) -> u8 {
let mut state: u8 = 0;
if self.inputs[0].is_high() { state |= STATE_A; }
if self.inputs[1].is_high() { state |= STATE_B; }
if self.inputs[2].is_high() { state |= STATE_C; }
if self.inputs[3].is_high() { state |= STATE_D; }
return state;
}
}
#[inline(always)]
fn is_valid_state(state: u8) -> bool {
matches!(state, STATE_A | STATE_AB | STATE_B | STATE_BC | STATE_C | STATE_CD | STATE_D | STATE_DA)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment