Created
March 26, 2025 16:24
-
-
Save bagbag/b210d759f9775d4898b2be1464f4a288 to your computer and use it in GitHub Desktop.
stepper decoder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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