1// A basic driver for the Goodix GT911 touch screen
2// This implementation supports both blocking and async communication over I2C
3
4#![no_std]
5
6use core::{marker::PhantomData, str};
7
8const GT911_I2C_ADDR_BA: u8 = 0x5D;
9const GT911_PRODUCT_ID_REG: u16 = 0x8140;
10const GT911_TOUCHPOINT_STATUS_REG: u16 = 0x814E;
11const GT911_TOUCHPOINT_1_REG: u16 = 0x814F;
12const GT911_COMMAND_REG: u16 = 0x8040;
13
14const MAX_NUM_TOUCHPOINTS: usize = 5;
15const TOUCHPOINT_ENTRY_LEN: usize = 8;
16pub const GET_TOUCH_BUF_SIZE: usize = TOUCHPOINT_ENTRY_LEN;
17pub const GET_MULTITOUCH_BUF_SIZE: usize = TOUCHPOINT_ENTRY_LEN * MAX_NUM_TOUCHPOINTS;
18
19/// The touchpoint
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Point {
23 /// The touchpoint number (zero based)
24 pub track_id: u8,
25 /// x coordinate in screen pixels
26 pub x: u16,
27 /// y coordinate in screen pixels
28 pub y: u16,
29 /// How much area the finder takes up on the touch point
30 pub area: u16,
31}
32
33/// Gt911 Error
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35#[derive(Debug, Clone)]
36pub enum Error<E> {
37 /// Usually indicates that you are attempting to communicate with a device that is not a 911
38 /// or that there is a general communication failure
39 UnexpectedProductId,
40 /// I2C communication error
41 I2C(E),
42 /// Not an actual error, it just means "no new data available"
43 /// This means that you have polled the device again in-between it detecting any new touch data
44 /// This can safely be ignored
45 NotReady,
46}
47
48/// Blocking Gt911
49pub struct Gt911Blocking<I2C> {
50 i2c_addr: u8, // e.g. 0x5D
51 i2c: PhantomData<I2C>,
52}
53
54/// Use the default I2C address for communication
55impl<I2C> Default for Gt911Blocking<I2C> {
56 fn default() -> Self {
57 Self {
58 i2c_addr: GT911_I2C_ADDR_BA,
59 i2c: PhantomData,
60 }
61 }
62}
63
64/// Blocking Gt911 implementation
65impl<I2C, E> Gt911Blocking<I2C>
66where
67 I2C: embedded_hal::i2c::I2c<Error = E>,
68{
69 /// Creates a new instance with a user specified i2c address
70 pub fn new(i2c_addr: u8) -> Self {
71 Self {
72 i2c_addr,
73 i2c: PhantomData,
74 }
75 }
76
77 /// Checks the ProductId for a "911\0" string response and resets the status register
78 /// Only needs to be called once on startup
79 pub fn init(&self, i2c: &mut I2C) -> Result<(), Error<E>> {
80 // switch to command mode
81 self.write(i2c, GT911_COMMAND_REG, 0)?;
82
83 // read the product_id and confirm that it is expected
84 let mut read = [0u8; 4];
85 self.read(i2c, GT911_PRODUCT_ID_REG, &mut read)?;
86 match str::from_utf8(&read) {
87 Ok(product_id) => {
88 if product_id != "911\0" {
89 return Err(Error::UnexpectedProductId);
90 }
91 }
92 Err(_) => {
93 return Err(Error::UnexpectedProductId);
94 }
95 }
96
97 // clear status register
98 self.write(i2c, GT911_TOUCHPOINT_STATUS_REG, 0)?;
99 Ok(())
100 }
101
102 /// Gets a single touch point
103 /// Returns Ok(None) for release, Some(point) for press or move and Err(Error::NotReady) for no data
104 pub fn get_touch(&self, i2c: &mut I2C) -> Result<Option<Point>, Error<E>> {
105 let num_touch_points = self.get_num_touch_points(i2c)?;
106
107 let point = if num_touch_points > 0 {
108 let mut read = [0u8; TOUCHPOINT_ENTRY_LEN];
109 self.read(i2c, GT911_TOUCHPOINT_1_REG, &mut read)?;
110 let point = decode_point(&read);
111 Some(point)
112 } else {
113 None
114 };
115
116 // clear status register
117 self.write(i2c, GT911_TOUCHPOINT_STATUS_REG, 0)?;
118 Ok(point)
119 }
120
121 /// Gets multiple stack allocated touch points (0-5 points)
122 /// Returns points.len()==0 for release, points.len()>0 for press or move and Err(Error::NotReady) for no data
123 pub fn get_multi_touch(
124 &self,
125 i2c: &mut I2C,
126 ) -> Result<heapless::Vec<Point, MAX_NUM_TOUCHPOINTS>, Error<E>> {
127 let num_touch_points = self.get_num_touch_points(i2c)?;
128
129 let points = if num_touch_points > 0 {
130 assert!(num_touch_points <= MAX_NUM_TOUCHPOINTS);
131 let mut points = heapless::Vec::new();
132
133 // read touch points
134 let mut read = [0u8; TOUCHPOINT_ENTRY_LEN * MAX_NUM_TOUCHPOINTS];
135 self.read(
136 i2c,
137 GT911_TOUCHPOINT_1_REG,
138 &mut read[..TOUCHPOINT_ENTRY_LEN * num_touch_points],
139 )?;
140
141 for n in 0..num_touch_points {
142 let start = n * TOUCHPOINT_ENTRY_LEN;
143 let point = decode_point(&read[start..start + TOUCHPOINT_ENTRY_LEN]);
144 points.push(point).ok();
145 }
146
147 points
148 } else {
149 heapless::Vec::new()
150 };
151
152 // clear status register
153 self.write(i2c, GT911_TOUCHPOINT_STATUS_REG, 0)?;
154 Ok(points)
155 }
156
157 fn get_num_touch_points(&self, i2c: &mut I2C) -> Result<usize, Error<E>> {
158 // read coords
159 let mut read = [0u8; 1];
160 self.read(i2c, GT911_TOUCHPOINT_STATUS_REG, &mut read)?;
161
162 let status = read[0];
163 let ready = (status & 0x80) > 0;
164 let num_touch_points = (status & 0x0F) as usize;
165
166 if ready {
167 Ok(num_touch_points)
168 } else {
169 Err(Error::NotReady)
170 }
171 }
172
173 fn write(&self, i2c: &mut I2C, register: u16, value: u8) -> Result<(), Error<E>> {
174 let register = register.to_be_bytes();
175 let cmd = [register[0], register[1], value];
176 i2c.write(self.i2c_addr, &cmd).map_err(Error::I2C)
177 }
178
179 fn read(&self, i2c: &mut I2C, register: u16, buf: &mut [u8]) -> Result<(), Error<E>> {
180 i2c.write_read(self.i2c_addr, &register.to_be_bytes(), buf)
181 .map_err(Error::I2C)
182 }
183}
184
185/// Async Gt911
186pub struct Gt911<I2C> {
187 i2c_addr: u8, // e.g. 0x5D
188 i2c: PhantomData<I2C>,
189}
190
191/// Use the default I2C address for communication
192impl<I2C> Default for Gt911<I2C> {
193 fn default() -> Self {
194 Self {
195 i2c_addr: GT911_I2C_ADDR_BA,
196 i2c: PhantomData,
197 }
198 }
199}
200
201/// Async Gt911 implementation
202impl<I2C, E> Gt911<I2C>
203where
204 I2C: embedded_hal_async::i2c::I2c<Error = E>,
205{
206 /// Creates a new instance with a user specified i2c address
207 pub fn new(i2c_addr: u8) -> Self {
208 Self {
209 i2c_addr,
210 i2c: PhantomData,
211 }
212 }
213
214 /// Checks the ProductId for a "911\0" string response and resets the status register
215 /// Only needs to be called once on startup
216 /// buf is a temp read buffer and should be at least 4 bytes in length
217 pub async fn init(&self, i2c: &mut I2C, buf: &mut [u8]) -> Result<(), Error<E>> {
218 // switch to command mode
219 self.write(i2c, GT911_COMMAND_REG, 0).await?;
220
221 // read the product_id and confirm that it is expected
222 const LEN: usize = 4;
223 assert!(buf.len() >= LEN);
224 self.read(i2c, GT911_PRODUCT_ID_REG, &mut buf[..LEN])
225 .await?;
226 match str::from_utf8(&buf[..LEN]) {
227 Ok(product_id) => {
228 if product_id != "911\0" {
229 return Err(Error::UnexpectedProductId);
230 }
231 }
232 Err(_) => {
233 return Err(Error::UnexpectedProductId);
234 }
235 }
236
237 // clear status register
238 self.write(i2c, GT911_TOUCHPOINT_STATUS_REG, 0).await?;
239 Ok(())
240 }
241
242 /// Gets a single touch point
243 /// Returns Ok(None) for release, Some(point) for press or move and Err(Error::NotReady) for no data
244 /// buf is a temp read buffer and should be at least 8 bytes in length
245 pub async fn get_touch(
246 &self,
247 i2c: &mut I2C,
248 buf: &mut [u8],
249 ) -> Result<Option<Point>, Error<E>> {
250 let num_touch_points = self.get_num_touch_points(i2c, buf).await?;
251
252 let point = if num_touch_points > 0 {
253 assert!(
254 buf.len() >= TOUCHPOINT_ENTRY_LEN,
255 "Buffer too small, use GET_TOUCH_BUF_SIZE"
256 );
257 self.read(
258 i2c,
259 GT911_TOUCHPOINT_1_REG,
260 &mut buf[..TOUCHPOINT_ENTRY_LEN],
261 )
262 .await?;
263 let point = decode_point(buf);
264 Some(point)
265 } else {
266 None
267 };
268
269 // clear status register
270 self.write(i2c, GT911_TOUCHPOINT_STATUS_REG, 0).await?;
271 Ok(point)
272 }
273
274 /// Gets multiple stack allocated touch points (0-5 points)
275 /// Returns points.len()==0 for release, points.len()>0 for press or move and Err(Error::NotReady) for no data
276 /// buf is a temp read buffer and should be at least num_touch_points * 8 bytes in length (40 bytes to be safe because there can be up to 5 touch points)
277 pub async fn get_multi_touch(
278 &self,
279 i2c: &mut I2C,
280 buf: &mut [u8],
281 ) -> Result<heapless::Vec<Point, MAX_NUM_TOUCHPOINTS>, Error<E>> {
282 let num_touch_points = self.get_num_touch_points(i2c, buf).await?;
283
284 let points = if num_touch_points > 0 {
285 assert!(num_touch_points <= MAX_NUM_TOUCHPOINTS);
286 let mut points = heapless::Vec::new();
287
288 // read touch points
289 let len: usize = num_touch_points * TOUCHPOINT_ENTRY_LEN;
290 assert!(
291 buf.len() >= len,
292 "Buffer too small, use GET_MULTITOUCH_BUF_SIZE"
293 );
294 self.read(i2c, GT911_TOUCHPOINT_1_REG, &mut buf[..len])
295 .await?;
296
297 for n in 0..num_touch_points {
298 let start = n * TOUCHPOINT_ENTRY_LEN;
299 let point = decode_point(&buf[start..start + TOUCHPOINT_ENTRY_LEN]);
300 points.push(point).ok();
301 }
302
303 points
304 } else {
305 heapless::Vec::new()
306 };
307
308 // clear status register
309 self.write(i2c, GT911_TOUCHPOINT_STATUS_REG, 0).await?;
310 Ok(points)
311 }
312
313 async fn get_num_touch_points(&self, i2c: &mut I2C, buf: &mut [u8]) -> Result<usize, Error<E>> {
314 // read coords
315 assert!(!buf.is_empty());
316 self.read(i2c, GT911_TOUCHPOINT_STATUS_REG, &mut buf[..1])
317 .await?;
318
319 let status = buf[0];
320 let ready = (status & 0x80) > 0;
321 let num_touch_points = (status & 0x0F) as usize;
322
323 if ready {
324 Ok(num_touch_points)
325 } else {
326 Err(Error::NotReady)
327 }
328 }
329
330 async fn write(&self, i2c: &mut I2C, register: u16, value: u8) -> Result<(), Error<E>> {
331 let register = register.to_be_bytes();
332 let cmd = [register[0], register[1], value];
333 i2c.write(self.i2c_addr, &cmd).await.map_err(Error::I2C)
334 }
335
336 async fn read(&self, i2c: &mut I2C, register: u16, buf: &mut [u8]) -> Result<(), Error<E>> {
337 i2c.write_read(self.i2c_addr, &register.to_be_bytes(), buf)
338 .await
339 .map_err(Error::I2C)
340 }
341}
342
343fn decode_point(buf: &[u8]) -> Point {
344 assert!(buf.len() >= TOUCHPOINT_ENTRY_LEN);
345 Point {
346 track_id: buf[0],
347 x: u16::from_le_bytes([buf[1], buf[2]]),
348 y: u16::from_le_bytes([buf[3], buf[4]]),
349 area: u16::from_le_bytes([buf[5], buf[6]]),
350 // NOTE: the last byte is reserved
351 }
352}
353