Skip to main content

cirque_pinnacle/
touchpad.rs

1use crate::*;
2use core::marker::PhantomData;
3use embedded_hal::spi::{Operation, SpiDevice};
4
5pub struct Touchpad<S: SpiDevice<u8>, M: Mode> {
6    spi: S,
7    phantom_: PhantomData<M>,
8}
9
10/// Pinnacle has four power modes - Active (touch detected), Idle (no touch),
11/// Low Power/ Sleep (lower power after ~ 5 seconds of inactivity)
12/// and Shutdown/Standby (no data reported).
13#[derive(Copy, Clone, Debug)]
14pub enum PowerMode {
15    /// By default, Pinnacle toggles between Active and Idle mode. Pinnacle is in
16    /// Active mode when a touch is detected (that is, a finger or stylus is present
17    /// and is moving or tapping on the trackpad). The measurement system is active
18    /// and data packets are being created and then sent to the host. Active mode
19    /// begins as soon as a touch is detected. Idle mode is entered when the finger
20    /// has been removed and there are no data packets to be sent. While in Idle mode,
21    /// Pinnacle wakes every 10 milliseconds to check for a touch.
22    Active,
23
24    /// Enabling sleep mode will cause Pinnacle to go into a low power mode
25    /// (around 50 μA) within 5 seconds of no touch detection. While in sleep mode,
26    /// Pinnacle will wake within 300 ms to report any detection of a finger/stylus.
27    Sleep,
28
29    /// Shutdown/Standby mode is a very low power mode
30    /// and Pinnacle does not track touch in this mode.
31    Shutdown,
32}
33
34#[expect(clippy::struct_excessive_bools)]
35pub struct Calibration {
36    pub background_comp: bool,
37    pub nerd_comp: bool,
38    pub track_error_comp: bool,
39    pub tap_comp: bool,
40    pub palm_error_comp: bool,
41    pub calibration_matrix: bool,
42    pub force_precalibration_noise_check: bool,
43}
44
45impl Default for Calibration {
46    fn default() -> Self {
47        Self {
48            background_comp: false,
49            nerd_comp: false,
50            track_error_comp: false,
51            tap_comp: false,
52            palm_error_comp: false,
53            calibration_matrix: true,
54            force_precalibration_noise_check: true,
55        }
56    }
57}
58
59pub struct Status {
60    /// Command Complete (`SW_CC`).
61    ///
62    /// Asserted after calibration, POR. Remains asserted until cleared by host.
63    pub command_complete: bool,
64
65    /// Software Data Ready (`SW_DR`).
66    ///
67    /// Asserted with new data. Remains asserted until cleared by host.
68    pub data_ready: bool,
69}
70
71impl<S: SpiDevice<u8>, M: Mode> Touchpad<S, M> {
72    pub(crate) const fn new(spi: S) -> Self {
73        Self {
74            spi,
75            phantom_: PhantomData,
76        }
77    }
78
79    pub fn product_id(&mut self) -> Result<u8, S::Error> {
80        self.read(PRODUCT_ID_ADDR)
81    }
82
83    pub fn firmware_id(&mut self) -> Result<u8, S::Error> {
84        self.read(FIRMWARE_ID_ADDR)
85    }
86
87    pub fn firmware_version(&mut self) -> Result<u8, S::Error> {
88        self.read(FIRMWARE_VERSION_ADDR)
89    }
90
91    /// When a touch is detected, Pinnacle loads X and Y position data into the
92    /// position registers and asserts the `SW_DR` flag, which also triggers
93    /// the `HW_DR` signal. While the finger/stylus is present, the position
94    /// registers are updated every 10 ms and `SW_DR` and `HW_DR` are asserted.
95    pub fn status(&mut self) -> Result<Status, S::Error> {
96        let status = self.read(STATUS1_ADDR)?;
97        Ok(Status {
98            command_complete: status & 0b1000 != 0,
99            data_ready: status & 0b0100 != 0,
100        })
101    }
102
103    /// Forces Pinnacle to re-calibrate.
104    ///
105    /// If the touchpad is reporting touches when no fingers are on the pad
106    /// then calibration (compensation) is wrong. Calling this function
107    /// will fix the problem.
108    ///
109    /// **IMPORTANT:** after calling this function, wait until [`Touchpad::calibrated`]
110    /// is true and then [`Touchpad::enable_feed`]. Until then, the device will not
111    /// produce any data and you should not touch the device.
112    pub fn calibrate(&mut self, c: &Calibration) -> Result<(), S::Error> {
113        let mut data = 1;
114        if c.background_comp {
115            data |= 0x02;
116        }
117        if c.nerd_comp {
118            data |= 0x04;
119        }
120        if c.track_error_comp {
121            data |= 0x08;
122        }
123        if c.tap_comp {
124            data |= 0x10;
125        }
126        if c.palm_error_comp {
127            data |= 0x20;
128        }
129        if !c.calibration_matrix {
130            data |= 0x40;
131        }
132        if c.force_precalibration_noise_check {
133            data |= 0x80;
134        }
135        self.disable_feed()?;
136        self.write(CAL_CONFIG1_ADDR, data)
137    }
138
139    /// Check if the touchpad is calibrated.
140    pub fn calibrated(&mut self) -> Result<bool, S::Error> {
141        let config = self.read(CAL_CONFIG1_ADDR)?;
142        Ok((config & 1) != 0)
143    }
144
145    /// Clear Command Complete and Software Data Ready flags simultaneously.
146    pub fn clear_flags(&mut self) -> Result<(), S::Error> {
147        self.write_with_delay(STATUS1_ADDR, 0x00)
148    }
149
150    /// Number of samples generated per second.
151    pub fn sample_rate(&mut self) -> Result<u8, S::Error> {
152        self.read(SAMPLE_RATE_ADDR)
153    }
154
155    /// Set the number of samples generated per second.
156    pub fn set_sample_rate(&mut self, sample_rate: u8) -> Result<(), S::Error> {
157        self.write(SAMPLE_RATE_ADDR, sample_rate)
158    }
159
160    /// During Z-idle (no touch detected) and when in absolute data mode,
161    /// Pinnacle will continue to send empty packets (both X and Y data set to 0x00)
162    /// every 10 ms. The number of empty packets to be sent can be set using
163    /// [`Touchpad::set_z_idle`]. The default value is 0x1E (30 decimal).
164    /// When set to zero (0), this register prevents any empty packets from being sent,
165    /// and the position registers will contain the last sensed location until
166    /// a new finger presence is detected.
167    ///
168    /// The Z-Idle count can be a helpful design tool. For example, tap-frequency
169    /// can be determined by counting the number of Z-idle packets reported
170    /// between a finger lifting off and touching back down
171    /// (cutting short the stream of Z-idle packets).
172    pub fn z_idle(&mut self) -> Result<u8, S::Error> {
173        self.read(Z_IDLE_ADDR)
174    }
175
176    /// Set the number of empty packets sent during Z-idle.
177    pub fn set_z_idle(&mut self, z_idle: u8) -> Result<(), S::Error> {
178        self.write(Z_IDLE_ADDR, z_idle)
179    }
180
181    /// Contains the pen Z_On threshold.
182    pub fn z_scaler(&mut self) -> Result<u8, S::Error> {
183        self.read(Z_SCALER_ADDR)
184    }
185
186    /// Set the pen Z_On threshold.
187    pub fn set_z_scaler(&mut self, z_scaler: u8) -> Result<(), S::Error> {
188        self.write(Z_SCALER_ADDR, z_scaler)
189    }
190
191    pub fn sleep_timer(&mut self) -> Result<u8, S::Error> {
192        self.read(SLEEP_TIMER_ADDR)
193    }
194
195    pub fn set_sleep_timer(&mut self, sleep_timer: u8) -> Result<(), S::Error> {
196        self.write(SLEEP_TIMER_ADDR, sleep_timer)
197    }
198
199    pub fn sleep_interval(&mut self) -> Result<u8, S::Error> {
200        self.read(SLEEP_INTERVAL_ADDR)
201    }
202
203    pub fn set_sleep_interval(&mut self, sleep_interval: u8) -> Result<(), S::Error> {
204        self.write(SLEEP_INTERVAL_ADDR, sleep_interval)
205    }
206
207    pub fn disable_feed(&mut self) -> Result<(), S::Error> {
208        let config = self.read(FEED_CONFIG1_ADDR)?;
209        let config = config & !0x01;
210        self.write(FEED_CONFIG1_ADDR, config)
211    }
212
213    pub fn enable_feed(&mut self) -> Result<(), S::Error> {
214        let config = self.read(FEED_CONFIG1_ADDR)?;
215        let config = config | 0x01;
216        self.write(FEED_CONFIG1_ADDR, config)
217    }
218
219    /// Get the current power mode.
220    pub fn power_mode(&mut self) -> Result<PowerMode, S::Error> {
221        let mode = self.read(SYS_CONFIG1_ADDR)?;
222        if mode & 0b10 != 0 {
223            return Ok(PowerMode::Shutdown);
224        }
225        if mode & 0b100 != 0 {
226            return Ok(PowerMode::Sleep);
227        }
228        Ok(PowerMode::Active)
229    }
230
231    /// Set the power mode.
232    pub fn set_power_mode(&mut self, mode: PowerMode) -> Result<(), S::Error> {
233        let mode = match mode {
234            PowerMode::Sleep => 0b100,
235            PowerMode::Shutdown => 0b10,
236            PowerMode::Active => 0b0,
237        };
238        self.write(SYS_CONFIG1_ADDR, mode)
239    }
240
241    fn read(&mut self, addr: u8) -> Result<u8, S::Error> {
242        let addr = READ_BITS | (addr & ADDR_MASK);
243        let mut buf = [addr, READ_FILL, READ_FILL, READ_FILL];
244        self.spi.transfer_in_place(&mut buf)?;
245        Ok(buf[3])
246    }
247
248    fn read_multi<const N: usize>(&mut self, addr: u8) -> Result<[u8; N], S::Error> {
249        let addr = READ_BITS | (addr & ADDR_MASK);
250        let mut addr_buf = [addr, READ_CONTINUE, READ_CONTINUE];
251        let mut buf = [READ_CONTINUE; N];
252        buf[N - 1] = READ_FILL;
253        self.spi.transaction(&mut [
254            Operation::TransferInPlace(&mut addr_buf),
255            Operation::TransferInPlace(&mut buf),
256        ])?;
257        Ok(buf)
258    }
259
260    pub(crate) fn write(&mut self, addr: u8, data: u8) -> Result<(), S::Error> {
261        let addr = WRITE_BITS | (addr & ADDR_MASK);
262        let buf = [addr, data];
263        self.spi.write(&buf)
264    }
265
266    fn write_with_delay(&mut self, addr: u8, data: u8) -> Result<(), S::Error> {
267        const US: u32 = 1000; // microseconds in nanosecond
268        let addr = WRITE_BITS | (addr & ADDR_MASK);
269        let buf = [addr, data];
270        self.spi.transaction(&mut [
271            Operation::Write(&buf),
272            // wait "Inter-Message Transfer Delay (required by slave)"
273            Operation::DelayNs(50 * US),
274        ])
275    }
276}
277
278impl<S: SpiDevice<u8>> Touchpad<S, Absolute> {
279    pub fn read_absolute(&mut self) -> Result<AbsoluteData, S::Error> {
280        // let data = self.read_multi::<6>(PACKET_BYTE_0_ADDR)?;
281
282        // The best here would be to use "read_multi" implemented as described
283        // in section 6.2.2 of the datasheet. However, somehow when we do that,
284        // we get a wrong value in the most significant bit of every byte.
285        // We haven't found how to fix it so instead we use here multiple reads.
286        let btn = self.read(0x12)?;
287        let xlow = self.read(0x14)?;
288        let ylow = self.read(0x15)?;
289        let high = self.read(0x16)?;
290        let z = self.read(0x17)?;
291
292        let data = AbsoluteData {
293            x: u16::from(xlow) | (u16::from(high & 0b_1111) << 8),
294            y: u16::from(ylow) | (u16::from(high & 0b_0111_0000) << 4),
295            z: z & 0b_0011_1111,
296            button_flags: btn & 0b_0011_1111,
297        };
298        self.clear_flags()?;
299        Ok(data)
300    }
301}
302
303impl<S: SpiDevice<u8>> Touchpad<S, Relative> {
304    pub fn read_relative(&mut self) -> Result<RelativeData, S::Error> {
305        let data = self.read_multi::<4>(PACKET_BYTE_0_ADDR)?;
306        let mut x = i16::from(data[1]);
307        let mut y = i16::from(data[2]);
308        if (data[0] & 0x10) > 0 {
309            x -= 256;
310        }
311        if (data[0] & 0x20) > 0 {
312            y -= 256;
313        }
314        let data = RelativeData {
315            x,
316            y,
317            buttons: Buttons {
318                primary: data[0] & 0b001 != 0,
319                secondary: data[0] & 0b010 != 0,
320                auxiliary: data[0] & 0b100 != 0,
321            },
322            #[expect(clippy::cast_possible_wrap)]
323            wheel: data[3] as i8,
324        };
325        self.clear_flags()?;
326        Ok(data)
327    }
328}