diff options
Diffstat (limited to 'quantum')
34 files changed, 3371 insertions, 670 deletions
diff --git a/quantum/crc.c b/quantum/crc.c new file mode 100644 index 0000000000..0d8b9d6017 --- /dev/null +++ b/quantum/crc.c @@ -0,0 +1,59 @@ +/* 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/>. + */ + +#include "crc.h" + +__attribute__((weak)) void crc_init(void){ + /* Software implementation nothing todo here. */ +}; + +#if defined(CRC8_USE_TABLE) +/** + * Static table used for the table_driven implementation. + */ +static const crc_t crc_table[256] = {0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, + 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3}; + +__attribute__((weak)) uint8_t crc8(const void *data, size_t data_len) { + const uint8_t *d = (const uint8_t *)data; + crc_t crc = 0xff; + size_t tbl_idx; + + while (data_len--) { + tbl_idx = crc ^ *d; + crc = crc_table[tbl_idx] & 0xff; + d++; + } + return crc & 0xff; +} +#else +__attribute__((weak)) uint8_t crc8(const void *data, size_t data_len) { + const uint8_t *d = (const uint8_t *)data; + crc_t crc = 0xff; + size_t i, j; + + for (i = 0; i < data_len; i++) { + crc ^= d[i]; + for (j = 0; j < 8; j++) { + if ((crc & 0x80) != 0) + crc = (crc_t)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc; +} +#endif
\ No newline at end of file diff --git a/quantum/crc.h b/quantum/crc.h new file mode 100644 index 0000000000..c17f5888e2 --- /dev/null +++ b/quantum/crc.h @@ -0,0 +1,44 @@ +/* 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 "quantum.h" + +/** + * The type of the CRC values. + * + * This type must be big enough to contain at least 8 bits. + */ +#if defined(CRC8_OPTIMIZE_SPEED) +typedef uint_fast8_t crc_t; +#else +typedef uint_least8_t crc_t; +#endif + +/** + * Initialize crc subsystem. + */ +__attribute__((weak)) void crc_init(void); + +/** + * Generate CRC8 value from given data. + * + * \param[in] data Pointer to a buffer of \a data_len bytes. + * \param[in] data_len Number of bytes in the \a data buffer. + * \return The calculated crc value. + */ +__attribute__((weak)) uint8_t crc8(const void *data, size_t data_len);
\ No newline at end of file diff --git a/quantum/debounce.h b/quantum/debounce.h index 9ca05c6824..5043868289 100644 --- a/quantum/debounce.h +++ b/quantum/debounce.h @@ -9,3 +9,5 @@ void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool bool debounce_active(void); void debounce_init(uint8_t num_rows); + +void debounce_free(void); diff --git a/quantum/debounce/asym_eager_defer_pk.c b/quantum/debounce/asym_eager_defer_pk.c new file mode 100644 index 0000000000..24380dc5e5 --- /dev/null +++ b/quantum/debounce/asym_eager_defer_pk.c @@ -0,0 +1,171 @@ +/* + * Copyright 2017 Alex Ong <the.onga@gmail.com> + * Copyright 2020 Andrei Purdea <andrei@purdea.ro> + * Copyright 2021 Simon Arlott + * + * 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/>. + */ + +/* +Basic symmetric per-key algorithm. Uses an 8-bit counter per key. +When no state changes have occured for DEBOUNCE milliseconds, we push the state. +*/ + +#include "matrix.h" +#include "timer.h" +#include "quantum.h" +#include <stdlib.h> + +#ifdef PROTOCOL_CHIBIOS +# if CH_CFG_USE_MEMCORE == FALSE +# error ChibiOS is configured without a memory allocator. Your keyboard may have set `#define CH_CFG_USE_MEMCORE FALSE`, which is incompatible with this debounce algorithm. +# endif +#endif + +#ifndef DEBOUNCE +# define DEBOUNCE 5 +#endif + +// Maximum debounce: 127ms +#if DEBOUNCE > 127 +# undef DEBOUNCE +# define DEBOUNCE 127 +#endif + +#define ROW_SHIFTER ((matrix_row_t)1) + +typedef struct { + bool pressed : 1; + uint8_t time : 7; +} debounce_counter_t; + +#if DEBOUNCE > 0 +static debounce_counter_t *debounce_counters; +static fast_timer_t last_time; +static bool counters_need_update; +static bool matrix_need_update; + +#define DEBOUNCE_ELAPSED 0 + +static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time); +static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows); + +// we use num_rows rather than MATRIX_ROWS to support split keyboards +void debounce_init(uint8_t num_rows) { + debounce_counters = malloc(num_rows * MATRIX_COLS * sizeof(debounce_counter_t)); + int i = 0; + for (uint8_t r = 0; r < num_rows; r++) { + for (uint8_t c = 0; c < MATRIX_COLS; c++) { + debounce_counters[i++].time = DEBOUNCE_ELAPSED; + } + } +} + +void debounce_free(void) { + free(debounce_counters); + debounce_counters = NULL; +} + +void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { + bool updated_last = false; + + if (counters_need_update) { + fast_timer_t now = timer_read_fast(); + fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time); + + last_time = now; + updated_last = true; + if (elapsed_time > UINT8_MAX) { + elapsed_time = UINT8_MAX; + } + + if (elapsed_time > 0) { + update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, elapsed_time); + } + } + + if (changed || matrix_need_update) { + if (!updated_last) { + last_time = timer_read_fast(); + } + + transfer_matrix_values(raw, cooked, num_rows); + } +} + +static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time) { + debounce_counter_t *debounce_pointer = debounce_counters; + + counters_need_update = false; + matrix_need_update = false; + + for (uint8_t row = 0; row < num_rows; row++) { + for (uint8_t col = 0; col < MATRIX_COLS; col++) { + matrix_row_t col_mask = (ROW_SHIFTER << col); + + if (debounce_pointer->time != DEBOUNCE_ELAPSED) { + if (debounce_pointer->time <= elapsed_time) { + debounce_pointer->time = DEBOUNCE_ELAPSED; + + if (debounce_pointer->pressed) { + // key-down: eager + matrix_need_update = true; + } else { + // key-up: defer + cooked[row] = (cooked[row] & ~col_mask) | (raw[row] & col_mask); + } + } else { + debounce_pointer->time -= elapsed_time; + counters_need_update = true; + } + } + debounce_pointer++; + } + } +} + +static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) { + debounce_counter_t *debounce_pointer = debounce_counters; + + for (uint8_t row = 0; row < num_rows; row++) { + matrix_row_t delta = raw[row] ^ cooked[row]; + for (uint8_t col = 0; col < MATRIX_COLS; col++) { + matrix_row_t col_mask = (ROW_SHIFTER << col); + + if (delta & col_mask) { + if (debounce_pointer->time == DEBOUNCE_ELAPSED) { + debounce_pointer->pressed = (raw[row] & col_mask); + debounce_pointer->time = DEBOUNCE; + counters_need_update = true; + + if (debounce_pointer->pressed) { + // key-down: eager + cooked[row] ^= col_mask; + } + } + } else if (debounce_pointer->time != DEBOUNCE_ELAPSED) { + if (!debounce_pointer->pressed) { + // key-up: defer + debounce_pointer->time = DEBOUNCE_ELAPSED; + } + } + debounce_pointer++; + } + } +} + +bool debounce_active(void) { return true; } +#else +# include "none.c" +#endif diff --git a/quantum/debounce/none.c b/quantum/debounce/none.c new file mode 100644 index 0000000000..b03892bc5b --- /dev/null +++ b/quantum/debounce/none.c @@ -0,0 +1,31 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "matrix.h" +#include "quantum.h" +#include <stdlib.h> + +void debounce_init(uint8_t num_rows) {} + +void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { + for (int i = 0; i < num_rows; i++) { + cooked[i] = raw[i]; + } +} + +bool debounce_active(void) { return false; } + +void debounce_free(void) {} diff --git a/quantum/debounce/sym_defer_g.c b/quantum/debounce/sym_defer_g.c index 3ed9055d2a..fbefd55ede 100644 --- a/quantum/debounce/sym_defer_g.c +++ b/quantum/debounce/sym_defer_g.c @@ -1,5 +1,6 @@ /* Copyright 2017 Alex Ong<the.onga@gmail.com> +Copyright 2021 Simon Arlott 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 @@ -23,30 +24,29 @@ When no state changes have occured for DEBOUNCE milliseconds, we push the state. # define DEBOUNCE 5 #endif -void debounce_init(uint8_t num_rows) {} +#if DEBOUNCE > 0 static bool debouncing = false; +static fast_timer_t debouncing_time; -#if DEBOUNCE > 0 -static uint16_t debouncing_time; -void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { +void debounce_init(uint8_t num_rows) {} + +void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { if (changed) { debouncing = true; - debouncing_time = timer_read(); + debouncing_time = timer_read_fast(); } - if (debouncing && timer_elapsed(debouncing_time) > DEBOUNCE) { + if (debouncing && timer_elapsed_fast(debouncing_time) >= DEBOUNCE) { for (int i = 0; i < num_rows; i++) { cooked[i] = raw[i]; } debouncing = false; } } -#else // no debouncing. -void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { - for (int i = 0; i < num_rows; i++) { - cooked[i] = raw[i]; - } -} -#endif bool debounce_active(void) { return debouncing; } + +void debounce_free(void) {} +#else // no debouncing. +# include "none.c" +#endif diff --git a/quantum/debounce/sym_defer_pk.c b/quantum/debounce/sym_defer_pk.c index 60513f98e1..626a9be841 100644 --- a/quantum/debounce/sym_defer_pk.c +++ b/quantum/debounce/sym_defer_pk.c @@ -1,6 +1,7 @@ /* Copyright 2017 Alex Ong<the.onga@gmail.com> Copyright 2020 Andrei Purdea<andrei@purdea.ro> +Copyright 2021 Simon Arlott 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 @@ -33,28 +34,25 @@ When no state changes have occured for DEBOUNCE milliseconds, we push the state. # define DEBOUNCE 5 #endif +// Maximum debounce: 255ms +#if DEBOUNCE > UINT8_MAX +# undef DEBOUNCE +# define DEBOUNCE UINT8_MAX +#endif + #define ROW_SHIFTER ((matrix_row_t)1) -#define debounce_counter_t uint8_t +typedef uint8_t debounce_counter_t; +#if DEBOUNCE > 0 static debounce_counter_t *debounce_counters; +static fast_timer_t last_time; static bool counters_need_update; -#define DEBOUNCE_ELAPSED 251 -#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1) - -static uint8_t wrapping_timer_read(void) { - static uint16_t time = 0; - static uint8_t last_result = 0; - uint16_t new_time = timer_read(); - uint16_t diff = new_time - time; - time = new_time; - last_result = (last_result + diff) % (MAX_DEBOUNCE + 1); - return last_result; -} +#define DEBOUNCE_ELAPSED 0 -void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); -void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); +static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time); +static void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows); // we use num_rows rather than MATRIX_ROWS to support split keyboards void debounce_init(uint8_t num_rows) { @@ -67,27 +65,49 @@ void debounce_init(uint8_t num_rows) { } } +void debounce_free(void) { + free(debounce_counters); + debounce_counters = NULL; +} + void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { - uint8_t current_time = wrapping_timer_read(); + bool updated_last = false; + if (counters_need_update) { - update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, current_time); + fast_timer_t now = timer_read_fast(); + fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time); + + last_time = now; + updated_last = true; + if (elapsed_time > UINT8_MAX) { + elapsed_time = UINT8_MAX; + } + + if (elapsed_time > 0) { + update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, elapsed_time); + } } if (changed) { - start_debounce_counters(raw, cooked, num_rows, current_time); + if (!updated_last) { + last_time = timer_read_fast(); + } + + start_debounce_counters(raw, cooked, num_rows); } } -void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { +static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time) { counters_need_update = false; debounce_counter_t *debounce_pointer = debounce_counters; for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t col = 0; col < MATRIX_COLS; col++) { if (*debounce_pointer != DEBOUNCE_ELAPSED) { - if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { + if (*debounce_pointer <= elapsed_time) { *debounce_pointer = DEBOUNCE_ELAPSED; cooked[row] = (cooked[row] & ~(ROW_SHIFTER << col)) | (raw[row] & (ROW_SHIFTER << col)); } else { + *debounce_pointer -= elapsed_time; counters_need_update = true; } } @@ -96,14 +116,14 @@ void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix } } -void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { +static void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) { debounce_counter_t *debounce_pointer = debounce_counters; for (uint8_t row = 0; row < num_rows; row++) { matrix_row_t delta = raw[row] ^ cooked[row]; for (uint8_t col = 0; col < MATRIX_COLS; col++) { if (delta & (ROW_SHIFTER << col)) { if (*debounce_pointer == DEBOUNCE_ELAPSED) { - *debounce_pointer = current_time; + *debounce_pointer = DEBOUNCE; counters_need_update = true; } } else { @@ -115,3 +135,6 @@ void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t } bool debounce_active(void) { return true; } +#else +# include "none.c" +#endif diff --git a/quantum/debounce/sym_eager_pk.c b/quantum/debounce/sym_eager_pk.c index e66cf92d79..15a3242e68 100644 --- a/quantum/debounce/sym_eager_pk.c +++ b/quantum/debounce/sym_eager_pk.c @@ -1,5 +1,6 @@ /* Copyright 2017 Alex Ong<the.onga@gmail.com> +Copyright 2021 Simon Arlott 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 @@ -33,29 +34,26 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred. # define DEBOUNCE 5 #endif +// Maximum debounce: 255ms +#if DEBOUNCE > UINT8_MAX +# undef DEBOUNCE +# define DEBOUNCE UINT8_MAX +#endif + #define ROW_SHIFTER ((matrix_row_t)1) -#define debounce_counter_t uint8_t +typedef uint8_t debounce_counter_t; +#if DEBOUNCE > 0 static debounce_counter_t *debounce_counters; +static fast_timer_t last_time; static bool counters_need_update; static bool matrix_need_update; -#define DEBOUNCE_ELAPSED 251 -#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1) - -static uint8_t wrapping_timer_read(void) { - static uint16_t time = 0; - static uint8_t last_result = 0; - uint16_t new_time = timer_read(); - uint16_t diff = new_time - time; - time = new_time; - last_result = (last_result + diff) % (MAX_DEBOUNCE + 1); - return last_result; -} +#define DEBOUNCE_ELAPSED 0 -void update_debounce_counters(uint8_t num_rows, uint8_t current_time); -void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); +static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time); +static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows); // we use num_rows rather than MATRIX_ROWS to support split keyboards void debounce_init(uint8_t num_rows) { @@ -68,27 +66,51 @@ void debounce_init(uint8_t num_rows) { } } +void debounce_free(void) { + free(debounce_counters); + debounce_counters = NULL; +} + void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { - uint8_t current_time = wrapping_timer_read(); + bool updated_last = false; + if (counters_need_update) { - update_debounce_counters(num_rows, current_time); + fast_timer_t now = timer_read_fast(); + fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time); + + last_time = now; + updated_last = true; + if (elapsed_time > UINT8_MAX) { + elapsed_time = UINT8_MAX; + } + + if (elapsed_time > 0) { + update_debounce_counters(num_rows, elapsed_time); + } } if (changed || matrix_need_update) { - transfer_matrix_values(raw, cooked, num_rows, current_time); + if (!updated_last) { + last_time = timer_read_fast(); + } + + transfer_matrix_values(raw, cooked, num_rows); } } // If the current time is > debounce counter, set the counter to enable input. -void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { +static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time) { counters_need_update = false; + matrix_need_update = false; debounce_counter_t *debounce_pointer = debounce_counters; for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t col = 0; col < MATRIX_COLS; col++) { if (*debounce_pointer != DEBOUNCE_ELAPSED) { - if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { + if (*debounce_pointer <= elapsed_time) { *debounce_pointer = DEBOUNCE_ELAPSED; + matrix_need_update = true; } else { + *debounce_pointer -= elapsed_time; counters_need_update = true; } } @@ -98,8 +120,7 @@ void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { } // upload from raw_matrix to final matrix; -void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { - matrix_need_update = false; +static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) { debounce_counter_t *debounce_pointer = debounce_counters; for (uint8_t row = 0; row < num_rows; row++) { matrix_row_t delta = raw[row] ^ cooked[row]; @@ -108,11 +129,9 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n matrix_row_t col_mask = (ROW_SHIFTER << col); if (delta & col_mask) { if (*debounce_pointer == DEBOUNCE_ELAPSED) { - *debounce_pointer = current_time; + *debounce_pointer = DEBOUNCE; counters_need_update = true; existing_row ^= col_mask; // flip the bit. - } else { - matrix_need_update = true; } } debounce_pointer++; @@ -122,3 +141,6 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n } bool debounce_active(void) { return true; } +#else +# include "none.c" +#endif diff --git a/quantum/debounce/sym_eager_pr.c b/quantum/debounce/sym_eager_pr.c index 20ccb46f1d..2ad592c5a6 100644 --- a/quantum/debounce/sym_eager_pr.c +++ b/quantum/debounce/sym_eager_pr.c @@ -1,5 +1,6 @@ /* Copyright 2019 Alex Ong<the.onga@gmail.com> +Copyright 2021 Simon Arlott 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 @@ -33,27 +34,25 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred. # define DEBOUNCE 5 #endif -#define debounce_counter_t uint8_t +// Maximum debounce: 255ms +#if DEBOUNCE > UINT8_MAX +# undef DEBOUNCE +# define DEBOUNCE UINT8_MAX +#endif + +typedef uint8_t debounce_counter_t; + +#if DEBOUNCE > 0 static bool matrix_need_update; static debounce_counter_t *debounce_counters; +static fast_timer_t last_time; static bool counters_need_update; -#define DEBOUNCE_ELAPSED 251 -#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1) - -static uint8_t wrapping_timer_read(void) { - static uint16_t time = 0; - static uint8_t last_result = 0; - uint16_t new_time = timer_read(); - uint16_t diff = new_time - time; - time = new_time; - last_result = (last_result + diff) % (MAX_DEBOUNCE + 1); - return last_result; -} +#define DEBOUNCE_ELAPSED 0 -void update_debounce_counters(uint8_t num_rows, uint8_t current_time); -void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); +static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time); +static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows); // we use num_rows rather than MATRIX_ROWS to support split keyboards void debounce_init(uint8_t num_rows) { @@ -63,27 +62,50 @@ void debounce_init(uint8_t num_rows) { } } +void debounce_free(void) { + free(debounce_counters); + debounce_counters = NULL; +} + void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { - uint8_t current_time = wrapping_timer_read(); - bool needed_update = counters_need_update; + bool updated_last = false; + if (counters_need_update) { - update_debounce_counters(num_rows, current_time); + fast_timer_t now = timer_read_fast(); + fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time); + + last_time = now; + updated_last = true; + if (elapsed_time > UINT8_MAX) { + elapsed_time = UINT8_MAX; + } + + if (elapsed_time > 0) { + update_debounce_counters(num_rows, elapsed_time); + } } - if (changed || (needed_update && !counters_need_update) || matrix_need_update) { - transfer_matrix_values(raw, cooked, num_rows, current_time); + if (changed || matrix_need_update) { + if (!updated_last) { + last_time = timer_read_fast(); + } + + transfer_matrix_values(raw, cooked, num_rows); } } // If the current time is > debounce counter, set the counter to enable input. -void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { +static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time) { counters_need_update = false; + matrix_need_update = false; debounce_counter_t *debounce_pointer = debounce_counters; for (uint8_t row = 0; row < num_rows; row++) { if (*debounce_pointer != DEBOUNCE_ELAPSED) { - if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { + if (*debounce_pointer <= elapsed_time) { *debounce_pointer = DEBOUNCE_ELAPSED; + matrix_need_update = true; } else { + *debounce_pointer -= elapsed_time; counters_need_update = true; } } @@ -92,8 +114,7 @@ void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { } // upload from raw_matrix to final matrix; -void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { - matrix_need_update = false; +static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) { debounce_counter_t *debounce_pointer = debounce_counters; for (uint8_t row = 0; row < num_rows; row++) { matrix_row_t existing_row = cooked[row]; @@ -102,11 +123,9 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n // determine new value basd on debounce pointer + raw value if (existing_row != raw_row) { if (*debounce_pointer == DEBOUNCE_ELAPSED) { - *debounce_pointer = current_time; + *debounce_pointer = DEBOUNCE; cooked[row] = raw_row; counters_need_update = true; - } else { - matrix_need_update = true; } } debounce_pointer++; @@ -114,3 +133,6 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n } bool debounce_active(void) { return true; } +#else +# include "none.c" +#endif diff --git a/quantum/debounce/tests/asym_eager_defer_pk_tests.cpp b/quantum/debounce/tests/asym_eager_defer_pk_tests.cpp new file mode 100644 index 0000000000..fe374c3dfa --- /dev/null +++ b/quantum/debounce/tests/asym_eager_defer_pk_tests.cpp @@ -0,0 +1,374 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "gtest/gtest.h" + +#include "debounce_test_common.h" + +TEST_F(DebounceTest, OneKeyShort1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Release key after 1ms delay */ + {1, {{0, 1, UP}}, {}}, + + /* + * Until the eager timer on DOWN is observed to finish, the defer timer + * on UP can't start. There's no workaround for this because it's not + * possible to debounce an event that isn't being tracked. + * + * sym_defer_pk has the same problem but the test has to track that the + * key changed state so the DOWN timer is always allowed to finish + * before starting the UP timer. + */ + {5, {}, {}}, + + {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */ + /* Press key again after 1ms delay */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Release key after 2ms delay */ + {2, {{0, 1, UP}}, {}}, + + {5, {}, {}}, /* See OneKeyShort1 */ + + {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */ + /* Press key again after 1ms delay */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Release key after 3ms delay */ + {3, {{0, 1, UP}}, {}}, + + {5, {}, {}}, /* See OneKeyShort1 */ + + {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */ + /* Press key again after 1ms delay */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Release key after 4ms delay */ + {4, {{0, 1, UP}}, {}}, + + {5, {}, {}}, /* See OneKeyShort1 */ + + {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */ + /* Press key again after 1ms delay */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort5) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Release key after 5ms delay */ + {5, {{0, 1, UP}}, {}}, + + {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */ + /* Press key again after 1ms delay */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort6) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Release key after 6ms delay */ + {6, {{0, 1, UP}}, {}}, + + {11, {}, {{0, 1, UP}}}, /* 5ms after UP at time 6 */ + /* Press key again after 1ms delay */ + {12, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort7) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Release key after 7ms delay */ + {7, {{0, 1, UP}}, {}}, + + {12, {}, {{0, 1, UP}}}, /* 5ms after UP at time 7 */ + /* Press key again after 1ms delay */ + {13, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort8) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Release key after 1ms delay */ + {1, {{0, 1, UP}}, {}}, + + {5, {}, {}}, /* See OneKeyShort1 */ + + {10, {}, {{0, 1, UP}}}, /* 5ms after UP at time 7 */ + /* Press key again after 0ms delay (scan 2) */ + {10, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort9) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Release key after 1ms delay */ + {1, {{0, 1, UP}}, {}}, + + {5, {}, {}}, /* See OneKeyShort1 */ + + /* Press key again after 0ms delay (same scan) before debounce finishes */ + {10, {{0, 1, DOWN}}, {}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {3, {{0, 1, UP}}, {}}, + {4, {{0, 1, DOWN}}, {}}, + {5, {{0, 1, UP}}, {}}, + {6, {{0, 1, DOWN}}, {}}, + {7, {{0, 1, UP}}, {}}, + {8, {{0, 1, DOWN}}, {}}, + {9, {{0, 1, UP}}, {}}, + {10, {{0, 1, DOWN}}, {}}, + {11, {{0, 1, UP}}, {}}, + {12, {{0, 1, DOWN}}, {}}, + {13, {{0, 1, UP}}, {}}, + {14, {{0, 1, DOWN}}, {}}, + {15, {{0, 1, UP}}, {}}, + + {20, {}, {{0, 1, UP}}}, + /* Press key again after 1ms delay */ + {21, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Change twice in the same time period */ + {1, {{0, 1, UP}}, {}}, + {1, {{0, 1, DOWN}}, {}}, + /* Change three times in the same time period */ + {2, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {2, {{0, 1, UP}}, {}}, + /* Change twice in the same time period */ + {6, {{0, 1, DOWN}}, {}}, + {6, {{0, 1, UP}}, {}}, + /* Change three times in the same time period */ + {7, {{0, 1, DOWN}}, {}}, + {7, {{0, 1, UP}}, {}}, + {7, {{0, 1, DOWN}}, {}}, + /* Change twice in the same time period */ + {8, {{0, 1, UP}}, {}}, + {8, {{0, 1, DOWN}}, {}}, + /* Change three times in the same time period */ + {9, {{0, 1, UP}}, {}}, + {9, {{0, 1, DOWN}}, {}}, + {9, {{0, 1, UP}}, {}}, + + {14, {}, {{0, 1, UP}}}, + /* Press key again after 1ms delay */ + {15, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyLong) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + {25, {{0, 1, UP}}, {}}, + + {30, {}, {{0, 1, UP}}}, + + {50, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + {75, {{0, 1, UP}}, {}}, + + {80, {}, {{0, 1, UP}}}, + + {100, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysShort) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 2, DOWN}}, {{0, 2, DOWN}}}, + /* Release key after 2ms delay */ + {2, {{0, 1, UP}}, {}}, + {3, {{0, 2, UP}}, {}}, + + {5, {}, {}}, /* See OneKeyShort1 */ + {6, {}, {}}, /* See OneKeyShort1 */ + + {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */ + /* Press key again after 1ms delay */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}, {0, 2, UP}}}, /* 5ms+5ms after DOWN at time 0 */ + {12, {{0, 2, DOWN}}, {{0, 2, DOWN}}}, /* 5ms+5ms after DOWN at time 0 */ + }); + runEvents(); +} + + +TEST_F(DebounceTest, OneKeyDelayedScan1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late, immediately release key */ + {300, {{0, 1, UP}}, {}}, + + {305, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late, immediately release key */ + {300, {{0, 1, UP}}, {}}, + + /* Processing is very late again */ + {600, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late */ + {300, {}, {}}, + /* Release key after 1ms */ + {301, {{0, 1, UP}}, {}}, + + {306, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late */ + {300, {}, {}}, + /* Release key after 1ms */ + {301, {{0, 1, UP}}, {}}, + + /* Processing is very late again */ + {600, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan5) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + {5, {{0, 1, UP}}, {}}, + + /* Processing is very late */ + {300, {}, {{0, 1, UP}}}, + /* Immediately press key again */ + {300, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan6) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + {5, {{0, 1, UP}}, {}}, + + /* Processing is very late */ + {300, {}, {{0, 1, UP}}}, + + /* Press key again after 1ms */ + {301, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan7) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + {5, {{0, 1, UP}}, {}}, + + /* Press key again before debounce expires */ + {300, {{0, 1, DOWN}}, {}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan8) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is a bit late */ + {50, {}, {}}, + /* Release key after 1ms */ + {51, {{0, 1, UP}}, {}}, + + /* Processing is a bit late again */ + {100, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} diff --git a/quantum/debounce/tests/debounce_test_common.cpp b/quantum/debounce/tests/debounce_test_common.cpp new file mode 100644 index 0000000000..1c5e7c9f4e --- /dev/null +++ b/quantum/debounce/tests/debounce_test_common.cpp @@ -0,0 +1,229 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "gtest/gtest.h" + +#include "debounce_test_common.h" + +#include <algorithm> +#include <iomanip> +#include <sstream> + +extern "C" { +#include "quantum.h" +#include "timer.h" +#include "debounce.h" + +void set_time(uint32_t t); +void advance_time(uint32_t ms); +} + +void DebounceTest::addEvents(std::initializer_list<DebounceTestEvent> events) { + events_.insert(events_.end(), events.begin(), events.end()); +} + +void DebounceTest::runEvents() { + /* Run the test multiple times, from 1kHz to 10kHz scan rate */ + for (extra_iterations_ = 0; extra_iterations_ < 10; extra_iterations_++) { + if (time_jumps_) { + /* Don't advance time smoothly, jump to the next event (some tests require this) */ + auto_advance_time_ = false; + runEventsInternal(); + } else { + /* Run the test with both smooth and irregular time; it must produce the same result */ + auto_advance_time_ = true; + runEventsInternal(); + auto_advance_time_ = false; + runEventsInternal(); + } + } +} + +void DebounceTest::runEventsInternal() { + fast_timer_t previous = 0; + bool first = true; + + /* Initialise keyboard with start time (offset to avoid testing at 0) and all keys UP */ + debounce_init(MATRIX_ROWS); + set_time(time_offset_); + std::fill(std::begin(input_matrix_), std::end(input_matrix_), 0); + std::fill(std::begin(output_matrix_), std::end(output_matrix_), 0); + + for (auto &event : events_) { + if (!auto_advance_time_) { + /* Jump to the next event */ + set_time(time_offset_ + event.time_); + } else if (!first && event.time_ == previous + 1) { + /* This event immediately follows the previous one, don't make extra debounce() calls */ + advance_time(1); + } else { + /* Fast forward to the time for this event, calling debounce() with no changes */ + ASSERT_LT((time_offset_ + event.time_) - timer_read_fast(), 60000) << "Test tries to advance more than 1 minute of time"; + + while (timer_read_fast() != time_offset_ + event.time_) { + runDebounce(false); + checkCookedMatrix(false, "debounce() modified cooked matrix"); + advance_time(1); + } + } + + first = false; + previous = event.time_; + + /* Prepare input matrix */ + for (auto &input : event.inputs_) { + matrixUpdate(input_matrix_, "input", input); + } + + /* Call debounce */ + runDebounce(!event.inputs_.empty()); + + /* Prepare output matrix */ + for (auto &output : event.outputs_) { + matrixUpdate(output_matrix_, "output", output); + } + + /* Check output matrix has expected change events */ + for (auto &output : event.outputs_) { + EXPECT_EQ(!!(cooked_matrix_[output.row_] & (1U << output.col_)), directionValue(output.direction_)) + << "Missing event at " << strTime() + << " expected key " << output.row_ << "," << output.col_ << " " << directionLabel(output.direction_) + << "\ninput_matrix: changed=" << !event.inputs_.empty() << "\n" << strMatrix(input_matrix_) + << "\nexpected_matrix:\n" << strMatrix(output_matrix_) + << "\nactual_matrix:\n" << strMatrix(cooked_matrix_); + } + + /* Check output matrix has no other changes */ + checkCookedMatrix(!event.inputs_.empty(), "debounce() cooked matrix does not match expected output matrix"); + + /* Perform some extra iterations of the matrix scan with no changes */ + for (int i = 0; i < extra_iterations_; i++) { + runDebounce(false); + checkCookedMatrix(false, "debounce() modified cooked matrix"); + } + } + + /* Check that no further changes happen for 1 minute */ + for (int i = 0; i < 60000; i++) { + runDebounce(false); + checkCookedMatrix(false, "debounce() modified cooked matrix"); + advance_time(1); + } + + debounce_free(); +} + +void DebounceTest::runDebounce(bool changed) { + std::copy(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_)); + std::copy(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_)); + + debounce(raw_matrix_, cooked_matrix_, MATRIX_ROWS, changed); + + if (!std::equal(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_))) { + FAIL() << "Fatal error: debounce() modified raw matrix at " << strTime() + << "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_) + << "\nraw_matrix:\n" << strMatrix(raw_matrix_); + } +} + +void DebounceTest::checkCookedMatrix(bool changed, const std::string &error_message) { + if (!std::equal(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_))) { + FAIL() << "Unexpected event: " << error_message << " at " << strTime() + << "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_) + << "\nexpected_matrix:\n" << strMatrix(output_matrix_) + << "\nactual_matrix:\n" << strMatrix(cooked_matrix_); + } +} + +std::string DebounceTest::strTime() { + std::stringstream text; + + text << "time " << (timer_read_fast() - time_offset_) + << " (extra_iterations=" << extra_iterations_ + << ", auto_advance_time=" << auto_advance_time_ << ")"; + + return text.str(); +} + +std::string DebounceTest::strMatrix(matrix_row_t matrix[]) { + std::stringstream text; + + text << "\t" << std::setw(3) << ""; + for (int col = 0; col < MATRIX_COLS; col++) { + text << " " << std::setw(2) << col; + } + text << "\n"; + + for (int row = 0; row < MATRIX_ROWS; row++) { + text << "\t" << std::setw(2) << row << ":"; + for (int col = 0; col < MATRIX_COLS; col++) { + text << ((matrix[row] & (1U << col)) ? " XX" : " __"); + } + + text << "\n"; + } + + return text.str(); +} + +bool DebounceTest::directionValue(Direction direction) { + switch (direction) { + case DOWN: + return true; + + case UP: + return false; + } +} + +std::string DebounceTest::directionLabel(Direction direction) { + switch (direction) { + case DOWN: + return "DOWN"; + + case UP: + return "UP"; + } +} + +/* Modify a matrix and verify that events always specify a change */ +void DebounceTest::matrixUpdate(matrix_row_t matrix[], const std::string &name, const MatrixTestEvent &event) { + ASSERT_NE(!!(matrix[event.row_] & (1U << event.col_)), directionValue(event.direction_)) + << "Test " << name << " at " << strTime() + << " sets key " << event.row_ << "," << event.col_ << " " << directionLabel(event.direction_) + << " but it is already " << directionLabel(event.direction_) + << "\n" << name << "_matrix:\n" << strMatrix(matrix); + + switch (event.direction_) { + case DOWN: + matrix[event.row_] |= (1U << event.col_); + break; + + case UP: + matrix[event.row_] &= ~(1U << event.col_); + break; + } +} + +DebounceTestEvent::DebounceTestEvent(fast_timer_t time, + std::initializer_list<MatrixTestEvent> inputs, + std::initializer_list<MatrixTestEvent> outputs) + : time_(time), inputs_(inputs), outputs_(outputs) { +} + +MatrixTestEvent::MatrixTestEvent(int row, int col, Direction direction) + : row_(row), col_(col), direction_(direction) { +} diff --git a/quantum/debounce/tests/debounce_test_common.h b/quantum/debounce/tests/debounce_test_common.h new file mode 100644 index 0000000000..d87e310594 --- /dev/null +++ b/quantum/debounce/tests/debounce_test_common.h @@ -0,0 +1,83 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "gtest/gtest.h" + +#include <initializer_list> +#include <list> +#include <string> + +extern "C" { +#include "quantum.h" +#include "timer.h" +} + +enum Direction { + DOWN, + UP, +}; + +class MatrixTestEvent { +public: + MatrixTestEvent(int row, int col, Direction direction); + + const int row_; + const int col_; + const Direction direction_; +}; + +class DebounceTestEvent { +public: + // 0, {{0, 1, DOWN}}, {{0, 1, DOWN}}) + DebounceTestEvent(fast_timer_t time, + std::initializer_list<MatrixTestEvent> inputs, + std::initializer_list<MatrixTestEvent> outputs); + + const fast_timer_t time_; + const std::list<MatrixTestEvent> inputs_; + const std::list<MatrixTestEvent> outputs_; +}; + +class DebounceTest : public ::testing::Test { +protected: + void addEvents(std::initializer_list<DebounceTestEvent> events); + void runEvents(); + + fast_timer_t time_offset_ = 7777; + bool time_jumps_ = false; + +private: + static bool directionValue(Direction direction); + static std::string directionLabel(Direction direction); + + void runEventsInternal(); + void runDebounce(bool changed); + void checkCookedMatrix(bool changed, const std::string &error_message); + void matrixUpdate(matrix_row_t matrix[], const std::string &name, const MatrixTestEvent &event); + + std::string strTime(); + std::string strMatrix(matrix_row_t matrix[]); + + std::list<DebounceTestEvent> events_; + + matrix_row_t input_matrix_[MATRIX_ROWS]; + matrix_row_t raw_matrix_[MATRIX_ROWS]; + matrix_row_t cooked_matrix_[MATRIX_ROWS]; + matrix_row_t output_matrix_[MATRIX_ROWS]; + + int extra_iterations_; + bool auto_advance_time_; +}; diff --git a/quantum/debounce/tests/rules.mk b/quantum/debounce/tests/rules.mk new file mode 100644 index 0000000000..66928d7eb6 --- /dev/null +++ b/quantum/debounce/tests/rules.mk @@ -0,0 +1,44 @@ +# Copyright 2021 Simon Arlott +# +# 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/>. + +DEBOUNCE_COMMON_DEFS := -DMATRIX_ROWS=4 -DMATRIX_COLS=10 -DDEBOUNCE=5 + +DEBOUNCE_COMMON_SRC := $(QUANTUM_PATH)/debounce/tests/debounce_test_common.cpp \ + $(TMK_PATH)/common/test/timer.c + +debounce_sym_defer_g_DEFS := $(DEBOUNCE_COMMON_DEFS) +debounce_sym_defer_g_SRC := $(DEBOUNCE_COMMON_SRC) \ + $(QUANTUM_PATH)/debounce/sym_defer_g.c \ + $(QUANTUM_PATH)/debounce/tests/sym_defer_g_tests.cpp + +debounce_sym_defer_pk_DEFS := $(DEBOUNCE_COMMON_DEFS) +debounce_sym_defer_pk_SRC := $(DEBOUNCE_COMMON_SRC) \ + $(QUANTUM_PATH)/debounce/sym_defer_pk.c \ + $(QUANTUM_PATH)/debounce/tests/sym_defer_pk_tests.cpp + +debounce_sym_eager_pk_DEFS := $(DEBOUNCE_COMMON_DEFS) +debounce_sym_eager_pk_SRC := $(DEBOUNCE_COMMON_SRC) \ + $(QUANTUM_PATH)/debounce/sym_eager_pk.c \ + $(QUANTUM_PATH)/debounce/tests/sym_eager_pk_tests.cpp + +debounce_sym_eager_pr_DEFS := $(DEBOUNCE_COMMON_DEFS) +debounce_sym_eager_pr_SRC := $(DEBOUNCE_COMMON_SRC) \ + $(QUANTUM_PATH)/debounce/sym_eager_pr.c \ + $(QUANTUM_PATH)/debounce/tests/sym_eager_pr_tests.cpp + +debounce_asym_eager_defer_pk_DEFS := $(DEBOUNCE_COMMON_DEFS) +debounce_asym_eager_defer_pk_SRC := $(DEBOUNCE_COMMON_SRC) \ + $(QUANTUM_PATH)/debounce/asym_eager_defer_pk.c \ + $(QUANTUM_PATH)/debounce/tests/asym_eager_defer_pk_tests.cpp diff --git a/quantum/debounce/tests/sym_defer_g_tests.cpp b/quantum/debounce/tests/sym_defer_g_tests.cpp new file mode 100644 index 0000000000..a56aecd8f3 --- /dev/null +++ b/quantum/debounce/tests/sym_defer_g_tests.cpp @@ -0,0 +1,223 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "gtest/gtest.h" + +#include "debounce_test_common.h" + +TEST_F(DebounceTest, OneKeyShort1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + /* 0ms delay (fast scan rate) */ + {5, {{0, 1, UP}}, {}}, + + {10, {}, {{0, 1, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + /* 1ms delay */ + {6, {{0, 1, UP}}, {}}, + + {11, {}, {{0, 1, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + /* 2ms delay */ + {7, {{0, 1, UP}}, {}}, + + {12, {}, {{0, 1, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyTooQuick1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + /* Release key exactly on the debounce time */ + {5, {{0, 1, UP}}, {}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyTooQuick2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + {6, {{0, 1, UP}}, {}}, + + /* Press key exactly on the debounce time */ + {11, {{0, 1, DOWN}}, {}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {1, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {3, {{0, 1, UP}}, {}}, + {4, {{0, 1, DOWN}}, {}}, + {5, {{0, 1, UP}}, {}}, + {6, {{0, 1, DOWN}}, {}}, + {11, {}, {{0, 1, DOWN}}}, /* 5ms after DOWN at time 7 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {5, {}, {{0, 1, DOWN}}}, + {6, {{0, 1, UP}}, {}}, + {7, {{0, 1, DOWN}}, {}}, + {8, {{0, 1, UP}}, {}}, + {9, {{0, 1, DOWN}}, {}}, + {10, {{0, 1, UP}}, {}}, + {15, {}, {{0, 1, UP}}}, /* 5ms after UP at time 10 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyLong) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + + {25, {{0, 1, UP}}, {}}, + + {30, {}, {{0, 1, UP}}}, + + {50, {{0, 1, DOWN}}, {}}, + + {55, {}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysShort) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {1, {{0, 2, DOWN}}, {}}, + + {6, {}, {{0, 1, DOWN}, {0, 2, DOWN}}}, + + {7, {{0, 1, UP}}, {}}, + {8, {{0, 2, UP}}, {}}, + + {13, {}, {{0, 1, UP}, {0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysSimultaneous1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}, {0, 2, DOWN}}}, + {6, {{0, 1, UP}, {0, 2, UP}}, {}}, + + {11, {}, {{0, 1, UP}, {0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysSimultaneous2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {1, {{0, 2, DOWN}}, {}}, + + {5, {}, {}}, + {6, {}, {{0, 1, DOWN}, {0, 2, DOWN}}}, + {7, {{0, 1, UP}}, {}}, + {8, {{0, 2, UP}}, {}}, + + {13, {}, {{0, 1, UP}, {0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Processing is very late */ + {300, {}, {{0, 1, DOWN}}}, + /* Immediately release key */ + {300, {{0, 1, UP}}, {}}, + + {305, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Processing is very late */ + {300, {}, {{0, 1, DOWN}}}, + /* Release key after 1ms */ + {301, {{0, 1, UP}}, {}}, + + {306, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Release key before debounce expires */ + {300, {{0, 1, UP}}, {}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Processing is a bit late */ + {50, {}, {{0, 1, DOWN}}}, + /* Release key after 1ms */ + {51, {{0, 1, UP}}, {}}, + + {56, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} diff --git a/quantum/debounce/tests/sym_defer_pk_tests.cpp b/quantum/debounce/tests/sym_defer_pk_tests.cpp new file mode 100644 index 0000000000..1f3061e59c --- /dev/null +++ b/quantum/debounce/tests/sym_defer_pk_tests.cpp @@ -0,0 +1,225 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "gtest/gtest.h" + +#include "debounce_test_common.h" + +TEST_F(DebounceTest, OneKeyShort1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + /* 0ms delay (fast scan rate) */ + {5, {{0, 1, UP}}, {}}, + + {10, {}, {{0, 1, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + /* 1ms delay */ + {6, {{0, 1, UP}}, {}}, + + {11, {}, {{0, 1, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + /* 2ms delay */ + {7, {{0, 1, UP}}, {}}, + + {12, {}, {{0, 1, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyTooQuick1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + /* Release key exactly on the debounce time */ + {5, {{0, 1, UP}}, {}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyTooQuick2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + {6, {{0, 1, UP}}, {}}, + + /* Press key exactly on the debounce time */ + {11, {{0, 1, DOWN}}, {}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {1, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {3, {{0, 1, UP}}, {}}, + {4, {{0, 1, DOWN}}, {}}, + {5, {{0, 1, UP}}, {}}, + {6, {{0, 1, DOWN}}, {}}, + {11, {}, {{0, 1, DOWN}}}, /* 5ms after DOWN at time 7 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {5, {}, {{0, 1, DOWN}}}, + {6, {{0, 1, UP}}, {}}, + {7, {{0, 1, DOWN}}, {}}, + {8, {{0, 1, UP}}, {}}, + {9, {{0, 1, DOWN}}, {}}, + {10, {{0, 1, UP}}, {}}, + {15, {}, {{0, 1, UP}}}, /* 5ms after UP at time 10 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyLong) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + + {25, {{0, 1, UP}}, {}}, + + {30, {}, {{0, 1, UP}}}, + + {50, {{0, 1, DOWN}}, {}}, + + {55, {}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysShort) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {1, {{0, 2, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + {6, {}, {{0, 2, DOWN}}}, + + {7, {{0, 1, UP}}, {}}, + {8, {{0, 2, UP}}, {}}, + + {12, {}, {{0, 1, UP}}}, + {13, {}, {{0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysSimultaneous1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}, {0, 2, DOWN}}}, + {6, {{0, 1, UP}, {0, 2, UP}}, {}}, + + {11, {}, {{0, 1, UP}, {0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysSimultaneous2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + {1, {{0, 2, DOWN}}, {}}, + + {5, {}, {{0, 1, DOWN}}}, + {6, {{0, 1, UP}}, {{0, 2, DOWN}}}, + {7, {{0, 2, UP}}, {}}, + + {11, {}, {{0, 1, UP}}}, + {12, {}, {{0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Processing is very late */ + {300, {}, {{0, 1, DOWN}}}, + /* Immediately release key */ + {300, {{0, 1, UP}}, {}}, + + {305, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Processing is very late */ + {300, {}, {{0, 1, DOWN}}}, + /* Release key after 1ms */ + {301, {{0, 1, UP}}, {}}, + + {306, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Release key before debounce expires */ + {300, {{0, 1, UP}}, {}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {}}, + + /* Processing is a bit late */ + {50, {}, {{0, 1, DOWN}}}, + /* Release key after 1ms */ + {51, {{0, 1, UP}}, {}}, + + {56, {}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} diff --git a/quantum/debounce/tests/sym_eager_pk_tests.cpp b/quantum/debounce/tests/sym_eager_pk_tests.cpp new file mode 100644 index 0000000000..e0fc205e33 --- /dev/null +++ b/quantum/debounce/tests/sym_eager_pk_tests.cpp @@ -0,0 +1,237 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "gtest/gtest.h" + +#include "debounce_test_common.h" + +TEST_F(DebounceTest, OneKeyShort1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 2ms delay (debounce has not yet finished) */ + {7, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 3ms delay (debounce has not yet finished) */ + {8, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 4ms delay (debounce has not yet finished) */ + {9, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort5) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 5ms delay (debounce has finished) */ + {10, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort6) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key after after 6ms delay (debounce has finished) */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {3, {{0, 1, UP}}, {}}, + {4, {{0, 1, DOWN}}, {}}, + {5, {{0, 1, UP}}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Change twice in the same time period */ + {1, {{0, 1, UP}}, {}}, + {1, {{0, 1, DOWN}}, {}}, + /* Change three times in the same time period */ + {2, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {2, {{0, 1, UP}}, {}}, + /* Change three times in the same time period */ + {3, {{0, 1, DOWN}}, {}}, + {3, {{0, 1, UP}}, {}}, + {3, {{0, 1, DOWN}}, {}}, + /* Change twice in the same time period */ + {4, {{0, 1, UP}}, {}}, + {4, {{0, 1, DOWN}}, {}}, + {5, {{0, 1, UP}}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyLong) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + {25, {{0, 1, UP}}, {{0, 1, UP}}}, + + {50, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysShort) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + {2, {{0, 2, DOWN}}, {{0, 2, DOWN}}}, + {3, {{0, 2, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {7, {}, {{0, 2, UP}}}, + + /* Press key again after 1ms delay (debounce has not yet finished) */ + {9, {{0, 2, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + + {12, {}, {{0, 2, DOWN}}}, /* 5ms after UP at time 7 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted */ + {300, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1 scan delay */ + {300, {}, {}}, + {300, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1ms delay */ + {300, {}, {}}, + {301, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is a bit late but the change will now be accepted */ + {50, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan5) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1 scan delay */ + {50, {}, {}}, + {50, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan6) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1ms delay */ + {50, {}, {}}, + {51, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} diff --git a/quantum/debounce/tests/sym_eager_pr_tests.cpp b/quantum/debounce/tests/sym_eager_pr_tests.cpp new file mode 100644 index 0000000000..2c4bca127e --- /dev/null +++ b/quantum/debounce/tests/sym_eager_pr_tests.cpp @@ -0,0 +1,280 @@ +/* Copyright 2021 Simon Arlott + * + * 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 "gtest/gtest.h" + +#include "debounce_test_common.h" + +TEST_F(DebounceTest, OneKeyShort1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 2ms delay (debounce has not yet finished) */ + {7, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 3ms delay (debounce has not yet finished) */ + {8, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 4ms delay (debounce has not yet finished) */ + {9, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort5) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 5ms delay (debounce has finished) */ + {10, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyShort6) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key after after 6ms delay (debounce has finished) */ + {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {3, {{0, 1, UP}}, {}}, + {4, {{0, 1, DOWN}}, {}}, + {5, {{0, 1, UP}}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyBouncing2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + /* Change twice in the same time period */ + {1, {{0, 1, UP}}, {}}, + {1, {{0, 1, DOWN}}, {}}, + /* Change three times in the same time period */ + {2, {{0, 1, UP}}, {}}, + {2, {{0, 1, DOWN}}, {}}, + {2, {{0, 1, UP}}, {}}, + /* Change three times in the same time period */ + {3, {{0, 1, DOWN}}, {}}, + {3, {{0, 1, UP}}, {}}, + {3, {{0, 1, DOWN}}, {}}, + /* Change twice in the same time period */ + {4, {{0, 1, UP}}, {}}, + {4, {{0, 1, DOWN}}, {}}, + {5, {{0, 1, UP}}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyLong) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + {25, {{0, 1, UP}}, {{0, 1, UP}}}, + + {50, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoRowsShort) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + {2, {{2, 0, DOWN}}, {{2, 0, DOWN}}}, + {3, {{2, 0, UP}}, {}}, + + {5, {}, {{0, 1, UP}}}, + /* Press key again after 1ms delay (debounce has not yet finished) */ + {6, {{0, 1, DOWN}}, {}}, + {7, {}, {{2, 0, UP}}}, + + /* Press key again after 1ms delay (debounce has not yet finished) */ + {9, {{2, 0, DOWN}}, {}}, + {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */ + + {12, {}, {{2, 0, DOWN}}}, /* 5ms after UP at time 7 */ + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysOverlap) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + {1, {{0, 1, UP}}, {}}, + /* Press a second key during the first debounce */ + {2, {{0, 2, DOWN}}, {}}, + + /* Key registers as soon as debounce finishes, 5ms after time 0 */ + {5, {}, {{0, 1, UP}, {0, 2, DOWN}}}, + {6, {{0, 1, DOWN}}, {}}, + + /* Key registers as soon as debounce finishes, 5ms after time 5 */ + {10, {}, {{0, 1, DOWN}}}, + /* Release both keys */ + {11, {{0, 1, UP}}, {}}, + {12, {{0, 2, UP}}, {}}, + + /* Keys register as soon as debounce finishes, 5ms after time 10 */ + {15, {}, {{0, 1, UP}, {0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysSimultaneous1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {{0, 1, DOWN}, {0, 2, DOWN}}}, + {20, {{0, 1, UP}}, {{0, 1, UP}}}, + {21, {{0, 2, UP}}, {}}, + + /* Key registers as soon as debounce finishes, 5ms after time 20 */ + {25, {}, {{0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, TwoKeysSimultaneous2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {{0, 1, DOWN}, {0, 2, DOWN}}}, + {20, {{0, 1, UP}, {0, 2, UP}}, {{0, 1, UP}, {0, 2, UP}}}, + }); + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan1) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted */ + {300, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan2) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1 scan delay */ + {300, {}, {}}, + {300, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan3) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1ms delay */ + {300, {}, {}}, + {301, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan4) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is a bit late but the change will now be accepted */ + {50, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan5) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1 scan delay */ + {50, {}, {}}, + {50, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} + +TEST_F(DebounceTest, OneKeyDelayedScan6) { + addEvents({ /* Time, Inputs, Outputs */ + {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}}, + + /* Processing is very late but the change will now be accepted even with a 1ms delay */ + {50, {}, {}}, + {51, {{0, 1, UP}}, {{0, 1, UP}}}, + }); + time_jumps_ = true; + runEvents(); +} diff --git a/quantum/debounce/tests/testlist.mk b/quantum/debounce/tests/testlist.mk new file mode 100644 index 0000000000..c54c45aa63 --- /dev/null +++ b/quantum/debounce/tests/testlist.mk @@ -0,0 +1,6 @@ +TEST_LIST += \ + debounce_sym_defer_g \ + debounce_sym_defer_pk \ + debounce_sym_eager_pk \ + debounce_sym_eager_pr \ + debounce_asym_eager_defer_pk diff --git a/quantum/led_matrix.c b/quantum/led_matrix.c index 7e0fdf896a..e7e933e1dd 100644 --- a/quantum/led_matrix.c +++ b/quantum/led_matrix.c @@ -33,6 +33,14 @@ const led_point_t k_led_matrix_center = {112, 32}; const led_point_t k_led_matrix_center = LED_MATRIX_CENTER; #endif +// clang-format off +#ifndef LED_MATRIX_IMMEDIATE_EEPROM +# define led_eeconfig_update(v) led_update_eeprom |= v +#else +# define led_eeconfig_update(v) if (v) eeconfig_update_led_matrix() +#endif +// clang-format on + // Generic effect runners #include "led_matrix_runners/effect_runner_dx_dy_dist.h" #include "led_matrix_runners/effect_runner_dx_dy.h" @@ -67,10 +75,6 @@ const led_point_t k_led_matrix_center = LED_MATRIX_CENTER; # define LED_DISABLE_TIMEOUT 0 #endif -#if LED_DISABLE_WHEN_USB_SUSPENDED != 1 -# undef LED_DISABLE_WHEN_USB_SUSPENDED -#endif - #if !defined(LED_MATRIX_MAXIMUM_BRIGHTNESS) || LED_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX # undef LED_MATRIX_MAXIMUM_BRIGHTNESS # define LED_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX @@ -108,6 +112,7 @@ last_hit_t g_last_hit_tracker; // internals static bool suspend_state = false; +static bool led_update_eeprom = false; static uint8_t led_last_enable = UINT8_MAX; static uint8_t led_last_effect = UINT8_MAX; static effect_params_t led_effect_params = {0, LED_FLAG_ALL, false}; @@ -280,6 +285,7 @@ static void led_task_timers(void) { static void led_task_sync(void) { // next task + if (led_update_eeprom) eeconfig_update_led_matrix(); if (sync_timer_elapsed32(g_led_timer) >= LED_MATRIX_LED_FLUSH_LIMIT) led_task_state = STARTING; } @@ -469,9 +475,7 @@ bool led_matrix_get_suspend_state(void) { return suspend_state; } void led_matrix_toggle_eeprom_helper(bool write_to_eeprom) { led_matrix_eeconfig.enable ^= 1; led_task_state = STARTING; - if (write_to_eeprom) { - eeconfig_update_led_matrix(); - } + led_eeconfig_update(write_to_eeprom); dprintf("led matrix toggle [%s]: led_matrix_eeconfig.enable = %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.enable); } void led_matrix_toggle_noeeprom(void) { led_matrix_toggle_eeprom_helper(false); } @@ -479,7 +483,7 @@ void led_matrix_toggle(void) { led_matrix_toggle_eeprom_helper(true); } void led_matrix_enable(void) { led_matrix_enable_noeeprom(); - eeconfig_update_led_matrix(); + led_eeconfig_update(true); } void led_matrix_enable_noeeprom(void) { @@ -489,7 +493,7 @@ void led_matrix_enable_noeeprom(void) { void led_matrix_disable(void) { led_matrix_disable_noeeprom(); - eeconfig_update_led_matrix(); + led_eeconfig_update(true); } void led_matrix_disable_noeeprom(void) { @@ -511,9 +515,7 @@ void led_matrix_mode_eeprom_helper(uint8_t mode, bool write_to_eeprom) { led_matrix_eeconfig.mode = mode; } led_task_state = STARTING; - if (write_to_eeprom) { - eeconfig_update_led_matrix(); - } + led_eeconfig_update(write_to_eeprom); dprintf("led matrix mode [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.mode); } void led_matrix_mode_noeeprom(uint8_t mode) { led_matrix_mode_eeprom_helper(mode, false); } @@ -540,9 +542,7 @@ void led_matrix_set_val_eeprom_helper(uint8_t val, bool write_to_eeprom) { return; } led_matrix_eeconfig.val = (val > LED_MATRIX_MAXIMUM_BRIGHTNESS) ? LED_MATRIX_MAXIMUM_BRIGHTNESS : val; - if (write_to_eeprom) { - eeconfig_update_led_matrix(); - } + led_eeconfig_update(write_to_eeprom); dprintf("led matrix set val [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.val); } void led_matrix_set_val_noeeprom(uint8_t val) { led_matrix_set_val_eeprom_helper(val, false); } @@ -560,9 +560,7 @@ void led_matrix_decrease_val(void) { led_matrix_decrease_val_helper(true); } void led_matrix_set_speed_eeprom_helper(uint8_t speed, bool write_to_eeprom) { led_matrix_eeconfig.speed = speed; - if (write_to_eeprom) { - eeconfig_update_led_matrix(); - } + led_eeconfig_update(write_to_eeprom); dprintf("led matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.speed); } void led_matrix_set_speed_noeeprom(uint8_t speed) { led_matrix_set_speed_eeprom_helper(speed, false); } diff --git a/quantum/matrix.c b/quantum/matrix.c index 34d6af2e6d..71ef270892 100644 --- a/quantum/matrix.c +++ b/quantum/matrix.c @@ -16,6 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <stdint.h> #include <stdbool.h> +#include <string.h> #include "util.h" #include "matrix.h" #include "debounce.h" @@ -24,14 +25,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #ifdef DIRECT_PINS static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS; #elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW) +# ifdef MATRIX_ROW_PINS static const pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; +# endif // MATRIX_ROW_PINS +# ifdef MATRIX_COL_PINS static const pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; +# endif // MATRIX_COL_PINS #endif /* matrix state(1:on, 0:off) */ extern matrix_row_t raw_matrix[MATRIX_ROWS]; // raw values extern matrix_row_t matrix[MATRIX_ROWS]; // debounced values +// user-defined overridable functions +__attribute__((weak)) void matrix_init_pins(void); +__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row); +__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col); + static inline void setPinOutput_writeLow(pin_t pin) { ATOMIC_BLOCK_FORCEON { setPinOutput(pin); @@ -47,7 +57,7 @@ static inline void setPinInputHigh_atomic(pin_t pin) { #ifdef DIRECT_PINS -static void init_pins(void) { +__attribute__((weak)) void matrix_init_pins(void) { for (int row = 0; row < MATRIX_ROWS; row++) { for (int col = 0; col < MATRIX_COLS; col++) { pin_t pin = direct_pins[row][col]; @@ -58,7 +68,7 @@ static void init_pins(void) { } } -static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { +__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { // Start with a clear matrix row matrix_row_t current_row_value = 0; @@ -69,16 +79,13 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) } } - // If the row has changed, store the row and return the changed flag. - if (current_matrix[current_row] != current_row_value) { - current_matrix[current_row] = current_row_value; - return true; - } - return false; + // Update the matrix + current_matrix[current_row] = current_row_value; } #elif defined(DIODE_DIRECTION) -# if (DIODE_DIRECTION == COL2ROW) +# if defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS) +# if (DIODE_DIRECTION == COL2ROW) static void select_row(uint8_t row) { setPinOutput_writeLow(row_pins[row]); } @@ -90,14 +97,14 @@ static void unselect_rows(void) { } } -static void init_pins(void) { +__attribute__((weak)) void matrix_init_pins(void) { unselect_rows(); for (uint8_t x = 0; x < MATRIX_COLS; x++) { setPinInputHigh_atomic(col_pins[x]); } } -static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { +__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { // Start with a clear matrix row matrix_row_t current_row_value = 0; @@ -118,15 +125,11 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) unselect_row(current_row); matrix_output_unselect_delay(); // wait for all Col signals to go HIGH - // If the row has changed, store the row and return the changed flag. - if (current_matrix[current_row] != current_row_value) { - current_matrix[current_row] = current_row_value; - return true; - } - return false; + // Update the matrix + current_matrix[current_row] = current_row_value; } -# elif (DIODE_DIRECTION == ROW2COL) +# elif (DIODE_DIRECTION == ROW2COL) static void select_col(uint8_t col) { setPinOutput_writeLow(col_pins[col]); } @@ -138,59 +141,46 @@ static void unselect_cols(void) { } } -static void init_pins(void) { +__attribute__((weak)) void matrix_init_pins(void) { unselect_cols(); for (uint8_t x = 0; x < MATRIX_ROWS; x++) { setPinInputHigh_atomic(row_pins[x]); } } -static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { - bool matrix_changed = false; - +__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { // Select col select_col(current_col); matrix_output_select_delay(); // For each row... for (uint8_t row_index = 0; row_index < MATRIX_ROWS; row_index++) { - // Store last value of row prior to reading - matrix_row_t last_row_value = current_matrix[row_index]; - matrix_row_t current_row_value = last_row_value; - // Check row pin state if (readPin(row_pins[row_index]) == 0) { // Pin LO, set col bit - current_row_value |= (MATRIX_ROW_SHIFTER << current_col); + current_matrix[row_index] |= (MATRIX_ROW_SHIFTER << current_col); } else { // Pin HI, clear col bit - current_row_value &= ~(MATRIX_ROW_SHIFTER << current_col); - } - - // Determine if the matrix changed state - if ((last_row_value != current_row_value)) { - matrix_changed |= true; - current_matrix[row_index] = current_row_value; + current_matrix[row_index] &= ~(MATRIX_ROW_SHIFTER << current_col); } } // Unselect col unselect_col(current_col); matrix_output_unselect_delay(); // wait for all Row signals to go HIGH - - return matrix_changed; } -# else -# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! -# endif +# else +# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! +# endif +# endif // defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS) #else # error DIODE_DIRECTION is not defined! #endif void matrix_init(void) { // initialize key pins - init_pins(); + matrix_init_pins(); // initialize matrix state: all keys off for (uint8_t i = 0; i < MATRIX_ROWS; i++) { @@ -204,20 +194,23 @@ void matrix_init(void) { } uint8_t matrix_scan(void) { - bool changed = false; + matrix_row_t curr_matrix[MATRIX_ROWS] = {0}; #if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW) // Set row, read cols for (uint8_t current_row = 0; current_row < MATRIX_ROWS; current_row++) { - changed |= read_cols_on_row(raw_matrix, current_row); + matrix_read_cols_on_row(curr_matrix, current_row); } #elif (DIODE_DIRECTION == ROW2COL) // Set col, read rows for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) { - changed |= read_rows_on_col(raw_matrix, current_col); + matrix_read_rows_on_col(curr_matrix, current_col); } #endif + bool changed = memcmp(raw_matrix, curr_matrix, sizeof(curr_matrix)) != 0; + if (changed) memcpy(raw_matrix, curr_matrix, sizeof(curr_matrix)); + debounce(raw_matrix, matrix, MATRIX_ROWS, changed); matrix_scan_quantum(); diff --git a/quantum/mcu_selection.mk b/quantum/mcu_selection.mk index 9268c4522e..f8def7f674 100644 --- a/quantum/mcu_selection.mk +++ b/quantum/mcu_selection.mk @@ -136,10 +136,6 @@ ifneq ($(findstring STM32F042, $(MCU)),) USE_FPU ?= no - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32F0 endif @@ -172,10 +168,6 @@ ifneq ($(findstring STM32F072, $(MCU)),) USE_FPU ?= no - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32F0 endif @@ -208,10 +200,6 @@ ifneq ($(findstring STM32F103, $(MCU)),) USE_FPU ?= no - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32F1 endif @@ -244,10 +232,6 @@ ifneq ($(findstring STM32F303, $(MCU)),) USE_FPU ?= yes - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32F3 endif @@ -280,10 +264,6 @@ ifneq ($(findstring STM32F401, $(MCU)),) USE_FPU ?= yes - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32F4 endif @@ -321,10 +301,6 @@ ifneq ($(findstring STM32F411, $(MCU)),) USE_FPU ?= yes - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32F4 endif @@ -357,10 +333,6 @@ ifneq ($(findstring STM32F446, $(MCU)),) BOARD ?= GENERIC_STM32_F446XE USE_FPU ?= yes - - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 endif ifneq ($(findstring STM32G431, $(MCU)),) @@ -391,10 +363,6 @@ ifneq ($(findstring STM32G431, $(MCU)),) USE_FPU ?= yes - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32G4 endif @@ -427,10 +395,6 @@ ifneq ($(findstring STM32G474, $(MCU)),) USE_FPU ?= yes - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32G4 endif @@ -465,10 +429,6 @@ ifneq (,$(filter $(MCU),STM32L433 STM32L443)) USE_FPU ?= yes - # Options to pass to dfu-util when flashing - DFU_ARGS ?= -d 0483:DF11 -a 0 -s 0x08000000:leave - DFU_SUFFIX_ARGS ?= -v 0483 -p DF11 - # UF2 settings UF2_FAMILY ?= STM32L4 endif diff --git a/quantum/quantum.h b/quantum/quantum.h index e4a7c5723c..66ba96fde8 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -176,6 +176,10 @@ extern layer_state_t layer_state; # include "oled_driver.h" #endif +#ifdef ST7565_ENABLE +# include "st7565.h" +#endif + #ifdef DIP_SWITCH_ENABLE # include "dip_switch.h" #endif diff --git a/quantum/rgb_matrix.c b/quantum/rgb_matrix.c index ab8dbd849b..3c5ddba939 100644 --- a/quantum/rgb_matrix.c +++ b/quantum/rgb_matrix.c @@ -31,6 +31,14 @@ const led_point_t k_rgb_matrix_center = {112, 32}; const led_point_t k_rgb_matrix_center = RGB_MATRIX_CENTER; #endif +// clang-format off +#ifndef RGB_MATRIX_IMMEDIATE_EEPROM +# define rgb_eeconfig_update(v) rgb_update_eeprom |= v +#else +# define rgb_eeconfig_update(v) if (v) eeconfig_update_rgb_matrix() +#endif +// clang-format on + __attribute__((weak)) RGB rgb_matrix_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv); } // Generic effect runners @@ -67,10 +75,6 @@ __attribute__((weak)) RGB rgb_matrix_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv # define RGB_DISABLE_TIMEOUT 0 #endif -#if RGB_DISABLE_WHEN_USB_SUSPENDED != 1 -# undef RGB_DISABLE_WHEN_USB_SUSPENDED -#endif - #if !defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS) || RGB_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX # undef RGB_MATRIX_MAXIMUM_BRIGHTNESS # define RGB_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX @@ -129,6 +133,7 @@ last_hit_t g_last_hit_tracker; // internals static bool suspend_state = false; +static bool rgb_update_eeprom = false; static uint8_t rgb_last_enable = UINT8_MAX; static uint8_t rgb_last_effect = UINT8_MAX; static effect_params_t rgb_effect_params = {0, LED_FLAG_ALL, false}; @@ -315,6 +320,7 @@ static void rgb_task_timers(void) { static void rgb_task_sync(void) { // next task + if (rgb_update_eeprom) eeconfig_update_rgb_matrix(); if (sync_timer_elapsed32(g_rgb_timer) >= RGB_MATRIX_LED_FLUSH_LIMIT) rgb_task_state = STARTING; } @@ -511,9 +517,7 @@ bool rgb_matrix_get_suspend_state(void) { return suspend_state; } void rgb_matrix_toggle_eeprom_helper(bool write_to_eeprom) { rgb_matrix_config.enable ^= 1; rgb_task_state = STARTING; - if (write_to_eeprom) { - eeconfig_update_rgb_matrix(); - } + rgb_eeconfig_update(write_to_eeprom); dprintf("rgb matrix toggle [%s]: rgb_matrix_config.enable = %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.enable); } void rgb_matrix_toggle_noeeprom(void) { rgb_matrix_toggle_eeprom_helper(false); } @@ -521,7 +525,7 @@ void rgb_matrix_toggle(void) { rgb_matrix_toggle_eeprom_helper(true); } void rgb_matrix_enable(void) { rgb_matrix_enable_noeeprom(); - eeconfig_update_rgb_matrix(); + rgb_eeconfig_update(true); } void rgb_matrix_enable_noeeprom(void) { @@ -531,7 +535,7 @@ void rgb_matrix_enable_noeeprom(void) { void rgb_matrix_disable(void) { rgb_matrix_disable_noeeprom(); - eeconfig_update_rgb_matrix(); + rgb_eeconfig_update(true); } void rgb_matrix_disable_noeeprom(void) { @@ -553,9 +557,7 @@ void rgb_matrix_mode_eeprom_helper(uint8_t mode, bool write_to_eeprom) { rgb_matrix_config.mode = mode; } rgb_task_state = STARTING; - if (write_to_eeprom) { - eeconfig_update_rgb_matrix(); - } + rgb_eeconfig_update(write_to_eeprom); dprintf("rgb matrix mode [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.mode); } void rgb_matrix_mode_noeeprom(uint8_t mode) { rgb_matrix_mode_eeprom_helper(mode, false); } @@ -584,9 +586,7 @@ void rgb_matrix_sethsv_eeprom_helper(uint16_t hue, uint8_t sat, uint8_t val, boo rgb_matrix_config.hsv.h = hue; rgb_matrix_config.hsv.s = sat; rgb_matrix_config.hsv.v = (val > RGB_MATRIX_MAXIMUM_BRIGHTNESS) ? RGB_MATRIX_MAXIMUM_BRIGHTNESS : val; - if (write_to_eeprom) { - eeconfig_update_rgb_matrix(); - } + rgb_eeconfig_update(write_to_eeprom); dprintf("rgb matrix set hsv [%s]: %u,%u,%u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.hsv.h, rgb_matrix_config.hsv.s, rgb_matrix_config.hsv.v); } void rgb_matrix_sethsv_noeeprom(uint16_t hue, uint8_t sat, uint8_t val) { rgb_matrix_sethsv_eeprom_helper(hue, sat, val, false); } @@ -623,9 +623,7 @@ void rgb_matrix_decrease_val(void) { rgb_matrix_decrease_val_helper(true); } void rgb_matrix_set_speed_eeprom_helper(uint8_t speed, bool write_to_eeprom) { rgb_matrix_config.speed = speed; - if (write_to_eeprom) { - eeconfig_update_rgb_matrix(); - } + rgb_eeconfig_update(write_to_eeprom); dprintf("rgb matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.speed); } void rgb_matrix_set_speed_noeeprom(uint8_t speed) { rgb_matrix_set_speed_eeprom_helper(speed, false); } diff --git a/quantum/rgb_matrix.h b/quantum/rgb_matrix.h index a615b8422c..741a2fe446 100644 --- a/quantum/rgb_matrix.h +++ b/quantum/rgb_matrix.h @@ -33,6 +33,8 @@ # include "is31fl3737.h" #elif defined(IS31FL3741) # include "is31fl3741.h" +#elif defined(AW20216) +# include "aw20216.h" #elif defined(WS2812) # include "ws2812.h" #endif diff --git a/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h b/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h index 9493b38508..a17e954b1b 100644 --- a/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h +++ b/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h @@ -4,7 +4,7 @@ RGB_MATRIX_EFFECT(JELLYBEAN_RAINDROPS) static void jellybean_raindrops_set_color(int i, effect_params_t* params) { if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) return; - HSV hsv = {rand() & 0xFF, rand() & 0xFF, rgb_matrix_config.hsv.v}; + HSV hsv = {rand() & 0xFF, qadd8(rand() & 0x7F, 0x80), rgb_matrix_config.hsv.v}; RGB rgb = rgb_matrix_hsv_to_rgb(hsv); rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); } diff --git a/quantum/rgb_matrix_drivers.c b/quantum/rgb_matrix_drivers.c index 896fa6d0ef..6a11d4791e 100644 --- a/quantum/rgb_matrix_drivers.c +++ b/quantum/rgb_matrix_drivers.c @@ -171,6 +171,22 @@ const rgb_matrix_driver_t rgb_matrix_driver = { }; # endif +#elif defined(AW20216) +# include "spi_master.h" +static void init(void) { + spi_init(); + AW20216_init(); +} + +static void flush(void) { AW20216_update_pwm_buffers(); } + +const rgb_matrix_driver_t rgb_matrix_driver = { + .init = init, + .flush = flush, + .set_color = AW20216_set_color, + .set_color_all = AW20216_set_color_all, +}; + #elif defined(WS2812) # if defined(RGBLIGHT_ENABLE) && !defined(RGBLIGHT_CUSTOM_DRIVER) # pragma message "Cannot use RGBLIGHT and RGB Matrix using WS2812 at the same time." diff --git a/quantum/serial_link/system/serial_link.c b/quantum/serial_link/system/serial_link.c index f77483ad8c..6363f8ff3b 100644 --- a/quantum/serial_link/system/serial_link.c +++ b/quantum/serial_link/system/serial_link.c @@ -29,10 +29,13 @@ SOFTWARE. #include "serial_link/protocol/transport.h" #include "serial_link/protocol/frame_router.h" #include "matrix.h" +#include "sync_timer.h" #include <stdbool.h> #include "print.h" #include "config.h" +#define SYNC_TIMER_OFFSET 2 + static event_source_t new_data_event; static bool serial_link_connected; static bool is_master = false; @@ -159,10 +162,16 @@ static matrix_object_t last_matrix = {}; SLAVE_TO_MASTER_OBJECT(keyboard_matrix, matrix_object_t); MASTER_TO_ALL_SLAVES_OBJECT(serial_link_connected, bool); +#ifndef DISABLE_SYNC_TIMER +MASTER_TO_ALL_SLAVES_OBJECT(sync_timer, uint32_t); +#endif static remote_object_t* remote_objects[] = { REMOTE_OBJECT(serial_link_connected), REMOTE_OBJECT(keyboard_matrix), +#ifndef DISABLE_SYNC_TIMER + REMOTE_OBJECT(sync_timer), +#endif }; void init_serial_link(void) { @@ -200,14 +209,27 @@ void serial_link_update(void) { m->rows[i] = matrix.rows[i]; } end_write_keyboard_matrix(); + *begin_write_serial_link_connected() = true; end_write_serial_link_connected(); + +#ifndef DISABLE_SYNC_TIMER + *begin_write_sync_timer() = sync_timer_read32() + SYNC_TIMER_OFFSET; + end_write_sync_timer(); +#endif } matrix_object_t* m = read_keyboard_matrix(0); if (m) { matrix_set_remote(m->rows, 0); } + +#ifndef DISABLE_SYNC_TIMER + uint32_t* t = read_sync_timer(); + if (t) { + sync_timer_update(*t); + } +#endif } void signal_data_written(void) { chEvtBroadcast(&new_data_event); } diff --git a/quantum/split_common/matrix.c b/quantum/split_common/matrix.c index 039e7d9773..56d91b07fe 100644 --- a/quantum/split_common/matrix.c +++ b/quantum/split_common/matrix.c @@ -16,23 +16,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <stdint.h> #include <stdbool.h> +#include <string.h> #include "util.h" #include "matrix.h" #include "debounce.h" #include "quantum.h" #include "split_util.h" #include "config.h" -#include "transport.h" +#include "transactions.h" -#define ERROR_DISCONNECT_COUNT 5 +#ifndef ERROR_DISCONNECT_COUNT +# define ERROR_DISCONNECT_COUNT 5 +#endif // ERROR_DISCONNECT_COUNT #define ROWS_PER_HAND (MATRIX_ROWS / 2) #ifdef DIRECT_PINS static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS; #elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW) +# ifdef MATRIX_ROW_PINS static pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; +# endif // MATRIX_ROW_PINS +# ifdef MATRIX_COL_PINS static pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; +# endif // MATRIX_COL_PINS #endif /* matrix state(1:on, 0:off) */ @@ -45,6 +52,9 @@ uint8_t thisHand, thatHand; // user-defined overridable functions __attribute__((weak)) void matrix_slave_scan_kb(void) { matrix_slave_scan_user(); } __attribute__((weak)) void matrix_slave_scan_user(void) {} +__attribute__((weak)) void matrix_init_pins(void); +__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row); +__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col); static inline void setPinOutput_writeLow(pin_t pin) { ATOMIC_BLOCK_FORCEON { @@ -61,7 +71,7 @@ static inline void setPinInputHigh_atomic(pin_t pin) { #ifdef DIRECT_PINS -static void init_pins(void) { +__attribute__((weak)) void matrix_init_pins(void) { for (int row = 0; row < MATRIX_ROWS; row++) { for (int col = 0; col < MATRIX_COLS; col++) { pin_t pin = direct_pins[row][col]; @@ -72,7 +82,7 @@ static void init_pins(void) { } } -static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { +__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { // Start with a clear matrix row matrix_row_t current_row_value = 0; @@ -83,16 +93,13 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) } } - // If the row has changed, store the row and return the changed flag. - if (current_matrix[current_row] != current_row_value) { - current_matrix[current_row] = current_row_value; - return true; - } - return false; + // Update the matrix + current_matrix[current_row] = current_row_value; } #elif defined(DIODE_DIRECTION) -# if (DIODE_DIRECTION == COL2ROW) +# if defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS) +# if (DIODE_DIRECTION == COL2ROW) static void select_row(uint8_t row) { setPinOutput_writeLow(row_pins[row]); } @@ -104,14 +111,14 @@ static void unselect_rows(void) { } } -static void init_pins(void) { +__attribute__((weak)) void matrix_init_pins(void) { unselect_rows(); for (uint8_t x = 0; x < MATRIX_COLS; x++) { setPinInputHigh_atomic(col_pins[x]); } } -static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { +__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { // Start with a clear matrix row matrix_row_t current_row_value = 0; @@ -132,15 +139,11 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) unselect_row(current_row); matrix_output_unselect_delay(); // wait for all Col signals to go HIGH - // If the row has changed, store the row and return the changed flag. - if (current_matrix[current_row] != current_row_value) { - current_matrix[current_row] = current_row_value; - return true; - } - return false; + // Update the matrix + current_matrix[current_row] = current_row_value; } -# elif (DIODE_DIRECTION == ROW2COL) +# elif (DIODE_DIRECTION == ROW2COL) static void select_col(uint8_t col) { setPinOutput_writeLow(col_pins[col]); } @@ -152,52 +155,39 @@ static void unselect_cols(void) { } } -static void init_pins(void) { +__attribute__((weak)) void matrix_init_pins(void) { unselect_cols(); for (uint8_t x = 0; x < ROWS_PER_HAND; x++) { setPinInputHigh_atomic(row_pins[x]); } } -static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { - bool matrix_changed = false; - +__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { // Select col select_col(current_col); matrix_output_select_delay(); // For each row... for (uint8_t row_index = 0; row_index < ROWS_PER_HAND; row_index++) { - // Store last value of row prior to reading - matrix_row_t last_row_value = current_matrix[row_index]; - matrix_row_t current_row_value = last_row_value; - // Check row pin state if (readPin(row_pins[row_index]) == 0) { // Pin LO, set col bit - current_row_value |= (MATRIX_ROW_SHIFTER << current_col); + current_matrix[row_index] |= (MATRIX_ROW_SHIFTER << current_col); } else { // Pin HI, clear col bit - current_row_value &= ~(MATRIX_ROW_SHIFTER << current_col); - } - - // Determine if the matrix changed state - if ((last_row_value != current_row_value)) { - matrix_changed |= true; - current_matrix[row_index] = current_row_value; + current_matrix[row_index] &= ~(MATRIX_ROW_SHIFTER << current_col); } } // Unselect col unselect_col(current_col); matrix_output_unselect_delay(); // wait for all Row signals to go HIGH - - return matrix_changed; } -# else -# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! -# endif +# else +# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! +# endif +# endif // defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS) #else # error DIODE_DIRECTION is not defined! #endif @@ -233,7 +223,7 @@ void matrix_init(void) { thatHand = ROWS_PER_HAND - thisHand; // initialize key pins - init_pins(); + matrix_init_pins(); // initialize matrix state: all keys off for (uint8_t i = 0; i < MATRIX_ROWS; i++) { @@ -288,20 +278,23 @@ bool matrix_post_scan(void) { } uint8_t matrix_scan(void) { - bool local_changed = false; + matrix_row_t curr_matrix[MATRIX_ROWS] = {0}; #if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW) // Set row, read cols for (uint8_t current_row = 0; current_row < ROWS_PER_HAND; current_row++) { - local_changed |= read_cols_on_row(raw_matrix, current_row); + matrix_read_cols_on_row(curr_matrix, current_row); } #elif (DIODE_DIRECTION == ROW2COL) // Set col, read rows for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) { - local_changed |= read_rows_on_col(raw_matrix, current_col); + matrix_read_rows_on_col(curr_matrix, current_col); } #endif + bool local_changed = memcmp(raw_matrix, curr_matrix, sizeof(curr_matrix)) != 0; + if (local_changed) memcpy(raw_matrix, curr_matrix, sizeof(curr_matrix)); + debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, local_changed); bool remote_changed = matrix_post_scan(); diff --git a/quantum/split_common/post_config.h b/quantum/split_common/post_config.h index 4ae1d52732..a4c0a1956b 100644 --- a/quantum/split_common/post_config.h +++ b/quantum/split_common/post_config.h @@ -7,13 +7,4 @@ # ifndef F_SCL # define F_SCL 100000UL // SCL frequency # endif - -#else // use serial -// When using serial, the user must define RGBLIGHT_SPLIT explicitly -// in config.h as needed. -// see quantum/rgblight_post_config.h -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) -// When using serial and RGBLIGHT_SPLIT need separate transaction -# define SERIAL_USE_MULTI_TRANSACTION -# endif #endif diff --git a/quantum/split_common/transaction_id_define.h b/quantum/split_common/transaction_id_define.h new file mode 100644 index 0000000000..464c73478a --- /dev/null +++ b/quantum/split_common/transaction_id_define.h @@ -0,0 +1,94 @@ +/* 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 + +enum serial_transaction_id { +#ifdef USE_I2C + I2C_EXECUTE_CALLBACK, +#endif // USE_I2C + + GET_SLAVE_MATRIX_CHECKSUM, + GET_SLAVE_MATRIX_DATA, + +#ifdef SPLIT_TRANSPORT_MIRROR + PUT_MASTER_MATRIX, +#endif // SPLIT_TRANSPORT_MIRROR + +#ifdef ENCODER_ENABLE + GET_ENCODERS_CHECKSUM, + GET_ENCODERS_DATA, +#endif // ENCODER_ENABLE + +#ifndef DISABLE_SYNC_TIMER + PUT_SYNC_TIMER, +#endif // DISABLE_SYNC_TIMER + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + PUT_LAYER_STATE, + PUT_DEFAULT_LAYER_STATE, +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +#ifdef SPLIT_LED_STATE_ENABLE + PUT_LED_STATE, +#endif // SPLIT_LED_STATE_ENABLE + +#ifdef SPLIT_MODS_ENABLE + PUT_MODS, +#endif // SPLIT_MODS_ENABLE + +#ifdef BACKLIGHT_ENABLE + PUT_BACKLIGHT, +#endif // BACKLIGHT_ENABLE + +#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + PUT_RGBLIGHT, +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + PUT_LED_MATRIX, +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + PUT_RGB_MATRIX, +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + PUT_WPM, +#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + PUT_RPC_INFO, + PUT_RPC_REQ_DATA, + EXECUTE_RPC, + GET_RPC_RESP_DATA, +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +// keyboard-specific +#ifdef SPLIT_TRANSACTION_IDS_KB + SPLIT_TRANSACTION_IDS_KB, +#endif // SPLIT_TRANSACTION_IDS_KB + +// user/keymap-specific +#ifdef SPLIT_TRANSACTION_IDS_USER + SPLIT_TRANSACTION_IDS_USER, +#endif // SPLIT_TRANSACTION_IDS_USER + + NUM_TOTAL_TRANSACTIONS +}; + +// Ensure we only use 5 bits for transaction +_Static_assert(NUM_TOTAL_TRANSACTIONS <= (1 << 5), "Max number of usable transactions exceeded"); diff --git a/quantum/split_common/transactions.c b/quantum/split_common/transactions.c new file mode 100644 index 0000000000..99a8623b3c --- /dev/null +++ b/quantum/split_common/transactions.c @@ -0,0 +1,670 @@ +/* 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/>. + */ + +#include <string.h> +#include <stddef.h> + +#include "debug.h" +#include "matrix.h" +#include "quantum.h" +#include "transactions.h" +#include "transport.h" +#include "transaction_id_define.h" + +#define SYNC_TIMER_OFFSET 2 + +#ifndef FORCED_SYNC_THROTTLE_MS +# define FORCED_SYNC_THROTTLE_MS 100 +#endif // FORCED_SYNC_THROTTLE_MS + +#define sizeof_member(type, member) sizeof(((type *)NULL)->member) + +#define trans_initiator2target_initializer_cb(member, cb) \ + { &dummy, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), 0, 0, cb } +#define trans_initiator2target_initializer(member) trans_initiator2target_initializer_cb(member, NULL) + +#define trans_target2initiator_initializer_cb(member, cb) \ + { &dummy, 0, 0, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), cb } +#define trans_target2initiator_initializer(member) trans_target2initiator_initializer_cb(member, NULL) + +#define transport_write(id, data, length) transport_execute_transaction(id, data, length, NULL, 0) +#define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length) + +static uint8_t crc8(const void *data, size_t len) { + const uint8_t *p = (const uint8_t *)data; + uint8_t crc = 0xff; + size_t i, j; + for (i = 0; i < len; i++) { + crc ^= p[i]; + for (j = 0; j < 8; j++) { + if ((crc & 0x80) != 0) + crc = (uint8_t)((crc << 1) ^ 0x31); + else + crc <<= 1; + } + } + return crc; +} + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +// Forward-declare the RPC callback handlers +void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +//////////////////////////////////////////////////// +// Helpers + +bool transaction_handler_master(bool okay, matrix_row_t master_matrix[], matrix_row_t slave_matrix[], const char *prefix, bool (*handler)(matrix_row_t master_matrix[], matrix_row_t slave_matrix[])) { + if (okay) { + bool this_okay = true; + for (int iter = 1; iter <= 10; ++iter) { + if (!this_okay) { + for (int i = 0; i < iter * iter; ++i) { + wait_us(10); + } + } + ATOMIC_BLOCK_FORCEON { this_okay = handler(master_matrix, slave_matrix); }; + if (this_okay) break; + } + okay &= this_okay; + if (!okay) { + dprintf("Failed to execute %s\n", prefix); + } + } + return okay; +} + +#define TRANSACTION_HANDLER_MASTER(prefix) \ + do { \ + okay &= transaction_handler_master(okay, master_matrix, slave_matrix, #prefix, &prefix##_master); \ + } while (0) + +#define TRANSACTION_HANDLER_SLAVE(prefix) \ + do { \ + ATOMIC_BLOCK_FORCEON { prefix##_slave(master_matrix, slave_matrix); }; \ + } while (0) + +inline static bool read_if_checksum_mismatch(int8_t trans_id_checksum, int8_t trans_id_retrieve, uint32_t *last_update, void *destination, const void *equiv_shmem, size_t length) { + uint8_t curr_checksum; + bool okay = transport_read(trans_id_checksum, &curr_checksum, sizeof(curr_checksum)); + if (okay && (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || curr_checksum != crc8(equiv_shmem, length))) { + okay &= transport_read(trans_id_retrieve, destination, length); + okay &= curr_checksum == crc8(equiv_shmem, length); + if (okay) { + *last_update = timer_read32(); + } + } else { + memcpy(destination, equiv_shmem, length); + } + return okay; +} + +inline static bool send_if_condition(int8_t trans_id, uint32_t *last_update, bool condition, void *source, size_t length) { + bool okay = true; + if (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || condition) { + okay &= transport_write(trans_id, source, length); + if (okay) { + *last_update = timer_read32(); + } + } + return okay; +} + +inline static bool send_if_data_mismatch(int8_t trans_id, uint32_t *last_update, void *source, const void *equiv_shmem, size_t length) { + // Just run a memcmp to compare the source and equivalent shmem location + return send_if_condition(trans_id, last_update, (memcmp(source, equiv_shmem, length) != 0), source, length); +} + +//////////////////////////////////////////////////// +// Slave matrix + +static bool slave_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + static matrix_row_t last_matrix[(MATRIX_ROWS) / 2] = {0}; // last successfully-read matrix, so we can replicate if there are checksum errors + matrix_row_t temp_matrix[(MATRIX_ROWS) / 2]; // holding area while we test whether or not checksum is correct + + bool okay = read_if_checksum_mismatch(GET_SLAVE_MATRIX_CHECKSUM, GET_SLAVE_MATRIX_DATA, &last_update, temp_matrix, split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix)); + if (okay) { + // Checksum matches the received data, save as the last matrix state + memcpy(last_matrix, temp_matrix, sizeof(temp_matrix)); + } + // Copy out the last-known-good matrix state to the slave matrix + memcpy(slave_matrix, last_matrix, sizeof(last_matrix)); + return okay; +} + +static void slave_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + memcpy(split_shmem->smatrix.matrix, slave_matrix, sizeof(split_shmem->smatrix.matrix)); + split_shmem->smatrix.checksum = crc8(split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix)); +} + +// clang-format off +#define TRANSACTIONS_SLAVE_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(slave_matrix_handlers) +#define TRANSACTIONS_SLAVE_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(slave_matrix_handlers) +#define TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS \ + [GET_SLAVE_MATRIX_CHECKSUM] = trans_target2initiator_initializer(smatrix.checksum), \ + [GET_SLAVE_MATRIX_DATA] = trans_target2initiator_initializer(smatrix.matrix), +// clang-format on + +//////////////////////////////////////////////////// +// Master matrix + +#ifdef SPLIT_TRANSPORT_MIRROR + +static bool master_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + return send_if_data_mismatch(PUT_MASTER_MATRIX, &last_update, master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix)); +} + +static void master_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + // Always copy to the master matrix + memcpy(master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix)); +} + +# define TRANSACTIONS_MASTER_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(master_matrix_handlers) +# define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(master_matrix_handlers) +# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS [PUT_MASTER_MATRIX] = trans_initiator2target_initializer(mmatrix.matrix), + +#else // SPLIT_TRANSPORT_MIRROR + +# define TRANSACTIONS_MASTER_MATRIX_MASTER() +# define TRANSACTIONS_MASTER_MATRIX_SLAVE() +# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS + +#endif // SPLIT_TRANSPORT_MIRROR + +//////////////////////////////////////////////////// +// Encoders + +#ifdef ENCODER_ENABLE + +static bool encoder_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t temp_state[NUMBER_OF_ENCODERS]; + + bool okay = read_if_checksum_mismatch(GET_ENCODERS_CHECKSUM, GET_ENCODERS_DATA, &last_update, temp_state, split_shmem->encoders.state, sizeof(temp_state)); + if (okay) encoder_update_raw(temp_state); + return okay; +} + +static void encoder_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + uint8_t encoder_state[NUMBER_OF_ENCODERS]; + encoder_state_raw(encoder_state); + // Always prepare the encoder state for read. + memcpy(split_shmem->encoders.state, encoder_state, sizeof(encoder_state)); + // Now update the checksum given that the encoders has been written to + split_shmem->encoders.checksum = crc8(encoder_state, sizeof(encoder_state)); +} + +// clang-format off +# define TRANSACTIONS_ENCODERS_MASTER() TRANSACTION_HANDLER_MASTER(encoder_handlers) +# define TRANSACTIONS_ENCODERS_SLAVE() TRANSACTION_HANDLER_SLAVE(encoder_handlers) +# define TRANSACTIONS_ENCODERS_REGISTRATIONS \ + [GET_ENCODERS_CHECKSUM] = trans_target2initiator_initializer(encoders.checksum), \ + [GET_ENCODERS_DATA] = trans_target2initiator_initializer(encoders.state), +// clang-format on + +#else // ENCODER_ENABLE + +# define TRANSACTIONS_ENCODERS_MASTER() +# define TRANSACTIONS_ENCODERS_SLAVE() +# define TRANSACTIONS_ENCODERS_REGISTRATIONS + +#endif // ENCODER_ENABLE + +//////////////////////////////////////////////////// +// Sync timer + +#ifndef DISABLE_SYNC_TIMER + +static bool sync_timer_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + + bool okay = true; + if (timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS) { + uint32_t sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; + okay &= transport_write(PUT_SYNC_TIMER, &sync_timer, sizeof(sync_timer)); + if (okay) { + last_update = timer_read32(); + } + } + return okay; +} + +static void sync_timer_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_sync_timer = 0; + if (last_sync_timer != split_shmem->sync_timer) { + last_sync_timer = split_shmem->sync_timer; + sync_timer_update(last_sync_timer); + } +} + +# define TRANSACTIONS_SYNC_TIMER_MASTER() TRANSACTION_HANDLER_MASTER(sync_timer_handlers) +# define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE(sync_timer_handlers) +# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS [PUT_SYNC_TIMER] = trans_initiator2target_initializer(sync_timer), + +#else // DISABLE_SYNC_TIMER + +# define TRANSACTIONS_SYNC_TIMER_MASTER() +# define TRANSACTIONS_SYNC_TIMER_SLAVE() +# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS + +#endif // DISABLE_SYNC_TIMER + +//////////////////////////////////////////////////// +// Layer state + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +static bool layer_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_layer_state_update = 0; + static uint32_t last_default_layer_state_update = 0; + + bool okay = send_if_condition(PUT_LAYER_STATE, &last_layer_state_update, (layer_state != split_shmem->layers.layer_state), &layer_state, sizeof(layer_state)); + if (okay) { + okay &= send_if_condition(PUT_DEFAULT_LAYER_STATE, &last_default_layer_state_update, (default_layer_state != split_shmem->layers.default_layer_state), &default_layer_state, sizeof(default_layer_state)); + } + return okay; +} + +static void layer_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + layer_state = split_shmem->layers.layer_state; + default_layer_state = split_shmem->layers.default_layer_state; +} + +// clang-format off +# define TRANSACTIONS_LAYER_STATE_MASTER() TRANSACTION_HANDLER_MASTER(layer_state_handlers) +# define TRANSACTIONS_LAYER_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(layer_state_handlers) +# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS \ + [PUT_LAYER_STATE] = trans_initiator2target_initializer(layers.layer_state), \ + [PUT_DEFAULT_LAYER_STATE] = trans_initiator2target_initializer(layers.default_layer_state), +// clang-format on + +#else // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +# define TRANSACTIONS_LAYER_STATE_MASTER() +# define TRANSACTIONS_LAYER_STATE_SLAVE() +# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS + +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +//////////////////////////////////////////////////// +// LED state + +#ifdef SPLIT_LED_STATE_ENABLE + +static bool led_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t led_state = host_keyboard_leds(); + return send_if_data_mismatch(PUT_LED_STATE, &last_update, &led_state, &split_shmem->led_state, sizeof(led_state)); +} + +static void led_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + void set_split_host_keyboard_leds(uint8_t led_state); + set_split_host_keyboard_leds(split_shmem->led_state); +} + +# define TRANSACTIONS_LED_STATE_MASTER() TRANSACTION_HANDLER_MASTER(led_state_handlers) +# define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(led_state_handlers) +# define TRANSACTIONS_LED_STATE_REGISTRATIONS [PUT_LED_STATE] = trans_initiator2target_initializer(led_state), + +#else // SPLIT_LED_STATE_ENABLE + +# define TRANSACTIONS_LED_STATE_MASTER() +# define TRANSACTIONS_LED_STATE_SLAVE() +# define TRANSACTIONS_LED_STATE_REGISTRATIONS + +#endif // SPLIT_LED_STATE_ENABLE + +//////////////////////////////////////////////////// +// Mods + +#ifdef SPLIT_MODS_ENABLE + +static bool mods_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + bool mods_need_sync = timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS; + split_mods_sync_t new_mods; + new_mods.real_mods = get_mods(); + if (!mods_need_sync && new_mods.real_mods != split_shmem->mods.real_mods) { + mods_need_sync = true; + } + + new_mods.weak_mods = get_weak_mods(); + if (!mods_need_sync && new_mods.weak_mods != split_shmem->mods.weak_mods) { + mods_need_sync = true; + } + +# ifndef NO_ACTION_ONESHOT + new_mods.oneshot_mods = get_oneshot_mods(); + if (!mods_need_sync && new_mods.oneshot_mods != split_shmem->mods.oneshot_mods) { + mods_need_sync = true; + } +# endif // NO_ACTION_ONESHOT + + bool okay = true; + if (mods_need_sync) { + okay &= transport_write(PUT_MODS, &new_mods, sizeof(new_mods)); + if (okay) { + last_update = timer_read32(); + } + } + + return okay; +} + +static void mods_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + set_mods(split_shmem->mods.real_mods); + set_weak_mods(split_shmem->mods.weak_mods); +# ifndef NO_ACTION_ONESHOT + set_oneshot_mods(split_shmem->mods.oneshot_mods); +# endif +} + +# define TRANSACTIONS_MODS_MASTER() TRANSACTION_HANDLER_MASTER(mods_handlers) +# define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods_handlers) +# define TRANSACTIONS_MODS_REGISTRATIONS [PUT_MODS] = trans_initiator2target_initializer(mods), + +#else // SPLIT_MODS_ENABLE + +# define TRANSACTIONS_MODS_MASTER() +# define TRANSACTIONS_MODS_SLAVE() +# define TRANSACTIONS_MODS_REGISTRATIONS + +#endif // SPLIT_MODS_ENABLE + +//////////////////////////////////////////////////// +// Backlight + +#ifdef BACKLIGHT_ENABLE + +static bool backlight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0; + return send_if_condition(PUT_BACKLIGHT, &last_update, (level != split_shmem->backlight_level), &level, sizeof(level)); +} + +static void backlight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { backlight_set(split_shmem->backlight_level); } + +# define TRANSACTIONS_BACKLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(backlight_handlers) +# define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight_handlers) +# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS [PUT_BACKLIGHT] = trans_initiator2target_initializer(backlight_level), + +#else // BACKLIGHT_ENABLE + +# define TRANSACTIONS_BACKLIGHT_MASTER() +# define TRANSACTIONS_BACKLIGHT_SLAVE() +# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS + +#endif // BACKLIGHT_ENABLE + +//////////////////////////////////////////////////// +// RGBLIGHT + +#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +static bool rgblight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + rgblight_syncinfo_t rgblight_sync; + rgblight_get_syncinfo(&rgblight_sync); + if (send_if_condition(PUT_RGBLIGHT, &last_update, (rgblight_sync.status.change_flags != 0), &rgblight_sync, sizeof(rgblight_sync))) { + rgblight_clear_change_flags(); + } else { + return false; + } + return true; +} + +static void rgblight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + // Update the RGB with the new data + if (split_shmem->rgblight_sync.status.change_flags != 0) { + rgblight_update_sync(&split_shmem->rgblight_sync, false); + split_shmem->rgblight_sync.status.change_flags = 0; + } +} + +# define TRANSACTIONS_RGBLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(rgblight_handlers) +# define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight_handlers) +# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS [PUT_RGBLIGHT] = trans_initiator2target_initializer(rgblight_sync), + +#else // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +# define TRANSACTIONS_RGBLIGHT_MASTER() +# define TRANSACTIONS_RGBLIGHT_SLAVE() +# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS + +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +//////////////////////////////////////////////////// +// LED Matrix + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +static bool led_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + led_matrix_sync_t led_matrix_sync; + memcpy(&led_matrix_sync.led_matrix, &led_matrix_eeconfig, sizeof(led_eeconfig_t)); + led_matrix_sync.led_suspend_state = led_matrix_get_suspend_state(); + return send_if_data_mismatch(PUT_LED_MATRIX, &last_update, &led_matrix_sync, &split_shmem->led_matrix_sync, sizeof(led_matrix_sync)); +} + +static void led_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + memcpy(&led_matrix_eeconfig, &split_shmem->led_matrix_sync.led_matrix, sizeof(led_eeconfig_t)); + led_matrix_set_suspend_state(split_shmem->led_matrix_sync.led_suspend_state); +} + +# define TRANSACTIONS_LED_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(led_matrix_handlers) +# define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix_handlers) +# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS [PUT_LED_MATRIX] = trans_initiator2target_initializer(led_matrix_sync), + +#else // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +# define TRANSACTIONS_LED_MATRIX_MASTER() +# define TRANSACTIONS_LED_MATRIX_SLAVE() +# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS + +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +//////////////////////////////////////////////////// +// RGB Matrix + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +static bool rgb_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + rgb_matrix_sync_t rgb_matrix_sync; + memcpy(&rgb_matrix_sync.rgb_matrix, &rgb_matrix_config, sizeof(rgb_config_t)); + rgb_matrix_sync.rgb_suspend_state = rgb_matrix_get_suspend_state(); + return send_if_data_mismatch(PUT_RGB_MATRIX, &last_update, &rgb_matrix_sync, &split_shmem->rgb_matrix_sync, sizeof(rgb_matrix_sync)); +} + +static void rgb_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + memcpy(&rgb_matrix_config, &split_shmem->rgb_matrix_sync.rgb_matrix, sizeof(rgb_config_t)); + rgb_matrix_set_suspend_state(split_shmem->rgb_matrix_sync.rgb_suspend_state); +} + +# define TRANSACTIONS_RGB_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(rgb_matrix_handlers) +# define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix_handlers) +# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS [PUT_RGB_MATRIX] = trans_initiator2target_initializer(rgb_matrix_sync), + +#else // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +# define TRANSACTIONS_RGB_MATRIX_MASTER() +# define TRANSACTIONS_RGB_MATRIX_SLAVE() +# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS + +#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +//////////////////////////////////////////////////// +// WPM + +#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +static bool wpm_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + static uint32_t last_update = 0; + uint8_t current_wpm = get_current_wpm(); + return send_if_condition(PUT_WPM, &last_update, (current_wpm != split_shmem->current_wpm), ¤t_wpm, sizeof(current_wpm)); +} + +static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { set_current_wpm(split_shmem->current_wpm); } + +# define TRANSACTIONS_WPM_MASTER() TRANSACTION_HANDLER_MASTER(wpm_handlers) +# define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE(wpm_handlers) +# define TRANSACTIONS_WPM_REGISTRATIONS [PUT_WPM] = trans_initiator2target_initializer(current_wpm), + +#else // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +# define TRANSACTIONS_WPM_MASTER() +# define TRANSACTIONS_WPM_SLAVE() +# define TRANSACTIONS_WPM_REGISTRATIONS + +#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +//////////////////////////////////////////////////// + +uint8_t dummy; +split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = { + // Set defaults + [0 ...(NUM_TOTAL_TRANSACTIONS - 1)] = {NULL, 0, 0, 0, 0, 0}, + +#ifdef USE_I2C + [I2C_EXECUTE_CALLBACK] = trans_initiator2target_initializer(transaction_id), +#endif // USE_I2C + + // clang-format off + TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS + TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS + TRANSACTIONS_ENCODERS_REGISTRATIONS + TRANSACTIONS_SYNC_TIMER_REGISTRATIONS + TRANSACTIONS_LAYER_STATE_REGISTRATIONS + TRANSACTIONS_LED_STATE_REGISTRATIONS + TRANSACTIONS_MODS_REGISTRATIONS + TRANSACTIONS_BACKLIGHT_REGISTRATIONS + TRANSACTIONS_RGBLIGHT_REGISTRATIONS + TRANSACTIONS_LED_MATRIX_REGISTRATIONS + TRANSACTIONS_RGB_MATRIX_REGISTRATIONS + TRANSACTIONS_WPM_REGISTRATIONS +// clang-format on + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + [PUT_RPC_INFO] = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback), + [PUT_RPC_REQ_DATA] = trans_initiator2target_initializer(rpc_m2s_buffer), + [EXECUTE_RPC] = trans_initiator2target_initializer_cb(rpc_info.transaction_id, slave_rpc_exec_callback), + [GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer), +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +}; + +bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + bool okay = true; + TRANSACTIONS_SLAVE_MATRIX_MASTER(); + TRANSACTIONS_MASTER_MATRIX_MASTER(); + TRANSACTIONS_ENCODERS_MASTER(); + TRANSACTIONS_SYNC_TIMER_MASTER(); + TRANSACTIONS_LAYER_STATE_MASTER(); + TRANSACTIONS_LED_STATE_MASTER(); + TRANSACTIONS_MODS_MASTER(); + TRANSACTIONS_BACKLIGHT_MASTER(); + TRANSACTIONS_RGBLIGHT_MASTER(); + TRANSACTIONS_LED_MATRIX_MASTER(); + TRANSACTIONS_RGB_MATRIX_MASTER(); + TRANSACTIONS_WPM_MASTER(); + return okay; +} + +void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { + TRANSACTIONS_SLAVE_MATRIX_SLAVE(); + TRANSACTIONS_MASTER_MATRIX_SLAVE(); + TRANSACTIONS_ENCODERS_SLAVE(); + TRANSACTIONS_SYNC_TIMER_SLAVE(); + TRANSACTIONS_LAYER_STATE_SLAVE(); + TRANSACTIONS_LED_STATE_SLAVE(); + TRANSACTIONS_MODS_SLAVE(); + TRANSACTIONS_BACKLIGHT_SLAVE(); + TRANSACTIONS_RGBLIGHT_SLAVE(); + TRANSACTIONS_LED_MATRIX_SLAVE(); + TRANSACTIONS_RGB_MATRIX_SLAVE(); + TRANSACTIONS_WPM_SLAVE(); +} + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback) { + // Prevent invoking RPC on QMK core sync data + if (transaction_id <= GET_RPC_RESP_DATA) return; + + // Set the callback + split_transaction_table[transaction_id].slave_callback = callback; + split_transaction_table[transaction_id].initiator2target_offset = offsetof(split_shared_memory_t, rpc_m2s_buffer); + split_transaction_table[transaction_id].target2initiator_offset = offsetof(split_shared_memory_t, rpc_s2m_buffer); +} + +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) { + // Prevent invoking RPC on QMK core sync data + if (transaction_id <= GET_RPC_RESP_DATA) return false; + // Prevent sizing issues + if (initiator2target_buffer_size > RPC_M2S_BUFFER_SIZE) return false; + if (target2initiator_buffer_size > RPC_S2M_BUFFER_SIZE) return false; + + // Prepare the metadata block + rpc_sync_info_t info = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size}; + + // Make sure the local side knows that we're not sending the full block of data + split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = initiator2target_buffer_size; + split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = target2initiator_buffer_size; + + // Run through the sequence: + // * set the transaction ID and lengths + // * send the request data + // * execute RPC callback + // * retrieve the response data + if (!transport_write(PUT_RPC_INFO, &info, sizeof(info))) { + return false; + } + if (!transport_write(PUT_RPC_REQ_DATA, initiator2target_buffer, initiator2target_buffer_size)) { + return false; + } + if (!transport_write(EXECUTE_RPC, &transaction_id, sizeof(transaction_id))) { + return false; + } + if (!transport_read(GET_RPC_RESP_DATA, target2initiator_buffer, target2initiator_buffer_size)) { + return false; + } + return true; +} + +void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) { + // The RPC info block contains the intended transaction ID, as well as the sizes for both inbound and outbound data. + // Ignore the args -- the `split_shmem` already has the info, we just need to act upon it. + // We must keep the `split_transaction_table` non-const, so that it is able to be modified at runtime. + + split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = split_shmem->rpc_info.m2s_length; + split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.s2m_length; +} + +void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) { + // We can assume that the buffer lengths are correctly set, now, given that sequentially the rpc_info callback was already executed. + // Go through the rpc_info and execute _that_ transaction's callback, with the scratch buffers as inputs. + int8_t transaction_id = split_shmem->rpc_info.transaction_id; + if (transaction_id < NUM_TOTAL_TRANSACTIONS) { + split_transaction_desc_t *trans = &split_transaction_table[transaction_id]; + if (trans->slave_callback) { + trans->slave_callback(split_shmem->rpc_info.m2s_length, split_shmem->rpc_m2s_buffer, split_shmem->rpc_info.s2m_length, split_shmem->rpc_s2m_buffer); + } + } +} + +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) diff --git a/quantum/split_common/transactions.h b/quantum/split_common/transactions.h new file mode 100644 index 0000000000..4306ba1d87 --- /dev/null +++ b/quantum/split_common/transactions.h @@ -0,0 +1,54 @@ +/* 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 "matrix.h" +#include "transaction_id_define.h" +#include "transport.h" + +typedef void (*slave_callback_t)(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); + +// Split transaction Descriptor +typedef struct _split_transaction_desc_t { + uint8_t * status; + uint8_t initiator2target_buffer_size; + uint16_t initiator2target_offset; + uint8_t target2initiator_buffer_size; + uint16_t target2initiator_offset; + slave_callback_t slave_callback; +} split_transaction_desc_t; + +// Forward declaration for the split transactions +extern split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS]; + +#define split_shmem_offset_ptr(offset) ((void *)(((uint8_t *)split_shmem) + (offset))) +#define split_trans_initiator2target_buffer(trans) (split_shmem_offset_ptr((trans)->initiator2target_offset)) +#define split_trans_target2initiator_buffer(trans) (split_shmem_offset_ptr((trans)->target2initiator_offset)) + +// returns false if valid data not received from slave +bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); +void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); + +void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback); + +bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); + +#define transaction_rpc_send(transaction_id, initiator2target_buffer_size, initiator2target_buffer) transaction_rpc_exec(transaction_id, initiator2target_buffer_size, initiator2target_buffer, 0, NULL) +#define transaction_rpc_recv(transaction_id, target2initiator_buffer_size, target2initiator_buffer) transaction_rpc_exec(transaction_id, 0, NULL, target2initiator_buffer_size, target2initiator_buffer) diff --git a/quantum/split_common/transport.c b/quantum/split_common/transport.c index 9ed0f7591b..a711ef85f0 100644 --- a/quantum/split_common/transport.c +++ b/quantum/split_common/transport.c @@ -1,452 +1,118 @@ -#include <string.h> -#include <stddef.h> - -#include "config.h" -#include "matrix.h" -#include "quantum.h" - -#define ROWS_PER_HAND (MATRIX_ROWS / 2) -#define SYNC_TIMER_OFFSET 2 - -#ifdef RGBLIGHT_ENABLE -# include "rgblight.h" -#endif - -#ifdef BACKLIGHT_ENABLE -# include "backlight.h" -#endif - -#ifdef ENCODER_ENABLE -# include "encoder.h" -static pin_t encoders_pad[] = ENCODERS_PAD_A; -# define NUMBER_OF_ENCODERS (sizeof(encoders_pad) / sizeof(pin_t)) -#endif - -#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) -# include "led_matrix.h" -#endif -#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) -# include "rgb_matrix.h" -#endif - -#if defined(USE_I2C) +/* 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/>. + */ -# include "i2c_master.h" -# include "i2c_slave.h" - -typedef struct _I2C_slave_buffer_t { -# ifndef DISABLE_SYNC_TIMER - uint32_t sync_timer; -# endif -# ifdef SPLIT_TRANSPORT_MIRROR - matrix_row_t mmatrix[ROWS_PER_HAND]; -# endif - matrix_row_t smatrix[ROWS_PER_HAND]; -# ifdef SPLIT_MODS_ENABLE - uint8_t real_mods; - uint8_t weak_mods; -# ifndef NO_ACTION_ONESHOT - uint8_t oneshot_mods; -# endif -# endif -# ifdef BACKLIGHT_ENABLE - uint8_t backlight_level; -# endif -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - rgblight_syncinfo_t rgblight_sync; -# endif -# ifdef ENCODER_ENABLE - uint8_t encoder_state[NUMBER_OF_ENCODERS]; -# endif -# ifdef WPM_ENABLE - uint8_t current_wpm; -# endif -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - led_eeconfig_t led_matrix; - bool led_suspend_state; -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - rgb_config_t rgb_matrix; - bool rgb_suspend_state; -# endif -} I2C_slave_buffer_t; +#include <string.h> +#include <debug.h> -static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg; +#include "transactions.h" +#include "transport.h" +#include "transaction_id_define.h" +#include "atomic_util.h" -# define I2C_SYNC_TIME_START offsetof(I2C_slave_buffer_t, sync_timer) -# define I2C_KEYMAP_MASTER_START offsetof(I2C_slave_buffer_t, mmatrix) -# define I2C_KEYMAP_SLAVE_START offsetof(I2C_slave_buffer_t, smatrix) -# define I2C_REAL_MODS_START offsetof(I2C_slave_buffer_t, real_mods) -# define I2C_WEAK_MODS_START offsetof(I2C_slave_buffer_t, weak_mods) -# define I2C_ONESHOT_MODS_START offsetof(I2C_slave_buffer_t, oneshot_mods) -# define I2C_BACKLIGHT_START offsetof(I2C_slave_buffer_t, backlight_level) -# define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync) -# define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state) -# define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm) -# define I2C_LED_MATRIX_START offsetof(I2C_slave_buffer_t, led_matrix) -# define I2C_LED_SUSPEND_START offsetof(I2C_slave_buffer_t, led_suspend_state) -# define I2C_RGB_MATRIX_START offsetof(I2C_slave_buffer_t, rgb_matrix) -# define I2C_RGB_SUSPEND_START offsetof(I2C_slave_buffer_t, rgb_suspend_state) +#ifdef USE_I2C -# define TIMEOUT 100 +# ifndef SLAVE_I2C_TIMEOUT +# define SLAVE_I2C_TIMEOUT 100 +# endif // SLAVE_I2C_TIMEOUT # ifndef SLAVE_I2C_ADDRESS # define SLAVE_I2C_ADDRESS 0x32 # endif -// Get rows from other half over i2c -bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { - i2c_readReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_SLAVE_START, (void *)slave_matrix, sizeof(i2c_buffer->smatrix), TIMEOUT); -# ifdef SPLIT_TRANSPORT_MIRROR - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_MASTER_START, (void *)master_matrix, sizeof(i2c_buffer->mmatrix), TIMEOUT); -# endif +# include "i2c_master.h" +# include "i2c_slave.h" - // write backlight info -# ifdef BACKLIGHT_ENABLE - uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0; - if (level != i2c_buffer->backlight_level) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_BACKLIGHT_START, (void *)&level, sizeof(level), TIMEOUT) >= 0) { - i2c_buffer->backlight_level = level; - } - } -# endif +// Ensure the I2C buffer has enough space +_Static_assert(sizeof(split_shared_memory_t) <= I2C_SLAVE_REG_COUNT, "split_shared_memory_t too large for I2C_SLAVE_REG_COUNT"); -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - if (rgblight_get_change_flags()) { - rgblight_syncinfo_t rgblight_sync; - rgblight_get_syncinfo(&rgblight_sync); - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_START, (void *)&rgblight_sync, sizeof(rgblight_sync), TIMEOUT) >= 0) { - rgblight_clear_change_flags(); - } - } -# endif +split_shared_memory_t *const split_shmem = (split_shared_memory_t *)i2c_slave_reg; -# ifdef ENCODER_ENABLE - i2c_readReg(SLAVE_I2C_ADDRESS, I2C_ENCODER_START, (void *)i2c_buffer->encoder_state, sizeof(i2c_buffer->encoder_state), TIMEOUT); - encoder_update_raw(i2c_buffer->encoder_state); -# endif +void transport_master_init(void) { i2c_init(); } +void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); } -# ifdef WPM_ENABLE - uint8_t current_wpm = get_current_wpm(); - if (current_wpm != i2c_buffer->current_wpm) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WPM_START, (void *)¤t_wpm, sizeof(current_wpm), TIMEOUT) >= 0) { - i2c_buffer->current_wpm = current_wpm; - } +i2c_status_t transport_trigger_callback(int8_t id) { + // If there's no callback, indicate that we were successful + if (!split_transaction_table[id].slave_callback) { + return I2C_STATUS_SUCCESS; } -# endif -# ifdef SPLIT_MODS_ENABLE - uint8_t real_mods = get_mods(); - if (real_mods != i2c_buffer->real_mods) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_REAL_MODS_START, (void *)&real_mods, sizeof(real_mods), TIMEOUT) >= 0) { - i2c_buffer->real_mods = real_mods; + // Kick off the "callback executor", now that data has been written to the slave + split_shmem->transaction_id = id; + split_transaction_desc_t *trans = &split_transaction_table[I2C_EXECUTE_CALLBACK]; + return i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size, SLAVE_I2C_TIMEOUT); +} + +bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) { + i2c_status_t status; + split_transaction_desc_t *trans = &split_transaction_table[id]; + if (initiator2target_length > 0) { + size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length; + memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len); + if ((status = i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) { + return false; } } - uint8_t weak_mods = get_weak_mods(); - if (weak_mods != i2c_buffer->weak_mods) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WEAK_MODS_START, (void *)&weak_mods, sizeof(weak_mods), TIMEOUT) >= 0) { - i2c_buffer->weak_mods = weak_mods; - } + // If we need to execute a callback on the slave, do so + if ((status = transport_trigger_callback(id)) < 0) { + return false; } -# ifndef NO_ACTION_ONESHOT - uint8_t oneshot_mods = get_oneshot_mods(); - if (oneshot_mods != i2c_buffer->oneshot_mods) { - if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_ONESHOT_MODS_START, (void *)&oneshot_mods, sizeof(oneshot_mods), TIMEOUT) >= 0) { - i2c_buffer->oneshot_mods = oneshot_mods; + if (target2initiator_length > 0) { + size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length; + if ((status = i2c_readReg(SLAVE_I2C_ADDRESS, trans->target2initiator_offset, split_trans_target2initiator_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) { + return false; } + memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len); } -# endif -# endif -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_MATRIX_START, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix), TIMEOUT); - bool suspend_state = led_matrix_get_suspend_state(); - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->led_suspend_state), TIMEOUT); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_MATRIX_START, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix), TIMEOUT); - bool suspend_state = rgb_matrix_get_suspend_state(); - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->rgb_suspend_state), TIMEOUT); -# endif - -# ifndef DISABLE_SYNC_TIMER - i2c_buffer->sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; - i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_SYNC_TIME_START, (void *)&i2c_buffer->sync_timer, sizeof(i2c_buffer->sync_timer), TIMEOUT); -# endif return true; } -void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { -# ifndef DISABLE_SYNC_TIMER - sync_timer_update(i2c_buffer->sync_timer); -# endif - // Copy matrix to I2C buffer - memcpy((void *)i2c_buffer->smatrix, (void *)slave_matrix, sizeof(i2c_buffer->smatrix)); -# ifdef SPLIT_TRANSPORT_MIRROR - memcpy((void *)master_matrix, (void *)i2c_buffer->mmatrix, sizeof(i2c_buffer->mmatrix)); -# endif - -// Read Backlight Info -# ifdef BACKLIGHT_ENABLE - backlight_set(i2c_buffer->backlight_level); -# endif - -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - // Update the RGB with the new data - if (i2c_buffer->rgblight_sync.status.change_flags != 0) { - rgblight_update_sync(&i2c_buffer->rgblight_sync, false); - i2c_buffer->rgblight_sync.status.change_flags = 0; - } -# endif - -# ifdef ENCODER_ENABLE - encoder_state_raw(i2c_buffer->encoder_state); -# endif - -# ifdef WPM_ENABLE - set_current_wpm(i2c_buffer->current_wpm); -# endif - -# ifdef SPLIT_MODS_ENABLE - set_mods(i2c_buffer->real_mods); - set_weak_mods(i2c_buffer->weak_mods); -# ifndef NO_ACTION_ONESHOT - set_oneshot_mods(i2c_buffer->oneshot_mods); -# endif -# endif - -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - memcpy((void *)i2c_buffer->led_matrix, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix)); - led_matrix_set_suspend_state(i2c_buffer->led_suspend_state); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - memcpy((void *)i2c_buffer->rgb_matrix, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix)); - rgb_matrix_set_suspend_state(i2c_buffer->rgb_suspend_state); -# endif -} - -void transport_master_init(void) { i2c_init(); } - -void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); } - -#else // USE_SERIAL +#else // USE_I2C # include "serial.h" -typedef struct _Serial_s2m_buffer_t { - // TODO: if MATRIX_COLS > 8 change to uint8_t packed_matrix[] for pack/unpack - matrix_row_t smatrix[ROWS_PER_HAND]; - -# ifdef ENCODER_ENABLE - uint8_t encoder_state[NUMBER_OF_ENCODERS]; -# endif - -} Serial_s2m_buffer_t; - -typedef struct _Serial_m2s_buffer_t { -# ifdef SPLIT_MODS_ENABLE - uint8_t real_mods; - uint8_t weak_mods; -# ifndef NO_ACTION_ONESHOT - uint8_t oneshot_mods; -# endif -# endif -# ifndef DISABLE_SYNC_TIMER - uint32_t sync_timer; -# endif -# ifdef SPLIT_TRANSPORT_MIRROR - matrix_row_t mmatrix[ROWS_PER_HAND]; -# endif -# ifdef BACKLIGHT_ENABLE - uint8_t backlight_level; -# endif -# ifdef WPM_ENABLE - uint8_t current_wpm; -# endif -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - led_eeconfig_t led_matrix; - bool led_suspend_state; -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - rgb_config_t rgb_matrix; - bool rgb_suspend_state; -# endif -} Serial_m2s_buffer_t; - -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) -// When MCUs on both sides drive their respective RGB LED chains, -// it is necessary to synchronize, so it is necessary to communicate RGB -// information. In that case, define RGBLIGHT_SPLIT with info on the number -// of LEDs on each half. -// -// Otherwise, if the master side MCU drives both sides RGB LED chains, -// there is no need to communicate. - -typedef struct _Serial_rgblight_t { - rgblight_syncinfo_t rgblight_sync; -} Serial_rgblight_t; +static split_shared_memory_t shared_memory; +split_shared_memory_t *const split_shmem = &shared_memory; -volatile Serial_rgblight_t serial_rgblight = {}; -uint8_t volatile status_rgblight = 0; -# endif - -volatile Serial_s2m_buffer_t serial_s2m_buffer = {}; -volatile Serial_m2s_buffer_t serial_m2s_buffer = {}; -uint8_t volatile status0 = 0; - -enum serial_transaction_id { - GET_SLAVE_MATRIX = 0, -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - PUT_RGBLIGHT, -# endif -}; - -SSTD_t transactions[] = { - [GET_SLAVE_MATRIX] = - { - (uint8_t *)&status0, - sizeof(serial_m2s_buffer), - (uint8_t *)&serial_m2s_buffer, - sizeof(serial_s2m_buffer), - (uint8_t *)&serial_s2m_buffer, - }, -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - [PUT_RGBLIGHT] = - { - (uint8_t *)&status_rgblight, sizeof(serial_rgblight), (uint8_t *)&serial_rgblight, 0, NULL // no slave to master transfer - }, -# endif -}; - -void transport_master_init(void) { soft_serial_initiator_init(transactions, TID_LIMIT(transactions)); } - -void transport_slave_init(void) { soft_serial_target_init(transactions, TID_LIMIT(transactions)); } +void transport_master_init(void) { soft_serial_initiator_init(); } +void transport_slave_init(void) { soft_serial_target_init(); } -# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) - -// rgblight synchronization information communication. - -void transport_rgblight_master(void) { - if (rgblight_get_change_flags()) { - rgblight_get_syncinfo((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync); - if (soft_serial_transaction(PUT_RGBLIGHT) == TRANSACTION_END) { - rgblight_clear_change_flags(); - } - } -} - -void transport_rgblight_slave(void) { - if (status_rgblight == TRANSACTION_ACCEPTED) { - rgblight_update_sync((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync, false); - status_rgblight = TRANSACTION_END; +bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) { + split_transaction_desc_t *trans = &split_transaction_table[id]; + if (initiator2target_length > 0) { + size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length; + memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len); } -} -# else -# define transport_rgblight_master() -# define transport_rgblight_slave() -# endif - -bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { -# ifndef SERIAL_USE_MULTI_TRANSACTION - if (soft_serial_transaction() != TRANSACTION_END) { - return false; - } -# else - transport_rgblight_master(); - if (soft_serial_transaction(GET_SLAVE_MATRIX) != TRANSACTION_END) { + if (soft_serial_transaction(id) != TRANSACTION_END) { return false; } -# endif - // TODO: if MATRIX_COLS > 8 change to unpack() - for (int i = 0; i < ROWS_PER_HAND; ++i) { - slave_matrix[i] = serial_s2m_buffer.smatrix[i]; -# ifdef SPLIT_TRANSPORT_MIRROR - serial_m2s_buffer.mmatrix[i] = master_matrix[i]; -# endif + if (target2initiator_length > 0) { + size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length; + memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len); } -# ifdef BACKLIGHT_ENABLE - // Write backlight level for slave to read - serial_m2s_buffer.backlight_level = is_backlight_enabled() ? get_backlight_level() : 0; -# endif - -# ifdef ENCODER_ENABLE - encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state); -# endif - -# ifdef WPM_ENABLE - // Write wpm to slave - serial_m2s_buffer.current_wpm = get_current_wpm(); -# endif - -# ifdef SPLIT_MODS_ENABLE - serial_m2s_buffer.real_mods = get_mods(); - serial_m2s_buffer.weak_mods = get_weak_mods(); -# ifndef NO_ACTION_ONESHOT - serial_m2s_buffer.oneshot_mods = get_oneshot_mods(); -# endif -# endif - -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - serial_m2s_buffer.led_matrix = led_matrix_eeconfig; - serial_m2s_buffer.led_suspend_state = led_matrix_get_suspend_state(); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - serial_m2s_buffer.rgb_matrix = rgb_matrix_config; - serial_m2s_buffer.rgb_suspend_state = rgb_matrix_get_suspend_state(); -# endif - -# ifndef DISABLE_SYNC_TIMER - serial_m2s_buffer.sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET; -# endif return true; } -void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { - transport_rgblight_slave(); -# ifndef DISABLE_SYNC_TIMER - sync_timer_update(serial_m2s_buffer.sync_timer); -# endif - - // TODO: if MATRIX_COLS > 8 change to pack() - for (int i = 0; i < ROWS_PER_HAND; ++i) { - serial_s2m_buffer.smatrix[i] = slave_matrix[i]; -# ifdef SPLIT_TRANSPORT_MIRROR - master_matrix[i] = serial_m2s_buffer.mmatrix[i]; -# endif - } -# ifdef BACKLIGHT_ENABLE - backlight_set(serial_m2s_buffer.backlight_level); -# endif - -# ifdef ENCODER_ENABLE - encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state); -# endif +#endif // USE_I2C -# ifdef WPM_ENABLE - set_current_wpm(serial_m2s_buffer.current_wpm); -# endif - -# ifdef SPLIT_MODS_ENABLE - set_mods(serial_m2s_buffer.real_mods); - set_weak_mods(serial_m2s_buffer.weak_mods); -# ifndef NO_ACTION_ONESHOT - set_oneshot_mods(serial_m2s_buffer.oneshot_mods); -# endif -# endif - -# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) - led_matrix_eeconfig = serial_m2s_buffer.led_matrix; - led_matrix_set_suspend_state(serial_m2s_buffer.led_suspend_state); -# endif -# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) - rgb_matrix_config = serial_m2s_buffer.rgb_matrix; - rgb_matrix_set_suspend_state(serial_m2s_buffer.rgb_suspend_state); -# endif -} +bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { return transactions_master(master_matrix, slave_matrix); } -#endif +void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { transactions_slave(master_matrix, slave_matrix); }
\ No newline at end of file diff --git a/quantum/split_common/transport.h b/quantum/split_common/transport.h index a9f66301bf..2e07f6b25c 100644 --- a/quantum/split_common/transport.h +++ b/quantum/split_common/transport.h @@ -1,10 +1,175 @@ +/* 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 "progmem.h" +#include "action_layer.h" #include "matrix.h" +#ifndef RPC_M2S_BUFFER_SIZE +# define RPC_M2S_BUFFER_SIZE 32 +#endif // RPC_M2S_BUFFER_SIZE + +#ifndef RPC_S2M_BUFFER_SIZE +# define RPC_S2M_BUFFER_SIZE 32 +#endif // RPC_S2M_BUFFER_SIZE + void transport_master_init(void); void transport_slave_init(void); // returns false if valid data not received from slave bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); + +bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length); + +#ifdef ENCODER_ENABLE +# include "encoder.h" +# define NUMBER_OF_ENCODERS (sizeof((pin_t[])ENCODERS_PAD_A) / sizeof(pin_t)) +#endif // ENCODER_ENABLE + +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif // BACKLIGHT_ENABLE + +#ifdef RGBLIGHT_ENABLE +# include "rgblight.h" +#endif // RGBLIGHT_ENABLE + +typedef struct _split_slave_matrix_sync_t { + uint8_t checksum; + matrix_row_t matrix[(MATRIX_ROWS) / 2]; +} split_slave_matrix_sync_t; + +#ifdef SPLIT_TRANSPORT_MIRROR +typedef struct _split_master_matrix_sync_t { + matrix_row_t matrix[(MATRIX_ROWS) / 2]; +} split_master_matrix_sync_t; +#endif // SPLIT_TRANSPORT_MIRROR + +#ifdef ENCODER_ENABLE +typedef struct _split_slave_encoder_sync_t { + uint8_t checksum; + uint8_t state[NUMBER_OF_ENCODERS]; +} split_slave_encoder_sync_t; +#endif // ENCODER_ENABLE + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) +typedef struct _split_layers_sync_t { + layer_state_t layer_state; + layer_state_t default_layer_state; +} split_layers_sync_t; +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) +# include "led_matrix.h" + +typedef struct _led_matrix_sync_t { + led_eeconfig_t led_matrix; + bool led_suspend_state; +} led_matrix_sync_t; +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) +# include "rgb_matrix.h" + +typedef struct _rgb_matrix_sync_t { + rgb_config_t rgb_matrix; + bool rgb_suspend_state; +} rgb_matrix_sync_t; +#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +#ifdef SPLIT_MODS_ENABLE +typedef struct _split_mods_sync_t { + uint8_t real_mods; + uint8_t weak_mods; +# ifndef NO_ACTION_ONESHOT + uint8_t oneshot_mods; +# endif // NO_ACTION_ONESHOT +} split_mods_sync_t; +#endif // SPLIT_MODS_ENABLE + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +typedef struct _rpc_sync_info_t { + int8_t transaction_id; + uint8_t m2s_length; + uint8_t s2m_length; +} rpc_sync_info_t; +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + +typedef struct _split_shared_memory_t { +#ifdef USE_I2C + int8_t transaction_id; +#endif // USE_I2C + + split_slave_matrix_sync_t smatrix; + +#ifdef SPLIT_TRANSPORT_MIRROR + split_master_matrix_sync_t mmatrix; +#endif // SPLIT_TRANSPORT_MIRROR + +#ifdef ENCODER_ENABLE + split_slave_encoder_sync_t encoders; +#endif // ENCODER_ENABLE + +#ifndef DISABLE_SYNC_TIMER + uint32_t sync_timer; +#endif // DISABLE_SYNC_TIMER + +#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + split_layers_sync_t layers; +#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE) + +#ifdef SPLIT_LED_STATE_ENABLE + uint8_t led_state; +#endif // SPLIT_LED_STATE_ENABLE + +#ifdef SPLIT_MODS_ENABLE + split_mods_sync_t mods; +#endif // SPLIT_MODS_ENABLE + +#ifdef BACKLIGHT_ENABLE + uint8_t backlight_level; +#endif // BACKLIGHT_ENABLE + +#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + rgblight_syncinfo_t rgblight_sync; +#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) + +#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + led_matrix_sync_t led_matrix_sync; +#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT) + +#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + rgb_matrix_sync_t rgb_matrix_sync; +#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT) + +#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + uint8_t current_wpm; +#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE) + +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) + rpc_sync_info_t rpc_info; + uint8_t rpc_m2s_buffer[RPC_M2S_BUFFER_SIZE]; + uint8_t rpc_s2m_buffer[RPC_S2M_BUFFER_SIZE]; +#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +} split_shared_memory_t; + +extern split_shared_memory_t *const split_shmem;
\ No newline at end of file |