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 | |
6 | use core::{marker::PhantomData, str}; |
7 | |
8 | const GT911_I2C_ADDR_BA: u8 = 0x5D; |
9 | const GT911_PRODUCT_ID_REG: u16 = 0x8140; |
10 | const GT911_TOUCHPOINT_STATUS_REG: u16 = 0x814E; |
11 | const GT911_TOUCHPOINT_1_REG: u16 = 0x814F; |
12 | const GT911_COMMAND_REG: u16 = 0x8040; |
13 | |
14 | const MAX_NUM_TOUCHPOINTS: usize = 5; |
15 | const TOUCHPOINT_ENTRY_LEN: usize = 8; |
16 | pub const GET_TOUCH_BUF_SIZE: usize = TOUCHPOINT_ENTRY_LEN; |
17 | pub 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)] |
22 | pub 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)] |
36 | pub 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 |
49 | pub struct Gt911Blocking<I2C> { |
50 | i2c_addr: u8, // e.g. 0x5D |
51 | i2c: PhantomData<I2C>, |
52 | } |
53 | |
54 | /// Use the default I2C address for communication |
55 | impl<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 |
65 | impl<I2C, E> Gt911Blocking<I2C> |
66 | where |
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, ®ister.to_be_bytes(), buf) |
181 | .map_err(Error::I2C) |
182 | } |
183 | } |
184 | |
185 | /// Async Gt911 |
186 | pub struct Gt911<I2C> { |
187 | i2c_addr: u8, // e.g. 0x5D |
188 | i2c: PhantomData<I2C>, |
189 | } |
190 | |
191 | /// Use the default I2C address for communication |
192 | impl<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 |
202 | impl<I2C, E> Gt911<I2C> |
203 | where |
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, ®ister.to_be_bytes(), buf) |
338 | .await |
339 | .map_err(Error::I2C) |
340 | } |
341 | } |
342 | |
343 | fn 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 | |