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}