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}