Skip to content

Commit 5db705d

Browse files
authored
Cirque trackpad features: circular scroll, inertial cursor (#17482)
1 parent 904ec0c commit 5db705d

11 files changed

+1123
-149
lines changed

builddefs/common_features.mk

+4
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,14 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
149149
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), cirque_pinnacle_i2c)
150150
OPT_DEFS += -DSTM32_I2C -DHAL_USE_I2C=TRUE
151151
SRC += drivers/sensors/cirque_pinnacle.c
152+
SRC += drivers/sensors/cirque_pinnacle_gestures.c
153+
SRC += $(QUANTUM_DIR)/pointing_device_gestures.c
152154
QUANTUM_LIB_SRC += i2c_master.c
153155
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), cirque_pinnacle_spi)
154156
OPT_DEFS += -DSTM32_SPI -DHAL_USE_SPI=TRUE
155157
SRC += drivers/sensors/cirque_pinnacle.c
158+
SRC += drivers/sensors/cirque_pinnacle_gestures.c
159+
SRC += $(QUANTUM_DIR)/pointing_device_gestures.c
156160
QUANTUM_LIB_SRC += spi_master.c
157161
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pimoroni_trackball)
158162
OPT_DEFS += -DSTM32_SPI -DHAL_USE_I2C=TRUE

docs/feature_pointing_device.md

+21-10
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ POINTING_DEVICE_DRIVER = cirque_pinnacle_spi
8989

9090
This supports the Cirque Pinnacle 1CA027 Touch Controller, which is used in the TM040040, TM035035 and the TM023023 trackpads. These are I2C or SPI compatible, and both configurations are supported.
9191

92-
| Setting | Description | Default |
93-
|-------------------------------- |-----------------------------------------------------------------------|--------------------- |
94-
|`CIRQUE_PINNACLE_X_LOWER` | (Optional) The minimum reachable X value on the sensor. | `127` |
95-
|`CIRQUE_PINNACLE_X_UPPER` | (Optional) The maximum reachable X value on the sensor. | `1919` |
96-
|`CIRQUE_PINNACLE_Y_LOWER` | (Optional) The minimum reachable Y value on the sensor. | `63` |
97-
|`CIRQUE_PINNACLE_Y_UPPER` | (Optional) The maximum reachable Y value on the sensor. | `1471` |
98-
|`CIRQUE_PINNACLE_ATTENUATION` | (Optional) Sets the attenuation of the sensor data. | `ADC_ATTENUATE_4X` |
99-
|`CIRQUE_PINNACLE_TAPPING_TERM` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
100-
|`CIRQUE_PINNACLE_TOUCH_DEBOUNCE` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
92+
| Setting | Description | Default |
93+
|-------------------------------- |------------------------------------------------------------|--------------------|
94+
|`CIRQUE_PINNACLE_X_LOWER` | (Optional) The minimum reachable X value on the sensor. | `127` |
95+
|`CIRQUE_PINNACLE_X_UPPER` | (Optional) The maximum reachable X value on the sensor. | `1919` |
96+
|`CIRQUE_PINNACLE_Y_LOWER` | (Optional) The minimum reachable Y value on the sensor. | `63` |
97+
|`CIRQUE_PINNACLE_Y_UPPER` | (Optional) The maximum reachable Y value on the sensor. | `1471` |
98+
|`CIRQUE_PINNACLE_DIAMETER_MM` | (Optional) Diameter of the trackpad sensor in millimeters. | `40` |
99+
|`CIRQUE_PINNACLE_ATTENUATION` | (Optional) Sets the attenuation of the sensor data. | `ADC_ATTENUATE_4X` |
100+
|`CIRQUE_PINNACLE_CURVED_OVERLAY` | (Optional) Applies settings tuned for curved overlay. | _not defined_ |
101101

102102
**`CIRQUE_PINNACLE_ATTENUATION`** is a measure of how much data is suppressed in regards to sensitivity. The higher the attenuation, the less sensitive the touchpad will be.
103103

@@ -120,10 +120,21 @@ Default attenuation is set to 4X, although if you are using a thicker overlay (s
120120
|`CIRQUE_PINNACLE_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ |
121121
|`CIRQUE_PINNACLE_SPI_CS_PIN` | (Required) Sets the Cable Select pin connected to the sensor. | _not defined_ |
122122

123-
Default Scaling/CPI is 1024.
123+
Default Scaling is 1024. Actual CPI depends on trackpad diameter.
124124

125125
Also see the `POINTING_DEVICE_TASK_THROTTLE_MS`, which defaults to 10ms when using Cirque Pinnacle, which matches the internal update rate of the position registers (in standard configuration). Advanced configuration for pen/stylus usage might require lower values.
126126

127+
#### Cirque Trackpad gestures
128+
129+
| Gesture Setting | Description | Default |
130+
|-----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|
131+
|`POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE` | (Optional) Enable inertial cursor. Cursor continues moving after a flick gesture and slows down by kinetic friction | _not defined_ |
132+
|`CIRQUE_PINNACLE_CIRCULAR_SCROLL_ENABLE` | (Optional) Enable circular scroll. Touch originating in outer ring can trigger scroll by moving along the perimeter. Near side triggers vertical scroll and far side triggers horizontal scroll. | _not defined_ |
133+
|`CIRQUE_PINNACLE_TAP_ENABLE` | (Optional) Enable tap to click. This currently only works on the master side. | _not defined_ |
134+
|`CIRQUE_PINNACLE_TAPPING_TERM` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
135+
|`CIRQUE_PINNACLE_TOUCH_DEBOUNCE` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` |
136+
137+
**`POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE`** is not specific to Cirque trackpad; any pointing device with a lift/contact status can integrate this gesture into its driver. e.g. PMW3360 can use Lift_Stat from Motion register. Note that `POINTING_DEVICE_MOTION_PIN` cannot be used with this feature; continuous polling of `pointing_device_get_report()` is needed to generate glide reports.
127138

128139
### Pimoroni Trackball
129140

drivers/sensors/cirque_pinnacle.c

+90-101
Original file line numberDiff line numberDiff line change
@@ -9,47 +9,16 @@
99
#include "wait.h"
1010
#include "timer.h"
1111

12-
// Registers for RAP
13-
// clang-format off
14-
#define FIRMWARE_ID 0x00
15-
#define FIRMWARE_VERSION_C 0x01
16-
#define STATUS_1 0x02
17-
#define SYSCONFIG_1 0x03
18-
#define FEEDCONFIG_1 0x04
19-
#define FEEDCONFIG_2 0x05
20-
#define CALIBRATION_CONFIG_1 0x07
21-
#define PS2_AU_CONTROL 0x08
22-
#define SAMPLE_RATE 0x09
23-
#define Z_IDLE_COUNT 0x0A
24-
#define Z_SCALER 0x0B
25-
#define SLEEP_INTERVAL 0x0C
26-
#define SLEEP_TIMER 0x0D
27-
#define PACKET_BYTE_0 0x12
28-
#define PACKET_BYTE_1 0x13
29-
#define PACKET_BYTE_2 0x14
30-
#define PACKET_BYTE_3 0x15
31-
#define PACKET_BYTE_4 0x16
32-
#define PACKET_BYTE_5 0x17
33-
34-
#define ERA_VALUE 0x1B
35-
#define ERA_HIGH_BYTE 0x1C
36-
#define ERA_LOW_BYTE 0x1D
37-
#define ERA_CONTROL 0x1E
38-
39-
// ADC-attenuation settings (held in BIT_7 and BIT_6)
40-
// 1X = most sensitive, 4X = least sensitive
41-
#define ADC_ATTENUATE_1X 0x00
42-
#define ADC_ATTENUATE_2X 0x40
43-
#define ADC_ATTENUATE_3X 0x80
44-
#define ADC_ATTENUATE_4X 0xC0
45-
4612
#ifndef CIRQUE_PINNACLE_ATTENUATION
47-
# define CIRQUE_PINNACLE_ATTENUATION ADC_ATTENUATE_4X
13+
# ifdef CIRQUE_PINNACLE_CURVED_OVERLAY
14+
# define CIRQUE_PINNACLE_ATTENUATION EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_2X
15+
# else
16+
# define CIRQUE_PINNACLE_ATTENUATION EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_4X
17+
# endif
4818
#endif
49-
// clang-format on
5019

5120
bool touchpad_init;
52-
uint16_t scale_data = 1024;
21+
uint16_t scale_data = CIRQUE_PINNACLE_DEFAULT_SCALE;
5322

5423
void cirque_pinnacle_clear_flags(void);
5524
void cirque_pinnacle_enable_feed(bool feedEnable);
@@ -106,90 +75,126 @@ void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResoluti
10675

10776
// Clears Status1 register flags (SW_CC and SW_DR)
10877
void cirque_pinnacle_clear_flags() {
109-
RAP_Write(STATUS_1, 0x00);
78+
RAP_Write(HOSTREG__STATUS1, HOSTREG__STATUS1_DEFVAL & ~(HOSTREG__STATUS1__COMMAND_COMPLETE | HOSTREG__STATUS1__DATA_READY));
11079
wait_us(50);
11180
}
11281

11382
// Enables/Disables the feed
11483
void cirque_pinnacle_enable_feed(bool feedEnable) {
115-
uint8_t temp;
116-
RAP_ReadBytes(FEEDCONFIG_1, &temp, 1); // Store contents of FeedConfig1 register
84+
uint8_t feedconfig1;
85+
RAP_ReadBytes(HOSTREG__FEEDCONFIG1, &feedconfig1, 1);
11786

11887
if (feedEnable) {
119-
temp |= 0x01; // Set Feed Enable bit
88+
feedconfig1 |= HOSTREG__FEEDCONFIG1__FEED_ENABLE;
12089
} else {
121-
temp &= ~0x01; // Clear Feed Enable bit
90+
feedconfig1 &= ~HOSTREG__FEEDCONFIG1__FEED_ENABLE;
12291
}
123-
RAP_Write(FEEDCONFIG_1, temp);
92+
RAP_Write(HOSTREG__FEEDCONFIG1, feedconfig1);
12493
}
12594

12695
/* ERA (Extended Register Access) Functions */
12796
// Reads <count> bytes from an extended register at <address> (16-bit address),
12897
// stores values in <*data>
12998
void ERA_ReadBytes(uint16_t address, uint8_t* data, uint16_t count) {
130-
uint8_t ERAControlValue = 0xFF;
99+
uint8_t ERAControlValue = 0xFF;
100+
uint16_t timeout_timer;
131101

132102
cirque_pinnacle_enable_feed(false); // Disable feed
133103

134-
RAP_Write(ERA_HIGH_BYTE, (uint8_t)(address >> 8)); // Send upper byte of ERA address
135-
RAP_Write(ERA_LOW_BYTE, (uint8_t)(address & 0x00FF)); // Send lower byte of ERA address
104+
RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_HIGH, (uint8_t)(address >> 8)); // Send upper byte of ERA address
105+
RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_LOW, (uint8_t)(address & 0x00FF)); // Send lower byte of ERA address
136106

137107
for (uint16_t i = 0; i < count; i++) {
138-
RAP_Write(ERA_CONTROL, 0x05); // Signal ERA-read (auto-increment) to Pinnacle
108+
RAP_Write(HOSTREG__EXT_REG_AXS_CTRL, HOSTREG__EREG_AXS__INC_ADDR_READ | HOSTREG__EREG_AXS__READ); // Signal ERA-read (auto-increment) to Pinnacle
139109

140110
// Wait for status register 0x1E to clear
111+
timeout_timer = timer_read();
141112
do {
142-
RAP_ReadBytes(ERA_CONTROL, &ERAControlValue, 1);
143-
} while (ERAControlValue != 0x00);
113+
RAP_ReadBytes(HOSTREG__EXT_REG_AXS_CTRL, &ERAControlValue, 1);
114+
} while ((ERAControlValue != 0x00) && (timer_elapsed(timeout_timer) <= CIRQUE_PINNACLE_TIMEOUT));
144115

145-
RAP_ReadBytes(ERA_VALUE, data + i, 1);
116+
RAP_ReadBytes(HOSTREG__EXT_REG_AXS_VALUE, data + i, 1);
146117

147118
cirque_pinnacle_clear_flags();
148119
}
149120
}
150121

151122
// Writes a byte, <data>, to an extended register at <address> (16-bit address)
152123
void ERA_WriteByte(uint16_t address, uint8_t data) {
153-
uint8_t ERAControlValue = 0xFF;
124+
uint8_t ERAControlValue = 0xFF;
125+
uint16_t timeout_timer;
154126

155127
cirque_pinnacle_enable_feed(false); // Disable feed
156128

157-
RAP_Write(ERA_VALUE, data); // Send data byte to be written
129+
RAP_Write(HOSTREG__EXT_REG_AXS_VALUE, data); // Send data byte to be written
158130

159-
RAP_Write(ERA_HIGH_BYTE, (uint8_t)(address >> 8)); // Upper byte of ERA address
160-
RAP_Write(ERA_LOW_BYTE, (uint8_t)(address & 0x00FF)); // Lower byte of ERA address
131+
RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_HIGH, (uint8_t)(address >> 8)); // Upper byte of ERA address
132+
RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_LOW, (uint8_t)(address & 0x00FF)); // Lower byte of ERA address
161133

162-
RAP_Write(ERA_CONTROL, 0x02); // Signal an ERA-write to Pinnacle
134+
RAP_Write(HOSTREG__EXT_REG_AXS_CTRL, HOSTREG__EREG_AXS__WRITE); // Signal an ERA-write to Pinnacle
163135

164136
// Wait for status register 0x1E to clear
137+
timeout_timer = timer_read();
165138
do {
166-
RAP_ReadBytes(ERA_CONTROL, &ERAControlValue, 1);
167-
} while (ERAControlValue != 0x00);
139+
RAP_ReadBytes(HOSTREG__EXT_REG_AXS_CTRL, &ERAControlValue, 1);
140+
} while ((ERAControlValue != 0x00) && (timer_elapsed(timeout_timer) <= CIRQUE_PINNACLE_TIMEOUT));
168141

169142
cirque_pinnacle_clear_flags();
170143
}
171144

172145
void cirque_pinnacle_set_adc_attenuation(uint8_t adcGain) {
173-
uint8_t temp = 0x00;
146+
uint8_t adcconfig = 0x00;
174147

175-
ERA_ReadBytes(0x0187, &temp, 1);
176-
temp &= 0x3F; // clear top two bits
177-
temp |= adcGain;
178-
ERA_WriteByte(0x0187, temp);
179-
ERA_ReadBytes(0x0187, &temp, 1);
148+
ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1);
149+
adcconfig &= EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK;
150+
adcconfig |= adcGain;
151+
ERA_WriteByte(EXTREG__TRACK_ADCCONFIG, adcconfig);
152+
ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1);
180153
}
181154

182155
// Changes thresholds to improve detection of fingers
156+
// Not needed for flat overlay?
183157
void cirque_pinnacle_tune_edge_sensitivity(void) {
184-
uint8_t temp = 0x00;
158+
uint8_t widezmin = 0x00;
185159

186-
ERA_ReadBytes(0x0149, &temp, 1);
187-
ERA_WriteByte(0x0149, 0x04);
188-
ERA_ReadBytes(0x0149, &temp, 1);
160+
ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &widezmin, 1);
161+
ERA_WriteByte(EXTREG__XAXIS_WIDEZMIN, 0x04); // magic number from Cirque sample code
162+
ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &widezmin, 1);
189163

190-
ERA_ReadBytes(0x0168, &temp, 1);
191-
ERA_WriteByte(0x0168, 0x03);
192-
ERA_ReadBytes(0x0168, &temp, 1);
164+
ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &widezmin, 1);
165+
ERA_WriteByte(EXTREG__YAXIS_WIDEZMIN, 0x03); // magic number from Cirque sample code
166+
ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &widezmin, 1);
167+
}
168+
169+
// Perform calibration
170+
void cirque_pinnacle_calibrate(void) {
171+
uint8_t calconfig;
172+
uint16_t timeout_timer;
173+
174+
RAP_ReadBytes(HOSTREG__CALCONFIG1, &calconfig, 1);
175+
calconfig |= HOSTREG__CALCONFIG1__CALIBRATE;
176+
RAP_Write(HOSTREG__CALCONFIG1, calconfig);
177+
178+
// Calibration takes ~100ms according to GT-AN-090624, doubling the timeout just to be safe
179+
timeout_timer = timer_read();
180+
do {
181+
RAP_ReadBytes(HOSTREG__CALCONFIG1, &calconfig, 1);
182+
} while ((calconfig & HOSTREG__CALCONFIG1__CALIBRATE) && (timer_elapsed(timeout_timer) <= 200));
183+
184+
cirque_pinnacle_clear_flags();
185+
}
186+
187+
// Enable/disable cursor smoothing, smoothing is enabled by default
188+
void cirque_pinnacle_cursor_smoothing(bool enable) {
189+
uint8_t feedconfig3;
190+
191+
RAP_ReadBytes(HOSTREG__FEEDCONFIG3, &feedconfig3, 1);
192+
if (enable) {
193+
feedconfig3 &= ~HOSTREG__FEEDCONFIG3__DISABLE_CROSS_RATE_SMOOTHING;
194+
} else {
195+
feedconfig3 |= HOSTREG__FEEDCONFIG3__DISABLE_CROSS_RATE_SMOOTHING;
196+
}
197+
RAP_Write(HOSTREG__FEEDCONFIG3, feedconfig3);
193198
}
194199

195200
/* Pinnacle-based TM040040/TM035035/TM023023 Functions */
@@ -205,44 +210,28 @@ void cirque_pinnacle_init(void) {
205210
// Host clears SW_CC flag
206211
cirque_pinnacle_clear_flags();
207212

208-
// SysConfig1 (Low Power Mode)
209-
// Bit 0: Reset, 1=Reset
210-
// Bit 1: Shutdown, 1=Shutdown, 0=Active
211-
// Bit 2: Sleep Enable, 1=low power mode, 0=normal mode
212213
// send a RESET command now, in case QMK had a soft-reset without a power cycle
213-
RAP_Write(SYSCONFIG_1, 0x01);
214+
RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1__RESET);
214215
wait_ms(30); // Pinnacle needs 10-15ms to boot, so wait long enough before configuring
215-
RAP_Write(SYSCONFIG_1, 0x00);
216+
RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1_DEFVAL);
216217
wait_us(50);
217218

218219
// FeedConfig2 (Feature flags for Relative Mode Only)
219-
// Bit 0: IntelliMouse Enable, 1=enable, 0=disable
220-
// Bit 1: All Taps Disable, 1=disable, 0=enable
221-
// Bit 2: Secondary Tap Disable, 1=disable, 0=enable
222-
// Bit 3: Scroll Disable, 1=disable, 0=enable
223-
// Bit 4: GlideExtend® Disable, 1=disable, 0=enable
224-
// Bit 5: reserved
225-
// Bit 6: reserved
226-
// Bit 7: Swap X & Y, 1=90° rotation, 0=0° rotation
227-
RAP_Write(FEEDCONFIG_2, 0x00);
220+
RAP_Write(HOSTREG__FEEDCONFIG2, HOSTREG__FEEDCONFIG2_DEFVAL);
228221

229222
// FeedConfig1 (Data Output Flags)
230-
// Bit 0: Feed enable, 1=feed, 0=no feed
231-
// Bit 1: Data mode, 1=absolute, 0=relative
232-
// Bit 2: Filter disable, 1=no filter, 0=filter
233-
// Bit 3: X disable, 1=no X data, 0=X data
234-
// Bit 4: Y disable, 1=no Y data, 0=Y data
235-
// Bit 5: reserved
236-
// Bit 6: X data Invert, 1=X max to 0, 0=0 to Y max
237-
// Bit 7: Y data Invert, 1=Y max to 0, 0=0 to Y max
238-
RAP_Write(FEEDCONFIG_1, CIRQUE_PINNACLE_POSITION_MODE << 1);
239-
240-
// Host sets z-idle packet count to 5 (default is 0x1F/30)
241-
RAP_Write(Z_IDLE_COUNT, 5);
223+
RAP_Write(HOSTREG__FEEDCONFIG1, CIRQUE_PINNACLE_POSITION_MODE ? HOSTREG__FEEDCONFIG1__DATA_TYPE__REL0_ABS1 : HOSTREG__FEEDCONFIG1_DEFVAL);
242224

243-
cirque_pinnacle_set_adc_attenuation(CIRQUE_PINNACLE_ATTENUATION);
225+
// Host sets z-idle packet count to 5 (default is 0x1E/30)
226+
RAP_Write(HOSTREG__ZIDLE, 5);
244227

228+
cirque_pinnacle_set_adc_attenuation(CIRQUE_PINNACLE_ATTENUATION);
229+
#ifdef CIRQUE_PINNACLE_CURVED_OVERLAY
245230
cirque_pinnacle_tune_edge_sensitivity();
231+
#endif
232+
// Force a calibration after setting ADC attenuation
233+
cirque_pinnacle_calibrate();
234+
246235
cirque_pinnacle_enable_feed(true);
247236
}
248237

@@ -252,15 +241,15 @@ pinnacle_data_t cirque_pinnacle_read_data(void) {
252241
pinnacle_data_t result = {0};
253242

254243
// Check if there is valid data available
255-
RAP_ReadBytes(STATUS_1, &data_ready, 1); // bit2 is Software Data Ready, bit3 is Command Complete, bit0 and bit1 are reserved/unused
256-
if ((data_ready & 0x04) == 0) {
244+
RAP_ReadBytes(HOSTREG__STATUS1, &data_ready, 1);
245+
if ((data_ready & HOSTREG__STATUS1__DATA_READY) == 0) {
257246
// no data available yet
258247
result.valid = false; // be explicit
259248
return result;
260249
}
261250

262251
// Read all data bytes
263-
RAP_ReadBytes(PACKET_BYTE_0, data, 6);
252+
RAP_ReadBytes(HOSTREG__PACKETBYTE_0, data, 6);
264253

265254
// Get ready for the next data sample
266255
cirque_pinnacle_clear_flags();

0 commit comments

Comments
 (0)