summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/avr/i2c_master.c19
-rw-r--r--drivers/avr/i2c_slave.c29
-rw-r--r--drivers/avr/i2c_slave.h13
-rw-r--r--drivers/avr/serial.c82
-rw-r--r--drivers/avr/serial.h62
-rw-r--r--drivers/awinic/aw20216.c182
-rw-r--r--drivers/awinic/aw20216.h251
-rw-r--r--drivers/chibios/serial.c52
-rw-r--r--drivers/chibios/serial.h62
-rw-r--r--drivers/chibios/serial_usart.c352
-rw-r--r--drivers/chibios/serial_usart.h40
-rw-r--r--drivers/chibios/serial_usart_duplex.c261
-rw-r--r--drivers/chibios/spi_master.c70
-rw-r--r--drivers/chibios/spi_master.h19
-rw-r--r--drivers/eeprom/eeprom_i2c.c23
-rw-r--r--drivers/haptic/haptic.c71
-rw-r--r--drivers/issi/is31fl3737.c54
-rw-r--r--drivers/lcd/st7565.c496
-rw-r--r--drivers/lcd/st7565.h219
-rw-r--r--drivers/oled/oled_driver.c26
-rw-r--r--drivers/oled/oled_driver.h4
-rw-r--r--drivers/sensors/adns5050.c193
-rw-r--r--drivers/sensors/adns5050.h79
-rw-r--r--drivers/sensors/adns9800.c219
-rw-r--r--drivers/sensors/adns9800.h35
-rw-r--r--drivers/sensors/adns9800_srom_A6.h3078
-rw-r--r--drivers/sensors/pimoroni_trackball.c140
-rw-r--r--drivers/sensors/pimoroni_trackball.h35
-rw-r--r--drivers/sensors/pmw3360.c237
-rw-r--r--drivers/sensors/pmw3360.h104
-rw-r--r--drivers/sensors/pmw3360_firmware.h300
-rw-r--r--drivers/serial.h46
32 files changed, 6207 insertions, 646 deletions
diff --git a/drivers/avr/i2c_master.c b/drivers/avr/i2c_master.c
index b1e4885298..2773e00778 100644
--- a/drivers/avr/i2c_master.c
+++ b/drivers/avr/i2c_master.c
@@ -28,8 +28,14 @@
# define F_SCL 400000UL // SCL frequency
#endif
+#ifndef I2C_START_RETRY_COUNT
+# define I2C_START_RETRY_COUNT 20
+#endif // I2C_START_RETRY_COUNT
+
#define TWBR_val (((F_CPU / F_SCL) - 16) / 2)
+#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
+
void i2c_init(void) {
TWSR = 0; /* no prescaler */
TWBR = (uint8_t)TWBR_val;
@@ -47,7 +53,7 @@ void i2c_init(void) {
#endif
}
-i2c_status_t i2c_start(uint8_t address, uint16_t timeout) {
+static i2c_status_t i2c_start_impl(uint8_t address, uint16_t timeout) {
// reset TWI control register
TWCR = 0;
// transmit START condition
@@ -86,6 +92,17 @@ i2c_status_t i2c_start(uint8_t address, uint16_t timeout) {
return I2C_STATUS_SUCCESS;
}
+i2c_status_t i2c_start(uint8_t address, uint16_t timeout) {
+ // Retry i2c_start_impl a bunch times in case the remote side has interrupts disabled.
+ uint16_t timeout_timer = timer_read();
+ uint16_t time_slice = MAX(1, (timeout == (I2C_TIMEOUT_INFINITE)) ? 5 : (timeout / (I2C_START_RETRY_COUNT))); // if it's infinite, wait 1ms between attempts, otherwise split up the entire timeout into the number of retries
+ i2c_status_t status;
+ do {
+ status = i2c_start_impl(address, time_slice);
+ } while ((status < 0) && ((timeout == I2C_TIMEOUT_INFINITE) || (timer_elapsed(timeout_timer) < timeout)));
+ return status;
+}
+
i2c_status_t i2c_write(uint8_t data, uint16_t timeout) {
// load data into data register
TWDR = data;
diff --git a/drivers/avr/i2c_slave.c b/drivers/avr/i2c_slave.c
index 62a378165a..2907f164c0 100644
--- a/drivers/avr/i2c_slave.c
+++ b/drivers/avr/i2c_slave.c
@@ -17,6 +17,7 @@
* GitHub repository: https://github.com/g4lvanix/I2C-slave-lib
*/
+#include <stddef.h>
#include <avr/io.h>
#include <util/twi.h>
#include <avr/interrupt.h>
@@ -24,6 +25,12 @@
#include "i2c_slave.h"
+#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+# include "transactions.h"
+
+static volatile bool is_callback_executor = false;
+#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+
volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];
static volatile uint8_t buffer_address;
@@ -48,11 +55,14 @@ ISR(TWI_vect) {
case TW_SR_SLA_ACK:
// The device is now a slave receiver
slave_has_register_set = false;
+#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+ is_callback_executor = false;
+#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
break;
case TW_SR_DATA_ACK:
// This device is a slave receiver and has received data
- // First byte is the location then the bytes will be writen in buffer with auto-incriment
+ // First byte is the location then the bytes will be writen in buffer with auto-increment
if (!slave_has_register_set) {
buffer_address = TWDR;
@@ -60,10 +70,25 @@ ISR(TWI_vect) {
ack = 0;
buffer_address = 0;
}
- slave_has_register_set = true; // address has been receaved now fill in buffer
+ slave_has_register_set = true; // address has been received now fill in buffer
+
+#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+ // Work out if we're attempting to execute a callback
+ is_callback_executor = buffer_address == split_transaction_table[I2C_EXECUTE_CALLBACK].initiator2target_offset;
+#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
} else {
i2c_slave_reg[buffer_address] = TWDR;
buffer_address++;
+
+#if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+ // If we're intending to execute a transaction callback, do so, as we've just received the transaction ID
+ if (is_callback_executor) {
+ split_transaction_desc_t *trans = &split_transaction_table[split_shmem->transaction_id];
+ if (trans->slave_callback) {
+ trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
+ }
+ }
+#endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
}
break;
diff --git a/drivers/avr/i2c_slave.h b/drivers/avr/i2c_slave.h
index 1cd0625ef4..a8647c9da3 100644
--- a/drivers/avr/i2c_slave.h
+++ b/drivers/avr/i2c_slave.h
@@ -22,7 +22,18 @@
#pragma once
-#define I2C_SLAVE_REG_COUNT 30
+#ifndef I2C_SLAVE_REG_COUNT
+
+# if defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+# include "transport.h"
+# define I2C_SLAVE_REG_COUNT sizeof(split_shared_memory_t)
+# else // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+# define I2C_SLAVE_REG_COUNT 30
+# endif // defined(USE_I2C) && defined(SPLIT_COMMON_TRANSACTIONS)
+
+#endif // I2C_SLAVE_REG_COUNT
+
+_Static_assert(I2C_SLAVE_REG_COUNT < 256, "I2C target registers must be single byte");
extern volatile uint8_t i2c_slave_reg[I2C_SLAVE_REG_COUNT];
diff --git a/drivers/avr/serial.c b/drivers/avr/serial.c
index 3647bee0d3..9a7345a53d 100644
--- a/drivers/avr/serial.c
+++ b/drivers/avr/serial.c
@@ -224,15 +224,8 @@
# define SERIAL_DELAY_HALF2 (SERIAL_DELAY - SERIAL_DELAY / 2)
# define SLAVE_INT_WIDTH_US 1
-# ifndef SERIAL_USE_MULTI_TRANSACTION
-# define SLAVE_INT_RESPONSE_TIME SERIAL_DELAY
-# else
-# define SLAVE_INT_ACK_WIDTH_UNIT 2
-# define SLAVE_INT_ACK_WIDTH 4
-# endif
-
-static SSTD_t *Transaction_table = NULL;
-static uint8_t Transaction_table_size = 0;
+# define SLAVE_INT_ACK_WIDTH_UNIT 2
+# define SLAVE_INT_ACK_WIDTH 4
inline static void serial_delay(void) ALWAYS_INLINE;
inline static void serial_delay(void) { _delay_us(SERIAL_DELAY); }
@@ -259,16 +252,12 @@ inline static void serial_low(void) { writePinLow(SOFT_SERIAL_PIN); }
inline static void serial_high(void) ALWAYS_INLINE;
inline static void serial_high(void) { writePinHigh(SOFT_SERIAL_PIN); }
-void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
+void soft_serial_initiator_init(void) {
serial_output();
serial_high();
}
-void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
+void soft_serial_target_init(void) {
serial_input_with_pullup();
// Enable INT0-INT7
@@ -395,19 +384,14 @@ static inline uint8_t nibble_bits_count(uint8_t bits) {
// interrupt handle to be used by the target device
ISR(SERIAL_PIN_INTERRUPT) {
-# ifndef SERIAL_USE_MULTI_TRANSACTION
- serial_low();
- serial_output();
- SSTD_t *trans = Transaction_table;
-# else
// recive transaction table index
uint8_t tid, bits;
uint8_t pecount = 0;
sync_recv();
- bits = serial_read_chunk(&pecount, 7);
+ bits = serial_read_chunk(&pecount, 8);
tid = bits >> 3;
- bits = (bits & 7) != nibble_bits_count(tid);
- if (bits || pecount > 0 || tid > Transaction_table_size) {
+ bits = (bits & 7) != (nibble_bits_count(tid) & 7);
+ if (bits || pecount > 0 || tid > NUM_TOTAL_TRANSACTIONS) {
return;
}
serial_delay_half1();
@@ -415,18 +399,22 @@ ISR(SERIAL_PIN_INTERRUPT) {
serial_high(); // response step1 low->high
serial_output();
_delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT * SLAVE_INT_ACK_WIDTH);
- SSTD_t *trans = &Transaction_table[tid];
+ split_transaction_desc_t *trans = &split_transaction_table[tid];
serial_low(); // response step2 ack high->low
-# endif
+
+ // If the transaction has a callback, we can execute it now
+ if (trans->slave_callback) {
+ trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
+ }
// target send phase
- if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)trans->target2initiator_buffer, trans->target2initiator_buffer_size);
+ if (trans->target2initiator_buffer_size > 0) serial_send_packet((uint8_t *)split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size);
// target switch to input
change_sender2reciver();
// target recive phase
if (trans->initiator2target_buffer_size > 0) {
- if (serial_recive_packet((uint8_t *)trans->initiator2target_buffer, trans->initiator2target_buffer_size)) {
+ if (serial_recive_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size)) {
*trans->status = TRANSACTION_ACCEPTED;
} else {
*trans->status = TRANSACTION_DATA_ERROR;
@@ -448,14 +436,12 @@ ISR(SERIAL_PIN_INTERRUPT) {
// TRANSACTION_NO_RESPONSE
// TRANSACTION_DATA_ERROR
// this code is very time dependent, so we need to disable interrupts
-# ifndef SERIAL_USE_MULTI_TRANSACTION
-int soft_serial_transaction(void) {
- SSTD_t *trans = Transaction_table;
-# else
int soft_serial_transaction(int sstd_index) {
- if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
- SSTD_t *trans = &Transaction_table[sstd_index];
-# endif
+ if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR;
+ split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
+
+ if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered
+
cli();
// signal to the target that we want to start a transaction
@@ -463,27 +449,11 @@ int soft_serial_transaction(int sstd_index) {
serial_low();
_delay_us(SLAVE_INT_WIDTH_US);
-# ifndef SERIAL_USE_MULTI_TRANSACTION
- // wait for the target response
- serial_input_with_pullup();
- _delay_us(SLAVE_INT_RESPONSE_TIME);
-
- // check if the target is present
- if (serial_read_pin()) {
- // target failed to pull the line low, assume not present
- serial_output();
- serial_high();
- *trans->status = TRANSACTION_NO_RESPONSE;
- sei();
- return TRANSACTION_NO_RESPONSE;
- }
-
-# else
// send transaction table index
int tid = (sstd_index << 3) | (7 & nibble_bits_count(sstd_index));
sync_send();
_delay_sub_us(TID_SEND_ADJUST);
- serial_write_chunk(tid, 7);
+ serial_write_chunk(tid, 8);
serial_delay_half1();
// wait for the target response (step1 low->high)
@@ -504,12 +474,11 @@ int soft_serial_transaction(int sstd_index) {
}
_delay_sub_us(SLAVE_INT_ACK_WIDTH_UNIT);
}
-# endif
// initiator recive phase
// if the target is present syncronize with it
if (trans->target2initiator_buffer_size > 0) {
- if (!serial_recive_packet((uint8_t *)trans->target2initiator_buffer, trans->target2initiator_buffer_size)) {
+ if (!serial_recive_packet((uint8_t *)split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size)) {
serial_output();
serial_high();
*trans->status = TRANSACTION_DATA_ERROR;
@@ -523,7 +492,7 @@ int soft_serial_transaction(int sstd_index) {
// initiator send phase
if (trans->initiator2target_buffer_size > 0) {
- serial_send_packet((uint8_t *)trans->initiator2target_buffer, trans->initiator2target_buffer_size);
+ serial_send_packet((uint8_t *)split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size);
}
// always, release the line when not in use
@@ -534,9 +503,8 @@ int soft_serial_transaction(int sstd_index) {
return TRANSACTION_END;
}
-# ifdef SERIAL_USE_MULTI_TRANSACTION
int soft_serial_get_and_clean_status(int sstd_index) {
- SSTD_t *trans = &Transaction_table[sstd_index];
+ split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
cli();
int retval = *trans->status;
*trans->status = 0;
@@ -544,8 +512,6 @@ int soft_serial_get_and_clean_status(int sstd_index) {
sei();
return retval;
}
-# endif
-
#endif
// Helix serial.c history
diff --git a/drivers/avr/serial.h b/drivers/avr/serial.h
deleted file mode 100644
index 53e66cf905..0000000000
--- a/drivers/avr/serial.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#pragma once
-
-#include <stdbool.h>
-
-// /////////////////////////////////////////////////////////////////
-// Need Soft Serial defines in config.h
-// /////////////////////////////////////////////////////////////////
-// ex.
-// #define SOFT_SERIAL_PIN ?? // ?? = D0,D1,D2,D3,E6
-// OPTIONAL: #define SELECT_SOFT_SERIAL_SPEED ? // ? = 1,2,3,4,5
-// // 1: about 137kbps (default)
-// // 2: about 75kbps
-// // 3: about 39kbps
-// // 4: about 26kbps
-// // 5: about 20kbps
-//
-// //// USE simple API (using signle-type transaction function)
-// /* nothing */
-// //// USE flexible API (using multi-type transaction function)
-// #define SERIAL_USE_MULTI_TRANSACTION
-//
-// /////////////////////////////////////////////////////////////////
-
-// Soft Serial Transaction Descriptor
-typedef struct _SSTD_t {
- uint8_t *status;
- uint8_t initiator2target_buffer_size;
- uint8_t *initiator2target_buffer;
- uint8_t target2initiator_buffer_size;
- uint8_t *target2initiator_buffer;
-} SSTD_t;
-#define TID_LIMIT(table) (sizeof(table) / sizeof(SSTD_t))
-
-// initiator is transaction start side
-void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size);
-// target is interrupt accept side
-void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size);
-
-// initiator resullt
-#define TRANSACTION_END 0
-#define TRANSACTION_NO_RESPONSE 0x1
-#define TRANSACTION_DATA_ERROR 0x2
-#define TRANSACTION_TYPE_ERROR 0x4
-#ifndef SERIAL_USE_MULTI_TRANSACTION
-int soft_serial_transaction(void);
-#else
-int soft_serial_transaction(int sstd_index);
-#endif
-
-// target status
-// *SSTD_t.status has
-// initiator:
-// TRANSACTION_END
-// or TRANSACTION_NO_RESPONSE
-// or TRANSACTION_DATA_ERROR
-// target:
-// TRANSACTION_DATA_ERROR
-// or TRANSACTION_ACCEPTED
-#define TRANSACTION_ACCEPTED 0x8
-#ifdef SERIAL_USE_MULTI_TRANSACTION
-int soft_serial_get_and_clean_status(int sstd_index);
-#endif
diff --git a/drivers/awinic/aw20216.c b/drivers/awinic/aw20216.c
new file mode 100644
index 0000000000..776653fa6c
--- /dev/null
+++ b/drivers/awinic/aw20216.c
@@ -0,0 +1,182 @@
+/* Copyright 2021 Jasper Chan
+ *
+ * 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 "aw20216.h"
+#include "spi_master.h"
+
+/* The AW20216 appears to be somewhat similar to the IS31FL743, although quite
+ * a few things are different, such as the command byte format and page ordering.
+ * The LED addresses start from 0x00 instead of 0x01.
+ */
+#define AWINIC_ID 0b1010 << 4
+
+#define AW_PAGE_FUNCTION 0x00 << 1 // PG0, Function registers
+#define AW_PAGE_PWM 0x01 << 1 // PG1, LED PWM control
+#define AW_PAGE_SCALING 0x02 << 1 // PG2, LED current scaling control
+#define AW_PAGE_PATCHOICE 0x03 << 1 // PG3, Pattern choice?
+#define AW_PAGE_PWMSCALING 0x04 << 1 // PG4, LED PWM + Scaling control?
+
+#define AW_WRITE 0
+#define AW_READ 1
+
+#define AW_REG_CONFIGURATION 0x00 // PG0
+#define AW_REG_GLOBALCURRENT 0x01 // PG0
+
+// Default value of AW_REG_CONFIGURATION
+// D7:D4 = 1011, SWSEL (SW1~SW12 active)
+// D3 = 0?, reserved (apparently this should be 1 but it doesn't seem to matter)
+// D2:D1 = 00, OSDE (open/short detection enable)
+// D0 = 0, CHIPEN (write 1 to enable LEDs when hardware enable pulled high)
+#define AW_CONFIG_DEFAULT 0b10110000
+#define AW_CHIPEN 1
+
+#define AW_PWM_REGISTER_COUNT 216
+
+#define AW_SPI_START(PIN) spi_start(PIN, false, 0, AW_SPI_DIVISOR)
+
+#ifndef AW_SCALING_MAX
+# define AW_SCALING_MAX 150
+#endif
+
+#ifndef AW_GLOBAL_CURRENT_MAX
+# define AW_GLOBAL_CURRENT_MAX 150
+#endif
+
+#ifndef DRIVER_1_CS
+# define DRIVER_1_CS B13
+#endif
+
+#ifndef DRIVER_1_EN
+# define DRIVER_1_EN C13
+#endif
+
+#ifndef AW_SPI_DIVISOR
+# define AW_SPI_DIVISOR 4
+#endif
+
+uint8_t g_spi_transfer_buffer[3] = {0};
+uint8_t g_pwm_buffer[DRIVER_COUNT][AW_PWM_REGISTER_COUNT];
+bool g_pwm_buffer_update_required[DRIVER_COUNT] = {false};
+
+bool AW20216_write_register(pin_t slave_pin, uint8_t page, uint8_t reg, uint8_t data) {
+ // Do we need to call spi_stop() if this fails?
+ if (!AW_SPI_START(slave_pin)) {
+ return false;
+ }
+
+ g_spi_transfer_buffer[0] = (AWINIC_ID | page | AW_WRITE);
+ g_spi_transfer_buffer[1] = reg;
+ g_spi_transfer_buffer[2] = data;
+
+ if (spi_transmit(g_spi_transfer_buffer, 3) != SPI_STATUS_SUCCESS) {
+ spi_stop();
+ return false;
+ }
+ spi_stop();
+ return true;
+}
+
+bool AW20216_init_scaling(void) {
+ // Set constant current to the max, control brightness with PWM
+ aw_led led;
+ for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
+ led = g_aw_leds[i];
+ if (led.driver == 0) {
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_SCALING, led.r, AW_SCALING_MAX);
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_SCALING, led.g, AW_SCALING_MAX);
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_SCALING, led.b, AW_SCALING_MAX);
+ }
+#ifdef DRIVER_2_CS
+ else if (led.driver == 1) {
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_SCALING, led.r, AW_SCALING_MAX);
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_SCALING, led.g, AW_SCALING_MAX);
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_SCALING, led.b, AW_SCALING_MAX);
+ }
+#endif
+ }
+ return true;
+}
+
+bool AW20216_soft_enable(void) {
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_FUNCTION, AW_REG_CONFIGURATION, AW_CONFIG_DEFAULT | AW_CHIPEN);
+#ifdef DRIVER_2_CS
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_FUNCTION, AW_REG_CONFIGURATION, AW_CONFIG_DEFAULT | AW_CHIPEN);
+#endif
+ return true;
+}
+
+void AW20216_update_pwm(int index, uint8_t red, uint8_t green, uint8_t blue) {
+ aw_led led = g_aw_leds[index];
+ if (led.driver == 0) {
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_PWM, led.r, red);
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_PWM, led.g, green);
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_PWM, led.b, blue);
+ }
+#ifdef DRIVER_2_CS
+ else if (led.driver == 1) {
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_PWM, led.r, red);
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_PWM, led.g, green);
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_PWM, led.b, blue);
+ }
+#endif
+ return;
+}
+
+void AW20216_init(void) {
+ // All LEDs should start with all scaling and PWM registers as off
+ setPinOutput(DRIVER_1_EN);
+ writePinHigh(DRIVER_1_EN);
+ AW20216_write_register(DRIVER_1_CS, AW_PAGE_FUNCTION, AW_REG_GLOBALCURRENT, AW_GLOBAL_CURRENT_MAX);
+#ifdef DRIVER_2_EN
+ setPinOutput(DRIVER_2_EN);
+ writePinHigh(DRIVER_2_EN);
+ AW20216_write_register(DRIVER_2_CS, AW_PAGE_FUNCTION, AW_REG_GLOBALCURRENT, AW_GLOBAL_CURRENT_MAX);
+#endif
+ AW20216_init_scaling();
+ AW20216_soft_enable();
+ return;
+}
+
+void AW20216_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) {
+ aw_led led = g_aw_leds[index];
+ g_pwm_buffer[led.driver][led.r] = red;
+ g_pwm_buffer[led.driver][led.g] = green;
+ g_pwm_buffer[led.driver][led.b] = blue;
+ g_pwm_buffer_update_required[led.driver] = true;
+ return;
+}
+void AW20216_set_color_all(uint8_t red, uint8_t green, uint8_t blue) {
+ for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) {
+ AW20216_set_color(i, red, green, blue);
+ }
+ return;
+}
+
+void AW20216_write_pwm_buffer(pin_t slave_pin, uint8_t buffer_idx) {
+ AW_SPI_START(slave_pin);
+ spi_write((AWINIC_ID | AW_PAGE_PWM | AW_WRITE));
+ spi_write(0);
+ spi_transmit(g_pwm_buffer[buffer_idx], AW_PWM_REGISTER_COUNT);
+ spi_stop();
+}
+
+void AW20216_update_pwm_buffers(void) {
+ AW20216_write_pwm_buffer(DRIVER_1_CS, 0);
+#ifdef DRIVER_2_CS
+ AW20216_write_pwm_buffer(DRIVER_2_CS, 1);
+#endif
+ return;
+}
diff --git a/drivers/awinic/aw20216.h b/drivers/awinic/aw20216.h
new file mode 100644
index 0000000000..9c6865cc82
--- /dev/null
+++ b/drivers/awinic/aw20216.h
@@ -0,0 +1,251 @@
+/* Copyright 2021 Jasper Chan (Gigahawk)
+ *
+ * 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>
+#include <stdbool.h>
+
+typedef struct aw_led {
+ uint8_t driver : 2;
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+} aw_led;
+
+extern const aw_led g_aw_leds[DRIVER_LED_TOTAL];
+
+void AW20216_init(void);
+void AW20216_set_color(int index, uint8_t red, uint8_t green, uint8_t blue);
+void AW20216_set_color_all(uint8_t red, uint8_t green, uint8_t blue);
+void AW20216_update_pwm_buffers(void);
+
+#define CS1_SW1 0x00
+#define CS2_SW1 0x01
+#define CS3_SW1 0x02
+#define CS4_SW1 0x03
+#define CS5_SW1 0x04
+#define CS6_SW1 0x05
+#define CS7_SW1 0x06
+#define CS8_SW1 0x07
+#define CS9_SW1 0x08
+#define CS10_SW1 0x09
+#define CS11_SW1 0x0A
+#define CS12_SW1 0x0B
+#define CS13_SW1 0x0C
+#define CS14_SW1 0x0D
+#define CS15_SW1 0x0E
+#define CS16_SW1 0x0F
+#define CS17_SW1 0x10
+#define CS18_SW1 0x11
+#define CS1_SW2 0x12
+#define CS2_SW2 0x13
+#define CS3_SW2 0x14
+#define CS4_SW2 0x15
+#define CS5_SW2 0x16
+#define CS6_SW2 0x17
+#define CS7_SW2 0x18
+#define CS8_SW2 0x19
+#define CS9_SW2 0x1A
+#define CS10_SW2 0x1B
+#define CS11_SW2 0x1C
+#define CS12_SW2 0x1D
+#define CS13_SW2 0x1E
+#define CS14_SW2 0x1F
+#define CS15_SW2 0x20
+#define CS16_SW2 0x21
+#define CS17_SW2 0x22
+#define CS18_SW2 0x23
+#define CS1_SW3 0x24
+#define CS2_SW3 0x25
+#define CS3_SW3 0x26
+#define CS4_SW3 0x27
+#define CS5_SW3 0x28
+#define CS6_SW3 0x29
+#define CS7_SW3 0x2A
+#define CS8_SW3 0x2B
+#define CS9_SW3 0x2C
+#define CS10_SW3 0x2D
+#define CS11_SW3 0x2E
+#define CS12_SW3 0x2F
+#define CS13_SW3 0x30
+#define CS14_SW3 0x31
+#define CS15_SW3 0x32
+#define CS16_SW3 0x33
+#define CS17_SW3 0x34
+#define CS18_SW3 0x35
+#define CS1_SW4 0x36
+#define CS2_SW4 0x37
+#define CS3_SW4 0x38
+#define CS4_SW4 0x39
+#define CS5_SW4 0x3A
+#define CS6_SW4 0x3B
+#define CS7_SW4 0x3C
+#define CS8_SW4 0x3D
+#define CS9_SW4 0x3E
+#define CS10_SW4 0x3F
+#define CS11_SW4 0x40
+#define CS12_SW4 0x41
+#define CS13_SW4 0x42
+#define CS14_SW4 0x43
+#define CS15_SW4 0x44
+#define CS16_SW4 0x45
+#define CS17_SW4 0x46
+#define CS18_SW4 0x47
+#define CS1_SW5 0x48
+#define CS2_SW5 0x49
+#define CS3_SW5 0x4A
+#define CS4_SW5 0x4B
+#define CS5_SW5 0x4C
+#define CS6_SW5 0x4D
+#define CS7_SW5 0x4E
+#define CS8_SW5 0x4F
+#define CS9_SW5 0x50
+#define CS10_SW5 0x51
+#define CS11_SW5 0x52
+#define CS12_SW5 0x53
+#define CS13_SW5 0x54
+#define CS14_SW5 0x55
+#define CS15_SW5 0x56
+#define CS16_SW5 0x57
+#define CS17_SW5 0x58
+#define CS18_SW5 0x59
+#define CS1_SW6 0x5A
+#define CS2_SW6 0x5B
+#define CS3_SW6 0x5C
+#define CS4_SW6 0x5D
+#define CS5_SW6 0x5E
+#define CS6_SW6 0x5F
+#define CS7_SW6 0x60
+#define CS8_SW6 0x61
+#define CS9_SW6 0x62
+#define CS10_SW6 0x63
+#define CS11_SW6 0x64
+#define CS12_SW6 0x65
+#define CS13_SW6 0x66
+#define CS14_SW6 0x67
+#define CS15_SW6 0x68
+#define CS16_SW6 0x69
+#define CS17_SW6 0x6A
+#define CS18_SW6 0x6B
+#define CS1_SW7 0x6C
+#define CS2_SW7 0x6D
+#define CS3_SW7 0x6E
+#define CS4_SW7 0x6F
+#define CS5_SW7 0x70
+#define CS6_SW7 0x71
+#define CS7_SW7 0x72
+#define CS8_SW7 0x73
+#define CS9_SW7 0x74
+#define CS10_SW7 0x75
+#define CS11_SW7 0x76
+#define CS12_SW7 0x77
+#define CS13_SW7 0x78
+#define CS14_SW7 0x79
+#define CS15_SW7 0x7A
+#define CS16_SW7 0x7B
+#define CS17_SW7 0x7C
+#define CS18_SW7 0x7D
+#define CS1_SW8 0x7E
+#define CS2_SW8 0x7F
+#define CS3_SW8 0x80
+#define CS4_SW8 0x81
+#define CS5_SW8 0x82
+#define CS6_SW8 0x83
+#define CS7_SW8 0x84
+#define CS8_SW8 0x85
+#define CS9_SW8 0x86
+#define CS10_SW8 0x87
+#define CS11_SW8 0x88
+#define CS12_SW8 0x89
+#define CS13_SW8 0x8A
+#define CS14_SW8 0x8B
+#define CS15_SW8 0x8C
+#define CS16_SW8 0x8D
+#define CS17_SW8 0x8E
+#define CS18_SW8 0x8F
+#define CS1_SW9 0x90
+#define CS2_SW9 0x91
+#define CS3_SW9 0x92
+#define CS4_SW9 0x93
+#define CS5_SW9 0x94
+#define CS6_SW9 0x95
+#define CS7_SW9 0x96
+#define CS8_SW9 0x97
+#define CS9_SW9 0x98
+#define CS10_SW9 0x99
+#define CS11_SW9 0x9A
+#define CS12_SW9 0x9B
+#define CS13_SW9 0x9C
+#define CS14_SW9 0x9D
+#define CS15_SW9 0x9E
+#define CS16_SW9 0x9F
+#define CS17_SW9 0xA0
+#define CS18_SW9 0xA1
+#define CS1_SW10 0xA2
+#define CS2_SW10 0xA3
+#define CS3_SW10 0xA4
+#define CS4_SW10 0xA5
+#define CS5_SW10 0xA6
+#define CS6_SW10 0xA7
+#define CS7_SW10 0xA8
+#define CS8_SW10 0xA9
+#define CS9_SW10 0xAA
+#define CS10_SW10 0xAB
+#define CS11_SW10 0xAC
+#define CS12_SW10 0xAD
+#define CS13_SW10 0xAE
+#define CS14_SW10 0xAF
+#define CS15_SW10 0xB0
+#define CS16_SW10 0xB1
+#define CS17_SW10 0xB2
+#define CS18_SW10 0xB3
+#define CS1_SW11 0xB4
+#define CS2_SW11 0xB5
+#define CS3_SW11 0xB6
+#define CS4_SW11 0xB7
+#define CS5_SW11 0xB8
+#define CS6_SW11 0xB9
+#define CS7_SW11 0xBA
+#define CS8_SW11 0xBB
+#define CS9_SW11 0xBC
+#define CS10_SW11 0xBD
+#define CS11_SW11 0xBE
+#define CS12_SW11 0xBF
+#define CS13_SW11 0xC0
+#define CS14_SW11 0xC1
+#define CS15_SW11 0xC2
+#define CS16_SW11 0xC3
+#define CS17_SW11 0xC4
+#define CS18_SW11 0xC5
+#define CS1_SW12 0xC6
+#define CS2_SW12 0xC7
+#define CS3_SW12 0xC8
+#define CS4_SW12 0xC9
+#define CS5_SW12 0xCA
+#define CS6_SW12 0xCB
+#define CS7_SW12 0xCC
+#define CS8_SW12 0xCD
+#define CS9_SW12 0xCE
+#define CS10_SW12 0xCF
+#define CS11_SW12 0xD0
+#define CS12_SW12 0xD1
+#define CS13_SW12 0xD2
+#define CS14_SW12 0xD3
+#define CS15_SW12 0xD4
+#define CS16_SW12 0xD5
+#define CS17_SW12 0xD6
+#define CS18_SW12 0xD7
diff --git a/drivers/chibios/serial.c b/drivers/chibios/serial.c
index 54f7e1321f..f54fbcee4e 100644
--- a/drivers/chibios/serial.c
+++ b/drivers/chibios/serial.c
@@ -74,21 +74,12 @@ static THD_FUNCTION(Thread1, arg) {
}
}
-static SSTD_t *Transaction_table = NULL;
-static uint8_t Transaction_table_size = 0;
-
-void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
-
+void soft_serial_initiator_init(void) {
serial_output();
serial_high();
}
-void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
-
+void soft_serial_target_init(void) {
serial_input();
palEnablePadEvent(PAL_PORT(SOFT_SERIAL_PIN), PAL_PAD(SOFT_SERIAL_PIN), PAL_EVENT_MODE_FALLING_EDGE);
@@ -154,16 +145,14 @@ void interrupt_handler(void *arg) {
uint8_t checksum_computed = 0;
int sstd_index = 0;
-#ifdef SERIAL_USE_MULTI_TRANSACTION
sstd_index = serial_read_byte();
sync_send();
-#endif
- SSTD_t *trans = &Transaction_table[sstd_index];
+ split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
- trans->initiator2target_buffer[i] = serial_read_byte();
+ split_trans_initiator2target_buffer(trans)[i] = serial_read_byte();
sync_send();
- checksum_computed += trans->initiator2target_buffer[i];
+ checksum_computed += split_trans_initiator2target_buffer(trans)[i];
}
checksum_computed ^= 7;
uint8_t checksum_received = serial_read_byte();
@@ -172,12 +161,17 @@ void interrupt_handler(void *arg) {
// wait for the sync to finish sending
serial_delay();
+ // Allow any slave processing to occur
+ if (trans->slave_callback) {
+ trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->target2initiator_buffer_size, split_trans_target2initiator_buffer(trans));
+ }
+
uint8_t checksum = 0;
for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
- serial_write_byte(trans->target2initiator_buffer[i]);
+ serial_write_byte(split_trans_target2initiator_buffer(trans)[i]);
sync_send();
serial_delay_half();
- checksum += trans->target2initiator_buffer[i];
+ checksum += split_trans_target2initiator_buffer(trans)[i];
}
serial_write_byte(checksum ^ 7);
sync_send();
@@ -206,15 +200,10 @@ void interrupt_handler(void *arg) {
// TRANSACTION_NO_RESPONSE
// TRANSACTION_DATA_ERROR
// this code is very time dependent, so we need to disable interrupts
-#ifndef SERIAL_USE_MULTI_TRANSACTION
-int soft_serial_transaction(void) {
- int sstd_index = 0;
-#else
int soft_serial_transaction(int sstd_index) {
-#endif
-
- if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
- SSTD_t *trans = &Transaction_table[sstd_index];
+ if (sstd_index > NUM_TOTAL_TRANSACTIONS) return TRANSACTION_TYPE_ERROR;
+ split_transaction_desc_t *trans = &split_transaction_table[sstd_index];
+ if (!trans->status) return TRANSACTION_TYPE_ERROR; // not registered
// TODO: remove extra delay between transactions
serial_delay();
@@ -244,14 +233,13 @@ int soft_serial_transaction(int sstd_index) {
uint8_t checksum = 0;
// send data to the slave
-#ifdef SERIAL_USE_MULTI_TRANSACTION
serial_write_byte(sstd_index); // first chunk is transaction id
sync_recv();
-#endif
+
for (int i = 0; i < trans->initiator2target_buffer_size; ++i) {
- serial_write_byte(trans->initiator2target_buffer[i]);
+ serial_write_byte(split_trans_initiator2target_buffer(trans)[i]);
sync_recv();
- checksum += trans->initiator2target_buffer[i];
+ checksum += split_trans_initiator2target_buffer(trans)[i];
}
serial_write_byte(checksum ^ 7);
sync_recv();
@@ -262,9 +250,9 @@ int soft_serial_transaction(int sstd_index) {
// receive data from the slave
uint8_t checksum_computed = 0;
for (int i = 0; i < trans->target2initiator_buffer_size; ++i) {
- trans->target2initiator_buffer[i] = serial_read_byte();
+ split_trans_target2initiator_buffer(trans)[i] = serial_read_byte();
sync_recv();
- checksum_computed += trans->target2initiator_buffer[i];
+ checksum_computed += split_trans_target2initiator_buffer(trans)[i];
}
checksum_computed ^= 7;
uint8_t checksum_received = serial_read_byte();
diff --git a/drivers/chibios/serial.h b/drivers/chibios/serial.h
deleted file mode 100644
index 0c1857d52e..0000000000
--- a/drivers/chibios/serial.h
+++ /dev/null
@@ -1,62 +0,0 @@
-#pragma once
-
-#include <stdbool.h>
-
-// /////////////////////////////////////////////////////////////////
-// Need Soft Serial defines in config.h
-// /////////////////////////////////////////////////////////////////
-// ex.
-// #define SOFT_SERIAL_PIN ?? // ?? = D0,D1,D2,D3,E6
-// OPTIONAL: #define SELECT_SOFT_SERIAL_SPEED ? // ? = 1,2,3,4,5
-// // 1: about 137kbps (default)
-// // 2: about 75kbps
-// // 3: about 39kbps
-// // 4: about 26kbps
-// // 5: about 20kbps
-//
-// //// USE simple API (using signle-type transaction function)
-// /* nothing */
-// //// USE flexible API (using multi-type transaction function)
-// #define SERIAL_USE_MULTI_TRANSACTION
-//
-// /////////////////////////////////////////////////////////////////
-
-// Soft Serial Transaction Descriptor
-typedef struct _SSTD_t {
- uint8_t *status;
- uint8_t initiator2target_buffer_size;
- uint8_t *initiator2target_buffer;
- uint8_t target2initiator_buffer_size;
- uint8_t *target2initiator_buffer;
-} SSTD_t;
-#define TID_LIMIT(table) (sizeof(table) / sizeof(SSTD_t))
-
-// initiator is transaction start side
-void soft_serial_initiator_init(SSTD_t *sstd_table, int sstd_table_size);
-// target is interrupt accept side
-void soft_serial_target_init(SSTD_t *sstd_table, int sstd_table_size);
-
-// initiator result
-#define TRANSACTION_END 0
-#define TRANSACTION_NO_RESPONSE 0x1
-#define TRANSACTION_DATA_ERROR 0x2
-#define TRANSACTION_TYPE_ERROR 0x4
-#ifndef SERIAL_USE_MULTI_TRANSACTION
-int soft_serial_transaction(void);
-#else
-int soft_serial_transaction(int sstd_index);
-#endif
-
-// target status
-// *SSTD_t.status has
-// initiator:
-// TRANSACTION_END
-// or TRANSACTION_NO_RESPONSE
-// or TRANSACTION_DATA_ERROR
-// target:
-// TRANSACTION_DATA_ERROR
-// or TRANSACTION_ACCEPTED
-#define TRANSACTION_ACCEPTED 0x8
-#ifdef SERIAL_USE_MULTI_TRANSACTION
-int soft_serial_get_and_clean_status(int sstd_index);
-#endif
diff --git a/drivers/chibios/serial_usart.c b/drivers/chibios/serial_usart.c
index cae29388c3..ea4473791c 100644
--- a/drivers/chibios/serial_usart.c
+++ b/drivers/chibios/serial_usart.c
@@ -16,190 +16,300 @@
#include "serial_usart.h"
-#ifndef USE_GPIOV1
-// The default PAL alternate modes are used to signal that the pins are used for USART
-# ifndef SERIAL_USART_TX_PAL_MODE
-# define SERIAL_USART_TX_PAL_MODE 7
+#if defined(SERIAL_USART_CONFIG)
+static SerialConfig serial_config = SERIAL_USART_CONFIG;
+#else
+static SerialConfig serial_config = {
+ .speed = (SERIAL_USART_SPEED), /* speed - mandatory */
+ .cr1 = (SERIAL_USART_CR1),
+ .cr2 = (SERIAL_USART_CR2),
+# if !defined(SERIAL_USART_FULL_DUPLEX)
+ .cr3 = ((SERIAL_USART_CR3) | USART_CR3_HDSEL) /* activate half-duplex mode */
+# else
+ .cr3 = (SERIAL_USART_CR3)
# endif
+};
#endif
-#ifndef SERIAL_USART_DRIVER
-# define SERIAL_USART_DRIVER SD1
-#endif
-
-#ifdef SOFT_SERIAL_PIN
-# define SERIAL_USART_TX_PIN SOFT_SERIAL_PIN
-#endif
-
-static inline msg_t sdWriteHalfDuplex(SerialDriver* driver, uint8_t* data, uint8_t size) {
- msg_t ret = sdWrite(driver, data, size);
+static SerialDriver* serial_driver = &SERIAL_USART_DRIVER;
- // Half duplex requires us to read back the data we just wrote - just throw it away
- uint8_t dump[size];
- sdRead(driver, dump, size);
+static inline bool react_to_transactions(void);
+static inline bool __attribute__((nonnull)) receive(uint8_t* destination, const size_t size);
+static inline bool __attribute__((nonnull)) send(const uint8_t* source, const size_t size);
+static inline int initiate_transaction(uint8_t sstd_index);
+static inline void usart_clear(void);
- return ret;
+/**
+ * @brief Clear the receive input queue.
+ */
+static inline void usart_clear(void) {
+ osalSysLock();
+ bool volatile queue_not_empty = !iqIsEmptyI(&serial_driver->iqueue);
+ osalSysUnlock();
+
+ while (queue_not_empty) {
+ osalSysLock();
+ /* Hard reset the input queue. */
+ iqResetI(&serial_driver->iqueue);
+ osalSysUnlock();
+ /* Allow pending interrupts to preempt.
+ * Do not merge the lock/unlock blocks into one
+ * or the code will not work properly.
+ * The empty read adds a tiny amount of delay. */
+ (void)queue_not_empty;
+ osalSysLock();
+ queue_not_empty = !iqIsEmptyI(&serial_driver->iqueue);
+ osalSysUnlock();
+ }
}
-#undef sdWrite
-#define sdWrite sdWriteHalfDuplex
-static inline msg_t sdWriteTimeoutHalfDuplex(SerialDriver* driver, uint8_t* data, uint8_t size, uint32_t timeout) {
- msg_t ret = sdWriteTimeout(driver, data, size, timeout);
-
- // Half duplex requires us to read back the data we just wrote - just throw it away
- uint8_t dump[size];
- sdReadTimeout(driver, dump, size, timeout);
+/**
+ * @brief Blocking send of buffer with timeout.
+ *
+ * @return true Send success.
+ * @return false Send failed.
+ */
+static inline bool send(const uint8_t* source, const size_t size) {
+ bool success = (size_t)sdWriteTimeout(serial_driver, source, size, TIME_MS2I(SERIAL_USART_TIMEOUT)) == size;
+
+#if !defined(SERIAL_USART_FULL_DUPLEX)
+ if (success) {
+ /* Half duplex fills the input queue with the data we wrote - just throw it away.
+ Under the right circumstances (e.g. bad cables paired with high baud rates)
+ less bytes can be present in the input queue, therefore a timeout is needed. */
+ uint8_t dump[size];
+ return receive(dump, size);
+ }
+#endif
- return ret;
+ return success;
}
-#undef sdWriteTimeout
-#define sdWriteTimeout sdWriteTimeoutHalfDuplex
-static inline void sdClear(SerialDriver* driver) {
- while (sdGetTimeout(driver, TIME_IMMEDIATE) != MSG_TIMEOUT) {
- // Do nothing with the data
- }
+/**
+ * @brief Blocking receive of size * bytes with timeout.
+ *
+ * @return true Receive success.
+ * @return false Receive failed.
+ */
+static inline bool receive(uint8_t* destination, const size_t size) {
+ bool success = (size_t)sdReadTimeout(serial_driver, destination, size, TIME_MS2I(SERIAL_USART_TIMEOUT)) == size;
+ return success;
}
-static SerialConfig sdcfg = {
- (SERIAL_USART_SPEED), // speed - mandatory
- (SERIAL_USART_CR1), // CR1
- (SERIAL_USART_CR2), // CR2
- (SERIAL_USART_CR3) // CR3
-};
-
-void handle_soft_serial_slave(void);
+#if !defined(SERIAL_USART_FULL_DUPLEX)
-/*
- * This thread runs on the slave and responds to transactions initiated
- * by the master
+/**
+ * @brief Initiate pins for USART peripheral. Half-duplex configuration.
*/
-static THD_WORKING_AREA(waSlaveThread, 2048);
-static THD_FUNCTION(SlaveThread, arg) {
- (void)arg;
- chRegSetThreadName("slave_transport");
+__attribute__((weak)) void usart_init(void) {
+# if defined(MCU_STM32)
+# if defined(USE_GPIOV1)
+ palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_STM32_ALTERNATE_OPENDRAIN);
+# else
+ palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_TX_PAL_MODE) | PAL_STM32_OTYPE_OPENDRAIN);
+# endif
- while (true) {
- handle_soft_serial_slave();
- }
+# if defined(USART_REMAP)
+ USART_REMAP;
+# endif
+# else
+# pragma message "usart_init: MCU Familiy not supported by default, please supply your own init code by implementing usart_init() in your keyboard files."
+# endif
}
-__attribute__((weak)) void usart_init(void) {
-#if defined(USE_GPIOV1)
- palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_STM32_ALTERNATE_OPENDRAIN);
#else
- palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_TX_PAL_MODE) | PAL_STM32_OTYPE_OPENDRAIN);
-#endif
-#if defined(USART_REMAP)
+/**
+ * @brief Initiate pins for USART peripheral. Full-duplex configuration.
+ */
+__attribute__((weak)) void usart_init(void) {
+# if defined(MCU_STM32)
+# if defined(USE_GPIOV1)
+ palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
+ palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_INPUT);
+# else
+ palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_TX_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
+ palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_RX_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
+# endif
+
+# if defined(USART_REMAP)
USART_REMAP;
-#endif
+# endif
+# else
+# pragma message "usart_init: MCU Familiy not supported by default, please supply your own init code by implementing usart_init() in your keyboard files."
+# endif
}
-void usart_master_init(void) {
- usart_init();
+#endif
- sdcfg.cr3 |= USART_CR3_HDSEL;
- sdStart(&SERIAL_USART_DRIVER, &sdcfg);
+/**
+ * @brief Overridable master specific initializations.
+ */
+__attribute__((weak, nonnull)) void usart_master_init(SerialDriver** driver) {
+ (void)driver;
+ usart_init();
}
-void usart_slave_init(void) {
+/**
+ * @brief Overridable slave specific initializations.
+ */
+__attribute__((weak, nonnull)) void usart_slave_init(SerialDriver** driver) {
+ (void)driver;
usart_init();
+}
- sdcfg.cr3 |= USART_CR3_HDSEL;
- sdStart(&SERIAL_USART_DRIVER, &sdcfg);
+/**
+ * @brief This thread runs on the slave and responds to transactions initiated
+ * by the master.
+ */
+static THD_WORKING_AREA(waSlaveThread, 1024);
+static THD_FUNCTION(SlaveThread, arg) {
+ (void)arg;
+ chRegSetThreadName("usart_tx_rx");
- // Start transport thread
- chThdCreateStatic(waSlaveThread, sizeof(waSlaveThread), HIGHPRIO, SlaveThread, NULL);
+ while (true) {
+ if (!react_to_transactions()) {
+ /* Clear the receive queue, to start with a clean slate.
+ * Parts of failed transactions or spurious bytes could still be in it. */
+ usart_clear();
+ }
+ }
}
-static SSTD_t* Transaction_table = NULL;
-static uint8_t Transaction_table_size = 0;
+/**
+ * @brief Slave specific initializations.
+ */
+void soft_serial_target_init(void) {
+ usart_slave_init(&serial_driver);
-void soft_serial_initiator_init(SSTD_t* sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
+ sdStart(serial_driver, &serial_config);
- usart_master_init();
+ /* Start transport thread. */
+ chThdCreateStatic(waSlaveThread, sizeof(waSlaveThread), HIGHPRIO, SlaveThread, NULL);
}
-void soft_serial_target_init(SSTD_t* sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
+/**
+ * @brief React to transactions started by the master.
+ */
+static inline bool react_to_transactions(void) {
+ /* Wait until there is a transaction for us. */
+ uint8_t sstd_index = (uint8_t)sdGet(serial_driver);
- usart_slave_init();
-}
+ /* Sanity check that we are actually responding to a valid transaction. */
+ if (sstd_index >= NUM_TOTAL_TRANSACTIONS) {
+ return false;
+ }
-void handle_soft_serial_slave(void) {
- uint8_t sstd_index = sdGet(&SERIAL_USART_DRIVER); // first chunk is always transaction id
- SSTD_t* trans = &Transaction_table[sstd_index];
+ split_transaction_desc_t* trans = &split_transaction_table[sstd_index];
- // Always write back the sstd_index as part of a basic handshake
+ /* Send back the handshake which is XORed as a simple checksum,
+ to signal that the slave is ready to receive possible transaction buffers */
sstd_index ^= HANDSHAKE_MAGIC;
- sdWrite(&SERIAL_USART_DRIVER, &sstd_index, sizeof(sstd_index));
+ if (!send(&sstd_index, sizeof(sstd_index))) {
+ *trans->status = TRANSACTION_DATA_ERROR;
+ return false;
+ }
+ /* Receive transaction buffer from the master. If this transaction requires it.*/
if (trans->initiator2target_buffer_size) {
- sdRead(&SERIAL_USART_DRIVER, trans->initiator2target_buffer, trans->initiator2target_buffer_size);
+ if (!receive(split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size)) {
+ *trans->status = TRANSACTION_DATA_ERROR;
+ return false;
+ }
}
- if (trans->target2initiator_buffer_size) {
- sdWrite(&SERIAL_USART_DRIVER, trans->target2initiator_buffer, trans->target2initiator_buffer_size);
+ /* Allow any slave processing to occur. */
+ if (trans->slave_callback) {
+ trans->slave_callback(trans->initiator2target_buffer_size, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size, split_trans_target2initiator_buffer(trans));
}
- if (trans->status) {
- *trans->status = TRANSACTION_ACCEPTED;
+ /* Send transaction buffer to the master. If this transaction requires it. */
+ if (trans->target2initiator_buffer_size) {
+ if (!send(split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size)) {
+ *trans->status = TRANSACTION_DATA_ERROR;
+ return false;
+ }
}
+
+ *trans->status = TRANSACTION_ACCEPTED;
+ return true;
}
-/////////
-// start transaction by initiator
-//
-// int soft_serial_transaction(int sstd_index)
-//
-// Returns:
-// TRANSACTION_END
-// TRANSACTION_NO_RESPONSE
-// TRANSACTION_DATA_ERROR
-#ifndef SERIAL_USE_MULTI_TRANSACTION
-int soft_serial_transaction(void) {
- uint8_t sstd_index = 0;
-#else
-int soft_serial_transaction(int index) {
- uint8_t sstd_index = index;
+/**
+ * @brief Master specific initializations.
+ */
+void soft_serial_initiator_init(void) {
+ usart_master_init(&serial_driver);
+
+#if defined(MCU_STM32) && defined(SERIAL_USART_PIN_SWAP)
+ serial_config.cr2 |= USART_CR2_SWAP; // master has swapped TX/RX pins
#endif
- if (sstd_index > Transaction_table_size) return TRANSACTION_TYPE_ERROR;
- SSTD_t* trans = &Transaction_table[sstd_index];
- msg_t res = 0;
+ sdStart(serial_driver, &serial_config);
+}
+
+/**
+ * @brief Start transaction from the master half to the slave half.
+ *
+ * @param index Transaction Table index of the transaction to start.
+ * @return int TRANSACTION_NO_RESPONSE in case of Timeout.
+ * TRANSACTION_TYPE_ERROR in case of invalid transaction index.
+ * TRANSACTION_END in case of success.
+ */
+int soft_serial_transaction(int index) {
+ /* Clear the receive queue, to start with a clean slate.
+ * Parts of failed transactions or spurious bytes could still be in it. */
+ usart_clear();
+ return initiate_transaction((uint8_t)index);
+}
+
+/**
+ * @brief Initiate transaction to slave half.
+ */
+static inline int initiate_transaction(uint8_t sstd_index) {
+ /* Sanity check that we are actually starting a valid transaction. */
+ if (sstd_index >= NUM_TOTAL_TRANSACTIONS) {
+ dprintln("USART: Illegal transaction Id.");
+ return TRANSACTION_TYPE_ERROR;
+ }
+
+ split_transaction_desc_t* trans = &split_transaction_table[sstd_index];
- sdClear(&SERIAL_USART_DRIVER);
+ /* Transaction is not registered. Abort. */
+ if (!trans->status) {
+ dprintln("USART: Transaction not registered.");
+ return TRANSACTION_TYPE_ERROR;
+ }
- // First chunk is always transaction id
- sdWriteTimeout(&SERIAL_USART_DRIVER, &sstd_index, sizeof(sstd_index), TIME_MS2I(SERIAL_USART_TIMEOUT));
+ /* Send transaction table index to the slave, which doubles as basic handshake token. */
+ if (!send(&sstd_index, sizeof(sstd_index))) {
+ dprintln("USART: Send Handshake failed.");
+ return TRANSACTION_TYPE_ERROR;
+ }
uint8_t sstd_index_shake = 0xFF;
- // Which we always read back first so that we can error out correctly
- // - due to the half duplex limitations on return codes, we always have to read *something*
- // - without the read, write only transactions *always* succeed, even during the boot process where the slave is not ready
- res = sdReadTimeout(&SERIAL_USART_DRIVER, &sstd_index_shake, sizeof(sstd_index_shake), TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (res < 0 || (sstd_index_shake != (sstd_index ^ HANDSHAKE_MAGIC))) {
- dprintf("serial::usart_shake NO_RESPONSE\n");
+ /* Which we always read back first so that we can error out correctly.
+ * - due to the half duplex limitations on return codes, we always have to read *something*.
+ * - without the read, write only transactions *always* succeed, even during the boot process where the slave is not ready.
+ */
+ if (!receive(&sstd_index_shake, sizeof(sstd_index_shake)) || (sstd_index_shake != (sstd_index ^ HANDSHAKE_MAGIC))) {
+ dprintln("USART: Handshake failed.");
return TRANSACTION_NO_RESPONSE;
}
+ /* Send transaction buffer to the slave. If this transaction requires it. */
if (trans->initiator2target_buffer_size) {
- res = sdWriteTimeout(&SERIAL_USART_DRIVER, trans->initiator2target_buffer, trans->initiator2target_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (res < 0) {
- dprintf("serial::usart_transmit NO_RESPONSE\n");
+ if (!send(split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size)) {
+ dprintln("USART: Send failed.");
return TRANSACTION_NO_RESPONSE;
}
}
+ /* Receive transaction buffer from the slave. If this transaction requires it. */
if (trans->target2initiator_buffer_size) {
- res = sdReadTimeout(&SERIAL_USART_DRIVER, trans->target2initiator_buffer, trans->target2initiator_buffer_size, TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (res < 0) {
- dprintf("serial::usart_receive NO_RESPONSE\n");
+ if (!receive(split_trans_target2initiator_buffer(trans), trans->target2initiator_buffer_size)) {
+ dprintln("USART: Receive failed.");
return TRANSACTION_NO_RESPONSE;
}
}
diff --git a/drivers/chibios/serial_usart.h b/drivers/chibios/serial_usart.h
index fee7b4d159..c64e15566f 100644
--- a/drivers/chibios/serial_usart.h
+++ b/drivers/chibios/serial_usart.h
@@ -23,19 +23,45 @@
#include <ch.h>
#include <hal.h>
-#ifndef USART_CR1_M0
+#if !defined(SERIAL_USART_DRIVER)
+# define SERIAL_USART_DRIVER SD1
+#endif
+
+#if !defined(USE_GPIOV1)
+/* The default PAL alternate modes are used to signal that the pins are used for USART. */
+# if !defined(SERIAL_USART_TX_PAL_MODE)
+# define SERIAL_USART_TX_PAL_MODE 7
+# endif
+# if !defined(SERIAL_USART_RX_PAL_MODE)
+# define SERIAL_USART_RX_PAL_MODE 7
+# endif
+#endif
+
+#if defined(SOFT_SERIAL_PIN)
+# define SERIAL_USART_TX_PIN SOFT_SERIAL_PIN
+#endif
+
+#if !defined(SERIAL_USART_TX_PIN)
+# define SERIAL_USART_TX_PIN A9
+#endif
+
+#if !defined(SERIAL_USART_RX_PIN)
+# define SERIAL_USART_RX_PIN A10
+#endif
+
+#if !defined(USART_CR1_M0)
# define USART_CR1_M0 USART_CR1_M // some platforms (f1xx) dont have this so
#endif
-#ifndef SERIAL_USART_CR1
+#if !defined(SERIAL_USART_CR1)
# define SERIAL_USART_CR1 (USART_CR1_PCE | USART_CR1_PS | USART_CR1_M0) // parity enable, odd parity, 9 bit length
#endif
-#ifndef SERIAL_USART_CR2
+#if !defined(SERIAL_USART_CR2)
# define SERIAL_USART_CR2 (USART_CR2_STOP_1) // 2 stop bits
#endif
-#ifndef SERIAL_USART_CR3
+#if !defined(SERIAL_USART_CR3)
# define SERIAL_USART_CR3 0
#endif
@@ -61,11 +87,11 @@
} while (0)
#endif
-#ifndef SELECT_SOFT_SERIAL_SPEED
+#if !defined(SELECT_SOFT_SERIAL_SPEED)
# define SELECT_SOFT_SERIAL_SPEED 1
#endif
-#ifdef SERIAL_USART_SPEED
+#if defined(SERIAL_USART_SPEED)
// Allow advanced users to directly set SERIAL_USART_SPEED
#elif SELECT_SOFT_SERIAL_SPEED == 0
# define SERIAL_USART_SPEED 460800
@@ -83,7 +109,7 @@
# error invalid SELECT_SOFT_SERIAL_SPEED value
#endif
-#ifndef SERIAL_USART_TIMEOUT
+#if !defined(SERIAL_USART_TIMEOUT)
# define SERIAL_USART_TIMEOUT 100
#endif
diff --git a/drivers/chibios/serial_usart_duplex.c b/drivers/chibios/serial_usart_duplex.c
deleted file mode 100644
index cc9b889ac2..0000000000
--- a/drivers/chibios/serial_usart_duplex.c
+++ /dev/null
@@ -1,261 +0,0 @@
-/* Copyright 2021 QMK
- *
- * 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 3 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 "serial_usart.h"
-
-#include <stdatomic.h>
-
-#if !defined(USE_GPIOV1)
-// The default PAL alternate modes are used to signal that the pins are used for USART
-# if !defined(SERIAL_USART_TX_PAL_MODE)
-# define SERIAL_USART_TX_PAL_MODE 7
-# endif
-# if !defined(SERIAL_USART_RX_PAL_MODE)
-# define SERIAL_USART_RX_PAL_MODE 7
-# endif
-#endif
-
-#if !defined(SERIAL_USART_DRIVER)
-# define SERIAL_USART_DRIVER UARTD1
-#endif
-
-#if !defined(SERIAL_USART_TX_PIN)
-# define SERIAL_USART_TX_PIN A9
-#endif
-
-#if !defined(SERIAL_USART_RX_PIN)
-# define SERIAL_USART_RX_PIN A10
-#endif
-
-#define SIGNAL_HANDSHAKE_RECEIVED 0x1
-
-void handle_transactions_slave(uint8_t sstd_index);
-static void receive_transaction_handshake(UARTDriver* uartp, uint16_t received_handshake);
-
-/*
- * UART driver configuration structure. We use the blocking DMA enabled API and
- * the rxchar callback to receive handshake tokens but only on the slave halve.
- */
-// clang-format off
-static UARTConfig uart_config = {
- .txend1_cb = NULL,
- .txend2_cb = NULL,
- .rxend_cb = NULL,
- .rxchar_cb = NULL,
- .rxerr_cb = NULL,
- .timeout_cb = NULL,
- .speed = (SERIAL_USART_SPEED),
- .cr1 = (SERIAL_USART_CR1),
- .cr2 = (SERIAL_USART_CR2),
- .cr3 = (SERIAL_USART_CR3)
-};
-// clang-format on
-
-static SSTD_t* Transaction_table = NULL;
-static uint8_t Transaction_table_size = 0;
-static atomic_uint_least8_t handshake = 0xFF;
-static thread_reference_t tp_target = NULL;
-
-/*
- * This callback is invoked when a character is received but the application
- * was not ready to receive it, the character is passed as parameter.
- * Receive transaction table index from initiator, which doubles as basic handshake token. */
-static void receive_transaction_handshake(UARTDriver* uartp, uint16_t received_handshake) {
- /* Check if received handshake is not a valid transaction id.
- * Please note that we can still catch a seemingly valid handshake
- * i.e. a byte from a ongoing transfer which is in the allowed range.
- * So this check mainly prevents any obviously wrong handshakes and
- * subsequent wakeups of the receiving thread, which is a costly operation. */
- if (received_handshake > Transaction_table_size) {
- return;
- }
-
- handshake = (uint8_t)received_handshake;
- chSysLockFromISR();
- /* Wakeup receiving thread to start a transaction. */
- chEvtSignalI(tp_target, (eventmask_t)SIGNAL_HANDSHAKE_RECEIVED);
- chSysUnlockFromISR();
-}
-
-__attribute__((weak)) void usart_init(void) {
-#if defined(USE_GPIOV1)
- palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
- palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_INPUT);
-#else
- palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_TX_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
- palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_RX_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
-#endif
-}
-
-/*
- * This thread runs on the slave half and reacts to transactions initiated from the master.
- */
-static THD_WORKING_AREA(waSlaveThread, 1024);
-static THD_FUNCTION(SlaveThread, arg) {
- (void)arg;
- chRegSetThreadName("slave_usart_tx_rx");
-
- while (true) {
- /* We sleep as long as there is no handshake waiting for us. */
- chEvtWaitAny((eventmask_t)SIGNAL_HANDSHAKE_RECEIVED);
- handle_transactions_slave(handshake);
- }
-}
-
-void soft_serial_target_init(SSTD_t* const sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
- usart_init();
-
-#if defined(USART_REMAP)
- USART_REMAP;
-#endif
-
- tp_target = chThdCreateStatic(waSlaveThread, sizeof(waSlaveThread), HIGHPRIO, SlaveThread, NULL);
-
- // Start receiving handshake tokens on slave halve
- uart_config.rxchar_cb = receive_transaction_handshake;
- uartStart(&SERIAL_USART_DRIVER, &uart_config);
-}
-
-/**
- * @brief React to transactions started by the master.
- * This version uses duplex send and receive usart pheriphals and DMA backed transfers.
- */
-void inline handle_transactions_slave(uint8_t sstd_index) {
- size_t buffer_size = 0;
- msg_t msg = 0;
- SSTD_t* trans = &Transaction_table[sstd_index];
-
- /* Send back the handshake which is XORed as a simple checksum,
- to signal that the slave is ready to receive possible transaction buffers */
- sstd_index ^= HANDSHAKE_MAGIC;
- buffer_size = (size_t)sizeof(sstd_index);
- msg = uartSendTimeout(&SERIAL_USART_DRIVER, &buffer_size, &sstd_index, TIME_MS2I(SERIAL_USART_TIMEOUT));
-
- if (msg != MSG_OK) {
- if (trans->status) {
- *trans->status = TRANSACTION_NO_RESPONSE;
- }
- return;
- }
-
- /* Receive transaction buffer from the master. If this transaction requires it.*/
- buffer_size = (size_t)trans->initiator2target_buffer_size;
- if (buffer_size) {
- msg = uartReceiveTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->initiator2target_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (msg != MSG_OK) {
- if (trans->status) {
- *trans->status = TRANSACTION_NO_RESPONSE;
- }
- return;
- }
- }
-
- /* Send transaction buffer to the master. If this transaction requires it. */
- buffer_size = (size_t)trans->target2initiator_buffer_size;
- if (buffer_size) {
- msg = uartSendFullTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->target2initiator_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (msg != MSG_OK) {
- if (trans->status) {
- *trans->status = TRANSACTION_NO_RESPONSE;
- }
- return;
- }
- }
-
- if (trans->status) {
- *trans->status = TRANSACTION_ACCEPTED;
- }
-}
-
-void soft_serial_initiator_init(SSTD_t* const sstd_table, int sstd_table_size) {
- Transaction_table = sstd_table;
- Transaction_table_size = (uint8_t)sstd_table_size;
- usart_init();
-
-#if defined(SERIAL_USART_PIN_SWAP)
- uart_config.cr2 |= USART_CR2_SWAP; // master has swapped TX/RX pins
-#endif
-
-#if defined(USART_REMAP)
- USART_REMAP;
-#endif
-
- uartStart(&SERIAL_USART_DRIVER, &uart_config);
-}
-
-/**
- * @brief Start transaction from the master to the slave.
- * This version uses duplex send and receive usart pheriphals and DMA backed transfers.
- *
- * @param index Transaction Table index of the transaction to start.
- * @return int TRANSACTION_NO_RESPONSE in case of Timeout.
- * TRANSACTION_TYPE_ERROR in case of invalid transaction index.
- * TRANSACTION_END in case of success.
- */
-#if !defined(SERIAL_USE_MULTI_TRANSACTION)
-int soft_serial_transaction(void) {
- uint8_t sstd_index = 0;
-#else
-int soft_serial_transaction(int index) {
- uint8_t sstd_index = index;
-#endif
-
- if (sstd_index > Transaction_table_size) {
- return TRANSACTION_TYPE_ERROR;
- }
-
- SSTD_t* const trans = &Transaction_table[sstd_index];
- msg_t msg = 0;
- size_t buffer_size = (size_t)sizeof(sstd_index);
-
- /* Send transaction table index to the slave, which doubles as basic handshake token. */
- uartSendFullTimeout(&SERIAL_USART_DRIVER, &buffer_size, &sstd_index, TIME_MS2I(SERIAL_USART_TIMEOUT));
-
- uint8_t sstd_index_shake = 0xFF;
- buffer_size = (size_t)sizeof(sstd_index_shake);
-
- /* Receive the handshake token from the slave. The token was XORed by the slave as a simple checksum.
- If the tokens match, the master will start to send and receive possible transaction buffers. */
- msg = uartReceiveTimeout(&SERIAL_USART_DRIVER, &buffer_size, &sstd_index_shake, TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (msg != MSG_OK || (sstd_index_shake != (sstd_index ^ HANDSHAKE_MAGIC))) {
- dprintln("USART: Handshake Failed");
- return TRANSACTION_NO_RESPONSE;
- }
-
- /* Send transaction buffer to the slave. If this transaction requires it. */
- buffer_size = (size_t)trans->initiator2target_buffer_size;
- if (buffer_size) {
- msg = uartSendFullTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->initiator2target_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (msg != MSG_OK) {
- dprintln("USART: Send Failed");
- return TRANSACTION_NO_RESPONSE;
- }
- }
-
- /* Receive transaction buffer from the slave. If this transaction requires it. */
- buffer_size = (size_t)trans->target2initiator_buffer_size;
- if (buffer_size) {
- msg = uartReceiveTimeout(&SERIAL_USART_DRIVER, &buffer_size, trans->target2initiator_buffer, TIME_MS2I(SERIAL_USART_TIMEOUT));
- if (msg != MSG_OK) {
- dprintln("USART: Receive Failed");
- return TRANSACTION_NO_RESPONSE;
- }
- }
-
- return TRANSACTION_END;
-}
diff --git a/drivers/chibios/spi_master.c b/drivers/chibios/spi_master.c
index 4852a6eba4..28ddcbb2ba 100644
--- a/drivers/chibios/spi_master.c
+++ b/drivers/chibios/spi_master.c
@@ -18,8 +18,13 @@
#include "timer.h"
-static pin_t currentSlavePin = NO_PIN;
-static SPIConfig spiConfig = {false, NULL, 0, 0, 0, 0};
+static pin_t currentSlavePin = NO_PIN;
+
+#if defined(K20x) || defined(KL2x)
+static SPIConfig spiConfig = {NULL, 0, 0, 0};
+#else
+static SPIConfig spiConfig = {false, NULL, 0, 0, 0, 0};
+#endif
__attribute__((weak)) void spi_init(void) {
static bool is_initialised = false;
@@ -27,15 +32,15 @@ __attribute__((weak)) void spi_init(void) {
is_initialised = true;
// Try releasing special pins for a short time
- palSetPadMode(PAL_PORT(SPI_SCK_PIN), PAL_PAD(SPI_SCK_PIN), PAL_MODE_INPUT);
- palSetPadMode(PAL_PORT(SPI_MOSI_PIN), PAL_PAD(SPI_MOSI_PIN), PAL_MODE_INPUT);
- palSetPadMode(PAL_PORT(SPI_MISO_PIN), PAL_PAD(SPI_MISO_PIN), PAL_MODE_INPUT);
+ setPinInput(SPI_SCK_PIN);
+ setPinInput(SPI_MOSI_PIN);
+ setPinInput(SPI_MISO_PIN);
chThdSleepMilliseconds(10);
#if defined(USE_GPIOV1)
- palSetPadMode(PAL_PORT(SPI_SCK_PIN), PAL_PAD(SPI_SCK_PIN), PAL_MODE_STM32_ALTERNATE_PUSHPULL);
- palSetPadMode(PAL_PORT(SPI_MOSI_PIN), PAL_PAD(SPI_MOSI_PIN), PAL_MODE_STM32_ALTERNATE_PUSHPULL);
- palSetPadMode(PAL_PORT(SPI_MISO_PIN), PAL_PAD(SPI_MISO_PIN), PAL_MODE_STM32_ALTERNATE_PUSHPULL);
+ palSetPadMode(PAL_PORT(SPI_SCK_PIN), PAL_PAD(SPI_SCK_PIN), SPI_SCK_PAL_MODE);
+ palSetPadMode(PAL_PORT(SPI_MOSI_PIN), PAL_PAD(SPI_MOSI_PIN), SPI_MOSI_PAL_MODE);
+ palSetPadMode(PAL_PORT(SPI_MISO_PIN), PAL_PAD(SPI_MISO_PIN), SPI_MISO_PAL_MODE);
#else
palSetPadMode(PAL_PORT(SPI_SCK_PIN), PAL_PAD(SPI_SCK_PIN), PAL_MODE_ALTERNATE(SPI_SCK_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(PAL_PORT(SPI_MOSI_PIN), PAL_PAD(SPI_MOSI_PIN), PAL_MODE_ALTERNATE(SPI_MOSI_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL | PAL_STM32_OSPEED_HIGHEST);
@@ -58,6 +63,54 @@ bool spi_start(pin_t slavePin, bool lsbFirst, uint8_t mode, uint16_t divisor) {
return false;
}
+#if defined(K20x) || defined(KL2x)
+ spiConfig.tar0 = SPIx_CTARn_FMSZ(7) | SPIx_CTARn_ASC(1);
+
+ if (lsbFirst) {
+ spiConfig.tar0 |= SPIx_CTARn_LSBFE;
+ }
+
+ switch (mode) {
+ case 0:
+ break;
+ case 1:
+ spiConfig.tar0 |= SPIx_CTARn_CPHA;
+ break;
+ case 2:
+ spiConfig.tar0 |= SPIx_CTARn_CPOL;
+ break;
+ case 3:
+ spiConfig.tar0 |= SPIx_CTARn_CPHA | SPIx_CTARn_CPOL;
+ break;
+ }
+
+ switch (roundedDivisor) {
+ case 2:
+ spiConfig.tar0 |= SPIx_CTARn_BR(0);
+ break;
+ case 4:
+ spiConfig.tar0 |= SPIx_CTARn_BR(1);
+ break;
+ case 8:
+ spiConfig.tar0 |= SPIx_CTARn_BR(3);
+ break;
+ case 16:
+ spiConfig.tar0 |= SPIx_CTARn_BR(4);
+ break;
+ case 32:
+ spiConfig.tar0 |= SPIx_CTARn_BR(5);
+ break;
+ case 64:
+ spiConfig.tar0 |= SPIx_CTARn_BR(6);
+ break;
+ case 128:
+ spiConfig.tar0 |= SPIx_CTARn_BR(7);
+ break;
+ case 256:
+ spiConfig.tar0 |= SPIx_CTARn_BR(8);
+ break;
+ }
+#else
spiConfig.cr1 = 0;
if (lsbFirst) {
@@ -103,6 +156,7 @@ bool spi_start(pin_t slavePin, bool lsbFirst, uint8_t mode, uint16_t divisor) {
spiConfig.cr1 |= SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0;
break;
}
+#endif
currentSlavePin = slavePin;
spiConfig.ssport = PAL_PORT(slavePin);
diff --git a/drivers/chibios/spi_master.h b/drivers/chibios/spi_master.h
index e93580e319..b5a6ef1437 100644
--- a/drivers/chibios/spi_master.h
+++ b/drivers/chibios/spi_master.h
@@ -21,6 +21,7 @@
#include <stdbool.h>
#include "gpio.h"
+#include "chibios_config.h"
#ifndef SPI_DRIVER
# define SPI_DRIVER SPID2
@@ -31,7 +32,11 @@
#endif
#ifndef SPI_SCK_PAL_MODE
-# define SPI_SCK_PAL_MODE 5
+# if defined(USE_GPIOV1)
+# define SPI_SCK_PAL_MODE PAL_MODE_STM32_ALTERNATE_PUSHPULL
+# else
+# define SPI_SCK_PAL_MODE 5
+# endif
#endif
#ifndef SPI_MOSI_PIN
@@ -39,7 +44,11 @@
#endif
#ifndef SPI_MOSI_PAL_MODE
-# define SPI_MOSI_PAL_MODE 5
+# if defined(USE_GPIOV1)
+# define SPI_MOSI_PAL_MODE PAL_MODE_STM32_ALTERNATE_PUSHPULL
+# else
+# define SPI_MOSI_PAL_MODE 5
+# endif
#endif
#ifndef SPI_MISO_PIN
@@ -47,7 +56,11 @@
#endif
#ifndef SPI_MISO_PAL_MODE
-# define SPI_MISO_PAL_MODE 5
+# if defined(USE_GPIOV1)
+# define SPI_MISO_PAL_MODE PAL_MODE_STM32_ALTERNATE_PUSHPULL
+# else
+# define SPI_MISO_PAL_MODE 5
+# endif
#endif
typedef int16_t spi_status_t;
diff --git a/drivers/eeprom/eeprom_i2c.c b/drivers/eeprom/eeprom_i2c.c
index 4210f06f9f..8e80ff544f 100644
--- a/drivers/eeprom/eeprom_i2c.c
+++ b/drivers/eeprom/eeprom_i2c.c
@@ -16,6 +16,9 @@
#include <stdint.h>
#include <string.h>
+#if defined(EXTERNAL_EEPROM_WP_PIN)
+# include "gpio.h"
+#endif
/*
Note that the implementations of eeprom_XXXX_YYYY on AVR are normally
@@ -50,7 +53,14 @@ static inline void fill_target_address(uint8_t *buffer, const void *addr) {
}
}
-void eeprom_driver_init(void) { i2c_init(); }
+void eeprom_driver_init(void) {
+ i2c_init();
+#if defined(EXTERNAL_EEPROM_WP_PIN)
+ /* We are setting the WP pin to high in a way that requires at least two bit-flips to change back to 0 */
+ writePin(EXTERNAL_EEPROM_WP_PIN, 1);
+ setPinInputHigh(EXTERNAL_EEPROM_WP_PIN);
+#endif
+}
void eeprom_driver_erase(void) {
#if defined(CONSOLE_ENABLE) && defined(DEBUG_EEPROM_OUTPUT)
@@ -89,6 +99,11 @@ void eeprom_write_block(const void *buf, void *addr, size_t len) {
uint8_t * read_buf = (uint8_t *)buf;
uintptr_t target_addr = (uintptr_t)addr;
+#if defined(EXTERNAL_EEPROM_WP_PIN)
+ setPinOutput(EXTERNAL_EEPROM_WP_PIN);
+ writePin(EXTERNAL_EEPROM_WP_PIN, 0);
+#endif
+
while (len > 0) {
uintptr_t page_offset = target_addr % EXTERNAL_EEPROM_PAGE_SIZE;
int write_length = EXTERNAL_EEPROM_PAGE_SIZE - page_offset;
@@ -116,4 +131,10 @@ void eeprom_write_block(const void *buf, void *addr, size_t len) {
target_addr += write_length;
len -= write_length;
}
+
+#if defined(EXTERNAL_EEPROM_WP_PIN)
+ /* We are setting the WP pin to high in a way that requires at least two bit-flips to change back to 0 */
+ writePin(EXTERNAL_EEPROM_WP_PIN, 1);
+ setPinInputHigh(EXTERNAL_EEPROM_WP_PIN);
+#endif
}
diff --git a/drivers/haptic/haptic.c b/drivers/haptic/haptic.c
index de3f400527..3fab1be1ae 100644
--- a/drivers/haptic/haptic.c
+++ b/drivers/haptic/haptic.c
@@ -291,6 +291,73 @@ void haptic_play(void) {
#endif
}
+__attribute__((weak)) bool get_haptic_enabled_key(uint16_t keycode, keyrecord_t *record) {
+ switch(keycode) {
+# ifdef NO_HAPTIC_MOD
+ case QK_MOD_TAP ... QK_MOD_TAP_MAX:
+ if (record->tap.count == 0) return false;
+ break;
+ case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
+ if (record->tap.count != TAPPING_TOGGLE) return false;
+ break;
+ case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
+ if (record->tap.count == 0) return false;
+ break;
+ case KC_LCTRL ... KC_RGUI:
+ case QK_MOMENTARY ... QK_MOMENTARY_MAX:
+# endif
+# ifdef NO_HAPTIC_FN
+ case KC_FN0 ... KC_FN31:
+# endif
+# ifdef NO_HAPTIC_ALPHA
+ case KC_A ... KC_Z:
+# endif
+# ifdef NO_HAPTIC_PUNCTUATION
+ case KC_ENTER:
+ case KC_ESCAPE:
+ case KC_BSPACE:
+ case KC_SPACE:
+ case KC_MINUS:
+ case KC_EQUAL:
+ case KC_LBRACKET:
+ case KC_RBRACKET:
+ case KC_BSLASH:
+ case KC_NONUS_HASH:
+ case KC_SCOLON:
+ case KC_QUOTE:
+ case KC_GRAVE:
+ case KC_COMMA:
+ case KC_SLASH:
+ case KC_DOT:
+ case KC_NONUS_BSLASH:
+# endif
+# ifdef NO_HAPTIC_LOCKKEYS
+ case KC_CAPSLOCK:
+ case KC_SCROLLLOCK:
+ case KC_NUMLOCK:
+# endif
+# ifdef NO_HAPTIC_NAV
+ case KC_PSCREEN:
+ case KC_PAUSE:
+ case KC_INSERT:
+ case KC_DELETE:
+ case KC_PGDOWN:
+ case KC_PGUP:
+ case KC_LEFT:
+ case KC_UP:
+ case KC_RIGHT:
+ case KC_DOWN:
+ case KC_END:
+ case KC_HOME:
+# endif
+# ifdef NO_HAPTIC_NUMERIC
+ case KC_1 ... KC_0:
+# endif
+ return false;
+ }
+ return true;
+}
+
bool process_haptic(uint16_t keycode, keyrecord_t *record) {
if (keycode == HPT_ON && record->event.pressed) {
haptic_enable();
@@ -335,12 +402,12 @@ bool process_haptic(uint16_t keycode, keyrecord_t *record) {
if (haptic_config.enable) {
if (record->event.pressed) {
// keypress
- if (haptic_config.feedback < 2) {
+ if (haptic_config.feedback < 2 && get_haptic_enabled_key(keycode, record)) {
haptic_play();
}
} else {
// keyrelease
- if (haptic_config.feedback > 0) {
+ if (haptic_config.feedback > 0 && get_haptic_enabled_key(keycode, record)) {
haptic_play();
}
}
diff --git a/drivers/issi/is31fl3737.c b/drivers/issi/is31fl3737.c
index 8647c93cc1..30906b4840 100644
--- a/drivers/issi/is31fl3737.c
+++ b/drivers/issi/is31fl3737.c
@@ -19,6 +19,7 @@
#include "is31fl3737.h"
#include "i2c_master.h"
#include "wait.h"
+#include "progmem.h"
// This is a 7-bit address, that gets left-shifted and bit 0
// set to 0 for write, 1 for read (as per I2C protocol)
@@ -65,11 +66,12 @@ uint8_t g_twi_transfer_buffer[20];
// We could optimize this and take out the unused registers from these
// buffers and the transfers in IS31FL3737_write_pwm_buffer() but it's
// probably not worth the extra complexity.
+
uint8_t g_pwm_buffer[DRIVER_COUNT][192];
-bool g_pwm_buffer_update_required = false;
+bool g_pwm_buffer_update_required[DRIVER_COUNT] = {false};
-uint8_t g_led_control_registers[DRIVER_COUNT][24] = {{0}};
-bool g_led_control_registers_update_required = false;
+uint8_t g_led_control_registers[DRIVER_COUNT][24] = {0};
+bool g_led_control_registers_update_required[DRIVER_COUNT] = {false};
void IS31FL3737_write_register(uint8_t addr, uint8_t reg, uint8_t data) {
g_twi_transfer_buffer[0] = reg;
@@ -153,12 +155,14 @@ void IS31FL3737_init(uint8_t addr) {
void IS31FL3737_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) {
if (index >= 0 && index < DRIVER_LED_TOTAL) {
- is31_led led = g_is31_leds[index];
-
- g_pwm_buffer[led.driver][led.r] = red;
- g_pwm_buffer[led.driver][led.g] = green;
- g_pwm_buffer[led.driver][led.b] = blue;
- g_pwm_buffer_update_required = true;
+ // copy the led config from progmem to SRAM
+ is31_led led;
+ memcpy_P(&led, (&g_is31_leds[index]), sizeof(led));
+
+ g_pwm_buffer[led.driver][led.r] = red;
+ g_pwm_buffer[led.driver][led.g] = green;
+ g_pwm_buffer[led.driver][led.b] = blue;
+ g_pwm_buffer_update_required[led.driver] = true;
}
}
@@ -169,7 +173,9 @@ void IS31FL3737_set_color_all(uint8_t red, uint8_t green, uint8_t blue) {
}
void IS31FL3737_set_led_control_register(uint8_t index, bool red, bool green, bool blue) {
- is31_led led = g_is31_leds[index];
+ // copy the led config from progmem to SRAM
+ is31_led led;
+ memcpy_P(&led, (&g_is31_leds[index]), sizeof(led));
uint8_t control_register_r = led.r / 8;
uint8_t control_register_g = led.g / 8;
@@ -194,30 +200,28 @@ void IS31FL3737_set_led_control_register(uint8_t index, bool red, bool green, bo
g_led_control_registers[led.driver][control_register_b] &= ~(1 << bit_b);
}
- g_led_control_registers_update_required = true;
+ g_led_control_registers_update_required[led.driver] = true;
}
-void IS31FL3737_update_pwm_buffers(uint8_t addr1, uint8_t addr2) {
- if (g_pwm_buffer_update_required) {
+void IS31FL3737_update_pwm_buffers(uint8_t addr, uint8_t index) {
+ if (g_pwm_buffer_update_required[index]) {
// Firstly we need to unlock the command register and select PG1
- IS31FL3737_write_register(addr1, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
- IS31FL3737_write_register(addr1, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM);
+ IS31FL3737_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
+ IS31FL3737_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM);
- IS31FL3737_write_pwm_buffer(addr1, g_pwm_buffer[0]);
- // IS31FL3737_write_pwm_buffer(addr2, g_pwm_buffer[1]);
+ IS31FL3737_write_pwm_buffer(addr, g_pwm_buffer[index]);
}
- g_pwm_buffer_update_required = false;
+ g_pwm_buffer_update_required[index] = false;
}
-void IS31FL3737_update_led_control_registers(uint8_t addr1, uint8_t addr2) {
- if (g_led_control_registers_update_required) {
+void IS31FL3737_update_led_control_registers(uint8_t addr, uint8_t index) {
+ if (g_led_control_registers_update_required[index]) {
// Firstly we need to unlock the command register and select PG0
- IS31FL3737_write_register(addr1, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
- IS31FL3737_write_register(addr1, ISSI_COMMANDREGISTER, ISSI_PAGE_LEDCONTROL);
+ IS31FL3737_write_register(addr, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5);
+ IS31FL3737_write_register(addr, ISSI_COMMANDREGISTER, ISSI_PAGE_LEDCONTROL);
for (int i = 0; i < 24; i++) {
- IS31FL3737_write_register(addr1, i, g_led_control_registers[0][i]);
- // IS31FL3737_write_register(addr2, i, g_led_control_registers[1][i]);
+ IS31FL3737_write_register(addr, i, g_led_control_registers[index][i]);
}
- g_led_control_registers_update_required = false;
}
+ g_led_control_registers_update_required[index] = false;
}
diff --git a/drivers/lcd/st7565.c b/drivers/lcd/st7565.c
new file mode 100644
index 0000000000..49b13c00f1
--- /dev/null
+++ b/drivers/lcd/st7565.c
@@ -0,0 +1,496 @@
+/*
+Copyright 2021
+
+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 "st7565.h"
+
+#include <string.h>
+
+#include "keyboard.h"
+#include "progmem.h"
+#include "timer.h"
+#include "wait.h"
+
+#include ST7565_FONT_H
+
+// Fundamental Commands
+#define CONTRAST 0x81
+#define DISPLAY_ALL_ON 0xA5
+#define DISPLAY_ALL_ON_RESUME 0xA4
+#define NORMAL_DISPLAY 0xA6
+#define INVERT_DISPLAY 0xA7
+#define DISPLAY_ON 0xAF
+#define DISPLAY_OFF 0xAE
+#define NOP 0xE3
+
+// Addressing Setting Commands
+#define PAM_SETCOLUMN_LSB 0x00
+#define PAM_SETCOLUMN_MSB 0x10
+#define PAM_PAGE_ADDR 0xB0 // 0xb0 -- 0xb7
+
+// Hardware Configuration Commands
+#define DISPLAY_START_LINE 0x40
+#define SEGMENT_REMAP 0xA0
+#define SEGMENT_REMAP_INV 0xA1
+#define COM_SCAN_INC 0xC0
+#define COM_SCAN_DEC 0xC8
+#define LCD_BIAS_7 0xA3
+#define LCD_BIAS_9 0xA2
+#define RESISTOR_RATIO 0x20
+#define POWER_CONTROL 0x28
+
+// Misc defines
+#ifndef ST7565_BLOCK_COUNT
+# define ST7565_BLOCK_COUNT (sizeof(ST7565_BLOCK_TYPE) * 8)
+#endif
+#ifndef ST7565_BLOCK_SIZE
+# define ST7565_BLOCK_SIZE (ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT)
+#endif
+
+#define ST7565_ALL_BLOCKS_MASK (((((ST7565_BLOCK_TYPE)1 << (ST7565_BLOCK_COUNT - 1)) - 1) << 1) | 1)
+
+#define HAS_FLAGS(bits, flags) ((bits & flags) == flags)
+
+// Display buffer's is the same as the display memory layout
+// this is so we don't end up with rounding errors with
+// parts of the display unusable or don't get cleared correctly
+// and also allows for drawing & inverting
+uint8_t st7565_buffer[ST7565_MATRIX_SIZE];
+uint8_t * st7565_cursor;
+ST7565_BLOCK_TYPE st7565_dirty = 0;
+bool st7565_initialized = false;
+bool st7565_active = false;
+bool st7565_inverted = false;
+display_rotation_t st7565_rotation = DISPLAY_ROTATION_0;
+#if ST7565_TIMEOUT > 0
+uint32_t st7565_timeout;
+#endif
+#if ST7565_UPDATE_INTERVAL > 0
+uint16_t st7565_update_timeout;
+#endif
+
+// Flips the rendering bits for a character at the current cursor position
+static void InvertCharacter(uint8_t *cursor) {
+ const uint8_t *end = cursor + ST7565_FONT_WIDTH;
+ while (cursor < end) {
+ *cursor = ~(*cursor);
+ cursor++;
+ }
+}
+
+bool st7565_init(display_rotation_t rotation) {
+ setPinOutput(ST7565_A0_PIN);
+ writePinHigh(ST7565_A0_PIN);
+ setPinOutput(ST7565_RST_PIN);
+ writePinHigh(ST7565_RST_PIN);
+
+ st7565_rotation = st7565_init_user(rotation);
+
+ spi_init();
+ spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
+
+ st7565_reset();
+
+ st7565_send_cmd(LCD_BIAS_7);
+ if (!HAS_FLAGS(st7565_rotation, DISPLAY_ROTATION_180)) {
+ st7565_send_cmd(SEGMENT_REMAP);
+ st7565_send_cmd(COM_SCAN_DEC);
+ } else {
+ st7565_send_cmd(SEGMENT_REMAP_INV);
+ st7565_send_cmd(COM_SCAN_INC);
+ }
+ st7565_send_cmd(DISPLAY_START_LINE | 0x00);
+ st7565_send_cmd(CONTRAST);
+ st7565_send_cmd(ST7565_CONTRAST);
+ st7565_send_cmd(RESISTOR_RATIO | 0x01);
+ st7565_send_cmd(POWER_CONTROL | 0x04);
+ wait_ms(50);
+ st7565_send_cmd(POWER_CONTROL | 0x06);
+ wait_ms(50);
+ st7565_send_cmd(POWER_CONTROL | 0x07);
+ wait_ms(10);
+ st7565_send_cmd(DISPLAY_ON);
+ st7565_send_cmd(DISPLAY_ALL_ON_RESUME);
+ st7565_send_cmd(NORMAL_DISPLAY);
+
+ spi_stop();
+
+#if ST7565_TIMEOUT > 0
+ st7565_timeout = timer_read32() + ST7565_TIMEOUT;
+#endif
+
+ st7565_clear();
+ st7565_initialized = true;
+ st7565_active = true;
+ return true;
+}
+
+__attribute__((weak)) display_rotation_t st7565_init_user(display_rotation_t rotation) { return rotation; }
+
+void st7565_clear(void) {
+ memset(st7565_buffer, 0, sizeof(st7565_buffer));
+ st7565_cursor = &st7565_buffer[0];
+ st7565_dirty = ST7565_ALL_BLOCKS_MASK;
+}
+
+uint8_t crot(uint8_t a, int8_t n) {
+ const uint8_t mask = 0x7;
+ n &= mask;
+ return a << n | a >> (-n & mask);
+}
+
+void st7565_render(void) {
+ if (!st7565_initialized) {
+ return;
+ }
+
+ // Do we have work to do?
+ st7565_dirty &= ST7565_ALL_BLOCKS_MASK;
+ if (!st7565_dirty) {
+ return;
+ }
+
+ // Find first dirty block
+ uint8_t update_start = 0;
+ while (!(st7565_dirty & ((ST7565_BLOCK_TYPE)1 << update_start))) {
+ ++update_start;
+ }
+
+ // Calculate commands to set memory addressing bounds.
+ uint8_t start_page = ST7565_BLOCK_SIZE * update_start / ST7565_DISPLAY_WIDTH;
+ uint8_t start_column = ST7565_BLOCK_SIZE * update_start % ST7565_DISPLAY_WIDTH;
+ // IC has 132 segment drivers, for panels with less width we need to offset the starting column
+ if (HAS_FLAGS(st7565_rotation, DISPLAY_ROTATION_180)) {
+ start_column += (132 - ST7565_DISPLAY_WIDTH);
+ }
+
+ spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
+
+ st7565_send_cmd(PAM_PAGE_ADDR | start_page);
+ st7565_send_cmd(PAM_SETCOLUMN_LSB | ((ST7565_COLUMN_OFFSET + start_column) & 0x0f));
+ st7565_send_cmd(PAM_SETCOLUMN_MSB | ((ST7565_COLUMN_OFFSET + start_column) >> 4 & 0x0f));
+
+ st7565_send_data(&st7565_buffer[ST7565_BLOCK_SIZE * update_start], ST7565_BLOCK_SIZE);
+
+ // Turn on display if it is off
+ st7565_on();
+
+ // Clear dirty flag
+ st7565_dirty &= ~((ST7565_BLOCK_TYPE)1 << update_start);
+}
+
+void st7565_set_cursor(uint8_t col, uint8_t line) {
+ uint16_t index = line * ST7565_DISPLAY_WIDTH + col * ST7565_FONT_WIDTH;
+
+ // Out of bounds?
+ if (index >= ST7565_MATRIX_SIZE) {
+ index = 0;
+ }
+
+ st7565_cursor = &st7565_buffer[index];
+}
+
+void st7565_advance_page(bool clearPageRemainder) {
+ uint16_t index = st7565_cursor - &st7565_buffer[0];
+ uint8_t remaining = ST7565_DISPLAY_WIDTH - (index % ST7565_DISPLAY_WIDTH);
+
+ if (clearPageRemainder) {
+ // Remaining Char count
+ remaining = remaining / ST7565_FONT_WIDTH;
+
+ // Write empty character until next line
+ while (remaining--) st7565_write_char(' ', false);
+ } else {
+ // Next page index out of bounds?
+ if (index + remaining >= ST7565_MATRIX_SIZE) {
+ index = 0;
+ remaining = 0;
+ }
+
+ st7565_cursor = &st7565_buffer[index + remaining];
+ }
+}
+
+void st7565_advance_char(void) {
+ uint16_t nextIndex = st7565_cursor - &st7565_buffer[0] + ST7565_FONT_WIDTH;
+ uint8_t remainingSpace = ST7565_DISPLAY_WIDTH - (nextIndex % ST7565_DISPLAY_WIDTH);
+
+ // Do we have enough space on the current line for the next character
+ if (remainingSpace < ST7565_FONT_WIDTH) {
+ nextIndex += remainingSpace;
+ }
+
+ // Did we go out of bounds
+ if (nextIndex >= ST7565_MATRIX_SIZE) {
+ nextIndex = 0;
+ }
+
+ // Update cursor position
+ st7565_cursor = &st7565_buffer[nextIndex];
+}
+
+// Main handler that writes character data to the display buffer
+void st7565_write_char(const char data, bool invert) {
+ // Advance to the next line if newline
+ if (data == '\n') {
+ // Old source wrote ' ' until end of line...
+ st7565_advance_page(true);
+ return;
+ }
+
+ if (data == '\r') {
+ st7565_advance_page(false);
+ return;
+ }
+
+ // copy the current render buffer to check for dirty after
+ static uint8_t st7565_temp_buffer[ST7565_FONT_WIDTH];
+ memcpy(&st7565_temp_buffer, st7565_cursor, ST7565_FONT_WIDTH);
+
+ _Static_assert(sizeof(font) >= ((ST7565_FONT_END + 1 - ST7565_FONT_START) * ST7565_FONT_WIDTH), "ST7565_FONT_END references outside array");
+
+ // set the reder buffer data
+ uint8_t cast_data = (uint8_t)data; // font based on unsigned type for index
+ if (cast_data < ST7565_FONT_START || cast_data > ST7565_FONT_END) {
+ memset(st7565_cursor, 0x00, ST7565_FONT_WIDTH);
+ } else {
+ const uint8_t *glyph = &font[(cast_data - ST7565_FONT_START) * ST7565_FONT_WIDTH];
+ memcpy_P(st7565_cursor, glyph, ST7565_FONT_WIDTH);
+ }
+
+ // Invert if needed
+ if (invert) {
+ InvertCharacter(st7565_cursor);
+ }
+
+ // Dirty check
+ if (memcmp(&st7565_temp_buffer, st7565_cursor, ST7565_FONT_WIDTH)) {
+ uint16_t index = st7565_cursor - &st7565_buffer[0];
+ st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
+ // Edgecase check if the written data spans the 2 chunks
+ st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << ((index + ST7565_FONT_WIDTH - 1) / ST7565_BLOCK_SIZE));
+ }
+
+ // Finally move to the next char
+ st7565_advance_char();
+}
+
+void st7565_write(const char *data, bool invert) {
+ const char *end = data + strlen(data);
+ while (data < end) {
+ st7565_write_char(*data, invert);
+ data++;
+ }
+}
+
+void st7565_write_ln(const char *data, bool invert) {
+ st7565_write(data, invert);
+ st7565_advance_page(true);
+}
+
+void st7565_pan(bool left) {
+ uint16_t i = 0;
+ for (uint16_t y = 0; y < ST7565_DISPLAY_HEIGHT / 8; y++) {
+ if (left) {
+ for (uint16_t x = 0; x < ST7565_DISPLAY_WIDTH - 1; x++) {
+ i = y * ST7565_DISPLAY_WIDTH + x;
+ st7565_buffer[i] = st7565_buffer[i + 1];
+ }
+ } else {
+ for (uint16_t x = ST7565_DISPLAY_WIDTH - 1; x > 0; x--) {
+ i = y * ST7565_DISPLAY_WIDTH + x;
+ st7565_buffer[i] = st7565_buffer[i - 1];
+ }
+ }
+ }
+ st7565_dirty = ST7565_ALL_BLOCKS_MASK;
+}
+
+display_buffer_reader_t st7565_read_raw(uint16_t start_index) {
+ if (start_index > ST7565_MATRIX_SIZE) start_index = ST7565_MATRIX_SIZE;
+ display_buffer_reader_t ret_reader;
+ ret_reader.current_element = &st7565_buffer[start_index];
+ ret_reader.remaining_element_count = ST7565_MATRIX_SIZE - start_index;
+ return ret_reader;
+}
+
+void st7565_write_raw_byte(const char data, uint16_t index) {
+ if (index > ST7565_MATRIX_SIZE) index = ST7565_MATRIX_SIZE;
+ if (st7565_buffer[index] == data) return;
+ st7565_buffer[index] = data;
+ st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
+}
+
+void st7565_write_raw(const char *data, uint16_t size) {
+ uint16_t cursor_start_index = st7565_cursor - &st7565_buffer[0];
+ if ((size + cursor_start_index) > ST7565_MATRIX_SIZE) size = ST7565_MATRIX_SIZE - cursor_start_index;
+ for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
+ uint8_t c = *data++;
+ if (st7565_buffer[i] == c) continue;
+ st7565_buffer[i] = c;
+ st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (i / ST7565_BLOCK_SIZE));
+ }
+}
+
+void st7565_write_pixel(uint8_t x, uint8_t y, bool on) {
+ if (x >= ST7565_DISPLAY_WIDTH) {
+ return;
+ }
+ uint16_t index = x + (y / 8) * ST7565_DISPLAY_WIDTH;
+ if (index >= ST7565_MATRIX_SIZE) {
+ return;
+ }
+ uint8_t data = st7565_buffer[index];
+ if (on) {
+ data |= (1 << (y % 8));
+ } else {
+ data &= ~(1 << (y % 8));
+ }
+ if (st7565_buffer[index] != data) {
+ st7565_buffer[index] = data;
+ st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (index / ST7565_BLOCK_SIZE));
+ }
+}
+
+#if defined(__AVR__)
+void st7565_write_P(const char *data, bool invert) {
+ uint8_t c = pgm_read_byte(data);
+ while (c != 0) {
+ st7565_write_char(c, invert);
+ c = pgm_read_byte(++data);
+ }
+}
+
+void st7565_write_ln_P(const char *data, bool invert) {
+ st7565_write_P(data, invert);
+ st7565_advance_page(true);
+}
+
+void st7565_write_raw_P(const char *data, uint16_t size) {
+ uint16_t cursor_start_index = st7565_cursor - &st7565_buffer[0];
+ if ((size + cursor_start_index) > ST7565_MATRIX_SIZE) size = ST7565_MATRIX_SIZE - cursor_start_index;
+ for (uint16_t i = cursor_start_index; i < cursor_start_index + size; i++) {
+ uint8_t c = pgm_read_byte(data++);
+ if (st7565_buffer[i] == c) continue;
+ st7565_buffer[i] = c;
+ st7565_dirty |= ((ST7565_BLOCK_TYPE)1 << (i / ST7565_BLOCK_SIZE));
+ }
+}
+#endif // defined(__AVR__)
+
+bool st7565_on(void) {
+ if (!st7565_initialized) {
+ return st7565_active;
+ }
+
+#if ST7565_TIMEOUT > 0
+ st7565_timeout = timer_read32() + ST7565_TIMEOUT;
+#endif
+
+ if (!st7565_active) {
+ spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
+ st7565_send_cmd(DISPLAY_ON);
+ spi_stop();
+ st7565_active = true;
+ st7565_on_user();
+ }
+ return st7565_active;
+}
+
+__attribute__((weak)) void st7565_on_user(void) {}
+
+bool st7565_off(void) {
+ if (!st7565_initialized) {
+ return !st7565_active;
+ }
+
+ if (st7565_active) {
+ spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
+ st7565_send_cmd(DISPLAY_OFF);
+ spi_stop();
+ st7565_active = false;
+ st7565_off_user();
+ }
+ return !st7565_active;
+}
+
+__attribute__((weak)) void st7565_off_user(void) {}
+
+bool st7565_is_on(void) { return st7565_active; }
+
+bool st7565_invert(bool invert) {
+ if (!st7565_initialized) {
+ return st7565_inverted;
+ }
+
+ if (invert != st7565_inverted) {
+ spi_start(ST7565_SS_PIN, false, 0, ST7565_SPI_CLK_DIVISOR);
+ st7565_send_cmd(invert ? INVERT_DISPLAY : NORMAL_DISPLAY);
+ spi_stop();
+ st7565_inverted = invert;
+ }
+ return st7565_inverted;
+}
+
+uint8_t st7565_max_chars(void) { return ST7565_DISPLAY_WIDTH / ST7565_FONT_WIDTH; }
+
+uint8_t st7565_max_lines(void) { return ST7565_DISPLAY_HEIGHT / ST7565_FONT_HEIGHT; }
+
+void st7565_task(void) {
+ if (!st7565_initialized) {
+ return;
+ }
+
+#if ST7565_UPDATE_INTERVAL > 0
+ if (timer_elapsed(st7565_update_timeout) >= ST7565_UPDATE_INTERVAL) {
+ st7565_update_timeout = timer_read();
+ st7565_set_cursor(0, 0);
+ st7565_task_user();
+ }
+#else
+ st7565_set_cursor(0, 0);
+ st7565_task_user();
+#endif
+
+ // Smart render system, no need to check for dirty
+ st7565_render();
+
+ // Display timeout check
+#if ST7565_TIMEOUT > 0
+ if (st7565_active && timer_expired32(timer_read32(), st7565_timeout)) {
+ st7565_off();
+ }
+#endif
+}
+
+__attribute__((weak)) void st7565_task_user(void) {}
+
+void st7565_reset(void) {
+ writePinLow(ST7565_RST_PIN);
+ wait_ms(20);
+ writePinHigh(ST7565_RST_PIN);
+ wait_ms(20);
+}
+
+spi_status_t st7565_send_cmd(uint8_t cmd) {
+ writePinLow(ST7565_A0_PIN);
+ return spi_write(cmd);
+}
+
+spi_status_t st7565_send_data(uint8_t *data, uint16_t length) {
+ writePinHigh(ST7565_A0_PIN);
+ return spi_transmit(data, length);
+}
diff --git a/drivers/lcd/st7565.h b/drivers/lcd/st7565.h
new file mode 100644
index 0000000000..d453dbe6da
--- /dev/null
+++ b/drivers/lcd/st7565.h
@@ -0,0 +1,219 @@
+/*
+Copyright 2021
+
+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>
+#include <stdbool.h>
+
+#include "spi_master.h"
+
+#ifndef ST7565_DISPLAY_WIDTH
+# define ST7565_DISPLAY_WIDTH 128
+#endif
+#ifndef ST7565_DISPLAY_HEIGHT
+# define ST7565_DISPLAY_HEIGHT 32
+#endif
+#ifndef ST7565_MATRIX_SIZE
+# define ST7565_MATRIX_SIZE (ST7565_DISPLAY_HEIGHT / 8 * ST7565_DISPLAY_WIDTH) // 1024 (compile time mathed)
+#endif
+#ifndef ST7565_BLOCK_TYPE
+# define ST7565_BLOCK_TYPE uint16_t
+#endif
+#ifndef ST7565_BLOCK_COUNT
+# define ST7565_BLOCK_COUNT (sizeof(ST7565_BLOCK_TYPE) * 8) // 32 (compile time mathed)
+#endif
+#ifndef ST7565_BLOCK_SIZE
+# define ST7565_BLOCK_SIZE (ST7565_MATRIX_SIZE / ST7565_BLOCK_COUNT) // 32 (compile time mathed)
+#endif
+
+// the column address corresponding to the first column in the display hardware
+#if !defined(ST7565_COLUMN_OFFSET)
+# define ST7565_COLUMN_OFFSET 0
+#endif
+
+// spi clock divisor
+#if !defined(ST7565_SPI_CLK_DIVISOR)
+# define ST7565_SPI_CLK_DIVISOR 4
+#endif
+
+// Custom font file to use
+#if !defined(ST7565_FONT_H)
+# define ST7565_FONT_H "glcdfont.c"
+#endif
+// unsigned char value of the first character in the font file
+#if !defined(ST7565_FONT_START)
+# define ST7565_FONT_START 0
+#endif
+// unsigned char value of the last character in the font file
+#if !defined(ST7565_FONT_END)
+# define ST7565_FONT_END 223
+#endif
+// Font render width
+#if !defined(ST7565_FONT_WIDTH)
+# define ST7565_FONT_WIDTH 6
+#endif
+// Font render height
+#if !defined(ST7565_FONT_HEIGHT)
+# define ST7565_FONT_HEIGHT 8
+#endif
+// Default contrast level
+#if !defined(ST7565_CONTRAST)
+# define ST7565_CONTRAST 32
+#endif
+
+#if !defined(ST7565_TIMEOUT)
+# if defined(ST7565_DISABLE_TIMEOUT)
+# define ST7565_TIMEOUT 0
+# else
+# define ST7565_TIMEOUT 60000
+# endif
+#endif
+
+#if !defined(ST7565_UPDATE_INTERVAL) && defined(SPLIT_KEYBOARD)
+# define ST7565_UPDATE_INTERVAL 50
+#endif
+
+typedef struct __attribute__((__packed__)) {
+ uint8_t *current_element;
+ uint16_t remaining_element_count;
+} display_buffer_reader_t;
+
+// Rotation enum values are flags
+typedef enum { DISPLAY_ROTATION_0, DISPLAY_ROTATION_180 } display_rotation_t;
+
+// Initialize the display, rotating the rendered output based on the define passed in.
+// Returns true if the display was initialized successfully
+bool st7565_init(display_rotation_t rotation);
+
+// Called at the start of st7565_init, weak function overridable by the user
+// rotation - the value passed into st7565_init
+// Return new display_rotation_t if you want to override default rotation
+display_rotation_t st7565_init_user(display_rotation_t rotation);
+
+// Clears the display buffer, resets cursor position to 0, and sets the buffer to dirty for rendering
+void st7565_clear(void);
+
+// Renders the dirty chunks of the buffer to display
+void st7565_render(void);
+
+// Moves cursor to character position indicated by column and line, wraps if out of bounds
+// Max column denoted by 'st7565_max_chars()' and max lines by 'st7565_max_lines()' functions
+void st7565_set_cursor(uint8_t col, uint8_t line);
+
+// Advances the cursor to the next page, writing ' ' if true
+// Wraps to the begining when out of bounds
+void st7565_advance_page(bool clearPageRemainder);
+
+// Moves the cursor forward 1 character length
+// Advance page if there is not enough room for the next character
+// Wraps to the begining when out of bounds
+void st7565_advance_char(void);
+
+// Writes a single character to the buffer at current cursor position
+// Advances the cursor while writing, inverts the pixels if true
+// Main handler that writes character data to the display buffer
+void st7565_write_char(const char data, bool invert);
+
+// Writes a string to the buffer at current cursor position
+// Advances the cursor while writing, inverts the pixels if true
+void st7565_write(const char *data, bool invert);
+
+// Writes a string to the buffer at current cursor position
+// Advances the cursor while writing, inverts the pixels if true
+// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
+void st7565_write_ln(const char *data, bool invert);
+
+// Pans the buffer to the right (or left by passing true) by moving contents of the buffer
+// Useful for moving the screen in preparation for new drawing
+void st7565_pan(bool left);
+
+// Returns a pointer to the requested start index in the buffer plus remaining
+// buffer length as struct
+display_buffer_reader_t st7565_read_raw(uint16_t start_index);
+
+// Writes a string to the buffer at current cursor position
+void st7565_write_raw(const char *data, uint16_t size);
+
+// Writes a single byte into the buffer at the specified index
+void st7565_write_raw_byte(const char data, uint16_t index);
+
+// Sets a specific pixel on or off
+// Coordinates start at top-left and go right and down for positive x and y
+void st7565_write_pixel(uint8_t x, uint8_t y, bool on);
+
+#if defined(__AVR__)
+// Writes a PROGMEM string to the buffer at current cursor position
+// Advances the cursor while writing, inverts the pixels if true
+// Remapped to call 'void st7565_write(const char *data, bool invert);' on ARM
+void st7565_write_P(const char *data, bool invert);
+
+// Writes a PROGMEM string to the buffer at current cursor position
+// Advances the cursor while writing, inverts the pixels if true
+// Advances the cursor to the next page, wiring ' ' to the remainder of the current page
+// Remapped to call 'void st7565_write_ln(const char *data, bool invert);' on ARM
+void st7565_write_ln_P(const char *data, bool invert);
+
+// Writes a PROGMEM string to the buffer at current cursor position
+void st7565_write_raw_P(const char *data, uint16_t size);
+#else
+# define st7565_write_P(data, invert) st7565_write(data, invert)
+# define st7565_write_ln_P(data, invert) st7565_write_ln(data, invert)
+# define st7565_write_raw_P(data, size) st7565_write_raw(data, size)
+#endif // defined(__AVR__)
+
+// Can be used to manually turn on the screen if it is off
+// Returns true if the screen was on or turns on
+bool st7565_on(void);
+
+// Called when st7565_on() turns on the screen, weak function overridable by the user
+// Not called if the screen is already on
+void st7565_on_user(void);
+
+// Can be used to manually turn off the screen if it is on
+// Returns true if the screen was off or turns off
+bool st7565_off(void);
+
+// Called when st7565_off() turns off the screen, weak function overridable by the user
+// Not called if the screen is already off
+void st7565_off_user(void);
+
+// Returns true if the screen is currently on, false if it is
+// not
+bool st7565_is_on(void);
+
+// Basically it's st7565_render, but with timeout management and st7565_task_user calling!
+void st7565_task(void);
+
+// Called at the start of st7565_task, weak function overridable by the user
+void st7565_task_user(void);
+
+// Inverts the display
+// Returns true if the screen was or is inverted
+bool st7565_invert(bool invert);
+
+// Returns the maximum number of characters that will fit on a line
+uint8_t st7565_max_chars(void);
+
+// Returns the maximum number of lines that will fit on the display
+uint8_t st7565_max_lines(void);
+
+void st7565_reset(void);
+
+spi_status_t st7565_send_cmd(uint8_t cmd);
+
+spi_status_t st7565_send_data(uint8_t *data, uint16_t length);
diff --git a/drivers/oled/oled_driver.c b/drivers/oled/oled_driver.c
index 8e5ed5f070..7d41978905 100644
--- a/drivers/oled/oled_driver.c
+++ b/drivers/oled/oled_driver.c
@@ -34,6 +34,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DISPLAY_ALL_ON 0xA5
#define DISPLAY_ALL_ON_RESUME 0xA4
#define NORMAL_DISPLAY 0xA6
+#define INVERT_DISPLAY 0xA7
#define DISPLAY_ON 0xAF
#define DISPLAY_OFF 0xAE
#define NOP 0xE3
@@ -114,6 +115,7 @@ OLED_BLOCK_TYPE oled_dirty = 0;
bool oled_initialized = false;
bool oled_active = false;
bool oled_scrolling = false;
+bool oled_inverted = false;
uint8_t oled_brightness = OLED_BRIGHTNESS;
oled_rotation_t oled_rotation = 0;
uint8_t oled_rotation_width = 0;
@@ -690,6 +692,30 @@ bool oled_scroll_off(void) {
return !oled_scrolling;
}
+bool oled_invert(bool invert) {
+ if (!oled_initialized) {
+ return oled_inverted;
+ }
+
+ if (invert && !oled_inverted) {
+ static const uint8_t PROGMEM display_inverted[] = {I2C_CMD, INVERT_DISPLAY};
+ if (I2C_TRANSMIT_P(display_inverted) != I2C_STATUS_SUCCESS) {
+ print("oled_invert cmd failed\n");
+ return oled_inverted;
+ }
+ oled_inverted = true;
+ } else if (!invert && oled_inverted) {
+ static const uint8_t PROGMEM display_normal[] = {I2C_CMD, NORMAL_DISPLAY};
+ if (I2C_TRANSMIT_P(display_normal) != I2C_STATUS_SUCCESS) {
+ print("oled_invert cmd failed\n");
+ return oled_inverted;
+ }
+ oled_inverted = false;
+ }
+
+ return oled_inverted;
+}
+
uint8_t oled_max_chars(void) {
if (!HAS_FLAGS(oled_rotation, OLED_ROTATION_90)) {
return OLED_DISPLAY_WIDTH / OLED_FONT_WIDTH;
diff --git a/drivers/oled/oled_driver.h b/drivers/oled/oled_driver.h
index a6b85f37e6..fc68f0ec95 100644
--- a/drivers/oled/oled_driver.h
+++ b/drivers/oled/oled_driver.h
@@ -313,6 +313,10 @@ bool oled_scroll_left(void);
// Returns true if the screen was not scrolling or stops scrolling
bool oled_scroll_off(void);
+// Inverts the display
+// Returns true if the screen was or is inverted
+bool oled_invert(bool invert);
+
// Returns the maximum number of characters that will fit on a line
uint8_t oled_max_chars(void);
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..c0ac644f70
--- /dev/null
+++ b/drivers/sensors/pimoroni_trackball.c
@@ -0,0 +1,140 @@
+/* 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 "pimoroni_trackball.h"
+#include "i2c_master.h"
+
+static uint8_t scrolling = 0;
+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 float precisionSpeed = 1;
+
+static uint16_t i2c_timeout_timer;
+
+#ifndef PIMORONI_I2C_TIMEOUT
+# define PIMORONI_I2C_TIMEOUT 100
+#endif
+#ifndef I2C_WAITCHECK
+# define I2C_WAITCHECK 1000
+#endif
+#ifndef MOUSE_DEBOUNCE
+# define MOUSE_DEBOUNCE 5
+#endif
+
+void trackball_set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) {
+ uint8_t data[] = {0x00, red, green, blue, white};
+ i2c_transmit(TRACKBALL_WRITE, data, sizeof(data), PIMORONI_I2C_TIMEOUT);
+}
+
+int16_t mouse_offset(uint8_t positive, uint8_t negative, int16_t scale) {
+ int16_t offset = (int16_t)positive - (int16_t)negative;
+ int16_t magnitude = (int16_t)(scale * offset * offset * precisionSpeed);
+ return offset < 0 ? -magnitude : magnitude;
+}
+
+void update_member(int8_t* member, int16_t* offset) {
+ if (*offset > 127) {
+ *member = 127;
+ *offset -= 127;
+ } else if (*offset < -127) {
+ *member = -127;
+ *offset += 127;
+ } else {
+ *member = *offset;
+ *offset = 0;
+ }
+}
+
+__attribute__((weak)) void trackball_check_click(bool pressed, report_mouse_t* mouse) {
+ if (pressed) {
+ mouse->buttons |= MOUSE_BTN1;
+ } else {
+ mouse->buttons &= ~MOUSE_BTN1;
+ }
+}
+
+float trackball_get_precision(void) { return precisionSpeed; }
+void trackball_set_precision(float precision) { precisionSpeed = precision; }
+bool trackball_is_scrolling(void) { return scrolling; }
+void trackball_set_scrolling(bool scroll) { scrolling = scroll; }
+
+__attribute__((weak)) void pointing_device_init(void) { i2c_init(); trackball_set_rgbw(0x00, 0x00, 0x00, 0x00); }
+
+void pointing_device_task(void) {
+ static bool debounce;
+ static uint16_t debounce_timer;
+ uint8_t state[5] = {};
+ if (timer_elapsed(i2c_timeout_timer) > I2C_WAITCHECK) {
+ if (i2c_readReg(TRACKBALL_READ, 0x04, state, 5, PIMORONI_I2C_TIMEOUT) == I2C_STATUS_SUCCESS) {
+ if (!state[4] && !debounce) {
+ if (scrolling) {
+#ifdef PIMORONI_TRACKBALL_INVERT_X
+ h_offset += mouse_offset(state[2], state[3], 1);
+#else
+ h_offset -= mouse_offset(state[2], state[3], 1);
+#endif
+#ifdef PIMORONI_TRACKBALL_INVERT_Y
+ v_offset += mouse_offset(state[1], state[0], 1);
+#else
+ v_offset -= mouse_offset(state[1], state[0], 1);
+#endif
+ } else {
+#ifdef PIMORONI_TRACKBALL_INVERT_X
+ x_offset -= mouse_offset(state[2], state[3], 5);
+#else
+ x_offset += mouse_offset(state[2], state[3], 5);
+#endif
+#ifdef PIMORONI_TRACKBALL_INVERT_Y
+ y_offset -= mouse_offset(state[1], state[0], 5);
+#else
+ y_offset += mouse_offset(state[1], state[0], 5);
+#endif
+ }
+ } else {
+ if (state[4]) {
+ debounce = true;
+ debounce_timer = timer_read();
+ }
+ }
+ } else {
+ i2c_timeout_timer = timer_read();
+ }
+ }
+
+ if (timer_elapsed(debounce_timer) > MOUSE_DEBOUNCE) debounce = false;
+
+ report_mouse_t mouse = pointing_device_get_report();
+
+#ifdef PIMORONI_TRACKBALL_CLICK
+ trackball_check_click(state[4] & (1 << 7), &mouse);
+#endif
+
+#ifndef PIMORONI_TRACKBALL_ROTATE
+ update_member(&mouse.x, &x_offset);
+ update_member(&mouse.y, &y_offset);
+ update_member(&mouse.h, &h_offset);
+ update_member(&mouse.v, &v_offset);
+#else
+ update_member(&mouse.x, &y_offset);
+ update_member(&mouse.y, &x_offset);
+ update_member(&mouse.h, &v_offset);
+ update_member(&mouse.v, &h_offset);
+#endif
+ pointing_device_set_report(mouse);
+ pointing_device_send();
+}
diff --git a/drivers/sensors/pimoroni_trackball.h b/drivers/sensors/pimoroni_trackball.h
new file mode 100644
index 0000000000..a30fb0bb8c
--- /dev/null
+++ b/drivers/sensors/pimoroni_trackball.h
@@ -0,0 +1,35 @@
+/* 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 "quantum.h"
+#include "pointing_device.h"
+
+#ifndef TRACKBALL_ADDRESS
+# define TRACKBALL_ADDRESS 0x0A
+#endif
+#define TRACKBALL_WRITE ((TRACKBALL_ADDRESS << 1) | I2C_WRITE)
+#define TRACKBALL_READ ((TRACKBALL_ADDRESS << 1) | I2C_READ)
+
+void trackball_set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white);
+void trackball_check_click(bool pressed, report_mouse_t *mouse);
+void trackball_register_button(bool pressed, enum mouse_buttons button);
+
+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..91ee87b957
--- /dev/null
+++ b/drivers/sensors/pmw3360.c
@@ -0,0 +1,237 @@
+/* Copyright 2020 Christopher Courtney, aka Drashna Jael're (@drashna) <drashna@live.com>
+ * Copyright 2019 Sunjun Kim
+ * Copyright 2020 Ploopy Corporation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "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();
+
+ 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
diff --git a/drivers/serial.h b/drivers/serial.h
new file mode 100644
index 0000000000..d9c2a69e96
--- /dev/null
+++ b/drivers/serial.h
@@ -0,0 +1,46 @@
+/* Copyright 2021 QMK
+ *
+ * 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>
+#include <stdbool.h>
+
+#include <transactions.h>
+
+// initiator is transaction start side
+void soft_serial_initiator_init(void);
+// target is interrupt accept side
+void soft_serial_target_init(void);
+
+// initiator result
+#define TRANSACTION_END 0
+#define TRANSACTION_NO_RESPONSE 0x1
+#define TRANSACTION_DATA_ERROR 0x2
+#define TRANSACTION_TYPE_ERROR 0x4
+int soft_serial_transaction(int sstd_index);
+
+// target status
+// *SSTD_t.status has
+// initiator:
+// TRANSACTION_END
+// or TRANSACTION_NO_RESPONSE
+// or TRANSACTION_DATA_ERROR
+// target:
+// TRANSACTION_DATA_ERROR
+// or TRANSACTION_ACCEPTED
+#define TRANSACTION_ACCEPTED 0x8
+int soft_serial_get_and_clean_status(int sstd_index);