1
0
Fork 0

Rework and expand Pointing Device support (#14343)

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

View file

@ -20,81 +20,95 @@
#include "adns5050.h"
#include "wait.h"
#include "debug.h"
#include "print.h"
#include "gpio.h"
#ifndef OPTIC_ROTATED
# define OPTIC_ROTATED false
#endif
// Registers
// clang-format off
#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
// clang-format on
// Definitions for the ADNS serial line.
#ifndef ADNS_SCLK_PIN
# define ADNS_SCLK_PIN B7
#endif
void adns5050_init(void) {
// Initialize the ADNS serial pins.
setPinOutput(ADNS5050_SCLK_PIN);
setPinOutput(ADNS5050_SDIO_PIN);
setPinOutput(ADNS5050_CS_PIN);
#ifndef ADNS_SDIO_PIN
# define ADNS_SDIO_PIN C6
#endif
// reboot the adns.
// if the adns hasn't initialized yet, this is harmless.
adns5050_write_reg(REG_CHIP_RESET, 0x5a);
#ifndef ADNS_CS_PIN
# define ADNS_CS_PIN B4
#endif
// wait maximum time before adns is ready.
// this ensures that the adns is actuall ready after reset.
wait_ms(55);
#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);
// read a burst from the adns and then discard it.
// gets the adns ready for write commands
// (for example, setting the dpi).
adns5050_read_burst();
}
// Perform a synchronization with the ADNS.
// 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);
void adns5050_sync(void) {
writePinLow(ADNS5050_CS_PIN);
wait_us(1);
writePinHigh(ADNS_CS_PIN);
writePinHigh(ADNS5050_CS_PIN);
}
void adns_cs_select(void) { writePinLow(ADNS_CS_PIN); }
void adns5050_cs_select(void) { writePinLow(ADNS5050_CS_PIN); }
void adns_cs_deselect(void) { writePinHigh(ADNS_CS_PIN); }
void adns5050_cs_deselect(void) { writePinHigh(ADNS5050_CS_PIN); }
uint8_t adns_serial_read(void) {
setPinInput(ADNS_SDIO_PIN);
uint8_t adns5050_serial_read(void) {
setPinInput(ADNS5050_SDIO_PIN);
uint8_t byte = 0;
for (uint8_t i = 0; i < 8; ++i) {
writePinLow(ADNS_SCLK_PIN);
writePinLow(ADNS5050_SCLK_PIN);
wait_us(1);
byte = (byte << 1) | readPin(ADNS_SDIO_PIN);
byte = (byte << 1) | readPin(ADNS5050_SDIO_PIN);
writePinHigh(ADNS_SCLK_PIN);
writePinHigh(ADNS5050_SCLK_PIN);
wait_us(1);
}
return byte;
}
void adns_serial_write(uint8_t data) {
setPinOutput(ADNS_SDIO_PIN);
void adns5050_serial_write(uint8_t data) {
setPinOutput(ADNS5050_SDIO_PIN);
for (int8_t b = 7; b >= 0; b--) {
writePinLow(ADNS_SCLK_PIN);
writePinLow(ADNS5050_SCLK_PIN);
if (data & (1 << b))
writePinHigh(ADNS_SDIO_PIN);
writePinHigh(ADNS5050_SDIO_PIN);
else
writePinLow(ADNS_SDIO_PIN);
writePinLow(ADNS5050_SDIO_PIN);
wait_us(2);
writePinHigh(ADNS_SCLK_PIN);
writePinHigh(ADNS5050_SCLK_PIN);
}
// tSWR. See page 15 of the ADNS spec sheet.
@ -108,17 +122,17 @@ void adns_serial_write(uint8_t data) {
// Read a byte of data from a register on the ADNS.
// 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();
uint8_t adns5050_read_reg(uint8_t reg_addr) {
adns5050_cs_select();
adns_serial_write(reg_addr);
adns5050_serial_write(reg_addr);
// We don't need a minimum tSRAD here. That's because a 4ms wait time is
// already included in adns_serial_write(), so we're good.
// already included in adns5050_serial_write(), so we're good.
// See page 10 and 15 of the ADNS spec sheet.
// wait_us(4);
uint8_t byte = adns_serial_read();
uint8_t byte = adns5050_serial_read();
// tSRW & tSRR. See page 15 of the ADNS spec sheet.
// Technically, this is only necessary if the next operation is an SDIO
@ -126,38 +140,38 @@ uint8_t adns_read_reg(uint8_t reg_addr) {
// Honestly, this wait could probably be removed.
wait_us(1);
adns_cs_deselect();
adns5050_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();
void adns5050_write_reg(uint8_t reg_addr, uint8_t data) {
adns5050_cs_select();
adns5050_serial_write(0b10000000 | reg_addr);
adns5050_serial_write(data);
adns5050_cs_deselect();
}
report_adns_t adns_read_burst(void) {
adns_cs_select();
report_adns5050_t adns5050_read_burst(void) {
adns5050_cs_select();
report_adns_t data;
report_adns5050_t data;
data.dx = 0;
data.dy = 0;
adns_serial_write(REG_MOTION_BURST);
adns5050_serial_write(REG_MOTION_BURST);
// We don't need a minimum tSRAD here. That's because a 4ms wait time is
// already included in adns_serial_write(), so we're good.
// already included in adns5050_serial_write(), so we're good.
// See page 10 and 15 of the ADNS spec sheet.
// wait_us(4);
uint8_t x = adns_serial_read();
uint8_t y = adns_serial_read();
uint8_t x = adns5050_serial_read();
uint8_t y = adns5050_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();
adns5050_cs_deselect();
data.dx = convert_twoscomp(x);
data.dy = convert_twoscomp(y);
@ -175,12 +189,21 @@ int8_t convert_twoscomp(uint8_t data) {
}
// Don't forget to use the definitions for CPI in the header file.
void adns_set_cpi(uint8_t cpi) { adns_write_reg(REG_MOUSE_CONTROL2, cpi); }
void adns5050_set_cpi(uint16_t cpi) {
uint8_t cpival = constrain((cpi / 125), 0x1, 0xD); // limits to 0--119
bool adns_check_signature(void) {
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);
adns5050_write_reg(REG_MOUSE_CONTROL2, 0b10000 | cpival);
}
uint16_t adns5050_get_cpi(void) {
uint8_t cpival = adns5050_read_reg(REG_MOUSE_CONTROL2);
return (uint16_t)((cpival & 0b10000) * 125);
}
bool adns5050_check_signature(void) {
uint8_t pid = adns5050_read_reg(REG_PRODUCT_ID);
uint8_t rid = adns5050_read_reg(REG_REVISION_ID);
uint8_t pid2 = adns5050_read_reg(REG_PRODUCT_ID2);
return (pid == 0x12 && rid == 0x01 && pid2 == 0x26);
}

View file

@ -21,59 +21,52 @@
#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
// clang-format off
#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
// clang-format on
#ifdef CONSOLE_ENABLE
void print_byte(uint8_t byte);
#define constrain(amt, low, high) ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)))
// Definitions for the ADNS serial line.
#ifndef ADNS5050_SCLK_PIN
# error "No clock pin defined -- missing ADNS5050_SCLK_PIN"
#endif
#ifndef ADNS5050_SDIO_PIN
# error "No data pin defined -- missing ADNS5050_SDIO_PIN"
#endif
#ifndef ADNS5050_CS_PIN
# error "No chip select pin defined -- missing ADNS5050_CS_PIN"
#endif
typedef struct {
int8_t dx;
int8_t dy;
} report_adns_t;
} report_adns5050_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);
void adns5050_init(void);
void adns5050_sync(void);
uint8_t adns5050_serial_read(void);
void adns5050_serial_write(uint8_t data);
uint8_t adns5050_read_reg(uint8_t reg_addr);
void adns5050_write_reg(uint8_t reg_addr, uint8_t data);
report_adns5050_t adns5050_read_burst(void);
void adns5050_set_cpi(uint16_t cpi);
uint16_t adns5050_get_cpi(void);
int8_t convert_twoscomp(uint8_t data);
bool adns5050_check_signature(void);

View file

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

View file

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

View file

@ -6,7 +6,7 @@
// clang-format off
const uint8_t firmware_data[FIRMWARE_LENGTH] PROGMEM = {
const uint8_t adns9800_firmware_data[FIRMWARE_LENGTH] PROGMEM = {
0x03, 0xA6, 0x68, 0x1E, 0x7D, 0x10, 0x7E, 0x7E, 0x5F, 0x1C, 0xB8, 0xF2, 0x47, 0x0C, 0x7B, 0x74,
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,

View file

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

View file

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

View file

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

View file

@ -0,0 +1,74 @@
// Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license
#pragma once
#include <stdint.h>
#include <stdbool.h>
// Convenient way to store and access measurements
typedef struct {
uint16_t xValue;
uint16_t yValue;
uint16_t zValue;
uint8_t buttonFlags;
bool touchDown;
} pinnacle_data_t;
void cirque_pinnacle_init(void);
pinnacle_data_t cirque_pinnacle_read_data(void);
void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResolution, uint16_t yResolution);
uint16_t cirque_pinnacle_get_scale(void);
void cirque_pinnacle_set_scale(uint16_t scale);
#ifndef CIRQUE_PINNACLE_TIMEOUT
# define CIRQUE_PINNACLE_TIMEOUT 20
#endif
// Coordinate scaling values
#ifndef CIRQUE_PINNACLE_X_LOWER
# define CIRQUE_PINNACLE_X_LOWER 127 // min "reachable" X value
#endif
#ifndef CIRQUE_PINNACLE_X_UPPER
# define CIRQUE_PINNACLE_X_UPPER 1919 // max "reachable" X value
#endif
#ifndef CIRQUE_PINNACLE_Y_LOWER
# define CIRQUE_PINNACLE_Y_LOWER 63 // min "reachable" Y value
#endif
#ifndef CIRQUE_PINNACLE_Y_UPPER
# define CIRQUE_PINNACLE_Y_UPPER 1471 // max "reachable" Y value
#endif
#ifndef CIRQUE_PINNACLE_X_RANGE
# define CIRQUE_PINNACLE_X_RANGE (CIRQUE_PINNACLE_X_UPPER - CIRQUE_PINNACLE_X_LOWER)
#endif
#ifndef CIRQUE_PINNACLE_Y_RANGE
# define CIRQUE_PINNACLE_Y_RANGE (CIRQUE_PINNACLE_Y_UPPER - CIRQUE_PINNACLE_Y_LOWER)
#endif
#if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c)
# include "i2c_master.h"
// Cirque's 7-bit I2C Slave Address
# ifndef CIRQUE_PINNACLE_ADDR
# define CIRQUE_PINNACLE_ADDR 0x2A
# endif
#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
# include "spi_master.h"
# ifndef CIRQUE_PINNACLE_CLOCK_SPEED
# define CIRQUE_PINNACLE_CLOCK_SPEED 10000000
# endif
# ifndef CIRQUE_PINNACLE_SPI_LSBFIRST
# define CIRQUE_PINNACLE_SPI_LSBFIRST false
# endif
# ifndef CIRQUE_PINNACLE_SPI_MODE
# define CIRQUE_PINNACLE_SPI_MODE 1
# endif
# ifndef CIRQUE_PINNACLE_SPI_DIVISOR
# ifdef __AVR__
# define CIRQUE_PINNACLE_SPI_DIVISOR (F_CPU / CIRQUE_PINNACLE_CLOCK_SPEED)
# else
# define CIRQUE_PINNACLE_SPI_DIVISOR 64
# endif
# ifndef CIRQUE_PINNACLE_SPI_CS_PIN
# error "No Chip Select pin has been defined -- missing CIRQUE_PINNACLE_SPI_CS_PIN define"
# endif
# endif
#endif

View file

@ -0,0 +1,43 @@
// Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license
#include "cirque_pinnacle.h"
#include "i2c_master.h"
#include "print.h"
#include "debug.h"
#include "stdio.h"
// Masks for Cirque Register Access Protocol (RAP)
#define WRITE_MASK 0x80
#define READ_MASK 0xA0
extern bool touchpad_init;
/* RAP Functions */
// Reads <count> Pinnacle registers starting at <address>
void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count) {
uint8_t cmdByte = READ_MASK | address; // Form the READ command byte
if (touchpad_init) {
i2c_writeReg(CIRQUE_PINNACLE_ADDR << 1, cmdByte, NULL, 0, CIRQUE_PINNACLE_TIMEOUT);
if (i2c_readReg(CIRQUE_PINNACLE_ADDR << 1, cmdByte, data, count, CIRQUE_PINNACLE_TIMEOUT) != I2C_STATUS_SUCCESS) {
#ifdef CONSOLE_ENABLE
dprintf("error right touchpad\n");
#endif
touchpad_init = false;
}
i2c_stop();
}
}
// Writes single-byte <data> to <address>
void RAP_Write(uint8_t address, uint8_t data) {
uint8_t cmdByte = WRITE_MASK | address; // Form the WRITE command byte
if (touchpad_init) {
if (i2c_writeReg(CIRQUE_PINNACLE_ADDR << 1, cmdByte, &data, sizeof(data), CIRQUE_PINNACLE_TIMEOUT) != I2C_STATUS_SUCCESS) {
#ifdef CONSOLE_ENABLE
dprintf("error right touchpad\n");
#endif
touchpad_init = false;
}
i2c_stop();
}
}

View file

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

View file

@ -17,73 +17,55 @@
#include "pimoroni_trackball.h"
#include "i2c_master.h"
#include "print.h"
#include "debug.h"
#include "timer.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
// clang-format off
#define PIMORONI_TRACKBALL_REG_LED_RED 0x00
#define PIMORONI_TRACKBALL_REG_LED_GRN 0x01
#define PIMORONI_TRACKBALL_REG_LED_BLU 0x02
#define PIMORONI_TRACKBALL_REG_LED_WHT 0x03
#define PIMORONI_TRACKBALL_REG_LEFT 0x04
#define PIMORONI_TRACKBALL_REG_RIGHT 0x05
#define PIMORONI_TRACKBALL_REG_UP 0x06
#define PIMORONI_TRACKBALL_REG_DOWN 0x07
// clang-format on
#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 uint16_t precision = 128;
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 pimoroni_trackball_get_precision(void) { return ((float)precision / 128); }
void pimoroni_trackball_set_precision(float floatprecision) { precision = (floatprecision * 128); }
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) {
void pimoroni_trackball_set_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
uint8_t data[4] = {r, g, b, w};
__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);
__attribute__((unused)) i2c_status_t status = i2c_writeReg(PIMORONI_TRACKBALL_ADDRESS << 1, PIMORONI_TRACKBALL_REG_LED_RED, data, sizeof(data), PIMORONI_TRACKBALL_TIMEOUT);
#ifdef CONSOLE_ENABLE
if (debug_mouse) 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);
i2c_status_t read_pimoroni_trackball(pimoroni_data_t* data) {
i2c_status_t status = i2c_readReg(PIMORONI_TRACKBALL_ADDRESS << 1, PIMORONI_TRACKBALL_REG_LEFT, (uint8_t*)data, sizeof(*data), PIMORONI_TRACKBALL_TIMEOUT);
#ifdef CONSOLE_ENABLE
if (debug_mouse) {
static uint16_t d_timer;
if (timer_elapsed(d_timer) > PIMORONI_TRACKBALL_DEBUG_INTERVAL) {
dprintf("Trackball READ i2c_status_t: %d L: %d R: %d Up: %d D: %d SW: %d\n", status, data->left, data->right, data->up, data->down, data->click);
d_timer = timer_read();
}
}
#endif
return status;
}
__attribute__((weak)) void pointing_device_init(void) {
__attribute__((weak)) void pimironi_trackball_device_init(void) {
i2c_init();
trackball_set_rgbw(0x00, 0x00, 0x00, 0x00);
pimoroni_trackball_set_rgbw(0x00, 0x00, 0x00, 0x00);
}
int16_t trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale) {
int16_t pimoroni_trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale) {
uint8_t offset = 0;
bool isnegative = false;
if (negative_dir > positive_dir) {
@ -96,7 +78,7 @@ int16_t trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_
return isnegative ? -(int16_t)(magnitude) : (int16_t)(magnitude);
}
void trackball_adapt_values(int8_t* mouse, int16_t* offset) {
void pimoroni_trackball_adapt_values(int8_t* mouse, int16_t* offset) {
if (*offset > 127) {
*mouse = 127;
*offset -= 127;
@ -108,94 +90,3 @@ void trackball_adapt_values(int8_t* mouse, int16_t* 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(&current_pimoroni_data);
if (status == I2C_STATUS_SUCCESS) {
error_count = 0;
if (pointing_device_task_user(&current_pimoroni_data)) {
mouse_report = pointing_device_get_report();
if (!(current_pimoroni_data.click & 128)) {
trackball_click(false, &mouse_report);
if (!debounce) {
if (scrolling) {
#ifdef PIMORONI_TRACKBALL_INVERT_X
h_offset += trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_SCROLL_SCALE);
#else
h_offset -= trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_SCROLL_SCALE);
#endif
#ifdef PIMORONI_TRACKBALL_INVERT_Y
v_offset += trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_SCROLL_SCALE);
#else
v_offset -= trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_SCROLL_SCALE);
#endif
} else {
#ifdef PIMORONI_TRACKBALL_INVERT_X
x_offset -= trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_MOUSE_SCALE);
#else
x_offset += trackball_get_offsets(current_pimoroni_data.right, current_pimoroni_data.left, PIMORONI_TRACKBALL_MOUSE_SCALE);
#endif
#ifdef PIMORONI_TRACKBALL_INVERT_Y
y_offset -= trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_MOUSE_SCALE);
#else
y_offset += trackball_get_offsets(current_pimoroni_data.down, current_pimoroni_data.up, PIMORONI_TRACKBALL_MOUSE_SCALE);
#endif
}
if (scrolling) {
#ifndef PIMORONI_TRACKBALL_ROTATE
trackball_adapt_values(&mouse_report.h, &h_offset);
trackball_adapt_values(&mouse_report.v, &v_offset);
#else
trackball_adapt_values(&mouse_report.h, &v_offset);
trackball_adapt_values(&mouse_report.v, &h_offset);
#endif
mouse_report.x = 0;
mouse_report.y = 0;
} else {
#ifndef PIMORONI_TRACKBALL_ROTATE
trackball_adapt_values(&mouse_report.x, &x_offset);
trackball_adapt_values(&mouse_report.y, &y_offset);
#else
trackball_adapt_values(&mouse_report.x, &y_offset);
trackball_adapt_values(&mouse_report.y, &x_offset);
#endif
mouse_report.h = 0;
mouse_report.v = 0;
}
} else {
debounce--;
}
} else {
trackball_click(true, &mouse_report);
debounce = PIMORONI_TRACKBALL_DEBOUNCE_CYCLES;
}
}
} else {
error_count++;
}
pointing_device_set_report(mouse_report);
pointing_device_send();
throttle = timer_read_fast();
}
}

View file

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

View file

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

View file

@ -18,6 +18,8 @@
#pragma once
#include <stdint.h>
#include "report.h"
#include "spi_master.h"
#ifndef PMW3360_CPI
@ -25,7 +27,7 @@
#endif
#ifndef PMW3360_CLOCK_SPEED
# define PMW3360_CLOCK_SPEED 70000000
# define PMW3360_CLOCK_SPEED 2000000
#endif
#ifndef PMW3360_SPI_LSBFIRST
@ -52,6 +54,17 @@
# error "No chip select pin defined -- missing PMW3360_CS_PIN"
#endif
/*
The pmw33660 and pmw3389 use the same registers and timing and such.
The only differences between the two is the firmware used, and the
range for the DPI. So add a semi-secret hack to allow use of the
pmw3389's firmware blob. Also, can set the max cpi range too.
This should work for the 3390 and 3391 too, in theory.
*/
#ifndef PMW3360_FIRMWARE_H
# define PMW3360_FIRMWARE_H "pmw3360_firmware.h"
#endif
#ifdef CONSOLE_ENABLE
void print_byte(uint8_t byte);
#endif
@ -64,19 +77,18 @@ typedef struct {
int8_t mdx;
int16_t dy; // displacement on y directions.
int8_t mdy;
} report_pmw_t;
} report_pmw3360_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);
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 pmw3360_init(void);
void pmw3360_set_cpi(uint16_t cpi);
uint16_t pmw3360_get_cpi(void);
void pmw3360_upload_firmware(void);
bool pmw3360_check_signature(void);
report_pmw3360_t pmw3360_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)))

View file

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

View file

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