Skip to main content

core_s3/
touch.rs

1//! FT6336U capacitive touch support for the CoreS3.
2
3use embedded_graphics::{geometry::Point, primitives::Rectangle};
4use embedded_hal::i2c::I2c;
5
6use crate::{devices, display::DisplayOrientation};
7
8const REG_DEVICE_MODE: u8 = 0x00;
9const REG_GESTURE_ID: u8 = 0x01;
10
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum TouchPhase {
14    Down,
15    Move,
16    Up,
17}
18
19#[cfg_attr(feature = "defmt", derive(defmt::Format))]
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum Gesture {
22    None,
23    MoveUp,
24    MoveRight,
25    MoveDown,
26    MoveLeft,
27    ZoomIn,
28    ZoomOut,
29    Unknown(u8),
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct TouchEvent {
34    pub id: u8,
35    pub point: Point,
36    pub phase: TouchPhase,
37}
38
39impl TouchEvent {
40    pub fn hits(self, area: Rectangle) -> bool {
41        area.contains(self.point)
42    }
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub struct TouchReport {
47    pub gesture: Gesture,
48    pub events: [Option<TouchEvent>; 2],
49}
50
51impl TouchReport {
52    pub const fn empty() -> Self {
53        Self {
54            gesture: Gesture::None,
55            events: [None, None],
56        }
57    }
58}
59
60pub struct Ft6336u<I2C> {
61    i2c: I2C,
62    address: u8,
63    orientation: DisplayOrientation,
64    width: u16,
65    height: u16,
66}
67
68impl<I2C> Ft6336u<I2C> {
69    pub const fn new(i2c: I2C) -> Self {
70        Self {
71            i2c,
72            address: devices::i2c::FT6336U_TOUCH,
73            orientation: DisplayOrientation::Landscape,
74            width: devices::display::WIDTH,
75            height: devices::display::HEIGHT,
76        }
77    }
78
79    pub fn set_orientation(&mut self, orientation: DisplayOrientation) {
80        self.orientation = orientation;
81    }
82
83    pub fn release(self) -> I2C {
84        self.i2c
85    }
86}
87
88impl<I2C, Error> Ft6336u<I2C>
89where
90    I2C: I2c<Error = Error>,
91{
92    pub fn init(&mut self) -> Result<(), Error> {
93        self.write_register(REG_DEVICE_MODE, 0x00)
94    }
95
96    pub fn read_report(&mut self) -> Result<TouchReport, Error> {
97        let mut data = [0u8; 12];
98        self.i2c
99            .write_read(self.address, &[REG_GESTURE_ID], &mut data)?;
100        let gesture = decode_gesture(data[0]);
101        let count = (data[1] & 0x0F).min(2);
102        let mut report = TouchReport {
103            gesture,
104            events: [None, None],
105        };
106        if count >= 1 {
107            report.events[0] = decode_touch(&data[2..8], self.orientation, self.width, self.height);
108        }
109        if count >= 2 {
110            report.events[1] =
111                decode_touch(&data[8..12], self.orientation, self.width, self.height);
112        }
113        Ok(report)
114    }
115
116    pub fn read_register(&mut self, register: u8) -> Result<u8, Error> {
117        let mut value = [0u8];
118        self.i2c.write_read(self.address, &[register], &mut value)?;
119        Ok(value[0])
120    }
121
122    pub fn write_register(&mut self, register: u8, value: u8) -> Result<(), Error> {
123        self.i2c.write(self.address, &[register, value])
124    }
125}
126
127fn decode_touch(
128    data: &[u8],
129    orientation: DisplayOrientation,
130    width: u16,
131    height: u16,
132) -> Option<TouchEvent> {
133    if data.len() < 4 {
134        return None;
135    }
136    let event = (data[0] >> 6) & 0x03;
137    let x = (u16::from(data[0] & 0x0F) << 8) | u16::from(data[1]);
138    let y = (u16::from(data[2] & 0x0F) << 8) | u16::from(data[3]);
139    let id = data.get(2).copied().unwrap_or(0) >> 4;
140    let phase = match event {
141        0 => TouchPhase::Down,
142        1 => TouchPhase::Up,
143        2 => TouchPhase::Move,
144        _ => TouchPhase::Move,
145    };
146    Some(TouchEvent {
147        id,
148        point: map_touch_point(
149            Point::new(i32::from(x), i32::from(y)),
150            orientation,
151            width,
152            height,
153        ),
154        phase,
155    })
156}
157
158const fn decode_gesture(value: u8) -> Gesture {
159    match value {
160        0x00 => Gesture::None,
161        0x10 => Gesture::MoveUp,
162        0x14 => Gesture::MoveRight,
163        0x18 => Gesture::MoveDown,
164        0x1C => Gesture::MoveLeft,
165        0x48 => Gesture::ZoomIn,
166        0x49 => Gesture::ZoomOut,
167        other => Gesture::Unknown(other),
168    }
169}
170
171pub fn map_touch_point(
172    point: Point,
173    orientation: DisplayOrientation,
174    width: u16,
175    height: u16,
176) -> Point {
177    let w = i32::from(width);
178    let h = i32::from(height);
179    match orientation {
180        DisplayOrientation::Landscape => point,
181        DisplayOrientation::LandscapeInverted => Point::new(w - 1 - point.x, h - 1 - point.y),
182        DisplayOrientation::Portrait => Point::new(point.y, w - 1 - point.x),
183        DisplayOrientation::PortraitInverted => Point::new(h - 1 - point.y, point.x),
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use embedded_graphics::prelude::Size;
191
192    #[test]
193    fn maps_touch_for_all_rotations() {
194        let p = Point::new(10, 20);
195        assert_eq!(
196            map_touch_point(p, DisplayOrientation::Landscape, 320, 240),
197            p
198        );
199        assert_eq!(
200            map_touch_point(p, DisplayOrientation::LandscapeInverted, 320, 240),
201            Point::new(309, 219)
202        );
203        assert_eq!(
204            map_touch_point(p, DisplayOrientation::Portrait, 320, 240),
205            Point::new(20, 309)
206        );
207        assert_eq!(
208            map_touch_point(p, DisplayOrientation::PortraitInverted, 320, 240),
209            Point::new(219, 10)
210        );
211    }
212
213    #[test]
214    fn hit_tests_event() {
215        let event = TouchEvent {
216            id: 0,
217            point: Point::new(12, 14),
218            phase: TouchPhase::Down,
219        };
220        assert!(event.hits(Rectangle::new(Point::new(10, 10), Size::new(20, 20))));
221    }
222}