Skip to content

Commit

Permalink
fix: api so support for angular velocity works and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ost-ing committed Jul 15, 2024
1 parent d36562c commit 3eb1204
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 118 deletions.
8 changes: 3 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rotary-encoder-embedded"
version = "0.2.0"
version = "0.3.0"
authors = ["Oliver Stenning <[email protected]>"]
edition = "2018"
description = "A rotary-encoder library built with embedded-hal"
Expand All @@ -13,7 +13,5 @@ repository = "https://github.com/ostenning/rotary-encoder-embedded"
[dependencies]
embedded-hal = { version = "0.2.7", features = ["unproven"] }

[features]
default = ["standard"]
standard = []
angular-velocity = []
[dev-dependencies]
embedded-hal-mock = { version = "0.10.0", features = ["eh0"] }
26 changes: 12 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,36 @@ A rotary encoder library for embedded rust applications
- Suitable for gray-code incremental encoders
- Implemented with embedded-hal (https://docs.rs/embedded-hal/0.2.7/embedded_hal)

## modes

The `RotaryEncoder` can operate in a number of different modes, these modes provide different types of feature sets and are individually gated behind feature flags to keep the binary size to a minimum.
The following modes are currently provided:

| Feature flag | Mode | Desc. |
| ------------- |----------------| -------|
| `standard` | `StandardMode` | Uses a state machine for transitions |
| `angular-velocity` | `AngularVelocityMode` | Same as `standard` but with additional angular-velocity calculations |

## `StandardMode` example

```rust
fn main() -> ! {
// Configure DT and CLK pins, typically pullup input
let rotary_dt = gpio_pin_1.into_pull_up_input()
let rotary_clk = gpio_pin_2.into_pull_up_input();

// Initialize the rotary encoder
let mut rotary_encoder = RotaryEncoder::new(
rotary_dt,
rotary_clk,
).into_standard_mode();

// Now you can update the state of the rotary encoder and get a direction value. Call this from an update routine, timer task or interrupt
let _dir = rotary_encoder.update();

// Alternatively if you want to access the encoder without embedded-hal pin traits and use boolean states, you can use the mode directly:
let mut raw_encoder = StandardMode::new();
let _dir = raw_encoder.update(true, false);

// ...timer initialize at 900Hz to poll the rotary encoder
loop {}
}

fn timer_interrupt_handler() {
// ... get rotary encoder
let rotary_encoder = ...
// Update the encoder, which will compute its direction
rotary_encoder.update();
match rotary_encoder.direction() {

// Update the encoder, which will compute and return its direction
match rotary_encoder.update() {
Direction::Clockwise => {
// Increment some value
}
Expand Down
85 changes: 54 additions & 31 deletions src/angular_velocity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use embedded_hal::digital::v2::InputPin;

use crate::Direction;
use crate::RotaryEncoder;
use embedded_hal::digital::v2::InputPin;

/// Default angular velocity increasing factor
const DEFAULT_VELOCITY_INC_FACTOR: f32 = 0.2;
Expand Down Expand Up @@ -65,14 +66,45 @@ where
/// when either the DT or CLK pins state changes. This function will update the RotaryEncoder's
/// Direction and current Angular Velocity.
/// * `current_time` - Current timestamp in ms (strictly monotonously increasing)
pub fn update(&mut self, current_time_millis: u64) {
self.mode.pin_state[0] =
(self.mode.pin_state[0] << 1) | self.pin_dt.is_high().unwrap_or_default() as u8;
self.mode.pin_state[1] =
(self.mode.pin_state[1] << 1) | self.pin_clk.is_high().unwrap_or_default() as u8;
pub fn update(&mut self, current_time_millis: u64) -> Direction {
self.mode.update(self.pin_dt.is_high().unwrap_or_default(), self.pin_clk.is_high().unwrap_or_default(), current_time_millis)
}

let a = self.mode.pin_state[0] & PIN_MASK;
let b = self.mode.pin_state[1] & PIN_MASK;
/// Returns the current angular velocity of the RotaryEncoder
/// The Angular Velocity is a value between 0.0 and 1.0
/// This is useful for incrementing/decrementing a value in an exponential fashion
pub fn velocity(&self) -> Velocity {
self.mode.velocity
}
}

impl AngularVelocityMode {
/// Initialises the AngularVelocityMode
pub fn new() -> Self {
Self {
pin_state: [0xFF, 2],
velocity: 0.0,
previous_time_millis: 0,
velocity_action_ms: DEFAULT_VELOCITY_ACTION_MS,
velocity_dec_factor: DEFAULT_VELOCITY_DEC_FACTOR,
velocity_inc_factor: DEFAULT_VELOCITY_INC_FACTOR,
}
}

/// Update to determine the direction
pub fn update(
&mut self,
dt_state: bool,
clk_state: bool,
current_time_millis: u64,
) -> Direction {
self.pin_state[0] =
(self.pin_state[0] << 1) | dt_state as u8;
self.pin_state[1] =
(self.pin_state[1] << 1) | clk_state as u8;

let a = self.pin_state[0] & PIN_MASK;
let b = self.pin_state[1] & PIN_MASK;

let mut dir: Direction = Direction::None;

Expand All @@ -81,28 +113,27 @@ where
} else if b == PIN_EDGE && a == 0x00 {
dir = Direction::Clockwise;
}
self.direction = dir;

if self.direction != Direction::None {
if current_time_millis - self.mode.previous_time_millis < self.mode.velocity_action_ms
&& self.mode.velocity < 1.0
if dir != Direction::None {
if current_time_millis - self.previous_time_millis < self.velocity_action_ms
&& self.velocity < 1.0
{
self.mode.velocity += self.mode.velocity_inc_factor;
if self.mode.velocity > 1.0 {
self.mode.velocity = 1.0;
self.velocity += self.velocity_inc_factor;
if self.velocity > 1.0 {
self.velocity = 1.0;
}
}
return;
} else {
self.previous_time_millis = current_time_millis;
}

self.mode.previous_time_millis = current_time_millis;
dir
}
}

/// Returns the current angular velocity of the RotaryEncoder
/// The Angular Velocity is a value between 0.0 and 1.0
/// This is useful for incrementing/decrementing a value in an exponential fashion
pub fn velocity(&self) -> Velocity {
self.mode.velocity
impl Default for AngularVelocityMode {
fn default() -> Self {
Self::new()
}
}

Expand All @@ -116,15 +147,7 @@ where
RotaryEncoder {
pin_dt: self.pin_dt,
pin_clk: self.pin_clk,
mode: AngularVelocityMode {
pin_state: [0xFF, 2],
velocity: 0.0,
previous_time_millis: 0,
velocity_action_ms: DEFAULT_VELOCITY_ACTION_MS,
velocity_dec_factor: DEFAULT_VELOCITY_DEC_FACTOR,
velocity_inc_factor: DEFAULT_VELOCITY_INC_FACTOR,
},
direction: Direction::None,
mode: AngularVelocityMode::new(),
}
}
}
90 changes: 46 additions & 44 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
#![deny(warnings)]
#![no_std]

/// Angular velocity mode
#[cfg(feature = "angular-velocity")]
use embedded_hal::digital::v2::InputPin;

/// Angular velocity api
pub mod angular_velocity;
/// Standard mode
#[cfg(feature = "standard")]
/// Standard api
pub mod standard;

use embedded_hal::digital::v2::InputPin;

/// Direction of Rotary Encoder rotation
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Direction {
Expand All @@ -27,26 +25,17 @@ pub enum Direction {

/// Rotary Encoder
pub struct RotaryEncoder<LOGIC, DT, CLK> {
pub struct RotaryEncoder<MODE, DT, CLK> {
mode: MODE,
pin_dt: DT,
pin_clk: CLK,
logic: LOGIC,
}

/// Core logic interface definition.
pub trait RotaryEncoderLogic {
/// Updates the `RotaryEncoder`, updating the `direction` property
fn update(&mut self, pin_dt: bool, pin_clk: bool);
/// Gets the last detected direction
fn direction(&self) -> Direction;
}

/// Common
impl<LOGIC, DT, CLK> RotaryEncoder<LOGIC, DT, CLK>
impl<MODE, DT, CLK> RotaryEncoder<MODE, DT, CLK>
where
DT: InputPin,
CLK: InputPin,
LOGIC: RotaryEncoderLogic,
{
/// Borrow a mutable reference to the underlying InputPins. This is useful for clearing hardware interrupts.
pub fn pins_mut(&mut self) -> (&mut DT, &mut CLK) {
Expand All @@ -58,31 +47,15 @@ where
(self.pin_dt, self.pin_clk)
}

/// Returns the current Direction of the RotaryEncoder
pub fn direction(&self) -> Direction {
self.logic.direction()
}
/// a
pub fn update(&mut self) {
self.logic.update(
self.pin_dt.is_high().unwrap_or_default(),
self.pin_clk.is_high().unwrap_or_default(),
);
/// Borrow the underlying mode
pub fn mode(&mut self) -> &mut MODE {
&mut self.mode
}
}

/// InitializeMode
/// This is the plain `RotaryEncoder` with no business logic attached. In order to use the `RotaryEncoder` it must be initialized to a valid `Mode`
pub struct InitalizeMode {}

/// Empty core logic implementation for InitalizeMode
impl RotaryEncoderLogic for InitalizeMode {
fn update(&mut self, _pin_dt: bool, _pin_clk: bool) {}

fn direction(&self) -> Direction {
Direction::None
}
}
pub struct InitalizeMode;

impl<DT, CLK> RotaryEncoder<InitalizeMode, DT, CLK>
where
Expand All @@ -94,14 +67,43 @@ where
RotaryEncoder {
pin_dt,
pin_clk,
logic: InitalizeMode {},
mode: InitalizeMode {},
}
}
}

/// Generic struct for the core logic
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RotaryEncoderCore<MODE> {
direction: Direction,
mode: MODE,
#[cfg(test)]
mod test {
use crate::{angular_velocity::AngularVelocityMode, standard::StandardMode, RotaryEncoder};
use embedded_hal_mock::eh0::pin::Mock;

fn get_pins() -> (Mock, Mock) {
(Mock::new([]), Mock::new([]))
}

#[test]
fn standard_mode_api() {
let (dt, clk) = get_pins();

// Standard mode can be used with embedded-hal pins
let mut encoder = RotaryEncoder::new(dt, clk).into_standard_mode();
let _dir = encoder.update();

// Or it can be used directly, bypassing the pins
let mut raw_encoder = StandardMode::new();
let _dir = raw_encoder.update(true, false);
}

#[test]
fn angular_velocity_mode_api() {
let (dt, clk) = get_pins();

// Angular velocity mode can be used with embedded-hal pins
let mut encoder = RotaryEncoder::new(dt, clk).into_angular_velocity_mode();
let _dir = encoder.update(0);

// Or it can be used directly, bypassing the pins
let mut raw_encoder = AngularVelocityMode::new();
let _dir = raw_encoder.update(true, false, 100);
}
}
Loading

0 comments on commit 3eb1204

Please sign in to comment.