Skip to main content

modulino/
joystick.rs

1//! Modulino Joystick driver.
2//!
3//! The Modulino Joystick module is an analog joystick with a push button.
4
5use crate::{addresses, I2cDevice, Result};
6use embedded_hal::i2c::I2c;
7
8/// Driver for the Modulino Joystick module.
9///
10/// The joystick reports X and Y values in the range -128 to 127,
11/// where (0, 0) is the center position.
12///
13/// # Example
14///
15/// ```rust,ignore
16/// use modulino::Joystick;
17///
18/// let mut joystick = Joystick::new(i2c)?;
19///
20/// loop {
21///     joystick.update()?;
22///     
23///     let x = joystick.x();
24///     let y = joystick.y();
25///     
26///     if joystick.button_pressed() {
27///         println!("Button pressed at position ({}, {})", x, y);
28///     }
29/// }
30/// ```
31pub struct Joystick<I2C> {
32    device: I2cDevice<I2C>,
33    x: i8,
34    y: i8,
35    button_pressed: bool,
36    deadzone: u8,
37}
38
39impl<I2C, E> Joystick<I2C>
40where
41    I2C: I2c<Error = E>,
42{
43    /// Default deadzone threshold.
44    pub const DEFAULT_DEADZONE: u8 = 10;
45
46    /// Create a new Joystick instance with the default address.
47    pub fn new(i2c: I2C) -> Result<Self, E> {
48        Self::new_with_address(i2c, addresses::JOYSTICK)
49    }
50
51    /// Create a new Joystick instance with a custom address.
52    pub fn new_with_address(i2c: I2C, address: u8) -> Result<Self, E> {
53        let mut joystick = Self {
54            device: I2cDevice::new(i2c, address),
55            x: 0,
56            y: 0,
57            button_pressed: false,
58            deadzone: Self::DEFAULT_DEADZONE,
59        };
60
61        // Read initial state
62        joystick.update()?;
63
64        Ok(joystick)
65    }
66
67    /// Get the I2C address.
68    pub fn address(&self) -> u8 {
69        self.device.address
70    }
71
72    /// Apply deadzone logic to normalize coordinates.
73    fn normalize_coordinate(&self, raw: u8) -> i8 {
74        // Convert from 0-255 range to -128 to 127 range
75        let centered = (raw as i16) - 128;
76
77        // Apply deadzone
78        if centered.abs() < self.deadzone as i16 {
79            0
80        } else {
81            centered as i8
82        }
83    }
84
85    /// Update the joystick state.
86    ///
87    /// This should be called periodically to read the latest values.
88    /// Returns `true` if the state has changed.
89    pub fn update(&mut self) -> Result<bool, E> {
90        let previous_x = self.x;
91        let previous_y = self.y;
92        let previous_button = self.button_pressed;
93
94        let mut buf = [0u8; 4]; // 1 pinstrap + 2 axes + 1 button
95        self.device.read(&mut buf)?;
96
97        // Skip first byte (pinstrap address)
98        let raw_x = buf[1];
99        let raw_y = buf[2];
100
101        self.x = self.normalize_coordinate(raw_x);
102        self.y = self.normalize_coordinate(raw_y);
103        self.button_pressed = buf[3] != 0;
104
105        Ok(self.x != previous_x || self.y != previous_y || self.button_pressed != previous_button)
106    }
107
108    /// Get the X-axis value (-128 to 127).
109    pub fn x(&self) -> i8 {
110        self.x
111    }
112
113    /// Get the Y-axis value (-128 to 127).
114    pub fn y(&self) -> i8 {
115        self.y
116    }
117
118    /// Get both axis values as a tuple.
119    pub fn position(&self) -> (i8, i8) {
120        (self.x, self.y)
121    }
122
123    /// Check if the button is pressed.
124    pub fn button_pressed(&self) -> bool {
125        self.button_pressed
126    }
127
128    /// Get the deadzone threshold.
129    pub fn deadzone(&self) -> u8 {
130        self.deadzone
131    }
132
133    /// Set the deadzone threshold.
134    ///
135    /// Values within this distance from center (0, 0) will be reported as 0.
136    pub fn set_deadzone(&mut self, deadzone: u8) {
137        self.deadzone = deadzone;
138    }
139
140    /// Check if the joystick is in the center position (within deadzone).
141    pub fn is_centered(&self) -> bool {
142        self.x == 0 && self.y == 0
143    }
144
145    /// Get the magnitude of joystick displacement from center.
146    pub fn magnitude(&self) -> f32 {
147        let x = self.x as f32;
148        let y = self.y as f32;
149        libm::sqrtf(x * x + y * y)
150    }
151
152    /// Get the angle of joystick displacement in radians.
153    ///
154    /// Returns 0 when centered. Angle is measured counter-clockwise from the positive X-axis.
155    pub fn angle(&self) -> f32 {
156        if self.is_centered() {
157            0.0
158        } else {
159            libm::atan2f(self.y as f32, self.x as f32)
160        }
161    }
162
163    /// Release the I2C bus.
164    pub fn release(self) -> I2C {
165        self.device.release()
166    }
167}