Rework and expand Pointing Device support (#14343)

Co-authored-by: Dasky <32983009+daskygit@users.noreply.github.com>
This commit is contained in:
Drashna Jaelre 2021-11-14 22:03:24 -08:00 committed by GitHub
parent 462c3a6151
commit 56e3f06a26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 2107 additions and 1705 deletions

View file

@ -107,10 +107,43 @@ ifeq ($(strip $(MOUSEKEY_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/mousekey.c SRC += $(QUANTUM_DIR)/mousekey.c
endif endif
VALID_POINTING_DEVICE_DRIVER_TYPES := adns5050 adns9800 analog_joystick cirque_pinnacle_i2c cirque_pinnacle_spi pmw3360 pimoroni_trackball custom
POINTING_DEVICE_DRIVER ?= custom
ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes) ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
ifeq ($(filter $(POINTING_DEVICE_DRIVER),$(VALID_POINTING_DEVICE_DRIVER_TYPES)),)
$(error POINTING_DEVICE_DRIVER="$(POINTING_DEVICE_DRIVER)" is not a valid pointing device type)
else
OPT_DEFS += -DPOINTING_DEVICE_ENABLE OPT_DEFS += -DPOINTING_DEVICE_ENABLE
MOUSE_ENABLE := yes MOUSE_ENABLE := yes
SRC += $(QUANTUM_DIR)/pointing_device.c SRC += $(QUANTUM_DIR)/pointing_device.c
SRC += $(QUANTUM_DIR)/pointing_device_drivers.c
ifneq ($(strip $(POINTING_DEVICE_DRIVER)), custom)
SRC += drivers/sensors/$(strip $(POINTING_DEVICE_DRIVER)).c
OPT_DEFS += -DPOINTING_DEVICE_DRIVER_$(strip $(shell echo $(POINTING_DEVICE_DRIVER) | tr '[:lower:]' '[:upper:]'))
endif
OPT_DEFS += -DPOINTING_DEVICE_DRIVER_$(strip $(POINTING_DEVICE_DRIVER))
ifeq ($(strip $(POINTING_DEVICE_DRIVER)), adns9800)
OPT_DEFS += -DSTM32_SPI -DHAL_USE_SPI=TRUE
QUANTUM_LIB_SRC += spi_master.c
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), analog_joystick)
OPT_DEFS += -DSTM32_ADC -DHAL_USE_ADC=TRUE
LIB_SRC += analog.c
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), cirque_pinnacle_i2c)
OPT_DEFS += -DSTM32_I2C -DHAL_USE_I2C=TRUE
SRC += drivers/sensors/cirque_pinnacle.c
QUANTUM_LIB_SRC += i2c_master.c
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), cirque_pinnacle_spi)
OPT_DEFS += -DSTM32_SPI -DHAL_USE_SPI=TRUE
SRC += drivers/sensors/cirque_pinnacle.c
QUANTUM_LIB_SRC += spi_master.c
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pimoroni_trackball)
OPT_DEFS += -DSTM32_SPI -DHAL_USE_I2C=TRUE
QUANTUM_LIB_SRC += i2c_master.c
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), pmw3360)
OPT_DEFS += -DSTM32_SPI -DHAL_USE_SPI=TRUE
QUANTUM_LIB_SRC += spi_master.c
endif
endif
endif endif
VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi

View file

@ -1,6 +1,6 @@
# Pointing Device :id=pointing-device # Pointing Device :id=pointing-device
Pointing Device is a generic name for a feature intended to be generic: moving the system pointer around. There are certainly other options for it - like mousekeys - but this aims to be easily modifiable and lightweight. You can implement custom keys to control functionality, or you can gather information from other peripherals and insert it directly here - let QMK handle the processing for you. Pointing Device is a generic name for a feature intended to be generic: moving the system pointer around. There are certainly other options for it - like mousekeys - but this aims to be easily modifiable and hardware driven. You can implement custom keys to control functionality, or you can gather information from other peripherals and insert it directly here - let QMK handle the processing for you.
To enable Pointing Device, uncomment the following line in your rules.mk: To enable Pointing Device, uncomment the following line in your rules.mk:
@ -8,12 +8,198 @@ To enable Pointing Device, uncomment the following line in your rules.mk:
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
``` ```
To manipulate the mouse report, you can use the following functions: ## Sensor Drivers
* `pointing_device_get_report()` - Returns the current report_mouse_t that represents the information sent to the host computer There are a number of sensors that are supported by default. Note that only one sensor can be enabled by `POINTING_DEVICE_DRIVER` at a time. If you need to enable more than one sensor, then you need to implement it manually.
* `pointing_device_set_report(report_mouse_t newMouseReport)` - Overrides and saves the report_mouse_t to be sent to the host computer
Keep in mind that a report_mouse_t (here "mouseReport") has the following properties: ### ADNS 5050 Sensor
To use the ADNS 5050 sensor, add this to your `rules.mk`
```make
POINTING_DEVICE_DRIVER = adns5050
```
The ADNS 5050 sensor uses a serial type protocol for communication, and requires an additional light source.
| Setting | Description |
|--------------------|---------------------------------------------------------------------|
|`ADNS5050_SCLK_PIN` | (Required) The pin connected to the clock pin of the sensor. |
|`ADNS5050_SDIO_PIN` | (Required) The pin connected to the data pin of the sensor. |
|`ADNS5050_CS_PIN` | (Required) The pin connected to the cable select pin of the sensor. |
The CPI range is 125-1375, in increments of 125. Defaults to 500 CPI.
### ADSN 9800 Sensor
To use the ADNS 9800 sensor, add this to your `rules.mk`
```make
POINTING_DEVICE_DRIVER = adns9800
```
The ADNS 9800 is an SPI driven optical sensor, that uses laser output for surface tracking.
| Setting | Description | Default |
|------------------------|------------------------------------------------------------------------|---------------|
|`ADNS9800_CLOCK_SPEED` | (Optional) Sets the clock speed that the sensor runs at. | `2000000` |
|`ADNS9800_SPI_LSBFIRST` | (Optional) Sets the Least/Most Significant Byte First setting for SPI. | `false` |
|`ADNS9800_SPI_MODE` | (Optional) Sets the SPI Mode for the sensor. | `3` |
|`ADNS9800_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ |
|`ADNS9800_CS_PIN` | (Required) Sets the Cable Select pin connected to the sensor. | _not defined_ |
The CPI range is 800-8200, in increments of 200. Defaults to 1800 CPI.
### Analog Joystick
To use an analog joystick to control the pointer, add this to your `rules.mk`
```make
POINTING_DEVICE_DRIVER = analog_joystick
```
The Analog Joystick is an analog (ADC) driven sensor. There are a variety of joysticks that you can use for this.
| Setting | Description | Default |
|----------------------------------|----------------------------------------------------------------------------|---------------|
|`ANALOG_JOYSTICK_X_AXIS_PIN` | (Required) The pin used for the vertical/X axis. | _not defined_ |
|`ANALOG_JOYSTICK_Y_AXIS_PIN` | (Required) The pin used for the horizontal/Y axis. | _not defined_ |
|`ANALOG_JOYSTICK_AXIS_MIN` | (Optional) Sets the lower range to be considered movement. | `0` |
|`ANALOG_JOYSTICK_AXIS_MAX` | (Optional) Sets the upper range to be considered movement. | `1023` |
|`ANALOG_JOYSTICK_SPEED_REGULATOR` | (Optional) The divisor used to slow down movement. (lower makes it faster) | `20` |
|`ANALOG_JOYSTICK_READ_INTERVAL` | (Optional) The interval in milliseconds between reads. | `10` |
|`ANALOG_JOYSTICK_SPEED_MAX` | (Optional) The maxiumum value used for motion. | `2` |
|`ANALOG_JOYSTICK_CLICK_PIN` | (Optional) The pin wired up to the press switch of the analog stick. | _not defined_ |
### Cirque Trackpad
To use the Cirque Trackpad sensor, add this to your `rules.mk`:
```make
POINTING_DEVICE_DRIVER = cirque_pinnacle_i2c
```
or
```make
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_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` |
| I2C Setting | Description | Default |
|--------------------------|---------------------------------------------------------------------------------|---------|
|`CIRQUE_PINNACLE_ADDR` | (Required) Sets the I2C Address for the Cirque Trackpad | `0x2A` |
|`CIRQUE_PINNACLE_TIMEOUT` | (Optional) The timeout for i2c communication with the trackpad in milliseconds. | `20` |
| SPI Setting | Description | Default |
|-------------------------------|------------------------------------------------------------------------|---------------|
|`CIRQUE_PINNACLE_CLOCK_SPEED` | (Optional) Sets the clock speed that the sensor runs at. | `1000000` |
|`CIRQUE_PINNACLE_SPI_LSBFIRST` | (Optional) Sets the Least/Most Significant Byte First setting for SPI. | `false` |
|`CIRQUE_PINNACLE_SPI_MODE` | (Optional) Sets the SPI Mode for the sensor. | `1` |
|`CIRQUE_PINNACLE_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ |
|`CIRQUE_PINNACLE_SPI_CS_PIN` | (Required) Sets the Cable Select pin connected to the sensor. | _not defined_ |
Default Scaling/CPI is 1024.
### Pimoroni Trackball
To use the Pimoroni Trackball module, add this to your `rules.mk`:
```make
POINTING_DEVICE_DRIVER = pimoroni_trackball
```
The Pimoroni Trackball module is a I2C based breakout board with an RGB enable trackball.
| Setting | Description | Default |
|-------------------------------------|------------------------------------------------------------------------------------|---------|
|`PIMORONI_TRACKBALL_ADDRESS` | (Required) Sets the I2C Address for the Pimoroni Trackball. | `0x0A` |
|`PIMORONI_TRACKBALL_TIMEOUT` | (Optional) The timeout for i2c communication with the trackpad in milliseconds. | `100` |
|`PIMORONI_TRACKBALL_INTERVAL_MS` | (Optional) The update/read interval for the sensor in milliseconds. | `8` |
|`PIMORONI_TRACKBALL_SCALE` | (Optional) The multiplier used to generate reports from the sensor. | `5` |
|`PIMORONI_TRACKBALL_DEBOUNCE_CYCLES` | (Optional) The number of scan cycles used for debouncing on the ball press. | `20` |
|`PIMORONI_TRACKBALL_ERROR_COUNT` | (Optional) Specifies the number of read/write errors until the sensor is disabled. | `10` |
### PMW 3360 Sensor
To use the PMW 3360 sensor, add this to your `rules.mk`
```make
POINTING_DEVICE_DRIVER = pmw3360
```
The PMW 3360 is an SPI driven optical sensor, that uses a built in IR LED for surface tracking.
| Setting | Description | Default |
|-----------------------------|--------------------------------------------------------------------------------------------|---------------|
|`PMW3360_CS_PIN` | (Required) Sets the Cable Select pin connected to the sensor. | _not defined_ |
|`PMW3360_CLOCK_SPEED` | (Optional) Sets the clock speed that the sensor runs at. | `2000000` |
|`PMW3360_SPI_LSBFIRST` | (Optional) Sets the Least/Most Significant Byte First setting for SPI. | `false` |
|`PMW3360_SPI_MODE` | (Optional) Sets the SPI Mode for the sensor. | `3` |
|`PMW3360_SPI_DIVISOR` | (Optional) Sets the SPI Divisor used for SPI communication. | _varies_ |
|`ROTATIONAL_TRANSFORM_ANGLE` | (Optional) Allows for the sensor data to be rotated +/- 30 degrees directly in the sensor. | `0` |
The CPI range is 100-12000, in increments of 100. Defaults to 1600 CPI.
### Custom Driver
If you have a sensor type that isn't supported here, you can manually implement it, by adding these functions (with the correct implementation for your device):
```c
void pointing_device_driver_init(void) {}
report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) { return mouse_report; }
uint16_t pointing_device_driver_get_cpi(void) { return 0; }
void pointing_device_driver_set_cpi(uint16_t cpi) {}
```
!> Ideally, new sensor hardware should be added to `drivers/sensors/` and `quantum/pointing_device_drivers.c`, but there may be cases where it's very specific to the hardware. So these functions are provided, just in case.
## Common Configuration
| Setting | Description | Default |
|-------------------------------|-----------------------------------------------------------------------|---------------|
|`POINTING_DEVICE_ROTATION_90` | (Optional) Rotates the X and Y data by 90 degrees. | _not defined_ |
|`POINTING_DEVICE_ROTATION_180` | (Optional) Rotates the X and Y data by 180 degrees. | _not defined_ |
|`POINTING_DEVICE_ROTATION_270` | (Optional) Rotates the X and Y data by 270 degrees. | _not defined_ |
|`POINTING_DEVICE_INVERT_X` | (Optional) Inverts the X axis report. | _not defined_ |
|`POINTING_DEVICE_INVERT_Y` | (Optional) Inverts the Y axis report. | _not defined_ |
|`POINTING_DEVICE_MOTION_PIN` | (Optional) If supported, will only read from sensor if pin is active. | _not defined_ |
## Callbacks and Functions
| Function | Description |
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
| `pointing_device_init_kb(void)` | Callback to allow for keyboard level initialization. Useful for additional hardware sensors. |
| `pointing_device_init_user(void)` | Callback to allow for user level initialization. Useful for additional hardware sensors. |
| `pointing_device_task_kb(mouse_report)` | Callback that sends sensor data, so keyboard code can intercept and modify the data. Returns a mouse report. |
| `pointing_device_task_user(mouse_report)` | Callback that sends sensor data, so user coe can intercept and modify the data. Returns a mouse report. |
| `pointing_device_handle_buttons(buttons, pressed, button)` | Callback to handle hardware button presses. Returns a `uint8_t`. |
| `pointing_device_get_cpi(void)` | Gets the current CPI/DPI setting from the sensor, if supported. |
| `pointing_device_set_cpi(uint16_t)` | Sets the CPI/DPI, if supported. |
| `pointing_device_get_report(void)` | Returns the current mouse report (as a `mouse_report_t` data structure). |
| `pointing_device_set_report(mouse_report)` | Sets the mouse report to the assigned `mouse_report_t` data structured passed to the function. |
| `pointing_device_send(void)` | Sends the current mouse report to the host system. Function can be replaced. |
| `has_mouse_report_changed(old, new)` | Compares the old and new `mouse_report_t` data and returns true only if it has changed. |
# Manipulating Mouse Reports
The report_mouse_t (here "mouseReport") has the following properties:
* `mouseReport.x` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing movement (+ to the right, - to the left) on the x axis. * `mouseReport.x` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing movement (+ to the right, - to the left) on the x axis.
* `mouseReport.y` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing movement (+ upward, - downward) on the y axis. * `mouseReport.y` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing movement (+ upward, - downward) on the y axis.
@ -21,8 +207,10 @@ Keep in mind that a report_mouse_t (here "mouseReport") has the following proper
* `mouseReport.h` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing horizontal scrolling (+ right, - left). * `mouseReport.h` - this is a signed int from -127 to 127 (not 128, this is defined in USB HID spec) representing horizontal scrolling (+ right, - left).
* `mouseReport.buttons` - this is a uint8_t in which all 8 bits are used. These bits represent the mouse button state - bit 0 is mouse button 1, and bit 7 is mouse button 8. * `mouseReport.buttons` - this is a uint8_t in which all 8 bits are used. These bits represent the mouse button state - bit 0 is mouse button 1, and bit 7 is mouse button 8.
Once you have made the necessary changes to the mouse report, you need to send it: To manually manipulate the mouse reports outside of the `pointing_device_task_*` functions, you can use:
* `pointing_device_get_report()` - Returns the current report_mouse_t that represents the information sent to the host computer
* `pointing_device_set_report(report_mouse_t newMouseReport)` - Overrides and saves the report_mouse_t to be sent to the host computer
* `pointing_device_send()` - Sends the mouse report to the host and zeroes out the report. * `pointing_device_send()` - Sends the mouse report to the host and zeroes out the report.
When the mouse report is sent, the x, y, v, and h values are set to 0 (this is done in `pointing_device_send()`, which can be overridden to avoid this behavior). This way, button states persist, but movement will only occur once. For further customization, both `pointing_device_init` and `pointing_device_task` can be overridden. When the mouse report is sent, the x, y, v, and h values are set to 0 (this is done in `pointing_device_send()`, which can be overridden to avoid this behavior). This way, button states persist, but movement will only occur once. For further customization, both `pointing_device_init` and `pointing_device_task` can be overridden.
@ -31,6 +219,8 @@ Additionally, by default, `pointing_device_send()` will only send a report when
Also, you use the `has_mouse_report_changed(new, old)` function to check to see if the report has changed. Also, you use the `has_mouse_report_changed(new, old)` function to check to see if the report has changed.
## Example
In the following example, a custom key is used to click the mouse and scroll 127 units vertically and horizontally, then undo all of that when released - because that's a totally useful function. Listen, this is an example: In the following example, a custom key is used to click the mouse and scroll 127 units vertically and horizontally, then undo all of that when released - because that's a totally useful function. Listen, this is an example:
```c ```c

View file

@ -20,81 +20,95 @@
#include "adns5050.h" #include "adns5050.h"
#include "wait.h" #include "wait.h"
#include "debug.h" #include "debug.h"
#include "print.h"
#include "gpio.h" #include "gpio.h"
#ifndef OPTIC_ROTATED // Registers
# define OPTIC_ROTATED false // clang-format off
#endif #define REG_PRODUCT_ID 0x00
#define REG_REVISION_ID 0x01
// Definitions for the ADNS serial line. #define REG_MOTION 0x02
#ifndef ADNS_SCLK_PIN #define REG_DELTA_X 0x03
# define ADNS_SCLK_PIN B7 #define REG_DELTA_Y 0x04
#endif #define REG_SQUAL 0x05
#define REG_SHUTTER_UPPER 0x06
#ifndef ADNS_SDIO_PIN #define REG_SHUTTER_LOWER 0x07
# define ADNS_SDIO_PIN C6 #define REG_MAXIMUM_PIXEL 0x08
#endif #define REG_PIXEL_SUM 0x09
#define REG_MINIMUM_PIXEL 0x0a
#ifndef ADNS_CS_PIN #define REG_PIXEL_GRAB 0x0b
# define ADNS_CS_PIN B4 #define REG_MOUSE_CONTROL 0x0d
#endif #define REG_MOUSE_CONTROL2 0x19
#define REG_LED_DC_MODE 0x22
#ifdef CONSOLE_ENABLE #define REG_CHIP_RESET 0x3a
void print_byte(uint8_t byte) { dprintf("%c%c%c%c%c%c%c%c|", (byte & 0x80 ? '1' : '0'), (byte & 0x40 ? '1' : '0'), (byte & 0x20 ? '1' : '0'), (byte & 0x10 ? '1' : '0'), (byte & 0x08 ? '1' : '0'), (byte & 0x04 ? '1' : '0'), (byte & 0x02 ? '1' : '0'), (byte & 0x01 ? '1' : '0')); } #define REG_PRODUCT_ID2 0x3e
#endif #define REG_INV_REV_ID 0x3f
#define REG_MOTION_BURST 0x63
// clang-format on
void adns5050_init(void) {
// Initialize the ADNS serial pins. // Initialize the ADNS serial pins.
void adns_init(void) { setPinOutput(ADNS5050_SCLK_PIN);
setPinOutput(ADNS_SCLK_PIN); setPinOutput(ADNS5050_SDIO_PIN);
setPinOutput(ADNS_SDIO_PIN); setPinOutput(ADNS5050_CS_PIN);
setPinOutput(ADNS_CS_PIN);
// reboot the adns.
// if the adns hasn't initialized yet, this is harmless.
adns5050_write_reg(REG_CHIP_RESET, 0x5a);
// wait maximum time before adns is ready.
// this ensures that the adns is actuall ready after reset.
wait_ms(55);
// read a burst from the adns and then discard it.
// gets the adns ready for write commands
// (for example, setting the dpi).
adns5050_read_burst();
} }
// Perform a synchronization with the ADNS. // Perform a synchronization with the ADNS.
// Just as with the serial protocol, this is used by the slave to send a // Just as with the serial protocol, this is used by the slave to send a
// synchronization signal to the master. // synchronization signal to the master.
void adns_sync(void) { void adns5050_sync(void) {
writePinLow(ADNS_CS_PIN); writePinLow(ADNS5050_CS_PIN);
wait_us(1); wait_us(1);
writePinHigh(ADNS_CS_PIN); writePinHigh(ADNS5050_CS_PIN);
} }
void adns_cs_select(void) { writePinLow(ADNS_CS_PIN); } void adns5050_cs_select(void) { writePinLow(ADNS5050_CS_PIN); }
void adns_cs_deselect(void) { writePinHigh(ADNS_CS_PIN); } void adns5050_cs_deselect(void) { writePinHigh(ADNS5050_CS_PIN); }
uint8_t adns_serial_read(void) { uint8_t adns5050_serial_read(void) {
setPinInput(ADNS_SDIO_PIN); setPinInput(ADNS5050_SDIO_PIN);
uint8_t byte = 0; uint8_t byte = 0;
for (uint8_t i = 0; i < 8; ++i) { for (uint8_t i = 0; i < 8; ++i) {
writePinLow(ADNS_SCLK_PIN); writePinLow(ADNS5050_SCLK_PIN);
wait_us(1); wait_us(1);
byte = (byte << 1) | readPin(ADNS_SDIO_PIN); byte = (byte << 1) | readPin(ADNS5050_SDIO_PIN);
writePinHigh(ADNS_SCLK_PIN); writePinHigh(ADNS5050_SCLK_PIN);
wait_us(1); wait_us(1);
} }
return byte; return byte;
} }
void adns_serial_write(uint8_t data) { void adns5050_serial_write(uint8_t data) {
setPinOutput(ADNS_SDIO_PIN); setPinOutput(ADNS5050_SDIO_PIN);
for (int8_t b = 7; b >= 0; b--) { for (int8_t b = 7; b >= 0; b--) {
writePinLow(ADNS_SCLK_PIN); writePinLow(ADNS5050_SCLK_PIN);
if (data & (1 << b)) if (data & (1 << b))
writePinHigh(ADNS_SDIO_PIN); writePinHigh(ADNS5050_SDIO_PIN);
else else
writePinLow(ADNS_SDIO_PIN); writePinLow(ADNS5050_SDIO_PIN);
wait_us(2); wait_us(2);
writePinHigh(ADNS_SCLK_PIN); writePinHigh(ADNS5050_SCLK_PIN);
} }
// tSWR. See page 15 of the ADNS spec sheet. // tSWR. See page 15 of the ADNS spec sheet.
@ -108,17 +122,17 @@ void adns_serial_write(uint8_t data) {
// Read a byte of data from a register on the ADNS. // Read a byte of data from a register on the ADNS.
// Don't forget to use the register map (as defined in the header file). // Don't forget to use the register map (as defined in the header file).
uint8_t adns_read_reg(uint8_t reg_addr) { uint8_t adns5050_read_reg(uint8_t reg_addr) {
adns_cs_select(); adns5050_cs_select();
adns_serial_write(reg_addr); adns5050_serial_write(reg_addr);
// We don't need a minimum tSRAD here. That's because a 4ms wait time is // We don't need a minimum tSRAD here. That's because a 4ms wait time is
// already included in adns_serial_write(), so we're good. // already included in adns5050_serial_write(), so we're good.
// See page 10 and 15 of the ADNS spec sheet. // See page 10 and 15 of the ADNS spec sheet.
// wait_us(4); // wait_us(4);
uint8_t byte = adns_serial_read(); uint8_t byte = adns5050_serial_read();
// tSRW & tSRR. See page 15 of the ADNS spec sheet. // tSRW & tSRR. See page 15 of the ADNS spec sheet.
// Technically, this is only necessary if the next operation is an SDIO // Technically, this is only necessary if the next operation is an SDIO
@ -126,38 +140,38 @@ uint8_t adns_read_reg(uint8_t reg_addr) {
// Honestly, this wait could probably be removed. // Honestly, this wait could probably be removed.
wait_us(1); wait_us(1);
adns_cs_deselect(); adns5050_cs_deselect();
return byte; return byte;
} }
void adns_write_reg(uint8_t reg_addr, uint8_t data) { void adns5050_write_reg(uint8_t reg_addr, uint8_t data) {
adns_cs_select(); adns5050_cs_select();
adns_serial_write(0b10000000 | reg_addr); adns5050_serial_write(0b10000000 | reg_addr);
adns_serial_write(data); adns5050_serial_write(data);
adns_cs_deselect(); adns5050_cs_deselect();
} }
report_adns_t adns_read_burst(void) { report_adns5050_t adns5050_read_burst(void) {
adns_cs_select(); adns5050_cs_select();
report_adns_t data; report_adns5050_t data;
data.dx = 0; data.dx = 0;
data.dy = 0; data.dy = 0;
adns_serial_write(REG_MOTION_BURST); adns5050_serial_write(REG_MOTION_BURST);
// We don't need a minimum tSRAD here. That's because a 4ms wait time is // We don't need a minimum tSRAD here. That's because a 4ms wait time is
// already included in adns_serial_write(), so we're good. // already included in adns5050_serial_write(), so we're good.
// See page 10 and 15 of the ADNS spec sheet. // See page 10 and 15 of the ADNS spec sheet.
// wait_us(4); // wait_us(4);
uint8_t x = adns_serial_read(); uint8_t x = adns5050_serial_read();
uint8_t y = adns_serial_read(); uint8_t y = adns5050_serial_read();
// Burst mode returns a bunch of other shit that we don't really need. // Burst mode returns a bunch of other shit that we don't really need.
// Setting CS to high ends burst mode early. // Setting CS to high ends burst mode early.
adns_cs_deselect(); adns5050_cs_deselect();
data.dx = convert_twoscomp(x); data.dx = convert_twoscomp(x);
data.dy = convert_twoscomp(y); data.dy = convert_twoscomp(y);
@ -175,12 +189,21 @@ int8_t convert_twoscomp(uint8_t data) {
} }
// Don't forget to use the definitions for CPI in the header file. // Don't forget to use the definitions for CPI in the header file.
void adns_set_cpi(uint8_t cpi) { adns_write_reg(REG_MOUSE_CONTROL2, cpi); } void adns5050_set_cpi(uint16_t cpi) {
uint8_t cpival = constrain((cpi / 125), 0x1, 0xD); // limits to 0--119
bool adns_check_signature(void) { adns5050_write_reg(REG_MOUSE_CONTROL2, 0b10000 | cpival);
uint8_t pid = adns_read_reg(REG_PRODUCT_ID); }
uint8_t rid = adns_read_reg(REG_REVISION_ID);
uint8_t pid2 = adns_read_reg(REG_PRODUCT_ID2); uint16_t adns5050_get_cpi(void) {
uint8_t cpival = adns5050_read_reg(REG_MOUSE_CONTROL2);
return (uint16_t)((cpival & 0b10000) * 125);
}
bool adns5050_check_signature(void) {
uint8_t pid = adns5050_read_reg(REG_PRODUCT_ID);
uint8_t rid = adns5050_read_reg(REG_REVISION_ID);
uint8_t pid2 = adns5050_read_reg(REG_PRODUCT_ID2);
return (pid == 0x12 && rid == 0x01 && pid2 == 0x26); return (pid == 0x12 && rid == 0x01 && pid2 == 0x26);
} }

View file

@ -21,28 +21,8 @@
#include <stdbool.h> #include <stdbool.h>
// Registers
#define REG_PRODUCT_ID 0x00
#define REG_REVISION_ID 0x01
#define REG_MOTION 0x02
#define REG_DELTA_X 0x03
#define REG_DELTA_Y 0x04
#define REG_SQUAL 0x05
#define REG_SHUTTER_UPPER 0x06
#define REG_SHUTTER_LOWER 0x07
#define REG_MAXIMUM_PIXEL 0x08
#define REG_PIXEL_SUM 0x09
#define REG_MINIMUM_PIXEL 0x0a
#define REG_PIXEL_GRAB 0x0b
#define REG_MOUSE_CONTROL 0x0d
#define REG_MOUSE_CONTROL2 0x19
#define REG_LED_DC_MODE 0x22
#define REG_CHIP_RESET 0x3a
#define REG_PRODUCT_ID2 0x3e
#define REG_INV_REV_ID 0x3f
#define REG_MOTION_BURST 0x63
// CPI values // CPI values
// clang-format off
#define CPI125 0x11 #define CPI125 0x11
#define CPI250 0x12 #define CPI250 0x12
#define CPI375 0x13 #define CPI375 0x13
@ -54,26 +34,39 @@
#define CPI1125 0x19 #define CPI1125 0x19
#define CPI1250 0x1a #define CPI1250 0x1a
#define CPI1375 0x1b #define CPI1375 0x1b
// clang-format on
#ifdef CONSOLE_ENABLE #define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
void print_byte(uint8_t byte);
// Definitions for the ADNS serial line.
#ifndef ADNS5050_SCLK_PIN
# error "No clock pin defined -- missing ADNS5050_SCLK_PIN"
#endif
#ifndef ADNS5050_SDIO_PIN
# error "No data pin defined -- missing ADNS5050_SDIO_PIN"
#endif
#ifndef ADNS5050_CS_PIN
# error "No chip select pin defined -- missing ADNS5050_CS_PIN"
#endif #endif
typedef struct { typedef struct {
int8_t dx; int8_t dx;
int8_t dy; int8_t dy;
} report_adns_t; } report_adns5050_t;
// A bunch of functions to implement the ADNS5050-specific serial protocol. // A bunch of functions to implement the ADNS5050-specific serial protocol.
// Note that the "serial.h" driver is insufficient, because it does not // Note that the "serial.h" driver is insufficient, because it does not
// manually manipulate a serial clock signal. // manually manipulate a serial clock signal.
void adns_init(void); void adns5050_init(void);
void adns_sync(void); void adns5050_sync(void);
uint8_t adns_serial_read(void); uint8_t adns5050_serial_read(void);
void adns_serial_write(uint8_t data); void adns5050_serial_write(uint8_t data);
uint8_t adns_read_reg(uint8_t reg_addr); uint8_t adns5050_read_reg(uint8_t reg_addr);
void adns_write_reg(uint8_t reg_addr, uint8_t data); void adns5050_write_reg(uint8_t reg_addr, uint8_t data);
report_adns_t adns_read_burst(void); report_adns5050_t adns5050_read_burst(void);
void adns5050_set_cpi(uint16_t cpi);
uint16_t adns5050_get_cpi(void);
int8_t convert_twoscomp(uint8_t data); int8_t convert_twoscomp(uint8_t data);
void adns_set_cpi(uint8_t cpi); bool adns5050_check_signature(void);
bool adns_check_signature(void);

View file

@ -15,11 +15,12 @@
*/ */
#include "spi_master.h" #include "spi_master.h"
#include "quantum.h"
#include "adns9800_srom_A6.h" #include "adns9800_srom_A6.h"
#include "adns9800.h" #include "adns9800.h"
#include "wait.h"
// registers // registers
// clang-format off
#define REG_Product_ID 0x00 #define REG_Product_ID 0x00
#define REG_Revision_ID 0x01 #define REG_Revision_ID 0x01
#define REG_Motion 0x02 #define REG_Motion 0x02
@ -66,32 +67,28 @@
#define REG_SROM_Load_Burst 0x62 #define REG_SROM_Load_Burst 0x62
#define REG_Pixel_Burst 0x64 #define REG_Pixel_Burst 0x64
#define ADNS_CLOCK_SPEED 2000000
#define MIN_CPI 200 #define MIN_CPI 200
#define MAX_CPI 8200 #define MAX_CPI 8200
#define CPI_STEP 200 #define CPI_STEP 200
#define CLAMP_CPI(value) value<MIN_CPI ? MIN_CPI : value> MAX_CPI ? MAX_CPI : value #define CLAMP_CPI(value) value<MIN_CPI ? MIN_CPI : value> MAX_CPI ? MAX_CPI : value
#define SPI_MODE 3
#define SPI_DIVISOR (F_CPU / ADNS_CLOCK_SPEED)
#define US_BETWEEN_WRITES 120 #define US_BETWEEN_WRITES 120
#define US_BETWEEN_READS 20 #define US_BETWEEN_READS 20
#define US_BEFORE_MOTION 100 #define US_BEFORE_MOTION 100
#define MSB1 0x80 #define MSB1 0x80
// clang-format on
extern const uint8_t firmware_data[]; void adns9800_spi_start(void) { spi_start(ADNS9800_CS_PIN, false, ADNS9800_SPI_MODE, ADNS9800_SPI_DIVISOR); }
void adns_spi_start(void) { spi_start(SPI_SS_PIN, false, SPI_MODE, SPI_DIVISOR); } void adns9800_write(uint8_t reg_addr, uint8_t data) {
adns9800_spi_start();
void adns_write(uint8_t reg_addr, uint8_t data) {
adns_spi_start();
spi_write(reg_addr | MSB1); spi_write(reg_addr | MSB1);
spi_write(data); spi_write(data);
spi_stop(); spi_stop();
wait_us(US_BETWEEN_WRITES); wait_us(US_BETWEEN_WRITES);
} }
uint8_t adns_read(uint8_t reg_addr) { uint8_t adns9800_read(uint8_t reg_addr) {
adns_spi_start(); adns9800_spi_start();
spi_write(reg_addr & 0x7f); spi_write(reg_addr & 0x7f);
uint8_t data = spi_read(); uint8_t data = spi_read();
spi_stop(); spi_stop();
@ -100,39 +97,39 @@ uint8_t adns_read(uint8_t reg_addr) {
return data; return data;
} }
void adns_init() { void adns9800_init() {
setPinOutput(SPI_SS_PIN); setPinOutput(ADNS9800_CS_PIN);
spi_init(); spi_init();
// reboot // reboot
adns_write(REG_Power_Up_Reset, 0x5a); adns9800_write(REG_Power_Up_Reset, 0x5a);
wait_ms(50); wait_ms(50);
// read registers and discard // read registers and discard
adns_read(REG_Motion); adns9800_read(REG_Motion);
adns_read(REG_Delta_X_L); adns9800_read(REG_Delta_X_L);
adns_read(REG_Delta_X_H); adns9800_read(REG_Delta_X_H);
adns_read(REG_Delta_Y_L); adns9800_read(REG_Delta_Y_L);
adns_read(REG_Delta_Y_H); adns9800_read(REG_Delta_Y_H);
// upload firmware // upload firmware
// 3k firmware mode // 3k firmware mode
adns_write(REG_Configuration_IV, 0x02); adns9800_write(REG_Configuration_IV, 0x02);
// enable initialisation // enable initialisation
adns_write(REG_SROM_Enable, 0x1d); adns9800_write(REG_SROM_Enable, 0x1d);
// wait a frame // wait a frame
wait_ms(10); wait_ms(10);
// start SROM download // start SROM download
adns_write(REG_SROM_Enable, 0x18); adns9800_write(REG_SROM_Enable, 0x18);
// write the SROM file // write the SROM file
adns_spi_start(); adns9800_spi_start();
spi_write(REG_SROM_Load_Burst | 0x80); spi_write(REG_SROM_Load_Burst | 0x80);
wait_us(15); wait_us(15);
@ -140,7 +137,7 @@ void adns_init() {
// send all bytes of the firmware // send all bytes of the firmware
unsigned char c; unsigned char c;
for (int i = 0; i < FIRMWARE_LENGTH; i++) { for (int i = 0; i < FIRMWARE_LENGTH; i++) {
c = (unsigned char)pgm_read_byte(firmware_data + i); c = (unsigned char)pgm_read_byte(adns9800_firmware_data + i);
spi_write(c); spi_write(c);
wait_us(15); wait_us(15);
} }
@ -150,18 +147,30 @@ void adns_init() {
wait_ms(10); wait_ms(10);
// enable laser // enable laser
uint8_t laser_ctrl0 = adns_read(REG_LASER_CTRL0); uint8_t laser_ctrl0 = adns9800_read(REG_LASER_CTRL0);
adns_write(REG_LASER_CTRL0, laser_ctrl0 & 0xf0); adns9800_write(REG_LASER_CTRL0, laser_ctrl0 & 0xf0);
adns9800_set_cpi(ADNS9800_CPI);
} }
config_adns_t adns_get_config(void) { config_adns9800_t adns9800_get_config(void) {
uint8_t config_1 = adns_read(REG_Configuration_I); uint8_t config_1 = adns9800_read(REG_Configuration_I);
return (config_adns_t){(config_1 & 0xFF) * CPI_STEP}; return (config_adns9800_t){(config_1 & 0xFF) * CPI_STEP};
} }
void adns_set_config(config_adns_t config) { void adns9800_set_config(config_adns9800_t config) {
uint8_t config_1 = (CLAMP_CPI(config.cpi) / CPI_STEP) & 0xFF; uint8_t config_1 = (CLAMP_CPI(config.cpi) / CPI_STEP) & 0xFF;
adns_write(REG_Configuration_I, config_1); adns9800_write(REG_Configuration_I, config_1);
}
uint16_t adns9800_get_cpi(void) {
uint8_t config_1 = adns9800_read(REG_Configuration_I);
return (uint16_t){(config_1 & 0xFF) * CPI_STEP};
}
void adns9800_set_cpi(uint16_t cpi) {
uint8_t config_1 = (CLAMP_CPI(cpi) / CPI_STEP) & 0xFF;
adns9800_write(REG_Configuration_I, config_1);
} }
static int16_t convertDeltaToInt(uint8_t high, uint8_t low) { static int16_t convertDeltaToInt(uint8_t high, uint8_t low) {
@ -174,10 +183,10 @@ static int16_t convertDeltaToInt(uint8_t high, uint8_t low) {
return twos_comp; return twos_comp;
} }
report_adns_t adns_get_report(void) { report_adns9800_t adns9800_get_report(void) {
report_adns_t report = {0, 0}; report_adns9800_t report = {0, 0};
adns_spi_start(); adns9800_spi_start();
// start burst mode // start burst mode
spi_write(REG_Motion_Burst & 0x7f); spi_write(REG_Motion_Burst & 0x7f);

View file

@ -18,18 +18,48 @@
#include <stdint.h> #include <stdint.h>
#ifndef ADNS9800_CPI
# define ADNS9800_CPI 1600
#endif
#ifndef ADNS9800_CLOCK_SPEED
# define ADNS9800_CLOCK_SPEED 2000000
#endif
#ifndef ADNS9800_SPI_LSBFIRST
# define ADNS9800_SPI_LSBFIRST false
#endif
#ifndef ADNS9800_SPI_MODE
# define ADNS9800_SPI_MODE 3
#endif
#ifndef ADNS9800_SPI_DIVISOR
# ifdef __AVR__
# define ADNS9800_SPI_DIVISOR (F_CPU / ADNS9800_CLOCK_SPEED)
# else
# define ADNS9800_SPI_DIVISOR 64
# endif
#endif
#ifndef ADNS9800_CS_PIN
# error "No chip select pin defined -- missing ADNS9800_CS_PIN"
#endif
typedef struct { typedef struct {
/* 200 - 8200 CPI supported */ /* 200 - 8200 CPI supported */
uint16_t cpi; uint16_t cpi;
} config_adns_t; } config_adns9800_t;
typedef struct { typedef struct {
int16_t x; int16_t x;
int16_t y; int16_t y;
} report_adns_t; } report_adns9800_t;
void adns_init(void); void adns9800_init(void);
config_adns_t adns_get_config(void); config_adns9800_t adns9800_get_config(void);
void adns_set_config(config_adns_t); void adns9800_set_config(config_adns9800_t);
uint16_t adns9800_get_cpi(void);
void adns9800_set_cpi(uint16_t cpi);
/* Reads and clears the current delta values on the ADNS sensor */ /* Reads and clears the current delta values on the ADNS sensor */
report_adns_t adns_get_report(void); report_adns9800_t adns9800_get_report(void);

View file

@ -6,7 +6,7 @@
// clang-format off // clang-format off
const uint8_t firmware_data[FIRMWARE_LENGTH] PROGMEM = { const uint8_t adns9800_firmware_data[FIRMWARE_LENGTH] PROGMEM = {
0x03, 0xA6, 0x68, 0x1E, 0x7D, 0x10, 0x7E, 0x7E, 0x5F, 0x1C, 0xB8, 0xF2, 0x47, 0x0C, 0x7B, 0x74, 0x03, 0xA6, 0x68, 0x1E, 0x7D, 0x10, 0x7E, 0x7E, 0x5F, 0x1C, 0xB8, 0xF2, 0x47, 0x0C, 0x7B, 0x74,
0x4B, 0x14, 0x8B, 0x75, 0x66, 0x51, 0x0B, 0x8C, 0x76, 0x74, 0x4B, 0x14, 0xAA, 0xD6, 0x0F, 0x9C, 0x4B, 0x14, 0x8B, 0x75, 0x66, 0x51, 0x0B, 0x8C, 0x76, 0x74, 0x4B, 0x14, 0xAA, 0xD6, 0x0F, 0x9C,
0xBA, 0xF6, 0x6E, 0x3F, 0xDD, 0x38, 0xD5, 0x02, 0x80, 0x9B, 0x82, 0x6D, 0x58, 0x13, 0xA4, 0xAB, 0xBA, 0xF6, 0x6E, 0x3F, 0xDD, 0x38, 0xD5, 0x02, 0x80, 0x9B, 0x82, 0x6D, 0x58, 0x13, 0xA4, 0xAB,

View file

@ -0,0 +1,94 @@
/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "analog_joystick.h"
#include "analog.h"
#include "gpio.h"
#include "wait.h"
// Set Parameters
uint16_t minAxisValue = ANALOG_JOYSTICK_AXIS_MIN;
uint16_t maxAxisValue = ANALOG_JOYSTICK_AXIS_MAX;
uint8_t maxCursorSpeed = ANALOG_JOYSTICK_SPEED_MAX;
uint8_t speedRegulator = ANALOG_JOYSTICK_SPEED_REGULATOR; // Lower Values Create Faster Movement
int16_t xOrigin, yOrigin;
uint16_t lastCursor = 0;
int16_t axisCoordinate(uint8_t pin, uint16_t origin) {
int8_t direction;
int16_t distanceFromOrigin;
int16_t range;
int16_t position = analogReadPin(pin);
if (origin == position) {
return 0;
} else if (origin > position) {
distanceFromOrigin = origin - position;
range = origin - minAxisValue;
direction = -1;
} else {
distanceFromOrigin = position - origin;
range = maxAxisValue - origin;
direction = 1;
}
float percent = (float)distanceFromOrigin / range;
int16_t coordinate = (int16_t)(percent * 100);
if (coordinate < 0) {
return 0;
} else if (coordinate > 100) {
return 100 * direction;
} else {
return coordinate * direction;
}
}
int8_t axisToMouseComponent(uint8_t pin, int16_t origin, uint8_t maxSpeed) {
int16_t coordinate = axisCoordinate(pin, origin);
if (coordinate != 0) {
float percent = (float)coordinate / 100;
return percent * maxCursorSpeed * (abs(coordinate) / speedRegulator);
} else {
return 0;
}
}
report_analog_joystick_t analog_joystick_read(void) {
report_analog_joystick_t report = {0};
if (timer_elapsed(lastCursor) > ANALOG_JOYSTICK_READ_INTERVAL) {
lastCursor = timer_read();
report.x = axisToMouseComponent(ANALOG_JOYSTICK_X_AXIS_PIN, xOrigin, maxCursorSpeed);
report.y = axisToMouseComponent(ANALOG_JOYSTICK_Y_AXIS_PIN, yOrigin, maxCursorSpeed);
}
#ifdef ANALOG_JOYSTICK_CLICK_PIN
report.button = !readPin(ANALOG_JOYSTICK_CLICK_PIN);
#endif
return report;
}
void analog_joystick_init(void) {
#ifdef ANALOG_JOYSTICK_CLICK_PIN
setPinInputHigh(ANALOG_JOYSTICK_CLICK_PIN);
#endif
// Account for drift
xOrigin = analogReadPin(ANALOG_JOYSTICK_X_AXIS_PIN);
yOrigin = analogReadPin(ANALOG_JOYSTICK_Y_AXIS_PIN);
}

View file

@ -0,0 +1,51 @@
/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifndef ANALOG_JOYSTICK_X_AXIS_PIN
# error No pin specified for X Axis
#endif
#ifndef ANALOG_JOYSTICK_Y_AXIS_PIN
# error No pin specified for Y Axis
#endif
#ifndef ANALOG_JOYSTICK_AXIS_MIN
# define ANALOG_JOYSTICK_AXIS_MIN 0
#endif
#ifndef ANALOG_JOYSTICK_AXIS_MAX
# define ANALOG_JOYSTICK_AXIS_MAX 1023
#endif
#ifndef ANALOG_JOYSTICK_SPEED_REGULATOR
# define ANALOG_JOYSTICK_SPEED_REGULATOR 20
#endif
#ifndef ANALOG_JOYSTICK_READ_INTERVAL
# define ANALOG_JOYSTICK_READ_INTERVAL 10
#endif
#ifndef ANALOG_JOYSTICK_SPEED_MAX
# define ANALOG_JOYSTICK_SPEED_MAX 2
#endif
typedef struct {
int8_t x;
int8_t y;
bool button;
} report_analog_joystick_t;
report_analog_joystick_t analog_joystick_read(void);
void analog_joystick_init(void);

View file

@ -0,0 +1,232 @@
// Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license
#include "cirque_pinnacle.h"
#include "print.h"
#include "debug.h"
#include "wait.h"
// Registers for RAP
// clang-format off
#define FIRMWARE_ID 0x00
#define FIRMWARE_VERSION_C 0x01
#define STATUS_1 0x02
#define SYSCONFIG_1 0x03
#define FEEDCONFIG_1 0x04
#define FEEDCONFIG_2 0x05
#define CALIBRATION_CONFIG_1 0x07
#define PS2_AU_CONTROL 0x08
#define SAMPLE_RATE 0x09
#define Z_IDLE_COUNT 0x0A
#define Z_SCALER 0x0B
#define SLEEP_INTERVAL 0x0C
#define SLEEP_TIMER 0x0D
#define PACKET_BYTE_0 0x12
#define PACKET_BYTE_1 0x13
#define PACKET_BYTE_2 0x14
#define PACKET_BYTE_3 0x15
#define PACKET_BYTE_4 0x16
#define PACKET_BYTE_5 0x17
#define ERA_VALUE 0x1B
#define ERA_HIGH_BYTE 0x1C
#define ERA_LOW_BYTE 0x1D
#define ERA_CONTROL 0x1E
// ADC-attenuation settings (held in BIT_7 and BIT_6)
// 1X = most sensitive, 4X = least sensitive
#define ADC_ATTENUATE_1X 0x00
#define ADC_ATTENUATE_2X 0x40
#define ADC_ATTENUATE_3X 0x80
#define ADC_ATTENUATE_4X 0xC0
// 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;
uint16_t scale_data = 1024;
void cirque_pinnacle_clear_flags(void);
void cirque_pinnacle_enable_feed(bool feedEnable);
void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count);
void RAP_Write(uint8_t address, uint8_t data);
#ifdef CONSOLE_ENABLE
void print_byte(uint8_t byte) { xprintf("%c%c%c%c%c%c%c%c|", (byte & 0x80 ? '1' : '0'), (byte & 0x40 ? '1' : '0'), (byte & 0x20 ? '1' : '0'), (byte & 0x10 ? '1' : '0'), (byte & 0x08 ? '1' : '0'), (byte & 0x04 ? '1' : '0'), (byte & 0x02 ? '1' : '0'), (byte & 0x01 ? '1' : '0')); }
#endif
/* Logical Scaling Functions */
// Clips raw coordinates to "reachable" window of sensor
// NOTE: values outside this window can only appear as a result of noise
void ClipCoordinates(pinnacle_data_t* coordinates) {
if (coordinates->xValue < CIRQUE_PINNACLE_X_LOWER) {
coordinates->xValue = CIRQUE_PINNACLE_X_LOWER;
} else if (coordinates->xValue > CIRQUE_PINNACLE_X_UPPER) {
coordinates->xValue = CIRQUE_PINNACLE_X_UPPER;
}
if (coordinates->yValue < CIRQUE_PINNACLE_Y_LOWER) {
coordinates->yValue = CIRQUE_PINNACLE_Y_LOWER;
} else if (coordinates->yValue > CIRQUE_PINNACLE_Y_UPPER) {
coordinates->yValue = CIRQUE_PINNACLE_Y_UPPER;
}
}
uint16_t cirque_pinnacle_get_scale(void) { return scale_data; }
void cirque_pinnacle_set_scale(uint16_t scale) { scale_data = scale; }
// Scales data to desired X & Y resolution
void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResolution, uint16_t yResolution) {
uint32_t xTemp = 0;
uint32_t yTemp = 0;
ClipCoordinates(coordinates);
xTemp = coordinates->xValue;
yTemp = coordinates->yValue;
// translate coordinates to (0, 0) reference by subtracting edge-offset
xTemp -= CIRQUE_PINNACLE_X_LOWER;
yTemp -= CIRQUE_PINNACLE_Y_LOWER;
// scale coordinates to (xResolution, yResolution) range
coordinates->xValue = (uint16_t)(xTemp * xResolution / CIRQUE_PINNACLE_X_RANGE);
coordinates->yValue = (uint16_t)(yTemp * yResolution / CIRQUE_PINNACLE_Y_RANGE);
}
// Clears Status1 register flags (SW_CC and SW_DR)
void cirque_pinnacle_clear_flags() {
RAP_Write(STATUS_1, 0x00);
wait_us(50);
}
// 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);
}
}
/* ERA (Extended Register Access) Functions */
// Reads <count> bytes from an extended register at <address> (16-bit address),
// stores values in <*data>
void ERA_ReadBytes(uint16_t address, uint8_t* data, uint16_t count) {
uint8_t ERAControlValue = 0xFF;
cirque_pinnacle_enable_feed(false); // Disable feed
RAP_Write(ERA_HIGH_BYTE, (uint8_t)(address >> 8)); // Send upper byte of ERA address
RAP_Write(ERA_LOW_BYTE, (uint8_t)(address & 0x00FF)); // Send lower byte of ERA address
for (uint16_t i = 0; i < count; i++) {
RAP_Write(ERA_CONTROL, 0x05); // Signal ERA-read (auto-increment) to Pinnacle
// Wait for status register 0x1E to clear
do {
RAP_ReadBytes(ERA_CONTROL, &ERAControlValue, 1);
} while (ERAControlValue != 0x00);
RAP_ReadBytes(ERA_VALUE, data + i, 1);
cirque_pinnacle_clear_flags();
}
}
// Writes a byte, <data>, to an extended register at <address> (16-bit address)
void ERA_WriteByte(uint16_t address, uint8_t data) {
uint8_t ERAControlValue = 0xFF;
cirque_pinnacle_enable_feed(false); // Disable feed
RAP_Write(ERA_VALUE, data); // Send data byte to be written
RAP_Write(ERA_HIGH_BYTE, (uint8_t)(address >> 8)); // Upper byte of ERA address
RAP_Write(ERA_LOW_BYTE, (uint8_t)(address & 0x00FF)); // Lower byte of ERA address
RAP_Write(ERA_CONTROL, 0x02); // Signal an ERA-write to Pinnacle
// Wait for status register 0x1E to clear
do {
RAP_ReadBytes(ERA_CONTROL, &ERAControlValue, 1);
} while (ERAControlValue != 0x00);
cirque_pinnacle_clear_flags();
}
void cirque_pinnacle_set_adc_attenuation(uint8_t adcGain) {
uint8_t temp = 0x00;
ERA_ReadBytes(0x0187, &temp, 1);
temp &= 0x3F; // clear top two bits
temp |= adcGain;
ERA_WriteByte(0x0187, temp);
ERA_ReadBytes(0x0187, &temp, 1);
}
// Changes thresholds to improve detection of fingers
void cirque_pinnacle_tune_edge_sensitivity(void) {
uint8_t temp = 0x00;
ERA_ReadBytes(0x0149, &temp, 1);
ERA_WriteByte(0x0149, 0x04);
ERA_ReadBytes(0x0149, &temp, 1);
ERA_ReadBytes(0x0168, &temp, 1);
ERA_WriteByte(0x0168, 0x03);
ERA_ReadBytes(0x0168, &temp, 1);
}
/* Pinnacle-based TM040040 Functions */
void cirque_pinnacle_init(void) {
#if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
spi_init();
#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c)
i2c_init();
#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);
// Host enables preferred output mode (absolute)
RAP_Write(FEEDCONFIG_1, FEEDCONFIG_1_VALUE);
// Host sets z-idle packet count to 5 (default is 30)
RAP_Write(Z_IDLE_COUNT, Z_IDLE_COUNT_VALUE);
cirque_pinnacle_set_adc_attenuation(0xFF);
cirque_pinnacle_tune_edge_sensitivity();
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};
RAP_ReadBytes(PACKET_BYTE_0, data, 6);
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);
return result;
}

View file

@ -0,0 +1,74 @@
// Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license
#pragma once
#include <stdint.h>
#include <stdbool.h>
// 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
#endif
// Coordinate scaling values
#ifndef CIRQUE_PINNACLE_X_LOWER
# define CIRQUE_PINNACLE_X_LOWER 127 // min "reachable" X value
#endif
#ifndef CIRQUE_PINNACLE_X_UPPER
# define CIRQUE_PINNACLE_X_UPPER 1919 // max "reachable" X value
#endif
#ifndef CIRQUE_PINNACLE_Y_LOWER
# define CIRQUE_PINNACLE_Y_LOWER 63 // min "reachable" Y value
#endif
#ifndef CIRQUE_PINNACLE_Y_UPPER
# define CIRQUE_PINNACLE_Y_UPPER 1471 // max "reachable" Y value
#endif
#ifndef CIRQUE_PINNACLE_X_RANGE
# define CIRQUE_PINNACLE_X_RANGE (CIRQUE_PINNACLE_X_UPPER - CIRQUE_PINNACLE_X_LOWER)
#endif
#ifndef CIRQUE_PINNACLE_Y_RANGE
# define CIRQUE_PINNACLE_Y_RANGE (CIRQUE_PINNACLE_Y_UPPER - CIRQUE_PINNACLE_Y_LOWER)
#endif
#if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c)
# include "i2c_master.h"
// Cirque's 7-bit I2C Slave Address
# ifndef CIRQUE_PINNACLE_ADDR
# define CIRQUE_PINNACLE_ADDR 0x2A
# endif
#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
# include "spi_master.h"
# ifndef CIRQUE_PINNACLE_CLOCK_SPEED
# define CIRQUE_PINNACLE_CLOCK_SPEED 10000000
# endif
# ifndef CIRQUE_PINNACLE_SPI_LSBFIRST
# define CIRQUE_PINNACLE_SPI_LSBFIRST false
# endif
# ifndef CIRQUE_PINNACLE_SPI_MODE
# define CIRQUE_PINNACLE_SPI_MODE 1
# endif
# ifndef CIRQUE_PINNACLE_SPI_DIVISOR
# ifdef __AVR__
# define CIRQUE_PINNACLE_SPI_DIVISOR (F_CPU / CIRQUE_PINNACLE_CLOCK_SPEED)
# else
# define CIRQUE_PINNACLE_SPI_DIVISOR 64
# endif
# ifndef CIRQUE_PINNACLE_SPI_CS_PIN
# error "No Chip Select pin has been defined -- missing CIRQUE_PINNACLE_SPI_CS_PIN define"
# endif
# endif
#endif

View file

@ -0,0 +1,43 @@
// Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license
#include "cirque_pinnacle.h"
#include "i2c_master.h"
#include "print.h"
#include "debug.h"
#include "stdio.h"
// Masks for Cirque Register Access Protocol (RAP)
#define WRITE_MASK 0x80
#define READ_MASK 0xA0
extern bool touchpad_init;
/* RAP Functions */
// Reads <count> Pinnacle registers starting at <address>
void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count) {
uint8_t cmdByte = READ_MASK | address; // Form the READ command byte
if (touchpad_init) {
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");
#endif
touchpad_init = false;
}
i2c_stop();
}
}
// Writes single-byte <data> to <address>
void RAP_Write(uint8_t address, uint8_t data) {
uint8_t cmdByte = WRITE_MASK | address; // Form the WRITE command byte
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");
#endif
touchpad_init = false;
}
i2c_stop();
}
}

View file

@ -0,0 +1,52 @@
// Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license
#include "cirque_pinnacle.h"
#include "spi_master.h"
#include "print.h"
#include "debug.h"
// Masks for Cirque Register Access Protocol (RAP)
#define WRITE_MASK 0x80
#define READ_MASK 0xA0
extern bool touchpad_init;
/* RAP Functions */
// Reads <count> Pinnacle registers starting at <address>
void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count) {
uint8_t cmdByte = READ_MASK | address; // Form the READ command byte
if (touchpad_init) {
if (spi_start(CIRQUE_PINNACLE_SPI_CS_PIN, CIRQUE_TRACKPAD_SPI_LSBFIRST, CIRQUE_PINNACLE_SPI_MODE, CIRQUE_PINNACLE_SPI_DIVISOR)) {
spi_write(cmdByte);
spi_read(); // filler
spi_read(); // filler
for (uint8_t i = 0; i < count; i++) {
data[i] = spi_read(); // each sepsequent read gets another register's contents
}
} else {
#ifdef CONSOLE_ENABLE
dprintf("error right touchpad\n");
#endif
touchpad_init = false;
j
}
spi_stop();
}
}
// Writes single-byte <data> to <address>
void RAP_Write(uint8_t address, uint8_t data) {
uint8_t cmdByte = WRITE_MASK | address; // Form the WRITE command byte
if (touchpad_init) {
if (spi_start(CIRQUE_PINNACLE_SPI_CS_PIN, CIRQUE_TRACKPAD_SPI_LSBFIRST, CIRQUE_PINNACLE_SPI_MODE, CIRQUE_PINNACLE_SPI_DIVISOR)) {
spi_write(cmdByte);
spi_write(data);
} else {
#ifdef CONSOLE_ENABLE
dprintf("error right touchpad\n");
#endif
touchpad_init = false;
}
spi_stop();
}
}

View file

@ -17,73 +17,55 @@
#include "pimoroni_trackball.h" #include "pimoroni_trackball.h"
#include "i2c_master.h" #include "i2c_master.h"
#include "print.h" #include "print.h"
#include "debug.h"
#include "timer.h"
#ifndef PIMORONI_TRACKBALL_ADDRESS // clang-format off
# define PIMORONI_TRACKBALL_ADDRESS 0x0A #define PIMORONI_TRACKBALL_REG_LED_RED 0x00
#endif #define PIMORONI_TRACKBALL_REG_LED_GRN 0x01
#ifndef PIMORONI_TRACKBALL_INTERVAL_MS #define PIMORONI_TRACKBALL_REG_LED_BLU 0x02
# define PIMORONI_TRACKBALL_INTERVAL_MS 8 #define PIMORONI_TRACKBALL_REG_LED_WHT 0x03
#endif #define PIMORONI_TRACKBALL_REG_LEFT 0x04
#ifndef PIMORONI_TRACKBALL_MOUSE_SCALE #define PIMORONI_TRACKBALL_REG_RIGHT 0x05
# define PIMORONI_TRACKBALL_MOUSE_SCALE 5 #define PIMORONI_TRACKBALL_REG_UP 0x06
#endif #define PIMORONI_TRACKBALL_REG_DOWN 0x07
#ifndef PIMORONI_TRACKBALL_SCROLL_SCALE // clang-format on
# define PIMORONI_TRACKBALL_SCROLL_SCALE 1
#endif
#ifndef PIMORONI_TRACKBALL_DEBOUNCE_CYCLES
# define PIMORONI_TRACKBALL_DEBOUNCE_CYCLES 20
#endif
#ifndef PIMORONI_TRACKBALL_ERROR_COUNT
# define PIMORONI_TRACKBALL_ERROR_COUNT 10
#endif
#define TRACKBALL_TIMEOUT 100
#define TRACKBALL_REG_LED_RED 0x00
#define TRACKBALL_REG_LED_GRN 0x01
#define TRACKBALL_REG_LED_BLU 0x02
#define TRACKBALL_REG_LED_WHT 0x03
#define TRACKBALL_REG_LEFT 0x04
#define TRACKBALL_REG_RIGHT 0x05
#define TRACKBALL_REG_UP 0x06
#define TRACKBALL_REG_DOWN 0x07
static pimoroni_data current_pimoroni_data;
static report_mouse_t mouse_report;
static bool scrolling = false;
static int16_t x_offset = 0;
static int16_t y_offset = 0;
static int16_t h_offset = 0;
static int16_t v_offset = 0;
static uint16_t precision = 128; static uint16_t precision = 128;
static uint8_t error_count = 0;
float trackball_get_precision(void) { return ((float)precision / 128); } float pimoroni_trackball_get_precision(void) { return ((float)precision / 128); }
void trackball_set_precision(float floatprecision) { precision = (floatprecision * 128); } void pimoroni_trackball_set_precision(float floatprecision) { precision = (floatprecision * 128); }
bool trackball_is_scrolling(void) { return scrolling; }
void trackball_set_scrolling(bool scroll) { scrolling = scroll; }
void trackball_set_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { void pimoroni_trackball_set_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
uint8_t data[4] = {r, g, b, w}; uint8_t data[4] = {r, g, b, w};
__attribute__((unused)) i2c_status_t status = i2c_writeReg(PIMORONI_TRACKBALL_ADDRESS << 1, TRACKBALL_REG_LED_RED, data, sizeof(data), TRACKBALL_TIMEOUT); __attribute__((unused)) i2c_status_t status = i2c_writeReg(PIMORONI_TRACKBALL_ADDRESS << 1, PIMORONI_TRACKBALL_REG_LED_RED, data, sizeof(data), PIMORONI_TRACKBALL_TIMEOUT);
#ifdef TRACKBALL_DEBUG
dprintf("Trackball RGBW i2c_status_t: %d\n", status); #ifdef CONSOLE_ENABLE
if (debug_mouse) dprintf("Trackball RGBW i2c_status_t: %d\n", status);
#endif #endif
} }
i2c_status_t read_pimoroni_trackball(pimoroni_data* data) { i2c_status_t read_pimoroni_trackball(pimoroni_data_t* data) {
i2c_status_t status = i2c_readReg(PIMORONI_TRACKBALL_ADDRESS << 1, TRACKBALL_REG_LEFT, (uint8_t*)data, sizeof(*data), TRACKBALL_TIMEOUT); i2c_status_t status = i2c_readReg(PIMORONI_TRACKBALL_ADDRESS << 1, PIMORONI_TRACKBALL_REG_LEFT, (uint8_t*)data, sizeof(*data), PIMORONI_TRACKBALL_TIMEOUT);
#ifdef TRACKBALL_DEBUG #ifdef CONSOLE_ENABLE
dprintf("Trackball READ i2c_status_t: %d\nLeft: %d\nRight: %d\nUp: %d\nDown: %d\nSwtich: %d\n", status, data->left, data->right, data->up, data->down, data->click); if (debug_mouse) {
static uint16_t d_timer;
if (timer_elapsed(d_timer) > PIMORONI_TRACKBALL_DEBUG_INTERVAL) {
dprintf("Trackball READ i2c_status_t: %d L: %d R: %d Up: %d D: %d SW: %d\n", status, data->left, data->right, data->up, data->down, data->click);
d_timer = timer_read();
}
}
#endif #endif
return status; return status;
} }
__attribute__((weak)) void pointing_device_init(void) { __attribute__((weak)) void pimironi_trackball_device_init(void) {
i2c_init(); i2c_init();
trackball_set_rgbw(0x00, 0x00, 0x00, 0x00); pimoroni_trackball_set_rgbw(0x00, 0x00, 0x00, 0x00);
} }
int16_t trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale) { int16_t pimoroni_trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale) {
uint8_t offset = 0; uint8_t offset = 0;
bool isnegative = false; bool isnegative = false;
if (negative_dir > positive_dir) { if (negative_dir > positive_dir) {
@ -96,7 +78,7 @@ int16_t trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_
return isnegative ? -(int16_t)(magnitude) : (int16_t)(magnitude); return isnegative ? -(int16_t)(magnitude) : (int16_t)(magnitude);
} }
void trackball_adapt_values(int8_t* mouse, int16_t* offset) { void pimoroni_trackball_adapt_values(int8_t* mouse, int16_t* offset) {
if (*offset > 127) { if (*offset > 127) {
*mouse = 127; *mouse = 127;
*offset -= 127; *offset -= 127;
@ -108,94 +90,3 @@ void trackball_adapt_values(int8_t* mouse, int16_t* offset) {
*offset = 0; *offset = 0;
} }
} }
__attribute__((weak)) void trackball_click(bool pressed, report_mouse_t* mouse) {
#ifdef PIMORONI_TRACKBALL_CLICK
if (pressed) {
mouse->buttons |= MOUSE_BTN1;
} else {
mouse->buttons &= ~MOUSE_BTN1;
}
#endif
}
__attribute__((weak)) bool pointing_device_task_user(pimoroni_data* trackball_data) { return true; };
__attribute__((weak)) void pointing_device_task() {
static fast_timer_t throttle = 0;
static uint16_t debounce = 0;
if (error_count < PIMORONI_TRACKBALL_ERROR_COUNT && timer_elapsed_fast(throttle) >= PIMORONI_TRACKBALL_INTERVAL_MS) {
i2c_status_t status = read_pimoroni_trackball(&current_pimoroni_data);
if (status == I2C_STATUS_SUCCESS) {
error_count = 0;
if (pointing_device_task_user(&current_pimoroni_data)) {
mouse_report = pointing_device_get_report();
if (!(current_pimoroni_data.click & 128)) {
trackball_click(false, &mouse_report);
if (!debounce) {
if (scrolling) {
#ifdef PIMORONI_TRACKBALL_INVERT_X
h_offset += trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_SCROLL_SCALE);
#else
h_offset -= trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_SCROLL_SCALE);
#endif
#ifdef PIMORONI_TRACKBALL_INVERT_Y
v_offset += trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_SCROLL_SCALE);
#else
v_offset -= trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_SCROLL_SCALE);
#endif
} else {
#ifdef PIMORONI_TRACKBALL_INVERT_X
x_offset -= trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_MOUSE_SCALE);
#else
x_offset += trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_MOUSE_SCALE);
#endif
#ifdef PIMORONI_TRACKBALL_INVERT_Y
y_offset -= trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_MOUSE_SCALE);
#else
y_offset += trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_MOUSE_SCALE);
#endif
}
if (scrolling) {
#ifndef PIMORONI_TRACKBALL_ROTATE
trackball_adapt_values(&mouse_report.h, &h_offset);
trackball_adapt_values(&mouse_report.v, &v_offset);
#else
trackball_adapt_values(&mouse_report.h, &v_offset);
trackball_adapt_values(&mouse_report.v, &h_offset);
#endif
mouse_report.x = 0;
mouse_report.y = 0;
} else {
#ifndef PIMORONI_TRACKBALL_ROTATE
trackball_adapt_values(&mouse_report.x, &x_offset);
trackball_adapt_values(&mouse_report.y, &y_offset);
#else
trackball_adapt_values(&mouse_report.x, &y_offset);
trackball_adapt_values(&mouse_report.y, &x_offset);
#endif
mouse_report.h = 0;
mouse_report.v = 0;
}
} else {
debounce--;
}
} else {
trackball_click(true, &mouse_report);
debounce = PIMORONI_TRACKBALL_DEBOUNCE_CYCLES;
}
}
} else {
error_count++;
}
pointing_device_set_report(mouse_report);
pointing_device_send();
throttle = timer_read_fast();
}
}

View file

@ -16,22 +16,46 @@
*/ */
#pragma once #pragma once
#include "quantum.h" #include <stdint.h>
#include "pointing_device.h" #include "report.h"
#include "i2c_master.h"
typedef struct pimoroni_data { #ifndef PIMORONI_TRACKBALL_ADDRESS
# define PIMORONI_TRACKBALL_ADDRESS 0x0A
#endif
#ifndef PIMORONI_TRACKBALL_INTERVAL_MS
# define PIMORONI_TRACKBALL_INTERVAL_MS 8
#endif
#ifndef PIMORONI_TRACKBALL_SCALE
# define PIMORONI_TRACKBALL_SCALE 5
#endif
#ifndef PIMORONI_TRACKBALL_DEBOUNCE_CYCLES
# define PIMORONI_TRACKBALL_DEBOUNCE_CYCLES 20
#endif
#ifndef PIMORONI_TRACKBALL_ERROR_COUNT
# define PIMORONI_TRACKBALL_ERROR_COUNT 10
#endif
#ifndef PIMORONI_TRACKBALL_TIMEOUT
# define PIMORONI_TRACKBALL_TIMEOUT 100
#endif
#ifndef PIMORONI_TRACKBALL_DEBUG_INTERVAL
# define PIMORONI_TRACKBALL_DEBUG_INTERVAL 100
#endif
typedef struct {
uint8_t left; uint8_t left;
uint8_t right; uint8_t right;
uint8_t up; uint8_t up;
uint8_t down; uint8_t down;
uint8_t click; uint8_t click;
} pimoroni_data; } pimoroni_data_t;
void trackball_set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white); void pimironi_trackball_device_init(void);
void trackball_click(bool pressed, report_mouse_t* mouse); void pimoroni_trackball_set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white);
int16_t trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale); int16_t pimoroni_trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale);
void trackball_adapt_values(int8_t* mouse, int16_t* offset); void pimoroni_trackball_adapt_values(int8_t* mouse, int16_t* offset);
float trackball_get_precision(void); float pimoroni_trackball_get_precision(void);
void trackball_set_precision(float precision); void pimoroni_trackball_set_precision(float precision);
bool trackball_is_scrolling(void); i2c_status_t read_pimoroni_trackball(pimoroni_data_t* data);
void trackball_set_scrolling(bool scroll);

View file

@ -20,9 +20,10 @@
#include "wait.h" #include "wait.h"
#include "debug.h" #include "debug.h"
#include "print.h" #include "print.h"
#include "pmw3360_firmware.h" #include PMW3360_FIRMWARE_H
// Registers // Registers
// clang-format off
#define REG_Product_ID 0x00 #define REG_Product_ID 0x00
#define REG_Revision_ID 0x01 #define REG_Revision_ID 0x01
#define REG_Motion 0x02 #define REG_Motion 0x02
@ -72,10 +73,18 @@
#define REG_Lift_Config 0x63 #define REG_Lift_Config 0x63
#define REG_Raw_Data_Burst 0x64 #define REG_Raw_Data_Burst 0x64
#define REG_LiftCutoff_Tune2 0x65 #define REG_LiftCutoff_Tune2 0x65
// clang-format on
#ifndef MAX_CPI
# define MAX_CPI 0x77 // limits to 0--119, should be max cpi/100
#endif
bool _inBurst = false; bool _inBurst = false;
#ifdef CONSOLE_ENABLE
void print_byte(uint8_t byte) { dprintf("%c%c%c%c%c%c%c%c|", (byte & 0x80 ? '1' : '0'), (byte & 0x40 ? '1' : '0'), (byte & 0x20 ? '1' : '0'), (byte & 0x10 ? '1' : '0'), (byte & 0x08 ? '1' : '0'), (byte & 0x04 ? '1' : '0'), (byte & 0x02 ? '1' : '0'), (byte & 0x01 ? '1' : '0')); } void print_byte(uint8_t byte) { dprintf("%c%c%c%c%c%c%c%c|", (byte & 0x80 ? '1' : '0'), (byte & 0x40 ? '1' : '0'), (byte & 0x20 ? '1' : '0'), (byte & 0x10 ? '1' : '0'), (byte & 0x08 ? '1' : '0'), (byte & 0x04 ? '1' : '0'), (byte & 0x02 ? '1' : '0'), (byte & 0x01 ? '1' : '0')); }
#endif
#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
bool spi_start_adv(void) { bool spi_start_adv(void) {
bool status = spi_start(PMW3360_CS_PIN, PMW3360_SPI_LSBFIRST, PMW3360_SPI_MODE, PMW3360_SPI_DIVISOR); bool status = spi_start(PMW3360_CS_PIN, PMW3360_SPI_LSBFIRST, PMW3360_SPI_MODE, PMW3360_SPI_DIVISOR);
@ -124,20 +133,20 @@ uint8_t spi_read_adv(uint8_t reg_addr) {
return data; return data;
} }
void pmw_set_cpi(uint16_t cpi) { void pmw3360_set_cpi(uint16_t cpi) {
uint8_t cpival = constrain((cpi / 100) - 1, 0, 0x77); // limits to 0--119 uint8_t cpival = constrain((cpi / 100) - 1, 0, MAX_CPI);
spi_start_adv(); spi_start_adv();
spi_write_adv(REG_Config1, cpival); spi_write_adv(REG_Config1, cpival);
spi_stop(); spi_stop();
} }
uint16_t pmw_get_cpi(void) { uint16_t pmw3360_get_cpi(void) {
uint8_t cpival = spi_read_adv(REG_Config1); uint8_t cpival = spi_read_adv(REG_Config1);
return (uint16_t)(cpival & 0xFF) * 100; return (uint16_t)(cpival & 0xFF) * 100;
} }
bool pmw_spi_init(void) { bool pmw3360_init(void) {
setPinOutput(PMW3360_CS_PIN); setPinOutput(PMW3360_CS_PIN);
spi_init(); spi_init();
@ -164,12 +173,12 @@ bool pmw_spi_init(void) {
spi_read_adv(REG_Delta_Y_L); spi_read_adv(REG_Delta_Y_L);
spi_read_adv(REG_Delta_Y_H); spi_read_adv(REG_Delta_Y_H);
pmw_upload_firmware(); pmw3360_upload_firmware();
spi_stop_adv(); spi_stop_adv();
wait_ms(10); wait_ms(10);
pmw_set_cpi(PMW3360_CPI); pmw3360_set_cpi(PMW3360_CPI);
wait_ms(1); wait_ms(1);
@ -177,14 +186,14 @@ bool pmw_spi_init(void) {
spi_write_adv(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -30, 30)); spi_write_adv(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -30, 30));
bool init_success = pmw_check_signature(); bool init_success = pmw3360_check_signature();
writePinLow(PMW3360_CS_PIN); writePinLow(PMW3360_CS_PIN);
return init_success; return init_success;
} }
void pmw_upload_firmware(void) { void pmw3360_upload_firmware(void) {
spi_write_adv(REG_SROM_Enable, 0x1d); spi_write_adv(REG_SROM_Enable, 0x1d);
wait_ms(10); wait_ms(10);
@ -211,16 +220,18 @@ void pmw_upload_firmware(void) {
wait_ms(10); wait_ms(10);
} }
bool pmw_check_signature(void) { bool pmw3360_check_signature(void) {
uint8_t pid = spi_read_adv(REG_Product_ID); uint8_t pid = spi_read_adv(REG_Product_ID);
uint8_t iv_pid = spi_read_adv(REG_Inverse_Product_ID); uint8_t iv_pid = spi_read_adv(REG_Inverse_Product_ID);
uint8_t SROM_ver = spi_read_adv(REG_SROM_ID); uint8_t SROM_ver = spi_read_adv(REG_SROM_ID);
return (pid == 0x42 && iv_pid == 0xBD && SROM_ver == 0x04); // signature for SROM 0x04 return (pid == firmware_signature[0] && iv_pid == firmware_signature[1] && SROM_ver == firmware_signature[2]); // signature for SROM 0x04
} }
report_pmw_t pmw_read_burst(void) { report_pmw3360_t pmw3360_read_burst(void) {
if (!_inBurst) { if (!_inBurst) {
#ifdef CONSOLE_ENABLE
dprintf("burst on"); dprintf("burst on");
#endif
spi_write_adv(REG_Motion_Burst, 0x00); spi_write_adv(REG_Motion_Burst, 0x00);
_inBurst = true; _inBurst = true;
} }
@ -229,12 +240,7 @@ report_pmw_t pmw_read_burst(void) {
spi_write(REG_Motion_Burst); spi_write(REG_Motion_Burst);
wait_us(35); // waits for tSRAD wait_us(35); // waits for tSRAD
report_pmw_t data; report_pmw3360_t data = {0};
data.motion = 0;
data.dx = 0;
data.mdx = 0;
data.dy = 0;
data.mdx = 0;
data.motion = spi_read(); data.motion = spi_read();
spi_write(0x00); // skip Observation spi_write(0x00); // skip Observation
@ -245,6 +251,7 @@ report_pmw_t pmw_read_burst(void) {
spi_stop(); spi_stop();
#ifdef CONSOLE_ENABLE
if (debug_mouse) { if (debug_mouse) {
print_byte(data.motion); print_byte(data.motion);
print_byte(data.dx); print_byte(data.dx);
@ -253,6 +260,7 @@ report_pmw_t pmw_read_burst(void) {
print_byte(data.mdy); print_byte(data.mdy);
dprintf("\n"); dprintf("\n");
} }
#endif
data.isMotion = (data.motion & 0x80) != 0; data.isMotion = (data.motion & 0x80) != 0;
data.isOnSurface = (data.motion & 0x08) == 0; data.isOnSurface = (data.motion & 0x08) == 0;

View file

@ -18,6 +18,8 @@
#pragma once #pragma once
#include <stdint.h>
#include "report.h"
#include "spi_master.h" #include "spi_master.h"
#ifndef PMW3360_CPI #ifndef PMW3360_CPI
@ -25,7 +27,7 @@
#endif #endif
#ifndef PMW3360_CLOCK_SPEED #ifndef PMW3360_CLOCK_SPEED
# define PMW3360_CLOCK_SPEED 70000000 # define PMW3360_CLOCK_SPEED 2000000
#endif #endif
#ifndef PMW3360_SPI_LSBFIRST #ifndef PMW3360_SPI_LSBFIRST
@ -52,6 +54,17 @@
# error "No chip select pin defined -- missing PMW3360_CS_PIN" # error "No chip select pin defined -- missing PMW3360_CS_PIN"
#endif #endif
/*
The pmw33660 and pmw3389 use the same registers and timing and such.
The only differences between the two is the firmware used, and the
range for the DPI. So add a semi-secret hack to allow use of the
pmw3389's firmware blob. Also, can set the max cpi range too.
This should work for the 3390 and 3391 too, in theory.
*/
#ifndef PMW3360_FIRMWARE_H
# define PMW3360_FIRMWARE_H "pmw3360_firmware.h"
#endif
#ifdef CONSOLE_ENABLE #ifdef CONSOLE_ENABLE
void print_byte(uint8_t byte); void print_byte(uint8_t byte);
#endif #endif
@ -64,19 +77,18 @@ typedef struct {
int8_t mdx; int8_t mdx;
int16_t dy; // displacement on y directions. int16_t dy; // displacement on y directions.
int8_t mdy; int8_t mdy;
} report_pmw_t; } report_pmw3360_t;
bool spi_start_adv(void); bool spi_start_adv(void);
void spi_stop_adv(void); void spi_stop_adv(void);
spi_status_t spi_write_adv(uint8_t reg_addr, uint8_t data); spi_status_t spi_write_adv(uint8_t reg_addr, uint8_t data);
uint8_t spi_read_adv(uint8_t reg_addr); uint8_t spi_read_adv(uint8_t reg_addr);
bool pmw_spi_init(void); bool pmw3360_init(void);
void pmw_set_cpi(uint16_t cpi); void pmw3360_set_cpi(uint16_t cpi);
uint16_t pmw_get_cpi(void); uint16_t pmw3360_get_cpi(void);
void pmw_upload_firmware(void); void pmw3360_upload_firmware(void);
bool pmw_check_signature(void); bool pmw3360_check_signature(void);
report_pmw_t pmw_read_burst(void); report_pmw3360_t pmw3360_read_burst(void);
#define degToRad(angleInDegrees) ((angleInDegrees)*M_PI / 180.0) #define degToRad(angleInDegrees) ((angleInDegrees)*M_PI / 180.0)
#define radToDeg(angleInRadians) ((angleInRadians)*180.0 / M_PI) #define radToDeg(angleInRadians) ((angleInRadians)*180.0 / M_PI)
#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))

View file

@ -20,8 +20,13 @@
#include "progmem.h" #include "progmem.h"
// PID, Inverse PID, SROM version
const uint8_t firmware_signature[] PROGMEM = {0x42, 0xBD, 0x04};
#define FIRMWARE_LENGTH 4094 #define FIRMWARE_LENGTH 4094
// Firmware Blob foor PMW3360
// clang-format off // clang-format off
const uint8_t firmware_data[FIRMWARE_LENGTH] PROGMEM = { const uint8_t firmware_data[FIRMWARE_LENGTH] PROGMEM = {
0x01, 0x04, 0x8E, 0x96, 0x6E, 0x77, 0x3E, 0xFE, 0x7E, 0x5F, 0x1D, 0xB8, 0xF2, 0x66, 0x4E, 0xFF, 0x01, 0x04, 0x8E, 0x96, 0x6E, 0x77, 0x3E, 0xFE, 0x7E, 0x5F, 0x1D, 0xB8, 0xF2, 0x66, 0x4E, 0xFF,

View file

@ -0,0 +1,303 @@
/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* Copyright 2019 Sunjun Kim
* Copyright 2020 Ploopy Corporation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// PID, Inverse PID, SROM version
const uint8_t firmware_signature[] PROGMEM = {0x42, 0xBD, 0x04};
// clang-format off
// Firmware Blob foor PMW3389
const uint16_t firmware_length = 4094;
// clang-format off
const uint8_t firmware_data[] PROGMEM = { // SROM 0x04
0x01, 0xe8, 0xba, 0x26, 0x0b, 0xb2, 0xbe, 0xfe, 0x7e, 0x5f, 0x3c, 0xdb, 0x15, 0xa8, 0xb3,
0xe4, 0x2b, 0xb5, 0xe8, 0x53, 0x07, 0x6d, 0x3b, 0xd1, 0x20, 0xc2, 0x06, 0x6f, 0x3d, 0xd9,
0x11, 0xa0, 0xc2, 0xe7, 0x2d, 0xb9, 0xd1, 0x20, 0xa3, 0xa5, 0xc8, 0xf3, 0x64, 0x4a, 0xf7,
0x4d, 0x18, 0x93, 0xa4, 0xca, 0xf7, 0x6c, 0x5a, 0x36, 0xee, 0x5e, 0x3e, 0xfe, 0x7e, 0x7e,
0x5f, 0x1d, 0x99, 0xb0, 0xc3, 0xe5, 0x29, 0xd3, 0x03, 0x65, 0x48, 0x12, 0x87, 0x6d, 0x58,
0x32, 0xe6, 0x2f, 0xdc, 0x3a, 0xf2, 0x4f, 0xfd, 0x59, 0x11, 0x81, 0x61, 0x21, 0xc0, 0x02,
0x86, 0x8e, 0x7f, 0x5d, 0x38, 0xf2, 0x47, 0x0c, 0x7b, 0x55, 0x28, 0xb3, 0xe4, 0x4a, 0x16,
0xab, 0xbf, 0xdd, 0x38, 0xf2, 0x66, 0x4e, 0xff, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xa5, 0xc8,
0x12, 0xa6, 0xaf, 0xdc, 0x3a, 0xd1, 0x41, 0x60, 0x75, 0x58, 0x24, 0x92, 0xd4, 0x72, 0x6c,
0xe0, 0x2f, 0xfd, 0x23, 0x8d, 0x1c, 0x5b, 0xb2, 0x97, 0x36, 0x3d, 0x0b, 0xa2, 0x49, 0xb1,
0x58, 0xf2, 0x1f, 0xc0, 0xcb, 0xf8, 0x41, 0x4f, 0xcd, 0x1e, 0x6b, 0x39, 0xa7, 0x2b, 0xe9,
0x30, 0x16, 0x83, 0xd2, 0x0e, 0x47, 0x8f, 0xe3, 0xb1, 0xdf, 0xa2, 0x15, 0xdb, 0x5d, 0x30,
0xc5, 0x1a, 0xab, 0x31, 0x99, 0xf3, 0xfa, 0xb2, 0x86, 0x69, 0xad, 0x7a, 0xe8, 0xa7, 0x18,
0x6a, 0xcc, 0xc8, 0x65, 0x23, 0x87, 0xa8, 0x5f, 0xf5, 0x21, 0x59, 0x75, 0x09, 0x71, 0x45,
0x55, 0x25, 0x4b, 0xda, 0xa1, 0xc3, 0xf7, 0x41, 0xab, 0x59, 0xd9, 0x74, 0x12, 0x55, 0x5f,
0xbc, 0xaf, 0xd9, 0xfd, 0xb0, 0x1e, 0xa3, 0x0f, 0xff, 0xde, 0x11, 0x16, 0x6a, 0xae, 0x0e,
0xe1, 0x5d, 0x3c, 0x10, 0x43, 0x9a, 0xa1, 0x0b, 0x24, 0x8f, 0x0d, 0x7f, 0x0b, 0x5e, 0x4c,
0x42, 0xa4, 0x84, 0x2c, 0x40, 0xd0, 0x55, 0x39, 0xe6, 0x4b, 0xf8, 0x9b, 0x2f, 0xdc, 0x28,
0xff, 0xfa, 0xb5, 0x85, 0x19, 0xe5, 0x28, 0xa1, 0x77, 0xaa, 0x73, 0xf3, 0x03, 0xc7, 0x62,
0xa6, 0x91, 0x18, 0xc9, 0xb0, 0xcd, 0x05, 0xdc, 0xca, 0x81, 0x26, 0x1a, 0x47, 0x40, 0xda,
0x36, 0x7d, 0x6a, 0x53, 0xc8, 0x5a, 0x77, 0x5d, 0x19, 0xa4, 0x1b, 0x23, 0x83, 0xd0, 0xb2,
0xaa, 0x0e, 0xbf, 0x77, 0x4e, 0x3a, 0x3b, 0x59, 0x00, 0x31, 0x0d, 0x02, 0x1b, 0x88, 0x7a,
0xd4, 0xbd, 0x9d, 0xcc, 0x58, 0x04, 0x69, 0xf6, 0x3b, 0xca, 0x42, 0xe2, 0xfd, 0xc3, 0x3d,
0x39, 0xc5, 0xd0, 0x71, 0xe4, 0xc8, 0xb7, 0x3e, 0x3f, 0xc8, 0xe9, 0xca, 0xc9, 0x3f, 0x04,
0x4e, 0x1b, 0x79, 0xca, 0xa5, 0x61, 0xc2, 0xed, 0x1d, 0xa6, 0xda, 0x5a, 0xe9, 0x7f, 0x65,
0x8c, 0xbe, 0x12, 0x6e, 0xa4, 0x5b, 0x33, 0x2f, 0x84, 0x28, 0x9c, 0x1c, 0x88, 0x2d, 0xff,
0x07, 0xbf, 0xa6, 0xd7, 0x5a, 0x88, 0x86, 0xb0, 0x3f, 0xf6, 0x31, 0x5b, 0x11, 0x6d, 0xf5,
0x58, 0xeb, 0x58, 0x02, 0x9e, 0xb5, 0x9a, 0xb1, 0xff, 0x25, 0x9d, 0x8b, 0x4f, 0xb6, 0x0a,
0xf9, 0xea, 0x3e, 0x3f, 0x21, 0x09, 0x65, 0x21, 0x22, 0xfe, 0x3d, 0x4e, 0x11, 0x5b, 0x9e,
0x5a, 0x59, 0x8b, 0xdd, 0xd8, 0xce, 0xd6, 0xd9, 0x59, 0xd2, 0x1e, 0xfd, 0xef, 0x0d, 0x1b,
0xd9, 0x61, 0x7f, 0xd7, 0x2d, 0xad, 0x62, 0x09, 0xe5, 0x22, 0x63, 0xea, 0xc7, 0x31, 0xd9,
0xa1, 0x38, 0x80, 0x5c, 0xa7, 0x32, 0x82, 0xec, 0x1b, 0xa2, 0x49, 0x5a, 0x06, 0xd2, 0x7c,
0xc9, 0x96, 0x57, 0xbb, 0x17, 0x75, 0xfc, 0x7a, 0x8f, 0x0d, 0x77, 0xb5, 0x7a, 0x8e, 0x3e,
0xf4, 0xba, 0x2f, 0x69, 0x13, 0x26, 0xd6, 0xd9, 0x21, 0x60, 0x2f, 0x21, 0x3e, 0x87, 0xee,
0xfd, 0x87, 0x16, 0x0d, 0xc8, 0x08, 0x00, 0x25, 0x71, 0xac, 0x2c, 0x03, 0x2a, 0x37, 0x2d,
0xb3, 0x34, 0x09, 0x91, 0xe3, 0x06, 0x2c, 0x38, 0x37, 0x95, 0x3b, 0x17, 0x7a, 0xaf, 0xac,
0x99, 0x55, 0xab, 0x41, 0x39, 0x5f, 0x8e, 0xa6, 0x43, 0x80, 0x03, 0x88, 0x6f, 0x7d, 0xbd,
0x5a, 0xb4, 0x2b, 0x32, 0x23, 0x5a, 0xa9, 0x31, 0x32, 0x39, 0x4c, 0x5b, 0xf4, 0x6b, 0xaf,
0x66, 0x6f, 0x3c, 0x8e, 0x2d, 0x82, 0x97, 0x9f, 0x4a, 0x01, 0xdc, 0x99, 0x98, 0x00, 0xec,
0x38, 0x7a, 0x79, 0x70, 0xa6, 0x85, 0xd6, 0x21, 0x63, 0x0d, 0x45, 0x9a, 0x2e, 0x5e, 0xa7,
0xb1, 0xea, 0x66, 0x6a, 0xbc, 0x62, 0x2d, 0x7b, 0x7d, 0x85, 0xea, 0x95, 0x2f, 0xc0, 0xe8,
0x6f, 0x35, 0xa0, 0x3a, 0x02, 0x25, 0xbc, 0xb2, 0x5f, 0x5c, 0x43, 0x96, 0xcc, 0x26, 0xd2,
0x16, 0xb4, 0x96, 0x73, 0xd7, 0x13, 0xc7, 0xae, 0x53, 0x15, 0x31, 0x89, 0x68, 0x66, 0x6d,
0x2c, 0x92, 0x1f, 0xcc, 0x5b, 0xa7, 0x8f, 0x5d, 0xbb, 0xc9, 0xdb, 0xe8, 0x3b, 0x9d, 0x61,
0x74, 0x8b, 0x05, 0xa1, 0x58, 0x52, 0x68, 0xee, 0x3d, 0x39, 0x79, 0xa0, 0x9b, 0xdd, 0xe1,
0x55, 0xc9, 0x60, 0xeb, 0xad, 0xb8, 0x5b, 0xc2, 0x5a, 0xb5, 0x2c, 0x18, 0x55, 0xa9, 0x50,
0xc3, 0xf6, 0x72, 0x5f, 0xcc, 0xe2, 0xf4, 0x55, 0xb5, 0xd6, 0xb5, 0x4a, 0x99, 0xa5, 0x28,
0x74, 0x97, 0x18, 0xe8, 0xc0, 0x84, 0x89, 0x50, 0x03, 0x86, 0x4d, 0x1a, 0xb7, 0x09, 0x90,
0xa2, 0x01, 0x04, 0xbb, 0x73, 0x62, 0xcb, 0x97, 0x22, 0x70, 0x5d, 0x52, 0x41, 0x8e, 0xd9,
0x90, 0x15, 0xaa, 0xab, 0x0a, 0x31, 0x65, 0xb4, 0xda, 0xd0, 0xee, 0x24, 0xc9, 0x41, 0x91,
0x1e, 0xbc, 0x46, 0x70, 0x40, 0x9d, 0xda, 0x0e, 0x2a, 0xe4, 0xb2, 0x4c, 0x9f, 0xf2, 0xfc,
0xf3, 0x84, 0x17, 0x44, 0x1e, 0xd7, 0xca, 0x23, 0x1f, 0x3f, 0x5a, 0x22, 0x3d, 0xaf, 0x9b,
0x2d, 0xfc, 0x41, 0xad, 0x26, 0xb4, 0x45, 0x67, 0x0b, 0x80, 0x0e, 0xf9, 0x61, 0x37, 0xec,
0x3b, 0xf4, 0x4b, 0x14, 0xdf, 0x5a, 0x0c, 0x3a, 0x50, 0x0b, 0x14, 0x0c, 0x72, 0xae, 0xc6,
0xc5, 0xec, 0x35, 0x53, 0x2d, 0x59, 0xed, 0x91, 0x74, 0xe2, 0xc4, 0xc8, 0xf2, 0x25, 0x6b,
0x97, 0x6f, 0xc9, 0x76, 0xce, 0xa9, 0xb1, 0x99, 0x8f, 0x5a, 0x92, 0x3b, 0xc4, 0x8d, 0x54,
0x50, 0x40, 0x72, 0xd6, 0x90, 0x83, 0xfc, 0xe5, 0x49, 0x8b, 0x17, 0xf5, 0xfd, 0x6b, 0x8d,
0x32, 0x02, 0xe9, 0x0a, 0xfe, 0xbf, 0x00, 0x6b, 0xa3, 0xad, 0x5f, 0x09, 0x4b, 0x97, 0x2b,
0x00, 0x58, 0x65, 0x2e, 0x07, 0x49, 0x0a, 0x3b, 0x6b, 0x2e, 0x50, 0x6c, 0x1d, 0xac, 0xb7,
0x6a, 0x26, 0xd8, 0x13, 0xa4, 0xca, 0x16, 0xae, 0xab, 0x93, 0xb9, 0x1c, 0x1c, 0xb4, 0x47,
0x6a, 0x38, 0x36, 0x17, 0x27, 0xc9, 0x7f, 0xc7, 0x64, 0xcb, 0x89, 0x58, 0xc5, 0x61, 0xc2,
0xc6, 0xea, 0x15, 0x0b, 0x34, 0x0c, 0x5d, 0x61, 0x76, 0x6e, 0x2b, 0x62, 0x40, 0x92, 0xa3,
0x6c, 0xef, 0xf4, 0xe4, 0xc3, 0xa1, 0xa8, 0xf5, 0x94, 0x79, 0x0d, 0xd1, 0x3d, 0xcb, 0x3d,
0x40, 0xb6, 0xd0, 0xf0, 0x10, 0x54, 0xd8, 0x47, 0x25, 0x51, 0xc5, 0x41, 0x79, 0x00, 0xe5,
0xa0, 0x72, 0xde, 0xbb, 0x3b, 0x62, 0x17, 0xf6, 0xbc, 0x5d, 0x00, 0x76, 0x2e, 0xa7, 0x3b,
0xb6, 0xf1, 0x98, 0x72, 0x59, 0x2a, 0x73, 0xb0, 0x21, 0xd6, 0x49, 0xe0, 0xc0, 0xd5, 0xeb,
0x02, 0x7d, 0x4b, 0x41, 0x28, 0x70, 0x2d, 0xec, 0x2b, 0x71, 0x1f, 0x0b, 0xb9, 0x71, 0x63,
0x06, 0xe6, 0xbc, 0x60, 0xbb, 0xf4, 0x9a, 0x62, 0x43, 0x09, 0x18, 0x4e, 0x93, 0x06, 0x4d,
0x76, 0xfa, 0x7f, 0xbd, 0x02, 0xe4, 0x50, 0x91, 0x12, 0xe5, 0x86, 0xff, 0x64, 0x1e, 0xaf,
0x7e, 0xb3, 0xb2, 0xde, 0x89, 0xc1, 0xa2, 0x6f, 0x40, 0x7b, 0x41, 0x51, 0x63, 0xea, 0x25,
0xd1, 0x97, 0x57, 0x92, 0xa8, 0x45, 0xa1, 0xa5, 0x45, 0x21, 0x43, 0x7f, 0x83, 0x15, 0x29,
0xd0, 0x30, 0x53, 0x32, 0xb4, 0x5a, 0x17, 0x96, 0xbc, 0xc2, 0x68, 0xa9, 0xb7, 0xaf, 0xac,
0xdf, 0xf1, 0xe3, 0x89, 0xba, 0x24, 0x79, 0x54, 0xc6, 0x14, 0x07, 0x1c, 0x1e, 0x0d, 0x3a,
0x6b, 0xe5, 0x3d, 0x4e, 0x10, 0x60, 0x96, 0xec, 0x6c, 0xda, 0x47, 0xae, 0x03, 0x25, 0x39,
0x1d, 0x74, 0xc8, 0xac, 0x6a, 0xf2, 0x6b, 0x05, 0x2a, 0x9a, 0xe7, 0xe8, 0x92, 0xd6, 0xc2,
0x6d, 0xfa, 0xe8, 0xa7, 0x9d, 0x5f, 0x48, 0xc9, 0x75, 0xf1, 0x66, 0x6a, 0xdb, 0x5d, 0x9a,
0xcd, 0x27, 0xdd, 0xb9, 0x24, 0x04, 0x9c, 0x18, 0xc2, 0x6d, 0x0c, 0x91, 0x34, 0x48, 0x42,
0x6f, 0xe9, 0x59, 0x70, 0xc4, 0x7e, 0x81, 0x0e, 0x32, 0x0a, 0x93, 0x48, 0xb0, 0xc0, 0x15,
0x9e, 0x05, 0xac, 0x36, 0x16, 0xcb, 0x59, 0x65, 0xa0, 0x83, 0xdf, 0x3e, 0xda, 0xfb, 0x1d,
0x1a, 0xdb, 0x65, 0xec, 0x9a, 0xc6, 0xc3, 0x8e, 0x3c, 0x45, 0xfd, 0xc8, 0xf5, 0x1c, 0x6a,
0x67, 0x0d, 0x8f, 0x99, 0x7d, 0x30, 0x21, 0x8c, 0xea, 0x22, 0x87, 0x65, 0xc9, 0xb2, 0x4c,
0xe4, 0x1b, 0x46, 0xba, 0x54, 0xbd, 0x7c, 0xca, 0xd5, 0x8f, 0x5b, 0xa5, 0x01, 0x04, 0xd8,
0x0a, 0x16, 0xbf, 0xb9, 0x50, 0x2e, 0x37, 0x2f, 0x64, 0xf3, 0x70, 0x11, 0x02, 0x05, 0x31,
0x9b, 0xa0, 0xb2, 0x01, 0x5e, 0x4f, 0x19, 0xc9, 0xd4, 0xea, 0xa1, 0x79, 0x54, 0x53, 0xa7,
0xde, 0x2f, 0x49, 0xd3, 0xd1, 0x63, 0xb5, 0x03, 0x15, 0x4e, 0xbf, 0x04, 0xb3, 0x26, 0x8b,
0x20, 0xb2, 0x45, 0xcf, 0xcd, 0x5b, 0x82, 0x32, 0x88, 0x61, 0xa7, 0xa8, 0xb2, 0xa0, 0x72,
0x96, 0xc0, 0xdb, 0x2b, 0xe2, 0x5f, 0xba, 0xe3, 0xf5, 0x8a, 0xde, 0xf1, 0x18, 0x01, 0x16,
0x40, 0xd9, 0x86, 0x12, 0x09, 0x18, 0x1b, 0x05, 0x0c, 0xb1, 0xb5, 0x47, 0xe2, 0x43, 0xab,
0xfe, 0x92, 0x63, 0x7e, 0x95, 0x2b, 0xf0, 0xaf, 0xe1, 0xf1, 0xc3, 0x4a, 0xff, 0x2b, 0x09,
0xbb, 0x4a, 0x0e, 0x9a, 0xc4, 0xd8, 0x64, 0x7d, 0x83, 0xa0, 0x4f, 0x44, 0xdb, 0xc4, 0xa8,
0x58, 0xef, 0xfc, 0x9e, 0x77, 0xf9, 0xa6, 0x8f, 0x58, 0x8b, 0x12, 0xf4, 0xe9, 0x81, 0x12,
0x47, 0x51, 0x41, 0x83, 0xef, 0xf6, 0x73, 0xbc, 0x8e, 0x0f, 0x4c, 0x8f, 0x4e, 0x69, 0x90,
0x77, 0x29, 0x5d, 0x92, 0xb0, 0x6d, 0x06, 0x67, 0x29, 0x60, 0xbd, 0x4b, 0x17, 0xc8, 0x89,
0x69, 0x28, 0x29, 0xd6, 0x78, 0xcb, 0x11, 0x4c, 0xba, 0x8b, 0x68, 0xae, 0x7e, 0x9f, 0xef,
0x95, 0xda, 0xe2, 0x9e, 0x7f, 0xe9, 0x55, 0xe5, 0xe1, 0xe2, 0xb7, 0xe6, 0x5f, 0xbb, 0x2c,
0xa2, 0xe6, 0xee, 0xc7, 0x0a, 0x60, 0xa9, 0xd1, 0x80, 0xdf, 0x7f, 0xd6, 0x97, 0xab, 0x1d,
0x22, 0x25, 0xfc, 0x79, 0x23, 0xe0, 0xae, 0xc5, 0xef, 0x16, 0xa4, 0xa1, 0x0f, 0x92, 0xa9,
0xc7, 0xe3, 0x3a, 0x55, 0xdf, 0x62, 0x49, 0xd9, 0xf5, 0x84, 0x49, 0xc5, 0x90, 0x34, 0xd3,
0xe1, 0xac, 0x99, 0x21, 0xb1, 0x02, 0x76, 0x4a, 0xfa, 0xd4, 0xbb, 0xa4, 0x9c, 0xa2, 0xe2,
0xcb, 0x3d, 0x3b, 0x14, 0x75, 0x60, 0xd1, 0x02, 0xb4, 0xa3, 0xb4, 0x72, 0x06, 0xf9, 0x19,
0x9c, 0xe2, 0xe4, 0xa7, 0x0f, 0x25, 0x88, 0xc6, 0x86, 0xd6, 0x8c, 0x74, 0x4e, 0x6e, 0xfc,
0xa8, 0x48, 0x9e, 0xa7, 0x9d, 0x1a, 0x4b, 0x37, 0x09, 0xc8, 0xb0, 0x10, 0xbe, 0x6f, 0xfe,
0xa3, 0xc4, 0x7a, 0xb5, 0x3d, 0xe8, 0x30, 0xf1, 0x0d, 0xa0, 0xb2, 0x44, 0xfc, 0x9b, 0x8c,
0xf8, 0x61, 0xed, 0x81, 0xd1, 0x62, 0x11, 0xb4, 0xe1, 0xd5, 0x39, 0x52, 0x89, 0xd3, 0xa8,
0x49, 0x31, 0xdf, 0xb6, 0xf9, 0x91, 0xf4, 0x1c, 0x9d, 0x09, 0x95, 0x40, 0x56, 0xe7, 0xe3,
0xcd, 0x5c, 0x92, 0xc1, 0x1d, 0x6b, 0xe9, 0x78, 0x6f, 0x8e, 0x94, 0x42, 0x66, 0xa2, 0xaa,
0xd3, 0xc8, 0x2e, 0xe3, 0xf6, 0x07, 0x72, 0x0b, 0x6b, 0x1e, 0x7b, 0xb9, 0x7c, 0xe0, 0xa0,
0xbc, 0xd9, 0x25, 0xdf, 0x87, 0xa8, 0x5f, 0x9c, 0xcc, 0xf0, 0xdb, 0x42, 0x8e, 0x07, 0x31,
0x13, 0x01, 0x66, 0x32, 0xd1, 0xb8, 0xd6, 0xe3, 0x5e, 0x12, 0x76, 0x61, 0xd3, 0x38, 0x89,
0xe6, 0x17, 0x6f, 0xa5, 0xf2, 0x71, 0x0e, 0xa5, 0xe2, 0x88, 0x30, 0xbb, 0xbe, 0x8a, 0xea,
0xc7, 0x62, 0xc4, 0xcf, 0xb8, 0xcd, 0x33, 0x8d, 0x3d, 0x3e, 0xb5, 0x60, 0x3a, 0x03, 0x92,
0xe4, 0x6d, 0x1b, 0xe0, 0xb4, 0x84, 0x08, 0x55, 0x88, 0xa7, 0x3a, 0xb9, 0x3d, 0x43, 0xc3,
0xc0, 0xfa, 0x07, 0x6a, 0xca, 0x94, 0xad, 0x99, 0x55, 0xf1, 0xf1, 0xc0, 0x23, 0x87, 0x1d,
0x3d, 0x1c, 0xd1, 0x66, 0xa0, 0x57, 0x10, 0x52, 0xa2, 0x7f, 0xbe, 0xf9, 0x88, 0xb6, 0x02,
0xbf, 0x08, 0x23, 0xa9, 0x0c, 0x63, 0x17, 0x2a, 0xae, 0xf5, 0xf7, 0xb7, 0x21, 0x83, 0x92,
0x31, 0x23, 0x0d, 0x20, 0xc3, 0xc2, 0x05, 0x21, 0x62, 0x8e, 0x45, 0xe8, 0x14, 0xc1, 0xda,
0x75, 0xb8, 0xf8, 0x92, 0x01, 0xd0, 0x5d, 0x18, 0x9f, 0x99, 0x11, 0x19, 0xf5, 0x35, 0xe8,
0x7f, 0x20, 0x88, 0x8c, 0x05, 0x75, 0xf5, 0xd7, 0x40, 0x17, 0xbb, 0x1e, 0x36, 0x52, 0xd9,
0xa4, 0x9c, 0xc2, 0x9d, 0x42, 0x81, 0xd8, 0xc7, 0x8a, 0xe7, 0x4c, 0x81, 0xe0, 0xb7, 0x57,
0xed, 0x48, 0x8b, 0xf0, 0x97, 0x15, 0x61, 0xd9, 0x2c, 0x7c, 0x45, 0xaf, 0xc2, 0xcd, 0xfc,
0xaa, 0x13, 0xad, 0x59, 0xcc, 0xb2, 0xb2, 0x6e, 0xdd, 0x63, 0x9c, 0x32, 0x0f, 0xec, 0x83,
0xbe, 0x78, 0xac, 0x91, 0x44, 0x1a, 0x1f, 0xea, 0xfd, 0x5d, 0x8e, 0xb4, 0xc0, 0x84, 0xd4,
0xac, 0xb4, 0x87, 0x5f, 0xac, 0xef, 0xdf, 0xcd, 0x12, 0x56, 0xc8, 0xcd, 0xfe, 0xc5, 0xda,
0xd3, 0xc1, 0x69, 0xf3, 0x61, 0x05, 0xea, 0x25, 0xe2, 0x12, 0x05, 0x8f, 0x39, 0x08, 0x08,
0x7c, 0x37, 0xb6, 0x7e, 0x5b, 0xd8, 0xb1, 0x0e, 0xf2, 0xdb, 0x4b, 0xf1, 0xad, 0x90, 0x01,
0x57, 0xcd, 0xa0, 0xb4, 0x52, 0xe8, 0xf3, 0xd7, 0x8a, 0xbd, 0x4f, 0x9f, 0x21, 0x40, 0x72,
0xa4, 0xfc, 0x0b, 0x01, 0x2b, 0x2f, 0xb6, 0x4c, 0x95, 0x2d, 0x35, 0x33, 0x41, 0x6b, 0xa0,
0x93, 0xe7, 0x2c, 0xf2, 0xd3, 0x72, 0x8b, 0xf4, 0x4f, 0x15, 0x3c, 0xaf, 0xd6, 0x12, 0xde,
0x3f, 0x83, 0x3f, 0xff, 0xf8, 0x7f, 0xf6, 0xcc, 0xa6, 0x7f, 0xc9, 0x9a, 0x6e, 0x1f, 0xc1,
0x0c, 0xfb, 0xee, 0x9c, 0xe7, 0xaf, 0xc9, 0x26, 0x54, 0xef, 0xb0, 0x39, 0xef, 0xb2, 0xe9,
0x23, 0xc4, 0xef, 0xd1, 0xa1, 0xa4, 0x25, 0x24, 0x6f, 0x8d, 0x6a, 0xe5, 0x8a, 0x32, 0x3a,
0xaf, 0xfc, 0xda, 0xce, 0x18, 0x25, 0x42, 0x07, 0x4d, 0x45, 0x8b, 0xdf, 0x85, 0xcf, 0x55,
0xb2, 0x24, 0xfe, 0x9c, 0x69, 0x74, 0xa7, 0x6e, 0xa0, 0xce, 0xc0, 0x39, 0xf4, 0x86, 0xc6,
0x8d, 0xae, 0xb9, 0x48, 0x64, 0x13, 0x0b, 0x40, 0x81, 0xa2, 0xc9, 0xa8, 0x85, 0x51, 0xee,
0x9f, 0xcf, 0xa2, 0x8c, 0x19, 0x52, 0x48, 0xe2, 0xc1, 0xa8, 0x58, 0xb4, 0x10, 0x24, 0x06,
0x58, 0x51, 0xfc, 0xb9, 0x12, 0xec, 0xfd, 0x73, 0xb4, 0x6d, 0x84, 0xfa, 0x06, 0x8b, 0x05,
0x0b, 0x2d, 0xd6, 0xd6, 0x1f, 0x29, 0x82, 0x9f, 0x19, 0x12, 0x1e, 0xb2, 0x04, 0x8f, 0x7f,
0x4d, 0xbd, 0x30, 0x2e, 0xe3, 0xe0, 0x88, 0x29, 0xc5, 0x93, 0xd6, 0x6c, 0x1f, 0x29, 0x45,
0x91, 0xa7, 0x58, 0xcd, 0x05, 0x17, 0xd6, 0x6d, 0xb3, 0xca, 0x66, 0xcc, 0x3c, 0x4a, 0x74,
0xfd, 0x08, 0x10, 0xa6, 0x99, 0x92, 0x10, 0xd2, 0x85, 0xab, 0x6e, 0x1d, 0x0e, 0x8b, 0x26,
0x46, 0xd1, 0x6c, 0x84, 0xc0, 0x26, 0x43, 0x59, 0x68, 0xf0, 0x13, 0x1d, 0xfb, 0xe3, 0xd1,
0xd2, 0xb4, 0x71, 0x9e, 0xf2, 0x59, 0x6a, 0x33, 0x29, 0x79, 0xd2, 0xd7, 0x26, 0xf1, 0xae,
0x78, 0x9e, 0x1f, 0x0f, 0x3f, 0xe3, 0xe8, 0xd0, 0x27, 0x78, 0x77, 0xf6, 0xac, 0x9c, 0x56,
0x39, 0x73, 0x8a, 0x6b, 0x2f, 0x34, 0x78, 0xb1, 0x11, 0xdb, 0xa4, 0x5c, 0x80, 0x01, 0x71,
0x6a, 0xc2, 0xd1, 0x2e, 0x5e, 0x76, 0x28, 0x70, 0x93, 0xae, 0x3e, 0x78, 0xb0, 0x1f, 0x0f,
0xda, 0xbf, 0xfb, 0x8a, 0x67, 0x65, 0x4f, 0x91, 0xed, 0x49, 0x75, 0x78, 0x62, 0xa2, 0x93,
0xb5, 0x70, 0x7f, 0x4d, 0x08, 0x4e, 0x79, 0x61, 0xa8, 0x5f, 0x7f, 0xb4, 0x65, 0x9f, 0x91,
0x54, 0x3a, 0xe8, 0x50, 0x33, 0xd3, 0xd5, 0x8a, 0x7c, 0xf3, 0x9e, 0x8b, 0x77, 0x7b, 0xc6,
0xc6, 0x0c, 0x45, 0x95, 0x1f, 0xb0, 0xd0, 0x0b, 0x27, 0x4a, 0xfd, 0xc7, 0xf7, 0x0d, 0x5a,
0x43, 0xc9, 0x7d, 0x35, 0xb0, 0x7d, 0xc4, 0x9c, 0x57, 0x1e, 0x76, 0x0d, 0xf1, 0x95, 0x30,
0x71, 0xcc, 0xb3, 0x66, 0x3b, 0x63, 0xa8, 0x6c, 0xa3, 0x43, 0xa0, 0x24, 0xcc, 0xb7, 0x53,
0xfe, 0xfe, 0xbc, 0x6e, 0x60, 0x89, 0xaf, 0x16, 0x21, 0xc8, 0x91, 0x6a, 0x89, 0xce, 0x80,
0x2c, 0xf1, 0x59, 0xce, 0xc3, 0x60, 0x61, 0x3b, 0x0b, 0x19, 0xfe, 0x99, 0xac, 0x65, 0x90,
0x15, 0x12, 0x05, 0xac, 0x7e, 0xff, 0x98, 0x7b, 0x66, 0x64, 0x0e, 0x4b, 0x5b, 0xaa, 0x8d,
0x3b, 0xd2, 0x56, 0xcf, 0x99, 0x39, 0xee, 0x22, 0x81, 0xd0, 0x60, 0x06, 0x66, 0x20, 0x81,
0x48, 0x3c, 0x6f, 0x3a, 0x77, 0xba, 0xcb, 0x52, 0xac, 0x79, 0x56, 0xaf, 0xe9, 0x16, 0x17,
0x0a, 0xa3, 0x82, 0x08, 0xd5, 0x3c, 0x97, 0xcb, 0x09, 0xff, 0x7f, 0xf9, 0x4f, 0x60, 0x05,
0xb9, 0x53, 0x26, 0xaa, 0xb8, 0x50, 0xaa, 0x19, 0x25, 0xae, 0x5f, 0xea, 0x8a, 0xd0, 0x89,
0x12, 0x80, 0x43, 0x50, 0x24, 0x12, 0x21, 0x14, 0xcd, 0x77, 0xeb, 0x21, 0xcc, 0x5c, 0x09,
0x64, 0xf3, 0xc7, 0xcb, 0xc5, 0x4b, 0xc3, 0xe7, 0xed, 0xe7, 0x86, 0x2c, 0x1d, 0x8e, 0x19,
0x52, 0x9b, 0x2a, 0x0c, 0x18, 0x72, 0x0b, 0x1e, 0x1b, 0xb0, 0x0f, 0x42, 0x99, 0x04, 0xae,
0xd5, 0xb7, 0x89, 0x1a, 0xb9, 0x4f, 0xd6, 0xaf, 0xf3, 0xc9, 0x93, 0x6f, 0xb0, 0x60, 0x83,
0x6e, 0x6b, 0xd1, 0x5f, 0x3f, 0x1a, 0x83, 0x1e, 0x24, 0x00, 0x87, 0xb5, 0x3e, 0xdb, 0xf9,
0x4d, 0xa7, 0x16, 0x2e, 0x19, 0x5b, 0x8f, 0x1b, 0x0d, 0x47, 0x72, 0x42, 0xe9, 0x0a, 0x11,
0x08, 0x2d, 0x88, 0x1c, 0xbc, 0xc7, 0xb4, 0xbe, 0x29, 0x4d, 0x03, 0x5e, 0xec, 0xdf, 0xf3,
0x3d, 0x2f, 0xe8, 0x1d, 0x9a, 0xd2, 0xd1, 0xab, 0x41, 0x3d, 0x87, 0x11, 0x45, 0xb0, 0x0d,
0x46, 0xf5, 0xe8, 0x95, 0x62, 0x1c, 0x68, 0xf7, 0xa6, 0x5b, 0x39, 0x4e, 0xbf, 0x47, 0xba,
0x5d, 0x7f, 0xb7, 0x6a, 0xf4, 0xba, 0x1d, 0x69, 0xf6, 0xa4, 0xe7, 0xe4, 0x6b, 0x3b, 0x0d,
0x23, 0x16, 0x4a, 0xb2, 0x68, 0xf0, 0xb2, 0x0d, 0x09, 0x17, 0x6a, 0x63, 0x8c, 0x83, 0xd3,
0xbd, 0x05, 0xc9, 0xf6, 0xf0, 0xa1, 0x31, 0x0b, 0x2c, 0xac, 0x83, 0xac, 0x80, 0x34, 0x32,
0xb4, 0xec, 0xd0, 0xbc, 0x54, 0x82, 0x9a, 0xc8, 0xf6, 0xa0, 0x7d, 0xc6, 0x79, 0x73, 0xf4,
0x20, 0x99, 0xf3, 0xb4, 0x01, 0xde, 0x91, 0x27, 0xf2, 0xc0, 0xdc, 0x81, 0x00, 0x4e, 0x7e,
0x07, 0x99, 0xc8, 0x3a, 0x51, 0xbc, 0x38, 0xd6, 0x8a, 0xa2, 0xde, 0x3b, 0x6a, 0x8c, 0x1a,
0x7c, 0x81, 0x0f, 0x3a, 0x1f, 0xe4, 0x05, 0x7b, 0x20, 0x35, 0x6b, 0xa5, 0x6a, 0xa7, 0xe7,
0xbc, 0x9c, 0x20, 0xec, 0x00, 0x15, 0xe2, 0x51, 0xaf, 0x77, 0xeb, 0x29, 0x3c, 0x7d, 0x2e,
0x00, 0x5c, 0x81, 0x21, 0xfa, 0x35, 0x6f, 0x40, 0xef, 0xfb, 0xd1, 0x3f, 0xcc, 0x9d, 0x55,
0x53, 0xfb, 0x5a, 0xa5, 0x56, 0x89, 0x0b, 0x52, 0xeb, 0x57, 0x73, 0x4f, 0x1b, 0x67, 0x24,
0xcb, 0xb8, 0x6a, 0x10, 0x69, 0xd6, 0xfb, 0x52, 0x40, 0xff, 0x20, 0xa5, 0xf3, 0x72, 0xe1,
0x3d, 0xa4, 0x8c, 0x81, 0x66, 0x16, 0x0d, 0x5d, 0xad, 0xa8, 0x50, 0x25, 0x78, 0x31, 0x77,
0x0c, 0x57, 0xe4, 0xe9, 0x15, 0x2d, 0xdb, 0x07, 0x87, 0xc8, 0xb0, 0x43, 0xde, 0xfc, 0xfe,
0xa9, 0xeb, 0xf5, 0xb0, 0xd3, 0x7b, 0xe9, 0x1f, 0x6e, 0xca, 0xe4, 0x03, 0x95, 0xc5, 0xd1,
0x59, 0x72, 0x63, 0xf0, 0x86, 0x54, 0xe8, 0x16, 0x62, 0x0b, 0x35, 0x29, 0xc2, 0x68, 0xd0,
0xd6, 0x3e, 0x90, 0x60, 0x57, 0x1d, 0xc9, 0xed, 0x3f, 0xed, 0xb0, 0x2f, 0x7e, 0x97, 0x02,
0x51, 0xec, 0xee, 0x6f, 0x82, 0x74, 0x76, 0x7f, 0xfb, 0xd6, 0xc4, 0xc3, 0xdd, 0xe8, 0xb1,
0x60, 0xfc, 0xc6, 0xb9, 0x0d, 0x6a, 0x33, 0x78, 0xc6, 0xc1, 0xbf, 0x86, 0x2c, 0x50, 0xcc,
0x9a, 0x70, 0x8e, 0x7b, 0xec, 0xab, 0x95, 0xac, 0x53, 0xa0, 0x4b, 0x07, 0x88, 0xaf, 0x42,
0xed, 0x19, 0x8d, 0xf6, 0x32, 0x17, 0x48, 0x47, 0x1d, 0x41, 0x6f, 0xfe, 0x2e, 0xa7, 0x8f,
0x4b, 0xa0, 0x51, 0xf3, 0xbf, 0x02, 0x0a, 0x48, 0x58, 0xf7, 0xa1, 0x6d, 0xea, 0xa5, 0x13,
0x5a, 0x5b, 0xea, 0x0c, 0x9e, 0x52, 0x4f, 0x9e, 0xb9, 0x71, 0x7f, 0x23, 0x83, 0xda, 0x1b,
0x86, 0x9a, 0x41, 0x29, 0xda, 0x70, 0xe7, 0x64, 0xa1, 0x7b, 0xd5, 0x0a, 0x22, 0x0d, 0x5c,
0x40, 0xc4, 0x81, 0x07, 0x25, 0x35, 0x4a, 0x1c, 0x10, 0xdb, 0x45, 0x0a, 0xff, 0x36, 0xd4,
0xe0, 0xeb, 0x5f, 0x68, 0xd6, 0x67, 0xc6, 0xd0, 0x8b, 0x76, 0x1a, 0x7d, 0x59, 0x42, 0xa1,
0xcb, 0x96, 0x4d, 0x84, 0x09, 0x9a, 0x3d, 0xe0, 0x52, 0x85, 0x6e, 0x48, 0x90, 0x85, 0x2a,
0x63, 0xb2, 0x69, 0xd2, 0x00, 0x43, 0x31, 0x37, 0xb3, 0x52, 0xaf, 0x62, 0xfa, 0xc1, 0xe0,
0x03, 0xfb, 0x62, 0xaa, 0x88, 0xc9, 0xb2, 0x2c, 0xd5, 0xa8, 0xf5, 0xa5, 0x4c, 0x12, 0x59,
0x4e, 0x06, 0x5e, 0x9b, 0x15, 0x66, 0x11, 0xb2, 0x27, 0x92, 0xdc, 0x98, 0x59, 0xde, 0xdf,
0xfa, 0x9a, 0x32, 0x2e, 0xc0, 0x5d, 0x3c, 0x33, 0x41, 0x6d, 0xaf, 0xb2, 0x25, 0x23, 0x14,
0xa5, 0x7b, 0xc7, 0x9b, 0x68, 0xf3, 0xda, 0xeb, 0xe3, 0xa9, 0xe2, 0x6f, 0x0e, 0x1d, 0x1c,
0xba, 0x55, 0xb6, 0x34, 0x6a, 0x93, 0x1f, 0x1f, 0xb8, 0x34, 0xc8, 0x84, 0x08, 0xb1, 0x6b,
0x6a, 0x28, 0x74, 0x74, 0xe5, 0xeb, 0x75, 0xe9, 0x7c, 0xd8, 0xba, 0xd8, 0x42, 0xa5, 0xee,
0x1f, 0x80, 0xd9, 0x96, 0xb2, 0x2e, 0xe7, 0xbf, 0xba, 0xeb, 0xd1, 0x69, 0xbb, 0x8f, 0xfd,
0x5a, 0x63, 0x8f, 0x39, 0x7f, 0xdf, 0x1d, 0x37, 0xd2, 0x18, 0x35, 0x9d, 0xb6, 0xcc, 0xe4,
0x27, 0x81, 0x89, 0x38, 0x38, 0x68, 0x33, 0xe7, 0x78, 0xd8, 0x76, 0xf5, 0xee, 0xd0, 0x4a,
0x07, 0x69, 0x19, 0x7a, 0xad, 0x18, 0xb1, 0x94, 0x61, 0x45, 0x53, 0xa2, 0x48, 0xda, 0x96,
0x4a, 0xf9, 0xee, 0x94, 0x2a, 0x1f, 0x6e, 0x18, 0x3c, 0x92, 0x46, 0xd1, 0x1a, 0x28, 0x18,
0x32, 0x1f, 0x3a, 0x45, 0xbe, 0x04, 0x35, 0x92, 0xe5, 0xa3, 0xcb, 0xb5, 0x2e, 0x32, 0x43,
0xac, 0x65, 0x17, 0x89, 0x99, 0x15, 0x03, 0x9e, 0xb1, 0x23, 0x2f, 0xed, 0x76, 0x4d, 0xd8,
0xac, 0x21, 0x40, 0xc4, 0x99, 0x4e, 0x65, 0x71, 0x2c, 0xb3, 0x45, 0xab, 0xfb, 0xe7, 0x72,
0x39, 0x56, 0x30, 0x6d, 0xfb, 0x74, 0xeb, 0x99, 0xf3, 0xcd, 0x57, 0x5c, 0x78, 0x75, 0xe9,
0x8d, 0xc3, 0xa2, 0xfb, 0x5d, 0xe0, 0x90, 0xc5, 0x55, 0xad, 0x91, 0x53, 0x4e, 0x9e, 0xbd,
0x8c, 0x49, 0xa4, 0xa4, 0x69, 0x10, 0x0c, 0xc5, 0x76, 0xe9, 0x25, 0x86, 0x8d, 0x66, 0x23,
0xa8, 0xdb, 0x5c, 0xe8, 0xd9, 0x30, 0xe1, 0x15, 0x7b, 0xc0, 0x99, 0x0f, 0x03, 0xec, 0xaa,
0x12, 0xef, 0xce, 0xd4, 0xea, 0x55, 0x5c, 0x08, 0x86, 0xf4, 0xf4, 0xb0, 0x83, 0x42, 0x95,
0x37, 0xb6, 0x38, 0xe0, 0x2b, 0x54, 0x89, 0xbd, 0x4e, 0x20, 0x9d, 0x3f, 0xc3, 0x4b, 0xb7,
0xec, 0xfa, 0x5a, 0x14, 0x03, 0xcb, 0x64, 0xc8, 0x34, 0x4a, 0x4b, 0x6e, 0xf8, 0x6e, 0x56,
0xf6, 0xdd, 0x5f, 0xa1, 0x24, 0xe2, 0xd4, 0xd0, 0x82, 0x64, 0x1f, 0x8e, 0x9b, 0xfa, 0xb4,
0xcb, 0xdb, 0x0a, 0xe8, 0x15, 0xfc, 0x15, 0xab, 0x4b, 0x18, 0xbf, 0xd4, 0x42, 0x14, 0x48,
0x82, 0x85, 0xdd, 0xeb, 0x49, 0x1b, 0x0b, 0x0b, 0x05, 0xe9, 0xb4, 0xa1, 0x33, 0x0a, 0x5d,
0x0e, 0x6c, 0x4b, 0xc0, 0xd6, 0x6c, 0x7c, 0xfb, 0x69, 0x0b, 0x53, 0x19, 0xe4, 0xf3, 0x35,
0xfc, 0xbe, 0xa1, 0x34, 0x02, 0x09, 0x4f, 0x74, 0x86, 0x92, 0xcd, 0x5d, 0x1a, 0xc1, 0x27,
0x0c, 0xf2, 0xc5, 0xcf, 0xdd, 0x23, 0x93, 0x02, 0xbd, 0x41, 0x5e, 0x42, 0xf0, 0xa0, 0x9d,
0x0c, 0x72, 0xc8, 0xec, 0x32, 0x0a, 0x8a, 0xfd, 0x3d, 0x5a, 0x41, 0x27, 0x0c, 0x88, 0x59,
0xad, 0x94, 0x2e, 0xef, 0x5d, 0x8f, 0xc7, 0xdf, 0x66, 0xe4, 0xdd, 0x56, 0x6c, 0x7b, 0xca,
0x55, 0x81, 0xae, 0xae, 0x5c, 0x1b, 0x1a, 0xab, 0xae, 0x99, 0x8d, 0xcc, 0x42, 0x97, 0x59,
0xf4, 0x14, 0x3f, 0x75, 0xc6, 0xd1, 0x88, 0xba, 0xaa, 0x84, 0x4a, 0xd0, 0x34, 0x08, 0x3b,
0x7d, 0xdb, 0x15, 0x06, 0xb0, 0x5c, 0xbd, 0x40, 0xf5, 0xa8, 0xec, 0xae, 0x36, 0x40, 0xdd,
0x90, 0x1c, 0x3e, 0x0d, 0x7e, 0x73, 0xc7, 0xc2, 0xc5, 0x6a, 0xff, 0x52, 0x05, 0x7f, 0xbe,
0xd0, 0x92, 0xfd, 0xb3, 0x6f, 0xff, 0x5d, 0xb7, 0x97, 0x64, 0x73, 0x7b, 0xca, 0xd1, 0x98,
0x24, 0x6b, 0x0b, 0x01, 0x68, 0xdd, 0x27, 0x85, 0x85, 0xb5, 0x83, 0xc1, 0xe0, 0x50, 0x64,
0xc7, 0xaf, 0xf1, 0xc6, 0x4d, 0xb1, 0xef, 0xc9, 0xb4, 0x0a, 0x6d, 0x65, 0xf3, 0x47, 0xcc,
0xa3, 0x02, 0x21, 0x0c, 0xbe, 0x22, 0x29, 0x05, 0xcf, 0x5f, 0xe8, 0x94, 0x6c, 0xe5, 0xdc,
0xc4, 0xdf, 0xbe, 0x3e, 0xa8, 0xb4, 0x18, 0xb0, 0x99, 0xb8, 0x6f, 0xff, 0x5d, 0xb9, 0xfd,
0x3b, 0x5d, 0x16, 0xbf, 0x3e, 0xd8, 0xb3, 0xd8, 0x08, 0x34, 0xf6, 0x47, 0x35, 0x5b, 0x72,
0x1a, 0x33, 0xad, 0x52, 0x5d, 0xb8, 0xd0, 0x77, 0xc6, 0xab, 0xba, 0x55, 0x09, 0x5f, 0x02,
0xf8, 0xd4, 0x5f, 0x53, 0x06, 0x91, 0xcd, 0x74, 0x42, 0xae, 0x54, 0x91, 0x81, 0x62, 0x13,
0x6f, 0xd8, 0xa9, 0x77, 0xc3, 0x6c, 0xcb, 0xf1, 0x29, 0x5a, 0xcc, 0xda, 0x35, 0xbd, 0x52,
0x23, 0xbe, 0x59, 0xeb, 0x12, 0x6d, 0xb7, 0x53, 0xee, 0xfc, 0xb4, 0x1b, 0x13, 0x5e, 0xba,
0x16, 0x7c, 0xc5, 0xf3, 0xe3, 0x6d, 0x07, 0x78, 0xf5, 0x2b, 0x21, 0x05, 0x88, 0x4c, 0xc0,
0xa1, 0xe3, 0x36, 0x10, 0xf8, 0x1b, 0xd8, 0x17, 0xfb, 0x6a, 0x4e, 0xd8, 0xb3, 0x47, 0x2d,
0x99, 0xbd, 0xbb, 0x5d, 0x37, 0x7d, 0xba, 0xf1, 0xe1, 0x7c, 0xc0, 0xc5, 0x54, 0x62, 0x7f,
0xcf, 0x5a, 0x4a, 0x93, 0xcc, 0xf1, 0x1b, 0x34, 0xc8, 0xa6, 0x05, 0x4c, 0x55, 0x8b, 0x54,
0x84, 0xd5, 0x77, 0xeb, 0xc0, 0x6d, 0x3a, 0x29, 0xbd, 0x75, 0x61, 0x09, 0x9a, 0x2c, 0xbb,
0xf7, 0x18, 0x79, 0x34, 0x90, 0x24, 0xa5, 0x81, 0x70, 0x87, 0xc5, 0x02, 0x7c, 0xba, 0xd4,
0x5e, 0x14, 0x8e, 0xe4, 0xed, 0xa2, 0x61, 0x6a, 0xb9, 0x6e, 0xb5, 0x4a, 0xb9, 0x01, 0x46,
0xf4, 0xcf, 0xbc, 0x09, 0x2f, 0x27, 0x4b, 0xbd, 0x86, 0x7a, 0x10, 0xe1, 0xd4, 0xc8, 0xd9,
0x20, 0x8d, 0x8a, 0x63, 0x00, 0x63, 0x44, 0xeb, 0x54, 0x0b, 0x75, 0x49, 0x10, 0xa2, 0xa7,
0xad, 0xb9, 0xd1, 0x01, 0x80, 0x63, 0x25, 0xc8, 0x12, 0xa6, 0xce, 0x1e, 0xbe, 0xfe, 0x7e,
0x5f, 0x3c, 0xdb, 0x34, 0xea, 0x37, 0xec, 0x3b, 0xd5, 0x28, 0xd2, 0x07, 0x8c, 0x9a, 0xb6,
0xee, 0x5e, 0x3e, 0xdf, 0x1d, 0x99, 0xb0, 0xe2, 0x46, 0xef, 0x5c, 0x1b, 0xb4, 0xea, 0x56,
0x2e, 0xde, 0x1f, 0x9d, 0xb8, 0xd3, 0x24, 0xab, 0xd4, 0x2a, 0xd6, 0x2e, 0xde, 0x1f, 0x9d,
0xb8, 0xf2, 0x66, 0x2f, 0xbd, 0xf8, 0x72, 0x66, 0x4e, 0x1e, 0x9f, 0x9d, 0xb8, 0xf2, 0x47,
0x0c, 0x9a, 0xb6, 0xee, 0x3f, 0xfc, 0x7a, 0x57, 0x0d, 0x79, 0x70, 0x62, 0x27, 0xad, 0xb9,
0xd1, 0x01, 0x61, 0x40, 0x02, 0x67, 0x2d, 0xd8, 0x32, 0xe6, 0x2f, 0xdc, 0x3a, 0xd7, 0x2c,
0xbb, 0xf4, 0x4b, 0xf5, 0x49, 0xf1, 0x60, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0xf9, 0x51, 0x01,
0x80, 0x63, 0x25, 0xa9, 0xb1, 0xe0, 0x42, 0xe7, 0x4c, 0x1a, 0x97, 0xac, 0xbb, 0xf4, 0x6a,
0x37, 0xcd, 0x18, 0xb2, 0xe6, 0x2f, 0xdc, 0x1b, 0x95, 0xa8, 0xd2, 0x07, 0x6d, 0x58, 0x32,
0xe6, 0x4e, 0x1e, 0x9f, 0xbc, 0xfa, 0x57, 0x0d, 0x79, 0x51, 0x20, 0xc2, 0x06, 0x6f, 0x5c,
0x1b, 0x95, 0xa8, 0xb3, 0xc5, 0xe9, 0x31, 0xe0, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0x18, 0x93,
0x85, 0x69, 0x31, 0xc1, 0xe1, 0x21, 0xc0, 0xe3, 0x44, 0x0a, 0x77, 0x6c, 0x5a, 0x17, 0x8d,
0x98, 0x93, 0xa4, 0xab, 0xd4, 0x2a, 0xb7, 0xec, 0x5a, 0x17, 0xac, 0xbb, 0xf4, 0x4b, 0x14,
0xaa, 0xb7, 0xec, 0x3b, 0xd5, 0x28, 0xb3, 0xc5, 0xe9, 0x31, 0xc1, 0x00, 0x82, 0x67, 0x4c,
0xfb, 0x55, 0x28, 0xd2, 0x26, 0xaf, 0xbd, 0xd9, 0x11, 0x81, 0x61, 0x21, 0xa1, 0xa1, 0xc0,
0x02, 0x86, 0x6f, 0x5c, 0x1b, 0xb4, 0xcb, 0x14, 0x8b, 0x94, 0xaa, 0xd6, 0x2e, 0xbf, 0xdd,
0x19, 0xb0, 0xe2, 0x46, 0x0e, 0x7f, 0x7c, 0x5b, 0x15, 0x89, 0x90, 0x83, 0x84, 0x6b, 0x54,
0x0b, 0x75, 0x68, 0x52, 0x07, 0x6d, 0x58, 0x32, 0xc7, 0xed, 0x58, 0x32, 0xc7, 0xed, 0x58,
0x32, 0xe6, 0x4e, 0xff, 0x7c, 0x7a, 0x76, 0x6e, 0x3f, 0xdd, 0x38, 0xd3, 0x05, 0x88, 0x92,
0xa6, 0xaf, 0xdc, 0x1b, 0xb4, 0xcb, 0xf5, 0x68, 0x52, 0x07, 0x8c, 0x7b, 0x55, 0x09, 0x90,
0x83, 0x84, 0x6b, 0x54, 0x2a, 0xb7, 0xec, 0x3b, 0xd5, 0x09, 0x90, 0xa2, 0xc6, 0x0e, 0x7f,
0x7c, 0x7a, 0x57, 0x0d, 0x98, 0xb2, 0xc7, 0xed, 0x58, 0x32, 0xc7, 0x0c, 0x7b, 0x74, 0x4b,
0x14, 0x8b, 0x94, 0xaa, 0xb7, 0xcd, 0x18, 0x93, 0xa4, 0xca, 0x16, 0xae, 0xbf, 0xdd, 0x19,
0xb0, 0xe2, 0x46, 0x0e, 0x7f, 0x5d, 0x19, 0x91, 0x81, 0x80, 0x63, 0x44, 0xeb, 0x35, 0xc9,
0x10, 0x83, 0x65, 0x48, 0x12, 0xa6, 0xce, 0x1e, 0x9f, 0xbc, 0xdb, 0x15, 0x89, 0x71, 0x60,
0x23, 0xc4, 0xeb, 0x54, 0x2a, 0xb7, 0xec, 0x5a, 0x36, 0xcf, 0x81, 0x10, 0xac, 0x74 };
// clang-format off

View file

@ -1,4 +1,4 @@
/* Copyright 2020 Alexander Tulloh /* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,20 +16,7 @@
#pragma once #pragma once
#include <stdint.h> #define ANALOG_JOYSTICK_X_AXIS_PIN B4
#define ANALOG_JOYSTICK_Y_AXIS_PIN B5
typedef struct { #define ANALOG_JOYSTICK_CLICK_PIN E6
/* 100 - 12000 CPI supported */
uint16_t cpi;
} config_pmw_t;
typedef struct {
int16_t x;
int16_t y;
} report_pmw_t;
void pmw_init(void);
config_pmw_t pmw_get_config(void);
void pmw_set_config(config_pmw_t);
/* Reads and clears the current delta values on the PMW sensor */
report_pmw_t pmw_get_report(void);

View file

@ -29,98 +29,3 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
}; };
// clang-format on // clang-format on
// Joystick
// Set Pins
// uint8_t xPin = 8; // VRx / /B4
// uint8_t yPin = 7; // VRy // B5
uint8_t swPin = E6; // SW
// Set Parameters
uint16_t minAxisValue = 0;
uint16_t maxAxisValue = 1023;
uint8_t maxCursorSpeed = 2;
uint8_t precisionSpeed = 1;
uint8_t speedRegulator = 20; // Lower Values Create Faster Movement
int8_t xPolarity = 1;
int8_t yPolarity = 1;
uint8_t cursorTimeout = 10;
int16_t xOrigin, yOrigin;
uint16_t lastCursor = 0;
int16_t axisCoordinate(uint8_t pin, uint16_t origin) {
int8_t direction;
int16_t distanceFromOrigin;
int16_t range;
int16_t position = analogReadPin(pin);
if (origin == position) {
return 0;
} else if (origin > position) {
distanceFromOrigin = origin - position;
range = origin - minAxisValue;
direction = -1;
} else {
distanceFromOrigin = position - origin;
range = maxAxisValue - origin;
direction = 1;
}
float percent = (float)distanceFromOrigin / range;
int16_t coordinate = (int16_t)(percent * 100);
if (coordinate < 0) {
return 0;
} else if (coordinate > 100) {
return 100 * direction;
} else {
return coordinate * direction;
}
}
int8_t axisToMouseComponent(uint8_t pin, int16_t origin, uint8_t maxSpeed, int8_t polarity) {
int coordinate = axisCoordinate(pin, origin);
if (coordinate != 0) {
float percent = (float)coordinate / 100;
if (get_mods() & MOD_BIT(KC_LSFT)) {
return percent * precisionSpeed * polarity * (abs(coordinate) / speedRegulator);
} else {
return percent * maxCursorSpeed * polarity * (abs(coordinate) / speedRegulator);
}
} else {
return 0;
}
}
void pointing_device_task(void) {
report_mouse_t report = pointing_device_get_report();
// todo read as one vector
if (timer_elapsed(lastCursor) > cursorTimeout) {
lastCursor = timer_read();
report.x = axisToMouseComponent(B4, xOrigin, maxCursorSpeed, xPolarity);
report.y = axisToMouseComponent(B5, yOrigin, maxCursorSpeed, yPolarity);
}
//
if (!readPin(E6)) {
report.buttons |= MOUSE_BTN1;
} else {
report.buttons &= ~MOUSE_BTN1;
}
pointing_device_set_report(report);
pointing_device_send();
}
void matrix_init_keymap(void) {
// init pin? Is needed?
setPinInputHigh(E6);
// Account for drift
xOrigin = analogReadPin(B4);
yOrigin = analogReadPin(B5);
}

View file

@ -1,7 +1,6 @@
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = analog_joystick
RGBLIGHT_ENABLE = no RGBLIGHT_ENABLE = no
CONSOLE_ENABLE = no CONSOLE_ENABLE = no
BOOTLOADER = qmk-dfu BOOTLOADER = qmk-dfu
SRC += analog.c

View file

@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DIODE_DIRECTION COL2ROW #define DIODE_DIRECTION COL2ROW
#define ROTATIONAL_TRANSFORM_ANGLE -25 #define ROTATIONAL_TRANSFORM_ANGLE -25
#define POINTING_DEVICE_INVERT_X
/* Bootmagic Lite key configuration */ /* Bootmagic Lite key configuration */
#define BOOTMAGIC_LITE_ROW 0 #define BOOTMAGIC_LITE_ROW 0

View file

@ -174,7 +174,11 @@ bool tap_toggling = false;
# define TAP_CHECK TAPPING_TERM # define TAP_CHECK TAPPING_TERM
# endif # endif
void process_mouse_user(report_mouse_t* mouse_report, int8_t x, int8_t y) { report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) {
int8_t x = mouse_report.x, y = mouse_report.y;
mouse_report.x = 0;
mouse_report.y = 0;
if (x != 0 && y != 0) { if (x != 0 && y != 0) {
mouse_timer = timer_read(); mouse_timer = timer_read();
# ifdef OLED_ENABLE # ifdef OLED_ENABLE
@ -185,13 +189,14 @@ void process_mouse_user(report_mouse_t* mouse_report, int8_t x, int8_t y) {
x = (x > 0 ? x * x / 16 + x : -x * x / 16 + x); x = (x > 0 ? x * x / 16 + x : -x * x / 16 + x);
y = (y > 0 ? y * y / 16 + y : -y * y / 16 + y); y = (y > 0 ? y * y / 16 + y : -y * y / 16 + y);
} }
mouse_report->x = x; mouse_report.x = x;
mouse_report->y = y; mouse_report.y = y;
if (!layer_state_is(_MOUSE)) { if (!layer_state_is(_MOUSE)) {
layer_on(_MOUSE); layer_on(_MOUSE);
} }
} }
} }
return mouse_report;
} }
void matrix_scan_keymap(void) { void matrix_scan_keymap(void) {

View file

@ -16,11 +16,11 @@ AUDIO_ENABLE = no # Audio output
SWAP_HANDS_ENABLE = yes SWAP_HANDS_ENABLE = yes
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = pmw3360
MOUSE_SHARED_EP = no MOUSE_SHARED_EP = no
SPLIT_KEYBOARD = yes SPLIT_KEYBOARD = yes
SRC += drivers/sensors/pmw3360.c QUANTUM_LIB_SRC += tm_sync.c
QUANTUM_LIB_SRC += spi_master.c tm_sync.c
DEFAULT_FOLDER = handwired/tractyl_manuform/5x6_right/teensy2pp DEFAULT_FOLDER = handwired/tractyl_manuform/5x6_right/teensy2pp

View file

@ -17,7 +17,6 @@
#include "tractyl_manuform.h" #include "tractyl_manuform.h"
#include "transactions.h" #include "transactions.h"
#include <string.h> #include <string.h>
#include "drivers/sensors/pmw3360.h"
kb_config_data_t kb_config; kb_config_data_t kb_config;
kb_mouse_report_t sync_mouse_report; kb_mouse_report_t sync_mouse_report;
@ -82,6 +81,6 @@ void housekeeping_task_sync(void) {
void trackball_set_cpi(uint16_t cpi) { void trackball_set_cpi(uint16_t cpi) {
kb_config.device_cpi = cpi; kb_config.device_cpi = cpi;
if (!is_keyboard_left()) { if (!is_keyboard_left()) {
pmw_set_cpi(cpi); pointing_device_set_cpi(cpi);
} }
} }

View file

@ -34,55 +34,7 @@ keyboard_config_t keyboard_config;
uint16_t dpi_array[] = TRACKBALL_DPI_OPTIONS; uint16_t dpi_array[] = TRACKBALL_DPI_OPTIONS;
#define DPI_OPTION_SIZE (sizeof(dpi_array) / sizeof(uint16_t)) #define DPI_OPTION_SIZE (sizeof(dpi_array) / sizeof(uint16_t))
bool BurstState = false; // init burst state for Trackball module
uint16_t MotionStart = 0; // Timer for accel, 0 is resting state
__attribute__((weak)) void process_mouse_user(report_mouse_t* mouse_report, int8_t x, int8_t y) {
mouse_report->x = x;
mouse_report->y = y;
}
__attribute__((weak)) void process_mouse(void) {
report_pmw_t data = pmw_read_burst();
// Reset timer if stopped moving
if (!data.isMotion) {
if (MotionStart != 0) MotionStart = 0;
return;
}
if (data.isOnSurface) {
// Set timer if new motion
if (MotionStart == 0) {
if (debug_mouse) dprintf("Starting motion.\n");
MotionStart = timer_read();
}
if (debug_mouse) {
dprintf("Delt] d: %d t: %u\n", abs(data.dx) + abs(data.dy), MotionStart);
}
if (debug_mouse) {
dprintf("Pre ] X: %d, Y: %d\n", data.dx, data.dy);
}
#if defined(PROFILE_LINEAR)
float scale = float(timer_elaspsed(MotionStart)) / 1000.0;
data.dx *= scale;
data.dy *= scale;
#elif defined(PROFILE_INVERSE)
// TODO
#else
// no post processing
#endif
// Wrap to HID size
data.dx = constrain(data.dx, -127, 127);
data.dy = constrain(data.dy, -127, 127);
if (debug_mouse) dprintf("Cons] X: %d, Y: %d\n", data.dx, data.dy);
// dprintf("Elapsed:%u, X: %f Y: %\n", i, pgm_read_byte(firmware_data+i));
sync_mouse_report.x = -data.dx;
sync_mouse_report.y = data.dy;
}
}
bool process_record_kb(uint16_t keycode, keyrecord_t* record) { bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
if (!process_record_user(keycode, record)) { if (!process_record_user(keycode, record)) {
@ -109,11 +61,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
#ifndef MOUSEKEY_ENABLE #ifndef MOUSEKEY_ENABLE
if (IS_MOUSEKEY_BUTTON(keycode)) { if (IS_MOUSEKEY_BUTTON(keycode)) {
report_mouse_t currentReport = pointing_device_get_report(); report_mouse_t currentReport = pointing_device_get_report();
if (record->event.pressed) { currentReport.buttons = pointing_device_handle_buttons(currentReport.buttons, record->event.pressed, keycode - KC_MS_BTN1);
currentReport.buttons |= 1 << (keycode - KC_MS_BTN1);
} else {
currentReport.buttons &= ~(1 << (keycode - KC_MS_BTN1));
}
pointing_device_set_report(currentReport); pointing_device_set_report(currentReport);
pointing_device_send(); pointing_device_send();
} }
@ -145,33 +93,28 @@ void keyboard_post_init_kb(void) {
} }
#ifdef POINTING_DEVICE_ENABLE #ifdef POINTING_DEVICE_ENABLE
void pointing_device_init(void) { void pointing_device_init_kb(void) {
if (!is_keyboard_left()) {
// initialize ball sensor
pmw_spi_init();
}
trackball_set_cpi(dpi_array[keyboard_config.dpi_config]); trackball_set_cpi(dpi_array[keyboard_config.dpi_config]);
pointing_device_init_user();
} }
void pointing_device_task(void) { report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
report_mouse_t mouse_report = pointing_device_get_report();
if (is_keyboard_left()) { if (is_keyboard_left()) {
if (is_keyboard_master()) { if (is_keyboard_master()) {
transaction_rpc_recv(RPC_ID_POINTER_STATE_SYNC, sizeof(sync_mouse_report), &sync_mouse_report); transaction_rpc_recv(RPC_ID_POINTER_STATE_SYNC, sizeof(sync_mouse_report), &sync_mouse_report);
process_mouse_user(&mouse_report, sync_mouse_report.x, sync_mouse_report.y); mouse_report.x = sync_mouse_report.x;
mouse_report.y = sync_mouse_report.y;
pointing_device_task_user(mouse_report);
} }
} else { } else {
process_mouse();
if (is_keyboard_master()) { if (is_keyboard_master()) {
process_mouse_user(&mouse_report, sync_mouse_report.x, sync_mouse_report.y); pointing_device_task_user(mouse_report);
sync_mouse_report.x = 0; } else {
sync_mouse_report.y = 0; sync_mouse_report.x = mouse_report.x;
sync_mouse_report.y = mouse_report.y;
} }
} }
return mouse_report;
pointing_device_set_report(mouse_report);
pointing_device_send();
} }
#endif #endif

View file

@ -58,3 +58,6 @@
/* Bootmagic Lite key configuration */ /* Bootmagic Lite key configuration */
// #define BOOTMAGIC_LITE_ROW 0 // #define BOOTMAGIC_LITE_ROW 0
// #define BOOTMAGIC_LITE_COLUMN 0 // #define BOOTMAGIC_LITE_COLUMN 0
#define ADNS9800_CS_PIN SPI_SS_PIN
#define PMW3360_CS_PIN SPI_SS_PIN

View file

@ -1 +0,0 @@
#define ADNS_9800

View file

@ -0,0 +1 @@
POINTING_DEVICE_DRIVER = adns9800

View file

@ -1 +0,0 @@
#define PMW_3360

View file

@ -0,0 +1 @@
POINTING_DEVICE_DRIVER = pmw3360

View file

@ -16,21 +16,17 @@
#include "oddball.h" #include "oddball.h"
#include "pointing_device.h" #include "pointing_device.h"
#include "optical_sensor/optical_sensor.h" extern const pointing_device_driver_t pointing_device_driver;
#define CLAMP_HID(value) value < -127 ? -127 : value > 127 ? 127 : value
static bool scroll_pressed; static bool scroll_pressed;
static bool mouse_buttons_dirty; static bool mouse_buttons_dirty;
static int8_t scroll_h; static int8_t scroll_h;
static int8_t scroll_v; static int8_t scroll_v;
void pointing_device_init(void){ void pointing_device_init_kb(void){
if(!is_keyboard_master()) if(!is_keyboard_master())
return; return;
optical_sensor_init();
// read config from EEPROM and update if needed // read config from EEPROM and update if needed
config_oddball_t kb_config; config_oddball_t kb_config;
@ -41,21 +37,17 @@ void pointing_device_init(void){
eeconfig_update_kb(kb_config.raw); eeconfig_update_kb(kb_config.raw);
} }
optical_sensor_set_config((config_optical_sensor_t){ kb_config.cpi }); pointing_device_set_cpi(kb_config.cpi);
} }
void pointing_device_task(void){ report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
if(!is_keyboard_master()) if (!is_keyboard_master()) return mouse_report;
return;
report_mouse_t mouse_report = pointing_device_get_report(); int8_t clamped_x = mouse_report.x, clamped_y = mouse_report.y;
report_optical_sensor_t sensor_report = optical_sensor_get_report(); mouse_report.x = 0;
mouse_report.y = 0;
int8_t clamped_x = CLAMP_HID(sensor_report.x);
int8_t clamped_y = CLAMP_HID(sensor_report.y);
if (scroll_pressed) { if (scroll_pressed) {
// accumulate scroll // accumulate scroll
scroll_h += clamped_x; scroll_h += clamped_x;
scroll_v += clamped_y; scroll_v += clamped_y;
@ -74,24 +66,12 @@ void pointing_device_task(void){
mouse_report.v = -scaled_scroll_v; mouse_report.v = -scaled_scroll_v;
scroll_v = 0; scroll_v = 0;
} }
} } else {
else {
mouse_report.x = -clamped_x; mouse_report.x = -clamped_x;
mouse_report.y = clamped_y; mouse_report.y = clamped_y;
} }
pointing_device_set_report(mouse_report); return mouse_report;
// only send report on change as even sending report with no change is treated as movement
if(mouse_buttons_dirty ||
mouse_report.x != 0 ||
mouse_report.y != 0 ||
mouse_report.h != 0 ||
mouse_report.v != 0){
mouse_buttons_dirty = false;
pointing_device_send();
}
} }
static void on_cpi_button(uint16_t cpi, keyrecord_t *record) { static void on_cpi_button(uint16_t cpi, keyrecord_t *record) {
@ -99,7 +79,7 @@ static void on_cpi_button(uint16_t cpi, keyrecord_t *record) {
if(!record->event.pressed) if(!record->event.pressed)
return; return;
optical_sensor_set_config((config_optical_sensor_t){ cpi }); pointing_device_set_cpi(cpi);
config_oddball_t kb_config; config_oddball_t kb_config;
kb_config.cpi = cpi; kb_config.cpi = cpi;

View file

@ -1,53 +0,0 @@
/* Copyright 2020 Alexander Tulloh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* common interface for opitcal sensors */
#if defined ADNS_9800
#include "drivers/sensors/adns9800.h"
#define config_optical_sensor_t config_adns_t
#define report_optical_sensor_t report_adns_t
#define optical_sensor_init adns_init
#define optical_sensor_get_config adns_get_config
#define optical_sensor_set_config adns_set_config
#define optical_sensor_get_report adns_get_report
#elif defined PMW_3360
#include "../pmw/pmw.h"
#define config_optical_sensor_t config_pmw_t
#define report_optical_sensor_t report_pmw_t
#define optical_sensor_init pmw_init
#define optical_sensor_get_config pmw_get_config
#define optical_sensor_set_config pmw_set_config
#define optical_sensor_get_report pmw_get_report
#else
/* fallback stub */
#include <stdint.h>
typedef struct {
uint16_t cpi;
} config_optical_sensor_t;
typedef struct {
int16_t x;
int16_t y;
} report_optical_sensor_t;
#define optical_sensor_init(){ }
#define optical_sensor_get_config() (config_optical_sensor_t){ }
#define optical_sensor_set_config(config_optical_sensor_t){ }
#define optical_sensor_get_report() (report_optical_sensor_t){ }
#endif

View file

@ -1,226 +0,0 @@
/* Copyright 2020 Alexander Tulloh
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "spi_master.h"
#include "quantum.h"
#include "pmw3360_srom_0x04.h"
#include "pmw.h"
// registers
#define Product_ID 0x00
#define Revision_ID 0x01
#define Motion 0x02
#define Delta_X_L 0x03
#define Delta_X_H 0x04
#define Delta_Y_L 0x05
#define Delta_Y_H 0x06
#define SQUAL 0x07
#define Raw_Data_Sum 0x08
#define Maximum_Raw_data 0x09
#define Minimum_Raw_data 0x0A
#define Shutter_Lower 0x0B
#define Shutter_Upper 0x0C
#define Control 0x0D
#define Config1 0x0F
#define Config2 0x10
#define Angle_Tune 0x11
#define Frame_Capture 0x12
#define SROM_Enable 0x13
#define Run_Downshift 0x14
#define Rest1_Rate_Lower 0x15
#define Rest1_Rate_Upper 0x16
#define Rest1_Downshift 0x17
#define Rest2_Rate_Lower 0x18
#define Rest2_Rate_Upper 0x19
#define Rest2_Downshift 0x1A
#define Rest3_Rate_Lower 0x1B
#define Rest3_Rate_Upper 0x1C
#define Observation 0x24
#define Data_Out_Lower 0x25
#define Data_Out_Upper 0x26
#define Raw_Data_Dump 0x29
#define SROM_ID 0x2A
#define Min_SQ_Run 0x2B
#define Raw_Data_Threshold 0x2C
#define Config5 0x2F
#define Power_Up_Reset 0x3A
#define Shutdown 0x3B
#define Inverse_Product_ID 0x3F
#define LiftCutoff_Tune3 0x41
#define Angle_Snap 0x42
#define LiftCutoff_Tune1 0x4A
#define Motion_Burst 0x50
#define LiftCutoff_Tune_Timeout 0x58
#define LiftCutoff_Tune_Min_Length 0x5A
#define SROM_Load_Burst 0x62
#define Lift_Config 0x63
#define Raw_Data_Burst 0x64
#define LiftCutoff_Tune2 0x65
#define PMW_CLOCK_SPEED 70000000
#define MIN_CPI 100
#define MAX_CPI 12000
#define CPI_STEP 100
#define CLAMP_CPI(value) value < MIN_CPI ? MIN_CPI : value > MAX_CPI ? MAX_CPI : value
#define SPI_MODE 3
#define SPI_DIVISOR (F_CPU / PMW_CLOCK_SPEED)
#define US_BETWEEN_WRITES 180
#define US_BETWEEN_READS 20
#define US_BEFORE_MOTION 35
#define MSB1 0x80
extern const uint16_t pmw_firmware_length;
extern const uint8_t pmw_firmware_data[];
void pmw_spi_start(void){
spi_start(SPI_SS_PIN, false, SPI_MODE, SPI_DIVISOR);
}
void pmw_write(uint8_t reg_addr, uint8_t data){
pmw_spi_start();
spi_write(reg_addr | MSB1 );
spi_write(data);
spi_stop();
wait_us(US_BETWEEN_WRITES);
}
uint8_t pmw_read(uint8_t reg_addr){
pmw_spi_start();
spi_write(reg_addr & 0x7f );
uint8_t data = spi_read();
spi_stop();
wait_us(US_BETWEEN_READS);
return data;
}
void pmw_init() {
setPinOutput(SPI_SS_PIN);
spi_init();
// reboot
pmw_write(Power_Up_Reset, 0x5a);
wait_ms(50);
// read registers and discard
pmw_read(Motion);
pmw_read(Delta_X_L);
pmw_read(Delta_X_H);
pmw_read(Delta_Y_L);
pmw_read(Delta_Y_H);
// upload firmware
// disable rest mode
pmw_write(Config2, 0x20);
// enable initialisation
pmw_write(SROM_Enable, 0x1d);
// wait a frame
wait_ms(10);
// start SROM download
pmw_write(SROM_Enable, 0x18);
// write the SROM file
pmw_spi_start();
spi_write(SROM_Load_Burst | 0x80);
wait_us(15);
// send all bytes of the firmware
unsigned char c;
for(int i = 0; i < pmw_firmware_length; i++){
c = (unsigned char)pgm_read_byte(pmw_firmware_data + i);
spi_write(c);
wait_us(15);
}
spi_stop();
wait_us(US_BETWEEN_WRITES);
// read id
pmw_read(SROM_ID);
// wired mouse
pmw_write(Config2, 0x00);
// first motion burst; write anything
pmw_write(Motion_Burst, 0xFF);
writePinLow(SPI_SS_PIN);
}
config_pmw_t pmw_get_config(void) {
uint8_t config_1 = pmw_read(Config1);
return (config_pmw_t){ (config_1 & 0xFF) * CPI_STEP };
}
void pmw_set_config(config_pmw_t config) {
uint8_t config_1 = (CLAMP_CPI(config.cpi) / CPI_STEP) & 0xFF;
pmw_write(Config1, config_1);
}
static int16_t convertDeltaToInt(uint8_t high, uint8_t low){
// join bytes into twos compliment
uint16_t twos_comp = (high << 8) | low;
// convert twos comp to int
if (twos_comp & 0x8000)
return -1 * (~twos_comp + 1);
return twos_comp;
}
report_pmw_t pmw_get_report(void) {
report_pmw_t report = {0, 0};
pmw_spi_start();
// start burst mode
spi_write(Motion_Burst & 0x7f);
wait_us(US_BEFORE_MOTION);
uint8_t motion = spi_read();
if(motion & 0x80) {
// clear observation register
spi_read();
// delta registers
uint8_t delta_x_l = spi_read();
uint8_t delta_x_h = spi_read();
uint8_t delta_y_l = spi_read();
uint8_t delta_y_h = spi_read();
report.x = convertDeltaToInt(delta_x_h, delta_x_l);
report.y = convertDeltaToInt(delta_y_h, delta_y_l);
}
spi_stop();
return report;
}

View file

@ -1,280 +0,0 @@
#pragma once
#include "progmem.h"
const uint16_t pmw_firmware_length = 4094;
const uint8_t pmw_firmware_data[] PROGMEM = {
0x01, 0x04, 0x8e, 0x96, 0x6e, 0x77, 0x3e, 0xfe, 0x7e, 0x5f, 0x1d, 0xb8, 0xf2, 0x66, 0x4e,
0xff, 0x5d, 0x19, 0xb0, 0xc2, 0x04, 0x69, 0x54, 0x2a, 0xd6, 0x2e, 0xbf, 0xdd, 0x19, 0xb0,
0xc3, 0xe5, 0x29, 0xb1, 0xe0, 0x23, 0xa5, 0xa9, 0xb1, 0xc1, 0x00, 0x82, 0x67, 0x4c, 0x1a,
0x97, 0x8d, 0x79, 0x51, 0x20, 0xc7, 0x06, 0x8e, 0x7c, 0x7c, 0x7a, 0x76, 0x4f, 0xfd, 0x59,
0x30, 0xe2, 0x46, 0x0e, 0x9e, 0xbe, 0xdf, 0x1d, 0x99, 0x91, 0xa0, 0xa5, 0xa1, 0xa9, 0xd0,
0x22, 0xc6, 0xef, 0x5c, 0x1b, 0x95, 0x89, 0x90, 0xa2, 0xa7, 0xcc, 0xfb, 0x55, 0x28, 0xb3,
0xe4, 0x4a, 0xf7, 0x6c, 0x3b, 0xf4, 0x6a, 0x56, 0x2e, 0xde, 0x1f, 0x9d, 0xb8, 0xd3, 0x05,
0x88, 0x92, 0xa6, 0xce, 0x1e, 0xbe, 0xdf, 0x1d, 0x99, 0xb0, 0xe2, 0x46, 0xef, 0x5c, 0x07,
0x11, 0x5d, 0x98, 0x0b, 0x9d, 0x94, 0x97, 0xee, 0x4e, 0x45, 0x33, 0x6b, 0x44, 0xc7, 0x29,
0x56, 0x27, 0x30, 0xc6, 0xa7, 0xd5, 0xf2, 0x56, 0xdf, 0xb4, 0x38, 0x62, 0xcb, 0xa0, 0xb6,
0xe3, 0x0f, 0x84, 0x06, 0x24, 0x05, 0x65, 0x6f, 0x76, 0x89, 0xb5, 0x77, 0x41, 0x27, 0x82,
0x66, 0x65, 0x82, 0xcc, 0xd5, 0xe6, 0x20, 0xd5, 0x27, 0x17, 0xc5, 0xf8, 0x03, 0x23, 0x7c,
0x5f, 0x64, 0xa5, 0x1d, 0xc1, 0xd6, 0x36, 0xcb, 0x4c, 0xd4, 0xdb, 0x66, 0xd7, 0x8b, 0xb1,
0x99, 0x7e, 0x6f, 0x4c, 0x36, 0x40, 0x06, 0xd6, 0xeb, 0xd7, 0xa2, 0xe4, 0xf4, 0x95, 0x51,
0x5a, 0x54, 0x96, 0xd5, 0x53, 0x44, 0xd7, 0x8c, 0xe0, 0xb9, 0x40, 0x68, 0xd2, 0x18, 0xe9,
0xdd, 0x9a, 0x23, 0x92, 0x48, 0xee, 0x7f, 0x43, 0xaf, 0xea, 0x77, 0x38, 0x84, 0x8c, 0x0a,
0x72, 0xaf, 0x69, 0xf8, 0xdd, 0xf1, 0x24, 0x83, 0xa3, 0xf8, 0x4a, 0xbf, 0xf5, 0x94, 0x13,
0xdb, 0xbb, 0xd8, 0xb4, 0xb3, 0xa0, 0xfb, 0x45, 0x50, 0x60, 0x30, 0x59, 0x12, 0x31, 0x71,
0xa2, 0xd3, 0x13, 0xe7, 0xfa, 0xe7, 0xce, 0x0f, 0x63, 0x15, 0x0b, 0x6b, 0x94, 0xbb, 0x37,
0x83, 0x26, 0x05, 0x9d, 0xfb, 0x46, 0x92, 0xfc, 0x0a, 0x15, 0xd1, 0x0d, 0x73, 0x92, 0xd6,
0x8c, 0x1b, 0x8c, 0xb8, 0x55, 0x8a, 0xce, 0xbd, 0xfe, 0x8e, 0xfc, 0xed, 0x09, 0x12, 0x83,
0x91, 0x82, 0x51, 0x31, 0x23, 0xfb, 0xb4, 0x0c, 0x76, 0xad, 0x7c, 0xd9, 0xb4, 0x4b, 0xb2,
0x67, 0x14, 0x09, 0x9c, 0x7f, 0x0c, 0x18, 0xba, 0x3b, 0xd6, 0x8e, 0x14, 0x2a, 0xe4, 0x1b,
0x52, 0x9f, 0x2b, 0x7d, 0xe1, 0xfb, 0x6a, 0x33, 0x02, 0xfa, 0xac, 0x5a, 0xf2, 0x3e, 0x88,
0x7e, 0xae, 0xd1, 0xf3, 0x78, 0xe8, 0x05, 0xd1, 0xe3, 0xdc, 0x21, 0xf6, 0xe1, 0x9a, 0xbd,
0x17, 0x0e, 0xd9, 0x46, 0x9b, 0x88, 0x03, 0xea, 0xf6, 0x66, 0xbe, 0x0e, 0x1b, 0x50, 0x49,
0x96, 0x40, 0x97, 0xf1, 0xf1, 0xe4, 0x80, 0xa6, 0x6e, 0xe8, 0x77, 0x34, 0xbf, 0x29, 0x40,
0x44, 0xc2, 0xff, 0x4e, 0x98, 0xd3, 0x9c, 0xa3, 0x32, 0x2b, 0x76, 0x51, 0x04, 0x09, 0xe7,
0xa9, 0xd1, 0xa6, 0x32, 0xb1, 0x23, 0x53, 0xe2, 0x47, 0xab, 0xd6, 0xf5, 0x69, 0x5c, 0x3e,
0x5f, 0xfa, 0xae, 0x45, 0x20, 0xe5, 0xd2, 0x44, 0xff, 0x39, 0x32, 0x6d, 0xfd, 0x27, 0x57,
0x5c, 0xfd, 0xf0, 0xde, 0xc1, 0xb5, 0x99, 0xe5, 0xf5, 0x1c, 0x77, 0x01, 0x75, 0xc5, 0x6d,
0x58, 0x92, 0xf2, 0xb2, 0x47, 0x00, 0x01, 0x26, 0x96, 0x7a, 0x30, 0xff, 0xb7, 0xf0, 0xef,
0x77, 0xc1, 0x8a, 0x5d, 0xdc, 0xc0, 0xd1, 0x29, 0x30, 0x1e, 0x77, 0x38, 0x7a, 0x94, 0xf1,
0xb8, 0x7a, 0x7e, 0xef, 0xa4, 0xd1, 0xac, 0x31, 0x4a, 0xf2, 0x5d, 0x64, 0x3d, 0xb2, 0xe2,
0xf0, 0x08, 0x99, 0xfc, 0x70, 0xee, 0x24, 0xa7, 0x7e, 0xee, 0x1e, 0x20, 0x69, 0x7d, 0x44,
0xbf, 0x87, 0x42, 0xdf, 0x88, 0x3b, 0x0c, 0xda, 0x42, 0xc9, 0x04, 0xf9, 0x45, 0x50, 0xfc,
0x83, 0x8f, 0x11, 0x6a, 0x72, 0xbc, 0x99, 0x95, 0xf0, 0xac, 0x3d, 0xa7, 0x3b, 0xcd, 0x1c,
0xe2, 0x88, 0x79, 0x37, 0x11, 0x5f, 0x39, 0x89, 0x95, 0x0a, 0x16, 0x84, 0x7a, 0xf6, 0x8a,
0xa4, 0x28, 0xe4, 0xed, 0x83, 0x80, 0x3b, 0xb1, 0x23, 0xa5, 0x03, 0x10, 0xf4, 0x66, 0xea,
0xbb, 0x0c, 0x0f, 0xc5, 0xec, 0x6c, 0x69, 0xc5, 0xd3, 0x24, 0xab, 0xd4, 0x2a, 0xb7, 0x99,
0x88, 0x76, 0x08, 0xa0, 0xa8, 0x95, 0x7c, 0xd8, 0x38, 0x6d, 0xcd, 0x59, 0x02, 0x51, 0x4b,
0xf1, 0xb5, 0x2b, 0x50, 0xe3, 0xb6, 0xbd, 0xd0, 0x72, 0xcf, 0x9e, 0xfd, 0x6e, 0xbb, 0x44,
0xc8, 0x24, 0x8a, 0x77, 0x18, 0x8a, 0x13, 0x06, 0xef, 0x97, 0x7d, 0xfa, 0x81, 0xf0, 0x31,
0xe6, 0xfa, 0x77, 0xed, 0x31, 0x06, 0x31, 0x5b, 0x54, 0x8a, 0x9f, 0x30, 0x68, 0xdb, 0xe2,
0x40, 0xf8, 0x4e, 0x73, 0xfa, 0xab, 0x74, 0x8b, 0x10, 0x58, 0x13, 0xdc, 0xd2, 0xe6, 0x78,
0xd1, 0x32, 0x2e, 0x8a, 0x9f, 0x2c, 0x58, 0x06, 0x48, 0x27, 0xc5, 0xa9, 0x5e, 0x81, 0x47,
0x89, 0x46, 0x21, 0x91, 0x03, 0x70, 0xa4, 0x3e, 0x88, 0x9c, 0xda, 0x33, 0x0a, 0xce, 0xbc,
0x8b, 0x8e, 0xcf, 0x9f, 0xd3, 0x71, 0x80, 0x43, 0xcf, 0x6b, 0xa9, 0x51, 0x83, 0x76, 0x30,
0x82, 0xc5, 0x6a, 0x85, 0x39, 0x11, 0x50, 0x1a, 0x82, 0xdc, 0x1e, 0x1c, 0xd5, 0x7d, 0xa9,
0x71, 0x99, 0x33, 0x47, 0x19, 0x97, 0xb3, 0x5a, 0xb1, 0xdf, 0xed, 0xa4, 0xf2, 0xe6, 0x26,
0x84, 0xa2, 0x28, 0x9a, 0x9e, 0xdf, 0xa6, 0x6a, 0xf4, 0xd6, 0xfc, 0x2e, 0x5b, 0x9d, 0x1a,
0x2a, 0x27, 0x68, 0xfb, 0xc1, 0x83, 0x21, 0x4b, 0x90, 0xe0, 0x36, 0xdd, 0x5b, 0x31, 0x42,
0x55, 0xa0, 0x13, 0xf7, 0xd0, 0x89, 0x53, 0x71, 0x99, 0x57, 0x09, 0x29, 0xc5, 0xf3, 0x21,
0xf8, 0x37, 0x2f, 0x40, 0xf3, 0xd4, 0xaf, 0x16, 0x08, 0x36, 0x02, 0xfc, 0x77, 0xc5, 0x8b,
0x04, 0x90, 0x56, 0xb9, 0xc9, 0x67, 0x9a, 0x99, 0xe8, 0x00, 0xd3, 0x86, 0xff, 0x97, 0x2d,
0x08, 0xe9, 0xb7, 0xb3, 0x91, 0xbc, 0xdf, 0x45, 0xc6, 0xed, 0x0f, 0x8c, 0x4c, 0x1e, 0xe6,
0x5b, 0x6e, 0x38, 0x30, 0xe4, 0xaa, 0xe3, 0x95, 0xde, 0xb9, 0xe4, 0x9a, 0xf5, 0xb2, 0x55,
0x9a, 0x87, 0x9b, 0xf6, 0x6a, 0xb2, 0xf2, 0x77, 0x9a, 0x31, 0xf4, 0x7a, 0x31, 0xd1, 0x1d,
0x04, 0xc0, 0x7c, 0x32, 0xa2, 0x9e, 0x9a, 0xf5, 0x62, 0xf8, 0x27, 0x8d, 0xbf, 0x51, 0xff,
0xd3, 0xdf, 0x64, 0x37, 0x3f, 0x2a, 0x6f, 0x76, 0x3a, 0x7d, 0x77, 0x06, 0x9e, 0x77, 0x7f,
0x5e, 0xeb, 0x32, 0x51, 0xf9, 0x16, 0x66, 0x9a, 0x09, 0xf3, 0xb0, 0x08, 0xa4, 0x70, 0x96,
0x46, 0x30, 0xff, 0xda, 0x4f, 0xe9, 0x1b, 0xed, 0x8d, 0xf8, 0x74, 0x1f, 0x31, 0x92, 0xb3,
0x73, 0x17, 0x36, 0xdb, 0x91, 0x30, 0xd6, 0x88, 0x55, 0x6b, 0x34, 0x77, 0x87, 0x7a, 0xe7,
0xee, 0x06, 0xc6, 0x1c, 0x8c, 0x19, 0x0c, 0x48, 0x46, 0x23, 0x5e, 0x9c, 0x07, 0x5c, 0xbf,
0xb4, 0x7e, 0xd6, 0x4f, 0x74, 0x9c, 0xe2, 0xc5, 0x50, 0x8b, 0xc5, 0x8b, 0x15, 0x90, 0x60,
0x62, 0x57, 0x29, 0xd0, 0x13, 0x43, 0xa1, 0x80, 0x88, 0x91, 0x00, 0x44, 0xc7, 0x4d, 0x19,
0x86, 0xcc, 0x2f, 0x2a, 0x75, 0x5a, 0xfc, 0xeb, 0x97, 0x2a, 0x70, 0xe3, 0x78, 0xd8, 0x91,
0xb0, 0x4f, 0x99, 0x07, 0xa3, 0x95, 0xea, 0x24, 0x21, 0xd5, 0xde, 0x51, 0x20, 0x93, 0x27,
0x0a, 0x30, 0x73, 0xa8, 0xff, 0x8a, 0x97, 0xe9, 0xa7, 0x6a, 0x8e, 0x0d, 0xe8, 0xf0, 0xdf,
0xec, 0xea, 0xb4, 0x6c, 0x1d, 0x39, 0x2a, 0x62, 0x2d, 0x3d, 0x5a, 0x8b, 0x65, 0xf8, 0x90,
0x05, 0x2e, 0x7e, 0x91, 0x2c, 0x78, 0xef, 0x8e, 0x7a, 0xc1, 0x2f, 0xac, 0x78, 0xee, 0xaf,
0x28, 0x45, 0x06, 0x4c, 0x26, 0xaf, 0x3b, 0xa2, 0xdb, 0xa3, 0x93, 0x06, 0xb5, 0x3c, 0xa5,
0xd8, 0xee, 0x8f, 0xaf, 0x25, 0xcc, 0x3f, 0x85, 0x68, 0x48, 0xa9, 0x62, 0xcc, 0x97, 0x8f,
0x7f, 0x2a, 0xea, 0xe0, 0x15, 0x0a, 0xad, 0x62, 0x07, 0xbd, 0x45, 0xf8, 0x41, 0xd8, 0x36,
0xcb, 0x4c, 0xdb, 0x6e, 0xe6, 0x3a, 0xe7, 0xda, 0x15, 0xe9, 0x29, 0x1e, 0x12, 0x10, 0xa0,
0x14, 0x2c, 0x0e, 0x3d, 0xf4, 0xbf, 0x39, 0x41, 0x92, 0x75, 0x0b, 0x25, 0x7b, 0xa3, 0xce,
0x39, 0x9c, 0x15, 0x64, 0xc8, 0xfa, 0x3d, 0xef, 0x73, 0x27, 0xfe, 0x26, 0x2e, 0xce, 0xda,
0x6e, 0xfd, 0x71, 0x8e, 0xdd, 0xfe, 0x76, 0xee, 0xdc, 0x12, 0x5c, 0x02, 0xc5, 0x3a, 0x4e,
0x4e, 0x4f, 0xbf, 0xca, 0x40, 0x15, 0xc7, 0x6e, 0x8d, 0x41, 0xf1, 0x10, 0xe0, 0x4f, 0x7e,
0x97, 0x7f, 0x1c, 0xae, 0x47, 0x8e, 0x6b, 0xb1, 0x25, 0x31, 0xb0, 0x73, 0xc7, 0x1b, 0x97,
0x79, 0xf9, 0x80, 0xd3, 0x66, 0x22, 0x30, 0x07, 0x74, 0x1e, 0xe4, 0xd0, 0x80, 0x21, 0xd6,
0xee, 0x6b, 0x6c, 0x4f, 0xbf, 0xf5, 0xb7, 0xd9, 0x09, 0x87, 0x2f, 0xa9, 0x14, 0xbe, 0x27,
0xd9, 0x72, 0x50, 0x01, 0xd4, 0x13, 0x73, 0xa6, 0xa7, 0x51, 0x02, 0x75, 0x25, 0xe1, 0xb3,
0x45, 0x34, 0x7d, 0xa8, 0x8e, 0xeb, 0xf3, 0x16, 0x49, 0xcb, 0x4f, 0x8c, 0xa1, 0xb9, 0x36,
0x85, 0x39, 0x75, 0x5d, 0x08, 0x00, 0xae, 0xeb, 0xf6, 0xea, 0xd7, 0x13, 0x3a, 0x21, 0x5a,
0x5f, 0x30, 0x84, 0x52, 0x26, 0x95, 0xc9, 0x14, 0xf2, 0x57, 0x55, 0x6b, 0xb1, 0x10, 0xc2,
0xe1, 0xbd, 0x3b, 0x51, 0xc0, 0xb7, 0x55, 0x4c, 0x71, 0x12, 0x26, 0xc7, 0x0d, 0xf9, 0x51,
0xa4, 0x38, 0x02, 0x05, 0x7f, 0xb8, 0xf1, 0x72, 0x4b, 0xbf, 0x71, 0x89, 0x14, 0xf3, 0x77,
0x38, 0xd9, 0x71, 0x24, 0xf3, 0x00, 0x11, 0xa1, 0xd8, 0xd4, 0x69, 0x27, 0x08, 0x37, 0x35,
0xc9, 0x11, 0x9d, 0x90, 0x1c, 0x0e, 0xe7, 0x1c, 0xff, 0x2d, 0x1e, 0xe8, 0x92, 0xe1, 0x18,
0x10, 0x95, 0x7c, 0xe0, 0x80, 0xf4, 0x96, 0x43, 0x21, 0xf9, 0x75, 0x21, 0x64, 0x38, 0xdd,
0x9f, 0x1e, 0x95, 0x16, 0xda, 0x56, 0x1d, 0x4f, 0x9a, 0x53, 0xb2, 0xe2, 0xe4, 0x18, 0xcb,
0x6b, 0x1a, 0x65, 0xeb, 0x56, 0xc6, 0x3b, 0xe5, 0xfe, 0xd8, 0x26, 0x3f, 0x3a, 0x84, 0x59,
0x72, 0x66, 0xa2, 0xf3, 0x75, 0xff, 0xfb, 0x60, 0xb3, 0x22, 0xad, 0x3f, 0x2d, 0x6b, 0xf9,
0xeb, 0xea, 0x05, 0x7c, 0xd8, 0x8f, 0x6d, 0x2c, 0x98, 0x9e, 0x2b, 0x93, 0xf1, 0x5e, 0x46,
0xf0, 0x87, 0x49, 0x29, 0x73, 0x68, 0xd7, 0x7f, 0xf9, 0xf0, 0xe5, 0x7d, 0xdb, 0x1d, 0x75,
0x19, 0xf3, 0xc4, 0x58, 0x9b, 0x17, 0x88, 0xa8, 0x92, 0xe0, 0xbe, 0xbd, 0x8b, 0x1d, 0x8d,
0x9f, 0x56, 0x76, 0xad, 0xaf, 0x29, 0xe2, 0xd9, 0xd5, 0x52, 0xf6, 0xb5, 0x56, 0x35, 0x57,
0x3a, 0xc8, 0xe1, 0x56, 0x43, 0x19, 0x94, 0xd3, 0x04, 0x9b, 0x6d, 0x35, 0xd8, 0x0b, 0x5f,
0x4d, 0x19, 0x8e, 0xec, 0xfa, 0x64, 0x91, 0x0a, 0x72, 0x20, 0x2b, 0xbc, 0x1a, 0x4a, 0xfe,
0x8b, 0xfd, 0xbb, 0xed, 0x1b, 0x23, 0xea, 0xad, 0x72, 0x82, 0xa1, 0x29, 0x99, 0x71, 0xbd,
0xf0, 0x95, 0xc1, 0x03, 0xdd, 0x7b, 0xc2, 0xb2, 0x3c, 0x28, 0x54, 0xd3, 0x68, 0xa4, 0x72,
0xc8, 0x66, 0x96, 0xe0, 0xd1, 0xd8, 0x7f, 0xf8, 0xd1, 0x26, 0x2b, 0xf7, 0xad, 0xba, 0x55,
0xca, 0x15, 0xb9, 0x32, 0xc3, 0xe5, 0x88, 0x97, 0x8e, 0x5c, 0xfb, 0x92, 0x25, 0x8b, 0xbf,
0xa2, 0x45, 0x55, 0x7a, 0xa7, 0x6f, 0x8b, 0x57, 0x5b, 0xcf, 0x0e, 0xcb, 0x1d, 0xfb, 0x20,
0x82, 0x77, 0xa8, 0x8c, 0xcc, 0x16, 0xce, 0x1d, 0xfa, 0xde, 0xcc, 0x0b, 0x62, 0xfe, 0xcc,
0xe1, 0xb7, 0xf0, 0xc3, 0x81, 0x64, 0x73, 0x40, 0xa0, 0xc2, 0x4d, 0x89, 0x11, 0x75, 0x33,
0x55, 0x33, 0x8d, 0xe8, 0x4a, 0xfd, 0xea, 0x6e, 0x30, 0x0b, 0xd7, 0x31, 0x2c, 0xde, 0x47,
0xe3, 0xbf, 0xf8, 0x55, 0x42, 0xe2, 0x7f, 0x59, 0xe5, 0x17, 0xef, 0x99, 0x34, 0x69, 0x91,
0xb1, 0x23, 0x8e, 0x20, 0x87, 0x2d, 0xa8, 0xfe, 0xd5, 0x8a, 0xf3, 0x84, 0x3a, 0xf0, 0x37,
0xe4, 0x09, 0x00, 0x54, 0xee, 0x67, 0x49, 0x93, 0xe4, 0x81, 0x70, 0xe3, 0x90, 0x4d, 0xef,
0xfe, 0x41, 0xb7, 0x99, 0x7b, 0xc1, 0x83, 0xba, 0x62, 0x12, 0x6f, 0x7d, 0xde, 0x6b, 0xaf,
0xda, 0x16, 0xf9, 0x55, 0x51, 0xee, 0xa6, 0x0c, 0x2b, 0x02, 0xa3, 0xfd, 0x8d, 0xfb, 0x30,
0x17, 0xe4, 0x6f, 0xdf, 0x36, 0x71, 0xc4, 0xca, 0x87, 0x25, 0x48, 0xb0, 0x47, 0xec, 0xea,
0xb4, 0xbf, 0xa5, 0x4d, 0x9b, 0x9f, 0x02, 0x93, 0xc4, 0xe3, 0xe4, 0xe8, 0x42, 0x2d, 0x68,
0x81, 0x15, 0x0a, 0xeb, 0x84, 0x5b, 0xd6, 0xa8, 0x74, 0xfb, 0x7d, 0x1d, 0xcb, 0x2c, 0xda,
0x46, 0x2a, 0x76, 0x62, 0xce, 0xbc, 0x5c, 0x9e, 0x8b, 0xe7, 0xcf, 0xbe, 0x78, 0xf5, 0x7c,
0xeb, 0xb3, 0x3a, 0x9c, 0xaa, 0x6f, 0xcc, 0x72, 0xd1, 0x59, 0xf2, 0x11, 0x23, 0xd6, 0x3f,
0x48, 0xd1, 0xb7, 0xce, 0xb0, 0xbf, 0xcb, 0xea, 0x80, 0xde, 0x57, 0xd4, 0x5e, 0x97, 0x2f,
0x75, 0xd1, 0x50, 0x8e, 0x80, 0x2c, 0x66, 0x79, 0xbf, 0x72, 0x4b, 0xbd, 0x8a, 0x81, 0x6c,
0xd3, 0xe1, 0x01, 0xdc, 0xd2, 0x15, 0x26, 0xc5, 0x36, 0xda, 0x2c, 0x1a, 0xc0, 0x27, 0x94,
0xed, 0xb7, 0x9b, 0x85, 0x0b, 0x5e, 0x80, 0x97, 0xc5, 0xec, 0x4f, 0xec, 0x88, 0x5d, 0x50,
0x07, 0x35, 0x47, 0xdc, 0x0b, 0x3b, 0x3d, 0xdd, 0x60, 0xaf, 0xa8, 0x5d, 0x81, 0x38, 0x24,
0x25, 0x5d, 0x5c, 0x15, 0xd1, 0xde, 0xb3, 0xab, 0xec, 0x05, 0x69, 0xef, 0x83, 0xed, 0x57,
0x54, 0xb8, 0x64, 0x64, 0x11, 0x16, 0x32, 0x69, 0xda, 0x9f, 0x2d, 0x7f, 0x36, 0xbb, 0x44,
0x5a, 0x34, 0xe8, 0x7f, 0xbf, 0x03, 0xeb, 0x00, 0x7f, 0x59, 0x68, 0x22, 0x79, 0xcf, 0x73,
0x6c, 0x2c, 0x29, 0xa7, 0xa1, 0x5f, 0x38, 0xa1, 0x1d, 0xf0, 0x20, 0x53, 0xe0, 0x1a, 0x63,
0x14, 0x58, 0x71, 0x10, 0xaa, 0x08, 0x0c, 0x3e, 0x16, 0x1a, 0x60, 0x22, 0x82, 0x7f, 0xba,
0xa4, 0x43, 0xa0, 0xd0, 0xac, 0x1b, 0xd5, 0x6b, 0x64, 0xb5, 0x14, 0x93, 0x31, 0x9e, 0x53,
0x50, 0xd0, 0x57, 0x66, 0xee, 0x5a, 0x4f, 0xfb, 0x03, 0x2a, 0x69, 0x58, 0x76, 0xf1, 0x83,
0xf7, 0x4e, 0xba, 0x8c, 0x42, 0x06, 0x60, 0x5d, 0x6d, 0xce, 0x60, 0x88, 0xae, 0xa4, 0xc3,
0xf1, 0x03, 0xa5, 0x4b, 0x98, 0xa1, 0xff, 0x67, 0xe1, 0xac, 0xa2, 0xb8, 0x62, 0xd7, 0x6f,
0xa0, 0x31, 0xb4, 0xd2, 0x77, 0xaf, 0x21, 0x10, 0x06, 0xc6, 0x9a, 0xff, 0x1d, 0x09, 0x17,
0x0e, 0x5f, 0xf1, 0xaa, 0x54, 0x34, 0x4b, 0x45, 0x8a, 0x87, 0x63, 0xa6, 0xdc, 0xf9, 0x24,
0x30, 0x67, 0xc6, 0xb2, 0xd6, 0x61, 0x33, 0x69, 0xee, 0x50, 0x61, 0x57, 0x28, 0xe7, 0x7e,
0xee, 0xec, 0x3a, 0x5a, 0x73, 0x4e, 0xa8, 0x8d, 0xe4, 0x18, 0xea, 0xec, 0x41, 0x64, 0xc8,
0xe2, 0xe8, 0x66, 0xb6, 0x2d, 0xb6, 0xfb, 0x6a, 0x6c, 0x16, 0xb3, 0xdd, 0x46, 0x43, 0xb9,
0x73, 0x00, 0x6a, 0x71, 0xed, 0x4e, 0x9d, 0x25, 0x1a, 0xc3, 0x3c, 0x4a, 0x95, 0x15, 0x99,
0x35, 0x81, 0x14, 0x02, 0xd6, 0x98, 0x9b, 0xec, 0xd8, 0x23, 0x3b, 0x84, 0x29, 0xaf, 0x0c,
0x99, 0x83, 0xa6, 0x9a, 0x34, 0x4f, 0xfa, 0xe8, 0xd0, 0x3c, 0x4b, 0xd0, 0xfb, 0xb6, 0x68,
0xb8, 0x9e, 0x8f, 0xcd, 0xf7, 0x60, 0x2d, 0x7a, 0x22, 0xe5, 0x7d, 0xab, 0x65, 0x1b, 0x95,
0xa7, 0xa8, 0x7f, 0xb6, 0x77, 0x47, 0x7b, 0x5f, 0x8b, 0x12, 0x72, 0xd0, 0xd4, 0x91, 0xef,
0xde, 0x19, 0x50, 0x3c, 0xa7, 0x8b, 0xc4, 0xa9, 0xb3, 0x23, 0xcb, 0x76, 0xe6, 0x81, 0xf0,
0xc1, 0x04, 0x8f, 0xa3, 0xb8, 0x54, 0x5b, 0x97, 0xac, 0x19, 0xff, 0x3f, 0x55, 0x27, 0x2f,
0xe0, 0x1d, 0x42, 0x9b, 0x57, 0xfc, 0x4b, 0x4e, 0x0f, 0xce, 0x98, 0xa9, 0x43, 0x57, 0x03,
0xbd, 0xe7, 0xc8, 0x94, 0xdf, 0x6e, 0x36, 0x73, 0x32, 0xb4, 0xef, 0x2e, 0x85, 0x7a, 0x6e,
0xfc, 0x6c, 0x18, 0x82, 0x75, 0x35, 0x90, 0x07, 0xf3, 0xe4, 0x9f, 0x3e, 0xdc, 0x68, 0xf3,
0xb5, 0xf3, 0x19, 0x80, 0x92, 0x06, 0x99, 0xa2, 0xe8, 0x6f, 0xff, 0x2e, 0x7f, 0xae, 0x42,
0xa4, 0x5f, 0xfb, 0xd4, 0x0e, 0x81, 0x2b, 0xc3, 0x04, 0xff, 0x2b, 0xb3, 0x74, 0x4e, 0x36,
0x5b, 0x9c, 0x15, 0x00, 0xc6, 0x47, 0x2b, 0xe8, 0x8b, 0x3d, 0xf1, 0x9c, 0x03, 0x9a, 0x58,
0x7f, 0x9b, 0x9c, 0xbf, 0x85, 0x49, 0x79, 0x35, 0x2e, 0x56, 0x7b, 0x41, 0x14, 0x39, 0x47,
0x83, 0x26, 0xaa, 0x07, 0x89, 0x98, 0x11, 0x1b, 0x86, 0xe7, 0x73, 0x7a, 0xd8, 0x7d, 0x78,
0x61, 0x53, 0xe9, 0x79, 0xf5, 0x36, 0x8d, 0x44, 0x92, 0x84, 0xf9, 0x13, 0x50, 0x58, 0x3b,
0xa4, 0x6a, 0x36, 0x65, 0x49, 0x8e, 0x3c, 0x0e, 0xf1, 0x6f, 0xd2, 0x84, 0xc4, 0x7e, 0x8e,
0x3f, 0x39, 0xae, 0x7c, 0x84, 0xf1, 0x63, 0x37, 0x8e, 0x3c, 0xcc, 0x3e, 0x44, 0x81, 0x45,
0xf1, 0x4b, 0xb9, 0xed, 0x6b, 0x36, 0x5d, 0xbb, 0x20, 0x60, 0x1a, 0x0f, 0xa3, 0xaa, 0x55,
0x77, 0x3a, 0xa9, 0xae, 0x37, 0x4d, 0xba, 0xb8, 0x86, 0x6b, 0xbc, 0x08, 0x50, 0xf6, 0xcc,
0xa4, 0xbd, 0x1d, 0x40, 0x72, 0xa5, 0x86, 0xfa, 0xe2, 0x10, 0xae, 0x3d, 0x58, 0x4b, 0x97,
0xf3, 0x43, 0x74, 0xa9, 0x9e, 0xeb, 0x21, 0xb7, 0x01, 0xa4, 0x86, 0x93, 0x97, 0xee, 0x2f,
0x4f, 0x3b, 0x86, 0xa1, 0x41, 0x6f, 0x41, 0x26, 0x90, 0x78, 0x5c, 0x7f, 0x30, 0x38, 0x4b,
0x3f, 0xaa, 0xec, 0xed, 0x5c, 0x6f, 0x0e, 0xad, 0x43, 0x87, 0xfd, 0x93, 0x35, 0xe6, 0x01,
0xef, 0x41, 0x26, 0x90, 0x99, 0x9e, 0xfb, 0x19, 0x5b, 0xad, 0xd2, 0x91, 0x8a, 0xe0, 0x46,
0xaf, 0x65, 0xfa, 0x4f, 0x84, 0xc1, 0xa1, 0x2d, 0xcf, 0x45, 0x8b, 0xd3, 0x85, 0x50, 0x55,
0x7c, 0xf9, 0x67, 0x88, 0xd4, 0x4e, 0xe9, 0xd7, 0x6b, 0x61, 0x54, 0xa1, 0xa4, 0xa6, 0xa2,
0xc2, 0xbf, 0x30, 0x9c, 0x40, 0x9f, 0x5f, 0xd7, 0x69, 0x2b, 0x24, 0x82, 0x5e, 0xd9, 0xd6,
0xa7, 0x12, 0x54, 0x1a, 0xf7, 0x55, 0x9f, 0x76, 0x50, 0xa9, 0x95, 0x84, 0xe6, 0x6b, 0x6d,
0xb5, 0x96, 0x54, 0xd6, 0xcd, 0xb3, 0xa1, 0x9b, 0x46, 0xa7, 0x94, 0x4d, 0xc4, 0x94, 0xb4,
0x98, 0xe3, 0xe1, 0xe2, 0x34, 0xd5, 0x33, 0x16, 0x07, 0x54, 0xcd, 0xb7, 0x77, 0x53, 0xdb,
0x4f, 0x4d, 0x46, 0x9d, 0xe9, 0xd4, 0x9c, 0x8a, 0x36, 0xb6, 0xb8, 0x38, 0x26, 0x6c, 0x0e,
0xff, 0x9c, 0x1b, 0x43, 0x8b, 0x80, 0xcc, 0xb9, 0x3d, 0xda, 0xc7, 0xf1, 0x8a, 0xf2, 0x6d,
0xb8, 0xd7, 0x74, 0x2f, 0x7e, 0x1e, 0xb7, 0xd3, 0x4a, 0xb4, 0xac, 0xfc, 0x79, 0x48, 0x6c,
0xbc, 0x96, 0xb6, 0x94, 0x46, 0x57, 0x2d, 0xb0, 0xa3, 0xfc, 0x1e, 0xb9, 0x52, 0x60, 0x85,
0x2d, 0x41, 0xd0, 0x43, 0x01, 0x1e, 0x1c, 0xd5, 0x7d, 0xfc, 0xf3, 0x96, 0x0d, 0xc7, 0xcb,
0x2a, 0x29, 0x9a, 0x93, 0xdd, 0x88, 0x2d, 0x37, 0x5d, 0xaa, 0xfb, 0x49, 0x68, 0xa0, 0x9c,
0x50, 0x86, 0x7f, 0x68, 0x56, 0x57, 0xf9, 0x79, 0x18, 0x39, 0xd4, 0xe0, 0x01, 0x84, 0x33,
0x61, 0xca, 0xa5, 0xd2, 0xd6, 0xe4, 0xc9, 0x8a, 0x4a, 0x23, 0x44, 0x4e, 0xbc, 0xf0, 0xdc,
0x24, 0xa1, 0xa0, 0xc4, 0xe2, 0x07, 0x3c, 0x10, 0xc4, 0xb5, 0x25, 0x4b, 0x65, 0x63, 0xf4,
0x80, 0xe7, 0xcf, 0x61, 0xb1, 0x71, 0x82, 0x21, 0x87, 0x2c, 0xf5, 0x91, 0x00, 0x32, 0x0c,
0xec, 0xa9, 0xb5, 0x9a, 0x74, 0x85, 0xe3, 0x36, 0x8f, 0x76, 0x4f, 0x9c, 0x6d, 0xce, 0xbc,
0xad, 0x0a, 0x4b, 0xed, 0x76, 0x04, 0xcb, 0xc3, 0xb9, 0x33, 0x9e, 0x01, 0x93, 0x96, 0x69,
0x7d, 0xc5, 0xa2, 0x45, 0x79, 0x9b, 0x04, 0x5c, 0x84, 0x09, 0xed, 0x88, 0x43, 0xc7, 0xab,
0x93, 0x14, 0x26, 0xa1, 0x40, 0xb5, 0xce, 0x4e, 0xbf, 0x2a, 0x42, 0x85, 0x3e, 0x2c, 0x3b,
0x54, 0xe8, 0x12, 0x1f, 0x0e, 0x97, 0x59, 0xb2, 0x27, 0x89, 0xfa, 0xf2, 0xdf, 0x8e, 0x68,
0x59, 0xdc, 0x06, 0xbc, 0xb6, 0x85, 0x0d, 0x06, 0x22, 0xec, 0xb1, 0xcb, 0xe5, 0x04, 0xe6,
0x3d, 0xb3, 0xb0, 0x41, 0x73, 0x08, 0x3f, 0x3c, 0x58, 0x86, 0x63, 0xeb, 0x50, 0xee, 0x1d,
0x2c, 0x37, 0x74, 0xa9, 0xd3, 0x18, 0xa3, 0x47, 0x6e, 0x93, 0x54, 0xad, 0x0a, 0x5d, 0xb8,
0x2a, 0x55, 0x5d, 0x78, 0xf6, 0xee, 0xbe, 0x8e, 0x3c, 0x76, 0x69, 0xb9, 0x40, 0xc2, 0x34,
0xec, 0x2a, 0xb9, 0xed, 0x7e, 0x20, 0xe4, 0x8d, 0x00, 0x38, 0xc7, 0xe6, 0x8f, 0x44, 0xa8,
0x86, 0xce, 0xeb, 0x2a, 0xe9, 0x90, 0xf1, 0x4c, 0xdf, 0x32, 0xfb, 0x73, 0x1b, 0x6d, 0x92,
0x1e, 0x95, 0xfe, 0xb4, 0xdb, 0x65, 0xdf, 0x4d, 0x23, 0x54, 0x89, 0x48, 0xbf, 0x4a, 0x2e,
0x70, 0xd6, 0xd7, 0x62, 0xb4, 0x33, 0x29, 0xb1, 0x3a, 0x33, 0x4c, 0x23, 0x6d, 0xa6, 0x76,
0xa5, 0x21, 0x63, 0x48, 0xe6, 0x90, 0x5d, 0xed, 0x90, 0x95, 0x0b, 0x7a, 0x84, 0xbe, 0xb8,
0x0d, 0x5e, 0x63, 0x0c, 0x62, 0x26, 0x4c, 0x14, 0x5a, 0xb3, 0xac, 0x23, 0xa4, 0x74, 0xa7,
0x6f, 0x33, 0x30, 0x05, 0x60, 0x01, 0x42, 0xa0, 0x28, 0xb7, 0xee, 0x19, 0x38, 0xf1, 0x64,
0x80, 0x82, 0x43, 0xe1, 0x41, 0x27, 0x1f, 0x1f, 0x90, 0x54, 0x7a, 0xd5, 0x23, 0x2e, 0xd1,
0x3d, 0xcb, 0x28, 0xba, 0x58, 0x7f, 0xdc, 0x7c, 0x91, 0x24, 0xe9, 0x28, 0x51, 0x83, 0x6e,
0xc5, 0x56, 0x21, 0x42, 0xed, 0xa0, 0x56, 0x22, 0xa1, 0x40, 0x80, 0x6b, 0xa8, 0xf7, 0x94,
0xca, 0x13, 0x6b, 0x0c, 0x39, 0xd9, 0xfd, 0xe9, 0xf3, 0x6f, 0xa6, 0x9e, 0xfc, 0x70, 0x8a,
0xb3, 0xbc, 0x59, 0x3c, 0x1e, 0x1d, 0x6c, 0xf9, 0x7c, 0xaf, 0xf9, 0x88, 0x71, 0x95, 0xeb,
0x57, 0x00, 0xbd, 0x9f, 0x8c, 0x4f, 0xe1, 0x24, 0x83, 0xc5, 0x22, 0xea, 0xfd, 0xd3, 0x0c,
0xe2, 0x17, 0x18, 0x7c, 0x6a, 0x4c, 0xde, 0x77, 0xb4, 0x53, 0x9b, 0x4c, 0x81, 0xcd, 0x23,
0x60, 0xaa, 0x0e, 0x25, 0x73, 0x9c, 0x02, 0x79, 0x32, 0x30, 0xdf, 0x74, 0xdf, 0x75, 0x19,
0xf4, 0xa5, 0x14, 0x5c, 0xf7, 0x7a, 0xa8, 0xa5, 0x91, 0x84, 0x7c, 0x60, 0x03, 0x06, 0x3b,
0xcd, 0x50, 0xb6, 0x27, 0x9c, 0xfe, 0xb1, 0xdd, 0xcc, 0xd3, 0xb0, 0x59, 0x24, 0xb2, 0xca,
0xe2, 0x1c, 0x81, 0x22, 0x9d, 0x07, 0x8f, 0x8e, 0xb9, 0xbe, 0x4e, 0xfa, 0xfc, 0x39, 0x65,
0xba, 0xbf, 0x9d, 0x12, 0x37, 0x5e, 0x97, 0x7e, 0xf3, 0x89, 0xf5, 0x5d, 0xf5, 0xe3, 0x09,
0x8c, 0x62, 0xb5, 0x20, 0x9d, 0x0c, 0x53, 0x8a, 0x68, 0x1b, 0xd2, 0x8f, 0x75, 0x17, 0x5d,
0xd4, 0xe5, 0xda, 0x75, 0x62, 0x19, 0x14, 0x6a, 0x26, 0x2d, 0xeb, 0xf8, 0xaf, 0x37, 0xf0,
0x6c, 0xa4, 0x55, 0xb1, 0xbc, 0xe2, 0x33, 0xc0, 0x9a, 0xca, 0xb0, 0x11, 0x49, 0x4f, 0x68,
0x9b, 0x3b, 0x6b, 0x3c, 0xcc, 0x13, 0xf6, 0xc7, 0x85, 0x61, 0x68, 0x42, 0xae, 0xbb, 0xdd,
0xcd, 0x45, 0x16, 0x29, 0x1d, 0xea, 0xdb, 0xc8, 0x03, 0x94, 0x3c, 0xee, 0x4f, 0x82, 0x11,
0xc3, 0xec, 0x28, 0xbd, 0x97, 0x05, 0x99, 0xde, 0xd7, 0xbb, 0x5e, 0x22, 0x1f, 0xd4, 0xeb,
0x64, 0xd9, 0x92, 0xd9, 0x85, 0xb7, 0x6a, 0x05, 0x6a, 0xe4, 0x24, 0x41, 0xf1, 0xcd, 0xf0,
0xd8, 0x3f, 0xf8, 0x9e, 0x0e, 0xcd, 0x0b, 0x7a, 0x70, 0x6b, 0x5a, 0x75, 0x0a, 0x6a, 0x33,
0x88, 0xec, 0x17, 0x75, 0x08, 0x70, 0x10, 0x2f, 0x24, 0xcf, 0xc4, 0xe9, 0x42, 0x00, 0x61,
0x94, 0xca, 0x1f, 0x3a, 0x76, 0x06, 0xfa, 0xd2, 0x48, 0x81, 0xf0, 0x77, 0x60, 0x03, 0x45,
0xd9, 0x61, 0xf4, 0xa4, 0x6f, 0x3d, 0xd9, 0x30, 0xc3, 0x04, 0x6b, 0x54, 0x2a, 0xb7, 0xec,
0x3b, 0xf4, 0x4b, 0xf5, 0x68, 0x52, 0x26, 0xce, 0xff, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xa5,
0xa9, 0xb1, 0xe0, 0x23, 0xc4, 0x0a, 0x77, 0x4d, 0xf9, 0x51, 0x20, 0xa3, 0xa5, 0xa9, 0xb1,
0xc1, 0x00, 0x82, 0x86, 0x8e, 0x7f, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xc4, 0xeb, 0x54, 0x0b,
0x75, 0x68, 0x52, 0x07, 0x8c, 0x9a, 0x97, 0x8d, 0x79, 0x70, 0x62, 0x46, 0xef, 0x5c, 0x1b,
0x95, 0x89, 0x71, 0x41, 0xe1, 0x21, 0xa1, 0xa1, 0xa1, 0xc0, 0x02, 0x67, 0x4c, 0x1a, 0xb6,
0xcf, 0xfd, 0x78, 0x53, 0x24, 0xab, 0xb5, 0xc9, 0xf1, 0x60, 0x23, 0xa5, 0xc8, 0x12, 0x87,
0x6d, 0x58, 0x13, 0x85, 0x88, 0x92, 0x87, 0x6d, 0x58, 0x32, 0xc7, 0x0c, 0x9a, 0x97, 0xac,
0xda, 0x36, 0xee, 0x5e, 0x3e, 0xdf, 0x1d, 0xb8, 0xf2, 0x66, 0x2f, 0xbd, 0xf8, 0x72, 0x47,
0xed, 0x58, 0x13, 0x85, 0x88, 0x92, 0x87, 0x8c, 0x7b, 0x55, 0x09, 0x90, 0xa2, 0xc6, 0xef,
0x3d, 0xf8, 0x53, 0x24, 0xab, 0xd4, 0x2a, 0xb7, 0xec, 0x5a, 0x36, 0xee, 0x5e, 0x3e, 0xdf,
0x3c, 0xfa, 0x76, 0x4f, 0xfd, 0x59, 0x30, 0xe2, 0x46, 0xef, 0x3d, 0xf8, 0x53, 0x05, 0x69,
0x31, 0xc1, 0x00, 0x82, 0x86, 0x8e, 0x7f, 0x5d, 0x19, 0xb0, 0xe2, 0x27, 0xcc, 0xfb, 0x74,
0x4b, 0x14, 0x8b, 0x94, 0x8b, 0x75, 0x68, 0x33, 0xc5, 0x08, 0x92, 0x87, 0x8c, 0x9a, 0xb6,
0xcf, 0x1c, 0xba, 0xd7, 0x0d, 0x98, 0xb2, 0xe6, 0x2f, 0xdc, 0x1b, 0x95, 0x89, 0x71, 0x60,
0x23, 0xc4, 0x0a, 0x96, 0x8f, 0x9c, 0xba, 0xf6, 0x6e, 0x3f, 0xfc, 0x5b, 0x15, 0xa8, 0xd2,
0x26, 0xaf, 0xbd, 0xf8, 0x72, 0x66, 0x2f, 0xdc, 0x1b, 0xb4, 0xcb, 0x14, 0x8b, 0x94, 0xaa,
0xb7, 0xcd, 0xf9, 0x51, 0x01, 0x80, 0x82, 0x86, 0x6f, 0x3d, 0xd9, 0x30, 0xe2, 0x27, 0xcc,
0xfb, 0x74, 0x4b, 0x14, 0xaa, 0xb7, 0xcd, 0xf9, 0x70, 0x43, 0x04, 0x6b, 0x35, 0xc9, 0xf1,
0x60, 0x23, 0xa5, 0xc8, 0xf3, 0x45, 0x08, 0x92, 0x87, 0x6d, 0x58, 0x32, 0xe6, 0x2f, 0xbd,
0xf8, 0x72, 0x66, 0x4e, 0x1e, 0xbe, 0xfe, 0x7e, 0x7e, 0x7e, 0x5f, 0x1d, 0x99, 0x91, 0xa0,
0xa3, 0xc4, 0x0a, 0x77, 0x4d, 0x18, 0x93, 0xa4, 0xab, 0xd4, 0x0b, 0x75, 0x49, 0x10, 0xa2,
0xc6, 0xef, 0x3d, 0xf8, 0x53, 0x24, 0xab, 0xb5, 0xe8, 0x33, 0xe4, 0x4a, 0x16, 0xae, 0xde,
0x1f, 0xbc, 0xdb, 0x15, 0xa8, 0xb3, 0xc5, 0x08, 0x73, 0x45, 0xe9, 0x31, 0xc1, 0xe1, 0x21,
0xa1, 0xa1, 0xa1, 0xc0, 0x02, 0x86, 0x6f, 0x5c, 0x3a, 0xd7, 0x0d, 0x98, 0x93, 0xa4, 0xca,
0x16, 0xae, 0xde, 0x1f, 0x9d, 0x99, 0xb0, 0xe2, 0x46, 0xef, 0x3d, 0xf8, 0x72, 0x47, 0x0c,
0x9a, 0xb6, 0xcf, 0xfd, 0x59, 0x11, 0xa0, 0xa3, 0xa5, 0xc8, 0xf3, 0x45, 0x08, 0x92, 0x87,
0x6d, 0x39, 0xf0, 0x43, 0x04, 0x8a, 0x96, 0xae, 0xde, 0x3e, 0xdf, 0x1d, 0x99, 0x91, 0xa0,
0xc2, 0x06, 0x6f, 0x3d, 0xf8, 0x72, 0x47, 0x0c, 0x9a, 0x97, 0x8d, 0x98, 0x93, 0x85, 0x88,
0x73, 0x45, 0xe9, 0x31, 0xe0, 0x23, 0xa5, 0xa9, 0xd0, 0x03, 0x84, 0x8a, 0x96, 0xae, 0xde,
0x1f, 0xbc, 0xdb, 0x15, 0xa8, 0xd2, 0x26, 0xce, 0xff, 0x5d, 0x19, 0x91, 0x81, 0x80, 0x82,
0x67, 0x2d, 0xd8, 0x13, 0xa4, 0xab, 0xd4, 0x0b, 0x94, 0xaa, 0xb7, 0xcd, 0xf9, 0x51, 0x20,
0xa3, 0xa5, 0xc8, 0xf3, 0x45, 0xe9, 0x50, 0x22, 0xc6, 0xef, 0x5c, 0x3a, 0xd7, 0x0d, 0x98,
0x93, 0x85, 0x88, 0x73, 0x64, 0x4a, 0xf7, 0x4d, 0xf9, 0x51, 0x20, 0xa3, 0xc4, 0x0a, 0x96,
0xae, 0xde, 0x3e, 0xfe, 0x7e, 0x7e, 0x7e, 0x5f, 0x3c, 0xfa, 0x76, 0x4f, 0xfd, 0x78, 0x72,
0x66, 0x2f, 0xbd, 0xd9, 0x30, 0xc3, 0xe5, 0x48, 0x12, 0x87, 0x8c, 0x7b, 0x55, 0x28, 0xd2,
0x07, 0x8c, 0x9a, 0x97, 0xac, 0xda, 0x17, 0x8d, 0x79, 0x51, 0x20, 0xa3, 0xc4, 0xeb, 0x54,
0x0b, 0x94, 0x8b, 0x94, 0xaa, 0xd6, 0x2e, 0xbf, 0xfc, 0x5b, 0x15, 0xa8, 0xd2, 0x26, 0xaf,
0xdc, 0x1b, 0xb4, 0xea, 0x37, 0xec, 0x3b, 0xf4, 0x6a, 0x37, 0xcd, 0x18, 0x93, 0x85, 0x69,
0x31, 0xc1, 0xe1, 0x40, 0xe3, 0x25, 0xc8, 0x12, 0x87, 0x8c, 0x9a, 0xb6, 0xcf, 0xfd, 0x59,
0x11, 0xa0, 0xc2, 0x06, 0x8e, 0x7f, 0x5d, 0x38, 0xf2, 0x47, 0x0c, 0x7b, 0x74, 0x6a, 0x37,
0xec, 0x5a, 0x36, 0xee, 0x3f, 0xfc, 0x7a, 0x76, 0x4f, 0x1c, 0x9b, 0x95, 0x89, 0x71, 0x41,
0x00, 0x63, 0x44, 0xeb, 0x54, 0x2a, 0xd6, 0x0f, 0x9c, 0xba, 0xd7, 0x0d, 0x98, 0x93, 0x85,
0x69, 0x31, 0xc1, 0x00, 0x82, 0x86, 0x8e, 0x9e, 0xbe, 0xdf, 0x3c, 0xfa, 0x57, 0x2c, 0xda,
0x36, 0xee, 0x3f, 0xfc, 0x5b, 0x15, 0x89, 0x71, 0x41, 0x00, 0x82, 0x86, 0x8e, 0x7f, 0x5d,
0x38, 0xf2, 0x47, 0xed, 0x58, 0x13, 0xa4, 0xca, 0xf7, 0x4d, 0xf9, 0x51, 0x01, 0x80, 0x63,
0x44, 0xeb, 0x54, 0x2a, 0xd6, 0x2e, 0xbf, 0xdd, 0x19, 0x91, 0xa0, 0xa3, 0xa5, 0xa9, 0xb1,
0xe0, 0x42, 0x06, 0x8e, 0x7f, 0x5d, 0x19, 0x91, 0xa0, 0xa3, 0xc4, 0x0a, 0x96, 0x8f, 0x7d,
0x78, 0x72, 0x47, 0x0c, 0x7b, 0x74, 0x6a, 0x56, 0x2e, 0xde, 0x1f, 0xbc, 0xfa, 0x57, 0x0d,
0x79, 0x51, 0x01, 0x61, 0x21, 0xa1, 0xc0, 0xe3, 0x25, 0xa9, 0xb1, 0xc1, 0xe1, 0x40, 0x02,
0x67, 0x4c, 0x1a, 0x97, 0x8d, 0x98, 0x93, 0xa4, 0xab, 0xd4, 0x2a, 0xd6, 0x0f, 0x9c, 0x9b,
0xb4, 0xcb, 0x14, 0xaa, 0xb7, 0xcd, 0xf9, 0x51, 0x20, 0xa3, 0xc4, 0xeb, 0x35, 0xc9, 0xf1,
0x60, 0x42, 0x06, 0x8e, 0x7f, 0x7c, 0x7a, 0x76, 0x6e, 0x3f, 0xfc, 0x7a, 0x76, 0x6e, 0x5e,
0x3e, 0xfe, 0x7e, 0x5f, 0x3c, 0xdb, 0x15, 0x89, 0x71, 0x41, 0xe1, 0x21, 0xc0, 0xe3, 0x44,
0xeb, 0x54, 0x2a, 0xb7, 0xcd, 0xf9, 0x70, 0x62, 0x27, 0xad, 0xd8, 0x32, 0xc7, 0x0c, 0x7b,
0x74, 0x4b, 0x14, 0xaa, 0xb7, 0xec, 0x3b, 0xd5, 0x28, 0xd2, 0x07, 0x6d, 0x39, 0xd1, 0x20,
0xc2, 0xe7, 0x4c, 0x1a, 0x97, 0x8d, 0x98, 0xb2, 0xc7, 0x0c, 0x59, 0x28, 0xf3, 0x9b };

View file

@ -21,7 +21,3 @@ SPLIT_KEYBOARD = yes
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
DEFAULT_FOLDER = oddball/v1 DEFAULT_FOLDER = oddball/v1
SRC += spi_master.c
SRC += drivers/sensors/adns9800.c
SRC += pmw/pmw.c

View file

@ -31,7 +31,8 @@
# define OPT_SCALE 1 // Multiplier for wheel # define OPT_SCALE 1 // Multiplier for wheel
#endif #endif
#ifndef PLOOPY_DPI_OPTIONS #ifndef PLOOPY_DPI_OPTIONS
# define PLOOPY_DPI_OPTIONS { 1200, 1600, 2400 } # define PLOOPY_DPI_OPTIONS \
{ 1200, 1600, 2400 }
# ifndef PLOOPY_DPI_DEFAULT # ifndef PLOOPY_DPI_DEFAULT
# define PLOOPY_DPI_DEFAULT 1 # define PLOOPY_DPI_DEFAULT 1
# endif # endif
@ -65,13 +66,7 @@ uint8_t OptLowPin = OPT_ENC1;
bool debug_encoder = false; bool debug_encoder = false;
bool is_drag_scroll = false; bool is_drag_scroll = false;
__attribute__((weak)) void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v) { void process_wheel(report_mouse_t* mouse_report) {
mouse_report->h = h;
mouse_report->v = v;
}
__attribute__((weak)) void process_wheel(report_mouse_t* mouse_report) {
// TODO: Replace this with interrupt driven code, polling is S L O W
// Lovingly ripped from the Ploopy Source // Lovingly ripped from the Ploopy Source
// If the mouse wheel was just released, do not scroll. // If the mouse wheel was just released, do not scroll.
@ -99,56 +94,25 @@ __attribute__((weak)) void process_wheel(report_mouse_t* mouse_report) {
int dir = opt_encoder_handler(p1, p2); int dir = opt_encoder_handler(p1, p2);
if (dir == 0) return; if (dir == 0) return;
process_wheel_user(mouse_report, mouse_report->h, (int)(mouse_report->v + (dir * OPT_SCALE))); mouse_report->v = (int8_t)(dir * OPT_SCALE);
} }
__attribute__((weak)) void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { __attribute__((weak)) report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
mouse_report->x = x; process_wheel(&mouse_report);
mouse_report->y = y;
}
__attribute__((weak)) void process_mouse(report_mouse_t* mouse_report) { if (is_drag_scroll) {
report_pmw_t data = pmw_read_burst(); mouse_report.h = mouse_report.x;
if (data.isOnSurface && data.isMotion) { #ifdef PLOOPY_DRAGSCROLL_INVERT
// Reset timer if stopped moving // Invert vertical scroll direction
if (!data.isMotion) { mouse_report.v = -mouse_report.y;
if (MotionStart != 0) MotionStart = 0;
return;
}
// Set timer if new motion
if ((MotionStart == 0) && data.isMotion) {
if (debug_mouse) dprintf("Starting motion.\n");
MotionStart = timer_read();
}
if (debug_mouse) {
dprintf("Delt] d: %d t: %u\n", abs(data.dx) + abs(data.dy), MotionStart);
}
if (debug_mouse) {
dprintf("Pre ] X: %d, Y: %d\n", data.dx, data.dy);
}
#if defined(PROFILE_LINEAR)
float scale = float(timer_elaspsed(MotionStart)) / 1000.0;
data.dx *= scale;
data.dy *= scale;
#elif defined(PROFILE_INVERSE)
// TODO
#else #else
// no post processing mouse_report.v = mouse_report.y;
#endif #endif
// apply multiplier mouse_report.x = 0;
// data.dx *= mouse_multiplier; mouse_report.y = 0;
// data.dy *= mouse_multiplier;
// Wrap to HID size
data.dx = constrain(data.dx, -127, 127);
data.dy = constrain(data.dy, -127, 127);
if (debug_mouse) dprintf("Cons] X: %d, Y: %d\n", data.dx, data.dy);
// dprintf("Elapsed:%u, X: %f Y: %\n", i, pgm_read_byte(firmware_data+i));
process_mouse_user(mouse_report, data.dx, data.dy);
} }
return pointing_device_task_user(mouse_report);
} }
bool process_record_kb(uint16_t keycode, keyrecord_t* record) { bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
@ -169,7 +133,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
if (keycode == DPI_CONFIG && record->event.pressed) { if (keycode == DPI_CONFIG && record->event.pressed) {
keyboard_config.dpi_config = (keyboard_config.dpi_config + 1) % DPI_OPTION_SIZE; keyboard_config.dpi_config = (keyboard_config.dpi_config + 1) % DPI_OPTION_SIZE;
eeconfig_update_kb(keyboard_config.raw); eeconfig_update_kb(keyboard_config.raw);
pmw_set_cpi(dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
} }
if (keycode == DRAG_SCROLL) { if (keycode == DRAG_SCROLL) {
@ -180,9 +144,9 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
is_drag_scroll ^= 1; is_drag_scroll ^= 1;
} }
#ifdef PLOOPY_DRAGSCROLL_FIXED #ifdef PLOOPY_DRAGSCROLL_FIXED
pmw_set_cpi(is_drag_scroll ? PLOOPY_DRAGSCROLL_DPI : dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(is_drag_scroll ? PLOOPY_DRAGSCROLL_DPI : dpi_array[keyboard_config.dpi_config]);
#else #else
pmw_set_cpi(is_drag_scroll ? (dpi_array[keyboard_config.dpi_config] * PLOOPY_DRAGSCROLL_MULTIPLIER) : dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(is_drag_scroll ? (dpi_array[keyboard_config.dpi_config] * PLOOPY_DRAGSCROLL_MULTIPLIER) : dpi_array[keyboard_config.dpi_config]);
#endif #endif
} }
@ -194,11 +158,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
#ifndef MOUSEKEY_ENABLE #ifndef MOUSEKEY_ENABLE
if (IS_MOUSEKEY_BUTTON(keycode)) { if (IS_MOUSEKEY_BUTTON(keycode)) {
report_mouse_t currentReport = pointing_device_get_report(); report_mouse_t currentReport = pointing_device_get_report();
if (record->event.pressed) { currentReport.buttons = pointing_device_handle_buttons(currentReport.buttons, record->event.pressed, keycode - KC_MS_BTN1);
currentReport.buttons |= 1 << (keycode - KC_MS_BTN1);
} else {
currentReport.buttons &= ~(1 << (keycode - KC_MS_BTN1));
}
pointing_device_set_report(currentReport); pointing_device_set_report(currentReport);
pointing_device_send(); pointing_device_send();
} }
@ -240,35 +200,12 @@ void keyboard_pre_init_kb(void) {
keyboard_pre_init_user(); keyboard_pre_init_user();
} }
void pointing_device_init(void) { void pointing_device_init_kb(void) {
// initialize ball sensor pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
pmw_spi_init();
// initialize the scroll wheel's optical encoder // initialize the scroll wheel's optical encoder
opt_encoder_init(); opt_encoder_init();
} }
void pointing_device_task(void) {
report_mouse_t mouse_report = pointing_device_get_report();
process_wheel(&mouse_report);
process_mouse(&mouse_report);
if (is_drag_scroll) {
mouse_report.h = mouse_report.x;
#ifdef PLOOPY_DRAGSCROLL_INVERT
// Invert vertical scroll direction
mouse_report.v = -mouse_report.y;
#else
mouse_report.v = mouse_report.y;
#endif
mouse_report.x = 0;
mouse_report.y = 0;
}
pointing_device_set_report(mouse_report);
pointing_device_send();
}
void eeconfig_init_kb(void) { void eeconfig_init_kb(void) {
keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT; keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT;
eeconfig_update_kb(keyboard_config.raw); eeconfig_update_kb(keyboard_config.raw);
@ -284,9 +221,3 @@ void matrix_init_kb(void) {
} }
matrix_init_user(); matrix_init_user();
} }
void keyboard_post_init_kb(void) {
pmw_set_cpi(dpi_array[keyboard_config.dpi_config]);
keyboard_post_init_user();
}

View file

@ -19,11 +19,8 @@
#pragma once #pragma once
#include "quantum.h" #include "quantum.h"
#include "spi_master.h"
#include "drivers/sensors/pmw3360.h"
#include "analog.h" #include "analog.h"
#include "opt_encoder.h" #include "opt_encoder.h"
#include "pointing_device.h"
// Sensor defs // Sensor defs
#define OPT_ENC1 F0 #define OPT_ENC1 F0
@ -31,10 +28,7 @@
#define OPT_ENC1_MUX 0 #define OPT_ENC1_MUX 0
#define OPT_ENC2_MUX 4 #define OPT_ENC2_MUX 4
void process_mouse(report_mouse_t* mouse_report);
void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y);
void process_wheel(report_mouse_t* mouse_report); void process_wheel(report_mouse_t* mouse_report);
void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v);
#define LAYOUT(BLL, BL, BM, BR, BRR, BF, BB, BDPI) \ #define LAYOUT(BLL, BL, BM, BR, BRR, BF, BB, BDPI) \
{ {BL, BM, BR, BF, BB, BRR, BLL, BDPI}, } { {BL, BM, BR, BF, BB, BRR, BLL, BDPI}, }

View file

@ -22,7 +22,8 @@ BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow
AUDIO_ENABLE = no # Audio output AUDIO_ENABLE = no # Audio output
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = pmw3360
MOUSEKEY_ENABLE = yes # Mouse keys MOUSEKEY_ENABLE = yes # Mouse keys
QUANTUM_LIB_SRC += analog.c spi_master.c QUANTUM_LIB_SRC += analog.c
SRC += drivers/sensors/pmw3360.c opt_encoder.c SRC += opt_encoder.c

View file

@ -19,9 +19,10 @@ BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow
AUDIO_ENABLE = no # Audio output AUDIO_ENABLE = no # Audio output
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = pmw3360
MOUSEKEY_ENABLE = yes # Mouse keys MOUSEKEY_ENABLE = yes # Mouse keys
QUANTUM_LIB_SRC += analog.c spi_master.c QUANTUM_LIB_SRC += analog.c
SRC += drivers/sensors/pmw3360.c opt_encoder.c SRC += opt_encoder.c
DEFAULT_FOLDER = ploopyco/trackball/rev1_005 DEFAULT_FOLDER = ploopyco/trackball/rev1_005

View file

@ -65,12 +65,7 @@ uint8_t OptLowPin = OPT_ENC1;
bool debug_encoder = false; bool debug_encoder = false;
bool is_drag_scroll = false; bool is_drag_scroll = false;
__attribute__((weak)) void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v) { void process_wheel(report_mouse_t* mouse_report) {
mouse_report->h = h;
mouse_report->v = v;
}
__attribute__((weak)) void process_wheel(report_mouse_t* mouse_report) {
// TODO: Replace this with interrupt driven code, polling is S L O W // TODO: Replace this with interrupt driven code, polling is S L O W
// Lovingly ripped from the Ploopy Source // Lovingly ripped from the Ploopy Source
@ -99,56 +94,25 @@ __attribute__((weak)) void process_wheel(report_mouse_t* mouse_report) {
int dir = opt_encoder_handler(p1, p2); int dir = opt_encoder_handler(p1, p2);
if (dir == 0) return; if (dir == 0) return;
process_wheel_user(mouse_report, mouse_report->h, (int)(mouse_report->v + (dir * OPT_SCALE))); mouse_report->v = (int8_t)(dir * OPT_SCALE);
} }
__attribute__((weak)) void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
mouse_report->x = x; process_wheel(&mouse_report);
mouse_report->y = y;
}
__attribute__((weak)) void process_mouse(report_mouse_t* mouse_report) { if (is_drag_scroll) {
report_pmw_t data = pmw_read_burst(); mouse_report.h = mouse_report.x;
if (data.isOnSurface && data.isMotion) { #ifdef PLOOPY_DRAGSCROLL_INVERT
// Reset timer if stopped moving // Invert vertical scroll direction
if (!data.isMotion) { mouse_report.v = -mouse_report.y;
if (MotionStart != 0) MotionStart = 0;
return;
}
// Set timer if new motion
if ((MotionStart == 0) && data.isMotion) {
if (debug_mouse) dprintf("Starting motion.\n");
MotionStart = timer_read();
}
if (debug_mouse) {
dprintf("Delt] d: %d t: %u\n", abs(data.dx) + abs(data.dy), MotionStart);
}
if (debug_mouse) {
dprintf("Pre ] X: %d, Y: %d\n", data.dx, data.dy);
}
#if defined(PROFILE_LINEAR)
float scale = float(timer_elaspsed(MotionStart)) / 1000.0;
data.dx *= scale;
data.dy *= scale;
#elif defined(PROFILE_INVERSE)
// TODO
#else #else
// no post processing mouse_report.v = mouse_report.y;
#endif #endif
// apply multiplier mouse_report.x = 0;
// data.dx *= mouse_multiplier; mouse_report.y = 0;
// data.dy *= mouse_multiplier;
// Wrap to HID size
data.dx = constrain(data.dx, -127, 127);
data.dy = constrain(data.dy, -127, 127);
if (debug_mouse) dprintf("Cons] X: %d, Y: %d\n", data.dx, data.dy);
// dprintf("Elapsed:%u, X: %f Y: %\n", i, pgm_read_byte(firmware_data+i));
process_mouse_user(mouse_report, data.dx, -data.dy);
} }
return pointing_device_task_user(mouse_report);
} }
bool process_record_kb(uint16_t keycode, keyrecord_t* record) { bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
@ -169,7 +133,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
if (keycode == DPI_CONFIG && record->event.pressed) { if (keycode == DPI_CONFIG && record->event.pressed) {
keyboard_config.dpi_config = (keyboard_config.dpi_config + 1) % DPI_OPTION_SIZE; keyboard_config.dpi_config = (keyboard_config.dpi_config + 1) % DPI_OPTION_SIZE;
eeconfig_update_kb(keyboard_config.raw); eeconfig_update_kb(keyboard_config.raw);
pmw_set_cpi(dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
} }
if (keycode == DRAG_SCROLL) { if (keycode == DRAG_SCROLL) {
@ -180,9 +144,9 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
is_drag_scroll ^= 1; is_drag_scroll ^= 1;
} }
#ifdef PLOOPY_DRAGSCROLL_FIXED #ifdef PLOOPY_DRAGSCROLL_FIXED
pmw_set_cpi(is_drag_scroll ? PLOOPY_DRAGSCROLL_DPI : dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(is_drag_scroll ? PLOOPY_DRAGSCROLL_DPI : dpi_array[keyboard_config.dpi_config]);
#else #else
pmw_set_cpi(is_drag_scroll ? (dpi_array[keyboard_config.dpi_config] * PLOOPY_DRAGSCROLL_MULTIPLIER) : dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(is_drag_scroll ? (dpi_array[keyboard_config.dpi_config] * PLOOPY_DRAGSCROLL_MULTIPLIER) : dpi_array[keyboard_config.dpi_config]);
#endif #endif
} }
@ -194,11 +158,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
#ifndef MOUSEKEY_ENABLE #ifndef MOUSEKEY_ENABLE
if (IS_MOUSEKEY_BUTTON(keycode)) { if (IS_MOUSEKEY_BUTTON(keycode)) {
report_mouse_t currentReport = pointing_device_get_report(); report_mouse_t currentReport = pointing_device_get_report();
if (record->event.pressed) { currentReport.buttons = pointing_device_handle_buttons(record->event.pressed, keycode - KC_MS_BTN1);
currentReport.buttons |= 1 << (keycode - KC_MS_BTN1);
} else {
currentReport.buttons &= ~(1 << (keycode - KC_MS_BTN1));
}
pointing_device_set_report(currentReport); pointing_device_set_report(currentReport);
pointing_device_send(); pointing_device_send();
} }
@ -239,35 +199,12 @@ void keyboard_pre_init_kb(void) {
keyboard_pre_init_user(); keyboard_pre_init_user();
} }
void pointing_device_init(void) { void pointing_device_init_kb(void) {
// initialize ball sensor pmw3360_set_cpi(dpi_array[keyboard_config.dpi_config]);
pmw_spi_init();
// initialize the scroll wheel's optical encoder // initialize the scroll wheel's optical encoder
opt_encoder_init(); opt_encoder_init();
} }
void pointing_device_task(void) {
report_mouse_t mouse_report = pointing_device_get_report();
process_wheel(&mouse_report);
process_mouse(&mouse_report);
if (is_drag_scroll) {
mouse_report.h = mouse_report.x;
#ifdef PLOOPY_DRAGSCROLL_INVERT
// Invert vertical scroll direction
mouse_report.v = -mouse_report.y;
#else
mouse_report.v = mouse_report.y;
#endif
mouse_report.x = 0;
mouse_report.y = 0;
}
pointing_device_set_report(mouse_report);
pointing_device_send();
}
void eeconfig_init_kb(void) { void eeconfig_init_kb(void) {
keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT; keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT;
eeconfig_update_kb(keyboard_config.raw); eeconfig_update_kb(keyboard_config.raw);
@ -285,7 +222,7 @@ void matrix_init_kb(void) {
} }
void keyboard_post_init_kb(void) { void keyboard_post_init_kb(void) {
pmw_set_cpi(dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
keyboard_post_init_user(); keyboard_post_init_user();
} }

View file

@ -19,11 +19,8 @@
#pragma once #pragma once
#include "quantum.h" #include "quantum.h"
#include "spi_master.h"
#include "drivers/sensors/pmw3360.h"
#include "analog.h" #include "analog.h"
#include "opt_encoder.h" #include "opt_encoder.h"
#include "pointing_device.h"
#if defined(KEYBOARD_ploopyco_trackball_rev1) #if defined(KEYBOARD_ploopyco_trackball_rev1)
# include "rev1.h" # include "rev1.h"
#elif defined(KEYBOARD_ploopyco_trackball_rev1_005) #elif defined(KEYBOARD_ploopyco_trackball_rev1_005)
@ -36,10 +33,7 @@
#define OPT_ENC1_MUX 0 #define OPT_ENC1_MUX 0
#define OPT_ENC2_MUX 4 #define OPT_ENC2_MUX 4
void process_mouse(report_mouse_t* mouse_report);
void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y);
void process_wheel(report_mouse_t* mouse_report); void process_wheel(report_mouse_t* mouse_report);
void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v);
#define LAYOUT(BL, BM, BR, BF, BB) \ #define LAYOUT(BL, BM, BR, BF, BB) \
{ {BL, BM, BR, BF, BB}, } { {BL, BM, BR, BF, BB}, }

View file

@ -56,3 +56,9 @@
// If board has a debug LED, you can enable it by defining this // If board has a debug LED, you can enable it by defining this
// #define DEBUG_LED_PIN F7 // #define DEBUG_LED_PIN F7
#define ADNS5050_SCLK_PIN B7
#define ADNS5050_SDIO_PIN C6
#define ADNS5050_CS_PIN B4
#define POINTING_DEVICE_ROTATION_90

View file

@ -44,7 +44,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
// to only scroll in one direction, if you wanted, as well. In fact, // to only scroll in one direction, if you wanted, as well. In fact,
// there is no reason that you need to send this to the mouse report. // there is no reason that you need to send this to the mouse report.
// You could have it register a key, instead. // You could have it register a key, instead.
void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { void process_mouse_user(report_mouse_t* mouse_report, int8_t x, int8_t y) {
if (is_drag_scroll) { if (is_drag_scroll) {
mouse_report->h = x; mouse_report->h = x;
mouse_report->v = y; mouse_report->v = y;

View file

@ -1,9 +1,6 @@
# MCU name # MCU name
MCU = atmega32u4 MCU = atmega32u4
# Processor frequency
F_CPU = 16000000
# Bootloader selection # Bootloader selection
BOOTLOADER = atmel-dfu BOOTLOADER = atmel-dfu
@ -22,9 +19,10 @@ BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow
AUDIO_ENABLE = no # Audio output AUDIO_ENABLE = no # Audio output
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = adns5050
MOUSEKEY_ENABLE = no # Mouse keys MOUSEKEY_ENABLE = no # Mouse keys
QUANTUM_LIB_SRC += analog.c QUANTUM_LIB_SRC += analog.c
SRC += drivers/sensors/adns5050.c opt_encoder.c SRC += opt_encoder.c
DEFAULT_FOLDER = ploopyco/trackball_mini/rev1_001 DEFAULT_FOLDER = ploopyco/trackball_mini/rev1_001

View file

@ -37,20 +37,17 @@
# define OPT_SCALE 1 // Multiplier for wheel # define OPT_SCALE 1 // Multiplier for wheel
#endif #endif
#define PLOOPY_DPI_OPTIONS { CPI375, CPI750, CPI1375 } #define PLOOPY_DPI_OPTIONS \
{ 375, 750, 1375 }
#define PLOOPY_DPI_DEFAULT 2 #define PLOOPY_DPI_DEFAULT 2
#ifndef PLOOPY_DRAGSCROLL_DPI #ifndef PLOOPY_DRAGSCROLL_DPI
# define PLOOPY_DRAGSCROLL_DPI CPI375 // Fixed-DPI Drag Scroll # define PLOOPY_DRAGSCROLL_DPI 375 // Fixed-DPI Drag Scroll
#endif #endif
#ifndef PLOOPY_DRAGSCROLL_MULTIPLIER #ifndef PLOOPY_DRAGSCROLL_MULTIPLIER
# define PLOOPY_DRAGSCROLL_MULTIPLIER 0.75 // Variable-DPI Drag Scroll # define PLOOPY_DRAGSCROLL_MULTIPLIER 0.75 // Variable-DPI Drag Scroll
#endif #endif
// Transformation constants for delta-X and delta-Y
const static float ADNS_X_TRANSFORM = -1.0;
const static float ADNS_Y_TRANSFORM = 1.0;
keyboard_config_t keyboard_config; keyboard_config_t keyboard_config;
uint16_t dpi_array[] = PLOOPY_DPI_OPTIONS; uint16_t dpi_array[] = PLOOPY_DPI_OPTIONS;
#define DPI_OPTION_SIZE (sizeof(dpi_array) / sizeof(uint16_t)) #define DPI_OPTION_SIZE (sizeof(dpi_array) / sizeof(uint16_t))
@ -70,12 +67,7 @@ uint8_t OptLowPin = OPT_ENC1;
bool debug_encoder = false; bool debug_encoder = false;
bool is_drag_scroll = false; bool is_drag_scroll = false;
__attribute__((weak)) void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v) { void process_wheel(report_mouse_t* mouse_report) {
mouse_report->h = h;
mouse_report->v = v;
}
__attribute__((weak)) void process_wheel(report_mouse_t* mouse_report) {
// If the mouse wheel was just released, do not scroll. // If the mouse wheel was just released, do not scroll.
if (timer_elapsed(lastMidClick) < SCROLL_BUTT_DEBOUNCE) if (timer_elapsed(lastMidClick) < SCROLL_BUTT_DEBOUNCE)
return; return;
@ -103,33 +95,31 @@ __attribute__((weak)) void process_wheel(report_mouse_t* mouse_report) {
if (dir == 0) if (dir == 0)
return; return;
process_wheel_user(mouse_report, mouse_report->h, (int)(mouse_report->v + (dir * OPT_SCALE))); mouse_report->v = (int8_t)(dir * OPT_SCALE);
} }
__attribute__((weak)) void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { void pointing_device_init_kb(void) {
mouse_report->x = x; opt_encoder_init();
mouse_report->y = y;
// set the DPI.
pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
} }
__attribute__((weak)) void process_mouse(report_mouse_t* mouse_report) { report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) {
report_adns_t data = adns_read_burst();
if (data.dx != 0 || data.dy != 0) { if (is_drag_scroll) {
if (debug_mouse) mouse_report.h = mouse_report.x;
dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy); #ifdef PLOOPY_DRAGSCROLL_INVERT
// Invert vertical scroll direction
// Apply delta-X and delta-Y transformations. mouse_report.v = -mouse_report.y;
// x and y are swapped #else
// the sensor is rotated mouse_report.v = mouse_report.y;
// by 90 degrees #endif
float xt = (float) data.dy * ADNS_X_TRANSFORM; mouse_report.x = 0;
float yt = (float) data.dx * ADNS_Y_TRANSFORM; mouse_report.y = 0;
int16_t xti = (int16_t)xt;
int16_t yti = (int16_t)yt;
process_mouse_user(mouse_report, xti, yti);
} }
return pointing_device_task_user(mouse_report);
} }
bool process_record_kb(uint16_t keycode, keyrecord_t* record) { bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
@ -147,7 +137,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
if (keycode == DPI_CONFIG && record->event.pressed) { if (keycode == DPI_CONFIG && record->event.pressed) {
keyboard_config.dpi_config = (keyboard_config.dpi_config + 1) % DPI_OPTION_SIZE; keyboard_config.dpi_config = (keyboard_config.dpi_config + 1) % DPI_OPTION_SIZE;
eeconfig_update_kb(keyboard_config.raw); eeconfig_update_kb(keyboard_config.raw);
adns_set_cpi(dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
} }
if (keycode == DRAG_SCROLL) { if (keycode == DRAG_SCROLL) {
@ -157,7 +147,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
{ {
is_drag_scroll ^= 1; is_drag_scroll ^= 1;
} }
adns_set_cpi(is_drag_scroll ? PLOOPY_DRAGSCROLL_DPI : dpi_array[keyboard_config.dpi_config]); pointing_device_set_cpi(is_drag_scroll ? PLOOPY_DRAGSCROLL_DPI : dpi_array[keyboard_config.dpi_config]);
} }
/* If Mousekeys is disabled, then use handle the mouse button /* If Mousekeys is disabled, then use handle the mouse button
@ -168,11 +158,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
#ifndef MOUSEKEY_ENABLE #ifndef MOUSEKEY_ENABLE
if (IS_MOUSEKEY_BUTTON(keycode)) { if (IS_MOUSEKEY_BUTTON(keycode)) {
report_mouse_t currentReport = pointing_device_get_report(); report_mouse_t currentReport = pointing_device_get_report();
if (record->event.pressed) { currentReport.buttons = pointing_device_handle_buttons(currentReport.buttons, record->event.pressed, keycode - KC_MS_BTN1);
currentReport.buttons |= 1 << (keycode - KC_MS_BTN1);
} else {
currentReport.buttons &= ~(1 << (keycode - KC_MS_BTN1));
}
pointing_device_set_report(currentReport); pointing_device_set_report(currentReport);
pointing_device_send(); pointing_device_send();
} }
@ -207,48 +193,6 @@ void keyboard_pre_init_kb(void) {
keyboard_pre_init_user(); keyboard_pre_init_user();
} }
void pointing_device_init(void) {
adns_init();
opt_encoder_init();
// reboot the adns.
// if the adns hasn't initialized yet, this is harmless.
adns_write_reg(REG_CHIP_RESET, 0x5a);
// wait maximum time before adns is ready.
// this ensures that the adns is actuall ready after reset.
wait_ms(55);
// read a burst from the adns and then discard it.
// gets the adns ready for write commands
// (for example, setting the dpi).
adns_read_burst();
// set the DPI.
adns_set_cpi(dpi_array[keyboard_config.dpi_config]);
}
void pointing_device_task(void) {
report_mouse_t mouse_report = pointing_device_get_report();
process_wheel(&mouse_report);
process_mouse(&mouse_report);
if (is_drag_scroll) {
mouse_report.h = mouse_report.x;
#ifdef PLOOPY_DRAGSCROLL_INVERT
// Invert vertical scroll direction
mouse_report.v = -mouse_report.y;
#else
mouse_report.v = mouse_report.y;
#endif
mouse_report.x = 0;
mouse_report.y = 0;
}
pointing_device_set_report(mouse_report);
pointing_device_send();
}
void eeconfig_init_kb(void) { void eeconfig_init_kb(void) {
keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT; keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT;
eeconfig_update_kb(keyboard_config.raw); eeconfig_update_kb(keyboard_config.raw);

View file

@ -20,10 +20,8 @@
#pragma once #pragma once
#include "quantum.h" #include "quantum.h"
#include "drivers/sensors/adns5050.h"
#include "analog.h" #include "analog.h"
#include "opt_encoder.h" #include "opt_encoder.h"
#include "pointing_device.h"
// Sensor defs // Sensor defs
#define OPT_ENC1 F0 #define OPT_ENC1 F0
@ -31,10 +29,7 @@
#define OPT_ENC1_MUX 0 #define OPT_ENC1_MUX 0
#define OPT_ENC2_MUX 4 #define OPT_ENC2_MUX 4
void process_mouse(report_mouse_t* mouse_report);
void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y);
void process_wheel(report_mouse_t* mouse_report); void process_wheel(report_mouse_t* mouse_report);
void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v);
#define LAYOUT(BL, BM, BR, BF, BB) \ #define LAYOUT(BL, BM, BR, BF, BB) \
{ {BL, BM, BR, BF, BB}, } { {BL, BM, BR, BF, BB}, }

View file

@ -45,3 +45,9 @@
a polling rate as possible. */ a polling rate as possible. */
#define USB_POLLING_INTERVAL_MS 1 #define USB_POLLING_INTERVAL_MS 1
#define USB_MAX_POWER_CONSUMPTION 100 #define USB_MAX_POWER_CONSUMPTION 100
#define ADNS5050_SCLK_PIN B7
#define ADNS5050_SDIO_PIN C6
#define ADNS5050_CS_PIN B4
#define POINTING_DEVICE_ROTATION_90

View file

@ -21,10 +21,10 @@
// safe range starts at `PLOOPY_SAFE_RANGE` instead. // safe range starts at `PLOOPY_SAFE_RANGE` instead.
uint8_t scroll_enabled = 0; uint8_t scroll_enabled = 0;
uint8_t lock_state = 0; uint8_t lock_state = 0;
int16_t delta_x = 0; int8_t delta_x = 0;
int16_t delta_y = 0; int8_t delta_y = 0;
void process_mouse_user(report_mouse_t *mouse_report, int16_t x, int16_t y) { void process_mouse_user(report_mouse_t *mouse_report, int8_t x, int8_t y) {
if (scroll_enabled) { if (scroll_enabled) {
delta_x += x; delta_x += x;
delta_y += y; delta_y += y;

View file

@ -1,9 +1,6 @@
# MCU name # MCU name
MCU = atmega32u4 MCU = atmega32u4
# Processor frequency
F_CPU = 16000000
# Bootloader selection # Bootloader selection
BOOTLOADER = atmel-dfu BOOTLOADER = atmel-dfu
@ -22,9 +19,7 @@ BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality
RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow
AUDIO_ENABLE = no # Audio output AUDIO_ENABLE = no # Audio output
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
POINTING_DEVICE_DRIVER = adns5050
MOUSEKEY_ENABLE = no # Mouse keys MOUSEKEY_ENABLE = no # Mouse keys
QUANTUM_LIB_SRC += analog.c
SRC += drivers/sensors/adns5050.c opt_encoder.c
DEFAULT_FOLDER = ploopyco/trackball_nano/rev1_001 DEFAULT_FOLDER = ploopyco/trackball_nano/rev1_001

View file

@ -37,7 +37,8 @@
#endif #endif
#ifndef PLOOPY_DPI_OPTIONS #ifndef PLOOPY_DPI_OPTIONS
# define PLOOPY_DPI_OPTIONS { CPI375, CPI750, CPI1375 } # define PLOOPY_DPI_OPTIONS \
{ 375, 750, 1375 }
# ifndef PLOOPY_DPI_DEFAULT # ifndef PLOOPY_DPI_DEFAULT
# define PLOOPY_DPI_DEFAULT 2 # define PLOOPY_DPI_DEFAULT 2
# endif # endif
@ -49,10 +50,6 @@
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { }; const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { };
// Transformation constants for delta-X and delta-Y
const static float ADNS_X_TRANSFORM = -1.0;
const static float ADNS_Y_TRANSFORM = 1.0;
keyboard_config_t keyboard_config; keyboard_config_t keyboard_config;
uint16_t dpi_array[] = PLOOPY_DPI_OPTIONS; uint16_t dpi_array[] = PLOOPY_DPI_OPTIONS;
#define DPI_OPTION_SIZE (sizeof(dpi_array) / sizeof(uint16_t)) #define DPI_OPTION_SIZE (sizeof(dpi_array) / sizeof(uint16_t))
@ -66,74 +63,10 @@ uint16_t dpi_array[] = PLOOPY_DPI_OPTIONS;
bool is_scroll_clicked = false; bool is_scroll_clicked = false;
bool BurstState = false; // init burst state for Trackball module bool BurstState = false; // init burst state for Trackball module
uint16_t MotionStart = 0; // Timer for accel, 0 is resting state uint16_t MotionStart = 0; // Timer for accel, 0 is resting state
uint16_t lastScroll = 0; // Previous confirmed wheel event
uint16_t lastMidClick = 0; // Stops scrollwheel from being read if it was pressed
uint8_t OptLowPin = OPT_ENC1;
bool debug_encoder = false;
__attribute__((weak)) void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { void pointing_device_init_kb(void) {
mouse_report->x = x; // set the DPI.
mouse_report->y = y; pointing_device_set_cpi(dpi_array[keyboard_config.dpi_config]);
}
__attribute__((weak)) void process_mouse(report_mouse_t* mouse_report) {
report_adns_t data = adns_read_burst();
if (data.dx != 0 || data.dy != 0) {
if (debug_mouse)
dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy);
// Apply delta-X and delta-Y transformations.
// x and y are swapped
// the sensor is rotated
// by 90 degrees
float xt = (float) data.dy * ADNS_X_TRANSFORM;
float yt = (float) data.dx * ADNS_Y_TRANSFORM;
int16_t xti = (int16_t)xt;
int16_t yti = (int16_t)yt;
process_mouse_user(mouse_report, xti, yti);
}
}
bool process_record_kb(uint16_t keycode, keyrecord_t* record) {
xprintf("KL: kc: %u, col: %u, row: %u, pressed: %u\n", keycode, record->event.key.col, record->event.key.row, record->event.pressed);
// Update Timer to prevent accidental scrolls
if ((record->event.key.col == 1) && (record->event.key.row == 0)) {
lastMidClick = timer_read();
is_scroll_clicked = record->event.pressed;
}
if (!process_record_user(keycode, record))
return false;
if (keycode == DPI_CONFIG && record->event.pressed) {
keyboard_config.dpi_config = (keyboard_config.dpi_config + 1) % DPI_OPTION_SIZE;
eeconfig_update_kb(keyboard_config.raw);
adns_set_cpi(dpi_array[keyboard_config.dpi_config]);
}
/* If Mousekeys is disabled, then use handle the mouse button
* keycodes. This makes things simpler, and allows usage of
* the keycodes in a consistent manner. But only do this if
* Mousekeys is not enable, so it's not handled twice.
*/
#ifndef MOUSEKEY_ENABLE
if (IS_MOUSEKEY_BUTTON(keycode)) {
report_mouse_t currentReport = pointing_device_get_report();
if (record->event.pressed) {
currentReport.buttons |= 1 << (keycode - KC_MS_BTN1);
} else {
currentReport.buttons &= ~(1 << (keycode - KC_MS_BTN1));
}
pointing_device_set_report(currentReport);
pointing_device_send();
}
#endif
return true;
} }
// Hardware Setup // Hardware Setup
@ -143,9 +76,6 @@ void keyboard_pre_init_kb(void) {
// debug_mouse = true; // debug_mouse = true;
// debug_encoder = true; // debug_encoder = true;
setPinInput(OPT_ENC1);
setPinInput(OPT_ENC2);
/* Ground all output pins connected to ground. This provides additional /* Ground all output pins connected to ground. This provides additional
* pathways to ground. If you're messing with this, know this: driving ANY * pathways to ground. If you're messing with this, know this: driving ANY
* of these pins high will cause a short. On the MCU. Ka-blooey. * of these pins high will cause a short. On the MCU. Ka-blooey.
@ -162,34 +92,6 @@ void keyboard_pre_init_kb(void) {
keyboard_pre_init_user(); keyboard_pre_init_user();
} }
void pointing_device_init(void) {
adns_init();
opt_encoder_init();
// reboot the adns.
// if the adns hasn't initialized yet, this is harmless.
adns_write_reg(REG_CHIP_RESET, 0x5a);
// wait maximum time before adns is ready.
// this ensures that the adns is actuall ready after reset.
wait_ms(55);
// read a burst from the adns and then discard it.
// gets the adns ready for write commands
// (for example, setting the dpi).
adns_read_burst();
// set the DPI.
adns_set_cpi(dpi_array[keyboard_config.dpi_config]);
}
void pointing_device_task(void) {
report_mouse_t mouse_report = pointing_device_get_report();
process_mouse(&mouse_report);
pointing_device_set_report(mouse_report);
pointing_device_send();
}
void eeconfig_init_kb(void) { void eeconfig_init_kb(void) {
keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT; keyboard_config.dpi_config = PLOOPY_DPI_DEFAULT;
eeconfig_update_kb(keyboard_config.raw); eeconfig_update_kb(keyboard_config.raw);

View file

@ -20,19 +20,6 @@
#pragma once #pragma once
#include "quantum.h" #include "quantum.h"
#include "drivers/sensors/adns5050.h"
#include "analog.h"
#include "opt_encoder.h"
#include "pointing_device.h"
// Sensor defs
#define OPT_ENC1 F0
#define OPT_ENC2 F4
#define OPT_ENC1_MUX 0
#define OPT_ENC2_MUX 4
void process_mouse(report_mouse_t* mouse_report);
void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y);
#define LAYOUT(k00) {{ KC_NO }} #define LAYOUT(k00) {{ KC_NO }}

View file

@ -1,5 +1,4 @@
EEPROM_DRIVER = i2c EEPROM_DRIVER = i2c
POINTING_DEVICE_ENABLE = yes POINTING_DEVICE_ENABLE = yes
SRC += drivers/sensors/pimoroni_trackball.c POINTING_DEVICE_DRIVER = pimoroni_trackball
QUANTUM_LIB_SRC += i2c_master.c

View file

@ -186,20 +186,20 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
#ifdef PIMORONI_TRACKBALL_ENABLE #ifdef PIMORONI_TRACKBALL_ENABLE
void run_trackball_cleanup(void) { void run_trackball_cleanup(void) {
if (trackball_is_scrolling()) { // if (trackball_is_scrolling()) {
trackball_set_rgbw(RGB_CYAN, 0x00); // trackball_set_rgbw(RGB_CYAN, 0x00);
} else if (trackball_get_precision() != 1.0) { // } else if (trackball_get_precision() != 1.0) {
trackball_set_rgbw(RGB_GREEN, 0x00); // trackball_set_rgbw(RGB_GREEN, 0x00);
} else { // } else {
trackball_set_rgbw(RGB_MAGENTA, 0x00); // trackball_set_rgbw(RGB_MAGENTA, 0x00);
} // }
} }
void keyboard_post_init_keymap(void) { void keyboard_post_init_keymap(void) {
// trackball_set_precision(1.5); // trackball_set_precision(1.5);
trackball_set_rgbw(RGB_MAGENTA, 0x00); // trackball_set_rgbw(RGB_MAGENTA, 0x00);
} }
void shutdown_keymap(void) { trackball_set_rgbw(RGB_RED, 0x00); } // void shutdown_keymap(void) { trackball_set_rgbw(RGB_RED, 0x00); }
static bool mouse_button_one, trackball_button_one; static bool mouse_button_one, trackball_button_one;
@ -244,16 +244,16 @@ bool process_record_keymap(uint16_t keycode, keyrecord_t *record) {
break; break;
#ifdef PIMORONI_TRACKBALL_ENABLE #ifdef PIMORONI_TRACKBALL_ENABLE
case PM_SCROLL: case PM_SCROLL:
trackball_set_scrolling(record->event.pressed); // trackball_set_scrolling(record->event.pressed);
run_trackball_cleanup(); run_trackball_cleanup();
break; break;
case PM_PRECISION: case PM_PRECISION:
if (record->event.pressed) { // if (record->event.pressed) {
trackball_set_precision(1.5); // trackball_set_precision(1.5);
} else { // } else {
trackball_set_precision(1); // trackball_set_precision(1);
} // }
run_trackball_cleanup(); // run_trackball_cleanup();
break; break;
# if !defined(MOUSEKEY_ENABLE) # if !defined(MOUSEKEY_ENABLE)
case KC_MS_BTN1: case KC_MS_BTN1:

View file

@ -1,34 +1,56 @@
/* /* Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com> * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* Copyright 2021 Dasky (@daskygit)
This program is free software: you can redistribute it and/or modify *
it under the terms of the GNU General Public License as published by * This program is free software: you can redistribute it and/or modify
the Free Software Foundation, either version 2 of the License, or * it under the terms of the GNU General Public License as published by
(at your option) any later version. * the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
This program is distributed in the hope that it will be useful, *
but WITHOUT ANY WARRANTY; without even the implied warranty of * This program is distributed in the hope that it will be useful,
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
You should have received a copy of the GNU General Public License *
along with this program. If not, see <http://www.gnu.org/licenses/>. * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <stdint.h>
#include "report.h"
#include "host.h"
#include "timer.h"
#include "print.h"
#include "debug.h"
#include "pointing_device.h" #include "pointing_device.h"
#ifdef MOUSEKEY_ENABLE
# include "mousekey.h"
#endif
#if (defined(POINTING_DEVICE_ROTATION_90) + defined(POINTING_DEVICE_ROTATION_180) + defined(POINTING_DEVICE_ROTATION_270)) > 1
# error More than one rotation selected. This is not supported.
#endif
static report_mouse_t mouseReport = {}; static report_mouse_t mouseReport = {};
__attribute__((weak)) bool has_mouse_report_changed(report_mouse_t new, report_mouse_t old) { return (new.buttons != old.buttons) || (new.x&& new.x != old.x) || (new.y&& new.y != old.y) || (new.h&& new.h != old.h) || (new.v&& new.v != old.v); } extern const pointing_device_driver_t pointing_device_driver;
__attribute__((weak)) bool has_mouse_report_changed(report_mouse_t new, report_mouse_t old) { return memcmp(&new, &old, sizeof(new)); }
__attribute__((weak)) void pointing_device_init_kb(void) {}
__attribute__((weak)) void pointing_device_init_user(void) {}
__attribute__((weak)) report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) { return pointing_device_task_user(mouse_report); }
__attribute__((weak)) report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) { return mouse_report; }
__attribute__((weak)) uint8_t pointing_device_handle_buttons(uint8_t buttons, bool pressed, pointing_device_buttons_t button) {
if (pressed) {
buttons |= 1 << (button);
} else {
buttons &= ~(1 << (button));
}
return buttons;
}
__attribute__((weak)) void pointing_device_init(void) { __attribute__((weak)) void pointing_device_init(void) {
// initialize device, if that needs to be done. pointing_device_driver.init();
#ifdef POINTING_DEVICE_MOTION_PIN
setPinInputHigh(POINTING_DEVICE_MOTION_PIN);
#endif
pointing_device_init_kb();
pointing_device_init_user();
} }
__attribute__((weak)) void pointing_device_send(void) { __attribute__((weak)) void pointing_device_send(void) {
@ -43,20 +65,55 @@ __attribute__((weak)) void pointing_device_send(void) {
mouseReport.y = 0; mouseReport.y = 0;
mouseReport.v = 0; mouseReport.v = 0;
mouseReport.h = 0; mouseReport.h = 0;
old_report = mouseReport;
memcpy(&old_report, &mouseReport, sizeof(mouseReport));
} }
__attribute__((weak)) void pointing_device_task(void) { __attribute__((weak)) void pointing_device_task(void) {
// gather info and put it in: // Gather report info
// mouseReport.x = 127 max -127 min #ifdef POINTING_DEVICE_MOTION_PIN
// mouseReport.y = 127 max -127 min if (!readPin(POINTING_DEVICE_MOTION_PIN))
// mouseReport.v = 127 max -127 min (scroll vertical) #endif
// mouseReport.h = 127 max -127 min (scroll horizontal) mouseReport = pointing_device_driver.get_report(mouseReport);
// mouseReport.buttons = 0x1F (decimal 31, binary 00011111) max (bitmask for mouse buttons 1-5, 1 is rightmost, 5 is leftmost) 0x00 min
// send the report // Support rotation of the sensor data
#if defined(POINTING_DEVICE_ROTATION_90) || defined(POINTING_DEVICE_ROTATION_180) || defined(POINTING_DEVICE_ROTATION_270)
int8_t x = mouseReport.x, y = mouseReport.y;
# if defined(POINTING_DEVICE_ROTATION_90)
mouseReport.x = y;
mouseReport.y = -x;
# elif defined(POINTING_DEVICE_ROTATION_180)
mouseReport.x = -x;
mouseReport.y = -y;
# elif defined(POINTING_DEVICE_ROTATION_270)
mouseReport.x = -y;
mouseReport.y = x;
# else
# error "How the heck did you get here?!"
# endif
#endif
// Support Inverting the X and Y Axises
#if defined(POINTING_DEVICE_INVERT_X)
mouseReport.x = -mouseReport.x;
#endif
#if defined(POINTING_DEVICE_INVERT_Y)
mouseReport.y = -mouseReport.y;
#endif
// allow kb to intercept and modify report
mouseReport = pointing_device_task_kb(mouseReport);
// combine with mouse report to ensure that the combined is sent correctly
#ifdef MOUSEKEY_ENABLE
report_mouse_t mousekey_report = mousekey_get_report();
mouseReport.buttons = mouseReport.buttons | mousekey_report.buttons;
#endif
pointing_device_send(); pointing_device_send();
} }
report_mouse_t pointing_device_get_report(void) { return mouseReport; } report_mouse_t pointing_device_get_report(void) { return mouseReport; }
void pointing_device_set_report(report_mouse_t newMouseReport) { mouseReport = newMouseReport; } void pointing_device_set_report(report_mouse_t newMouseReport) { mouseReport = newMouseReport; }
uint16_t pointing_device_get_cpi(void) { return pointing_device_driver.get_cpi(); }
void pointing_device_set_cpi(uint16_t cpi) { pointing_device_driver.set_cpi(cpi); }

View file

@ -21,9 +21,68 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "host.h" #include "host.h"
#include "report.h" #include "report.h"
#if defined(POINTING_DEVICE_DRIVER_adns5050)
# include "drivers/sensors/adns5050.h"
#elif defined(POINTING_DEVICE_DRIVER_adns9800)
# include "spi_master.h"
# include "drivers/sensors/adns9800.h"
#elif defined(POINTING_DEVICE_DRIVER_analog_joystick)
# include "analog.h"
# include "drivers/sensors/analog_joystick.h"
#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
# include "drivers/sensors/cirque_pinnacle.h"
#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
# include "i2c_master.h"
# include "drivers/sensors/pimoroni_trackball.h"
// support for legacy pimoroni defines
# ifdef PIMORONI_TRACKBALL_INVERT_X
# define POINTING_DEVICE_INVERT_X
# endif
# ifdef PIMORONI_TRACKBALL_INVERT_Y
# define POINTING_DEVICE_INVERT_Y
# endif
# ifdef PIMORONI_TRACKBALL_ROTATE
# define POINTING_DEVICE_ROTATION_90
# endif
#elif defined(POINTING_DEVICE_DRIVER_pmw3360)
# include "spi_master.h"
# include "drivers/sensors/pmw3360.h"
#else
void pointing_device_driver_init(void);
report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report);
uint16_t pointing_device_driver_get_cpi(void);
void pointing_device_driver_set_cpi(uint16_t cpi);
#endif
typedef struct {
void (*init)(void);
report_mouse_t (*get_report)(report_mouse_t mouse_report);
void (*set_cpi)(uint16_t);
uint16_t (*get_cpi)(void);
} pointing_device_driver_t;
typedef enum {
POINTING_DEVICE_BUTTON1,
POINTING_DEVICE_BUTTON2,
POINTING_DEVICE_BUTTON3,
POINTING_DEVICE_BUTTON4,
POINTING_DEVICE_BUTTON5,
POINTING_DEVICE_BUTTON6,
POINTING_DEVICE_BUTTON7,
POINTING_DEVICE_BUTTON8,
} pointing_device_buttons_t;
void pointing_device_init(void); void pointing_device_init(void);
void pointing_device_task(void); void pointing_device_task(void);
void pointing_device_send(void); void pointing_device_send(void);
report_mouse_t pointing_device_get_report(void); report_mouse_t pointing_device_get_report(void);
void pointing_device_set_report(report_mouse_t newMouseReport); void pointing_device_set_report(report_mouse_t newMouseReport);
bool has_mouse_report_changed(report_mouse_t new, report_mouse_t old); bool has_mouse_report_changed(report_mouse_t new, report_mouse_t old);
uint16_t pointing_device_get_cpi(void);
void pointing_device_set_cpi(uint16_t cpi);
void pointing_device_init_kb(void);
void pointing_device_init_user(void);
report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report);
report_mouse_t pointing_device_task_user(report_mouse_t mouse_report);
uint8_t pointing_device_handle_buttons(uint8_t buttons, bool pressed, pointing_device_buttons_t button);

View file

@ -0,0 +1,262 @@
/* Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
* Copyright 2021 Dasky (@daskygit)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pointing_device.h"
#include "debug.h"
#include "wait.h"
#include "timer.h"
#include <stddef.h>
// hid mouse reports cannot exceed -127 to 127, so constrain to that value
#define constrain_hid(amt) ((amt) < -127 ? -127 : ((amt) > 127 ? 127 : (amt)))
// get_report functions should probably be moved to their respective drivers.
#if defined(POINTING_DEVICE_DRIVER_adns5050)
report_mouse_t adns5050_get_report(report_mouse_t mouse_report) {
report_adns5050_t data = adns5050_read_burst();
if (data.dx != 0 || data.dy != 0) {
# ifdef CONSOLE_ENABLE
if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy);
# endif
mouse_report.x = data.dx;
mouse_report.y = data.dy;
}
return mouse_report;
}
// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = adns5050_init,
.get_report = adns5050_get_report,
.set_cpi = adns5050_set_cpi,
.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) {
report_adns9800_t sensor_report = adns9800_get_report();
int8_t clamped_x = constrain_hid(sensor_report.x);
int8_t clamped_y = constrain_hid(sensor_report.y);
mouse_report.x = clamped_x;
mouse_report.y = clamped_y;
return mouse_report;
}
// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = adns9800_init,
.get_report = adns9800_get_report_driver,
.set_cpi = adns9800_set_cpi,
.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();
# ifdef CONSOLE_ENABLE
if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.x, data.y);
# endif
mouse_report.x = data.x;
mouse_report.y = data.y;
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, data.button, POINTING_DEVICE_BUTTON1);
return mouse_report;
}
// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = analog_joystick_init,
.get_report = analog_joystick_get_report,
.set_cpi = NULL,
.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
# ifdef TAPPING_TERM_PER_KEY
# include "action.h"
# include "action_tapping.h"
# define CIRQUE_PINNACLE_TAPPING_TERM get_tapping_term(KC_BTN1, NULL)
# else
# ifdef TAPPING_TERM
# define CIRQUE_PINNACLE_TAPPING_TERM TAPPING_TERM
# else
# define CIRQUE_PINNACLE_TAPPING_TERM 200
# endif
# endif
# endif
# ifndef CIRQUE_PINNACLE_TOUCH_DEBOUNCE
# define CIRQUE_PINNACLE_TOUCH_DEBOUNCE (CIRQUE_PINNACLE_TAPPING_TERM * 8)
# 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;
int8_t report_x = 0, report_y = 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 (x && y && touchData.xValue && touchData.yValue) {
report_x = (int8_t)(touchData.xValue - x);
report_y = (int8_t)(touchData.yValue - y);
}
x = touchData.xValue;
y = touchData.yValue;
if ((bool)touchData.zValue != is_z_down) {
is_z_down = (bool)touchData.zValue;
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);
pointing_device_set_report(mouse_report);
pointing_device_send();
# if TAP_CODE_DELAY > 0
wait_ms(TAP_CODE_DELAY);
# endif
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1);
pointing_device_set_report(mouse_report);
pointing_device_send();
}
}
mouse_timer = timer_read();
}
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;
}
// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = cirque_pinnacle_init,
.get_report = cirque_pinnacle_get_report,
.set_cpi = cirque_pinnacle_set_scale,
.get_cpi = cirque_pinnacle_get_scale
};
// clang-format on
#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
report_mouse_t pimorono_trackball_get_report(report_mouse_t mouse_report) {
static fast_timer_t throttle = 0;
static uint16_t debounce = 0;
static uint8_t error_count = 0;
pimoroni_data_t pimoroni_data = {0};
static int16_t x_offset = 0, y_offset = 0;
if (error_count < PIMORONI_TRACKBALL_ERROR_COUNT && timer_elapsed_fast(throttle) >= PIMORONI_TRACKBALL_INTERVAL_MS) {
i2c_status_t status = read_pimoroni_trackball(&pimoroni_data);
if (status == I2C_STATUS_SUCCESS) {
error_count = 0;
if (!(pimoroni_data.click & 128)) {
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1);
if (!debounce) {
x_offset += pimoroni_trackball_get_offsets(pimoroni_data.right, pimoroni_data.left, PIMORONI_TRACKBALL_SCALE);
y_offset += pimoroni_trackball_get_offsets(pimoroni_data.down, pimoroni_data.up, PIMORONI_TRACKBALL_SCALE);
pimoroni_trackball_adapt_values(&mouse_report.x, &x_offset);
pimoroni_trackball_adapt_values(&mouse_report.y, &y_offset);
} else {
debounce--;
}
} else {
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
debounce = PIMORONI_TRACKBALL_DEBOUNCE_CYCLES;
}
} else {
error_count++;
}
throttle = timer_read_fast();
}
return mouse_report;
}
// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = pimironi_trackball_device_init,
.get_report = pimorono_trackball_get_report,
.set_cpi = NULL,
.get_cpi = NULL
};
// clang-format on
#elif defined(POINTING_DEVICE_DRIVER_pmw3360)
static void init(void) { pmw3360_init(); }
report_mouse_t pmw3360_get_report(report_mouse_t mouse_report) {
report_pmw3360_t data = pmw3360_read_burst();
static uint16_t MotionStart = 0; // Timer for accel, 0 is resting state
if (data.isOnSurface && data.isMotion) {
// Reset timer if stopped moving
if (!data.isMotion) {
if (MotionStart != 0) MotionStart = 0;
return mouse_report;
}
// Set timer if new motion
if ((MotionStart == 0) && data.isMotion) {
# ifdef CONSOLE_ENABLE
if (debug_mouse) dprintf("Starting motion.\n");
# endif
MotionStart = timer_read();
}
mouse_report.x = constrain_hid(data.dx);
mouse_report.y = constrain_hid(data.dy);
}
return mouse_report;
}
// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = init,
.get_report = pmw3360_get_report,
.set_cpi = pmw3360_set_cpi,
.get_cpi = pmw3360_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) { return mouse_report; }
__attribute__((weak)) uint16_t pointing_device_driver_get_cpi(void) { return 0; }
__attribute__((weak)) void pointing_device_driver_set_cpi(uint16_t cpi) {}
// clang-format off
const pointing_device_driver_t pointing_device_driver = {
.init = pointing_device_driver_init,
.get_report = pointing_device_driver_get_report,
.get_cpi = pointing_device_driver_get_cpi,
.set_cpi = pointing_device_driver_set_cpi
};
// clang-format on
#endif

View file

@ -531,6 +531,10 @@ void suspend_power_down_quantum(void) {
# ifdef ST7565_ENABLE # ifdef ST7565_ENABLE
st7565_off(); st7565_off();
# endif # endif
# if defined(POINTING_DEVICE_ENABLE)
// run to ensure scanning occurs while suspended
pointing_device_task();
# endif
#endif #endif
} }

View file

@ -204,6 +204,10 @@ extern layer_state_t layer_state;
# include "encoder.h" # include "encoder.h"
#endif #endif
#ifdef POINTING_DEVICE_ENABLE
# include "pointing_device.h"
#endif
// For tri-layer // For tri-layer
void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3); void update_tri_layer(uint8_t layer1, uint8_t layer2, uint8_t layer3);
layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3); layer_state_t update_tri_layer_state(layer_state_t state, uint8_t layer1, uint8_t layer2, uint8_t layer3);