* Initial Commit for Ploopyco Trackball This is a WIP at this point. Most of it compiles, but the SPI commands are non-functioning as they come from Arduino, so don't exist in LUFA * Convert SPI commands from arduino to LUFA But I have no idea if this is actually correct or not * Update keyboard readme * Clean up ploopyco trackball * Update readme * Update mouse key stuff * last minutue cleanup * Add caveat about scroll wheel * Fixup to code * Additional fixup * Add movement multiplier * Rename folders * mid changes * temp * Got it working!!!!! * Additional cleanup of code * Make unused pin calls more compact * Rotation info * Add debouncing checks * Make everything replaceable * Add info.json * Include ISP flashing info * Better handling for user customization * Reconfigure CPI stuff * fix issues with debug printing * Fix tiny scroll issue * Add and update scroll code from ploopy mouse * Update licensing * Add PloopyCo Mouse * Cleanup and layout stuff * Move common files to main folder for reuse * Increase polling rate * Update image for mouse * Apply changes from code review * Add VIA supportpull/10467/head
@ -0,0 +1,73 @@ | |||
/* 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 | |||
#include "config_common.h" | |||
/* USB Device descriptor parameter */ | |||
#define VENDOR_ID 0x5043 | |||
#define PRODUCT_ID 0x4D6F | |||
#define DEVICE_VER 0x0001 | |||
#define MANUFACTURER Ploopyco | |||
#define PRODUCT Mouse | |||
/* key matrix size */ | |||
#define MATRIX_ROWS 1 | |||
#define MATRIX_COLS 8 | |||
/* | |||
* Keyboard Matrix Assignments | |||
* | |||
* Change this to how you wired your keyboard | |||
* COLS: AVR pins used for columns, left to right | |||
* ROWS: AVR pins used for rows, top to bottom | |||
* DIODE_DIRECTION: COL2ROW = COL = Anode (+), ROW = Cathode (-, marked on diode) | |||
* ROW2COL = ROW = Anode (+), COL = Cathode (-, marked on diode) | |||
* | |||
*/ | |||
#define DIRECT_PINS \ | |||
{ \ | |||
{ D4, D2, E6, B6, D7, C6, C7, B7 } \ | |||
} | |||
// These pins are not broken out, and cannot be used normally. | |||
// They are set as output and pulled high, by default | |||
#define UNUSED_PINS \ | |||
{ B4, D6, F1, F5, F6, F7 } | |||
/* Debounce reduces chatter (unintended double-presses) - set 0 if debouncing is not needed */ | |||
#define DEBOUNCE 5 | |||
/* Much more so than a keyboard, speed matters for a mouse. So we'll go for as high | |||
a polling rate as possible. */ | |||
#define USB_POLLING_INTERVAL_MS 1 | |||
/* define if matrix has ghost (lacks anti-ghosting diodes) */ | |||
//#define MATRIX_HAS_GHOST | |||
/* disable action features */ | |||
//#define NO_ACTION_LAYER | |||
//#define NO_ACTION_TAPPING | |||
//#define NO_ACTION_ONESHOT | |||
#define NO_ACTION_MACRO | |||
#define NO_ACTION_FUNCTION | |||
/* Bootmagic Lite key configuration */ | |||
#define BOOTMAGIC_LITE_ROW 0 | |||
#define BOOTMAGIC_LITE_COLUMN 3 |
@ -0,0 +1,21 @@ | |||
{ | |||
"keyboard_name": "PloopyCo Mouse", | |||
"url": "", | |||
"maintainer": "drashna", | |||
"width": 8, | |||
"height": 3, | |||
"layouts": { | |||
"LAYOUT": { | |||
"layout": [ | |||
{"x":1, "y":0, "h":2}, | |||
{"x":2, "y":0, "h":2}, | |||
{"x":3, "y":0.25, "h":1.25}, | |||
{"x":4, "y":0, "h":2}, | |||
{"x":5, "y":0, "h":2}, | |||
{"x":0, "y":0}, | |||
{"x":0, "y":1}, | |||
{"x":3, "y":1.5} | |||
] | |||
} | |||
} | |||
} |
@ -0,0 +1,23 @@ | |||
/* 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/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |||
[0] = LAYOUT(/* Base */ | |||
C(KC_C), KC_BTN1, KC_BTN3, KC_BTN2, C(KC_C), KC_BTN4, KC_BTN5, C(KC_Z)), | |||
}; |
@ -0,0 +1 @@ | |||
# The default keymap for Ploopyco Trackball |
@ -0,0 +1,26 @@ | |||
/* 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/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |||
[0] = LAYOUT(/* Base */ | |||
C(KC_C), KC_BTN1, KC_BTN3, KC_BTN2, C(KC_C), KC_BTN4, KC_BTN5, C(KC_Z)), | |||
[1] = LAYOUT(_______, _______, _______, _______, _______, _______, _______, _______), | |||
[2] = LAYOUT(_______, _______, _______, _______, _______, _______, _______, _______), | |||
[3] = LAYOUT(_______, _______, _______, _______, _______, _______, _______, _______), | |||
}; |
@ -0,0 +1 @@ | |||
VIA_ENABLE = yes |
@ -0,0 +1,237 @@ | |||
/* 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/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
#ifndef OPT_DEBOUNCE | |||
# define OPT_DEBOUNCE 5 // (ms) Time between scroll events | |||
#endif | |||
#ifndef SCROLL_BUTT_DEBOUNCE | |||
# define SCROLL_BUTT_DEBOUNCE 100 // (ms) Time between scroll events | |||
#endif | |||
#ifndef OPT_THRES | |||
# define OPT_THRES 150 // (0-1024) Threshold for actication | |||
#endif | |||
#ifndef OPT_SCALE | |||
# define OPT_SCALE 1 // Multiplier for wheel | |||
#endif | |||
// TODO: Implement libinput profiles | |||
// https://wayland.freedesktop.org/libinput/doc/latest/pointer-acceleration.html | |||
// Compile time accel selection | |||
// Valid options are ACC_NONE, ACC_LINEAR, ACC_CUSTOM, ACC_QUADRATIC | |||
// Trackball State | |||
bool is_scroll_clicked = false; | |||
bool BurstState = false; // init burst state for Trackball module | |||
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_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v) { | |||
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 | |||
// If the mouse wheel was just released, do not scroll. | |||
if (timer_elapsed(lastMidClick) < SCROLL_BUTT_DEBOUNCE) { | |||
return; | |||
} | |||
// Limit the number of scrolls per unit time. | |||
if (timer_elapsed(lastScroll) < OPT_DEBOUNCE) { | |||
return; | |||
} | |||
// Don't scroll if the middle button is depressed. | |||
if (is_scroll_clicked) { | |||
#ifndef IGNORE_SCROLL_CLICK | |||
return; | |||
#endif | |||
} | |||
lastScroll = timer_read(); | |||
uint16_t p1 = adc_read(OPT_ENC1_MUX); | |||
uint16_t p2 = adc_read(OPT_ENC2_MUX); | |||
if (debug_encoder) dprintf("OPT1: %d, OPT2: %d\n", p1, p2); | |||
uint8_t dir = opt_encoder_handler(p1, p2); | |||
if (dir == 0) return; | |||
process_wheel_user(mouse_report, mouse_report->h, (int)(mouse_report->v + (dir * OPT_SCALE))); | |||
} | |||
__attribute__((weak)) void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { | |||
mouse_report->x = x; | |||
mouse_report->y = y; | |||
} | |||
__attribute__((weak)) void process_mouse(report_mouse_t* mouse_report) { | |||
report_pmw_t data = pmw_read_burst(); | |||
if (data.isOnSurface && data.isMotion) { | |||
// Reset timer if stopped moving | |||
if (!data.isMotion) { | |||
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 | |||
// no post processing | |||
#endif | |||
// apply multiplier | |||
// data.dx *= mouse_multiplier; | |||
// 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); | |||
} | |||
} | |||
bool process_record_kb(uint16_t keycode, keyrecord_t* record) { | |||
if (debug_mouse) { | |||
dprintf("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 == 2) && (record->event.key.row == 0)) { | |||
lastMidClick = timer_read(); | |||
is_scroll_clicked = record->event.pressed; | |||
} | |||
/* 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) { | |||
if (keycode == KC_MS_BTN1) | |||
currentReport.buttons |= MOUSE_BTN1; | |||
else if (keycode == KC_MS_BTN2) | |||
currentReport.buttons |= MOUSE_BTN2; | |||
else if (keycode == KC_MS_BTN3) | |||
currentReport.buttons |= MOUSE_BTN3; | |||
else if (keycode == KC_MS_BTN4) | |||
currentReport.buttons |= MOUSE_BTN4; | |||
else if (keycode == KC_MS_BTN5) | |||
currentReport.buttons |= MOUSE_BTN5; | |||
} else { | |||
if (keycode == KC_MS_BTN1) | |||
currentReport.buttons &= ~MOUSE_BTN1; | |||
else if (keycode == KC_MS_BTN2) | |||
currentReport.buttons &= ~MOUSE_BTN2; | |||
else if (keycode == KC_MS_BTN3) | |||
currentReport.buttons &= ~MOUSE_BTN3; | |||
else if (keycode == KC_MS_BTN4) | |||
currentReport.buttons &= ~MOUSE_BTN4; | |||
else if (keycode == KC_MS_BTN5) | |||
currentReport.buttons &= ~MOUSE_BTN5; | |||
} | |||
pointing_device_set_report(currentReport); | |||
} | |||
#endif | |||
return process_record_user(keycode, record); | |||
} | |||
// Hardware Setup | |||
void keyboard_pre_init_kb(void) { | |||
// debug_enable = true; | |||
// debug_matrix = true; | |||
// debug_mouse = true; | |||
// debug_encoder = true; | |||
setPinInput(OPT_ENC1); | |||
setPinInput(OPT_ENC2); | |||
// This is the debug LED. | |||
setPinOutput(F7); | |||
writePin(F7, debug_enable); | |||
/* Ground all output pins connected to ground. This provides additional | |||
* 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. | |||
*/ | |||
#ifdef UNUSED_PINS | |||
const pin_t unused_pins[] = UNUSED_PINS; | |||
for (uint8_t i = 0; i < (sizeof(unused_pins) / sizeof(pin_t)); i++) { | |||
setPinOutput(unused_pins[i]); | |||
writePinLow(unused_pins[i]); | |||
} | |||
#endif | |||
keyboard_pre_init_user(); | |||
} | |||
void pointing_device_init(void) { | |||
// initialize ball sensor | |||
pmw_spi_init(); | |||
// initialize the scroll wheel's optical encoder | |||
opt_encoder_init(); | |||
} | |||
bool has_report_changed (report_mouse_t first, report_mouse_t second) { | |||
return !( | |||
(!first.buttons && first.buttons == second.buttons) && | |||
(!first.x && first.x == second.x) && | |||
(!first.y && first.y == second.y) && | |||
(!first.h && first.h == second.h) && | |||
(!first.v && first.v == second.v) ); | |||
} | |||
void pointing_device_task(void) { | |||
report_mouse_t mouse_report = pointing_device_get_report(); | |||
process_wheel(&mouse_report); | |||
process_mouse(&mouse_report); | |||
pointing_device_set_report(mouse_report); | |||
if (has_report_changed(mouse_report, pointing_device_get_report())) { | |||
pointing_device_send(); | |||
} | |||
} |
@ -0,0 +1,40 @@ | |||
/* 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 | |||
#include "quantum.h" | |||
#include "spi_master.h" | |||
#include "pmw3600.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); | |||
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) \ | |||
{ {BL, BM, BR, BF, BB, BRR, BLL, BDPI}, } |
@ -0,0 +1,68 @@ | |||
# Ploopyco Mouse | |||
![Ploopyco Mouse](https://i.redd.it/bf7bkzqzeti51.jpg) | |||
It's a DIY, QMK Powered Trackball!!!! | |||
Everything works. However the scroll wheel has some issues and acts very odd. | |||
* Keyboard Maintainer: [PloopyCo](https://github.com/ploopyco), [Drashna Jael're](https://github.com/drashna/), [Germ](https://github.com/germ/) | |||
* Hardware Supported: ATMega32u4 8MHz(3.3v) | |||
* Hardware Availability: [Store](https://ploopy.co), [GitHub](https://github.com/ploopyco) | |||
Make example for this keyboard (after setting up your build environment): | |||
make ploopyco/mouse:default:flash | |||
To jump to the bootloader, hold down "Button 4" (immediate right of the Mouse) | |||
See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs). | |||
# Customzing your PloopyCo Trackball | |||
While the defaults are designed so that it can be plugged in and used right away, there are a number of things that you may want to change. Such as adding DPI control, or to use the ball to scroll while holding a button. To allow for this sort of control, there is a callback for both the scroll wheel and the mouse censor. | |||
The default behavior for this is: | |||
```c | |||
void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v) { | |||
mouse_report->h = h; | |||
mouse_report->v = v; | |||
} | |||
void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { | |||
mouse_report->x = x; | |||
mouse_report->y = y; | |||
} | |||
``` | |||
This should allow you to more heavily customize the behavior. | |||
Alternatively, the `process_wheel` and `process_mouse` functions can both be replaced too, to allow for even more functionality. | |||
Additionally, you can change the DPI/CPI or speed of the Mouse by calling `pmw_set_cpi` at any time. And tThe default can be changed by adding a define to the keymap's `config.h` file: | |||
#define PMW_CPI 1600 | |||
# Programming QMK-DFU onto the PloopyCo Mouse | |||
If you would rather have DFU on this board, you can use the QMK-DFU bootloader on the device. To do so, you want to run: | |||
make ploopyco/trackball:default:production | |||
Once you have that, you'll need to [ISP Flash](https://docs.qmk.fm/#/isp_flashing_guide) the chip with the new bootloader hex file created (or the production hex), and set the fuses: | |||
| Fuse | Setting | | |||
|----------|------------------| | |||
| Low | `0xDF` | | |||
| High | `0xD8` or `0x98` | | |||
| Extended | `0xCB` | | |||
Original (Caterina) settings: | |||
| Fuse | Setting | | |||
|----------|------------------| | |||
| Low | `0xFF` | | |||
| High | `0xD8` | | |||
| Extended | `0xFE` | |
@ -0,0 +1,30 @@ | |||
# MCU name | |||
MCU = atmega32u4 | |||
# Processor frequency | |||
F_CPU = 8000000 | |||
# Bootloader selection | |||
BOOTLOADER = caterina | |||
# Build Options | |||
# change yes to no to disable | |||
# | |||
BOOTMAGIC_ENABLE = lite # Virtual DIP switch configuration | |||
EXTRAKEY_ENABLE = yes # Audio control and System control | |||
CONSOLE_ENABLE = yes # Console for debug | |||
COMMAND_ENABLE = no # Commands for debug and configuration | |||
# Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE | |||
SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend | |||
# if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work | |||
NKRO_ENABLE = no # USB Nkey Rollover | |||
BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality | |||
RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow | |||
UNICODE_ENABLE = no # Unicode | |||
BLUETOOTH_ENABLE = no # Enable Bluetooth | |||
AUDIO_ENABLE = no # Audio output | |||
POINTING_DEVICE_ENABLE = yes | |||
MOUSEKEY_ENABLE = no # Mouse keys | |||
QUANTUM_LIB_SRC += analog.c spi_master.c | |||
SRC += pmw3600.c opt_encoder.c |
@ -0,0 +1,211 @@ | |||
/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com> | |||
* 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/>. | |||
*/ | |||
#include "opt_encoder.h" | |||
/* Setup function for the scroll wheel. Initializes | |||
the relevant variables. */ | |||
void opt_encoder_init(void) { | |||
state = HIHI; | |||
lohif = false; | |||
hilof = false; | |||
lowA = 1023; | |||
highA = 0; | |||
cLowA = false; | |||
cHighA = false; | |||
lowIndexA = 0; | |||
highIndexA = 0; | |||
lowOverflowA = false; | |||
highOverflowA = false; | |||
lowB = 1023; | |||
highB = 0; | |||
cLowB = false; | |||
cHighB = false; | |||
lowIndexB = 0; | |||
highIndexB = 0; | |||
lowOverflowB = false; | |||
highOverflowB = false; | |||
scrollThresholdA = 0; | |||
scrollThresholdB = 0; | |||
} | |||
int opt_encoder_handler(int curA, int curB) { | |||
if (lowOverflowA == false || highOverflowA == false) calculateThresholdA(curA); | |||
if (lowOverflowB == false || highOverflowB == false) calculateThresholdB(curB); | |||
bool LO = false; | |||
bool HI = true; | |||
bool sA, sB; | |||
int ret = 0; | |||
if (curA < scrollThresholdA) | |||
sA = LO; | |||
else | |||
sA = HI; | |||
if (curB < scrollThresholdB) | |||
sB = LO; | |||
else | |||
sB = HI; | |||
if (state == HIHI) { | |||
if (sA == LO && sB == HI) { | |||
state = LOHI; | |||
if (hilof) { | |||
ret = 1; | |||
hilof = false; | |||
} | |||
} else if (sA == HI && sB == LO) { | |||
state = HILO; | |||
if (lohif) { | |||
ret = -1; | |||
lohif = false; | |||
} | |||
} | |||
} | |||
else if (state == HILO) { | |||
if (sA == HI && sB == HI) { | |||
state = HIHI; | |||
hilof = true; | |||
lohif = false; | |||
} else if (sA == LO && sB == LO) { | |||
state = LOLO; | |||
hilof = true; | |||
lohif = false; | |||
} | |||
} | |||
else if (state == LOLO) { | |||
if (sA == HI && sB == LO) { | |||
state = HILO; | |||
if (lohif) { | |||
ret = 1; | |||
lohif = false; | |||
} | |||
} else if (sA == LO && sB == HI) { | |||
state = LOHI; | |||
if (hilof) { | |||
ret = -1; | |||
hilof = false; | |||
} | |||
} | |||
} | |||
else { // state must be LOHI | |||
if (sA == HI && sB == HI) { | |||
state = HIHI; | |||
lohif = true; | |||
hilof = false; | |||
} else if (sA == LO && sB == LO) { | |||
state = LOLO; | |||
lohif = true; | |||
hilof = false; | |||
} | |||
} | |||
return ret; | |||
} | |||
void calculateThresholdA(int curA) { scrollThresholdA = calculateThreshold(curA, &lowA, &highA, &cLowA, &cHighA, arLowA, arHighA, &lowIndexA, &highIndexA, &lowOverflowA, &highOverflowA); } | |||
void calculateThresholdB(int curB) { scrollThresholdB = calculateThreshold(curB, &lowB, &highB, &cLowB, &cHighB, arLowB, arHighB, &lowIndexB, &highIndexB, &lowOverflowB, &highOverflowB); } | |||
int calculateThreshold(int cur, int* low, int* high, bool* cLow, bool* cHigh, int arLow[], int arHigh[], int* lowIndex, int* highIndex, bool* lowOverflow, bool* highOverflow) { | |||
if (cur < *low) *low = cur; | |||
if (cur > *high) *high = cur; | |||
int curThresh = thresholdEquation(*low, *high); | |||
int range = *high - *low; | |||
// The range is enforced to be over a certain limit because noise | |||
// can cause erroneous readings, making these calculations unstable. | |||
if (range >= SCROLL_THRESH_RANGE_LIM) { | |||
if (cur < curThresh) { | |||
if (*cHigh == true) { | |||
// We were just high, and now we crossed to low. | |||
// high reflects a sample of a high reading. | |||
arHigh[*highIndex] = *high; | |||
incrementIndex(highIndex, highOverflow); | |||
int midpoint = ((*high - *low) / 2) + *low; | |||
*low = midpoint; | |||
*high = midpoint; | |||
*cLow = false; | |||
*cHigh = false; | |||
} else { | |||
*cLow = true; | |||
} | |||
} | |||
if (cur > curThresh) { | |||
if (*cLow == true) { | |||
// We were just low, and now we crossed to high. | |||
// low reflects a sample of a low reading. | |||
arLow[*lowIndex] = *low; | |||
incrementIndex(lowIndex, lowOverflow); | |||
int midpoint = ((*high - *low) / 2) + *low; | |||
*low = midpoint; | |||
*high = midpoint; | |||
*cLow = false; | |||
*cHigh = false; | |||
} else { | |||
*cHigh = true; | |||
} | |||
} | |||
} | |||
int calcHigh = 0; | |||
if (*highOverflow == true) { | |||
for (int i = 0; i < SCROLLER_AR_SIZE; i++) { | |||
calcHigh += arHigh[i]; | |||
} | |||
calcHigh = calcHigh / SCROLLER_AR_SIZE; | |||
} else if (*highIndex != 0) { | |||
for (int i = 0; i < *highIndex; i++) { | |||
calcHigh += arHigh[i]; | |||
} | |||
calcHigh = calcHigh / *highIndex; | |||
} else { | |||
calcHigh = *high; | |||
} | |||
int calcLow = 0; | |||
if (*lowOverflow == true) { | |||
for (int i = 0; i < SCROLLER_AR_SIZE; i++) { | |||
calcLow += arLow[i]; | |||
} | |||
calcLow = calcLow / SCROLLER_AR_SIZE; | |||
} else if (*lowIndex != 0) { | |||
for (int i = 0; i < *lowIndex; i++) { | |||
calcLow += arLow[i]; | |||
} | |||
calcLow = calcLow / *lowIndex; | |||
} else { | |||
calcLow = *low; | |||
} | |||
return thresholdEquation(calcLow, calcHigh); | |||
} | |||
int thresholdEquation(int lo, int hi) { return ((hi - lo) / 3) + lo; } | |||
void incrementIndex(int* index, bool* ovflw) { | |||
if (*index < SCROLLER_AR_SIZE - 1) | |||
(*index)++; | |||
else { | |||
*index = 0; | |||
*ovflw = true; | |||
} | |||
} |
@ -0,0 +1,66 @@ | |||
/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com> | |||
* 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 | |||
#include <stdbool.h> | |||
#ifndef SCROLLER_AR_SIZE | |||
# define SCROLLER_AR_SIZE 31 | |||
#endif | |||
#ifndef SCROLL_THRESH_RANGE_LIM | |||
# define SCROLL_THRESH_RANGE_LIM 10 | |||
#endif | |||
enum State { HIHI, HILO, LOLO, LOHI }; | |||
enum State state; | |||
/* Variables used for scroll wheel functionality. */ | |||
bool lohif; | |||
bool hilof; | |||
int lowA; | |||
int highA; | |||
bool cLowA; | |||
bool cHighA; | |||
int lowIndexA; | |||
int highIndexA; | |||
bool lowOverflowA; | |||
bool highOverflowA; | |||
int lowB; | |||
int highB; | |||
bool cLowB; | |||
bool cHighB; | |||
int lowIndexB; | |||
int highIndexB; | |||
bool lowOverflowB; | |||
bool highOverflowB; | |||
int scrollThresholdA; | |||
int scrollThresholdB; | |||
int arLowA[SCROLLER_AR_SIZE]; | |||
int arHighA[SCROLLER_AR_SIZE]; | |||
int arLowB[SCROLLER_AR_SIZE]; | |||
int arHighB[SCROLLER_AR_SIZE]; | |||
void calculateThresholdA(int curA); | |||
void calculateThresholdB(int curB); | |||
int calculateThreshold(int cur, int* low, int* high, bool* cLow, bool* cHigh, int arLow[], int arHigh[], int* lowIndex, int* highIndex, bool* lowOverflow, bool* highOverflow); | |||
int thresholdEquation(int lo, int hi); | |||
void incrementIndex(int* index, bool* ovflw); | |||
void opt_encoder_init(void); | |||
int opt_encoder_handler(int curA, int curB); |
@ -0,0 +1,222 @@ | |||
/* 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/>. | |||
*/ | |||
#include "pmw3600.h" | |||
#include "pmw3600_firmware.h" | |||
#ifdef CONSOLE_ENABLE | |||
# include "print.h" | |||
#endif | |||
bool _inBurst = false; | |||
#ifndef PMW_CPI | |||
# define PMW_CPI 1600 | |||
#endif | |||
#ifndef SPI_DIVISOR | |||
# define SPI_DIVISOR 2 | |||
#endif | |||
static const int8_t ROTATIONAL_TRANSFORM_ANGLE = 20; | |||
#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')); } | |||
#endif | |||
bool spi_start_adv(void) { | |||
bool status = spi_start(SPI_SS_PIN, false, 3, SPI_DIVISOR); | |||
wait_us(1); | |||
return status; | |||
} | |||
void spi_stop_adv(void) { | |||
wait_us(1); | |||
spi_stop(); | |||
} | |||
spi_status_t spi_write_adv(uint8_t reg_addr, uint8_t data) { | |||
if (reg_addr != REG_Motion_Burst) { | |||
_inBurst = false; | |||
} | |||
spi_start_adv(); | |||
// send address of the register, with MSBit = 1 to indicate it's a write | |||
spi_status_t status = spi_write(reg_addr | 0x80); | |||
status = spi_write(data); | |||
// tSCLK-NCS for write operation | |||
wait_us(20); | |||
// tSWW/tSWR (=120us) minus tSCLK-NCS. Could be shortened, but is looks like a safe lower bound | |||
wait_us(100); | |||
spi_stop(); | |||
return status; | |||
} | |||
uint8_t spi_read_adv(uint8_t reg_addr) { | |||
spi_start_adv(); | |||
// send adress of the register, with MSBit = 0 to indicate it's a read | |||
spi_write(reg_addr & 0x7f); | |||
uint8_t data = spi_read(); | |||
// tSCLK-NCS for read operation is 120ns | |||
wait_us(1); | |||
// tSRW/tSRR (=20us) minus tSCLK-NCS | |||
wait_us(19); | |||
spi_stop(); | |||
return data; | |||
} | |||
void pmw_set_cpi(uint16_t cpi) { | |||
int cpival = constrain((cpi / 100) - 1, 0, 0x77); // limits to 0--119 | |||
spi_start_adv(); | |||
spi_write_adv(REG_Config1, cpival); | |||
spi_stop(); | |||
} | |||
bool pmw_spi_init(void) { | |||
spi_init(); | |||
_inBurst = false; | |||
spi_stop(); | |||
spi_start_adv(); | |||
spi_stop(); | |||
spi_write_adv(REG_Shutdown, 0xb6); // Shutdown first | |||
wait_ms(300); | |||
spi_start_adv(); | |||
wait_us(40); | |||
spi_stop_adv(); | |||
wait_us(40); | |||
spi_write_adv(REG_Power_Up_Reset, 0x5a); | |||
wait_ms(50); | |||
spi_read_adv(REG_Motion); | |||
spi_read_adv(REG_Delta_X_L); | |||
spi_read_adv(REG_Delta_X_H); | |||
spi_read_adv(REG_Delta_Y_L); | |||
spi_read_adv(REG_Delta_Y_H); | |||
pmw_upload_firmware(); | |||
spi_write_adv(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -30, 30)); | |||
spi_stop_adv(); | |||
wait_ms(10); | |||
pmw_set_cpi(PMW_CPI); | |||
wait_ms(1); | |||
return pmw_check_signature(); | |||
} | |||
void pmw_upload_firmware(void) { | |||
spi_write_adv(REG_Config2, 0x00); | |||
spi_write_adv(REG_SROM_Enable, 0x1d); | |||
wait_ms(10); | |||
spi_write_adv(REG_SROM_Enable, 0x18); | |||
spi_start_adv(); | |||
spi_write(REG_SROM_Load_Burst | 0x80); | |||
wait_us(15); | |||
unsigned char c; | |||
for (int i = 0; i < firmware_length; i++) { | |||
c = (unsigned char)pgm_read_byte(firmware_data + i); | |||
spi_write(c); | |||
wait_us(15); | |||
} | |||
wait_us(200); | |||
spi_read_adv(REG_SROM_ID); | |||
spi_write_adv(REG_Config2, 0x00); | |||
spi_stop(); | |||
wait_ms(10); | |||
} | |||
bool pmw_check_signature(void) { | |||
uint8_t pid = spi_read_adv(REG_Product_ID); | |||
uint8_t iv_pid = spi_read_adv(REG_Inverse_Product_ID); | |||
uint8_t SROM_ver = spi_read_adv(REG_SROM_ID); | |||
return (pid == 0x42 && iv_pid == 0xBD && SROM_ver == 0x04); // signature for SROM 0x04 | |||
} | |||
report_pmw_t pmw_read_burst(void) { | |||
if (!_inBurst) { | |||
#ifdef CONSOLE_ENABLE | |||
dprintf("burst on"); | |||
#endif | |||
spi_write_adv(REG_Motion_Burst, 0x00); | |||
_inBurst = true; | |||
} | |||
spi_start_adv(); | |||
spi_write(REG_Motion_Burst); | |||
wait_us(35); // waits for tSRAD | |||
report_pmw_t data; | |||
data.motion = 0; | |||
data.dx = 0; | |||
data.mdx = 0; | |||
data.dy = 0; | |||
data.mdx = 0; | |||
data.motion = spi_read(); | |||
spi_write(0x00); // skip Observation | |||
data.dx = spi_read(); | |||
data.mdx = spi_read(); | |||
data.dy = spi_read(); | |||
data.mdy = spi_read(); | |||
spi_stop(); | |||
#ifdef CONSOLE_ENABLE | |||
print_byte(data.motion); | |||
print_byte(data.dx); | |||
print_byte(data.mdx); | |||
print_byte(data.dy); | |||
print_byte(data.mdy); | |||
dprintf("\n"); | |||
#endif | |||
data.isMotion = (data.motion & 0x80) != 0; | |||
data.isOnSurface = (data.motion & 0x08) == 0; | |||
data.dx |= (data.mdx << 8); | |||
data.dx = data.dx * -1; | |||
data.dy |= (data.mdy << 8); | |||
// data.dy = data.dy * -1; | |||
spi_stop(); | |||
if (data.motion & 0b111) { // panic recovery, sometimes burst mode works weird. | |||
_inBurst = false; | |||
} | |||
return data; | |||
} |
@ -0,0 +1,103 @@ | |||
/* 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 | |||
#include "spi_master.h" | |||
// Registers | |||
#define REG_Product_ID 0x00 | |||
#define REG_Revision_ID 0x01 | |||
#define REG_Motion 0x02 | |||
#define REG_Delta_X_L 0x03 | |||
#define REG_Delta_X_H 0x04 | |||
#define REG_Delta_Y_L 0x05 | |||
#define REG_Delta_Y_H 0x06 | |||
#define REG_SQUAL 0x07 | |||
#define REG_Raw_Data_Sum 0x08 | |||
#define REG_Maximum_Raw_data 0x09 | |||
#define REG_Minimum_Raw_data 0x0A | |||
#define REG_Shutter_Lower 0x0B | |||
#define REG_Shutter_Upper 0x0C | |||
#define REG_Control 0x0D | |||
#define REG_Config1 0x0F | |||
#define REG_Config2 0x10 | |||
#define REG_Angle_Tune 0x11 | |||
#define REG_Frame_Capture 0x12 | |||
#define REG_SROM_Enable 0x13 | |||
#define REG_Run_Downshift 0x14 | |||
#define REG_Rest1_Rate_Lower 0x15 | |||
#define REG_Rest1_Rate_Upper 0x16 | |||
#define REG_Rest1_Downshift 0x17 | |||
#define REG_Rest2_Rate_Lower 0x18 | |||
#define REG_Rest2_Rate_Upper 0x19 | |||
#define REG_Rest2_Downshift 0x1A | |||
#define REG_Rest3_Rate_Lower 0x1B | |||
#define REG_Rest3_Rate_Upper 0x1C | |||
#define REG_Observation 0x24 | |||
#define REG_Data_Out_Lower 0x25 | |||
#define REG_Data_Out_Upper 0x26 | |||
#define REG_Raw_Data_Dump 0x29 | |||
#define REG_SROM_ID 0x2A | |||
#define REG_Min_SQ_Run 0x2B | |||
#define REG_Raw_Data_Threshold 0x2C | |||
#define REG_Config5 0x2F | |||
#define REG_Power_Up_Reset 0x3A | |||
#define REG_Shutdown 0x3B | |||
#define REG_Inverse_Product_ID 0x3F | |||
#define REG_LiftCutoff_Tune3 0x41 | |||
#define REG_Angle_Snap 0x42 | |||
#define REG_LiftCutoff_Tune1 0x4A | |||
#define REG_Motion_Burst 0x50 | |||
#define REG_LiftCutoff_Tune_Timeout 0x58 | |||
#define REG_LiftCutoff_Tune_Min_Length 0x5A | |||
#define REG_SROM_Load_Burst 0x62 | |||
#define REG_Lift_Config 0x63 | |||
#define REG_Raw_Data_Burst 0x64 | |||
#define REG_LiftCutoff_Tune2 0x65 | |||
#ifdef CONSOLE_ENABLE | |||
void print_byte(uint8_t byte); | |||
#endif | |||
typedef struct { | |||
int8_t motion; | |||
bool isMotion; // True if a motion is detected. | |||
bool isOnSurface; // True when a chip is on a surface | |||
int16_t dx; // displacement on x directions. Unit: Count. (CPI * Count = Inch value) | |||
int8_t mdx; | |||
int16_t dy; // displacement on y directions. | |||
int8_t mdy; | |||
} report_pmw_t; | |||
bool spi_start_adv(void); | |||
void spi_stop_adv(void); | |||
spi_status_t spi_write_adv(uint8_t reg_addr, uint8_t data); | |||
uint8_t spi_read_adv(uint8_t reg_addr); | |||
bool pmw_spi_init(void); | |||
void pmw_set_cpi(uint16_t cpi); | |||
void pmw_upload_firmware(void); | |||
bool pmw_check_signature(void); | |||
report_pmw_t pmw_read_burst(void); | |||
#define degToRad(angleInDegrees) ((angleInDegrees)*M_PI / 180.0) | |||
#define radToDeg(angleInRadians) ((angleInRadians)*180.0 / M_PI) | |||
#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt))) |
@ -0,0 +1,300 @@ | |||
/* 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 | |||
// clang-format off | |||
// Firmware Blob foor PMW3360 | |||
const uint16_t firmware_length = 4094; | |||
// clang-format off | |||
const uint8_t firmware_data[] PROGMEM = { // SROM 0x04 | |||
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 }; | |||
// clang-format off |
@ -0,0 +1,69 @@ | |||
/* 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 | |||
#include "config_common.h" | |||
/* USB Device descriptor parameter */ | |||
#define VENDOR_ID 0x5043 | |||
#define PRODUCT_ID 0x5442 | |||
#define DEVICE_VER 0x0001 | |||
#define MANUFACTURER Ploopyco | |||
#define PRODUCT Trackball | |||
/* key matrix size */ | |||
#define MATRIX_ROWS 1 | |||
#define MATRIX_COLS 5 | |||
/* | |||
* Keyboard Matrix Assignments | |||
* | |||
* Change this to how you wired your keyboard | |||
* COLS: AVR pins used for columns, left to right | |||
* ROWS: AVR pins used for rows, top to bottom | |||
* DIODE_DIRECTION: COL2ROW = COL = Anode (+), ROW = Cathode (-, marked on diode) | |||
* ROW2COL = ROW = Anode (+), COL = Cathode (-, marked on diode) | |||
* | |||
*/ | |||
#define DIRECT_PINS { { D4, D2, E6, B5, D7 } } | |||
// These pins are not broken out, and cannot be used normally. | |||
// They are set as output and pulled high, by default | |||
#define UNUSED_PINS { D1, D3, B4, B6, B7, D6, C7, F6, F5, F3 } | |||
/* Debounce reduces chatter (unintended double-presses) - set 0 if debouncing is not needed */ | |||
#define DEBOUNCE 5 | |||
/* define if matrix has ghost (lacks anti-ghosting diodes) */ | |||
//#define MATRIX_HAS_GHOST | |||
/* disable action features */ | |||
//#define NO_ACTION_LAYER | |||
//#define NO_ACTION_TAPPING | |||
//#define NO_ACTION_ONESHOT | |||
#define NO_ACTION_MACRO | |||
#define NO_ACTION_FUNCTION | |||
/* Much more so than a keyboard, speed matters for a mouse. So we'll go for as high | |||
a polling rate as possible. */ | |||
#define USB_POLLING_INTERVAL_MS 1 | |||
/* Bootmagic Lite key configuration */ | |||
#define BOOTMAGIC_LITE_ROW 0 | |||
#define BOOTMAGIC_LITE_COLUMN 3 |
@ -0,0 +1,18 @@ | |||
{ | |||
"keyboard_name": "PloopyCo Trackball", | |||
"url": "", | |||
"maintainer": "drashna", | |||
"width": 8, | |||
"height": 3, | |||
"layouts": { | |||
"LAYOUT": { | |||
"layout": [ | |||
{"x":0, "y":0, "h":2}, | |||
{"x":1, "y":0.25, "h":1.5}, | |||
{"x":2, "y":0, "h":2}, | |||
{"x":3.5, "y":0, "h":2}, | |||
{"x":4.5, "y":0, "h":2} | |||
] | |||
} | |||
} | |||
} |
@ -0,0 +1,26 @@ | |||
/* 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/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |||
[0] = LAYOUT( /* Base */ | |||
KC_BTN1, KC_BTN3, KC_BTN2, | |||
KC_BTN4, KC_BTN5 | |||
), | |||
}; |
@ -0,0 +1 @@ | |||
# The default keymap for Ploopyco Trackball |
@ -0,0 +1,26 @@ | |||
/* 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/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |||
[0] = LAYOUT( /* Base */ | |||
KC_BTN1, KC_BTN3, KC_BTN2, | |||
KC_BTN4, KC_BTN5 | |||
), | |||
}; |
@ -0,0 +1 @@ | |||
VIA_ENABLE = yes |
@ -0,0 +1,68 @@ | |||
# Ploopyco Trackball | |||
![Ploopyco Trackball](https://i.redd.it/j7z0y83txps31.jpg) | |||
It's a DIY, QMK Powered Trackball!!!! | |||
Everything works. However the scroll wheel has some issues and acts very odd. | |||
* Keyboard Maintainer: [PloopyCo](https://github.com/ploopyco), [Drashna Jael're](https://github.com/drashna/), [Germ](https://github.com/germ/) | |||
* Hardware Supported: ATMega32u4 8MHz(3.3v) | |||
* Hardware Availability: [Store](https://ploopy.co), [GitHub](https://github.com/ploopyco) | |||
Make example for this keyboard (after setting up your build environment): | |||
make ploopyco/trackball:default:flash | |||
To jump to the bootloader, hold down "Button 4" (immediate right of the trackball) | |||
See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs). | |||
# Customzing your PloopyCo Trackball | |||
While the defaults are designed so that it can be plugged in and used right away, there are a number of things that you may want to change. Such as adding DPI control, or to use the ball to scroll while holding a button. To allow for this sort of control, there is a callback for both the scroll wheel and the mouse censor. | |||
The default behavior for this is: | |||
```c | |||
void process_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v) { | |||
mouse_report->h = h; | |||
mouse_report->v = v; | |||
} | |||
void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { | |||
mouse_report->x = x; | |||
mouse_report->y = y; | |||
} | |||
``` | |||
This should allow you to more heavily customize the behavior. | |||
Alternatively, the `process_wheel` and `process_mouse` functions can both be replaced too, to allow for even more functionality. | |||
Additionally, you can change the DPI/CPI or speed of the trackball by calling `pmw_set_cpi` at any time. And tThe default can be changed by adding a define to the keymap's `config.h` file: | |||
#define PMW_CPI 1600 | |||
# Programming QMK-DFU onto the PloopyCo Trackball | |||
If you would rather have DFU on this board, you can use the QMK-DFU bootloader on the device. To do so, you want to run: | |||
make ploopyco/trackball:default:production | |||
Once you have that, you'll need to [ISP Flash](https://docs.qmk.fm/#/isp_flashing_guide) the chip with the new bootloader hex file created (or the production hex), and set the fuses: | |||
| Fuse | Setting | | |||
|----------|------------------| | |||
| Low | `0xDF` | | |||
| High | `0xD8` or `0x98` | | |||
| Extended | `0xCB` | | |||
Original (Caterina) settings: | |||
| Fuse | Setting | | |||
|----------|------------------| | |||
| Low | `0xFF` | | |||
| High | `0xD8` | | |||
| Extended | `0xFE` | |
@ -0,0 +1,30 @@ | |||
# MCU name | |||
MCU = atmega32u4 | |||
# Processor frequency | |||
F_CPU = 8000000 | |||
# Bootloader selection | |||
BOOTLOADER = caterina | |||
# Build Options | |||
# change yes to no to disable | |||
# | |||
BOOTMAGIC_ENABLE = lite # Virtual DIP switch configuration | |||
EXTRAKEY_ENABLE = yes # Audio control and System control | |||
CONSOLE_ENABLE = yes # Console for debug | |||
COMMAND_ENABLE = no # Commands for debug and configuration | |||
# Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE | |||
SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend | |||
# if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work | |||
NKRO_ENABLE = no # USB Nkey Rollover | |||
BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality | |||
RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow | |||
UNICODE_ENABLE = no # Unicode | |||
BLUETOOTH_ENABLE = no # Enable Bluetooth | |||
AUDIO_ENABLE = no # Audio output | |||
POINTING_DEVICE_ENABLE = yes | |||
MOUSEKEY_ENABLE = no # Mouse keys | |||
QUANTUM_LIB_SRC += analog.c spi_master.c | |||
SRC += pmw3600.c opt_encoder.c |
@ -0,0 +1,237 @@ | |||
/* 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/>. | |||
*/ | |||
#include QMK_KEYBOARD_H | |||
#ifndef OPT_DEBOUNCE | |||
# define OPT_DEBOUNCE 5 // (ms) Time between scroll events | |||
#endif | |||
#ifndef SCROLL_BUTT_DEBOUNCE | |||
# define SCROLL_BUTT_DEBOUNCE 100 // (ms) Time between scroll events | |||
#endif | |||
#ifndef OPT_THRES | |||
# define OPT_THRES 150 // (0-1024) Threshold for actication | |||
#endif | |||
#ifndef OPT_SCALE | |||
# define OPT_SCALE 1 // Multiplier for wheel | |||
#endif | |||
// TODO: Implement libinput profiles | |||
// https://wayland.freedesktop.org/libinput/doc/latest/pointer-acceleration.html | |||
// Compile time accel selection | |||
// Valid options are ACC_NONE, ACC_LINEAR, ACC_CUSTOM, ACC_QUADRATIC | |||
// Trackball State | |||
bool is_scroll_clicked = false; | |||
bool BurstState = false; // init burst state for Trackball module | |||
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_wheel_user(report_mouse_t* mouse_report, int16_t h, int16_t v) { | |||
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 | |||
// If the mouse wheel was just released, do not scroll. | |||
if (timer_elapsed(lastMidClick) < SCROLL_BUTT_DEBOUNCE) { | |||
return; | |||
} | |||
// Limit the number of scrolls per unit time. | |||
if (timer_elapsed(lastScroll) < OPT_DEBOUNCE) { | |||
return; | |||
} | |||
// Don't scroll if the middle button is depressed. | |||
if (is_scroll_clicked) { | |||
#ifndef IGNORE_SCROLL_CLICK | |||
return; | |||
#endif | |||
} | |||
lastScroll = timer_read(); | |||
uint16_t p1 = adc_read(OPT_ENC1_MUX); | |||
uint16_t p2 = adc_read(OPT_ENC2_MUX); | |||
if (debug_encoder) dprintf("OPT1: %d, OPT2: %d\n", p1, p2); | |||
uint8_t dir = opt_encoder_handler(p1, p2); | |||
if (dir == 0) return; | |||
process_wheel_user(mouse_report, mouse_report->h, (int)(mouse_report->v + (dir * OPT_SCALE))); | |||
} | |||
__attribute__((weak)) void process_mouse_user(report_mouse_t* mouse_report, int16_t x, int16_t y) { | |||
mouse_report->x = x; | |||
mouse_report->y = y; | |||
} | |||
__attribute__((weak)) void process_mouse(report_mouse_t* mouse_report) { | |||
report_pmw_t data = pmw_read_burst(); | |||
if (data.isOnSurface && data.isMotion) { | |||
// Reset timer if stopped moving | |||
if (!data.isMotion) { | |||
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 | |||
// no post processing | |||
#endif | |||
// apply multiplier | |||
// data.dx *= mouse_multiplier; | |||
// 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); | |||
} | |||
} | |||
bool process_record_kb(uint16_t keycode, keyrecord_t* record) { | |||
if (debug_mouse) { | |||
dprintf("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 == 2) && (record->event.key.row == 0)) { | |||
lastMidClick = timer_read(); | |||
is_scroll_clicked = record->event.pressed; | |||
} | |||
/* 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) { | |||
if (keycode == KC_MS_BTN1) | |||
currentReport.buttons |= MOUSE_BTN1; | |||
else if (keycode == KC_MS_BTN2) | |||
currentReport.buttons |= MOUSE_BTN2; | |||
else if (keycode == KC_MS_BTN3) | |||
currentReport.buttons |= MOUSE_BTN3; | |||
else if (keycode == KC_MS_BTN4) | |||
currentReport.buttons |= MOUSE_BTN4; | |||
else if (keycode == KC_MS_BTN5) | |||
currentReport.buttons |= MOUSE_BTN5; | |||
} else { | |||
if (keycode == KC_MS_BTN1) | |||
currentReport.buttons &= ~MOUSE_BTN1; | |||
else if (keycode == KC_MS_BTN2) | |||
currentReport.buttons &= ~MOUSE_BTN2; | |||
else if (keycode == KC_MS_BTN3) | |||
currentReport.buttons &= ~MOUSE_BTN3; | |||
else if (keycode == KC_MS_BTN4) | |||
currentReport.buttons &= ~MOUSE_BTN4; | |||
else if (keycode == KC_MS_BTN5) | |||
currentReport.buttons &= ~MOUSE_BTN5; | |||
} | |||
pointing_device_set_report(currentReport); | |||
} | |||
#endif | |||
return process_record_user(keycode, record); | |||
} | |||
// Hardware Setup | |||
void keyboard_pre_init_kb(void) { | |||
// debug_enable = true; | |||
// debug_matrix = true; | |||
// debug_mouse = true; | |||
// debug_encoder = true; | |||
setPinInput(OPT_ENC1); | |||
setPinInput(OPT_ENC2); | |||
// This is the debug LED. | |||
setPinOutput(F7); | |||
writePin(F7, debug_enable); | |||
/* Ground all output pins connected to ground. This provides additional | |||
* 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. | |||
*/ | |||
#ifdef UNUSED_PINS | |||
const pin_t unused_pins[] = UNUSED_PINS; | |||
for (uint8_t i = 0; i < (sizeof(unused_pins) / sizeof(pin_t)); i++) { | |||
setPinOutput(unused_pins[i]); | |||
writePinLow(unused_pins[i]); | |||
} | |||
#endif | |||
keyboard_pre_init_user(); | |||
} | |||
void pointing_device_init(void) { | |||
// initialize ball sensor | |||
pmw_spi_init(); | |||
// initialize the scroll wheel's optical encoder | |||
opt_encoder_init(); | |||
} | |||
bool has_report_changed (report_mouse_t first, report_mouse_t second) { | |||
return !( | |||
(!first.buttons && first.buttons == second.buttons) && | |||
(!first.x && first.x == second.x) && | |||
(!first.y && first.y == second.y) && | |||
(!first.h && first.h == second.h) && | |||
(!first.v && first.v == second.v) ); | |||
} | |||
void pointing_device_task(void) { | |||
report_mouse_t mouse_report = pointing_device_get_report(); | |||
process_wheel(&mouse_report); | |||
process_mouse(&mouse_report); | |||
pointing_device_set_report(mouse_report); | |||
if (has_report_changed(mouse_report, pointing_device_get_report())) { | |||
pointing_device_send(); | |||
} | |||
} |
@ -0,0 +1,40 @@ | |||
/* 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 | |||
#include "quantum.h" | |||
#include "spi_master.h" | |||
#include "pmw3600.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); | |||
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) \ | |||
{ {BL, BM, BR, BF, BB}, } |