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}