Skip to main content

cst226_rs/
lib.rs

1//! # CST226 Touch Controller Driver Crate
2//!
3//! A `no_std` driver for the CST226 touch controller, providing functionality to read touch points and manage power modes.
4//!
5//! This driver is based on publicly available C++ drivers, as official datasheets for the CST226 are not widely available.
6//! It is `embedded-hal` compatible and provides a generic interface for the device's hardware reset functionality.
7//! This allows the reset pin to be controlled by a direct GPIO pin or an I2C I/O expander.
8//!
9//! This driver reads the entire touch data block in a single I2C transaction to report all active touch points simultaneously.
10//!
11//! 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.
12//!
13//! ## Usage
14//!
15//! 1. Implement the `ResetInterface` trait for your specific reset mechanism.
16//! 2. Create an instance of the `Cst226Driver`.
17//! 3. Initialize the touch driver.
18//! 4. In a loop, use the `get_touches()` method to read the current touch state.
19//!
20//! ```rust
21//! // This is a conceptual example. `I2CInstance`, `PinInstance`, and `DelayInstance`
22//! // would be your concrete implementations from a HAL crate.
23//!
24//! // 1. Implement the ResetInterface for your hardware.
25//! struct GpioReset<P: OutputPin> {
26//!     pin: P,
27//! }
28//!
29//! impl<P: OutputPin> ResetInterface for GpioReset<P> {
30//!     type Error = P::Error;
31//!     fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), Self::Error> {
32//!         self.pin.set_high()?;
33//!         delay.delay_ms(5);
34//!         self.pin.set_low()?;
35//!         delay.delay_ms(5);
36//!         self.pin.set_high()?;
37//!         delay.delay_ms(30);
38//!         Ok(())
39//!     }
40//! }
41//!
42//! // 2. Create driver instance.
43//! let reset_pin = GpioReset { pin: PinInstance };
44//! let mut touch = Cst226Driver::new(
45//!     I2CInstance,
46//!     CST226_DEVICE_ADDRESS,
47//!     reset_pin,
48//! );
49//!
50//! // 3. Initialize the driver.
51//! touch.initialize(&mut DelayInstance).expect("Failed to initialize touch driver");
52//!
53//! // 4. Read touches in a loop.
54//! loop {
55//!     let touches = touch.get_touches().unwrap();
56//!
57//!     for (i, touch) in touches.iter().enumerate() {
58//!         println!("Touch {}: x={}, y={}", i + 1, touch.x, touch.y);
59//!     }
60//! }
61//! ```
62//!
63//! Notes:
64//! - If the reset pin is controlled via an I2C GPIO expander sharing the same bus with the touch driver, you should use a shared bus implementation like `embedded_hal_bus` to manage I2C access.
65
66#![no_std]
67
68use embedded_hal::delay::DelayNs;
69use embedded_hal::i2c::I2c;
70use heapless::Vec;
71
72// Constants for CST226 device address and registers
73pub const CST226_DEVICE_ADDRESS: u8 = 0x5A;
74const CST226_REG_STATUS: u8 = 0x00;
75const CST226_BUFFER_SIZE: usize = 28;
76const MAX_TOUCH_POINTS: usize = 5;
77
78// Swipe detection threshold
79const SWIPE_MIN_DISTANCE: i32 = 50; // pixels
80
81/// Power modes for the touch device.
82#[derive(Clone, Copy, Debug)]
83pub enum PowerMode {
84    /// The device is actively scanning for touches.
85    Active,
86    /// The device is in a low-power sleep state. Wake-up requires a hardware reset.
87    Sleep,
88}
89
90/// Represents a single touch point with X and Y coordinates.
91#[derive(Debug, Default, Clone, Copy)]
92pub struct TouchPoint {
93    pub x: u16,
94    pub y: u16,
95}
96
97/// Single-finger swipe gestures.
98#[derive(Debug, Clone, Copy, PartialEq)]
99pub enum Gesture {
100    None,
101    SwipeUp,
102    SwipeDown,
103    SwipeLeft,
104    SwipeRight,
105}
106
107/// Internal state for gesture detection.
108#[derive(Default)]
109struct GestureState {
110    is_touching: bool,
111    start_point: TouchPoint,
112    last_point: TouchPoint,
113}
114
115/// Trait for controlling the CST226 hardware reset pin.
116pub trait ResetInterface {
117    /// The specific error type for this reset implementation.
118    type Error;
119
120    /// Performs the hardware reset sequence for the CST226 device.
121    /// This could be a GPIO port or an I2C expander-controlled pin.
122    /// Recommended Implementation: HIGH -> delay -> LOW -> delay -> HIGH -> delay.
123    fn reset(&mut self) -> Result<(), Self::Error>;
124}
125
126/// Driver Errors
127#[derive(Debug)]
128pub enum DriverError<ResetError, I2cError> {
129    /// Error originating from the I2C bus.
130    I2cError(I2cError),
131    /// Error originating from the reset pin control.
132    ResetError(ResetError),
133}
134
135/// CST226 touch driver
136pub struct Cst226Driver<I2C, D, RST> {
137    i2c: I2C,
138    device_address: u8,
139    delay: D,
140    reset: RST,
141    gesture_state: GestureState,
142}
143
144impl<I2C, D, RST> Cst226Driver<I2C, D, RST>
145where
146    I2C: I2c,
147    D: DelayNs,
148    RST: ResetInterface,
149{
150    /// Creates a new instance of the CST226 driver.
151    pub fn new(i2c: I2C, device_address: u8, reset: RST, delay: D) -> Self {
152        Cst226Driver {
153            i2c,
154            device_address,
155            delay,
156            reset,
157            gesture_state: GestureState::default(),
158        }
159    }
160
161    /// Initializes the device.
162    /// This method performs a hardware reset to ensure the chip is in a known state.
163    pub fn initialize(&mut self) -> Result<(), DriverError<RST::Error, I2C::Error>> {
164        self.reset.reset().map_err(DriverError::ResetError)?;
165        self.delay.delay_ms(10);
166        self.gesture_state = GestureState::default();
167        Ok(())
168    }
169
170    /// Sets the power mode of the device.
171    /// To wake the device from `Sleep` mode, a hardware reset is required by calling `initialize()`.
172    pub fn set_power_mode(&mut self, mode: PowerMode) -> Result<(), I2C::Error> {
173        match mode {
174            PowerMode::Sleep => self.i2c.write(self.device_address, &[0xD1, 0x05]),
175            PowerMode::Active => {
176                // Waking up requires a hardware reset, which is handled by initialize().
177                // We do nothing here, user must call initialize().
178                Ok(())
179            }
180        }
181    }
182
183    /// Reads all active touch points' state in a single transaction.
184    ///
185    /// Returns a `Vec` containing `TouchPoint`s (up to 5).
186    pub fn get_touches(&mut self) -> Result<Vec<TouchPoint, MAX_TOUCH_POINTS>, I2C::Error> {
187        let mut buffer = [0u8; CST226_BUFFER_SIZE];
188        self.i2c
189            .write_read(self.device_address, &[CST226_REG_STATUS], &mut buffer)?;
190
191        // Check for invalid data markers
192        if buffer[6] != 0xAB || buffer[0] == 0xAB || buffer[5] == 0x80 {
193            return Ok(Vec::new());
194        }
195
196        let num_touches = (buffer[5] & 0x7F) as usize;
197
198        if num_touches == 0 {
199            return Ok(Vec::new());
200        }
201
202        // If touch count is invalid, clear the status register and return
203        if num_touches > MAX_TOUCH_POINTS {
204            self.i2c.write(self.device_address, &[0x00, 0xAB])?;
205            return Ok(Vec::new());
206        }
207
208        let mut touches = Vec::new();
209        let mut index: usize = 0;
210
211        for i in 0..num_touches {
212            if index + 4 >= CST226_BUFFER_SIZE {
213                break; // Avoid buffer overflow
214            }
215
216            let x = ((buffer[index + 1] as u16) << 4) | (((buffer[index + 3] >> 4) & 0x0F) as u16);
217            let y = ((buffer[index + 2] as u16) << 4) | ((buffer[index + 3] & 0x0F) as u16);
218
219            if touches.push(TouchPoint { x, y }).is_err() {
220                break; // Stop if Vec is full
221            }
222
223            // The data format for the CST226 is unusual. The first touch point block
224            // is 7 bytes long, while subsequent blocks are 5 bytes.
225            index += if i == 0 { 7 } else { 5 };
226        }
227
228        Ok(touches)
229    }
230
231    /// Detects single-finger swipe gestures based on touch history.
232    ///
233    /// Call this method periodically with the current time in milliseconds.
234    /// Returns `Gesture::None` if no gesture is detected or if multiple fingers are used.
235    pub fn get_gesture(&mut self) -> Result<Gesture, I2C::Error> {
236        let touches = self.get_touches()?;
237
238        let state = &mut self.gesture_state;
239
240        if touches.len() > 1 {
241            // Only support single finger
242            state.is_touching = false;
243            return Ok(Gesture::None);
244        }
245
246        if touches.is_empty() {
247            // Touch up
248            if state.is_touching {
249                state.is_touching = false;
250                let dx = state.last_point.x as i32 - state.start_point.x as i32;
251                let dy = state.last_point.y as i32 - state.start_point.y as i32;
252
253                // Swipe detection
254                if dx.abs() > dy.abs() && dx.abs() > SWIPE_MIN_DISTANCE {
255                    return Ok(if dx > 0 {
256                        Gesture::SwipeRight
257                    } else {
258                        Gesture::SwipeLeft
259                    });
260                } else if dy.abs() > dx.abs() && dy.abs() > SWIPE_MIN_DISTANCE {
261                    return Ok(if dy > 0 {
262                        Gesture::SwipeDown
263                    } else {
264                        Gesture::SwipeUp
265                    });
266                }
267            }
268            return Ok(Gesture::None);
269        } else {
270            // Touch down or move
271            let point = touches[0];
272            if !state.is_touching {
273                state.is_touching = true;
274                state.start_point = point;
275                state.last_point = point;
276            } else {
277                state.last_point = point;
278            }
279            return Ok(Gesture::None);
280        }
281    }
282}