From 01bc974365581da6563e2bbd2425233d97280fdd Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Sat, 25 Jun 2022 22:22:28 +0200 Subject: [PATCH] improvements for Cirque Pinnacle trackpads (#17091) --- docs/feature_pointing_device.md | 21 +++--- drivers/sensors/cirque_pinnacle.c | 98 +++++++++++++++++++-------- drivers/sensors/cirque_pinnacle.h | 50 +++++++++----- drivers/sensors/cirque_pinnacle_i2c.c | 4 +- drivers/sensors/cirque_pinnacle_spi.c | 4 +- quantum/pointing_device_drivers.c | 45 +++++++++--- 6 files changed, 154 insertions(+), 68 deletions(-) diff --git a/docs/feature_pointing_device.md b/docs/feature_pointing_device.md index 5dacc9f5ab6..264362ea77f 100644 --- a/docs/feature_pointing_device.md +++ b/docs/feature_pointing_device.md @@ -89,15 +89,15 @@ POINTING_DEVICE_DRIVER = cirque_pinnacle_spi 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. -| Setting | Description | Default | -|---------------------------------|---------------------------------------------------------------------------------|-----------------------| -|`CIRQUE_PINNACLE_X_LOWER` | (Optional) The minimum reachable X value on the sensor. | `127` | -|`CIRQUE_PINNACLE_X_UPPER` | (Optional) The maximum reachable X value on the sensor. | `1919` | -|`CIRQUE_PINNACLE_Y_LOWER` | (Optional) The minimum reachable Y value on the sensor. | `63` | -|`CIRQUE_PINNACLE_Y_UPPER` | (Optional) The maximum reachable Y value on the sensor. | `1471` | -|`CIRQUE_PINNACLE_ATTENUATION` | (Optional) Sets the attenuation of the sensor data. | `ADC_ATTENUATE_4X` | -|`CIRQUE_PINNACLE_TAPPING_TERM` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` | -|`CIRQUE_PINNACLE_TOUCH_DEBOUNCE` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` | +| Setting | Description | Default | +|-------------------------------- |-----------------------------------------------------------------------|--------------------- | +|`CIRQUE_PINNACLE_X_LOWER` | (Optional) The minimum reachable X value on the sensor. | `127` | +|`CIRQUE_PINNACLE_X_UPPER` | (Optional) The maximum reachable X value on the sensor. | `1919` | +|`CIRQUE_PINNACLE_Y_LOWER` | (Optional) The minimum reachable Y value on the sensor. | `63` | +|`CIRQUE_PINNACLE_Y_UPPER` | (Optional) The maximum reachable Y value on the sensor. | `1471` | +|`CIRQUE_PINNACLE_ATTENUATION` | (Optional) Sets the attenuation of the sensor data. | `ADC_ATTENUATE_4X` | +|`CIRQUE_PINNACLE_TAPPING_TERM` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` | +|`CIRQUE_PINNACLE_TOUCH_DEBOUNCE` | (Optional) Length of time that a touch can be to be considered a tap. | `TAPPING_TERM`/`200` | **`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. @@ -122,6 +122,9 @@ Default attenuation is set to 4X, although if you are using a thicker overlay (s Default Scaling/CPI is 1024. +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. + + ### Pimoroni Trackball To use the Pimoroni Trackball module, add this to your `rules.mk`: diff --git a/drivers/sensors/cirque_pinnacle.c b/drivers/sensors/cirque_pinnacle.c index 670b96f2fb4..1d1e4ccfc6c 100644 --- a/drivers/sensors/cirque_pinnacle.c +++ b/drivers/sensors/cirque_pinnacle.c @@ -1,8 +1,13 @@ // Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license +// based on https://github.com/cirque-corp/Cirque_Pinnacle_1CA027/tree/master/Circular_Trackpad +// with modifications and changes for QMK +// refer to documentation: Gen2 and Gen3 (Pinnacle ASIC) at https://www.cirque.com/documentation + #include "cirque_pinnacle.h" #include "print.h" #include "debug.h" #include "wait.h" +#include "timer.h" // Registers for RAP // clang-format off @@ -41,12 +46,6 @@ #ifndef CIRQUE_PINNACLE_ATTENUATION # define CIRQUE_PINNACLE_ATTENUATION ADC_ATTENUATE_4X #endif - -// Register config values for this demo -#define SYSCONFIG_1_VALUE 0x00 -#define FEEDCONFIG_1_VALUE 0x03 // 0x03 for absolute mode 0x01 for relative mode -#define FEEDCONFIG_2_VALUE 0x1C // 0x1F for normal functionality 0x1E for intellimouse disabled -#define Z_IDLE_COUNT_VALUE 0x05 // clang-format on bool touchpad_init; @@ -114,16 +113,14 @@ void cirque_pinnacle_clear_flags() { // Enables/Disables the feed void cirque_pinnacle_enable_feed(bool feedEnable) { uint8_t temp; - RAP_ReadBytes(FEEDCONFIG_1, &temp, 1); // Store contents of FeedConfig1 register if (feedEnable) { temp |= 0x01; // Set Feed Enable bit - RAP_Write(0x04, temp); } else { temp &= ~0x01; // Clear Feed Enable bit - RAP_Write(0x04, temp); } + RAP_Write(FEEDCONFIG_1, temp); } /* ERA (Extended Register Access) Functions */ @@ -195,7 +192,7 @@ void cirque_pinnacle_tune_edge_sensitivity(void) { ERA_ReadBytes(0x0168, &temp, 1); } -/* Pinnacle-based TM040040 Functions */ +/* Pinnacle-based TM040040/TM035035/TM023023 Functions */ void cirque_pinnacle_init(void) { #if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi) spi_init(); @@ -204,18 +201,44 @@ void cirque_pinnacle_init(void) { #endif touchpad_init = true; + // Host clears SW_CC flag cirque_pinnacle_clear_flags(); - // Host configures bits of registers 0x03 and 0x05 - RAP_Write(SYSCONFIG_1, SYSCONFIG_1_VALUE); - RAP_Write(FEEDCONFIG_2, FEEDCONFIG_2_VALUE); + // SysConfig1 (Low Power Mode) + // Bit 0: Reset, 1=Reset + // Bit 1: Shutdown, 1=Shutdown, 0=Active + // Bit 2: Sleep Enable, 1=low power mode, 0=normal mode + // send a RESET command now, in case QMK had a soft-reset without a power cycle + RAP_Write(SYSCONFIG_1, 0x01); + wait_ms(30); // Pinnacle needs 10-15ms to boot, so wait long enough before configuring + RAP_Write(SYSCONFIG_1, 0x00); + wait_us(50); - // Host enables preferred output mode (absolute) - RAP_Write(FEEDCONFIG_1, FEEDCONFIG_1_VALUE); + // FeedConfig2 (Feature flags for Relative Mode Only) + // Bit 0: IntelliMouse Enable, 1=enable, 0=disable + // Bit 1: All Taps Disable, 1=disable, 0=enable + // Bit 2: Secondary Tap Disable, 1=disable, 0=enable + // Bit 3: Scroll Disable, 1=disable, 0=enable + // Bit 4: GlideExtend® Disable, 1=disable, 0=enable + // Bit 5: reserved + // Bit 6: reserved + // Bit 7: Swap X & Y, 1=90° rotation, 0=0° rotation + RAP_Write(FEEDCONFIG_2, 0x00); - // Host sets z-idle packet count to 5 (default is 30) - RAP_Write(Z_IDLE_COUNT, Z_IDLE_COUNT_VALUE); + // FeedConfig1 (Data Output Flags) + // Bit 0: Feed enable, 1=feed, 0=no feed + // Bit 1: Data mode, 1=absolute, 0=relative + // Bit 2: Filter disable, 1=no filter, 0=filter + // Bit 3: X disable, 1=no X data, 0=X data + // Bit 4: Y disable, 1=no Y data, 0=Y data + // Bit 5: reserved + // Bit 6: X data Invert, 1=X max to 0, 0=0 to Y max + // Bit 7: Y data Invert, 1=Y max to 0, 0=0 to Y max + RAP_Write(FEEDCONFIG_1, CIRQUE_PINNACLE_POSITION_MODE << 1); + + // Host sets z-idle packet count to 5 (default is 0x1F/30) + RAP_Write(Z_IDLE_COUNT, 5); cirque_pinnacle_set_adc_attenuation(CIRQUE_PINNACLE_ATTENUATION); @@ -223,21 +246,42 @@ void cirque_pinnacle_init(void) { cirque_pinnacle_enable_feed(true); } -// Reads XYZ data from Pinnacle registers 0x14 through 0x17 -// Stores result in pinnacle_data_t struct with xValue, yValue, and zValue members pinnacle_data_t cirque_pinnacle_read_data(void) { - uint8_t data[6] = {0}; - pinnacle_data_t result = {0}; + uint8_t data_ready = 0; + uint8_t data[6] = {0}; + pinnacle_data_t result = {0}; + + // Check if there is valid data available + RAP_ReadBytes(STATUS_1, &data_ready, 1); // bit2 is Software Data Ready, bit3 is Command Complete, bit0 and bit1 are reserved/unused + if ((data_ready & 0x04) == 0) { + // no data available yet + result.valid = false; // be explicit + return result; + } + + // Read all data bytes RAP_ReadBytes(PACKET_BYTE_0, data, 6); + // Get ready for the next data sample cirque_pinnacle_clear_flags(); - result.buttonFlags = data[0] & 0x3F; - result.xValue = data[2] | ((data[4] & 0x0F) << 8); - result.yValue = data[3] | ((data[4] & 0xF0) << 4); - result.zValue = data[5] & 0x3F; - - result.touchDown = (result.xValue != 0 || result.yValue != 0); +#if CIRQUE_PINNACLE_POSITION_MODE + // Decode data for absolute mode + // Register 0x13 is unused in this mode (palm detection area) + result.buttonFlags = data[0] & 0x3F; // bit0 to bit5 are switch 0-5, only hardware button presses (from input pin on the Pinnacle chip) + result.xValue = data[2] | ((data[4] & 0x0F) << 8); // merge high and low bits for X + result.yValue = data[3] | ((data[4] & 0xF0) << 4); // merge high and low bits for Y + result.zValue = data[5] & 0x3F; // Z is only lower 6 bits, upper 2 bits are reserved/unused + result.touchDown = (result.xValue != 0 || result.yValue != 0); // (0,0) is a "magic coordinate" to indicate "finger touched down" +#else + // Decode data for relative mode + // Registers 0x16 and 0x17 are unused in this mode + result.buttons = data[0] & 0x07; // bit0 = primary button, bit1 = secondary button, bit2 = auxilary button, if Taps enabled then also software-recognized taps are reported + result.xDelta = data[1]; + result.yDelta = data[2]; + result.wheelCount = data[3]; +#endif + result.valid = true; return result; } diff --git a/drivers/sensors/cirque_pinnacle.h b/drivers/sensors/cirque_pinnacle.h index c8cb360e038..d65bdb41b64 100644 --- a/drivers/sensors/cirque_pinnacle.h +++ b/drivers/sensors/cirque_pinnacle.h @@ -5,23 +5,14 @@ #include #include -// Convenient way to store and access measurements -typedef struct { - uint16_t xValue; - uint16_t yValue; - uint16_t zValue; - uint8_t buttonFlags; - bool touchDown; -} pinnacle_data_t; - -void cirque_pinnacle_init(void); -pinnacle_data_t cirque_pinnacle_read_data(void); -void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResolution, uint16_t yResolution); -uint16_t cirque_pinnacle_get_scale(void); -void cirque_pinnacle_set_scale(uint16_t scale); - #ifndef CIRQUE_PINNACLE_TIMEOUT -# define CIRQUE_PINNACLE_TIMEOUT 20 +# define CIRQUE_PINNACLE_TIMEOUT 20 // I2C timeout in milliseconds +#endif + +#define CIRQUE_PINNACLE_ABSOLUTE_MODE 1 +#define CIRQUE_PINNACLE_RELATIVE_MODE 0 +#ifndef CIRQUE_PINNACLE_POSITION_MODE +# define CIRQUE_PINNACLE_POSITION_MODE CIRQUE_PINNACLE_ABSOLUTE_MODE #endif // Coordinate scaling values @@ -43,7 +34,9 @@ void cirque_pinnacle_set_scale(uint16_t scale); #ifndef CIRQUE_PINNACLE_Y_RANGE # define CIRQUE_PINNACLE_Y_RANGE (CIRQUE_PINNACLE_Y_UPPER - CIRQUE_PINNACLE_Y_LOWER) #endif - +#if !defined(POINTING_DEVICE_TASK_THROTTLE_MS) +# define POINTING_DEVICE_TASK_THROTTLE_MS 10 // Cirque Pinnacle in normal operation produces data every 10ms. Advanced configuration for pen/stylus usage might require lower values. +#endif #if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) # include "i2c_master.h" // Cirque's 7-bit I2C Slave Address @@ -72,3 +65,26 @@ void cirque_pinnacle_set_scale(uint16_t scale); # endif # endif #endif + +// Convenient way to store and access measurements +typedef struct { + bool valid; // true if valid data was read, false if no data was ready +#if CIRQUE_PINNACLE_POSITION_MODE + uint16_t xValue; + uint16_t yValue; + uint16_t zValue; + uint8_t buttonFlags; + bool touchDown; +#else + uint8_t xDelta; + uint8_t yDelta; + uint8_t wheelCount; + uint8_t buttons; +#endif +} pinnacle_data_t; + +void cirque_pinnacle_init(void); +pinnacle_data_t cirque_pinnacle_read_data(void); +void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResolution, uint16_t yResolution); +uint16_t cirque_pinnacle_get_scale(void); +void cirque_pinnacle_set_scale(uint16_t scale); diff --git a/drivers/sensors/cirque_pinnacle_i2c.c b/drivers/sensors/cirque_pinnacle_i2c.c index 8a38f1dcea0..b328dd9a7a3 100644 --- a/drivers/sensors/cirque_pinnacle_i2c.c +++ b/drivers/sensors/cirque_pinnacle_i2c.c @@ -19,7 +19,7 @@ void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count) { i2c_writeReg(CIRQUE_PINNACLE_ADDR << 1, cmdByte, NULL, 0, CIRQUE_PINNACLE_TIMEOUT); if (i2c_readReg(CIRQUE_PINNACLE_ADDR << 1, cmdByte, data, count, CIRQUE_PINNACLE_TIMEOUT) != I2C_STATUS_SUCCESS) { #ifdef CONSOLE_ENABLE - dprintf("error right touchpad\n"); + dprintf("error cirque_pinnacle i2c_readReg\n"); #endif touchpad_init = false; } @@ -34,7 +34,7 @@ void RAP_Write(uint8_t address, uint8_t data) { if (touchpad_init) { if (i2c_writeReg(CIRQUE_PINNACLE_ADDR << 1, cmdByte, &data, sizeof(data), CIRQUE_PINNACLE_TIMEOUT) != I2C_STATUS_SUCCESS) { #ifdef CONSOLE_ENABLE - dprintf("error right touchpad\n"); + dprintf("error cirque_pinnacle i2c_writeReg\n"); #endif touchpad_init = false; } diff --git a/drivers/sensors/cirque_pinnacle_spi.c b/drivers/sensors/cirque_pinnacle_spi.c index 34c77df07be..bd980fc8633 100644 --- a/drivers/sensors/cirque_pinnacle_spi.c +++ b/drivers/sensors/cirque_pinnacle_spi.c @@ -25,7 +25,7 @@ void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count) { } } else { #ifdef CONSOLE_ENABLE - dprintf("error right touchpad\n"); + dprintf("error cirque_pinnacle spi_start read\n"); #endif touchpad_init = false; } @@ -43,7 +43,7 @@ void RAP_Write(uint8_t address, uint8_t data) { spi_write(data); } else { #ifdef CONSOLE_ENABLE - dprintf("error right touchpad\n"); + dprintf("error cirque_pinnacle spi_start write\n"); #endif touchpad_init = false; } diff --git a/quantum/pointing_device_drivers.c b/quantum/pointing_device_drivers.c index 41d7018c866..435fcabf7cc 100644 --- a/quantum/pointing_device_drivers.c +++ b/quantum/pointing_device_drivers.c @@ -50,6 +50,7 @@ const pointing_device_driver_t pointing_device_driver = { .get_cpi = adns5050_get_cpi, }; // clang-format on + #elif defined(POINTING_DEVICE_DRIVER_adns9800) report_mouse_t adns9800_get_report_driver(report_mouse_t mouse_report) { @@ -69,6 +70,7 @@ const pointing_device_driver_t pointing_device_driver = { .get_cpi = adns9800_get_cpi }; // clang-format on + #elif defined(POINTING_DEVICE_DRIVER_analog_joystick) report_mouse_t analog_joystick_get_report(report_mouse_t mouse_report) { report_analog_joystick_t data = analog_joystick_read(); @@ -93,6 +95,7 @@ const pointing_device_driver_t pointing_device_driver = { .get_cpi = NULL }; // clang-format on + #elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi) # ifndef CIRQUE_PINNACLE_TAPPING_TERM # include "action.h" @@ -104,22 +107,40 @@ const pointing_device_driver_t pointing_device_driver = { # endif report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) { - pinnacle_data_t touchData = cirque_pinnacle_read_data(); - static uint16_t x = 0, y = 0, mouse_timer = 0; - mouse_xy_report_t report_x = 0, report_y = 0; - static bool is_z_down = false; + pinnacle_data_t touchData = cirque_pinnacle_read_data(); + mouse_xy_report_t report_x = 0, report_y = 0; + static mouse_xy_report_t x = 0, y = 0; + static uint16_t mouse_timer = 0; + static bool is_z_down = false; - cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale()); // Scale coordinates to arbitrary X, Y resolution +# if !CIRQUE_PINNACLE_POSITION_MODE +# error Cirque Pinnacle with relative mode not implemented yet. +# endif + + if (!touchData.valid) { + return mouse_report; + } + +# if CONSOLE_ENABLE + if (debug_mouse && touchData.touchDown) { + dprintf("cirque_pinnacle touchData x=%4d y=%4d z=%2d\n", touchData.xValue, touchData.yValue, touchData.zValue); + } +# endif + + // Scale coordinates to arbitrary X, Y resolution + cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale()); if (x && y && touchData.xValue && touchData.yValue) { report_x = (mouse_xy_report_t)(touchData.xValue - x); report_y = (mouse_xy_report_t)(touchData.yValue - y); } - x = touchData.xValue; - y = touchData.yValue; + x = touchData.xValue; + y = touchData.yValue; + mouse_report.x = report_x; + mouse_report.y = report_y; - if ((bool)touchData.zValue != is_z_down) { - is_z_down = (bool)touchData.zValue; + if (touchData.touchDown != is_z_down) { + is_z_down = touchData.touchDown; if (!touchData.zValue) { if (timer_elapsed(mouse_timer) < CIRQUE_PINNACLE_TAPPING_TERM && mouse_timer != 0) { mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1); @@ -138,8 +159,6 @@ report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) { if (timer_elapsed(mouse_timer) > (CIRQUE_PINNACLE_TOUCH_DEBOUNCE)) { mouse_timer = 0; } - mouse_report.x = report_x; - mouse_report.y = report_y; return mouse_report; } @@ -210,6 +229,7 @@ const pointing_device_driver_t pointing_device_driver = { .get_cpi = pimoroni_trackball_get_cpi }; // clang-format on + #elif defined(POINTING_DEVICE_DRIVER_pmw3360) static void pmw3360_device_init(void) { pmw3360_init(0); @@ -248,6 +268,7 @@ const pointing_device_driver_t pointing_device_driver = { .get_cpi = pmw3360_get_cpi }; // clang-format on + #elif defined(POINTING_DEVICE_DRIVER_pmw3389) static void pmw3389_device_init(void) { pmw3389_init(); @@ -286,6 +307,7 @@ const pointing_device_driver_t pointing_device_driver = { .get_cpi = pmw3389_get_cpi }; // clang-format on + #else __attribute__((weak)) void pointing_device_driver_init(void) {} __attribute__((weak)) report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) { @@ -304,4 +326,5 @@ const pointing_device_driver_t pointing_device_driver = { .set_cpi = pointing_device_driver_set_cpi }; // clang-format on + #endif