diff options
Diffstat (limited to 'drivers/sensors')
-rw-r--r-- | drivers/sensors/adns5050.c | 193 | ||||
-rw-r--r-- | drivers/sensors/adns5050.h | 79 | ||||
-rw-r--r-- | drivers/sensors/adns9800.c | 219 | ||||
-rw-r--r-- | drivers/sensors/adns9800.h | 35 | ||||
-rw-r--r-- | drivers/sensors/adns9800_srom_A6.h | 3078 | ||||
-rw-r--r-- | drivers/sensors/pimoroni_trackball.c | 201 | ||||
-rw-r--r-- | drivers/sensors/pimoroni_trackball.h | 37 | ||||
-rw-r--r-- | drivers/sensors/pmw3360.c | 239 | ||||
-rw-r--r-- | drivers/sensors/pmw3360.h | 104 | ||||
-rw-r--r-- | drivers/sensors/pmw3360_firmware.h | 300 |
10 files changed, 4485 insertions, 0 deletions
diff --git a/drivers/sensors/adns5050.c b/drivers/sensors/adns5050.c new file mode 100644 index 0000000000..e7273977d5 --- /dev/null +++ b/drivers/sensors/adns5050.c @@ -0,0 +1,193 @@ +/* Copyright 2021 Colin Lam (Ploopy Corporation) + * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com> + * Copyright 2019 Sunjun Kim + * Copyright 2019 Hiroyuki Okada + * + * 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 "adns5050.h" +#include "wait.h" +#include "debug.h" +#include "print.h" +#include "gpio.h" + +#ifndef OPTIC_ROTATED +# define OPTIC_ROTATED false +#endif + +// Definitions for the ADNS serial line. +#ifndef ADNS_SCLK_PIN +# define ADNS_SCLK_PIN B7 +#endif + +#ifndef ADNS_SDIO_PIN +# define ADNS_SDIO_PIN C6 +#endif + +#ifndef ADNS_CS_PIN +# define ADNS_CS_PIN B4 +#endif + +#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 + +// Initialize the ADNS serial pins. +void adns_init(void) { + setPinOutput(ADNS_SCLK_PIN); + setPinOutput(ADNS_SDIO_PIN); + setPinOutput(ADNS_CS_PIN); +} + +// Perform a synchronization with the ADNS. +// Just as with the serial protocol, this is used by the slave to send a +// synchronization signal to the master. +void adns_sync(void) { + writePinLow(ADNS_CS_PIN); + wait_us(1); + writePinHigh(ADNS_CS_PIN); +} + +void adns_cs_select(void) { + writePinLow(ADNS_CS_PIN); +} + +void adns_cs_deselect(void) { + writePinHigh(ADNS_CS_PIN); +} + +uint8_t adns_serial_read(void) { + setPinInput(ADNS_SDIO_PIN); + uint8_t byte = 0; + + for (uint8_t i = 0; i < 8; ++i) { + writePinLow(ADNS_SCLK_PIN); + wait_us(1); + + byte = (byte << 1) | readPin(ADNS_SDIO_PIN); + + writePinHigh(ADNS_SCLK_PIN); + wait_us(1); + } + + return byte; +} + +void adns_serial_write(uint8_t data) { + setPinOutput(ADNS_SDIO_PIN); + + for (int8_t b = 7; b >= 0; b--) { + writePinLow(ADNS_SCLK_PIN); + + if (data & (1 << b)) + writePinHigh(ADNS_SDIO_PIN); + else + writePinLow(ADNS_SDIO_PIN); + + wait_us(2); + + writePinHigh(ADNS_SCLK_PIN); + } + + // tSWR. See page 15 of the ADNS spec sheet. + // Technically, this is only necessary if the next operation is an SDIO + // read. This is not guaranteed to be the case, but we're being lazy. + wait_us(4); + + // Note that tSWW is never necessary. All write operations require at + // least 32us, which exceeds tSWW, so there's never a need to wait for it. +} + +// 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). +uint8_t adns_read_reg(uint8_t reg_addr) { + adns_cs_select(); + + adns_serial_write(reg_addr); + + // 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. + // See page 10 and 15 of the ADNS spec sheet. + //wait_us(4); + + uint8_t byte = adns_serial_read(); + + // tSRW & tSRR. See page 15 of the ADNS spec sheet. + // Technically, this is only necessary if the next operation is an SDIO + // read or write. This is not guaranteed to be the case. + // Honestly, this wait could probably be removed. + wait_us(1); + + adns_cs_deselect(); + + return byte; +} + +void adns_write_reg(uint8_t reg_addr, uint8_t data) { + adns_cs_select(); + adns_serial_write( 0b10000000 | reg_addr ); + adns_serial_write(data); + adns_cs_deselect(); +} + +report_adns_t adns_read_burst(void) { + adns_cs_select(); + + report_adns_t data; + data.dx = 0; + data.dy = 0; + + adns_serial_write(REG_MOTION_BURST); + + // 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. + // See page 10 and 15 of the ADNS spec sheet. + //wait_us(4); + + uint8_t x = adns_serial_read(); + uint8_t y = adns_serial_read(); + + // Burst mode returns a bunch of other shit that we don't really need. + // Setting CS to high ends burst mode early. + adns_cs_deselect(); + + data.dx = convert_twoscomp(x); + data.dy = convert_twoscomp(y); + + return data; +} + +// Convert a two's complement byte from an unsigned data type into a signed +// data type. +int8_t convert_twoscomp(uint8_t data) { + if ((data & 0x80) == 0x80) + return -128 + (data & 0x7F); + else + return data; +} + +// 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); +} + +bool adns_check_signature(void) { + 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); + + return (pid == 0x12 && rid == 0x01 && pid2 == 0x26); +} diff --git a/drivers/sensors/adns5050.h b/drivers/sensors/adns5050.h new file mode 100644 index 0000000000..ff8e8f78e9 --- /dev/null +++ b/drivers/sensors/adns5050.h @@ -0,0 +1,79 @@ +/* Copyright 2021 Colin Lam (Ploopy Corporation) + * Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com> + * Copyright 2019 Sunjun Kim + * Copyright 2019 Hiroyuki Okada + * + * 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> + +// 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 +#define CPI125 0x11 +#define CPI250 0x12 +#define CPI375 0x13 +#define CPI500 0x14 +#define CPI625 0x15 +#define CPI750 0x16 +#define CPI875 0x17 +#define CPI1000 0x18 +#define CPI1125 0x19 +#define CPI1250 0x1a +#define CPI1375 0x1b + +#ifdef CONSOLE_ENABLE +void print_byte(uint8_t byte); +#endif + +typedef struct { + int8_t dx; + int8_t dy; +} report_adns_t; + +// A bunch of functions to implement the ADNS5050-specific serial protocol. +// Note that the "serial.h" driver is insufficient, because it does not +// manually manipulate a serial clock signal. +void adns_init(void); +void adns_sync(void); +uint8_t adns_serial_read(void); +void adns_serial_write(uint8_t data); +uint8_t adns_read_reg(uint8_t reg_addr); +void adns_write_reg(uint8_t reg_addr, uint8_t data); +report_adns_t adns_read_burst(void); +int8_t convert_twoscomp(uint8_t data); +void adns_set_cpi(uint8_t cpi); +bool adns_check_signature(void); diff --git a/drivers/sensors/adns9800.c b/drivers/sensors/adns9800.c new file mode 100644 index 0000000000..36213179f7 --- /dev/null +++ b/drivers/sensors/adns9800.c @@ -0,0 +1,219 @@ +/* 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 "adns9800_srom_A6.h" +#include "adns9800.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_Pixel_Sum 0x08 +#define REG_Maximum_Pixel 0x09 +#define REG_Minimum_Pixel 0x0a +#define REG_Shutter_Lower 0x0b +#define REG_Shutter_Upper 0x0c +#define REG_Frame_Period_Lower 0x0d +#define REG_Frame_Period_Upper 0x0e +#define REG_Configuration_I 0x0f +#define REG_Configuration_II 0x10 +#define REG_Frame_Capture 0x12 +#define REG_SROM_Enable 0x13 +#define REG_Run_Downshift 0x14 +#define REG_Rest1_Rate 0x15 +#define REG_Rest1_Downshift 0x16 +#define REG_Rest2_Rate 0x17 +#define REG_Rest2_Downshift 0x18 +#define REG_Rest3_Rate 0x19 +#define REG_Frame_Period_Max_Bound_Lower 0x1a +#define REG_Frame_Period_Max_Bound_Upper 0x1b +#define REG_Frame_Period_Min_Bound_Lower 0x1c +#define REG_Frame_Period_Min_Bound_Upper 0x1d +#define REG_Shutter_Max_Bound_Lower 0x1e +#define REG_Shutter_Max_Bound_Upper 0x1f +#define REG_LASER_CTRL0 0x20 +#define REG_Observation 0x24 +#define REG_Data_Out_Lower 0x25 +#define REG_Data_Out_Upper 0x26 +#define REG_SROM_ID 0x2a +#define REG_Lift_Detection_Thr 0x2e +#define REG_Configuration_V 0x2f +#define REG_Configuration_IV 0x39 +#define REG_Power_Up_Reset 0x3a +#define REG_Shutdown 0x3b +#define REG_Inverse_Product_ID 0x3f +#define REG_Motion_Burst 0x50 +#define REG_SROM_Load_Burst 0x62 +#define REG_Pixel_Burst 0x64 + +#define ADNS_CLOCK_SPEED 2000000 +#define MIN_CPI 200 +#define MAX_CPI 8200 +#define CPI_STEP 200 +#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_READS 20 +#define US_BEFORE_MOTION 100 +#define MSB1 0x80 + +extern const uint16_t adns_firmware_length; +extern const uint8_t adns_firmware_data[]; + +void adns_spi_start(void){ + spi_start(SPI_SS_PIN, false, SPI_MODE, SPI_DIVISOR); +} + +void adns_write(uint8_t reg_addr, uint8_t data){ + + adns_spi_start(); + spi_write(reg_addr | MSB1); + spi_write(data); + spi_stop(); + wait_us(US_BETWEEN_WRITES); +} + +uint8_t adns_read(uint8_t reg_addr){ + + adns_spi_start(); + spi_write(reg_addr & 0x7f ); + uint8_t data = spi_read(); + spi_stop(); + wait_us(US_BETWEEN_READS); + + return data; +} + +void adns_init() { + + setPinOutput(SPI_SS_PIN); + + spi_init(); + + // reboot + adns_write(REG_Power_Up_Reset, 0x5a); + wait_ms(50); + + // read registers and discard + adns_read(REG_Motion); + adns_read(REG_Delta_X_L); + adns_read(REG_Delta_X_H); + adns_read(REG_Delta_Y_L); + adns_read(REG_Delta_Y_H); + + // upload firmware + + // 3k firmware mode + adns_write(REG_Configuration_IV, 0x02); + + // enable initialisation + adns_write(REG_SROM_Enable, 0x1d); + + // wait a frame + wait_ms(10); + + // start SROM download + adns_write(REG_SROM_Enable, 0x18); + + // write the SROM file + + adns_spi_start(); + + spi_write(REG_SROM_Load_Burst | 0x80); + wait_us(15); + + // send all bytes of the firmware + unsigned char c; + for(int i = 0; i < adns_firmware_length; i++){ + c = (unsigned char)pgm_read_byte(adns_firmware_data + i); + spi_write(c); + wait_us(15); + } + + spi_stop(); + + wait_ms(10); + + // enable laser + uint8_t laser_ctrl0 = adns_read(REG_LASER_CTRL0); + adns_write(REG_LASER_CTRL0, laser_ctrl0 & 0xf0); +} + +config_adns_t adns_get_config(void) { + uint8_t config_1 = adns_read(REG_Configuration_I); + return (config_adns_t){ (config_1 & 0xFF) * CPI_STEP }; +} + +void adns_set_config(config_adns_t config) { + uint8_t config_1 = (CLAMP_CPI(config.cpi) / CPI_STEP) & 0xFF; + adns_write(REG_Configuration_I, 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_adns_t adns_get_report(void) { + + report_adns_t report = {0, 0}; + + adns_spi_start(); + + // start burst mode + spi_write(REG_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); + } + + // clear residual motion + spi_write(REG_Motion & 0x7f); + + spi_stop(); + + return report; +} diff --git a/drivers/sensors/adns9800.h b/drivers/sensors/adns9800.h new file mode 100644 index 0000000000..2f50b8f1be --- /dev/null +++ b/drivers/sensors/adns9800.h @@ -0,0 +1,35 @@ +/* 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/>. + */ + +#pragma once + +#include <stdint.h> + +typedef struct { + /* 200 - 8200 CPI supported */ + uint16_t cpi; +} config_adns_t; + +typedef struct { + int16_t x; + int16_t y; +} report_adns_t; + +void adns_init(void); +config_adns_t adns_get_config(void); +void adns_set_config(config_adns_t); +/* Reads and clears the current delta values on the ADNS sensor */ +report_adns_t adns_get_report(void); diff --git a/drivers/sensors/adns9800_srom_A6.h b/drivers/sensors/adns9800_srom_A6.h new file mode 100644 index 0000000000..f5b3abeb62 --- /dev/null +++ b/drivers/sensors/adns9800_srom_A6.h @@ -0,0 +1,3078 @@ +#pragma once + +#include "progmem.h" + +const uint16_t adns_firmware_length = 3070; + +const uint8_t adns_firmware_data[] PROGMEM = { +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, +0xba, +0xf6, +0x6e, +0x3f, +0xdd, +0x38, +0xd5, +0x02, +0x80, +0x9b, +0x82, +0x6d, +0x58, +0x13, +0xa4, +0xab, +0xb5, +0xc9, +0x10, +0xa2, +0xc6, +0x0a, +0x7f, +0x5d, +0x19, +0x91, +0xa0, +0xa3, +0xce, +0xeb, +0x3e, +0xc9, +0xf1, +0x60, +0x42, +0xe7, +0x4c, +0xfb, +0x74, +0x6a, +0x56, +0x2e, +0xbf, +0xdd, +0x38, +0xd3, +0x05, +0x88, +0x92, +0xa6, +0xce, +0xff, +0x5d, +0x38, +0xd1, +0xcf, +0xef, +0x58, +0xcb, +0x65, +0x48, +0xf0, +0x35, +0x85, +0xa9, +0xb2, +0x8f, +0x5e, +0xf3, +0x80, +0x94, +0x97, +0x7e, +0x75, +0x97, +0x87, +0x73, +0x13, +0xb0, +0x8a, +0x69, +0xd4, +0x0a, +0xde, +0xc1, +0x79, +0x59, +0x36, +0xdb, +0x9d, +0xd6, +0xb8, +0x15, +0x6f, +0xce, +0x3c, +0x72, +0x32, +0x45, +0x88, +0xdf, +0x6c, +0xa5, +0x6d, +0xe8, +0x76, +0x96, +0x14, +0x74, +0x20, +0xdc, +0xf4, +0xfa, +0x37, +0x6a, +0x27, +0x32, +0xe3, +0x29, +0xbf, +0xc4, +0xc7, +0x06, +0x9d, +0x58, +0xe7, +0x87, +0x7c, +0x2e, +0x9f, +0x6e, +0x49, +0x07, +0x5d, +0x23, +0x64, +0x54, +0x83, +0x6e, +0xcb, +0xb7, +0x77, +0xf7, +0x2b, +0x6e, +0x0f, +0x2e, +0x66, +0x12, +0x60, +0x55, +0x65, +0xfc, +0x43, +0xb3, +0x58, +0x73, +0x5b, +0xe8, +0x67, +0x04, +0x43, +0x02, +0xde, +0xb3, +0x89, +0xa0, +0x6d, +0x3a, +0x27, +0x79, +0x64, +0x5b, +0x0c, +0x16, +0x9e, +0x66, +0xb1, +0x8b, +0x87, +0x0c, +0x5d, +0xf2, +0xb6, +0x3d, +0x71, +0xdf, +0x42, +0x03, +0x8a, +0x06, +0x8d, +0xef, +0x1d, +0xa8, +0x96, +0x5c, +0xed, +0x31, +0x61, +0x5c, +0xa1, +0x34, +0xf6, +0x8c, +0x08, +0x60, +0x33, +0x07, +0x00, +0x3e, +0x79, +0x95, +0x1b, +0x43, +0x7f, +0xfe, +0xb6, +0xa6, +0xd4, +0x9d, +0x76, +0x72, +0xbf, +0xad, +0xc0, +0x15, +0xe8, +0x37, +0x31, +0xa3, +0x72, +0x63, +0x52, +0x1d, +0x1c, +0x5d, +0x51, +0x1b, +0xe1, +0xa9, +0xed, +0x60, +0x32, +0x3e, +0xa9, +0x50, +0x28, +0x53, +0x06, +0x59, +0xe2, +0xfc, +0xe7, +0x02, +0x64, +0x39, +0x21, +0x56, +0x4a, +0xa5, +0x40, +0x80, +0x81, +0xd5, +0x5a, +0x60, +0x7b, +0x68, +0x84, +0xf1, +0xe0, +0xb1, +0xb6, +0x5b, +0xdf, +0xa8, +0x1d, +0x6d, +0x65, +0x20, +0xc0, +0xa2, +0xb9, +0xd9, +0xbb, +0x00, +0xa6, +0xdb, +0x8b, +0x01, +0x53, +0x91, +0xfe, +0xc4, +0x51, +0x85, +0xb0, +0x96, +0x7f, +0xfd, +0x51, +0xdd, +0x14, +0x03, +0x67, +0x2e, +0x75, +0x1c, +0x76, +0xd3, +0x6e, +0xdd, +0x99, +0x55, +0x76, +0xe5, +0xab, +0x23, +0xfc, +0x4a, +0xd5, +0xc6, +0xe8, +0x2e, +0xca, +0x8a, +0xb3, +0xf6, +0x8c, +0x6c, +0xb0, +0xe9, +0xf2, +0xe7, +0x9e, +0x69, +0x41, +0xed, +0xf1, +0x6d, +0xd2, +0x86, +0xd8, +0x7e, +0xcb, +0x5d, +0x47, +0x6c, +0x85, +0x6a, +0x23, +0xed, +0x20, +0x40, +0x93, +0xb4, +0x20, +0xc7, +0xa5, +0xc9, +0xaf, +0x03, +0x15, +0xac, +0x19, +0xe5, +0x2a, +0x36, +0xdf, +0x6d, +0xc5, +0x8c, +0x80, +0x07, +0xce, +0x92, +0x0c, +0xd8, +0x06, +0x62, +0x0f, +0xdd, +0x48, +0x46, +0x1a, +0x53, +0xc7, +0x8a, +0x8c, +0x5d, +0x5d, +0xb4, +0xa1, +0x02, +0xd3, +0xa9, +0xb8, +0xf3, +0x94, +0x8f, +0x3f, +0xe5, +0x54, +0xd4, +0x11, +0x65, +0xb2, +0x5e, +0x09, +0x0b, +0x81, +0xe3, +0x75, +0xa7, +0x89, +0x81, +0x39, +0x6c, +0x46, +0xf6, +0x06, +0x9f, +0x27, +0x3b, +0xb6, +0x2d, +0x5f, +0x1d, +0x4b, +0xd4, +0x7b, +0x1d, +0x61, +0x74, +0x89, +0xe4, +0xe3, +0xbd, +0x98, +0x1b, +0xc4, +0x51, +0x3b, +0xa4, +0xfa, +0xe0, +0x92, +0xf7, +0xbe, +0xf2, +0x4d, +0xbb, +0xff, +0xad, +0x4f, +0x6d, +0x68, +0xc2, +0x79, +0x40, +0xaa, +0x9b, +0x8f, +0x0c, +0x32, +0x4b, +0x5f, +0x3e, +0xab, +0x59, +0x98, +0xb3, +0xf5, +0x1d, +0xac, +0x5e, +0xbc, +0x78, +0xd3, +0x01, +0x6c, +0x64, +0x15, +0x2f, +0xd8, +0x71, +0xa6, +0x2d, +0x45, +0xe1, +0x22, +0x42, +0xe4, +0x4e, +0x04, +0x3c, +0x7d, +0xf4, +0x40, +0x21, +0xb4, +0x67, +0x05, +0xa8, +0xe2, +0xf3, +0x72, +0x87, +0x4c, +0x7d, +0xd9, +0x1b, +0x65, +0x97, +0xf3, +0xc2, +0xe3, +0xe4, +0xc8, +0xd2, +0xde, +0xf6, +0xef, +0xdc, +0xbb, +0x44, +0x08, +0x5e, +0xe2, +0x45, +0x27, +0x01, +0xb0, +0xf6, +0x43, +0xe7, +0x3a, +0xf6, +0xdc, +0x9d, +0xed, +0xf3, +0xc5, +0x0c, +0xb8, +0x9c, +0x98, +0x3a, +0xd8, +0x36, +0xee, +0x96, +0x72, +0x67, +0xe7, +0x81, +0x91, +0xd5, +0x05, +0x0a, +0xe0, +0x82, +0xd5, +0x8f, +0xe8, +0xf9, +0xb0, +0xc9, +0xcf, +0x93, +0xe7, +0x04, +0xc5, +0xbc, +0x2b, +0x43, +0x56, +0x7e, +0xe8, +0x67, +0x7c, +0xe5, +0xfb, +0x49, +0xad, +0x5e, +0x9f, +0x25, +0x13, +0xde, +0x6e, +0x6e, +0xe9, +0xf1, +0xec, +0x87, +0x0b, +0x59, +0x81, +0x76, +0x84, +0x76, +0xb3, +0x24, +0xaf, +0x30, +0xfd, +0x27, +0x8b, +0xab, +0xd8, +0x00, +0x8b, +0x9b, +0x0c, +0xd2, +0xb2, +0x4e, +0x5e, +0x9d, +0x1d, +0x96, +0x01, +0x00, +0x67, +0xc1, +0x5f, +0x02, +0x20, +0xfd, +0x45, +0x6a, +0x01, +0x60, +0x58, +0x45, +0xca, +0x47, +0x21, +0x90, +0x5a, +0xc4, +0x43, +0x26, +0x1a, +0xd7, +0xa5, +0x4a, +0xb2, +0x5d, +0x2b, +0x35, +0x49, +0xfb, +0xa5, +0x17, +0x92, +0x21, +0x1e, +0x93, +0x96, +0x67, +0xa2, +0x7e, +0x36, +0x7a, +0xde, +0x5f, +0xbe, +0x7a, +0x58, +0x9d, +0xf8, +0x78, +0xa3, +0xfa, +0xc8, +0xd5, +0x17, +0xf0, +0x21, +0x97, +0x8c, +0x80, +0xb5, +0x4b, +0x3b, +0xbd, +0xbb, +0x41, +0x21, +0xa8, +0x50, +0x67, +0xf7, +0xe7, +0x19, +0x80, +0x10, +0x8e, +0xce, +0x04, +0x18, +0x3f, +0x51, +0x6b, +0x77, +0xd8, +0x9e, +0x16, +0xaf, +0xec, +0xef, +0x48, +0x16, +0x4d, +0x9e, +0x85, +0x38, +0x18, +0x3e, +0xd4, +0x28, +0x87, +0x60, +0x2a, +0xf6, +0x7f, +0x09, +0x86, +0x6f, +0x9c, +0x3c, +0x3a, +0xff, +0xab, +0xd0, +0x61, +0xa2, +0x97, +0x0d, +0x71, +0x94, +0x7e, +0xfd, +0xb9, +0x80, +0x02, +0x89, +0x6a, +0xb3, +0x84, +0x6c, +0x2a, +0x77, +0x62, +0xbe, +0x0b, +0xf4, +0xaf, +0xac, +0x7b, +0x7c, +0x8e, +0xca, +0x01, +0xba, +0x71, +0x78, +0x94, +0xfd, +0xb5, +0x39, +0xa4, +0x4d, +0x2f, +0x78, +0xcf, +0xca, +0x92, +0x0c, +0x1a, +0x99, +0x48, +0x4c, +0x11, +0x96, +0xb5, +0x4e, +0x41, +0x28, +0xe4, +0xa6, +0xfe, +0x4b, +0x72, +0x91, +0xe7, +0xd4, +0xdd, +0x9f, +0x12, +0xe6, +0x29, +0x38, +0xce, +0x45, +0xae, +0x02, +0xb8, +0x24, +0xae, +0xbd, +0xe9, +0x66, +0x08, +0x62, +0xa2, +0x2c, +0x2b, +0x00, +0xe2, +0x23, +0xd9, +0xc4, +0x48, +0xe4, +0xd3, +0xac, +0xbb, +0x34, +0xc7, +0xf0, +0xe3, +0x4f, +0xb9, +0x30, +0xea, +0xa2, +0x12, +0xf1, +0x30, +0x2c, +0x36, +0xde, +0x48, +0xf2, +0xb0, +0x4c, +0x43, +0x3f, +0x2e, +0x58, +0xe4, +0x20, +0xe3, +0x58, +0xcd, +0x31, +0x22, +0xf0, +0xa2, +0x2a, +0xe6, +0x19, +0x90, +0x55, +0x86, +0xf6, +0x55, +0x79, +0xd1, +0xd7, +0x46, +0x2f, +0xc0, +0xdc, +0x99, +0xe8, +0xf3, +0x6a, +0xdf, +0x7f, +0xeb, +0x24, +0x4a, +0x1e, +0x5a, +0x75, +0xde, +0x2f, +0x5c, +0x19, +0x61, +0x03, +0x53, +0x54, +0x6a, +0x3b, +0x18, +0x70, +0xb6, +0x4f, +0xf1, +0x9c, +0x0a, +0x59, +0x9d, +0x19, +0x92, +0x65, +0x8c, +0x83, +0x14, +0x2d, +0x44, +0x8a, +0x75, +0xa9, +0xf5, +0x90, +0xd2, +0x66, +0x4e, +0xfa, +0x69, +0x0f, +0x5b, +0x0b, +0x98, +0x65, +0xc8, +0x11, +0x42, +0x59, +0x7f, +0xdd, +0x1b, +0x75, +0x17, +0x31, +0x4c, +0x75, +0x58, +0xeb, +0x58, +0x63, +0x7d, +0xf2, +0xa6, +0xc2, +0x6e, +0xb7, +0x3f, +0x3e, +0x5e, +0x47, +0xad, +0xb7, +0x04, +0xe8, +0x05, +0xf8, +0xb2, +0xcf, +0x19, +0xf3, +0xd2, +0x85, +0xfe, +0x3e, +0x3e, +0xb1, +0x62, +0x08, +0x2c, +0x10, +0x07, +0x0d, +0x73, +0x90, +0x17, +0xfa, +0x9b, +0x56, +0x02, +0x75, +0xf9, +0x51, +0xe0, +0xe9, +0x1a, +0x7b, +0x9f, +0xb3, +0xf3, +0x98, +0xb8, +0x1c, +0x9c, +0xe1, +0xd5, +0x35, +0xae, +0xc8, +0x60, +0x48, +0x11, +0x09, +0x94, +0x6b, +0xd0, +0x8b, +0x15, +0xbc, +0x05, +0x68, +0xd3, +0x54, +0x8a, +0x51, +0x39, +0x5c, +0x42, +0x76, +0xce, +0xd8, +0xad, +0x89, +0x30, +0xc9, +0x05, +0x1c, +0xcc, +0x94, +0x3f, +0x0f, +0x90, +0x6f, +0x72, +0x2d, +0x85, +0x64, +0x9a, +0xb9, +0x23, +0xf9, +0x0b, +0xc3, +0x7c, +0x39, +0x0f, +0x97, +0x07, +0x97, +0xda, +0x58, +0x48, +0x33, +0x05, +0x23, +0xb8, +0x82, +0xe8, +0xd3, +0x53, +0x89, +0xaf, +0x33, +0x80, +0x22, +0x84, +0x0c, +0x95, +0x5c, +0x67, +0xb8, +0x77, +0x0c, +0x5c, +0xa2, +0x5f, +0x3d, +0x58, +0x0f, +0x27, +0xf3, +0x2f, +0xae, +0x48, +0xbd, +0x0b, +0x6f, +0x54, +0xfb, +0x67, +0x4c, +0xea, +0x32, +0x27, +0xf1, +0xfa, +0xe2, +0xb0, +0xec, +0x0b, +0x15, +0xb4, +0x70, +0xf6, +0x5c, +0xdd, +0x71, +0x60, +0xc3, +0xc1, +0xa8, +0x32, +0x65, +0xac, +0x7a, +0x77, +0x41, +0xe5, +0xa9, +0x6b, +0x11, +0x81, +0xfa, +0x34, +0x8d, +0xfb, +0xc1, +0x80, +0x6e, +0xc4, +0x60, +0x30, +0x07, +0xd4, +0x8b, +0x67, +0xbd, +0xaa, +0x8c, +0x9c, +0x64, +0xac, +0xdb, +0x0b, +0x24, +0x8b, +0x63, +0x6f, +0xe6, +0xbc, +0xe7, +0x33, +0xa4, +0x4a, +0x4c, +0xa7, +0x9f, +0x43, +0x53, +0xd2, +0xbb, +0x8f, +0x43, +0xc7, +0x3d, +0x78, +0x68, +0x3f, +0xa5, +0x3d, +0xca, +0x69, +0x84, +0xa6, +0x97, +0x2d, +0xc0, +0x7d, +0x31, +0x34, +0x55, +0x1d, +0x07, +0xb1, +0x5f, +0x40, +0x5c, +0x93, +0xb0, +0xbc, +0x7c, +0xb0, +0xbc, +0xe7, +0x12, +0xee, +0x6b, +0x2b, +0xd3, +0x4d, +0x67, +0x70, +0x3a, +0x9a, +0xf2, +0x3c, +0x7c, +0x81, +0xfa, +0xd7, +0xd9, +0x90, +0x91, +0x81, +0xb8, +0xb1, +0xf3, +0x48, +0x6a, +0x26, +0x4f, +0x0c, +0xce, +0xb0, +0x9e, +0xfd, +0x4a, +0x3a, +0xaf, +0xac, +0x5b, +0x3f, +0xbf, +0x44, +0x5a, +0xa3, +0x19, +0x1e, +0x4b, +0xe7, +0x36, +0x6a, +0xd7, +0x20, +0xae, +0xd7, +0x7d, +0x3b, +0xe7, +0xff, +0x3a, +0x86, +0x2e, +0xd0, +0x4a, +0x3e, +0xaf, +0x9f, +0x8e, +0x01, +0xbf, +0xf8, +0x4f, +0xc1, +0xe8, +0x6f, +0x74, +0xe1, +0x45, +0xd3, +0xf7, +0x04, +0x6a, +0x4b, +0x9d, +0xec, +0x33, +0x27, +0x76, +0xd7, +0xc5, +0xe1, +0xb0, +0x3b, +0x0e, +0x23, +0xec, +0xf0, +0x86, +0xd2, +0x1a, +0xbf, +0x3d, +0x04, +0x62, +0xb3, +0x6c, +0xb2, +0xeb, +0x17, +0x05, +0xa6, +0x0a, +0x8a, +0x7e, +0x83, +0x1c, +0xb6, +0x37, +0x09, +0xc6, +0x0b, +0x70, +0x3c, +0xb5, +0x93, +0x81, +0xd8, +0x93, +0xa0, +0x5f, +0x1e, +0x08, +0xe2, +0xc6, +0xe5, +0xc9, +0x72, +0xf1, +0xf1, +0xc1, +0xed, +0xd5, +0x58, +0x93, +0x83, +0xf8, +0x65, +0x67, +0x2e, +0x0d, +0xa9, +0xf1, +0x64, +0x12, +0xe6, +0x4c, +0xea, +0x15, +0x3f, +0x8c, +0x1a, +0xb6, +0xbf, +0xf6, +0xb9, +0x52, +0x35, +0x09, +0xb0, +0xe6, +0xf7, +0xcd, +0xf1, +0xa5, +0xaa, +0x81, +0xd1, +0x81, +0x6f, +0xb4, +0xa9, +0x66, +0x1f, +0xfc, +0x48, +0xc0, +0xb6, +0xd1, +0x8b, +0x06, +0x2f, +0xf6, +0xef, +0x1f, +0x0a, +0xe6, +0xce, +0x3a, +0x4a, +0x55, +0xbf, +0x6d, +0xf9, +0x4d, +0xd4, +0x08, +0x45, +0x4b, +0xc3, +0x66, +0x19, +0x92, +0x10, +0xe1, +0x17, +0x8e, +0x28, +0x91, +0x16, +0xbf, +0x3c, +0xee, +0xa3, +0xa6, +0x99, +0x92, +0x10, +0xe1, +0xf6, +0xcc, +0xac, +0xb8, +0x65, +0x0b, +0x43, +0x66, +0xf8, +0xe3, +0xe5, +0x3f, +0x24, +0x89, +0x47, +0x5d, +0x78, +0x43, +0xd0, +0x61, +0x17, +0xbd, +0x5b, +0x64, +0x54, +0x08, +0x45, +0x59, +0x93, +0xf6, +0x95, +0x8a, +0x41, +0x51, +0x62, +0x4b, +0x51, +0x02, +0x30, +0x73, +0xc7, +0x87, +0xc5, +0x4b, +0xa2, +0x97, +0x0f, +0xe8, +0x46, +0x5f, +0x7e, +0x2a, +0xe1, +0x30, +0x20, +0xb0, +0xfa, +0xe7, +0xce, +0x61, +0x42, +0x57, +0x6e, +0x21, +0xf3, +0x7a, +0xec, +0xe3, +0x25, +0xc7, +0x25, +0xf3, +0x67, +0xa7, +0x57, +0x40, +0x00, +0x02, +0xcf, +0x1c, +0x80, +0x77, +0x67, +0xbd, +0x70, +0xa1, +0x19, +0x92, +0x31, +0x75, +0x93, +0x27, +0x27, +0xb6, +0x82, +0xe4, +0xeb, +0x1d, +0x78, +0x48, +0xe7, +0xa5, +0x5e, +0x57, +0xef, +0x64, +0x28, +0x64, +0x1b, +0xf6, +0x11, +0xb2, +0x03, +0x9d, +0xb9, +0x18, +0x02, +0x27, +0xf7, +0xbe, +0x9d, +0x55, +0xfc, +0x00, +0xd2, +0xc7, +0xae, +0xad, +0x0b, +0xc5, +0xe9, +0x42, +0x41, +0x48, +0xd8, +0x32, +0xcf, +0xf6, +0x0f, +0xf5, +0xbc, +0x97, +0xc6, +0x99, +0x47, +0x76, +0xbd, +0x89, +0x06, +0x0f, +0x63, +0x0c, +0x51, +0xd4, +0x5e, +0xea, +0x48, +0xa8, +0xa2, +0x56, +0x1c, +0x79, +0x84, +0x86, +0x40, +0x88, +0x41, +0x76, +0x55, +0xfc, +0xc2, +0xd7, +0xfd, +0xc9, +0xc7, +0x80, +0x61, +0x35, +0xa7, +0x43, +0x20, +0xf7, +0xeb, +0x6c, +0x66, +0x13, +0xb0, +0xec, +0x02, +0x75, +0x3e, +0x4b, +0xaf, +0xb9, +0x5d, +0x40, +0xda, +0xd6, +0x6e, +0x2d, +0x39, +0x54, +0xc2, +0x95, +0x35, +0x54, +0x25, +0x72, +0xe1, +0x78, +0xb8, +0xeb, +0xc1, +0x16, +0x58, +0x0f, +0x9c, +0x9b, +0xb4, +0xea, +0x37, +0xec, +0x3b, +0x11, +0xba, +0xd5, +0x8a, +0xa9, +0xe3, +0x98, +0x00, +0x51, +0x1c, +0x14, +0xe0, +0x40, +0x96, +0xe5, +0xe9, +0xf2, +0x21, +0x22, +0xb1, +0x23, +0x60, +0x78, +0xd3, +0x17, +0xf8, +0x7a, +0xa5, +0xa8, +0xba, +0x20, +0xd3, +0x15, +0x1e, +0x32, +0xe4, +0x5e, +0x15, +0x48, +0xae, +0xa9, +0xe5, +0xb8, +0x33, +0xec, +0xe8, +0xa2, +0x42, +0xac, +0xbf, +0x10, +0x84, +0x53, +0x87, +0x19, +0xb4, +0x5f, +0x76, +0x4d, +0x01, +0x9d, +0x56, +0x74, +0xd9, +0x5c, +0x97, +0xe7, +0x88, +0xea, +0x3a, +0xbf, +0xdc, +0x4c, +0x33, +0x8a, +0x16, +0xb9, +0x5b, +0xfa, +0xd8, +0x42, +0xa7, +0xbb, +0x3c, +0x04, +0x27, +0x78, +0x49, +0x81, +0x2a, +0x5a, +0x7d, +0x7c, +0x23, +0xa8, +0xba, +0xf7, +0x9a, +0x9f, +0xd2, +0x66, +0x3e, +0x38, +0x3c, +0x75, +0xf9, +0xd1, +0x30, +0x26, +0x30, +0x6e, +0x5a, +0x6e, +0xdc, +0x6a, +0x69, +0x32, +0x50, +0x33, +0x47, +0x9e, +0xa4, +0xa8, +0x64, +0x66, +0xf0, +0x8a, +0xe4, +0xfd, +0x27, +0x6f, +0x51, +0x25, +0x8b, +0x43, +0x74, +0xc9, +0x8e, +0xbd, +0x88, +0x31, +0xbe, +0xec, +0x65, +0xd2, +0xcb, +0x8d, +0x5a, +0x13, +0x48, +0x16, +0x8c, +0x61, +0x0b, +0x11, +0xf6, +0xc6, +0x66, +0xae, +0xc3, +0xcc, +0x0c, +0xd2, +0xe1, +0x9f, +0x82, +0x41, +0x3f, +0x56, +0xf9, +0x73, +0xef, +0xdc, +0x30, +0x50, +0xcf, +0xb6, +0x7f, +0xbc, +0xd0, +0xb3, +0x10, +0xab, +0x24, +0xe4, +0xec, +0xad, +0x18, +0x8c, +0x39, +0x2d, +0x30, +0x4c, +0xc5, +0x40, +0x0d, +0xf6, +0xac, +0xd6, +0x18, +0x5d, +0x96, +0xbf, +0x5f, +0x71, +0x75, +0x96, +0x22, +0x97, +0x0f, +0x02, +0x94, +0x6e, +0xa6, +0xae, +0x6d, +0x8f, +0x1e, +0xca, +0x12, +0x9b, +0x2a, +0x1c, +0xce, +0xa9, +0xee, +0xfd, +0x12, +0x8e, +0xfc, +0xed, +0x09, +0x33, +0xba, +0xf4, +0x1a, +0x15, +0xf6, +0x9d, +0x87, +0x16, +0x43, +0x7c, +0x78, +0x57, +0xe1, +0x44, +0xc9, +0xeb, +0x1f, +0x58, +0x4d, +0xc1, +0x49, +0x11, +0x5c, +0xb2, +0x11, +0xa8, +0x55, +0x16, +0xf1, +0xc6, +0x50, +0xe9, +0x87, +0x89, +0xf6, +0xcf, +0xd8, +0x9c, +0x51, +0xa7, +0xbc, +0x5b, +0x31, +0x6d, +0x4d, +0x51, +0xd0, +0x4c, +0xbc, +0x0d, +0x58, +0x2d, +0x7b, +0x88, +0x7a, +0xf9, +0x8e, +0xd6, +0x40, +0x4d, +0xbb, +0xbe, +0xc4, +0xe5, +0x07, +0xfc, +0xd9, +0x7b, +0x6d, +0xa6, +0x42, +0x57, +0x8f, +0x02, +0x94, +0x4f, +0xe4, +0x2a, +0x65, +0xe2, +0x19, +0x5a, +0x50, +0xe1, +0x25, +0x65, +0x4a, +0x60, +0xc2, +0xcd, +0xa8, +0xec, +0x05, +0x2e, +0x87, +0x7b, +0x95, +0xb7, +0x4f, +0xa0, +0x0b, +0x1b, +0x4a, +0x7f, +0x92, +0xc8, +0x90, +0xee, +0x89, +0x1e, +0x10, +0xd2, +0x85, +0xe4, +0x9f, +0x63, +0xc8, +0x12, +0xbb, +0x4e, +0xb8, +0xcf, +0x0a, +0xec, +0x18, +0x4e, +0xe6, +0x7c, +0xb3, +0x33, +0x26, +0xc7, +0x1f, +0xd2, +0x04, +0x23, +0xea, +0x07, +0x0c, +0x5f, +0x90, +0xbd, +0xa7, +0x6a, +0x0f, +0x4a, +0xd6, +0x10, +0x01, +0x3c, +0x12, +0x29, +0x2e, +0x96, +0xc0, +0x4d, +0xbb, +0xbe, +0xe5, +0xa7, +0x83, +0xd5, +0x6a, +0x3c, +0xe3, +0x5b, +0xb8, +0xf2, +0x5c, +0x6d, +0x1f, +0xa6, +0xf3, +0x12, +0x24, +0xf6, +0xd6, +0x3b, +0x10, +0x14, +0x09, +0x07, +0x82, +0xe8, +0x30, +0x6a, +0x99, +0xdc, +0x95, +0x01, +0x9c, +0xd4, +0x68, +0x3b, +0xca, +0x98, +0x12, +0xab, +0x77, +0x25, +0x15, +0x7d, +0x10, +0x32, +0x45, +0x98, +0xcd, +0x7a, +0xdf, +0x71, +0x8a, +0x75, +0xc1, +0x1c, +0xd4, +0x68, +0x25, +0xeb, +0xbb, +0x54, +0x27, +0x6f, +0x2a, +0xf7, +0xb9, +0x98, +0x03, +0x27, +0xde, +0x24, +0xa8, +0xbb, +0x98, +0xc2, +0x84, +0xff, +0x9b, +0x51, +0xd8, +0x53, +0x50, +0xda, +0xf5, +0x88, +0xaa, +0x87, +0x2f, +0xae, +0xd6, +0xea, +0x6b, +0xde, +0xc8, +0xd7, +0xa7, +0x28, +0x65, +0x81, +0xe8, +0xb2, +0x3b, +0x1d, +0x4f, +0x75, +0x8f, +0x9f, +0x7a, +0x74, +0x8e, +0xc1, +0x5f, +0x9a, +0xa8, +0x9d, +0xfa, +0x03, +0xa3, +0x71, +0x9b, +0x37, +0x6d, +0xd5, +0x0b, +0xf5, +0xe1, +0xa1, +0x1b, +0x01, +0x6a, +0xc6, +0x67, +0xaa, +0xea, +0x2c, +0x9d, +0xa4, +0xd2, +0x6e, +0xfc, +0xde, +0x2e, +0x7f, +0x94, +0x69, +0xe5, +0x4a, +0xe0, +0x01, +0x48, +0x3c, +0x6b, +0xf7, +0x1e, +0xb6, +0x0b, +0x5f, +0xf9, +0x2e, +0x07, +0xc5, +0xe8, +0xae, +0x37, +0x1b, +0xbc, +0x3c, +0xd8, +0xd5, +0x0b, +0x91, +0x9e, +0x80, +0x24, +0xf5, +0x06, +0x0c, +0x0e, +0x98, +0x07, +0x96, +0x2d, +0x19, +0xdc, +0x58, +0x93, +0xcc, +0xfb, +0x4e, +0xeb, +0xbd, +0x0f, +0xf5, +0xaf, +0x01, +0xfa, +0xf1, +0x7c, +0x43, +0x8c, +0xb8, +0x56, +0x3e, +0xbe, +0x77, +0x4e, +0x2b, +0xf7, +0xbb, +0xb7, +0x45, +0x47, +0xcd, +0xcc, +0xa6, +0x4c, +0x72, +0x7b, +0x6a, +0x2a, +0x70, +0x13, +0x07, +0xfd, +0xb8, +0x9c, +0x98, +0x3a, +0xd8, +0x23, +0x67, +0x5b, +0x34, +0xd5, +0x14, +0x0c, +0xab, +0x77, +0x1f, +0xf8, +0x3d, +0x5a, +0x9f, +0x92, +0xb7, +0x2c, +0xad, +0x31, +0xde, +0x61, +0x07, +0xb3, +0x6b, +0xf7, +0x38, +0x15, +0x95, +0x46, +0x14, +0x48, +0x53, +0x69, +0x52, +0x66, +0x07, +0x6d, +0x83, +0x71, +0x8a, +0x67, +0x25, +0x20, +0x0f, +0xfe, +0xd7, +0x02, +0xd7, +0x6e, +0x2c, +0xd2, +0x1a, +0x0a, +0x5d, +0xfd, +0x0f, +0x74, +0xe3, +0xa4, +0x36, +0x07, +0x9a, +0xdf, +0xd4, +0x79, +0xbf, +0xef, +0x59, +0xc0, +0x44, +0x52, +0x87, +0x9a, +0x6e, +0x1d, +0x0e, +0xee, +0xde, +0x2e, +0x1a, +0xa9, +0x8f, +0x3a, +0xc9, +0xba, +0xec, +0x99, +0x78, +0x2d, +0x55, +0x6b, +0x14, +0xc2, +0x06, +0xd5, +0xfc, +0x93, +0x53, +0x4d, +0x11, +0x8c, +0xf8, +0xfa, +0x79, +0x7c, +0xa6, +0x64, +0xae, +0x61, +0xb8, +0x7b, +0x94, +0x56, +0xa6, +0x39, +0x78, +0x9a, +0xe5, +0xc7, +0xdf, +0x18, +0x63, +0x23, +0x9c, +0xfa, +0x66, +0xbb, +0xb7, +0x5a, +0x27, +0x4c, +0xd1, +0xa1, +0x83, +0x22, +0xb3, +0x52, +0x49, +0x35, +0xb0, +0x22, +0x83, +0x59, +0x12, +0x00, +0x16, +0x98, +0xdd, +0xad, +0xc2, +0x94, +0xf9, +0xd3, +0x7b, +0x64, +0x7f, +0x44, +0x3e, +0x3c, +0x8b, +0x9a, +0x83, +0x9c, +0x69, +0x6b, +0xe4, +0xdf, +0x9f, +0xed, +0x54, +0x1f, +0xe5, +0x5d, +0x7a, +0x05, +0x82, +0xb3, +0xdd, +0xef, +0xfc, +0x53, +0x96, +0xb0, +0x2c, +0x5a, +0xf8, +0xdf, +0x9c, +0x8b, +0x16, +0x4e, +0xdf, +0xda, +0x4d, +0x09, +0x09, +0x69, +0x50, +0x03, +0x65, +0xd8, +0x73, +0x70, +0xe8, +0x86, +0xbf, +0xbb, +0x35, +0xce, +0xb2, +0x46, +0xcb, +0x02, +0x00, +0x5b, +0xb4, +0xe2, +0xc6, +0x8f, +0x2f, +0x98, +0xaf, +0x87, +0x4b, +0x48, +0x45, +0xed, +0xcc, +0x1d, +0xe6, +0x58, +0xd6, +0xf2, +0x50, +0x25, +0x9f, +0x52, +0xc7, +0xcb, +0x8a, +0x17, +0x9d, +0x5b, +0xe5, +0xc8, +0xd7, +0x72, +0xb7, +0x52, +0xb2, +0xc4, +0x98, +0xe3, +0x7a, +0x17, +0x3e, +0xc6, +0x60, +0xa7, +0x97, +0xb0, +0xcf, +0x18, +0x81, +0x53, +0x84, +0x4c, +0xd5, +0x17, +0x32, +0x03, +0x13, +0x39, +0x51, +0x09, +0x10, +0xe3, +0x77, +0x49, +0x4f, +0x62, +0x01, +0xbf, +0x8c, +0x9a, +0xe0, +0x41, +0x9e, +0x89, +0x74, +0x36, +0xf9, +0x96, +0x86, +0x2e, +0x96, +0x1c, +0x4a, +0xb7, +0x2b, +0x4a, +0x97, +0xbc, +0x99, +0x40, +0xa3, +0xe0, +0x3d, +0xc8, +0xad, +0x2f, +0xdf, +0x4f, +0x2c, +0xc4, +0x69, +0x82, +0x9f, +0x9b, +0x81, +0x0c, +0x61, +0x5c, +0xa5, +0x9d, +0x8c, +0x89, +0xc0, +0x2c, +0xb4, +0x4a, +0x33, +0x4e, +0xeb, +0xa2, +0x56, +0x40, +0xc0, +0xc2, +0x46, +0xaf, +0x6a, +0xfc, +0x67, +0xd1, +0x80, +0x5e, +0xc5, +0x6d, +0x84, +0x43, +0x27, +0x3f, +0x55, +0x15, +0x96, +0x6a, +0xa0, +0xa5, +0xda, +0xb7, +0xff, +0xb7, +0x75, +0x6e, +0x4c, +0x49, +0x91, +0x9d, +0x22, +0xa3, +0x46, +0xea, +0xed, +0x9a, +0x00, +0xe2, +0x32, +0xc3, +0xd6, +0xa9, +0x71, +0x20, +0x55, +0xa3, +0x19, +0xed, +0xf8, +0x4f, +0xa7, +0x12, +0x9c, +0x66, +0x87, +0xaf, +0x4e, +0xb7, +0xf0, +0xdb, +0xbf, +0xef, +0xf0, +0xf6, +0xaf, +0xea, +0xda, +0x09, +0xfe, +0xde, +0x38, +0x5c, +0xa5, +0xa2, +0xdf, +0x99, +0x45, +0xa8, +0xe4, +0xe7, +0x92, +0xac, +0x67, +0xaa, +0x4f, +0xbf, +0x77, +0x3e, +0xa2, +0x40, +0x49, +0x22, +0x4a, +0x1e, +0x3b, +0xaa, +0x70, +0x7f, +0x95, +0xaf, +0x37, +0x4b, +0xfc, +0x99, +0xe2, +0xe0, +0xba, +0xd7, +0x34, +0xce, +0x55, +0x88, +0x5b, +0x84, +0x1b, +0x57, +0xc4, +0x80, +0x03, +0x53, +0xc9, +0x2f, +0x93, +0x04, +0x4d, +0xd5, +0x96, +0xe5, +0x70, +0xa6, +0x6e, +0x63, +0x5d, +0x9d, +0x6c, +0xdb, +0x02, +0x0a, +0xa9, +0xda, +0x8b, +0x53, +0xdc, +0xd9, +0x9a, +0xc5, +0x94, +0x2c, +0x91, +0x92, +0x2a, +0xde, +0xbb, +0x8b, +0x13, +0xb9, +0x19, +0x96, +0x64, +0xcc, +0xf2, +0x64, +0x39, +0xb7, +0x75, +0x49, +0xe9, +0x86, +0xc2, +0x86, +0x62, +0xd9, +0x24, +0xd3, +0x81, +0x35, +0x49, +0xfc, +0xa0, +0xa5, +0xa0, +0x93, +0x05, +0x64, +0xb4, +0x1a, +0x57, +0xce, +0x0c, +0x90, +0x02, +0x27, +0xc5, +0x7a, +0x2b, +0x5d, +0xae, +0x3e, +0xd5, +0xdd, +0x10, +0x7c, +0x14, +0xea, +0x3a, +0x08, +0xac, +0x72, +0x4e, +0x90, +0x3d, +0x3b, +0x7c, +0x86, +0x2e, +0xeb, +0xd4, +0x06, +0x70, +0xe6, +0xc7, +0xfb, +0x5f, +0xbd, +0x18, +0xf4, +0x11, +0xa4, +0x1a, +0x93, +0xc3, +0xbe, +0xd9, +0xfb, +0x26, +0x48, +0x2f, +0x37, +0x3c, +0xd0, +0x03, +0x47, +0x1a, +0xf7, +0x62, +0x19, +0x24, +0x5c, +0xf4, +0xa8, +0x92, +0x20, +0x7a, +0xf2, +0x9e, +0x2a, +0xc5, +0x95, +0xa2, +0xfb, +0xa4, +0xea, +0x85, +0xd8, +0x56, +0xb7, +0x70, +0xd1, +0x60, +0x30, +0xa5, +0x30, +0x82, +0x70, +0xdc, +0x7a, +0x65, +0x8a, +0x36, +0x3f, +0x5b, +0x0c, +0xae, +0x54, +0x7c, +0xd3, +0x57, +0x84, +0x7b, +0x3a, +0x65, +0x18, +0x81, +0xee, +0x05, +0x9b, +0x44, +0x4d, +0xb8, +0xda, +0xa2, +0xa1, +0xc9, +0x15, +0xd3, +0x73, +0x03, +0x0e, +0x43, +0xe9, +0x8e, +0x15, +0xf9, +0xbe, +0xc6, +0xc5, +0x8a, +0xe5, +0xc0, +0x1e, +0xc2, +0x37, +0x9e, +0x2a, +0x26, +0xa5, +0xa0, +0xbd, +0x24, +0x5f, +0xb9, +0xc1, +0xab, +0x34, +0x48, +0xb9, +0x5d, +0x98, +0xb4, +0x65, +0x18, +0xf3, +0x63, +0x19, +0x44, +0x1b, +0x11, +0x16, +0xff, +0xdc, +0xf1, +0x79, +0x08, +0x86, +0x0f, +0x52, +0x98, +0x73, +0xc4, +0x92, +0x90, +0x2b, +0x47, +0x09, +0xd0, +0x43, +0x6c, +0x2f, +0x20, +0xeb, +0xdc, +0xda, +0xc5, +0x08, +0x7b, +0x94, +0x42, +0x30, +0x6a, +0xc7, +0xda, +0x8c, +0xc3, +0x76, +0xa7, +0xa5, +0xcc, +0x62, +0x13, +0x00, +0x60, +0x31, +0x58, +0x44, +0x9b, +0xf5, +0x64, +0x14, +0xf5, +0x11, +0xc5, +0x54, +0x52, +0x83, +0xd4, +0x73, +0x01, +0x16, +0x0e, +0xb3, +0x7a, +0x29, +0x69, +0x35, +0x56, +0xd4, +0xee, +0x8a, +0x17, +0xa2, +0x99, +0x24, +0x9c, +0xd7, +0x8f, +0xdb, +0x55, +0xb5, +0x3e +}; diff --git a/drivers/sensors/pimoroni_trackball.c b/drivers/sensors/pimoroni_trackball.c new file mode 100644 index 0000000000..48098ff0cc --- /dev/null +++ b/drivers/sensors/pimoroni_trackball.c @@ -0,0 +1,201 @@ +/* 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 "pimoroni_trackball.h" +#include "i2c_master.h" +#include "print.h" + +#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_MOUSE_SCALE +# define PIMORONI_TRACKBALL_MOUSE_SCALE 5 +#endif +#ifndef PIMORONI_TRACKBALL_SCROLL_SCALE +# 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 uint8_t error_count = 0; + +float trackball_get_precision(void) { return ((float)precision / 128); } +void 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) { + 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); +#ifdef TRACKBALL_DEBUG + dprintf("Trackball RGBW i2c_status_t: %d\n", status); +#endif +} + +i2c_status_t read_pimoroni_trackball(pimoroni_data* data) { + i2c_status_t status = i2c_readReg(PIMORONI_TRACKBALL_ADDRESS << 1, TRACKBALL_REG_LEFT, (uint8_t*)data, sizeof(*data), TRACKBALL_TIMEOUT); +#ifdef TRACKBALL_DEBUG + 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); +#endif + return status; +} + +__attribute__((weak)) void pointing_device_init(void) { + i2c_init(); + trackball_set_rgbw(0x00, 0x00, 0x00, 0x00); +} + +int16_t trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale) { + uint8_t offset = 0; + bool isnegative = false; + if (negative_dir > positive_dir) { + offset = negative_dir - positive_dir; + isnegative = true; + } else { + offset = positive_dir - negative_dir; + } + uint16_t magnitude = (scale * offset * offset * precision) >> 7; + return isnegative ? -(int16_t)(magnitude) : (int16_t)(magnitude); +} + +void trackball_adapt_values(int8_t* mouse, int16_t* offset) { + if (*offset > 127) { + *mouse = 127; + *offset -= 127; + } else if (*offset < -127) { + *mouse = -127; + *offset += 127; + } else { + *mouse = *offset; + *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(¤t_pimoroni_data); + + if (status == I2C_STATUS_SUCCESS) { + error_count = 0; + + if (pointing_device_task_user(¤t_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(); + } +} diff --git a/drivers/sensors/pimoroni_trackball.h b/drivers/sensors/pimoroni_trackball.h new file mode 100644 index 0000000000..6b2a41425d --- /dev/null +++ b/drivers/sensors/pimoroni_trackball.h @@ -0,0 +1,37 @@ +/* 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/>. + */ +#pragma once + +#include "quantum.h" +#include "pointing_device.h" + +typedef struct pimoroni_data { + uint8_t left; + uint8_t right; + uint8_t up; + uint8_t down; + uint8_t click; +} pimoroni_data; + +void trackball_set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white); +void trackball_click(bool pressed, report_mouse_t* mouse); +int16_t trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale); +void trackball_adapt_values(int8_t* mouse, int16_t* offset); +float trackball_get_precision(void); +void trackball_set_precision(float precision); +bool trackball_is_scrolling(void); +void trackball_set_scrolling(bool scroll); diff --git a/drivers/sensors/pmw3360.c b/drivers/sensors/pmw3360.c new file mode 100644 index 0000000000..13c5bdea26 --- /dev/null +++ b/drivers/sensors/pmw3360.c @@ -0,0 +1,239 @@ +/* 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 "wait.h" +#include "debug.h" +#include "print.h" +#include "pmw3360.h" +#include "pmw3360_firmware.h" + +bool _inBurst = false; + +#ifndef PMW_CPI +# define PMW_CPI 1600 +#endif +#ifndef PMW_CLOCK_SPEED +# define PMW_CLOCK_SPEED 70000000 +#endif +#ifndef SPI_MODE +# define SPI_MODE 3 +#endif +#ifndef SPI_DIVISOR +# define SPI_DIVISOR (F_CPU / PMW_CLOCK_SPEED) +#endif +#ifndef ROTATIONAL_TRANSFORM_ANGLE +# define ROTATIONAL_TRANSFORM_ANGLE 0x00 +#endif +#ifndef PMW_CS_PIN +# define PMW_CS_PIN SPI_SS_PIN +#endif + +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')); } + +bool spi_start_adv(void) { + bool status = spi_start(PMW_CS_PIN, false, SPI_MODE, 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) { + uint8_t cpival = constrain((cpi / 100) - 1, 0, 0x77); // limits to 0--119 + + spi_start_adv(); + spi_write_adv(REG_Config1, cpival); + spi_stop(); +} + +uint16_t pmw_get_cpi(void) { + uint8_t cpival = spi_read_adv(REG_Config1); + return (uint16_t)(cpival & 0xFF) * 100; +} + +bool pmw_spi_init(void) { + setPinOutput(PMW_CS_PIN); + + 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_stop_adv(); + + wait_ms(10); + pmw_set_cpi(PMW_CPI); + + wait_ms(1); + + spi_write_adv(REG_Config2, 0x00); + + spi_write_adv(REG_Angle_Tune, constrain(ROTATIONAL_TRANSFORM_ANGLE, -30, 30)); + + bool init_success = pmw_check_signature(); + + writePinLow(PMW_CS_PIN); + + return init_success; +} + +void pmw_upload_firmware(void) { + 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) { + dprintf("burst on"); + 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(); + + if (debug_mouse) { + print_byte(data.motion); + print_byte(data.dx); + print_byte(data.mdx); + print_byte(data.dy); + print_byte(data.mdy); + dprintf("\n"); + } + + 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; +} diff --git a/drivers/sensors/pmw3360.h b/drivers/sensors/pmw3360.h new file mode 100644 index 0000000000..d5b1741791 --- /dev/null +++ b/drivers/sensors/pmw3360.h @@ -0,0 +1,104 @@ +/* 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); +uint16_t pmw_get_cpi(void); +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))) diff --git a/drivers/sensors/pmw3360_firmware.h b/drivers/sensors/pmw3360_firmware.h new file mode 100644 index 0000000000..cca5a6a4d8 --- /dev/null +++ b/drivers/sensors/pmw3360_firmware.h @@ -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 |