-
-
Notifications
You must be signed in to change notification settings - Fork 39.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cirque trackpad features: circular scroll, inertial cursor #17482
Conversation
ERAControlValue set to 0xFF and waits for read value as 0x00. If sensor not connected, it may never change. No timeout specified for extended register access in datasheet, use driver default timeout.
Pinnacle automatically captures baseline on powerup, that baseline is invalid after changing ADC attenuation so recapture it. Expose function in header so calibration can be triggered by user. e.g. Press a key combo to recalibrate after finger gets captured in baseline
Set trackpad diameter with CIRQUE_PINNACLE_DIAMETER_MM.
Extract tap logic into a separate function, and add the ability to disable it at runtime.
Scroll initiates from touch to the outer ring. Left half starts vertical scroll, right half starts horizontal scroll. Rotate clockwise to scroll down/right, keep rotating to keep scrolling. Disabled by default, can be configured at runtime.
Cursor continues moving after flick, slows down by kinetic friction, similar to trackball emulation on Steam Controller. Based on https://hal.archives-ouvertes.fr/hal-00989252. Disabled by default, can be enabled at runtime.
pointing_device_drivers.c has gotten quite large, any suggestions for where the extra processing code should live? Should I move |
do { | ||
RAP_ReadBytes(ERA_CONTROL, &ERAControlValue, 1); | ||
} while (ERAControlValue != 0x00); | ||
} while ((ERAControlValue != 0x00) && (timer_elapsed(timeout_timer) <= CIRQUE_PINNACLE_TIMEOUT)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least on I2C we could use && touchpad_init==true
directly, as this will be set to false if there is any I2C error (or timeout). Not sure if SPI considers CIRQUE_PINNACLE_TIMEOUT
at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SPI doesn't have ack or check for any timeout. It will return whatever state MISO happens to be floating in, if the device is not connected. That could be either 0xFF or 0x00 (or something inbetween if there's noise on the line?).
Looking at AVR's spi_master.c for example: spi_start()
always returns true, except if chip select pin is invalid but that's an error before ever interacting with the device. So touch_init
check can't really apply for SPI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually fixed a dead lock for me that happened with the code before. I started a small refactoring of the RAP_Read/Write
functions. If you find it useful feel free to include it in this pr cirque_timeout.patch.txt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a side note, if a single I2C transfer fails the whole driver will turn into an invalid state - which requires a reboot of the keyboard. I can think of mechanism on the pointing device driver level that will try to re-init a driver in such a case after a certain timeout. But this is not in the scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think handling touch_init
and I2C transaction failures adds too much to this PR, let's handle that in a separate one.
drivers/sensors/cirque_pinnacle.c
Outdated
void cirque_pinnacle_cursor_smoothing(bool enable) { | ||
uint8_t feedconfig3; | ||
|
||
// FeedConfig3 (Advanced feature flags) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this register available in all trackpad versions (TM040040/TM035035/TM023023) and Pinnacle firmwares?
GT-AN-090620 does not list it at all (presumably hidden under RESERVED
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FeedConfig3 register is listed in GT-AN-090625, which is stated as a companion document to GT-AN-090620. Same thing is stated for the ERA document (GT-AN-090623) and compensation document (GT-AN-090624).
Additionally, it is documented in Cirque's circle trackpad example code here. I don't see any indication it would not be present for a particular version of circle trackpad.
Empirically, I've tested this register on TM035035 and TM040040; the effect is subtle on 40mm but more prominent on 35mm.
67e9f24
to
ae20d9a
Compare
ae20d9a
to
43b2f99
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for continued effort to improve the cirque track-pads. Excited to try these myself (I have some heading my way). I have to review the code in greater detail later on. On thing that I would like to see addressed is the introduction of bitfield structs for all the configuration registers that get manipulated and that are currently only described indirectly. This should help with type safety and readability of the code.
I'm not sure here. Either we should go with the current approach to not mix the actual mouse report generation with the low level driver code. In this case we should split the pointing_device_drivers.c into sensor types and add them under a folder. Or we move the report generation logic into the low level drivers itself. Both might be out of scope for this PR though. Also I would like to hear @drashna opinion on this.
I wouldn't add that just to save memory. If a feature introduced behavior that might be unwanted it should be made opt-out either at compile time or runtime. |
Register definitions header based on Pinnacle.h from Cirque's Arduino AnyMeas_Example as that is the most complete definition anywhere. Extended register details taken from GT-AN-090625. Default values from a register dump on TM040040.
tuneEdgeSensitivity() in Cirque examples were only called for curved overlay. Disabling cirque_pinnacle_tune_edge_sensitivity() for flat overlay seems to help with the issue where finger near the outer perimeter is captured into baseline and renders the trackpad unusable until baseline recovers. Default ADC attenuation in Cirque's curved overlay examples is 2x.
28cc3a8
to
f85df63
Compare
Follow sequence from Cirque sample code. Set ADC attenuation, tune edge sensitivity, then calibrate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'd prefer that the drivers file here be kept simple as possible.
Ideally, "fancy" features (kind of like this), be handled by pointing_device.c
, so that any device can benefit from that code.
I do understand that the cirque is very much unique, especially in comparison to the other sensors. I'm not sure what the best solution is, especially long term.
However, it may be best to pull out the features that would work with other sensors (the inertial cursor code, perhaps), and move that to the pointing_devices.c
file, while device specific features (such as the circular scroll) be moved into the cirque files.
Long term, if we get other trackpads supported, it may be worth having those features in the core (rather than sensor code), and having them behind defines. Eg, "optical sensor" vs "trackpads" (and probably added by default).
quantum/pointing_device_drivers.c
Outdated
} | ||
|
||
return mouse_report; | ||
} | ||
|
||
uint16_t cirque_pinnacle_get_cpi(void) { | ||
return (uint16_t)roundf((float)cirque_pinnacle_get_scale() * 25.4f / (float)CIRQUE_PINNACLE_DIAMETER_MM); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a huge fan of using floats here, since AVR is still being used.
If it helps, the lib8tion library is present already (used for rgb stuff mostly), and that may help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, it may be best to pull out the features that would work with other sensors (the inertial cursor code, perhaps), and move that to the pointing_devices.c file, while device specific features (such as the circular scroll) be moved into the cirque files.
The report structure currently doesn't support pulling these features to higher level code. Inertial cursor depends on knowing when a finger lifts, and circular scroll depends on absolute coordinates, both of which are not exposed by report_mouse_t
. It might be possible to differentiate finger lift vs. stationary finger by the last velocity before mouse_report.x
and mouse_report.y
become 0, but that sounds pretty janky to me.
Seems like I should move these lower level to somewhere in drivers/sensors/ then, at least in the short term? I'm not sure about modifying the get_report()
API for this one use case.
I'm not a huge fan of using floats here, since AVR is still being used.
If it helps, the lib8tion library is present already (used for rgb stuff mostly), and that may help.
For simple stuff like converting between inch and millimeter, sure I can get rid of the float usage.
I had tried earlier on to use lib8tion for the fancy trigonometry further in, but wasn't able to get it to behave correctly. Some inputs were too wide for lib8tion and had wrapped around before I could evaluate the accuracy.
Been running this as is on AVR; while it has trouble reporting at 1KHz on master side, it's still above 500Hz (tested by making inertial cursor reports run at 1ms interval and turning off friction coefficient).
Inertial cursor is usable by any sensor with a lift status. Extracted into quantum/pointing_device_gestures.[ch], to be extracted into other get_report() functions if desired. For example, one could mount a PMW3360 upside down with a clear sheet over the lens, then use !(report.motion & 0x08) as Z and flick a finger on said clear surface as a pointing device. Circular scroll is specific to a circle trackpad reporting absolute coordinates, Cirque is currently the only sensor of this type in QMK. Tap could be generalized to multiple sensors, keeping it to lower level for now since tap enable will be a register write for relative mode. These two live in drivers/sensors/cirque_pinnacle_gestures.[ch].
dx & dy can be int16_t, up the accumulated position elements to 32-bits. Change coef to Q8.
Floating point is actually faster on AVR; cursor_glide_start() is around 33% faster with the call to hypotf() (~218us vs. ~327us), cursor_glide() is about equal between floating point and fixed point. Fixed point is much faster on a CPU with native 32-bit words and no FPU, such as ARM Cortex-M0. (~55us vs. ~18us for cursor_glide_start on an M0) Did not have a microcontroller board with Cortex-M4F to test performance with FPU.
Scroll wheel clicks are a rather coarse output, make use of lib8tion to reduce code size and speed up the process. atan2_8() did not produce enough resolution for correct scroll movement, replaced with a clone of that function in higher precision. Adjusted configuration to be easier to understand, scroll validation criteria now expressed in pixels and angles.
Extracted circular scroll and tap to drivers/sensors/cirque_pinnacle_gestures.[ch]. |
fa20e58
to
f08d5e7
Compare
Make configurations explicit. Also make the cursor glide cleanup more complete by memsetting to 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dkao My cirque trackpads finally arrived and I have tested the additions! Great work and I'm looking forward to see more keyboards using these in the future.
Now to the review part:
I wouldn't add that just to save memory. If a feature introduced behavior that might be unwanted it should be made opt-out either at compile time or runtime.
I gave this statement before actually looking at the numbers and the code, so sorry for revoking that decision so late in the process. This is due to comparing the compile size before and after this PR on AVR Pro Micros gave a difference of ~2.3KB in data, which is unfortunately the lowest denominator we have to address and huge in the world of these mcus. Therefore my proposal:
- The gestures are opt-in at compile time.
- The gestures are enabled at runtime by default.
- The gestures enabled state can be switched at runtime (already implemented).
Otherwise LGTM!
Make all gestures opt-in at compile time. This includes tap which was previously enabled by default. Also quashed a -Werror=maybe-uninitialized in cirque_pinnacle_get_report()
Remove stale math.h include
abbab2b
to
18aa3a1
Compare
Added enable flags for these three gestures, to be defined in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dkao Thanks for addressing my remarks so fast! Any final changes before the merge?
@KarlK90 Thanks, I have some changes queued up to handle POINTING_DEVICE_COMBINED for circular scroll. Was thinking to open up a separate PR since this one has already been reviewed. Can add them here if you don't mind.
EDIT: #17654 |
Adding gestures such as circular scroll and inertial cursor, along with some general QoL changes
Description
Types of Changes
Issues Fixed or Closed by This PR
Checklist