ft3x68_rs/lib.rs
1//! # FT3x68 Touch Controller Driver Crate
2//!
3//! A driver for the FT3x68 touch controller(s), providing functionality to read touch points, gestures, and manage power modes.
4//!
5//! There is limited public information relative to the FocalTech FT touch controllers. As such, much of the operational infromation is not documented in datasheets.
6//! This driver is largely based on the Arduino_DriveBus repo found here: <https://github.com/Xk-w/Arduino_DriveBus/tree/master>.
7//!
8//!
9//! This driver is `embedded-hal` compatible and provides a generic interface for the implementing the device reset functionality.
10//! This is because the reset pin can be controlled via a GPIO port or an I2C I/O expander-controlled pin.
11//!
12//! Some drivers include an INT pin indicating touch events, but this driver does not use it. If interrupts need to be supported, the user can attach the pin output to an interrupt and implement their own callback.
13//!
14//! The driver currenlty supports both the FT3168 and FT3268 devices.
15//!
16//! ## Usage
17//!
18//! 1. Implement the `ResetInterface` trait for your specific reset mechanism.
19//! 2. Create an instance of the `Ft3x68Driver`.
20//! 3. Initialize the touch driver.
21//! 4. Use the provided methods to read touch points and/or gestures.
22//!
23//! ```rust
24//! // Initialize GPIO Reset Pin with pin or I2C instance
25//! // ResetDriver would be a type that implements the `ResetInterface` trait.
26//! let reset = ResetDriver::new(PinInstance);
27//!
28//! let mut touch = Ft3x68Driver::new(
29//! I2CInstace,
30//! FT3168_DEVICE_ADDRESS,
31//! reset,
32//! delay,
33//! );
34//!
35//! touch
36//! .initialize()
37//! .expect("Failed to initialize touch driver");
38//!
39//! loop {
40//! touch
41//! .touch1()
42//! .map(|touch| println!("Touch 1: {:?}", touch))
43//! .unwrap();
44//! }
45//! ```
46//!
47//! Notes:
48//! - To detect gestures, the gesture mode must be enabled using the `set_gesture_mode` method.
49//! - If the reset pin is controlled via an I2C GPIO expander sharing the same bus with the touch driver, you should use the `embedded_hal_bus` to manage multiple instances of I2C. Refer to the examples folder to see how that looks like.
50
51#![no_std]
52use embedded_hal::delay::DelayNs;
53use embedded_hal::i2c::I2c;
54use heapless::Vec;
55
56// Constants for FT3x68 device addresses and registers
57pub const FT3168_DEVICE_ADDRESS: u8 = 0x38;
58pub const FT3268_DEVICE_ADDRESS: u8 = 0x38;
59
60const FT3X68_RD_DEVICE_GESTUREID: u8 = 0xD3;
61const FT3X68_RD_DEVICE_FINGERNUM: u8 = 0x02;
62const FT3X68_RD_DEVICE_X1POSH: u8 = 0x03;
63const FT3X68_RD_DEVICE_X1POSL: u8 = 0x04;
64const FT3X68_RD_DEVICE_Y1POSH: u8 = 0x05;
65const FT3X68_RD_DEVICE_Y1POSL: u8 = 0x06;
66const FT3X68_RD_DEVICE_X2POSH: u8 = 0x09;
67const FT3X68_RD_DEVICE_X2POSL: u8 = 0x0A;
68const FT3X68_RD_DEVICE_Y2POSH: u8 = 0x0B;
69const FT3X68_RD_DEVICE_Y2POSL: u8 = 0x0C;
70const FT3X68_RD_WR_DEVICE_GESTUREID_MODE: u8 = 0xD0;
71const FT3X68_RD_WR_DEVICE_POWER_MODE: u8 = 0xA5;
72const FT3X68_RD_WR_DEVICE_PROXIMITY_SENSING_MODE: u8 = 0xB0;
73const FT3X68_RD_DEVICE_ID: u8 = 0xA0;
74
75/// Power modes for the touch device.
76#[derive(Clone, Copy, Debug)]
77#[repr(u8)]
78pub enum PowerMode {
79 Active = 0x00,
80 Monitor = 0x01,
81 Standby = 0x02,
82 Hibernate = 0x03,
83}
84
85/// Gesture IDs returned by the device.
86#[derive(Debug, PartialEq, Eq)]
87pub enum Gesture {
88 None,
89 SwipeLeft,
90 SwipeRight,
91 SwipeUp,
92 SwipeDown,
93 DoubleClick,
94 Unknown(u8),
95}
96
97/// Represents a single touch point.
98#[derive(Debug, Default)]
99pub struct TouchPoint {
100 pub x: u16,
101 pub y: u16,
102}
103
104/// Represents the touch state - either active with coordinates or released
105#[derive(Debug)]
106pub enum TouchState {
107 /// Touch is active with coordinates
108 Pressed(TouchPoint),
109 /// Touch was released (no active touches)
110 Released,
111}
112
113/// Trait for controlling the FT3x658 hardware reset pin.
114pub trait ResetInterface {
115 /// The specific error type for this reset implementation.
116 type Error;
117
118 /// Performs the hardware reset sequence for the FT3x68 device.
119 /// This could be a GPIO port or an I2C expander-controlled pin.
120 /// Recommended Implmentation --> HIGH delay 1ms --> LOW delay 20ms --> HIGH delay 50ms
121 fn reset(&mut self) -> Result<(), Self::Error>;
122}
123
124/// Driver Errors
125#[derive(Debug)]
126pub enum DriverError<ResetError, I2cError> {
127 /// Error originating from the display interface (QSPI/SPI/I2C).
128 I2cError(I2cError),
129 /// Error originating from the reset pin control.
130 ResetError(ResetError),
131}
132
133/// FT3x68 touch driver
134pub struct Ft3x68Driver<I2C, D, RST> {
135 i2c: I2C,
136 device_address: u8,
137 delay: D,
138 reset: RST,
139}
140
141impl<I2C, D, RST> Ft3x68Driver<I2C, D, RST>
142where
143 I2C: I2c,
144 D: DelayNs,
145 RST: ResetInterface,
146{
147 /// Creates a new instance of the FT3x68 driver.
148 pub fn new(i2c: I2C, device_address: u8, reset: RST, delay: D) -> Self {
149 Ft3x68Driver {
150 i2c,
151 device_address,
152 delay,
153 reset,
154 }
155 }
156
157 /// Initializes the device defaulting to the Active power mode.
158 /// This method performs a hardware reset and sets the device to the specified power mode.
159 /// The hardware reset is perfromed by the `reset` method of the `ResetInterface` trait implementation.
160 pub fn initialize(&mut self) -> Result<(), DriverError<RST::Error, I2C::Error>> {
161 // Perform hardware reset
162 self.reset.reset().map_err(DriverError::ResetError)?;
163 self.delay.delay_ms(50);
164 // Set the device to active power mode
165 self.i2c
166 .write(
167 self.device_address,
168 &[FT3X68_RD_WR_DEVICE_POWER_MODE, PowerMode::Active as u8],
169 )
170 .map_err(DriverError::I2cError)?;
171 self.delay.delay_ms(20);
172 Ok(())
173 }
174
175 /// Initializes the device with a specific power mode.
176 /// This method performs a hardware reset and sets the device to the specified power mode.
177 /// The hardware reset is perfromed by the `reset` method of the `ResetInterface` trait implementation.
178 pub fn initialize_with_mode(
179 &mut self,
180 mode: PowerMode,
181 ) -> Result<(), DriverError<RST::Error, I2C::Error>> {
182 // Perform hardware reset
183 self.reset.reset().map_err(DriverError::ResetError)?;
184 self.delay.delay_ms(50);
185 // Set the device to active power mode
186 self.i2c
187 .write(
188 self.device_address,
189 &[FT3X68_RD_WR_DEVICE_POWER_MODE, mode as u8],
190 )
191 .map_err(DriverError::I2cError)?;
192 self.delay.delay_ms(20);
193 Ok(())
194 }
195
196 /// Reads the device ID.
197 /// 0x00:FT6456 0x04:FT3268 0x01:FT3067 0x05:FT3368 0x02:FT3068 0x03:FT3168
198 pub fn read_device_id(&mut self) -> Result<i32, I2C::Error> {
199 let mut buffer = [0u8; 1];
200 self.i2c
201 .write_read(self.device_address, &[FT3X68_RD_DEVICE_ID], &mut buffer)?;
202 Ok(buffer[0] as i32)
203 }
204
205 /// Sets the power mode of the device.
206 pub fn set_power_mode(&mut self, mode: PowerMode) -> Result<(), I2C::Error> {
207 self.i2c.write(
208 self.device_address,
209 &[FT3X68_RD_WR_DEVICE_POWER_MODE, mode as u8],
210 )
211 }
212
213 /// Enables or disables the proximity sensing mode.
214 pub fn set_proximity_sensing_mode(&mut self, enable: bool) -> Result<(), I2C::Error> {
215 self.i2c.write(
216 self.device_address,
217 &[FT3X68_RD_WR_DEVICE_PROXIMITY_SENSING_MODE, enable as u8],
218 )
219 }
220
221 /// Enables or disables the gesture recognition mode.
222 pub fn set_gesture_mode(&mut self, enable: bool) -> Result<(), I2C::Error> {
223 self.i2c.write(
224 self.device_address,
225 &[FT3X68_RD_WR_DEVICE_GESTUREID_MODE, enable as u8],
226 )
227 }
228
229 /// Reads the detected gesture.
230 pub fn read_gesture(&mut self) -> Result<Gesture, I2C::Error> {
231 let mut buffer = [0u8; 1];
232 self.i2c.write_read(
233 self.device_address,
234 &[FT3X68_RD_DEVICE_GESTUREID],
235 &mut buffer,
236 )?;
237 Ok(match buffer[0] {
238 0x00 => Gesture::None,
239 0x20 => Gesture::SwipeLeft,
240 0x21 => Gesture::SwipeRight,
241 0x22 => Gesture::SwipeUp,
242 0x23 => Gesture::SwipeDown,
243 0x24 => Gesture::DoubleClick,
244 other => Gesture::Unknown(other),
245 })
246 }
247
248 /// Reads the number of active touch points.
249 pub fn finger_number(&mut self) -> Result<u8, I2C::Error> {
250 let mut buffer = [0u8; 1];
251 self.i2c.write_read(
252 self.device_address,
253 &[FT3X68_RD_DEVICE_FINGERNUM],
254 &mut buffer,
255 )?;
256 Ok(buffer[0])
257 }
258
259 /// Reads the state of the first touch point or detects a release.
260 pub fn touch1(&mut self) -> Result<TouchState, I2C::Error> {
261 let fingers = self.finger_number()?;
262 if fingers == 0 {
263 return Ok(TouchState::Released);
264 }
265
266 let mut data = [0u8; 4];
267 self.i2c
268 .write_read(self.device_address, &[FT3X68_RD_DEVICE_X1POSH], &mut data)?;
269
270 let x = ((data[0] as u16 & 0x0F) << 8) | data[1] as u16;
271 let y = ((data[2] as u16 & 0x0F) << 8) | data[3] as u16;
272
273 Ok(TouchState::Pressed(TouchPoint { x, y }))
274 }
275
276 /// Reads the state of the second touch point or detects if no second touch exists.
277 pub fn touch2(&mut self) -> Result<TouchState, I2C::Error> {
278 let fingers = self.finger_number()?;
279 if fingers < 2 {
280 return Ok(TouchState::Released);
281 }
282
283 let mut data = [0u8; 4];
284 self.i2c
285 .write_read(self.device_address, &[FT3X68_RD_DEVICE_X2POSH], &mut data)?;
286
287 let x = ((data[0] as u16 & 0x0F) << 8) | data[1] as u16;
288 let y = ((data[2] as u16 & 0x0F) << 8) | data[3] as u16;
289
290 Ok(TouchState::Pressed(TouchPoint { x, y }))
291 }
292
293 /// Returns all active touch points up to the maximum supported (typically 2).
294 pub fn get_touches(&mut self) -> Result<Vec<TouchPoint, 2>, I2C::Error> {
295 let mut touches = Vec::new();
296 let fingers = self.finger_number()?;
297
298 if fingers >= 1 {
299 if let TouchState::Pressed(point) = self.touch1()? {
300 touches.push(point).ok(); // Ignore error if Vec is full
301 }
302 }
303
304 if fingers >= 2 {
305 if let TouchState::Pressed(point) = self.touch2()? {
306 touches.push(point).ok(); // Ignore error if Vec is full
307 }
308 }
309
310 Ok(touches)
311 }
312}