-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wheel_encoder_full_resolution * Update wheel_encoder.rs Full resolution wheel encoder * Update wheel_encoder.rs - clean * Update wheel_encoder.rs get_velocity and read added to match previous code. * handling Err for wheel_encoder * Err handling in state_machine.rs * fixing mismatched error types * -cleanup * sm - cleanup * measure function only * simplified to a single function only * fixing measure call removed log import * threshold only using distance values * -cleanup removed .read() * cleanup typo * chk * import cleanup * import cleanup * time import * Changing pin numbers * Upgrade RPPAL to v0.18.0 for `InputPin` with RPi 5 - `Pin::into_input` did not work properly with Raspberry Pi 5 * Reimplement encoder transitions and measurement * Fix wheel encoder initialization in main * Fix lint issues * Add resting velocity and wheel diameter * Clean up encoder enums * Remove other full-resolution copy --------- Co-authored-by: ryescholin <[email protected]> Co-authored-by: Taesung Hwang <[email protected]> Co-authored-by: Taesung Hwang <[email protected]>
- Loading branch information
1 parent
d221109
commit c0e5283
Showing
5 changed files
with
148 additions
and
58 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,166 @@ | ||
use std::time::Instant; | ||
use std::{ops::Sub, time::Instant}; | ||
|
||
use rppal::gpio::{Gpio, InputPin, Level}; | ||
|
||
use crate::utils::GpioPins; | ||
|
||
const WHEEL_DIAMETER: f32 = 0.25; // feet | ||
const ENCODER_RESOLUTION: f32 = 16.0; // pulses per revolution | ||
const DISTANCE_PER_COUNT: f32 = WHEEL_DIAMETER * std::f32::consts::PI / ENCODER_RESOLUTION; // feet | ||
|
||
#[derive(Clone, Copy, num_enum::FromPrimitive, num_enum::IntoPrimitive)] | ||
#[repr(i8)] | ||
enum EncoderState { | ||
A = 0b00, | ||
B = 0b01, | ||
C = 0b11, | ||
D = 0b10, | ||
#[num_enum(catch_all)] | ||
Unknown(i8) = -1, | ||
} | ||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)] | ||
#[repr(i8)] | ||
enum EncoderDiff { | ||
Backwards = -1, | ||
Stationary = 0, | ||
Forwards = 1, | ||
Undersampling = 2, | ||
#[num_enum(catch_all)] | ||
Unknown(i8), | ||
} | ||
|
||
impl From<EncoderDiff> for i16 { | ||
fn from(value: EncoderDiff) -> Self { | ||
i16::from(i8::from(value)) | ||
} | ||
} | ||
|
||
impl From<EncoderDiff> for f32 { | ||
fn from(value: EncoderDiff) -> Self { | ||
f32::from(i8::from(value)) | ||
} | ||
} | ||
|
||
impl Sub for EncoderState { | ||
type Output = EncoderDiff; | ||
|
||
fn sub(self, other: Self) -> Self::Output { | ||
let diff = (i8::from(self) - i8::from(other) + 5) % 4 - 1; | ||
EncoderDiff::from(diff) | ||
} | ||
} | ||
|
||
/// Encode wheel encoder state as gray code 00, 01, 11, 10 | ||
fn encode_state(a: Level, b: Level) -> EncoderState { | ||
let state = ((a as i8) << 1) + (a as i8 ^ b as i8); | ||
EncoderState::from(state) | ||
} | ||
|
||
pub struct WheelEncoder { | ||
counter: f32, | ||
counter: i16, | ||
pin_a: InputPin, | ||
pin_b: InputPin, | ||
a_last_read: Level, | ||
b_last_read: Level, | ||
last_distance: f32, | ||
last_velocity_time: Instant, | ||
last_state: EncoderState, | ||
last_time: Instant, | ||
velocity: f32, | ||
} | ||
|
||
impl WheelEncoder { | ||
pub fn new() -> Self { | ||
let gpio = Gpio::new().unwrap(); | ||
let pin_a = gpio | ||
.get(GpioPins::WHEEL_ENCODER_A.into()) | ||
.unwrap() | ||
.into_input(); | ||
let pin_b = gpio | ||
.get(GpioPins::WHEEL_ENCODER_B.into()) | ||
.unwrap() | ||
.into_input(); | ||
|
||
let initial_state = encode_state(pin_a.read(), pin_b.read()); | ||
|
||
WheelEncoder { | ||
counter: 0.0, | ||
pin_a: gpio | ||
.get(GpioPins::WHEEL_ENCODER_A.into()) | ||
.unwrap() | ||
.into_input(), | ||
pin_b: gpio | ||
.get(GpioPins::WHEEL_ENCODER_B.into()) | ||
.unwrap() | ||
.into_input(), | ||
a_last_read: Level::High, | ||
b_last_read: Level::Low, | ||
last_distance: 0.0, | ||
last_velocity_time: Instant::now(), | ||
counter: 0, | ||
last_time: Instant::now(), | ||
last_state: initial_state, | ||
pin_a, | ||
pin_b, | ||
velocity: 0.0, | ||
} | ||
} | ||
|
||
pub fn read(&mut self) -> f32 { | ||
let a_state = self.pin_a.read(); | ||
let b_state = self.pin_b.read(); | ||
|
||
if (a_state != self.a_last_read || b_state != self.b_last_read) && b_state != a_state { | ||
self.counter += 1.0; | ||
|
||
let current_time = Instant::now(); | ||
let distance = (self.counter / 16.0) * 3.0; | ||
let velocity_elapsed = current_time | ||
.duration_since(self.last_velocity_time) | ||
.as_secs_f32(); | ||
|
||
if velocity_elapsed >= 0.1 { | ||
let distance_delta = distance - self.last_distance; | ||
self.velocity = distance_delta / velocity_elapsed; | ||
self.last_velocity_time = current_time; | ||
self.last_distance = distance; | ||
} | ||
pub fn measure(&mut self) -> Result<f32, &str> { | ||
let state = self.read_state(); | ||
|
||
let inc = state - self.last_state; | ||
|
||
if inc == EncoderDiff::Undersampling { | ||
return Err("Wheel encoder faulted"); | ||
} | ||
|
||
self.a_last_read = a_state; | ||
self.b_last_read = b_state; | ||
let time = Instant::now(); | ||
let dt = time.duration_since(self.last_time).as_secs_f32(); | ||
|
||
(self.counter / 16.0) * 3.0 | ||
} | ||
if inc != EncoderDiff::Stationary { | ||
self.velocity = DISTANCE_PER_COUNT * f32::from(i8::from(inc)) / dt; | ||
self.last_time = time; | ||
} | ||
|
||
// When exceeding expected time to next increment, decrease velocity | ||
if self.velocity * dt > DISTANCE_PER_COUNT { | ||
self.velocity = DISTANCE_PER_COUNT * self.velocity.signum() / dt; | ||
} | ||
|
||
self.counter += i16::from(inc); | ||
self.last_state = state; | ||
|
||
pub fn _reset(&mut self) { | ||
self.counter = 0.0; | ||
self.last_distance = 0.0; | ||
self.velocity = 0.0; | ||
self.last_velocity_time = Instant::now(); | ||
Ok(f32::from(self.counter) * DISTANCE_PER_COUNT) | ||
} | ||
|
||
pub fn get_velocity(&self) -> f32 { | ||
self.velocity | ||
} | ||
|
||
fn read_state(&self) -> EncoderState { | ||
encode_state(self.pin_a.read(), self.pin_b.read()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn encoder_diff_stationary() { | ||
let state_1 = encode_state(Level::Low, Level::High); | ||
let state_2 = encode_state(Level::Low, Level::High); | ||
assert_eq!(state_2 - state_1, EncoderDiff::Stationary); | ||
} | ||
|
||
#[test] | ||
fn encoder_diff_forwards() { | ||
let state_1 = encode_state(Level::High, Level::Low); | ||
let state_2 = encode_state(Level::Low, Level::Low); | ||
let diff = state_2 - state_1; | ||
assert_eq!(diff, EncoderDiff::Forwards); | ||
assert_eq!(i8::from(diff), 1); | ||
} | ||
|
||
#[test] | ||
fn encoder_diff_backwards() { | ||
let state_1 = encode_state(Level::High, Level::Low); | ||
let state_2 = encode_state(Level::High, Level::High); | ||
let diff = state_2 - state_1; | ||
assert_eq!(diff, EncoderDiff::Backwards); | ||
assert_eq!(i8::from(diff), -1); | ||
assert_eq!(f32::from(diff), -1.0); | ||
} | ||
|
||
#[test] | ||
fn encoder_diff_undersampling() { | ||
let state_1 = encode_state(Level::High, Level::Low); | ||
let state_2 = encode_state(Level::Low, Level::High); | ||
assert_eq!(state_2 - state_1, EncoderDiff::Undersampling); | ||
} | ||
} |
This file contains 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
This file contains 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