aboutsummaryrefslogtreecommitdiff
path: root/quantum
diff options
context:
space:
mode:
Diffstat (limited to 'quantum')
-rw-r--r--quantum/crc.c59
-rw-r--r--quantum/crc.h44
-rw-r--r--quantum/debounce.h2
-rw-r--r--quantum/debounce/asym_eager_defer_pk.c171
-rw-r--r--quantum/debounce/none.c31
-rw-r--r--quantum/debounce/sym_defer_g.c26
-rw-r--r--quantum/debounce/sym_defer_pk.c67
-rw-r--r--quantum/debounce/sym_eager_pk.c72
-rw-r--r--quantum/debounce/sym_eager_pr.c76
-rw-r--r--quantum/debounce/tests/asym_eager_defer_pk_tests.cpp374
-rw-r--r--quantum/debounce/tests/debounce_test_common.cpp229
-rw-r--r--quantum/debounce/tests/debounce_test_common.h83
-rw-r--r--quantum/debounce/tests/rules.mk44
-rw-r--r--quantum/debounce/tests/sym_defer_g_tests.cpp223
-rw-r--r--quantum/debounce/tests/sym_defer_pk_tests.cpp225
-rw-r--r--quantum/debounce/tests/sym_eager_pk_tests.cpp237
-rw-r--r--quantum/debounce/tests/sym_eager_pr_tests.cpp280
-rw-r--r--quantum/debounce/tests/testlist.mk6
-rw-r--r--quantum/led_matrix.c34
-rw-r--r--quantum/matrix.c79
-rw-r--r--quantum/quantum.h4
-rw-r--r--quantum/rgb_matrix.c34
-rw-r--r--quantum/rgb_matrix.h2
-rw-r--r--quantum/rgb_matrix_animations/jellybean_raindrops_anim.h2
-rw-r--r--quantum/rgb_matrix_drivers.c16
-rw-r--r--quantum/split_common/matrix.c83
-rw-r--r--quantum/split_common/post_config.h9
-rw-r--r--quantum/split_common/transaction_id_define.h94
-rw-r--r--quantum/split_common/transactions.c670
-rw-r--r--quantum/split_common/transactions.h54
-rw-r--r--quantum/split_common/transport.c484
-rw-r--r--quantum/split_common/transport.h165
32 files changed, 3349 insertions, 630 deletions
diff --git a/quantum/crc.c b/quantum/crc.c
new file mode 100644
index 000000000..0d8b9d601
--- /dev/null
+++ b/quantum/crc.c
@@ -0,0 +1,59 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "crc.h"
18
19__attribute__((weak)) void crc_init(void){
20 /* Software implementation nothing todo here. */
21};
22
23#if defined(CRC8_USE_TABLE)
24/**
25 * Static table used for the table_driven implementation.
26 */
27static 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,
28 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};
29
30__attribute__((weak)) uint8_t crc8(const void *data, size_t data_len) {
31 const uint8_t *d = (const uint8_t *)data;
32 crc_t crc = 0xff;
33 size_t tbl_idx;
34
35 while (data_len--) {
36 tbl_idx = crc ^ *d;
37 crc = crc_table[tbl_idx] & 0xff;
38 d++;
39 }
40 return crc & 0xff;
41}
42#else
43__attribute__((weak)) uint8_t crc8(const void *data, size_t data_len) {
44 const uint8_t *d = (const uint8_t *)data;
45 crc_t crc = 0xff;
46 size_t i, j;
47
48 for (i = 0; i < data_len; i++) {
49 crc ^= d[i];
50 for (j = 0; j < 8; j++) {
51 if ((crc & 0x80) != 0)
52 crc = (crc_t)((crc << 1) ^ 0x31);
53 else
54 crc <<= 1;
55 }
56 }
57 return crc;
58}
59#endif \ No newline at end of file
diff --git a/quantum/crc.h b/quantum/crc.h
new file mode 100644
index 000000000..c17f5888e
--- /dev/null
+++ b/quantum/crc.h
@@ -0,0 +1,44 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#pragma once
18
19#include "quantum.h"
20
21/**
22 * The type of the CRC values.
23 *
24 * This type must be big enough to contain at least 8 bits.
25 */
26#if defined(CRC8_OPTIMIZE_SPEED)
27typedef uint_fast8_t crc_t;
28#else
29typedef uint_least8_t crc_t;
30#endif
31
32/**
33 * Initialize crc subsystem.
34 */
35__attribute__((weak)) void crc_init(void);
36
37/**
38 * Generate CRC8 value from given data.
39 *
40 * \param[in] data Pointer to a buffer of \a data_len bytes.
41 * \param[in] data_len Number of bytes in the \a data buffer.
42 * \return The calculated crc value.
43 */
44__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 9ca05c682..504386828 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
9bool debounce_active(void); 9bool debounce_active(void);
10 10
11void debounce_init(uint8_t num_rows); 11void debounce_init(uint8_t num_rows);
12
13void 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 000000000..24380dc5e
--- /dev/null
+++ b/quantum/debounce/asym_eager_defer_pk.c
@@ -0,0 +1,171 @@
1/*
2 * Copyright 2017 Alex Ong <the.onga@gmail.com>
3 * Copyright 2020 Andrei Purdea <andrei@purdea.ro>
4 * Copyright 2021 Simon Arlott
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21Basic symmetric per-key algorithm. Uses an 8-bit counter per key.
22When no state changes have occured for DEBOUNCE milliseconds, we push the state.
23*/
24
25#include "matrix.h"
26#include "timer.h"
27#include "quantum.h"
28#include <stdlib.h>
29
30#ifdef PROTOCOL_CHIBIOS
31# if CH_CFG_USE_MEMCORE == FALSE
32# 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.
33# endif
34#endif
35
36#ifndef DEBOUNCE
37# define DEBOUNCE 5
38#endif
39
40// Maximum debounce: 127ms
41#if DEBOUNCE > 127
42# undef DEBOUNCE
43# define DEBOUNCE 127
44#endif
45
46#define ROW_SHIFTER ((matrix_row_t)1)
47
48typedef struct {
49 bool pressed : 1;
50 uint8_t time : 7;
51} debounce_counter_t;
52
53#if DEBOUNCE > 0
54static debounce_counter_t *debounce_counters;
55static fast_timer_t last_time;
56static bool counters_need_update;
57static bool matrix_need_update;
58
59#define DEBOUNCE_ELAPSED 0
60
61static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time);
62static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows);
63
64// we use num_rows rather than MATRIX_ROWS to support split keyboards
65void debounce_init(uint8_t num_rows) {
66 debounce_counters = malloc(num_rows * MATRIX_COLS * sizeof(debounce_counter_t));
67 int i = 0;
68 for (uint8_t r = 0; r < num_rows; r++) {
69 for (uint8_t c = 0; c < MATRIX_COLS; c++) {
70 debounce_counters[i++].time = DEBOUNCE_ELAPSED;
71 }
72 }
73}
74
75void debounce_free(void) {
76 free(debounce_counters);
77 debounce_counters = NULL;
78}
79
80void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
81 bool updated_last = false;
82
83 if (counters_need_update) {
84 fast_timer_t now = timer_read_fast();
85 fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time);
86
87 last_time = now;
88 updated_last = true;
89 if (elapsed_time > UINT8_MAX) {
90 elapsed_time = UINT8_MAX;
91 }
92
93 if (elapsed_time > 0) {
94 update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, elapsed_time);
95 }
96 }
97
98 if (changed || matrix_need_update) {
99 if (!updated_last) {
100 last_time = timer_read_fast();
101 }
102
103 transfer_matrix_values(raw, cooked, num_rows);
104 }
105}
106
107static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time) {
108 debounce_counter_t *debounce_pointer = debounce_counters;
109
110 counters_need_update = false;
111 matrix_need_update = false;
112
113 for (uint8_t row = 0; row < num_rows; row++) {
114 for (uint8_t col = 0; col < MATRIX_COLS; col++) {
115 matrix_row_t col_mask = (ROW_SHIFTER << col);
116
117 if (debounce_pointer->time != DEBOUNCE_ELAPSED) {
118 if (debounce_pointer->time <= elapsed_time) {
119 debounce_pointer->time = DEBOUNCE_ELAPSED;
120
121 if (debounce_pointer->pressed) {
122 // key-down: eager
123 matrix_need_update = true;
124 } else {
125 // key-up: defer
126 cooked[row] = (cooked[row] & ~col_mask) | (raw[row] & col_mask);
127 }
128 } else {
129 debounce_pointer->time -= elapsed_time;
130 counters_need_update = true;
131 }
132 }
133 debounce_pointer++;
134 }
135 }
136}
137
138static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) {
139 debounce_counter_t *debounce_pointer = debounce_counters;
140
141 for (uint8_t row = 0; row < num_rows; row++) {
142 matrix_row_t delta = raw[row] ^ cooked[row];
143 for (uint8_t col = 0; col < MATRIX_COLS; col++) {
144 matrix_row_t col_mask = (ROW_SHIFTER << col);
145
146 if (delta & col_mask) {
147 if (debounce_pointer->time == DEBOUNCE_ELAPSED) {
148 debounce_pointer->pressed = (raw[row] & col_mask);
149 debounce_pointer->time = DEBOUNCE;
150 counters_need_update = true;
151
152 if (debounce_pointer->pressed) {
153 // key-down: eager
154 cooked[row] ^= col_mask;
155 }
156 }
157 } else if (debounce_pointer->time != DEBOUNCE_ELAPSED) {
158 if (!debounce_pointer->pressed) {
159 // key-up: defer
160 debounce_pointer->time = DEBOUNCE_ELAPSED;
161 }
162 }
163 debounce_pointer++;
164 }
165 }
166}
167
168bool debounce_active(void) { return true; }
169#else
170# include "none.c"
171#endif
diff --git a/quantum/debounce/none.c b/quantum/debounce/none.c
new file mode 100644
index 000000000..b03892bc5
--- /dev/null
+++ b/quantum/debounce/none.c
@@ -0,0 +1,31 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "matrix.h"
18#include "quantum.h"
19#include <stdlib.h>
20
21void debounce_init(uint8_t num_rows) {}
22
23void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
24 for (int i = 0; i < num_rows; i++) {
25 cooked[i] = raw[i];
26 }
27}
28
29bool debounce_active(void) { return false; }
30
31void debounce_free(void) {}
diff --git a/quantum/debounce/sym_defer_g.c b/quantum/debounce/sym_defer_g.c
index 3ed9055d2..fbefd55ed 100644
--- a/quantum/debounce/sym_defer_g.c
+++ b/quantum/debounce/sym_defer_g.c
@@ -1,5 +1,6 @@
1/* 1/*
2Copyright 2017 Alex Ong<the.onga@gmail.com> 2Copyright 2017 Alex Ong<the.onga@gmail.com>
3Copyright 2021 Simon Arlott
3This program is free software: you can redistribute it and/or modify 4This program is free software: you can redistribute it and/or modify
4it under the terms of the GNU General Public License as published by 5it under the terms of the GNU General Public License as published by
5the Free Software Foundation, either version 2 of the License, or 6the 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.
23# define DEBOUNCE 5 24# define DEBOUNCE 5
24#endif 25#endif
25 26
26void debounce_init(uint8_t num_rows) {} 27#if DEBOUNCE > 0
27static bool debouncing = false; 28static bool debouncing = false;
29static fast_timer_t debouncing_time;
28 30
29#if DEBOUNCE > 0 31void debounce_init(uint8_t num_rows) {}
30static uint16_t debouncing_time; 32
31void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { 33void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
32 if (changed) { 34 if (changed) {
33 debouncing = true; 35 debouncing = true;
34 debouncing_time = timer_read(); 36 debouncing_time = timer_read_fast();
35 } 37 }
36 38
37 if (debouncing && timer_elapsed(debouncing_time) > DEBOUNCE) { 39 if (debouncing && timer_elapsed_fast(debouncing_time) >= DEBOUNCE) {
38 for (int i = 0; i < num_rows; i++) { 40 for (int i = 0; i < num_rows; i++) {
39 cooked[i] = raw[i]; 41 cooked[i] = raw[i];
40 } 42 }
41 debouncing = false; 43 debouncing = false;
42 } 44 }
43} 45}
44#else // no debouncing.
45void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
46 for (int i = 0; i < num_rows; i++) {
47 cooked[i] = raw[i];
48 }
49}
50#endif
51 46
52bool debounce_active(void) { return debouncing; } 47bool debounce_active(void) { return debouncing; }
48
49void debounce_free(void) {}
50#else // no debouncing.
51# include "none.c"
52#endif
diff --git a/quantum/debounce/sym_defer_pk.c b/quantum/debounce/sym_defer_pk.c
index 60513f98e..626a9be84 100644
--- a/quantum/debounce/sym_defer_pk.c
+++ b/quantum/debounce/sym_defer_pk.c
@@ -1,6 +1,7 @@
1/* 1/*
2Copyright 2017 Alex Ong<the.onga@gmail.com> 2Copyright 2017 Alex Ong<the.onga@gmail.com>
3Copyright 2020 Andrei Purdea<andrei@purdea.ro> 3Copyright 2020 Andrei Purdea<andrei@purdea.ro>
4Copyright 2021 Simon Arlott
4This program is free software: you can redistribute it and/or modify 5This program is free software: you can redistribute it and/or modify
5it under the terms of the GNU General Public License as published by 6it under the terms of the GNU General Public License as published by
6the Free Software Foundation, either version 2 of the License, or 7the 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.
33# define DEBOUNCE 5 34# define DEBOUNCE 5
34#endif 35#endif
35 36
37// Maximum debounce: 255ms
38#if DEBOUNCE > UINT8_MAX
39# undef DEBOUNCE
40# define DEBOUNCE UINT8_MAX
41#endif
42
36#define ROW_SHIFTER ((matrix_row_t)1) 43#define ROW_SHIFTER ((matrix_row_t)1)
37 44
38#define debounce_counter_t uint8_t 45typedef uint8_t debounce_counter_t;
39 46
47#if DEBOUNCE > 0
40static debounce_counter_t *debounce_counters; 48static debounce_counter_t *debounce_counters;
49static fast_timer_t last_time;
41static bool counters_need_update; 50static bool counters_need_update;
42 51
43#define DEBOUNCE_ELAPSED 251 52#define DEBOUNCE_ELAPSED 0
44#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1)
45
46static uint8_t wrapping_timer_read(void) {
47 static uint16_t time = 0;
48 static uint8_t last_result = 0;
49 uint16_t new_time = timer_read();
50 uint16_t diff = new_time - time;
51 time = new_time;
52 last_result = (last_result + diff) % (MAX_DEBOUNCE + 1);
53 return last_result;
54}
55 53
56void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); 54static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time);
57void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); 55static void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows);
58 56
59// we use num_rows rather than MATRIX_ROWS to support split keyboards 57// we use num_rows rather than MATRIX_ROWS to support split keyboards
60void debounce_init(uint8_t num_rows) { 58void debounce_init(uint8_t num_rows) {
@@ -67,27 +65,49 @@ void debounce_init(uint8_t num_rows) {
67 } 65 }
68} 66}
69 67
68void debounce_free(void) {
69 free(debounce_counters);
70 debounce_counters = NULL;
71}
72
70void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { 73void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
71 uint8_t current_time = wrapping_timer_read(); 74 bool updated_last = false;
75
72 if (counters_need_update) { 76 if (counters_need_update) {
73 update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, current_time); 77 fast_timer_t now = timer_read_fast();
78 fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time);
79
80 last_time = now;
81 updated_last = true;
82 if (elapsed_time > UINT8_MAX) {
83 elapsed_time = UINT8_MAX;
84 }
85
86 if (elapsed_time > 0) {
87 update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, elapsed_time);
88 }
74 } 89 }
75 90
76 if (changed) { 91 if (changed) {
77 start_debounce_counters(raw, cooked, num_rows, current_time); 92 if (!updated_last) {
93 last_time = timer_read_fast();
94 }
95
96 start_debounce_counters(raw, cooked, num_rows);
78 } 97 }
79} 98}
80 99
81void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { 100static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time) {
82 counters_need_update = false; 101 counters_need_update = false;
83 debounce_counter_t *debounce_pointer = debounce_counters; 102 debounce_counter_t *debounce_pointer = debounce_counters;
84 for (uint8_t row = 0; row < num_rows; row++) { 103 for (uint8_t row = 0; row < num_rows; row++) {
85 for (uint8_t col = 0; col < MATRIX_COLS; col++) { 104 for (uint8_t col = 0; col < MATRIX_COLS; col++) {
86 if (*debounce_pointer != DEBOUNCE_ELAPSED) { 105 if (*debounce_pointer != DEBOUNCE_ELAPSED) {
87 if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { 106 if (*debounce_pointer <= elapsed_time) {
88 *debounce_pointer = DEBOUNCE_ELAPSED; 107 *debounce_pointer = DEBOUNCE_ELAPSED;
89 cooked[row] = (cooked[row] & ~(ROW_SHIFTER << col)) | (raw[row] & (ROW_SHIFTER << col)); 108 cooked[row] = (cooked[row] & ~(ROW_SHIFTER << col)) | (raw[row] & (ROW_SHIFTER << col));
90 } else { 109 } else {
110 *debounce_pointer -= elapsed_time;
91 counters_need_update = true; 111 counters_need_update = true;
92 } 112 }
93 } 113 }
@@ -96,14 +116,14 @@ void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix
96 } 116 }
97} 117}
98 118
99void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { 119static void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) {
100 debounce_counter_t *debounce_pointer = debounce_counters; 120 debounce_counter_t *debounce_pointer = debounce_counters;
101 for (uint8_t row = 0; row < num_rows; row++) { 121 for (uint8_t row = 0; row < num_rows; row++) {
102 matrix_row_t delta = raw[row] ^ cooked[row]; 122 matrix_row_t delta = raw[row] ^ cooked[row];
103 for (uint8_t col = 0; col < MATRIX_COLS; col++) { 123 for (uint8_t col = 0; col < MATRIX_COLS; col++) {
104 if (delta & (ROW_SHIFTER << col)) { 124 if (delta & (ROW_SHIFTER << col)) {
105 if (*debounce_pointer == DEBOUNCE_ELAPSED) { 125 if (*debounce_pointer == DEBOUNCE_ELAPSED) {
106 *debounce_pointer = current_time; 126 *debounce_pointer = DEBOUNCE;
107 counters_need_update = true; 127 counters_need_update = true;
108 } 128 }
109 } else { 129 } else {
@@ -115,3 +135,6 @@ void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t
115} 135}
116 136
117bool debounce_active(void) { return true; } 137bool debounce_active(void) { return true; }
138#else
139# include "none.c"
140#endif
diff --git a/quantum/debounce/sym_eager_pk.c b/quantum/debounce/sym_eager_pk.c
index e66cf92d7..15a3242e6 100644
--- a/quantum/debounce/sym_eager_pk.c
+++ b/quantum/debounce/sym_eager_pk.c
@@ -1,5 +1,6 @@
1/* 1/*
2Copyright 2017 Alex Ong<the.onga@gmail.com> 2Copyright 2017 Alex Ong<the.onga@gmail.com>
3Copyright 2021 Simon Arlott
3This program is free software: you can redistribute it and/or modify 4This program is free software: you can redistribute it and/or modify
4it under the terms of the GNU General Public License as published by 5it under the terms of the GNU General Public License as published by
5the Free Software Foundation, either version 2 of the License, or 6the Free Software Foundation, either version 2 of the License, or
@@ -33,29 +34,26 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred.
33# define DEBOUNCE 5 34# define DEBOUNCE 5
34#endif 35#endif
35 36
37// Maximum debounce: 255ms
38#if DEBOUNCE > UINT8_MAX
39# undef DEBOUNCE
40# define DEBOUNCE UINT8_MAX
41#endif
42
36#define ROW_SHIFTER ((matrix_row_t)1) 43#define ROW_SHIFTER ((matrix_row_t)1)
37 44
38#define debounce_counter_t uint8_t 45typedef uint8_t debounce_counter_t;
39 46
47#if DEBOUNCE > 0
40static debounce_counter_t *debounce_counters; 48static debounce_counter_t *debounce_counters;
49static fast_timer_t last_time;
41static bool counters_need_update; 50static bool counters_need_update;
42static bool matrix_need_update; 51static bool matrix_need_update;
43 52
44#define DEBOUNCE_ELAPSED 251 53#define DEBOUNCE_ELAPSED 0
45#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1)
46
47static uint8_t wrapping_timer_read(void) {
48 static uint16_t time = 0;
49 static uint8_t last_result = 0;
50 uint16_t new_time = timer_read();
51 uint16_t diff = new_time - time;
52 time = new_time;
53 last_result = (last_result + diff) % (MAX_DEBOUNCE + 1);
54 return last_result;
55}
56 54
57void update_debounce_counters(uint8_t num_rows, uint8_t current_time); 55static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time);
58void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); 56static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows);
59 57
60// we use num_rows rather than MATRIX_ROWS to support split keyboards 58// we use num_rows rather than MATRIX_ROWS to support split keyboards
61void debounce_init(uint8_t num_rows) { 59void debounce_init(uint8_t num_rows) {
@@ -68,27 +66,51 @@ void debounce_init(uint8_t num_rows) {
68 } 66 }
69} 67}
70 68
69void debounce_free(void) {
70 free(debounce_counters);
71 debounce_counters = NULL;
72}
73
71void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { 74void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
72 uint8_t current_time = wrapping_timer_read(); 75 bool updated_last = false;
76
73 if (counters_need_update) { 77 if (counters_need_update) {
74 update_debounce_counters(num_rows, current_time); 78 fast_timer_t now = timer_read_fast();
79 fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time);
80
81 last_time = now;
82 updated_last = true;
83 if (elapsed_time > UINT8_MAX) {
84 elapsed_time = UINT8_MAX;
85 }
86
87 if (elapsed_time > 0) {
88 update_debounce_counters(num_rows, elapsed_time);
89 }
75 } 90 }
76 91
77 if (changed || matrix_need_update) { 92 if (changed || matrix_need_update) {
78 transfer_matrix_values(raw, cooked, num_rows, current_time); 93 if (!updated_last) {
94 last_time = timer_read_fast();
95 }
96
97 transfer_matrix_values(raw, cooked, num_rows);
79 } 98 }
80} 99}
81 100
82// If the current time is > debounce counter, set the counter to enable input. 101// If the current time is > debounce counter, set the counter to enable input.
83void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { 102static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time) {
84 counters_need_update = false; 103 counters_need_update = false;
104 matrix_need_update = false;
85 debounce_counter_t *debounce_pointer = debounce_counters; 105 debounce_counter_t *debounce_pointer = debounce_counters;
86 for (uint8_t row = 0; row < num_rows; row++) { 106 for (uint8_t row = 0; row < num_rows; row++) {
87 for (uint8_t col = 0; col < MATRIX_COLS; col++) { 107 for (uint8_t col = 0; col < MATRIX_COLS; col++) {
88 if (*debounce_pointer != DEBOUNCE_ELAPSED) { 108 if (*debounce_pointer != DEBOUNCE_ELAPSED) {
89 if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { 109 if (*debounce_pointer <= elapsed_time) {
90 *debounce_pointer = DEBOUNCE_ELAPSED; 110 *debounce_pointer = DEBOUNCE_ELAPSED;
111 matrix_need_update = true;
91 } else { 112 } else {
113 *debounce_pointer -= elapsed_time;
92 counters_need_update = true; 114 counters_need_update = true;
93 } 115 }
94 } 116 }
@@ -98,8 +120,7 @@ void update_debounce_counters(uint8_t num_rows, uint8_t current_time) {
98} 120}
99 121
100// upload from raw_matrix to final matrix; 122// upload from raw_matrix to final matrix;
101void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { 123static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) {
102 matrix_need_update = false;
103 debounce_counter_t *debounce_pointer = debounce_counters; 124 debounce_counter_t *debounce_pointer = debounce_counters;
104 for (uint8_t row = 0; row < num_rows; row++) { 125 for (uint8_t row = 0; row < num_rows; row++) {
105 matrix_row_t delta = raw[row] ^ cooked[row]; 126 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
108 matrix_row_t col_mask = (ROW_SHIFTER << col); 129 matrix_row_t col_mask = (ROW_SHIFTER << col);
109 if (delta & col_mask) { 130 if (delta & col_mask) {
110 if (*debounce_pointer == DEBOUNCE_ELAPSED) { 131 if (*debounce_pointer == DEBOUNCE_ELAPSED) {
111 *debounce_pointer = current_time; 132 *debounce_pointer = DEBOUNCE;
112 counters_need_update = true; 133 counters_need_update = true;
113 existing_row ^= col_mask; // flip the bit. 134 existing_row ^= col_mask; // flip the bit.
114 } else {
115 matrix_need_update = true;
116 } 135 }
117 } 136 }
118 debounce_pointer++; 137 debounce_pointer++;
@@ -122,3 +141,6 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n
122} 141}
123 142
124bool debounce_active(void) { return true; } 143bool debounce_active(void) { return true; }
144#else
145# include "none.c"
146#endif
diff --git a/quantum/debounce/sym_eager_pr.c b/quantum/debounce/sym_eager_pr.c
index 20ccb46f1..2ad592c5a 100644
--- a/quantum/debounce/sym_eager_pr.c
+++ b/quantum/debounce/sym_eager_pr.c
@@ -1,5 +1,6 @@
1/* 1/*
2Copyright 2019 Alex Ong<the.onga@gmail.com> 2Copyright 2019 Alex Ong<the.onga@gmail.com>
3Copyright 2021 Simon Arlott
3This program is free software: you can redistribute it and/or modify 4This program is free software: you can redistribute it and/or modify
4it under the terms of the GNU General Public License as published by 5it under the terms of the GNU General Public License as published by
5the Free Software Foundation, either version 2 of the License, or 6the Free Software Foundation, either version 2 of the License, or
@@ -33,27 +34,25 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred.
33# define DEBOUNCE 5 34# define DEBOUNCE 5
34#endif 35#endif
35 36
36#define debounce_counter_t uint8_t 37// Maximum debounce: 255ms
38#if DEBOUNCE > UINT8_MAX
39# undef DEBOUNCE
40# define DEBOUNCE UINT8_MAX
41#endif
42
43typedef uint8_t debounce_counter_t;
44
45#if DEBOUNCE > 0
37static bool matrix_need_update; 46static bool matrix_need_update;
38 47
39static debounce_counter_t *debounce_counters; 48static debounce_counter_t *debounce_counters;
49static fast_timer_t last_time;
40static bool counters_need_update; 50static bool counters_need_update;
41 51
42#define DEBOUNCE_ELAPSED 251 52#define DEBOUNCE_ELAPSED 0
43#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1)
44
45static uint8_t wrapping_timer_read(void) {
46 static uint16_t time = 0;
47 static uint8_t last_result = 0;
48 uint16_t new_time = timer_read();
49 uint16_t diff = new_time - time;
50 time = new_time;
51 last_result = (last_result + diff) % (MAX_DEBOUNCE + 1);
52 return last_result;
53}
54 53
55void update_debounce_counters(uint8_t num_rows, uint8_t current_time); 54static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time);
56void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time); 55static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows);
57 56
58// we use num_rows rather than MATRIX_ROWS to support split keyboards 57// we use num_rows rather than MATRIX_ROWS to support split keyboards
59void debounce_init(uint8_t num_rows) { 58void debounce_init(uint8_t num_rows) {
@@ -63,27 +62,50 @@ void debounce_init(uint8_t num_rows) {
63 } 62 }
64} 63}
65 64
65void debounce_free(void) {
66 free(debounce_counters);
67 debounce_counters = NULL;
68}
69
66void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { 70void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
67 uint8_t current_time = wrapping_timer_read(); 71 bool updated_last = false;
68 bool needed_update = counters_need_update; 72
69 if (counters_need_update) { 73 if (counters_need_update) {
70 update_debounce_counters(num_rows, current_time); 74 fast_timer_t now = timer_read_fast();
75 fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time);
76
77 last_time = now;
78 updated_last = true;
79 if (elapsed_time > UINT8_MAX) {
80 elapsed_time = UINT8_MAX;
81 }
82
83 if (elapsed_time > 0) {
84 update_debounce_counters(num_rows, elapsed_time);
85 }
71 } 86 }
72 87
73 if (changed || (needed_update && !counters_need_update) || matrix_need_update) { 88 if (changed || matrix_need_update) {
74 transfer_matrix_values(raw, cooked, num_rows, current_time); 89 if (!updated_last) {
90 last_time = timer_read_fast();
91 }
92
93 transfer_matrix_values(raw, cooked, num_rows);
75 } 94 }
76} 95}
77 96
78// If the current time is > debounce counter, set the counter to enable input. 97// If the current time is > debounce counter, set the counter to enable input.
79void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { 98static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time) {
80 counters_need_update = false; 99 counters_need_update = false;
100 matrix_need_update = false;
81 debounce_counter_t *debounce_pointer = debounce_counters; 101 debounce_counter_t *debounce_pointer = debounce_counters;
82 for (uint8_t row = 0; row < num_rows; row++) { 102 for (uint8_t row = 0; row < num_rows; row++) {
83 if (*debounce_pointer != DEBOUNCE_ELAPSED) { 103 if (*debounce_pointer != DEBOUNCE_ELAPSED) {
84 if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { 104 if (*debounce_pointer <= elapsed_time) {
85 *debounce_pointer = DEBOUNCE_ELAPSED; 105 *debounce_pointer = DEBOUNCE_ELAPSED;
106 matrix_need_update = true;
86 } else { 107 } else {
108 *debounce_pointer -= elapsed_time;
87 counters_need_update = true; 109 counters_need_update = true;
88 } 110 }
89 } 111 }
@@ -92,8 +114,7 @@ void update_debounce_counters(uint8_t num_rows, uint8_t current_time) {
92} 114}
93 115
94// upload from raw_matrix to final matrix; 116// upload from raw_matrix to final matrix;
95void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { 117static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) {
96 matrix_need_update = false;
97 debounce_counter_t *debounce_pointer = debounce_counters; 118 debounce_counter_t *debounce_pointer = debounce_counters;
98 for (uint8_t row = 0; row < num_rows; row++) { 119 for (uint8_t row = 0; row < num_rows; row++) {
99 matrix_row_t existing_row = cooked[row]; 120 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
102 // determine new value basd on debounce pointer + raw value 123 // determine new value basd on debounce pointer + raw value
103 if (existing_row != raw_row) { 124 if (existing_row != raw_row) {
104 if (*debounce_pointer == DEBOUNCE_ELAPSED) { 125 if (*debounce_pointer == DEBOUNCE_ELAPSED) {
105 *debounce_pointer = current_time; 126 *debounce_pointer = DEBOUNCE;
106 cooked[row] = raw_row; 127 cooked[row] = raw_row;
107 counters_need_update = true; 128 counters_need_update = true;
108 } else {
109 matrix_need_update = true;
110 } 129 }
111 } 130 }
112 debounce_pointer++; 131 debounce_pointer++;
@@ -114,3 +133,6 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n
114} 133}
115 134
116bool debounce_active(void) { return true; } 135bool debounce_active(void) { return true; }
136#else
137# include "none.c"
138#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 000000000..fe374c3df
--- /dev/null
+++ b/quantum/debounce/tests/asym_eager_defer_pk_tests.cpp
@@ -0,0 +1,374 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18
19#include "debounce_test_common.h"
20
21TEST_F(DebounceTest, OneKeyShort1) {
22 addEvents({ /* Time, Inputs, Outputs */
23 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
24 /* Release key after 1ms delay */
25 {1, {{0, 1, UP}}, {}},
26
27 /*
28 * Until the eager timer on DOWN is observed to finish, the defer timer
29 * on UP can't start. There's no workaround for this because it's not
30 * possible to debounce an event that isn't being tracked.
31 *
32 * sym_defer_pk has the same problem but the test has to track that the
33 * key changed state so the DOWN timer is always allowed to finish
34 * before starting the UP timer.
35 */
36 {5, {}, {}},
37
38 {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */
39 /* Press key again after 1ms delay */
40 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
41 });
42 runEvents();
43}
44
45TEST_F(DebounceTest, OneKeyShort2) {
46 addEvents({ /* Time, Inputs, Outputs */
47 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
48 /* Release key after 2ms delay */
49 {2, {{0, 1, UP}}, {}},
50
51 {5, {}, {}}, /* See OneKeyShort1 */
52
53 {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */
54 /* Press key again after 1ms delay */
55 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
56 });
57 runEvents();
58}
59
60TEST_F(DebounceTest, OneKeyShort3) {
61 addEvents({ /* Time, Inputs, Outputs */
62 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
63 /* Release key after 3ms delay */
64 {3, {{0, 1, UP}}, {}},
65
66 {5, {}, {}}, /* See OneKeyShort1 */
67
68 {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */
69 /* Press key again after 1ms delay */
70 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
71 });
72 runEvents();
73}
74
75TEST_F(DebounceTest, OneKeyShort4) {
76 addEvents({ /* Time, Inputs, Outputs */
77 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
78 /* Release key after 4ms delay */
79 {4, {{0, 1, UP}}, {}},
80
81 {5, {}, {}}, /* See OneKeyShort1 */
82
83 {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */
84 /* Press key again after 1ms delay */
85 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
86 });
87 runEvents();
88}
89
90TEST_F(DebounceTest, OneKeyShort5) {
91 addEvents({ /* Time, Inputs, Outputs */
92 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
93
94 /* Release key after 5ms delay */
95 {5, {{0, 1, UP}}, {}},
96
97 {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */
98 /* Press key again after 1ms delay */
99 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
100 });
101 runEvents();
102}
103
104TEST_F(DebounceTest, OneKeyShort6) {
105 addEvents({ /* Time, Inputs, Outputs */
106 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
107
108 /* Release key after 6ms delay */
109 {6, {{0, 1, UP}}, {}},
110
111 {11, {}, {{0, 1, UP}}}, /* 5ms after UP at time 6 */
112 /* Press key again after 1ms delay */
113 {12, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
114 });
115 runEvents();
116}
117
118TEST_F(DebounceTest, OneKeyShort7) {
119 addEvents({ /* Time, Inputs, Outputs */
120 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
121
122 /* Release key after 7ms delay */
123 {7, {{0, 1, UP}}, {}},
124
125 {12, {}, {{0, 1, UP}}}, /* 5ms after UP at time 7 */
126 /* Press key again after 1ms delay */
127 {13, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
128 });
129 runEvents();
130}
131
132TEST_F(DebounceTest, OneKeyShort8) {
133 addEvents({ /* Time, Inputs, Outputs */
134 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
135 /* Release key after 1ms delay */
136 {1, {{0, 1, UP}}, {}},
137
138 {5, {}, {}}, /* See OneKeyShort1 */
139
140 {10, {}, {{0, 1, UP}}}, /* 5ms after UP at time 7 */
141 /* Press key again after 0ms delay (scan 2) */
142 {10, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
143 });
144 runEvents();
145}
146
147TEST_F(DebounceTest, OneKeyShort9) {
148 addEvents({ /* Time, Inputs, Outputs */
149 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
150 /* Release key after 1ms delay */
151 {1, {{0, 1, UP}}, {}},
152
153 {5, {}, {}}, /* See OneKeyShort1 */
154
155 /* Press key again after 0ms delay (same scan) before debounce finishes */
156 {10, {{0, 1, DOWN}}, {}},
157 });
158 runEvents();
159}
160
161TEST_F(DebounceTest, OneKeyBouncing1) {
162 addEvents({ /* Time, Inputs, Outputs */
163 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
164 {1, {{0, 1, UP}}, {}},
165 {2, {{0, 1, DOWN}}, {}},
166 {3, {{0, 1, UP}}, {}},
167 {4, {{0, 1, DOWN}}, {}},
168 {5, {{0, 1, UP}}, {}},
169 {6, {{0, 1, DOWN}}, {}},
170 {7, {{0, 1, UP}}, {}},
171 {8, {{0, 1, DOWN}}, {}},
172 {9, {{0, 1, UP}}, {}},
173 {10, {{0, 1, DOWN}}, {}},
174 {11, {{0, 1, UP}}, {}},
175 {12, {{0, 1, DOWN}}, {}},
176 {13, {{0, 1, UP}}, {}},
177 {14, {{0, 1, DOWN}}, {}},
178 {15, {{0, 1, UP}}, {}},
179
180 {20, {}, {{0, 1, UP}}},
181 /* Press key again after 1ms delay */
182 {21, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
183 });
184 runEvents();
185}
186
187TEST_F(DebounceTest, OneKeyBouncing2) {
188 addEvents({ /* Time, Inputs, Outputs */
189 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
190 /* Change twice in the same time period */
191 {1, {{0, 1, UP}}, {}},
192 {1, {{0, 1, DOWN}}, {}},
193 /* Change three times in the same time period */
194 {2, {{0, 1, UP}}, {}},
195 {2, {{0, 1, DOWN}}, {}},
196 {2, {{0, 1, UP}}, {}},
197 /* Change twice in the same time period */
198 {6, {{0, 1, DOWN}}, {}},
199 {6, {{0, 1, UP}}, {}},
200 /* Change three times in the same time period */
201 {7, {{0, 1, DOWN}}, {}},
202 {7, {{0, 1, UP}}, {}},
203 {7, {{0, 1, DOWN}}, {}},
204 /* Change twice in the same time period */
205 {8, {{0, 1, UP}}, {}},
206 {8, {{0, 1, DOWN}}, {}},
207 /* Change three times in the same time period */
208 {9, {{0, 1, UP}}, {}},
209 {9, {{0, 1, DOWN}}, {}},
210 {9, {{0, 1, UP}}, {}},
211
212 {14, {}, {{0, 1, UP}}},
213 /* Press key again after 1ms delay */
214 {15, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
215 });
216 runEvents();
217}
218
219TEST_F(DebounceTest, OneKeyLong) {
220 addEvents({ /* Time, Inputs, Outputs */
221 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
222
223 {25, {{0, 1, UP}}, {}},
224
225 {30, {}, {{0, 1, UP}}},
226
227 {50, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
228
229 {75, {{0, 1, UP}}, {}},
230
231 {80, {}, {{0, 1, UP}}},
232
233 {100, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
234 });
235 runEvents();
236}
237
238TEST_F(DebounceTest, TwoKeysShort) {
239 addEvents({ /* Time, Inputs, Outputs */
240 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
241 {1, {{0, 2, DOWN}}, {{0, 2, DOWN}}},
242 /* Release key after 2ms delay */
243 {2, {{0, 1, UP}}, {}},
244 {3, {{0, 2, UP}}, {}},
245
246 {5, {}, {}}, /* See OneKeyShort1 */
247 {6, {}, {}}, /* See OneKeyShort1 */
248
249 {10, {}, {{0, 1, UP}}}, /* 5ms+5ms after DOWN at time 0 */
250 /* Press key again after 1ms delay */
251 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}, {0, 2, UP}}}, /* 5ms+5ms after DOWN at time 0 */
252 {12, {{0, 2, DOWN}}, {{0, 2, DOWN}}}, /* 5ms+5ms after DOWN at time 0 */
253 });
254 runEvents();
255}
256
257
258TEST_F(DebounceTest, OneKeyDelayedScan1) {
259 addEvents({ /* Time, Inputs, Outputs */
260 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
261
262 /* Processing is very late, immediately release key */
263 {300, {{0, 1, UP}}, {}},
264
265 {305, {}, {{0, 1, UP}}},
266 });
267 time_jumps_ = true;
268 runEvents();
269}
270
271TEST_F(DebounceTest, OneKeyDelayedScan2) {
272 addEvents({ /* Time, Inputs, Outputs */
273 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
274
275 /* Processing is very late, immediately release key */
276 {300, {{0, 1, UP}}, {}},
277
278 /* Processing is very late again */
279 {600, {}, {{0, 1, UP}}},
280 });
281 time_jumps_ = true;
282 runEvents();
283}
284
285TEST_F(DebounceTest, OneKeyDelayedScan3) {
286 addEvents({ /* Time, Inputs, Outputs */
287 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
288
289 /* Processing is very late */
290 {300, {}, {}},
291 /* Release key after 1ms */
292 {301, {{0, 1, UP}}, {}},
293
294 {306, {}, {{0, 1, UP}}},
295 });
296 time_jumps_ = true;
297 runEvents();
298}
299
300TEST_F(DebounceTest, OneKeyDelayedScan4) {
301 addEvents({ /* Time, Inputs, Outputs */
302 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
303
304 /* Processing is very late */
305 {300, {}, {}},
306 /* Release key after 1ms */
307 {301, {{0, 1, UP}}, {}},
308
309 /* Processing is very late again */
310 {600, {}, {{0, 1, UP}}},
311 });
312 time_jumps_ = true;
313 runEvents();
314}
315
316TEST_F(DebounceTest, OneKeyDelayedScan5) {
317 addEvents({ /* Time, Inputs, Outputs */
318 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
319
320 {5, {{0, 1, UP}}, {}},
321
322 /* Processing is very late */
323 {300, {}, {{0, 1, UP}}},
324 /* Immediately press key again */
325 {300, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
326 });
327 time_jumps_ = true;
328 runEvents();
329}
330
331TEST_F(DebounceTest, OneKeyDelayedScan6) {
332 addEvents({ /* Time, Inputs, Outputs */
333 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
334
335 {5, {{0, 1, UP}}, {}},
336
337 /* Processing is very late */
338 {300, {}, {{0, 1, UP}}},
339
340 /* Press key again after 1ms */
341 {301, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
342 });
343 time_jumps_ = true;
344 runEvents();
345}
346
347TEST_F(DebounceTest, OneKeyDelayedScan7) {
348 addEvents({ /* Time, Inputs, Outputs */
349 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
350
351 {5, {{0, 1, UP}}, {}},
352
353 /* Press key again before debounce expires */
354 {300, {{0, 1, DOWN}}, {}},
355 });
356 time_jumps_ = true;
357 runEvents();
358}
359
360TEST_F(DebounceTest, OneKeyDelayedScan8) {
361 addEvents({ /* Time, Inputs, Outputs */
362 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
363
364 /* Processing is a bit late */
365 {50, {}, {}},
366 /* Release key after 1ms */
367 {51, {{0, 1, UP}}, {}},
368
369 /* Processing is a bit late again */
370 {100, {}, {{0, 1, UP}}},
371 });
372 time_jumps_ = true;
373 runEvents();
374}
diff --git a/quantum/debounce/tests/debounce_test_common.cpp b/quantum/debounce/tests/debounce_test_common.cpp
new file mode 100644
index 000000000..1c5e7c9f4
--- /dev/null
+++ b/quantum/debounce/tests/debounce_test_common.cpp
@@ -0,0 +1,229 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18
19#include "debounce_test_common.h"
20
21#include <algorithm>
22#include <iomanip>
23#include <sstream>
24
25extern "C" {
26#include "quantum.h"
27#include "timer.h"
28#include "debounce.h"
29
30void set_time(uint32_t t);
31void advance_time(uint32_t ms);
32}
33
34void DebounceTest::addEvents(std::initializer_list<DebounceTestEvent> events) {
35 events_.insert(events_.end(), events.begin(), events.end());
36}
37
38void DebounceTest::runEvents() {
39 /* Run the test multiple times, from 1kHz to 10kHz scan rate */
40 for (extra_iterations_ = 0; extra_iterations_ < 10; extra_iterations_++) {
41 if (time_jumps_) {
42 /* Don't advance time smoothly, jump to the next event (some tests require this) */
43 auto_advance_time_ = false;
44 runEventsInternal();
45 } else {
46 /* Run the test with both smooth and irregular time; it must produce the same result */
47 auto_advance_time_ = true;
48 runEventsInternal();
49 auto_advance_time_ = false;
50 runEventsInternal();
51 }
52 }
53}
54
55void DebounceTest::runEventsInternal() {
56 fast_timer_t previous = 0;
57 bool first = true;
58
59 /* Initialise keyboard with start time (offset to avoid testing at 0) and all keys UP */
60 debounce_init(MATRIX_ROWS);
61 set_time(time_offset_);
62 std::fill(std::begin(input_matrix_), std::end(input_matrix_), 0);
63 std::fill(std::begin(output_matrix_), std::end(output_matrix_), 0);
64
65 for (auto &event : events_) {
66 if (!auto_advance_time_) {
67 /* Jump to the next event */
68 set_time(time_offset_ + event.time_);
69 } else if (!first && event.time_ == previous + 1) {
70 /* This event immediately follows the previous one, don't make extra debounce() calls */
71 advance_time(1);
72 } else {
73 /* Fast forward to the time for this event, calling debounce() with no changes */
74 ASSERT_LT((time_offset_ + event.time_) - timer_read_fast(), 60000) << "Test tries to advance more than 1 minute of time";
75
76 while (timer_read_fast() != time_offset_ + event.time_) {
77 runDebounce(false);
78 checkCookedMatrix(false, "debounce() modified cooked matrix");
79 advance_time(1);
80 }
81 }
82
83 first = false;
84 previous = event.time_;
85
86 /* Prepare input matrix */
87 for (auto &input : event.inputs_) {
88 matrixUpdate(input_matrix_, "input", input);
89 }
90
91 /* Call debounce */
92 runDebounce(!event.inputs_.empty());
93
94 /* Prepare output matrix */
95 for (auto &output : event.outputs_) {
96 matrixUpdate(output_matrix_, "output", output);
97 }
98
99 /* Check output matrix has expected change events */
100 for (auto &output : event.outputs_) {
101 EXPECT_EQ(!!(cooked_matrix_[output.row_] & (1U << output.col_)), directionValue(output.direction_))
102 << "Missing event at " << strTime()
103 << " expected key " << output.row_ << "," << output.col_ << " " << directionLabel(output.direction_)
104 << "\ninput_matrix: changed=" << !event.inputs_.empty() << "\n" << strMatrix(input_matrix_)
105 << "\nexpected_matrix:\n" << strMatrix(output_matrix_)
106 << "\nactual_matrix:\n" << strMatrix(cooked_matrix_);
107 }
108
109 /* Check output matrix has no other changes */
110 checkCookedMatrix(!event.inputs_.empty(), "debounce() cooked matrix does not match expected output matrix");
111
112 /* Perform some extra iterations of the matrix scan with no changes */
113 for (int i = 0; i < extra_iterations_; i++) {
114 runDebounce(false);
115 checkCookedMatrix(false, "debounce() modified cooked matrix");
116 }
117 }
118
119 /* Check that no further changes happen for 1 minute */
120 for (int i = 0; i < 60000; i++) {
121 runDebounce(false);
122 checkCookedMatrix(false, "debounce() modified cooked matrix");
123 advance_time(1);
124 }
125
126 debounce_free();
127}
128
129void DebounceTest::runDebounce(bool changed) {
130 std::copy(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_));
131 std::copy(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_));
132
133 debounce(raw_matrix_, cooked_matrix_, MATRIX_ROWS, changed);
134
135 if (!std::equal(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_))) {
136 FAIL() << "Fatal error: debounce() modified raw matrix at " << strTime()
137 << "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_)
138 << "\nraw_matrix:\n" << strMatrix(raw_matrix_);
139 }
140}
141
142void DebounceTest::checkCookedMatrix(bool changed, const std::string &error_message) {
143 if (!std::equal(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_))) {
144 FAIL() << "Unexpected event: " << error_message << " at " << strTime()
145 << "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_)
146 << "\nexpected_matrix:\n" << strMatrix(output_matrix_)
147 << "\nactual_matrix:\n" << strMatrix(cooked_matrix_);
148 }
149}
150
151std::string DebounceTest::strTime() {
152 std::stringstream text;
153
154 text << "time " << (timer_read_fast() - time_offset_)
155 << " (extra_iterations=" << extra_iterations_
156 << ", auto_advance_time=" << auto_advance_time_ << ")";
157
158 return text.str();
159}
160
161std::string DebounceTest::strMatrix(matrix_row_t matrix[]) {
162 std::stringstream text;
163
164 text << "\t" << std::setw(3) << "";
165 for (int col = 0; col < MATRIX_COLS; col++) {
166 text << " " << std::setw(2) << col;
167 }
168 text << "\n";
169
170 for (int row = 0; row < MATRIX_ROWS; row++) {
171 text << "\t" << std::setw(2) << row << ":";
172 for (int col = 0; col < MATRIX_COLS; col++) {
173 text << ((matrix[row] & (1U << col)) ? " XX" : " __");
174 }
175
176 text << "\n";
177 }
178
179 return text.str();
180}
181
182bool DebounceTest::directionValue(Direction direction) {
183 switch (direction) {
184 case DOWN:
185 return true;
186
187 case UP:
188 return false;
189 }
190}
191
192std::string DebounceTest::directionLabel(Direction direction) {
193 switch (direction) {
194 case DOWN:
195 return "DOWN";
196
197 case UP:
198 return "UP";
199 }
200}
201
202/* Modify a matrix and verify that events always specify a change */
203void DebounceTest::matrixUpdate(matrix_row_t matrix[], const std::string &name, const MatrixTestEvent &event) {
204 ASSERT_NE(!!(matrix[event.row_] & (1U << event.col_)), directionValue(event.direction_))
205 << "Test " << name << " at " << strTime()
206 << " sets key " << event.row_ << "," << event.col_ << " " << directionLabel(event.direction_)
207 << " but it is already " << directionLabel(event.direction_)
208 << "\n" << name << "_matrix:\n" << strMatrix(matrix);
209
210 switch (event.direction_) {
211 case DOWN:
212 matrix[event.row_] |= (1U << event.col_);
213 break;
214
215 case UP:
216 matrix[event.row_] &= ~(1U << event.col_);
217 break;
218 }
219}
220
221DebounceTestEvent::DebounceTestEvent(fast_timer_t time,
222 std::initializer_list<MatrixTestEvent> inputs,
223 std::initializer_list<MatrixTestEvent> outputs)
224 : time_(time), inputs_(inputs), outputs_(outputs) {
225}
226
227MatrixTestEvent::MatrixTestEvent(int row, int col, Direction direction)
228 : row_(row), col_(col), direction_(direction) {
229}
diff --git a/quantum/debounce/tests/debounce_test_common.h b/quantum/debounce/tests/debounce_test_common.h
new file mode 100644
index 000000000..d87e31059
--- /dev/null
+++ b/quantum/debounce/tests/debounce_test_common.h
@@ -0,0 +1,83 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18
19#include <initializer_list>
20#include <list>
21#include <string>
22
23extern "C" {
24#include "quantum.h"
25#include "timer.h"
26}
27
28enum Direction {
29 DOWN,
30 UP,
31};
32
33class MatrixTestEvent {
34public:
35 MatrixTestEvent(int row, int col, Direction direction);
36
37 const int row_;
38 const int col_;
39 const Direction direction_;
40};
41
42class DebounceTestEvent {
43public:
44 // 0, {{0, 1, DOWN}}, {{0, 1, DOWN}})
45 DebounceTestEvent(fast_timer_t time,
46 std::initializer_list<MatrixTestEvent> inputs,
47 std::initializer_list<MatrixTestEvent> outputs);
48
49 const fast_timer_t time_;
50 const std::list<MatrixTestEvent> inputs_;
51 const std::list<MatrixTestEvent> outputs_;
52};
53
54class DebounceTest : public ::testing::Test {
55protected:
56 void addEvents(std::initializer_list<DebounceTestEvent> events);
57 void runEvents();
58
59 fast_timer_t time_offset_ = 7777;
60 bool time_jumps_ = false;
61
62private:
63 static bool directionValue(Direction direction);
64 static std::string directionLabel(Direction direction);
65
66 void runEventsInternal();
67 void runDebounce(bool changed);
68 void checkCookedMatrix(bool changed, const std::string &error_message);
69 void matrixUpdate(matrix_row_t matrix[], const std::string &name, const MatrixTestEvent &event);
70
71 std::string strTime();
72 std::string strMatrix(matrix_row_t matrix[]);
73
74 std::list<DebounceTestEvent> events_;
75
76 matrix_row_t input_matrix_[MATRIX_ROWS];
77 matrix_row_t raw_matrix_[MATRIX_ROWS];
78 matrix_row_t cooked_matrix_[MATRIX_ROWS];
79 matrix_row_t output_matrix_[MATRIX_ROWS];
80
81 int extra_iterations_;
82 bool auto_advance_time_;
83};
diff --git a/quantum/debounce/tests/rules.mk b/quantum/debounce/tests/rules.mk
new file mode 100644
index 000000000..66928d7eb
--- /dev/null
+++ b/quantum/debounce/tests/rules.mk
@@ -0,0 +1,44 @@
1# Copyright 2021 Simon Arlott
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16DEBOUNCE_COMMON_DEFS := -DMATRIX_ROWS=4 -DMATRIX_COLS=10 -DDEBOUNCE=5
17
18DEBOUNCE_COMMON_SRC := $(QUANTUM_PATH)/debounce/tests/debounce_test_common.cpp \
19 $(TMK_PATH)/common/test/timer.c
20
21debounce_sym_defer_g_DEFS := $(DEBOUNCE_COMMON_DEFS)
22debounce_sym_defer_g_SRC := $(DEBOUNCE_COMMON_SRC) \
23 $(QUANTUM_PATH)/debounce/sym_defer_g.c \
24 $(QUANTUM_PATH)/debounce/tests/sym_defer_g_tests.cpp
25
26debounce_sym_defer_pk_DEFS := $(DEBOUNCE_COMMON_DEFS)
27debounce_sym_defer_pk_SRC := $(DEBOUNCE_COMMON_SRC) \
28 $(QUANTUM_PATH)/debounce/sym_defer_pk.c \
29 $(QUANTUM_PATH)/debounce/tests/sym_defer_pk_tests.cpp
30
31debounce_sym_eager_pk_DEFS := $(DEBOUNCE_COMMON_DEFS)
32debounce_sym_eager_pk_SRC := $(DEBOUNCE_COMMON_SRC) \
33 $(QUANTUM_PATH)/debounce/sym_eager_pk.c \
34 $(QUANTUM_PATH)/debounce/tests/sym_eager_pk_tests.cpp
35
36debounce_sym_eager_pr_DEFS := $(DEBOUNCE_COMMON_DEFS)
37debounce_sym_eager_pr_SRC := $(DEBOUNCE_COMMON_SRC) \
38 $(QUANTUM_PATH)/debounce/sym_eager_pr.c \
39 $(QUANTUM_PATH)/debounce/tests/sym_eager_pr_tests.cpp
40
41debounce_asym_eager_defer_pk_DEFS := $(DEBOUNCE_COMMON_DEFS)
42debounce_asym_eager_defer_pk_SRC := $(DEBOUNCE_COMMON_SRC) \
43 $(QUANTUM_PATH)/debounce/asym_eager_defer_pk.c \
44 $(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 000000000..a56aecd8f
--- /dev/null
+++ b/quantum/debounce/tests/sym_defer_g_tests.cpp
@@ -0,0 +1,223 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18
19#include "debounce_test_common.h"
20
21TEST_F(DebounceTest, OneKeyShort1) {
22 addEvents({ /* Time, Inputs, Outputs */
23 {0, {{0, 1, DOWN}}, {}},
24
25 {5, {}, {{0, 1, DOWN}}},
26 /* 0ms delay (fast scan rate) */
27 {5, {{0, 1, UP}}, {}},
28
29 {10, {}, {{0, 1, UP}}},
30 });
31 runEvents();
32}
33
34TEST_F(DebounceTest, OneKeyShort2) {
35 addEvents({ /* Time, Inputs, Outputs */
36 {0, {{0, 1, DOWN}}, {}},
37
38 {5, {}, {{0, 1, DOWN}}},
39 /* 1ms delay */
40 {6, {{0, 1, UP}}, {}},
41
42 {11, {}, {{0, 1, UP}}},
43 });
44 runEvents();
45}
46
47TEST_F(DebounceTest, OneKeyShort3) {
48 addEvents({ /* Time, Inputs, Outputs */
49 {0, {{0, 1, DOWN}}, {}},
50
51 {5, {}, {{0, 1, DOWN}}},
52 /* 2ms delay */
53 {7, {{0, 1, UP}}, {}},
54
55 {12, {}, {{0, 1, UP}}},
56 });
57 runEvents();
58}
59
60TEST_F(DebounceTest, OneKeyTooQuick1) {
61 addEvents({ /* Time, Inputs, Outputs */
62 {0, {{0, 1, DOWN}}, {}},
63 /* Release key exactly on the debounce time */
64 {5, {{0, 1, UP}}, {}},
65 });
66 runEvents();
67}
68
69TEST_F(DebounceTest, OneKeyTooQuick2) {
70 addEvents({ /* Time, Inputs, Outputs */
71 {0, {{0, 1, DOWN}}, {}},
72
73 {5, {}, {{0, 1, DOWN}}},
74 {6, {{0, 1, UP}}, {}},
75
76 /* Press key exactly on the debounce time */
77 {11, {{0, 1, DOWN}}, {}},
78 });
79 runEvents();
80}
81
82TEST_F(DebounceTest, OneKeyBouncing1) {
83 addEvents({ /* Time, Inputs, Outputs */
84 {0, {{0, 1, DOWN}}, {}},
85 {1, {{0, 1, UP}}, {}},
86 {2, {{0, 1, DOWN}}, {}},
87 {3, {{0, 1, UP}}, {}},
88 {4, {{0, 1, DOWN}}, {}},
89 {5, {{0, 1, UP}}, {}},
90 {6, {{0, 1, DOWN}}, {}},
91 {11, {}, {{0, 1, DOWN}}}, /* 5ms after DOWN at time 7 */
92 });
93 runEvents();
94}
95
96TEST_F(DebounceTest, OneKeyBouncing2) {
97 addEvents({ /* Time, Inputs, Outputs */
98 {0, {{0, 1, DOWN}}, {}},
99 {5, {}, {{0, 1, DOWN}}},
100 {6, {{0, 1, UP}}, {}},
101 {7, {{0, 1, DOWN}}, {}},
102 {8, {{0, 1, UP}}, {}},
103 {9, {{0, 1, DOWN}}, {}},
104 {10, {{0, 1, UP}}, {}},
105 {15, {}, {{0, 1, UP}}}, /* 5ms after UP at time 10 */
106 });
107 runEvents();
108}
109
110TEST_F(DebounceTest, OneKeyLong) {
111 addEvents({ /* Time, Inputs, Outputs */
112 {0, {{0, 1, DOWN}}, {}},
113
114 {5, {}, {{0, 1, DOWN}}},
115
116 {25, {{0, 1, UP}}, {}},
117
118 {30, {}, {{0, 1, UP}}},
119
120 {50, {{0, 1, DOWN}}, {}},
121
122 {55, {}, {{0, 1, DOWN}}},
123 });
124 runEvents();
125}
126
127TEST_F(DebounceTest, TwoKeysShort) {
128 addEvents({ /* Time, Inputs, Outputs */
129 {0, {{0, 1, DOWN}}, {}},
130 {1, {{0, 2, DOWN}}, {}},
131
132 {6, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
133
134 {7, {{0, 1, UP}}, {}},
135 {8, {{0, 2, UP}}, {}},
136
137 {13, {}, {{0, 1, UP}, {0, 2, UP}}},
138 });
139 runEvents();
140}
141
142TEST_F(DebounceTest, TwoKeysSimultaneous1) {
143 addEvents({ /* Time, Inputs, Outputs */
144 {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {}},
145
146 {5, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
147 {6, {{0, 1, UP}, {0, 2, UP}}, {}},
148
149 {11, {}, {{0, 1, UP}, {0, 2, UP}}},
150 });
151 runEvents();
152}
153
154TEST_F(DebounceTest, TwoKeysSimultaneous2) {
155 addEvents({ /* Time, Inputs, Outputs */
156 {0, {{0, 1, DOWN}}, {}},
157 {1, {{0, 2, DOWN}}, {}},
158
159 {5, {}, {}},
160 {6, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
161 {7, {{0, 1, UP}}, {}},
162 {8, {{0, 2, UP}}, {}},
163
164 {13, {}, {{0, 1, UP}, {0, 2, UP}}},
165 });
166 runEvents();
167}
168
169TEST_F(DebounceTest, OneKeyDelayedScan1) {
170 addEvents({ /* Time, Inputs, Outputs */
171 {0, {{0, 1, DOWN}}, {}},
172
173 /* Processing is very late */
174 {300, {}, {{0, 1, DOWN}}},
175 /* Immediately release key */
176 {300, {{0, 1, UP}}, {}},
177
178 {305, {}, {{0, 1, UP}}},
179 });
180 time_jumps_ = true;
181 runEvents();
182}
183
184TEST_F(DebounceTest, OneKeyDelayedScan2) {
185 addEvents({ /* Time, Inputs, Outputs */
186 {0, {{0, 1, DOWN}}, {}},
187
188 /* Processing is very late */
189 {300, {}, {{0, 1, DOWN}}},
190 /* Release key after 1ms */
191 {301, {{0, 1, UP}}, {}},
192
193 {306, {}, {{0, 1, UP}}},
194 });
195 time_jumps_ = true;
196 runEvents();
197}
198
199TEST_F(DebounceTest, OneKeyDelayedScan3) {
200 addEvents({ /* Time, Inputs, Outputs */
201 {0, {{0, 1, DOWN}}, {}},
202
203 /* Release key before debounce expires */
204 {300, {{0, 1, UP}}, {}},
205 });
206 time_jumps_ = true;
207 runEvents();
208}
209
210TEST_F(DebounceTest, OneKeyDelayedScan4) {
211 addEvents({ /* Time, Inputs, Outputs */
212 {0, {{0, 1, DOWN}}, {}},
213
214 /* Processing is a bit late */
215 {50, {}, {{0, 1, DOWN}}},
216 /* Release key after 1ms */
217 {51, {{0, 1, UP}}, {}},
218
219 {56, {}, {{0, 1, UP}}},
220 });
221 time_jumps_ = true;
222 runEvents();
223}
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 000000000..1f3061e59
--- /dev/null
+++ b/quantum/debounce/tests/sym_defer_pk_tests.cpp
@@ -0,0 +1,225 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18
19#include "debounce_test_common.h"
20
21TEST_F(DebounceTest, OneKeyShort1) {
22 addEvents({ /* Time, Inputs, Outputs */
23 {0, {{0, 1, DOWN}}, {}},
24
25 {5, {}, {{0, 1, DOWN}}},
26 /* 0ms delay (fast scan rate) */
27 {5, {{0, 1, UP}}, {}},
28
29 {10, {}, {{0, 1, UP}}},
30 });
31 runEvents();
32}
33
34TEST_F(DebounceTest, OneKeyShort2) {
35 addEvents({ /* Time, Inputs, Outputs */
36 {0, {{0, 1, DOWN}}, {}},
37
38 {5, {}, {{0, 1, DOWN}}},
39 /* 1ms delay */
40 {6, {{0, 1, UP}}, {}},
41
42 {11, {}, {{0, 1, UP}}},
43 });
44 runEvents();
45}
46
47TEST_F(DebounceTest, OneKeyShort3) {
48 addEvents({ /* Time, Inputs, Outputs */
49 {0, {{0, 1, DOWN}}, {}},
50
51 {5, {}, {{0, 1, DOWN}}},
52 /* 2ms delay */
53 {7, {{0, 1, UP}}, {}},
54
55 {12, {}, {{0, 1, UP}}},
56 });
57 runEvents();
58}
59
60TEST_F(DebounceTest, OneKeyTooQuick1) {
61 addEvents({ /* Time, Inputs, Outputs */
62 {0, {{0, 1, DOWN}}, {}},
63 /* Release key exactly on the debounce time */
64 {5, {{0, 1, UP}}, {}},
65 });
66 runEvents();
67}
68
69TEST_F(DebounceTest, OneKeyTooQuick2) {
70 addEvents({ /* Time, Inputs, Outputs */
71 {0, {{0, 1, DOWN}}, {}},
72
73 {5, {}, {{0, 1, DOWN}}},
74 {6, {{0, 1, UP}}, {}},
75
76 /* Press key exactly on the debounce time */
77 {11, {{0, 1, DOWN}}, {}},
78 });
79 runEvents();
80}
81
82TEST_F(DebounceTest, OneKeyBouncing1) {
83 addEvents({ /* Time, Inputs, Outputs */
84 {0, {{0, 1, DOWN}}, {}},
85 {1, {{0, 1, UP}}, {}},
86 {2, {{0, 1, DOWN}}, {}},
87 {3, {{0, 1, UP}}, {}},
88 {4, {{0, 1, DOWN}}, {}},
89 {5, {{0, 1, UP}}, {}},
90 {6, {{0, 1, DOWN}}, {}},
91 {11, {}, {{0, 1, DOWN}}}, /* 5ms after DOWN at time 7 */
92 });
93 runEvents();
94}
95
96TEST_F(DebounceTest, OneKeyBouncing2) {
97 addEvents({ /* Time, Inputs, Outputs */
98 {0, {{0, 1, DOWN}}, {}},
99 {5, {}, {{0, 1, DOWN}}},
100 {6, {{0, 1, UP}}, {}},
101 {7, {{0, 1, DOWN}}, {}},
102 {8, {{0, 1, UP}}, {}},
103 {9, {{0, 1, DOWN}}, {}},
104 {10, {{0, 1, UP}}, {}},
105 {15, {}, {{0, 1, UP}}}, /* 5ms after UP at time 10 */
106 });
107 runEvents();
108}
109
110TEST_F(DebounceTest, OneKeyLong) {
111 addEvents({ /* Time, Inputs, Outputs */
112 {0, {{0, 1, DOWN}}, {}},
113
114 {5, {}, {{0, 1, DOWN}}},
115
116 {25, {{0, 1, UP}}, {}},
117
118 {30, {}, {{0, 1, UP}}},
119
120 {50, {{0, 1, DOWN}}, {}},
121
122 {55, {}, {{0, 1, DOWN}}},
123 });
124 runEvents();
125}
126
127TEST_F(DebounceTest, TwoKeysShort) {
128 addEvents({ /* Time, Inputs, Outputs */
129 {0, {{0, 1, DOWN}}, {}},
130 {1, {{0, 2, DOWN}}, {}},
131
132 {5, {}, {{0, 1, DOWN}}},
133 {6, {}, {{0, 2, DOWN}}},
134
135 {7, {{0, 1, UP}}, {}},
136 {8, {{0, 2, UP}}, {}},
137
138 {12, {}, {{0, 1, UP}}},
139 {13, {}, {{0, 2, UP}}},
140 });
141 runEvents();
142}
143
144TEST_F(DebounceTest, TwoKeysSimultaneous1) {
145 addEvents({ /* Time, Inputs, Outputs */
146 {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {}},
147
148 {5, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
149 {6, {{0, 1, UP}, {0, 2, UP}}, {}},
150
151 {11, {}, {{0, 1, UP}, {0, 2, UP}}},
152 });
153 runEvents();
154}
155
156TEST_F(DebounceTest, TwoKeysSimultaneous2) {
157 addEvents({ /* Time, Inputs, Outputs */
158 {0, {{0, 1, DOWN}}, {}},
159 {1, {{0, 2, DOWN}}, {}},
160
161 {5, {}, {{0, 1, DOWN}}},
162 {6, {{0, 1, UP}}, {{0, 2, DOWN}}},
163 {7, {{0, 2, UP}}, {}},
164
165 {11, {}, {{0, 1, UP}}},
166 {12, {}, {{0, 2, UP}}},
167 });
168 runEvents();
169}
170
171TEST_F(DebounceTest, OneKeyDelayedScan1) {
172 addEvents({ /* Time, Inputs, Outputs */
173 {0, {{0, 1, DOWN}}, {}},
174
175 /* Processing is very late */
176 {300, {}, {{0, 1, DOWN}}},
177 /* Immediately release key */
178 {300, {{0, 1, UP}}, {}},
179
180 {305, {}, {{0, 1, UP}}},
181 });
182 time_jumps_ = true;
183 runEvents();
184}
185
186TEST_F(DebounceTest, OneKeyDelayedScan2) {
187 addEvents({ /* Time, Inputs, Outputs */
188 {0, {{0, 1, DOWN}}, {}},
189
190 /* Processing is very late */
191 {300, {}, {{0, 1, DOWN}}},
192 /* Release key after 1ms */
193 {301, {{0, 1, UP}}, {}},
194
195 {306, {}, {{0, 1, UP}}},
196 });
197 time_jumps_ = true;
198 runEvents();
199}
200
201TEST_F(DebounceTest, OneKeyDelayedScan3) {
202 addEvents({ /* Time, Inputs, Outputs */
203 {0, {{0, 1, DOWN}}, {}},
204
205 /* Release key before debounce expires */
206 {300, {{0, 1, UP}}, {}},
207 });
208 time_jumps_ = true;
209 runEvents();
210}
211
212TEST_F(DebounceTest, OneKeyDelayedScan4) {
213 addEvents({ /* Time, Inputs, Outputs */
214 {0, {{0, 1, DOWN}}, {}},
215
216 /* Processing is a bit late */
217 {50, {}, {{0, 1, DOWN}}},
218 /* Release key after 1ms */
219 {51, {{0, 1, UP}}, {}},
220
221 {56, {}, {{0, 1, UP}}},
222 });
223 time_jumps_ = true;
224 runEvents();
225}
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 000000000..e0fc205e3
--- /dev/null
+++ b/quantum/debounce/tests/sym_eager_pk_tests.cpp
@@ -0,0 +1,237 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18
19#include "debounce_test_common.h"
20
21TEST_F(DebounceTest, OneKeyShort1) {
22 addEvents({ /* Time, Inputs, Outputs */
23 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
24 {1, {{0, 1, UP}}, {}},
25
26 {5, {}, {{0, 1, UP}}},
27 /* Press key again after 1ms delay (debounce has not yet finished) */
28 {6, {{0, 1, DOWN}}, {}},
29 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
30 });
31 runEvents();
32}
33
34TEST_F(DebounceTest, OneKeyShort2) {
35 addEvents({ /* Time, Inputs, Outputs */
36 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
37 {1, {{0, 1, UP}}, {}},
38
39 {5, {}, {{0, 1, UP}}},
40 /* Press key again after 2ms delay (debounce has not yet finished) */
41 {7, {{0, 1, DOWN}}, {}},
42 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
43 });
44 runEvents();
45}
46
47TEST_F(DebounceTest, OneKeyShort3) {
48 addEvents({ /* Time, Inputs, Outputs */
49 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
50 {1, {{0, 1, UP}}, {}},
51
52 {5, {}, {{0, 1, UP}}},
53 /* Press key again after 3ms delay (debounce has not yet finished) */
54 {8, {{0, 1, DOWN}}, {}},
55 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
56 });
57 runEvents();
58}
59
60TEST_F(DebounceTest, OneKeyShort4) {
61 addEvents({ /* Time, Inputs, Outputs */
62 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
63 {1, {{0, 1, UP}}, {}},
64
65 {5, {}, {{0, 1, UP}}},
66 /* Press key again after 4ms delay (debounce has not yet finished) */
67 {9, {{0, 1, DOWN}}, {}},
68 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
69 });
70 runEvents();
71}
72
73TEST_F(DebounceTest, OneKeyShort5) {
74 addEvents({ /* Time, Inputs, Outputs */
75 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
76 {1, {{0, 1, UP}}, {}},
77
78 {5, {}, {{0, 1, UP}}},
79 /* Press key again after 5ms delay (debounce has finished) */
80 {10, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
81 });
82 runEvents();
83}
84
85TEST_F(DebounceTest, OneKeyShort6) {
86 addEvents({ /* Time, Inputs, Outputs */
87 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
88 {1, {{0, 1, UP}}, {}},
89
90 {5, {}, {{0, 1, UP}}},
91 /* Press key after after 6ms delay (debounce has finished) */
92 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
93 });
94 runEvents();
95}
96
97TEST_F(DebounceTest, OneKeyBouncing1) {
98 addEvents({ /* Time, Inputs, Outputs */
99 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
100 {1, {{0, 1, UP}}, {}},
101 {2, {{0, 1, DOWN}}, {}},
102 {3, {{0, 1, UP}}, {}},
103 {4, {{0, 1, DOWN}}, {}},
104 {5, {{0, 1, UP}}, {{0, 1, UP}}},
105 /* Press key again after 1ms delay (debounce has not yet finished) */
106 {6, {{0, 1, DOWN}}, {}},
107 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
108 });
109 runEvents();
110}
111
112TEST_F(DebounceTest, OneKeyBouncing2) {
113 addEvents({ /* Time, Inputs, Outputs */
114 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
115 /* Change twice in the same time period */
116 {1, {{0, 1, UP}}, {}},
117 {1, {{0, 1, DOWN}}, {}},
118 /* Change three times in the same time period */
119 {2, {{0, 1, UP}}, {}},
120 {2, {{0, 1, DOWN}}, {}},
121 {2, {{0, 1, UP}}, {}},
122 /* Change three times in the same time period */
123 {3, {{0, 1, DOWN}}, {}},
124 {3, {{0, 1, UP}}, {}},
125 {3, {{0, 1, DOWN}}, {}},
126 /* Change twice in the same time period */
127 {4, {{0, 1, UP}}, {}},
128 {4, {{0, 1, DOWN}}, {}},
129 {5, {{0, 1, UP}}, {{0, 1, UP}}},
130 /* Press key again after 1ms delay (debounce has not yet finished) */
131 {6, {{0, 1, DOWN}}, {}},
132 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
133 });
134 runEvents();
135}
136
137TEST_F(DebounceTest, OneKeyLong) {
138 addEvents({ /* Time, Inputs, Outputs */
139 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
140
141 {25, {{0, 1, UP}}, {{0, 1, UP}}},
142
143 {50, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
144 });
145 runEvents();
146}
147
148TEST_F(DebounceTest, TwoKeysShort) {
149 addEvents({ /* Time, Inputs, Outputs */
150 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
151 {1, {{0, 1, UP}}, {}},
152 {2, {{0, 2, DOWN}}, {{0, 2, DOWN}}},
153 {3, {{0, 2, UP}}, {}},
154
155 {5, {}, {{0, 1, UP}}},
156 /* Press key again after 1ms delay (debounce has not yet finished) */
157 {6, {{0, 1, DOWN}}, {}},
158 {7, {}, {{0, 2, UP}}},
159
160 /* Press key again after 1ms delay (debounce has not yet finished) */
161 {9, {{0, 2, DOWN}}, {}},
162 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
163
164 {12, {}, {{0, 2, DOWN}}}, /* 5ms after UP at time 7 */
165 });
166 runEvents();
167}
168
169TEST_F(DebounceTest, OneKeyDelayedScan1) {
170 addEvents({ /* Time, Inputs, Outputs */
171 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
172
173 /* Processing is very late but the change will now be accepted */
174 {300, {{0, 1, UP}}, {{0, 1, UP}}},
175 });
176 time_jumps_ = true;
177 runEvents();
178}
179
180TEST_F(DebounceTest, OneKeyDelayedScan2) {
181 addEvents({ /* Time, Inputs, Outputs */
182 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
183
184 /* Processing is very late but the change will now be accepted even with a 1 scan delay */
185 {300, {}, {}},
186 {300, {{0, 1, UP}}, {{0, 1, UP}}},
187 });
188 time_jumps_ = true;
189 runEvents();
190}
191
192TEST_F(DebounceTest, OneKeyDelayedScan3) {
193 addEvents({ /* Time, Inputs, Outputs */
194 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
195
196 /* Processing is very late but the change will now be accepted even with a 1ms delay */
197 {300, {}, {}},
198 {301, {{0, 1, UP}}, {{0, 1, UP}}},
199 });
200 time_jumps_ = true;
201 runEvents();
202}
203
204TEST_F(DebounceTest, OneKeyDelayedScan4) {
205 addEvents({ /* Time, Inputs, Outputs */
206 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
207
208 /* Processing is a bit late but the change will now be accepted */
209 {50, {{0, 1, UP}}, {{0, 1, UP}}},
210 });
211 time_jumps_ = true;
212 runEvents();
213}
214
215TEST_F(DebounceTest, OneKeyDelayedScan5) {
216 addEvents({ /* Time, Inputs, Outputs */
217 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
218
219 /* Processing is very late but the change will now be accepted even with a 1 scan delay */
220 {50, {}, {}},
221 {50, {{0, 1, UP}}, {{0, 1, UP}}},
222 });
223 time_jumps_ = true;
224 runEvents();
225}
226
227TEST_F(DebounceTest, OneKeyDelayedScan6) {
228 addEvents({ /* Time, Inputs, Outputs */
229 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
230
231 /* Processing is very late but the change will now be accepted even with a 1ms delay */
232 {50, {}, {}},
233 {51, {{0, 1, UP}}, {{0, 1, UP}}},
234 });
235 time_jumps_ = true;
236 runEvents();
237}
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 000000000..2c4bca127
--- /dev/null
+++ b/quantum/debounce/tests/sym_eager_pr_tests.cpp
@@ -0,0 +1,280 @@
1/* Copyright 2021 Simon Arlott
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "gtest/gtest.h"
18
19#include "debounce_test_common.h"
20
21TEST_F(DebounceTest, OneKeyShort1) {
22 addEvents({ /* Time, Inputs, Outputs */
23 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
24 {1, {{0, 1, UP}}, {}},
25
26 {5, {}, {{0, 1, UP}}},
27 /* Press key again after 1ms delay (debounce has not yet finished) */
28 {6, {{0, 1, DOWN}}, {}},
29 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
30 });
31 runEvents();
32}
33
34TEST_F(DebounceTest, OneKeyShort2) {
35 addEvents({ /* Time, Inputs, Outputs */
36 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
37 {1, {{0, 1, UP}}, {}},
38
39 {5, {}, {{0, 1, UP}}},
40 /* Press key again after 2ms delay (debounce has not yet finished) */
41 {7, {{0, 1, DOWN}}, {}},
42 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
43 });
44 runEvents();
45}
46
47TEST_F(DebounceTest, OneKeyShort3) {
48 addEvents({ /* Time, Inputs, Outputs */
49 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
50 {1, {{0, 1, UP}}, {}},
51
52 {5, {}, {{0, 1, UP}}},
53 /* Press key again after 3ms delay (debounce has not yet finished) */
54 {8, {{0, 1, DOWN}}, {}},
55 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
56 });
57 runEvents();
58}
59
60TEST_F(DebounceTest, OneKeyShort4) {
61 addEvents({ /* Time, Inputs, Outputs */
62 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
63 {1, {{0, 1, UP}}, {}},
64
65 {5, {}, {{0, 1, UP}}},
66 /* Press key again after 4ms delay (debounce has not yet finished) */
67 {9, {{0, 1, DOWN}}, {}},
68 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
69 });
70 runEvents();
71}
72
73TEST_F(DebounceTest, OneKeyShort5) {
74 addEvents({ /* Time, Inputs, Outputs */
75 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
76 {1, {{0, 1, UP}}, {}},
77
78 {5, {}, {{0, 1, UP}}},
79 /* Press key again after 5ms delay (debounce has finished) */
80 {10, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
81 });
82 runEvents();
83}
84
85TEST_F(DebounceTest, OneKeyShort6) {
86 addEvents({ /* Time, Inputs, Outputs */
87 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
88 {1, {{0, 1, UP}}, {}},
89
90 {5, {}, {{0, 1, UP}}},
91 /* Press key after after 6ms delay (debounce has finished) */
92 {11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
93 });
94 runEvents();
95}
96
97TEST_F(DebounceTest, OneKeyBouncing1) {
98 addEvents({ /* Time, Inputs, Outputs */
99 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
100 {1, {{0, 1, UP}}, {}},
101 {2, {{0, 1, DOWN}}, {}},
102 {3, {{0, 1, UP}}, {}},
103 {4, {{0, 1, DOWN}}, {}},
104 {5, {{0, 1, UP}}, {{0, 1, UP}}},
105 /* Press key again after 1ms delay (debounce has not yet finished) */
106 {6, {{0, 1, DOWN}}, {}},
107 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
108 });
109 runEvents();
110}
111
112TEST_F(DebounceTest, OneKeyBouncing2) {
113 addEvents({ /* Time, Inputs, Outputs */
114 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
115 /* Change twice in the same time period */
116 {1, {{0, 1, UP}}, {}},
117 {1, {{0, 1, DOWN}}, {}},
118 /* Change three times in the same time period */
119 {2, {{0, 1, UP}}, {}},
120 {2, {{0, 1, DOWN}}, {}},
121 {2, {{0, 1, UP}}, {}},
122 /* Change three times in the same time period */
123 {3, {{0, 1, DOWN}}, {}},
124 {3, {{0, 1, UP}}, {}},
125 {3, {{0, 1, DOWN}}, {}},
126 /* Change twice in the same time period */
127 {4, {{0, 1, UP}}, {}},
128 {4, {{0, 1, DOWN}}, {}},
129 {5, {{0, 1, UP}}, {{0, 1, UP}}},
130 /* Press key again after 1ms delay (debounce has not yet finished) */
131 {6, {{0, 1, DOWN}}, {}},
132 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
133 });
134 runEvents();
135}
136
137TEST_F(DebounceTest, OneKeyLong) {
138 addEvents({ /* Time, Inputs, Outputs */
139 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
140
141 {25, {{0, 1, UP}}, {{0, 1, UP}}},
142
143 {50, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
144 });
145 runEvents();
146}
147
148TEST_F(DebounceTest, TwoRowsShort) {
149 addEvents({ /* Time, Inputs, Outputs */
150 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
151 {1, {{0, 1, UP}}, {}},
152 {2, {{2, 0, DOWN}}, {{2, 0, DOWN}}},
153 {3, {{2, 0, UP}}, {}},
154
155 {5, {}, {{0, 1, UP}}},
156 /* Press key again after 1ms delay (debounce has not yet finished) */
157 {6, {{0, 1, DOWN}}, {}},
158 {7, {}, {{2, 0, UP}}},
159
160 /* Press key again after 1ms delay (debounce has not yet finished) */
161 {9, {{2, 0, DOWN}}, {}},
162 {10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
163
164 {12, {}, {{2, 0, DOWN}}}, /* 5ms after UP at time 7 */
165 });
166 runEvents();
167}
168
169TEST_F(DebounceTest, TwoKeysOverlap) {
170 addEvents({ /* Time, Inputs, Outputs */
171 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
172 {1, {{0, 1, UP}}, {}},
173 /* Press a second key during the first debounce */
174 {2, {{0, 2, DOWN}}, {}},
175
176 /* Key registers as soon as debounce finishes, 5ms after time 0 */
177 {5, {}, {{0, 1, UP}, {0, 2, DOWN}}},
178 {6, {{0, 1, DOWN}}, {}},
179
180 /* Key registers as soon as debounce finishes, 5ms after time 5 */
181 {10, {}, {{0, 1, DOWN}}},
182 /* Release both keys */
183 {11, {{0, 1, UP}}, {}},
184 {12, {{0, 2, UP}}, {}},
185
186 /* Keys register as soon as debounce finishes, 5ms after time 10 */
187 {15, {}, {{0, 1, UP}, {0, 2, UP}}},
188 });
189 runEvents();
190}
191
192TEST_F(DebounceTest, TwoKeysSimultaneous1) {
193 addEvents({ /* Time, Inputs, Outputs */
194 {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {{0, 1, DOWN}, {0, 2, DOWN}}},
195 {20, {{0, 1, UP}}, {{0, 1, UP}}},
196 {21, {{0, 2, UP}}, {}},
197
198 /* Key registers as soon as debounce finishes, 5ms after time 20 */
199 {25, {}, {{0, 2, UP}}},
200 });
201 runEvents();
202}
203
204TEST_F(DebounceTest, TwoKeysSimultaneous2) {
205 addEvents({ /* Time, Inputs, Outputs */
206 {0, {{0, 1, DOWN}, {0, 2, DOWN}}, {{0, 1, DOWN}, {0, 2, DOWN}}},
207 {20, {{0, 1, UP}, {0, 2, UP}}, {{0, 1, UP}, {0, 2, UP}}},
208 });
209 runEvents();
210}
211
212TEST_F(DebounceTest, OneKeyDelayedScan1) {
213 addEvents({ /* Time, Inputs, Outputs */
214 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
215
216 /* Processing is very late but the change will now be accepted */
217 {300, {{0, 1, UP}}, {{0, 1, UP}}},
218 });
219 time_jumps_ = true;
220 runEvents();
221}
222
223TEST_F(DebounceTest, OneKeyDelayedScan2) {
224 addEvents({ /* Time, Inputs, Outputs */
225 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
226
227 /* Processing is very late but the change will now be accepted even with a 1 scan delay */
228 {300, {}, {}},
229 {300, {{0, 1, UP}}, {{0, 1, UP}}},
230 });
231 time_jumps_ = true;
232 runEvents();
233}
234
235TEST_F(DebounceTest, OneKeyDelayedScan3) {
236 addEvents({ /* Time, Inputs, Outputs */
237 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
238
239 /* Processing is very late but the change will now be accepted even with a 1ms delay */
240 {300, {}, {}},
241 {301, {{0, 1, UP}}, {{0, 1, UP}}},
242 });
243 time_jumps_ = true;
244 runEvents();
245}
246
247TEST_F(DebounceTest, OneKeyDelayedScan4) {
248 addEvents({ /* Time, Inputs, Outputs */
249 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
250
251 /* Processing is a bit late but the change will now be accepted */
252 {50, {{0, 1, UP}}, {{0, 1, UP}}},
253 });
254 time_jumps_ = true;
255 runEvents();
256}
257
258TEST_F(DebounceTest, OneKeyDelayedScan5) {
259 addEvents({ /* Time, Inputs, Outputs */
260 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
261
262 /* Processing is very late but the change will now be accepted even with a 1 scan delay */
263 {50, {}, {}},
264 {50, {{0, 1, UP}}, {{0, 1, UP}}},
265 });
266 time_jumps_ = true;
267 runEvents();
268}
269
270TEST_F(DebounceTest, OneKeyDelayedScan6) {
271 addEvents({ /* Time, Inputs, Outputs */
272 {0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
273
274 /* Processing is very late but the change will now be accepted even with a 1ms delay */
275 {50, {}, {}},
276 {51, {{0, 1, UP}}, {{0, 1, UP}}},
277 });
278 time_jumps_ = true;
279 runEvents();
280}
diff --git a/quantum/debounce/tests/testlist.mk b/quantum/debounce/tests/testlist.mk
new file mode 100644
index 000000000..c54c45aa6
--- /dev/null
+++ b/quantum/debounce/tests/testlist.mk
@@ -0,0 +1,6 @@
1TEST_LIST += \
2 debounce_sym_defer_g \
3 debounce_sym_defer_pk \
4 debounce_sym_eager_pk \
5 debounce_sym_eager_pr \
6 debounce_asym_eager_defer_pk
diff --git a/quantum/led_matrix.c b/quantum/led_matrix.c
index 7e0fdf896..e7e933e1d 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};
33const led_point_t k_led_matrix_center = LED_MATRIX_CENTER; 33const led_point_t k_led_matrix_center = LED_MATRIX_CENTER;
34#endif 34#endif
35 35
36// clang-format off
37#ifndef LED_MATRIX_IMMEDIATE_EEPROM
38# define led_eeconfig_update(v) led_update_eeprom |= v
39#else
40# define led_eeconfig_update(v) if (v) eeconfig_update_led_matrix()
41#endif
42// clang-format on
43
36// Generic effect runners 44// Generic effect runners
37#include "led_matrix_runners/effect_runner_dx_dy_dist.h" 45#include "led_matrix_runners/effect_runner_dx_dy_dist.h"
38#include "led_matrix_runners/effect_runner_dx_dy.h" 46#include "led_matrix_runners/effect_runner_dx_dy.h"
@@ -67,10 +75,6 @@ const led_point_t k_led_matrix_center = LED_MATRIX_CENTER;
67# define LED_DISABLE_TIMEOUT 0 75# define LED_DISABLE_TIMEOUT 0
68#endif 76#endif
69 77
70#if LED_DISABLE_WHEN_USB_SUSPENDED != 1
71# undef LED_DISABLE_WHEN_USB_SUSPENDED
72#endif
73
74#if !defined(LED_MATRIX_MAXIMUM_BRIGHTNESS) || LED_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX 78#if !defined(LED_MATRIX_MAXIMUM_BRIGHTNESS) || LED_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX
75# undef LED_MATRIX_MAXIMUM_BRIGHTNESS 79# undef LED_MATRIX_MAXIMUM_BRIGHTNESS
76# define LED_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX 80# define LED_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX
@@ -108,6 +112,7 @@ last_hit_t g_last_hit_tracker;
108 112
109// internals 113// internals
110static bool suspend_state = false; 114static bool suspend_state = false;
115static bool led_update_eeprom = false;
111static uint8_t led_last_enable = UINT8_MAX; 116static uint8_t led_last_enable = UINT8_MAX;
112static uint8_t led_last_effect = UINT8_MAX; 117static uint8_t led_last_effect = UINT8_MAX;
113static effect_params_t led_effect_params = {0, LED_FLAG_ALL, false}; 118static effect_params_t led_effect_params = {0, LED_FLAG_ALL, false};
@@ -280,6 +285,7 @@ static void led_task_timers(void) {
280 285
281static void led_task_sync(void) { 286static void led_task_sync(void) {
282 // next task 287 // next task
288 if (led_update_eeprom) eeconfig_update_led_matrix();
283 if (sync_timer_elapsed32(g_led_timer) >= LED_MATRIX_LED_FLUSH_LIMIT) led_task_state = STARTING; 289 if (sync_timer_elapsed32(g_led_timer) >= LED_MATRIX_LED_FLUSH_LIMIT) led_task_state = STARTING;
284} 290}
285 291
@@ -469,9 +475,7 @@ bool led_matrix_get_suspend_state(void) { return suspend_state; }
469void led_matrix_toggle_eeprom_helper(bool write_to_eeprom) { 475void led_matrix_toggle_eeprom_helper(bool write_to_eeprom) {
470 led_matrix_eeconfig.enable ^= 1; 476 led_matrix_eeconfig.enable ^= 1;
471 led_task_state = STARTING; 477 led_task_state = STARTING;
472 if (write_to_eeprom) { 478 led_eeconfig_update(write_to_eeprom);
473 eeconfig_update_led_matrix();
474 }
475 dprintf("led matrix toggle [%s]: led_matrix_eeconfig.enable = %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.enable); 479 dprintf("led matrix toggle [%s]: led_matrix_eeconfig.enable = %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.enable);
476} 480}
477void led_matrix_toggle_noeeprom(void) { led_matrix_toggle_eeprom_helper(false); } 481void 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); }
479 483
480void led_matrix_enable(void) { 484void led_matrix_enable(void) {
481 led_matrix_enable_noeeprom(); 485 led_matrix_enable_noeeprom();
482 eeconfig_update_led_matrix(); 486 led_eeconfig_update(true);
483} 487}
484 488
485void led_matrix_enable_noeeprom(void) { 489void led_matrix_enable_noeeprom(void) {
@@ -489,7 +493,7 @@ void led_matrix_enable_noeeprom(void) {
489 493
490void led_matrix_disable(void) { 494void led_matrix_disable(void) {
491 led_matrix_disable_noeeprom(); 495 led_matrix_disable_noeeprom();
492 eeconfig_update_led_matrix(); 496 led_eeconfig_update(true);
493} 497}
494 498
495void led_matrix_disable_noeeprom(void) { 499void led_matrix_disable_noeeprom(void) {
@@ -511,9 +515,7 @@ void led_matrix_mode_eeprom_helper(uint8_t mode, bool write_to_eeprom) {
511 led_matrix_eeconfig.mode = mode; 515 led_matrix_eeconfig.mode = mode;
512 } 516 }
513 led_task_state = STARTING; 517 led_task_state = STARTING;
514 if (write_to_eeprom) { 518 led_eeconfig_update(write_to_eeprom);
515 eeconfig_update_led_matrix();
516 }
517 dprintf("led matrix mode [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.mode); 519 dprintf("led matrix mode [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.mode);
518} 520}
519void led_matrix_mode_noeeprom(uint8_t mode) { led_matrix_mode_eeprom_helper(mode, false); } 521void 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) {
540 return; 542 return;
541 } 543 }
542 led_matrix_eeconfig.val = (val > LED_MATRIX_MAXIMUM_BRIGHTNESS) ? LED_MATRIX_MAXIMUM_BRIGHTNESS : val; 544 led_matrix_eeconfig.val = (val > LED_MATRIX_MAXIMUM_BRIGHTNESS) ? LED_MATRIX_MAXIMUM_BRIGHTNESS : val;
543 if (write_to_eeprom) { 545 led_eeconfig_update(write_to_eeprom);
544 eeconfig_update_led_matrix();
545 }
546 dprintf("led matrix set val [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.val); 546 dprintf("led matrix set val [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.val);
547} 547}
548void led_matrix_set_val_noeeprom(uint8_t val) { led_matrix_set_val_eeprom_helper(val, false); } 548void 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); }
560 560
561void led_matrix_set_speed_eeprom_helper(uint8_t speed, bool write_to_eeprom) { 561void led_matrix_set_speed_eeprom_helper(uint8_t speed, bool write_to_eeprom) {
562 led_matrix_eeconfig.speed = speed; 562 led_matrix_eeconfig.speed = speed;
563 if (write_to_eeprom) { 563 led_eeconfig_update(write_to_eeprom);
564 eeconfig_update_led_matrix();
565 }
566 dprintf("led matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.speed); 564 dprintf("led matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", led_matrix_eeconfig.speed);
567} 565}
568void led_matrix_set_speed_noeeprom(uint8_t speed) { led_matrix_set_speed_eeprom_helper(speed, false); } 566void 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 34d6af2e6..71ef27089 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/>.
16*/ 16*/
17#include <stdint.h> 17#include <stdint.h>
18#include <stdbool.h> 18#include <stdbool.h>
19#include <string.h>
19#include "util.h" 20#include "util.h"
20#include "matrix.h" 21#include "matrix.h"
21#include "debounce.h" 22#include "debounce.h"
@@ -24,14 +25,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
24#ifdef DIRECT_PINS 25#ifdef DIRECT_PINS
25static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS; 26static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS;
26#elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW) 27#elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW)
28# ifdef MATRIX_ROW_PINS
27static const pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; 29static const pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS;
30# endif // MATRIX_ROW_PINS
31# ifdef MATRIX_COL_PINS
28static const pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; 32static const pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS;
33# endif // MATRIX_COL_PINS
29#endif 34#endif
30 35
31/* matrix state(1:on, 0:off) */ 36/* matrix state(1:on, 0:off) */
32extern matrix_row_t raw_matrix[MATRIX_ROWS]; // raw values 37extern matrix_row_t raw_matrix[MATRIX_ROWS]; // raw values
33extern matrix_row_t matrix[MATRIX_ROWS]; // debounced values 38extern matrix_row_t matrix[MATRIX_ROWS]; // debounced values
34 39
40// user-defined overridable functions
41__attribute__((weak)) void matrix_init_pins(void);
42__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row);
43__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col);
44
35static inline void setPinOutput_writeLow(pin_t pin) { 45static inline void setPinOutput_writeLow(pin_t pin) {
36 ATOMIC_BLOCK_FORCEON { 46 ATOMIC_BLOCK_FORCEON {
37 setPinOutput(pin); 47 setPinOutput(pin);
@@ -47,7 +57,7 @@ static inline void setPinInputHigh_atomic(pin_t pin) {
47 57
48#ifdef DIRECT_PINS 58#ifdef DIRECT_PINS
49 59
50static void init_pins(void) { 60__attribute__((weak)) void matrix_init_pins(void) {
51 for (int row = 0; row < MATRIX_ROWS; row++) { 61 for (int row = 0; row < MATRIX_ROWS; row++) {
52 for (int col = 0; col < MATRIX_COLS; col++) { 62 for (int col = 0; col < MATRIX_COLS; col++) {
53 pin_t pin = direct_pins[row][col]; 63 pin_t pin = direct_pins[row][col];
@@ -58,7 +68,7 @@ static void init_pins(void) {
58 } 68 }
59} 69}
60 70
61static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { 71__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
62 // Start with a clear matrix row 72 // Start with a clear matrix row
63 matrix_row_t current_row_value = 0; 73 matrix_row_t current_row_value = 0;
64 74
@@ -69,16 +79,13 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
69 } 79 }
70 } 80 }
71 81
72 // If the row has changed, store the row and return the changed flag. 82 // Update the matrix
73 if (current_matrix[current_row] != current_row_value) { 83 current_matrix[current_row] = current_row_value;
74 current_matrix[current_row] = current_row_value;
75 return true;
76 }
77 return false;
78} 84}
79 85
80#elif defined(DIODE_DIRECTION) 86#elif defined(DIODE_DIRECTION)
81# if (DIODE_DIRECTION == COL2ROW) 87# if defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS)
88# if (DIODE_DIRECTION == COL2ROW)
82 89
83static void select_row(uint8_t row) { setPinOutput_writeLow(row_pins[row]); } 90static void select_row(uint8_t row) { setPinOutput_writeLow(row_pins[row]); }
84 91
@@ -90,14 +97,14 @@ static void unselect_rows(void) {
90 } 97 }
91} 98}
92 99
93static void init_pins(void) { 100__attribute__((weak)) void matrix_init_pins(void) {
94 unselect_rows(); 101 unselect_rows();
95 for (uint8_t x = 0; x < MATRIX_COLS; x++) { 102 for (uint8_t x = 0; x < MATRIX_COLS; x++) {
96 setPinInputHigh_atomic(col_pins[x]); 103 setPinInputHigh_atomic(col_pins[x]);
97 } 104 }
98} 105}
99 106
100static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { 107__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
101 // Start with a clear matrix row 108 // Start with a clear matrix row
102 matrix_row_t current_row_value = 0; 109 matrix_row_t current_row_value = 0;
103 110
@@ -118,15 +125,11 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
118 unselect_row(current_row); 125 unselect_row(current_row);
119 matrix_output_unselect_delay(); // wait for all Col signals to go HIGH 126 matrix_output_unselect_delay(); // wait for all Col signals to go HIGH
120 127
121 // If the row has changed, store the row and return the changed flag. 128 // Update the matrix
122 if (current_matrix[current_row] != current_row_value) { 129 current_matrix[current_row] = current_row_value;
123 current_matrix[current_row] = current_row_value;
124 return true;
125 }
126 return false;
127} 130}
128 131
129# elif (DIODE_DIRECTION == ROW2COL) 132# elif (DIODE_DIRECTION == ROW2COL)
130 133
131static void select_col(uint8_t col) { setPinOutput_writeLow(col_pins[col]); } 134static void select_col(uint8_t col) { setPinOutput_writeLow(col_pins[col]); }
132 135
@@ -138,59 +141,46 @@ static void unselect_cols(void) {
138 } 141 }
139} 142}
140 143
141static void init_pins(void) { 144__attribute__((weak)) void matrix_init_pins(void) {
142 unselect_cols(); 145 unselect_cols();
143 for (uint8_t x = 0; x < MATRIX_ROWS; x++) { 146 for (uint8_t x = 0; x < MATRIX_ROWS; x++) {
144 setPinInputHigh_atomic(row_pins[x]); 147 setPinInputHigh_atomic(row_pins[x]);
145 } 148 }
146} 149}
147 150
148static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { 151__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) {
149 bool matrix_changed = false;
150
151 // Select col 152 // Select col
152 select_col(current_col); 153 select_col(current_col);
153 matrix_output_select_delay(); 154 matrix_output_select_delay();
154 155
155 // For each row... 156 // For each row...
156 for (uint8_t row_index = 0; row_index < MATRIX_ROWS; row_index++) { 157 for (uint8_t row_index = 0; row_index < MATRIX_ROWS; row_index++) {
157 // Store last value of row prior to reading
158 matrix_row_t last_row_value = current_matrix[row_index];
159 matrix_row_t current_row_value = last_row_value;
160
161 // Check row pin state 158 // Check row pin state
162 if (readPin(row_pins[row_index]) == 0) { 159 if (readPin(row_pins[row_index]) == 0) {
163 // Pin LO, set col bit 160 // Pin LO, set col bit
164 current_row_value |= (MATRIX_ROW_SHIFTER << current_col); 161 current_matrix[row_index] |= (MATRIX_ROW_SHIFTER << current_col);
165 } else { 162 } else {
166 // Pin HI, clear col bit 163 // Pin HI, clear col bit
167 current_row_value &= ~(MATRIX_ROW_SHIFTER << current_col); 164 current_matrix[row_index] &= ~(MATRIX_ROW_SHIFTER << current_col);
168 }
169
170 // Determine if the matrix changed state
171 if ((last_row_value != current_row_value)) {
172 matrix_changed |= true;
173 current_matrix[row_index] = current_row_value;
174 } 165 }
175 } 166 }
176 167
177 // Unselect col 168 // Unselect col
178 unselect_col(current_col); 169 unselect_col(current_col);
179 matrix_output_unselect_delay(); // wait for all Row signals to go HIGH 170 matrix_output_unselect_delay(); // wait for all Row signals to go HIGH
180
181 return matrix_changed;
182} 171}
183 172
184# else 173# else
185# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! 174# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL!
186# endif 175# endif
176# endif // defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS)
187#else 177#else
188# error DIODE_DIRECTION is not defined! 178# error DIODE_DIRECTION is not defined!
189#endif 179#endif
190 180
191void matrix_init(void) { 181void matrix_init(void) {
192 // initialize key pins 182 // initialize key pins
193 init_pins(); 183 matrix_init_pins();
194 184
195 // initialize matrix state: all keys off 185 // initialize matrix state: all keys off
196 for (uint8_t i = 0; i < MATRIX_ROWS; i++) { 186 for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
@@ -204,20 +194,23 @@ void matrix_init(void) {
204} 194}
205 195
206uint8_t matrix_scan(void) { 196uint8_t matrix_scan(void) {
207 bool changed = false; 197 matrix_row_t curr_matrix[MATRIX_ROWS] = {0};
208 198
209#if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW) 199#if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW)
210 // Set row, read cols 200 // Set row, read cols
211 for (uint8_t current_row = 0; current_row < MATRIX_ROWS; current_row++) { 201 for (uint8_t current_row = 0; current_row < MATRIX_ROWS; current_row++) {
212 changed |= read_cols_on_row(raw_matrix, current_row); 202 matrix_read_cols_on_row(curr_matrix, current_row);
213 } 203 }
214#elif (DIODE_DIRECTION == ROW2COL) 204#elif (DIODE_DIRECTION == ROW2COL)
215 // Set col, read rows 205 // Set col, read rows
216 for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) { 206 for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) {
217 changed |= read_rows_on_col(raw_matrix, current_col); 207 matrix_read_rows_on_col(curr_matrix, current_col);
218 } 208 }
219#endif 209#endif
220 210
211 bool changed = memcmp(raw_matrix, curr_matrix, sizeof(curr_matrix)) != 0;
212 if (changed) memcpy(raw_matrix, curr_matrix, sizeof(curr_matrix));
213
221 debounce(raw_matrix, matrix, MATRIX_ROWS, changed); 214 debounce(raw_matrix, matrix, MATRIX_ROWS, changed);
222 215
223 matrix_scan_quantum(); 216 matrix_scan_quantum();
diff --git a/quantum/quantum.h b/quantum/quantum.h
index e4a7c5723..66ba96fde 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -176,6 +176,10 @@ extern layer_state_t layer_state;
176# include "oled_driver.h" 176# include "oled_driver.h"
177#endif 177#endif
178 178
179#ifdef ST7565_ENABLE
180# include "st7565.h"
181#endif
182
179#ifdef DIP_SWITCH_ENABLE 183#ifdef DIP_SWITCH_ENABLE
180# include "dip_switch.h" 184# include "dip_switch.h"
181#endif 185#endif
diff --git a/quantum/rgb_matrix.c b/quantum/rgb_matrix.c
index ab8dbd849..3c5ddba93 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};
31const led_point_t k_rgb_matrix_center = RGB_MATRIX_CENTER; 31const led_point_t k_rgb_matrix_center = RGB_MATRIX_CENTER;
32#endif 32#endif
33 33
34// clang-format off
35#ifndef RGB_MATRIX_IMMEDIATE_EEPROM
36# define rgb_eeconfig_update(v) rgb_update_eeprom |= v
37#else
38# define rgb_eeconfig_update(v) if (v) eeconfig_update_rgb_matrix()
39#endif
40// clang-format on
41
34__attribute__((weak)) RGB rgb_matrix_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv); } 42__attribute__((weak)) RGB rgb_matrix_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv); }
35 43
36// Generic effect runners 44// Generic effect runners
@@ -67,10 +75,6 @@ __attribute__((weak)) RGB rgb_matrix_hsv_to_rgb(HSV hsv) { return hsv_to_rgb(hsv
67# define RGB_DISABLE_TIMEOUT 0 75# define RGB_DISABLE_TIMEOUT 0
68#endif 76#endif
69 77
70#if RGB_DISABLE_WHEN_USB_SUSPENDED != 1
71# undef RGB_DISABLE_WHEN_USB_SUSPENDED
72#endif
73
74#if !defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS) || RGB_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX 78#if !defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS) || RGB_MATRIX_MAXIMUM_BRIGHTNESS > UINT8_MAX
75# undef RGB_MATRIX_MAXIMUM_BRIGHTNESS 79# undef RGB_MATRIX_MAXIMUM_BRIGHTNESS
76# define RGB_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX 80# define RGB_MATRIX_MAXIMUM_BRIGHTNESS UINT8_MAX
@@ -129,6 +133,7 @@ last_hit_t g_last_hit_tracker;
129 133
130// internals 134// internals
131static bool suspend_state = false; 135static bool suspend_state = false;
136static bool rgb_update_eeprom = false;
132static uint8_t rgb_last_enable = UINT8_MAX; 137static uint8_t rgb_last_enable = UINT8_MAX;
133static uint8_t rgb_last_effect = UINT8_MAX; 138static uint8_t rgb_last_effect = UINT8_MAX;
134static effect_params_t rgb_effect_params = {0, LED_FLAG_ALL, false}; 139static effect_params_t rgb_effect_params = {0, LED_FLAG_ALL, false};
@@ -315,6 +320,7 @@ static void rgb_task_timers(void) {
315 320
316static void rgb_task_sync(void) { 321static void rgb_task_sync(void) {
317 // next task 322 // next task
323 if (rgb_update_eeprom) eeconfig_update_rgb_matrix();
318 if (sync_timer_elapsed32(g_rgb_timer) >= RGB_MATRIX_LED_FLUSH_LIMIT) rgb_task_state = STARTING; 324 if (sync_timer_elapsed32(g_rgb_timer) >= RGB_MATRIX_LED_FLUSH_LIMIT) rgb_task_state = STARTING;
319} 325}
320 326
@@ -511,9 +517,7 @@ bool rgb_matrix_get_suspend_state(void) { return suspend_state; }
511void rgb_matrix_toggle_eeprom_helper(bool write_to_eeprom) { 517void rgb_matrix_toggle_eeprom_helper(bool write_to_eeprom) {
512 rgb_matrix_config.enable ^= 1; 518 rgb_matrix_config.enable ^= 1;
513 rgb_task_state = STARTING; 519 rgb_task_state = STARTING;
514 if (write_to_eeprom) { 520 rgb_eeconfig_update(write_to_eeprom);
515 eeconfig_update_rgb_matrix();
516 }
517 dprintf("rgb matrix toggle [%s]: rgb_matrix_config.enable = %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.enable); 521 dprintf("rgb matrix toggle [%s]: rgb_matrix_config.enable = %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.enable);
518} 522}
519void rgb_matrix_toggle_noeeprom(void) { rgb_matrix_toggle_eeprom_helper(false); } 523void 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); }
521 525
522void rgb_matrix_enable(void) { 526void rgb_matrix_enable(void) {
523 rgb_matrix_enable_noeeprom(); 527 rgb_matrix_enable_noeeprom();
524 eeconfig_update_rgb_matrix(); 528 rgb_eeconfig_update(true);
525} 529}
526 530
527void rgb_matrix_enable_noeeprom(void) { 531void rgb_matrix_enable_noeeprom(void) {
@@ -531,7 +535,7 @@ void rgb_matrix_enable_noeeprom(void) {
531 535
532void rgb_matrix_disable(void) { 536void rgb_matrix_disable(void) {
533 rgb_matrix_disable_noeeprom(); 537 rgb_matrix_disable_noeeprom();
534 eeconfig_update_rgb_matrix(); 538 rgb_eeconfig_update(true);
535} 539}
536 540
537void rgb_matrix_disable_noeeprom(void) { 541void rgb_matrix_disable_noeeprom(void) {
@@ -553,9 +557,7 @@ void rgb_matrix_mode_eeprom_helper(uint8_t mode, bool write_to_eeprom) {
553 rgb_matrix_config.mode = mode; 557 rgb_matrix_config.mode = mode;
554 } 558 }
555 rgb_task_state = STARTING; 559 rgb_task_state = STARTING;
556 if (write_to_eeprom) { 560 rgb_eeconfig_update(write_to_eeprom);
557 eeconfig_update_rgb_matrix();
558 }
559 dprintf("rgb matrix mode [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.mode); 561 dprintf("rgb matrix mode [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.mode);
560} 562}
561void rgb_matrix_mode_noeeprom(uint8_t mode) { rgb_matrix_mode_eeprom_helper(mode, false); } 563void 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
584 rgb_matrix_config.hsv.h = hue; 586 rgb_matrix_config.hsv.h = hue;
585 rgb_matrix_config.hsv.s = sat; 587 rgb_matrix_config.hsv.s = sat;
586 rgb_matrix_config.hsv.v = (val > RGB_MATRIX_MAXIMUM_BRIGHTNESS) ? RGB_MATRIX_MAXIMUM_BRIGHTNESS : val; 588 rgb_matrix_config.hsv.v = (val > RGB_MATRIX_MAXIMUM_BRIGHTNESS) ? RGB_MATRIX_MAXIMUM_BRIGHTNESS : val;
587 if (write_to_eeprom) { 589 rgb_eeconfig_update(write_to_eeprom);
588 eeconfig_update_rgb_matrix();
589 }
590 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); 590 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);
591} 591}
592void rgb_matrix_sethsv_noeeprom(uint16_t hue, uint8_t sat, uint8_t val) { rgb_matrix_sethsv_eeprom_helper(hue, sat, val, false); } 592void 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); }
623 623
624void rgb_matrix_set_speed_eeprom_helper(uint8_t speed, bool write_to_eeprom) { 624void rgb_matrix_set_speed_eeprom_helper(uint8_t speed, bool write_to_eeprom) {
625 rgb_matrix_config.speed = speed; 625 rgb_matrix_config.speed = speed;
626 if (write_to_eeprom) { 626 rgb_eeconfig_update(write_to_eeprom);
627 eeconfig_update_rgb_matrix();
628 }
629 dprintf("rgb matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.speed); 627 dprintf("rgb matrix set speed [%s]: %u\n", (write_to_eeprom) ? "EEPROM" : "NOEEPROM", rgb_matrix_config.speed);
630} 628}
631void rgb_matrix_set_speed_noeeprom(uint8_t speed) { rgb_matrix_set_speed_eeprom_helper(speed, false); } 629void 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 a615b8422..741a2fe44 100644
--- a/quantum/rgb_matrix.h
+++ b/quantum/rgb_matrix.h
@@ -33,6 +33,8 @@
33# include "is31fl3737.h" 33# include "is31fl3737.h"
34#elif defined(IS31FL3741) 34#elif defined(IS31FL3741)
35# include "is31fl3741.h" 35# include "is31fl3741.h"
36#elif defined(AW20216)
37# include "aw20216.h"
36#elif defined(WS2812) 38#elif defined(WS2812)
37# include "ws2812.h" 39# include "ws2812.h"
38#endif 40#endif
diff --git a/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h b/quantum/rgb_matrix_animations/jellybean_raindrops_anim.h
index 9493b3850..a17e954b1 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)
4 4
5static void jellybean_raindrops_set_color(int i, effect_params_t* params) { 5static void jellybean_raindrops_set_color(int i, effect_params_t* params) {
6 if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) return; 6 if (!HAS_ANY_FLAGS(g_led_config.flags[i], params->flags)) return;
7 HSV hsv = {rand() & 0xFF, rand() & 0xFF, rgb_matrix_config.hsv.v}; 7 HSV hsv = {rand() & 0xFF, qadd8(rand() & 0x7F, 0x80), rgb_matrix_config.hsv.v};
8 RGB rgb = rgb_matrix_hsv_to_rgb(hsv); 8 RGB rgb = rgb_matrix_hsv_to_rgb(hsv);
9 rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b); 9 rgb_matrix_set_color(i, rgb.r, rgb.g, rgb.b);
10} 10}
diff --git a/quantum/rgb_matrix_drivers.c b/quantum/rgb_matrix_drivers.c
index 896fa6d0e..6a11d4791 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 = {
171}; 171};
172# endif 172# endif
173 173
174#elif defined(AW20216)
175# include "spi_master.h"
176static void init(void) {
177 spi_init();
178 AW20216_init();
179}
180
181static void flush(void) { AW20216_update_pwm_buffers(); }
182
183const rgb_matrix_driver_t rgb_matrix_driver = {
184 .init = init,
185 .flush = flush,
186 .set_color = AW20216_set_color,
187 .set_color_all = AW20216_set_color_all,
188};
189
174#elif defined(WS2812) 190#elif defined(WS2812)
175# if defined(RGBLIGHT_ENABLE) && !defined(RGBLIGHT_CUSTOM_DRIVER) 191# if defined(RGBLIGHT_ENABLE) && !defined(RGBLIGHT_CUSTOM_DRIVER)
176# pragma message "Cannot use RGBLIGHT and RGB Matrix using WS2812 at the same time." 192# pragma message "Cannot use RGBLIGHT and RGB Matrix using WS2812 at the same time."
diff --git a/quantum/split_common/matrix.c b/quantum/split_common/matrix.c
index 039e7d977..56d91b07f 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/>.
16*/ 16*/
17#include <stdint.h> 17#include <stdint.h>
18#include <stdbool.h> 18#include <stdbool.h>
19#include <string.h>
19#include "util.h" 20#include "util.h"
20#include "matrix.h" 21#include "matrix.h"
21#include "debounce.h" 22#include "debounce.h"
22#include "quantum.h" 23#include "quantum.h"
23#include "split_util.h" 24#include "split_util.h"
24#include "config.h" 25#include "config.h"
25#include "transport.h" 26#include "transactions.h"
26 27
27#define ERROR_DISCONNECT_COUNT 5 28#ifndef ERROR_DISCONNECT_COUNT
29# define ERROR_DISCONNECT_COUNT 5
30#endif // ERROR_DISCONNECT_COUNT
28 31
29#define ROWS_PER_HAND (MATRIX_ROWS / 2) 32#define ROWS_PER_HAND (MATRIX_ROWS / 2)
30 33
31#ifdef DIRECT_PINS 34#ifdef DIRECT_PINS
32static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS; 35static pin_t direct_pins[MATRIX_ROWS][MATRIX_COLS] = DIRECT_PINS;
33#elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW) 36#elif (DIODE_DIRECTION == ROW2COL) || (DIODE_DIRECTION == COL2ROW)
37# ifdef MATRIX_ROW_PINS
34static pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS; 38static pin_t row_pins[MATRIX_ROWS] = MATRIX_ROW_PINS;
39# endif // MATRIX_ROW_PINS
40# ifdef MATRIX_COL_PINS
35static pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; 41static pin_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS;
42# endif // MATRIX_COL_PINS
36#endif 43#endif
37 44
38/* matrix state(1:on, 0:off) */ 45/* matrix state(1:on, 0:off) */
@@ -45,6 +52,9 @@ uint8_t thisHand, thatHand;
45// user-defined overridable functions 52// user-defined overridable functions
46__attribute__((weak)) void matrix_slave_scan_kb(void) { matrix_slave_scan_user(); } 53__attribute__((weak)) void matrix_slave_scan_kb(void) { matrix_slave_scan_user(); }
47__attribute__((weak)) void matrix_slave_scan_user(void) {} 54__attribute__((weak)) void matrix_slave_scan_user(void) {}
55__attribute__((weak)) void matrix_init_pins(void);
56__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row);
57__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col);
48 58
49static inline void setPinOutput_writeLow(pin_t pin) { 59static inline void setPinOutput_writeLow(pin_t pin) {
50 ATOMIC_BLOCK_FORCEON { 60 ATOMIC_BLOCK_FORCEON {
@@ -61,7 +71,7 @@ static inline void setPinInputHigh_atomic(pin_t pin) {
61 71
62#ifdef DIRECT_PINS 72#ifdef DIRECT_PINS
63 73
64static void init_pins(void) { 74__attribute__((weak)) void matrix_init_pins(void) {
65 for (int row = 0; row < MATRIX_ROWS; row++) { 75 for (int row = 0; row < MATRIX_ROWS; row++) {
66 for (int col = 0; col < MATRIX_COLS; col++) { 76 for (int col = 0; col < MATRIX_COLS; col++) {
67 pin_t pin = direct_pins[row][col]; 77 pin_t pin = direct_pins[row][col];
@@ -72,7 +82,7 @@ static void init_pins(void) {
72 } 82 }
73} 83}
74 84
75static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { 85__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
76 // Start with a clear matrix row 86 // Start with a clear matrix row
77 matrix_row_t current_row_value = 0; 87 matrix_row_t current_row_value = 0;
78 88
@@ -83,16 +93,13 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
83 } 93 }
84 } 94 }
85 95
86 // If the row has changed, store the row and return the changed flag. 96 // Update the matrix
87 if (current_matrix[current_row] != current_row_value) { 97 current_matrix[current_row] = current_row_value;
88 current_matrix[current_row] = current_row_value;
89 return true;
90 }
91 return false;
92} 98}
93 99
94#elif defined(DIODE_DIRECTION) 100#elif defined(DIODE_DIRECTION)
95# if (DIODE_DIRECTION == COL2ROW) 101# if defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS)
102# if (DIODE_DIRECTION == COL2ROW)
96 103
97static void select_row(uint8_t row) { setPinOutput_writeLow(row_pins[row]); } 104static void select_row(uint8_t row) { setPinOutput_writeLow(row_pins[row]); }
98 105
@@ -104,14 +111,14 @@ static void unselect_rows(void) {
104 } 111 }
105} 112}
106 113
107static void init_pins(void) { 114__attribute__((weak)) void matrix_init_pins(void) {
108 unselect_rows(); 115 unselect_rows();
109 for (uint8_t x = 0; x < MATRIX_COLS; x++) { 116 for (uint8_t x = 0; x < MATRIX_COLS; x++) {
110 setPinInputHigh_atomic(col_pins[x]); 117 setPinInputHigh_atomic(col_pins[x]);
111 } 118 }
112} 119}
113 120
114static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) { 121__attribute__((weak)) void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row) {
115 // Start with a clear matrix row 122 // Start with a clear matrix row
116 matrix_row_t current_row_value = 0; 123 matrix_row_t current_row_value = 0;
117 124
@@ -132,15 +139,11 @@ static bool read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)
132 unselect_row(current_row); 139 unselect_row(current_row);
133 matrix_output_unselect_delay(); // wait for all Col signals to go HIGH 140 matrix_output_unselect_delay(); // wait for all Col signals to go HIGH
134 141
135 // If the row has changed, store the row and return the changed flag. 142 // Update the matrix
136 if (current_matrix[current_row] != current_row_value) { 143 current_matrix[current_row] = current_row_value;
137 current_matrix[current_row] = current_row_value;
138 return true;
139 }
140 return false;
141} 144}
142 145
143# elif (DIODE_DIRECTION == ROW2COL) 146# elif (DIODE_DIRECTION == ROW2COL)
144 147
145static void select_col(uint8_t col) { setPinOutput_writeLow(col_pins[col]); } 148static void select_col(uint8_t col) { setPinOutput_writeLow(col_pins[col]); }
146 149
@@ -152,52 +155,39 @@ static void unselect_cols(void) {
152 } 155 }
153} 156}
154 157
155static void init_pins(void) { 158__attribute__((weak)) void matrix_init_pins(void) {
156 unselect_cols(); 159 unselect_cols();
157 for (uint8_t x = 0; x < ROWS_PER_HAND; x++) { 160 for (uint8_t x = 0; x < ROWS_PER_HAND; x++) {
158 setPinInputHigh_atomic(row_pins[x]); 161 setPinInputHigh_atomic(row_pins[x]);
159 } 162 }
160} 163}
161 164
162static bool read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) { 165__attribute__((weak)) void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col) {
163 bool matrix_changed = false;
164
165 // Select col 166 // Select col
166 select_col(current_col); 167 select_col(current_col);
167 matrix_output_select_delay(); 168 matrix_output_select_delay();
168 169
169 // For each row... 170 // For each row...
170 for (uint8_t row_index = 0; row_index < ROWS_PER_HAND; row_index++) { 171 for (uint8_t row_index = 0; row_index < ROWS_PER_HAND; row_index++) {
171 // Store last value of row prior to reading
172 matrix_row_t last_row_value = current_matrix[row_index];
173 matrix_row_t current_row_value = last_row_value;
174
175 // Check row pin state 172 // Check row pin state
176 if (readPin(row_pins[row_index]) == 0) { 173 if (readPin(row_pins[row_index]) == 0) {
177 // Pin LO, set col bit 174 // Pin LO, set col bit
178 current_row_value |= (MATRIX_ROW_SHIFTER << current_col); 175 current_matrix[row_index] |= (MATRIX_ROW_SHIFTER << current_col);
179 } else { 176 } else {
180 // Pin HI, clear col bit 177 // Pin HI, clear col bit
181 current_row_value &= ~(MATRIX_ROW_SHIFTER << current_col); 178 current_matrix[row_index] &= ~(MATRIX_ROW_SHIFTER << current_col);
182 }
183
184 // Determine if the matrix changed state
185 if ((last_row_value != current_row_value)) {
186 matrix_changed |= true;
187 current_matrix[row_index] = current_row_value;
188 } 179 }
189 } 180 }
190 181
191 // Unselect col 182 // Unselect col
192 unselect_col(current_col); 183 unselect_col(current_col);
193 matrix_output_unselect_delay(); // wait for all Row signals to go HIGH 184 matrix_output_unselect_delay(); // wait for all Row signals to go HIGH
194
195 return matrix_changed;
196} 185}
197 186
198# else 187# else
199# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL! 188# error DIODE_DIRECTION must be one of COL2ROW or ROW2COL!
200# endif 189# endif
190# endif // defined(MATRIX_ROW_PINS) && defined(MATRIX_COL_PINS)
201#else 191#else
202# error DIODE_DIRECTION is not defined! 192# error DIODE_DIRECTION is not defined!
203#endif 193#endif
@@ -233,7 +223,7 @@ void matrix_init(void) {
233 thatHand = ROWS_PER_HAND - thisHand; 223 thatHand = ROWS_PER_HAND - thisHand;
234 224
235 // initialize key pins 225 // initialize key pins
236 init_pins(); 226 matrix_init_pins();
237 227
238 // initialize matrix state: all keys off 228 // initialize matrix state: all keys off
239 for (uint8_t i = 0; i < MATRIX_ROWS; i++) { 229 for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
@@ -288,20 +278,23 @@ bool matrix_post_scan(void) {
288} 278}
289 279
290uint8_t matrix_scan(void) { 280uint8_t matrix_scan(void) {
291 bool local_changed = false; 281 matrix_row_t curr_matrix[MATRIX_ROWS] = {0};
292 282
293#if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW) 283#if defined(DIRECT_PINS) || (DIODE_DIRECTION == COL2ROW)
294 // Set row, read cols 284 // Set row, read cols
295 for (uint8_t current_row = 0; current_row < ROWS_PER_HAND; current_row++) { 285 for (uint8_t current_row = 0; current_row < ROWS_PER_HAND; current_row++) {
296 local_changed |= read_cols_on_row(raw_matrix, current_row); 286 matrix_read_cols_on_row(curr_matrix, current_row);
297 } 287 }
298#elif (DIODE_DIRECTION == ROW2COL) 288#elif (DIODE_DIRECTION == ROW2COL)
299 // Set col, read rows 289 // Set col, read rows
300 for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) { 290 for (uint8_t current_col = 0; current_col < MATRIX_COLS; current_col++) {
301 local_changed |= read_rows_on_col(raw_matrix, current_col); 291 matrix_read_rows_on_col(curr_matrix, current_col);
302 } 292 }
303#endif 293#endif
304 294
295 bool local_changed = memcmp(raw_matrix, curr_matrix, sizeof(curr_matrix)) != 0;
296 if (local_changed) memcpy(raw_matrix, curr_matrix, sizeof(curr_matrix));
297
305 debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, local_changed); 298 debounce(raw_matrix, matrix + thisHand, ROWS_PER_HAND, local_changed);
306 299
307 bool remote_changed = matrix_post_scan(); 300 bool remote_changed = matrix_post_scan();
diff --git a/quantum/split_common/post_config.h b/quantum/split_common/post_config.h
index 4ae1d5273..a4c0a1956 100644
--- a/quantum/split_common/post_config.h
+++ b/quantum/split_common/post_config.h
@@ -7,13 +7,4 @@
7# ifndef F_SCL 7# ifndef F_SCL
8# define F_SCL 100000UL // SCL frequency 8# define F_SCL 100000UL // SCL frequency
9# endif 9# endif
10
11#else // use serial
12// When using serial, the user must define RGBLIGHT_SPLIT explicitly
13// in config.h as needed.
14// see quantum/rgblight_post_config.h
15# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
16// When using serial and RGBLIGHT_SPLIT need separate transaction
17# define SERIAL_USE_MULTI_TRANSACTION
18# endif
19#endif 10#endif
diff --git a/quantum/split_common/transaction_id_define.h b/quantum/split_common/transaction_id_define.h
new file mode 100644
index 000000000..464c73478
--- /dev/null
+++ b/quantum/split_common/transaction_id_define.h
@@ -0,0 +1,94 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#pragma once
18
19enum serial_transaction_id {
20#ifdef USE_I2C
21 I2C_EXECUTE_CALLBACK,
22#endif // USE_I2C
23
24 GET_SLAVE_MATRIX_CHECKSUM,
25 GET_SLAVE_MATRIX_DATA,
26
27#ifdef SPLIT_TRANSPORT_MIRROR
28 PUT_MASTER_MATRIX,
29#endif // SPLIT_TRANSPORT_MIRROR
30
31#ifdef ENCODER_ENABLE
32 GET_ENCODERS_CHECKSUM,
33 GET_ENCODERS_DATA,
34#endif // ENCODER_ENABLE
35
36#ifndef DISABLE_SYNC_TIMER
37 PUT_SYNC_TIMER,
38#endif // DISABLE_SYNC_TIMER
39
40#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
41 PUT_LAYER_STATE,
42 PUT_DEFAULT_LAYER_STATE,
43#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
44
45#ifdef SPLIT_LED_STATE_ENABLE
46 PUT_LED_STATE,
47#endif // SPLIT_LED_STATE_ENABLE
48
49#ifdef SPLIT_MODS_ENABLE
50 PUT_MODS,
51#endif // SPLIT_MODS_ENABLE
52
53#ifdef BACKLIGHT_ENABLE
54 PUT_BACKLIGHT,
55#endif // BACKLIGHT_ENABLE
56
57#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
58 PUT_RGBLIGHT,
59#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
60
61#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
62 PUT_LED_MATRIX,
63#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
64
65#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
66 PUT_RGB_MATRIX,
67#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
68
69#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
70 PUT_WPM,
71#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
72
73#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
74 PUT_RPC_INFO,
75 PUT_RPC_REQ_DATA,
76 EXECUTE_RPC,
77 GET_RPC_RESP_DATA,
78#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
79
80// keyboard-specific
81#ifdef SPLIT_TRANSACTION_IDS_KB
82 SPLIT_TRANSACTION_IDS_KB,
83#endif // SPLIT_TRANSACTION_IDS_KB
84
85// user/keymap-specific
86#ifdef SPLIT_TRANSACTION_IDS_USER
87 SPLIT_TRANSACTION_IDS_USER,
88#endif // SPLIT_TRANSACTION_IDS_USER
89
90 NUM_TOTAL_TRANSACTIONS
91};
92
93// Ensure we only use 5 bits for transaction
94_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 000000000..99a8623b3
--- /dev/null
+++ b/quantum/split_common/transactions.c
@@ -0,0 +1,670 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include <string.h>
18#include <stddef.h>
19
20#include "debug.h"
21#include "matrix.h"
22#include "quantum.h"
23#include "transactions.h"
24#include "transport.h"
25#include "transaction_id_define.h"
26
27#define SYNC_TIMER_OFFSET 2
28
29#ifndef FORCED_SYNC_THROTTLE_MS
30# define FORCED_SYNC_THROTTLE_MS 100
31#endif // FORCED_SYNC_THROTTLE_MS
32
33#define sizeof_member(type, member) sizeof(((type *)NULL)->member)
34
35#define trans_initiator2target_initializer_cb(member, cb) \
36 { &dummy, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), 0, 0, cb }
37#define trans_initiator2target_initializer(member) trans_initiator2target_initializer_cb(member, NULL)
38
39#define trans_target2initiator_initializer_cb(member, cb) \
40 { &dummy, 0, 0, sizeof_member(split_shared_memory_t, member), offsetof(split_shared_memory_t, member), cb }
41#define trans_target2initiator_initializer(member) trans_target2initiator_initializer_cb(member, NULL)
42
43#define transport_write(id, data, length) transport_execute_transaction(id, data, length, NULL, 0)
44#define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length)
45
46static uint8_t crc8(const void *data, size_t len) {
47 const uint8_t *p = (const uint8_t *)data;
48 uint8_t crc = 0xff;
49 size_t i, j;
50 for (i = 0; i < len; i++) {
51 crc ^= p[i];
52 for (j = 0; j < 8; j++) {
53 if ((crc & 0x80) != 0)
54 crc = (uint8_t)((crc << 1) ^ 0x31);
55 else
56 crc <<= 1;
57 }
58 }
59 return crc;
60}
61
62#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
63// Forward-declare the RPC callback handlers
64void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
65void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
66#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
67
68////////////////////////////////////////////////////
69// Helpers
70
71bool 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[])) {
72 if (okay) {
73 bool this_okay = true;
74 for (int iter = 1; iter <= 10; ++iter) {
75 if (!this_okay) {
76 for (int i = 0; i < iter * iter; ++i) {
77 wait_us(10);
78 }
79 }
80 ATOMIC_BLOCK_FORCEON { this_okay = handler(master_matrix, slave_matrix); };
81 if (this_okay) break;
82 }
83 okay &= this_okay;
84 if (!okay) {
85 dprintf("Failed to execute %s\n", prefix);
86 }
87 }
88 return okay;
89}
90
91#define TRANSACTION_HANDLER_MASTER(prefix) \
92 do { \
93 okay &= transaction_handler_master(okay, master_matrix, slave_matrix, #prefix, &prefix##_master); \
94 } while (0)
95
96#define TRANSACTION_HANDLER_SLAVE(prefix) \
97 do { \
98 ATOMIC_BLOCK_FORCEON { prefix##_slave(master_matrix, slave_matrix); }; \
99 } while (0)
100
101inline 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) {
102 uint8_t curr_checksum;
103 bool okay = transport_read(trans_id_checksum, &curr_checksum, sizeof(curr_checksum));
104 if (okay && (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || curr_checksum != crc8(equiv_shmem, length))) {
105 okay &= transport_read(trans_id_retrieve, destination, length);
106 okay &= curr_checksum == crc8(equiv_shmem, length);
107 if (okay) {
108 *last_update = timer_read32();
109 }
110 } else {
111 memcpy(destination, equiv_shmem, length);
112 }
113 return okay;
114}
115
116inline static bool send_if_condition(int8_t trans_id, uint32_t *last_update, bool condition, void *source, size_t length) {
117 bool okay = true;
118 if (timer_elapsed32(*last_update) >= FORCED_SYNC_THROTTLE_MS || condition) {
119 okay &= transport_write(trans_id, source, length);
120 if (okay) {
121 *last_update = timer_read32();
122 }
123 }
124 return okay;
125}
126
127inline static bool send_if_data_mismatch(int8_t trans_id, uint32_t *last_update, void *source, const void *equiv_shmem, size_t length) {
128 // Just run a memcmp to compare the source and equivalent shmem location
129 return send_if_condition(trans_id, last_update, (memcmp(source, equiv_shmem, length) != 0), source, length);
130}
131
132////////////////////////////////////////////////////
133// Slave matrix
134
135static bool slave_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
136 static uint32_t last_update = 0;
137 static matrix_row_t last_matrix[(MATRIX_ROWS) / 2] = {0}; // last successfully-read matrix, so we can replicate if there are checksum errors
138 matrix_row_t temp_matrix[(MATRIX_ROWS) / 2]; // holding area while we test whether or not checksum is correct
139
140 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));
141 if (okay) {
142 // Checksum matches the received data, save as the last matrix state
143 memcpy(last_matrix, temp_matrix, sizeof(temp_matrix));
144 }
145 // Copy out the last-known-good matrix state to the slave matrix
146 memcpy(slave_matrix, last_matrix, sizeof(last_matrix));
147 return okay;
148}
149
150static void slave_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
151 memcpy(split_shmem->smatrix.matrix, slave_matrix, sizeof(split_shmem->smatrix.matrix));
152 split_shmem->smatrix.checksum = crc8(split_shmem->smatrix.matrix, sizeof(split_shmem->smatrix.matrix));
153}
154
155// clang-format off
156#define TRANSACTIONS_SLAVE_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(slave_matrix_handlers)
157#define TRANSACTIONS_SLAVE_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(slave_matrix_handlers)
158#define TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS \
159 [GET_SLAVE_MATRIX_CHECKSUM] = trans_target2initiator_initializer(smatrix.checksum), \
160 [GET_SLAVE_MATRIX_DATA] = trans_target2initiator_initializer(smatrix.matrix),
161// clang-format on
162
163////////////////////////////////////////////////////
164// Master matrix
165
166#ifdef SPLIT_TRANSPORT_MIRROR
167
168static bool master_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
169 static uint32_t last_update = 0;
170 return send_if_data_mismatch(PUT_MASTER_MATRIX, &last_update, master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
171}
172
173static void master_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
174 // Always copy to the master matrix
175 memcpy(master_matrix, split_shmem->mmatrix.matrix, sizeof(split_shmem->mmatrix.matrix));
176}
177
178# define TRANSACTIONS_MASTER_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(master_matrix_handlers)
179# define TRANSACTIONS_MASTER_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(master_matrix_handlers)
180# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS [PUT_MASTER_MATRIX] = trans_initiator2target_initializer(mmatrix.matrix),
181
182#else // SPLIT_TRANSPORT_MIRROR
183
184# define TRANSACTIONS_MASTER_MATRIX_MASTER()
185# define TRANSACTIONS_MASTER_MATRIX_SLAVE()
186# define TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
187
188#endif // SPLIT_TRANSPORT_MIRROR
189
190////////////////////////////////////////////////////
191// Encoders
192
193#ifdef ENCODER_ENABLE
194
195static bool encoder_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
196 static uint32_t last_update = 0;
197 uint8_t temp_state[NUMBER_OF_ENCODERS];
198
199 bool okay = read_if_checksum_mismatch(GET_ENCODERS_CHECKSUM, GET_ENCODERS_DATA, &last_update, temp_state, split_shmem->encoders.state, sizeof(temp_state));
200 if (okay) encoder_update_raw(temp_state);
201 return okay;
202}
203
204static void encoder_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
205 uint8_t encoder_state[NUMBER_OF_ENCODERS];
206 encoder_state_raw(encoder_state);
207 // Always prepare the encoder state for read.
208 memcpy(split_shmem->encoders.state, encoder_state, sizeof(encoder_state));
209 // Now update the checksum given that the encoders has been written to
210 split_shmem->encoders.checksum = crc8(encoder_state, sizeof(encoder_state));
211}
212
213// clang-format off
214# define TRANSACTIONS_ENCODERS_MASTER() TRANSACTION_HANDLER_MASTER(encoder_handlers)
215# define TRANSACTIONS_ENCODERS_SLAVE() TRANSACTION_HANDLER_SLAVE(encoder_handlers)
216# define TRANSACTIONS_ENCODERS_REGISTRATIONS \
217 [GET_ENCODERS_CHECKSUM] = trans_target2initiator_initializer(encoders.checksum), \
218 [GET_ENCODERS_DATA] = trans_target2initiator_initializer(encoders.state),
219// clang-format on
220
221#else // ENCODER_ENABLE
222
223# define TRANSACTIONS_ENCODERS_MASTER()
224# define TRANSACTIONS_ENCODERS_SLAVE()
225# define TRANSACTIONS_ENCODERS_REGISTRATIONS
226
227#endif // ENCODER_ENABLE
228
229////////////////////////////////////////////////////
230// Sync timer
231
232#ifndef DISABLE_SYNC_TIMER
233
234static bool sync_timer_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
235 static uint32_t last_update = 0;
236
237 bool okay = true;
238 if (timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS) {
239 uint32_t sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
240 okay &= transport_write(PUT_SYNC_TIMER, &sync_timer, sizeof(sync_timer));
241 if (okay) {
242 last_update = timer_read32();
243 }
244 }
245 return okay;
246}
247
248static void sync_timer_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
249 static uint32_t last_sync_timer = 0;
250 if (last_sync_timer != split_shmem->sync_timer) {
251 last_sync_timer = split_shmem->sync_timer;
252 sync_timer_update(last_sync_timer);
253 }
254}
255
256# define TRANSACTIONS_SYNC_TIMER_MASTER() TRANSACTION_HANDLER_MASTER(sync_timer_handlers)
257# define TRANSACTIONS_SYNC_TIMER_SLAVE() TRANSACTION_HANDLER_SLAVE(sync_timer_handlers)
258# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS [PUT_SYNC_TIMER] = trans_initiator2target_initializer(sync_timer),
259
260#else // DISABLE_SYNC_TIMER
261
262# define TRANSACTIONS_SYNC_TIMER_MASTER()
263# define TRANSACTIONS_SYNC_TIMER_SLAVE()
264# define TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
265
266#endif // DISABLE_SYNC_TIMER
267
268////////////////////////////////////////////////////
269// Layer state
270
271#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
272
273static bool layer_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
274 static uint32_t last_layer_state_update = 0;
275 static uint32_t last_default_layer_state_update = 0;
276
277 bool okay = send_if_condition(PUT_LAYER_STATE, &last_layer_state_update, (layer_state != split_shmem->layers.layer_state), &layer_state, sizeof(layer_state));
278 if (okay) {
279 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));
280 }
281 return okay;
282}
283
284static void layer_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
285 layer_state = split_shmem->layers.layer_state;
286 default_layer_state = split_shmem->layers.default_layer_state;
287}
288
289// clang-format off
290# define TRANSACTIONS_LAYER_STATE_MASTER() TRANSACTION_HANDLER_MASTER(layer_state_handlers)
291# define TRANSACTIONS_LAYER_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(layer_state_handlers)
292# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS \
293 [PUT_LAYER_STATE] = trans_initiator2target_initializer(layers.layer_state), \
294 [PUT_DEFAULT_LAYER_STATE] = trans_initiator2target_initializer(layers.default_layer_state),
295// clang-format on
296
297#else // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
298
299# define TRANSACTIONS_LAYER_STATE_MASTER()
300# define TRANSACTIONS_LAYER_STATE_SLAVE()
301# define TRANSACTIONS_LAYER_STATE_REGISTRATIONS
302
303#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
304
305////////////////////////////////////////////////////
306// LED state
307
308#ifdef SPLIT_LED_STATE_ENABLE
309
310static bool led_state_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
311 static uint32_t last_update = 0;
312 uint8_t led_state = host_keyboard_leds();
313 return send_if_data_mismatch(PUT_LED_STATE, &last_update, &led_state, &split_shmem->led_state, sizeof(led_state));
314}
315
316static void led_state_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
317 void set_split_host_keyboard_leds(uint8_t led_state);
318 set_split_host_keyboard_leds(split_shmem->led_state);
319}
320
321# define TRANSACTIONS_LED_STATE_MASTER() TRANSACTION_HANDLER_MASTER(led_state_handlers)
322# define TRANSACTIONS_LED_STATE_SLAVE() TRANSACTION_HANDLER_SLAVE(led_state_handlers)
323# define TRANSACTIONS_LED_STATE_REGISTRATIONS [PUT_LED_STATE] = trans_initiator2target_initializer(led_state),
324
325#else // SPLIT_LED_STATE_ENABLE
326
327# define TRANSACTIONS_LED_STATE_MASTER()
328# define TRANSACTIONS_LED_STATE_SLAVE()
329# define TRANSACTIONS_LED_STATE_REGISTRATIONS
330
331#endif // SPLIT_LED_STATE_ENABLE
332
333////////////////////////////////////////////////////
334// Mods
335
336#ifdef SPLIT_MODS_ENABLE
337
338static bool mods_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
339 static uint32_t last_update = 0;
340 bool mods_need_sync = timer_elapsed32(last_update) >= FORCED_SYNC_THROTTLE_MS;
341 split_mods_sync_t new_mods;
342 new_mods.real_mods = get_mods();
343 if (!mods_need_sync && new_mods.real_mods != split_shmem->mods.real_mods) {
344 mods_need_sync = true;
345 }
346
347 new_mods.weak_mods = get_weak_mods();
348 if (!mods_need_sync && new_mods.weak_mods != split_shmem->mods.weak_mods) {
349 mods_need_sync = true;
350 }
351
352# ifndef NO_ACTION_ONESHOT
353 new_mods.oneshot_mods = get_oneshot_mods();
354 if (!mods_need_sync && new_mods.oneshot_mods != split_shmem->mods.oneshot_mods) {
355 mods_need_sync = true;
356 }
357# endif // NO_ACTION_ONESHOT
358
359 bool okay = true;
360 if (mods_need_sync) {
361 okay &= transport_write(PUT_MODS, &new_mods, sizeof(new_mods));
362 if (okay) {
363 last_update = timer_read32();
364 }
365 }
366
367 return okay;
368}
369
370static void mods_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
371 set_mods(split_shmem->mods.real_mods);
372 set_weak_mods(split_shmem->mods.weak_mods);
373# ifndef NO_ACTION_ONESHOT
374 set_oneshot_mods(split_shmem->mods.oneshot_mods);
375# endif
376}
377
378# define TRANSACTIONS_MODS_MASTER() TRANSACTION_HANDLER_MASTER(mods_handlers)
379# define TRANSACTIONS_MODS_SLAVE() TRANSACTION_HANDLER_SLAVE(mods_handlers)
380# define TRANSACTIONS_MODS_REGISTRATIONS [PUT_MODS] = trans_initiator2target_initializer(mods),
381
382#else // SPLIT_MODS_ENABLE
383
384# define TRANSACTIONS_MODS_MASTER()
385# define TRANSACTIONS_MODS_SLAVE()
386# define TRANSACTIONS_MODS_REGISTRATIONS
387
388#endif // SPLIT_MODS_ENABLE
389
390////////////////////////////////////////////////////
391// Backlight
392
393#ifdef BACKLIGHT_ENABLE
394
395static bool backlight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
396 static uint32_t last_update = 0;
397 uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0;
398 return send_if_condition(PUT_BACKLIGHT, &last_update, (level != split_shmem->backlight_level), &level, sizeof(level));
399}
400
401static void backlight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { backlight_set(split_shmem->backlight_level); }
402
403# define TRANSACTIONS_BACKLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(backlight_handlers)
404# define TRANSACTIONS_BACKLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(backlight_handlers)
405# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS [PUT_BACKLIGHT] = trans_initiator2target_initializer(backlight_level),
406
407#else // BACKLIGHT_ENABLE
408
409# define TRANSACTIONS_BACKLIGHT_MASTER()
410# define TRANSACTIONS_BACKLIGHT_SLAVE()
411# define TRANSACTIONS_BACKLIGHT_REGISTRATIONS
412
413#endif // BACKLIGHT_ENABLE
414
415////////////////////////////////////////////////////
416// RGBLIGHT
417
418#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
419
420static bool rgblight_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
421 static uint32_t last_update = 0;
422 rgblight_syncinfo_t rgblight_sync;
423 rgblight_get_syncinfo(&rgblight_sync);
424 if (send_if_condition(PUT_RGBLIGHT, &last_update, (rgblight_sync.status.change_flags != 0), &rgblight_sync, sizeof(rgblight_sync))) {
425 rgblight_clear_change_flags();
426 } else {
427 return false;
428 }
429 return true;
430}
431
432static void rgblight_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
433 // Update the RGB with the new data
434 if (split_shmem->rgblight_sync.status.change_flags != 0) {
435 rgblight_update_sync(&split_shmem->rgblight_sync, false);
436 split_shmem->rgblight_sync.status.change_flags = 0;
437 }
438}
439
440# define TRANSACTIONS_RGBLIGHT_MASTER() TRANSACTION_HANDLER_MASTER(rgblight_handlers)
441# define TRANSACTIONS_RGBLIGHT_SLAVE() TRANSACTION_HANDLER_SLAVE(rgblight_handlers)
442# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS [PUT_RGBLIGHT] = trans_initiator2target_initializer(rgblight_sync),
443
444#else // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
445
446# define TRANSACTIONS_RGBLIGHT_MASTER()
447# define TRANSACTIONS_RGBLIGHT_SLAVE()
448# define TRANSACTIONS_RGBLIGHT_REGISTRATIONS
449
450#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
451
452////////////////////////////////////////////////////
453// LED Matrix
454
455#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
456
457static bool led_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
458 static uint32_t last_update = 0;
459 led_matrix_sync_t led_matrix_sync;
460 memcpy(&led_matrix_sync.led_matrix, &led_matrix_eeconfig, sizeof(led_eeconfig_t));
461 led_matrix_sync.led_suspend_state = led_matrix_get_suspend_state();
462 return send_if_data_mismatch(PUT_LED_MATRIX, &last_update, &led_matrix_sync, &split_shmem->led_matrix_sync, sizeof(led_matrix_sync));
463}
464
465static void led_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
466 memcpy(&led_matrix_eeconfig, &split_shmem->led_matrix_sync.led_matrix, sizeof(led_eeconfig_t));
467 led_matrix_set_suspend_state(split_shmem->led_matrix_sync.led_suspend_state);
468}
469
470# define TRANSACTIONS_LED_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(led_matrix_handlers)
471# define TRANSACTIONS_LED_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(led_matrix_handlers)
472# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS [PUT_LED_MATRIX] = trans_initiator2target_initializer(led_matrix_sync),
473
474#else // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
475
476# define TRANSACTIONS_LED_MATRIX_MASTER()
477# define TRANSACTIONS_LED_MATRIX_SLAVE()
478# define TRANSACTIONS_LED_MATRIX_REGISTRATIONS
479
480#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
481
482////////////////////////////////////////////////////
483// RGB Matrix
484
485#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
486
487static bool rgb_matrix_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
488 static uint32_t last_update = 0;
489 rgb_matrix_sync_t rgb_matrix_sync;
490 memcpy(&rgb_matrix_sync.rgb_matrix, &rgb_matrix_config, sizeof(rgb_config_t));
491 rgb_matrix_sync.rgb_suspend_state = rgb_matrix_get_suspend_state();
492 return send_if_data_mismatch(PUT_RGB_MATRIX, &last_update, &rgb_matrix_sync, &split_shmem->rgb_matrix_sync, sizeof(rgb_matrix_sync));
493}
494
495static void rgb_matrix_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
496 memcpy(&rgb_matrix_config, &split_shmem->rgb_matrix_sync.rgb_matrix, sizeof(rgb_config_t));
497 rgb_matrix_set_suspend_state(split_shmem->rgb_matrix_sync.rgb_suspend_state);
498}
499
500# define TRANSACTIONS_RGB_MATRIX_MASTER() TRANSACTION_HANDLER_MASTER(rgb_matrix_handlers)
501# define TRANSACTIONS_RGB_MATRIX_SLAVE() TRANSACTION_HANDLER_SLAVE(rgb_matrix_handlers)
502# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS [PUT_RGB_MATRIX] = trans_initiator2target_initializer(rgb_matrix_sync),
503
504#else // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
505
506# define TRANSACTIONS_RGB_MATRIX_MASTER()
507# define TRANSACTIONS_RGB_MATRIX_SLAVE()
508# define TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
509
510#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
511
512////////////////////////////////////////////////////
513// WPM
514
515#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
516
517static bool wpm_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
518 static uint32_t last_update = 0;
519 uint8_t current_wpm = get_current_wpm();
520 return send_if_condition(PUT_WPM, &last_update, (current_wpm != split_shmem->current_wpm), &current_wpm, sizeof(current_wpm));
521}
522
523static void wpm_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { set_current_wpm(split_shmem->current_wpm); }
524
525# define TRANSACTIONS_WPM_MASTER() TRANSACTION_HANDLER_MASTER(wpm_handlers)
526# define TRANSACTIONS_WPM_SLAVE() TRANSACTION_HANDLER_SLAVE(wpm_handlers)
527# define TRANSACTIONS_WPM_REGISTRATIONS [PUT_WPM] = trans_initiator2target_initializer(current_wpm),
528
529#else // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
530
531# define TRANSACTIONS_WPM_MASTER()
532# define TRANSACTIONS_WPM_SLAVE()
533# define TRANSACTIONS_WPM_REGISTRATIONS
534
535#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
536
537////////////////////////////////////////////////////
538
539uint8_t dummy;
540split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = {
541 // Set defaults
542 [0 ...(NUM_TOTAL_TRANSACTIONS - 1)] = {NULL, 0, 0, 0, 0, 0},
543
544#ifdef USE_I2C
545 [I2C_EXECUTE_CALLBACK] = trans_initiator2target_initializer(transaction_id),
546#endif // USE_I2C
547
548 // clang-format off
549 TRANSACTIONS_SLAVE_MATRIX_REGISTRATIONS
550 TRANSACTIONS_MASTER_MATRIX_REGISTRATIONS
551 TRANSACTIONS_ENCODERS_REGISTRATIONS
552 TRANSACTIONS_SYNC_TIMER_REGISTRATIONS
553 TRANSACTIONS_LAYER_STATE_REGISTRATIONS
554 TRANSACTIONS_LED_STATE_REGISTRATIONS
555 TRANSACTIONS_MODS_REGISTRATIONS
556 TRANSACTIONS_BACKLIGHT_REGISTRATIONS
557 TRANSACTIONS_RGBLIGHT_REGISTRATIONS
558 TRANSACTIONS_LED_MATRIX_REGISTRATIONS
559 TRANSACTIONS_RGB_MATRIX_REGISTRATIONS
560 TRANSACTIONS_WPM_REGISTRATIONS
561// clang-format on
562
563#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
564 [PUT_RPC_INFO] = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback),
565 [PUT_RPC_REQ_DATA] = trans_initiator2target_initializer(rpc_m2s_buffer),
566 [EXECUTE_RPC] = trans_initiator2target_initializer_cb(rpc_info.transaction_id, slave_rpc_exec_callback),
567 [GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer),
568#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
569};
570
571bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
572 bool okay = true;
573 TRANSACTIONS_SLAVE_MATRIX_MASTER();
574 TRANSACTIONS_MASTER_MATRIX_MASTER();
575 TRANSACTIONS_ENCODERS_MASTER();
576 TRANSACTIONS_SYNC_TIMER_MASTER();
577 TRANSACTIONS_LAYER_STATE_MASTER();
578 TRANSACTIONS_LED_STATE_MASTER();
579 TRANSACTIONS_MODS_MASTER();
580 TRANSACTIONS_BACKLIGHT_MASTER();
581 TRANSACTIONS_RGBLIGHT_MASTER();
582 TRANSACTIONS_LED_MATRIX_MASTER();
583 TRANSACTIONS_RGB_MATRIX_MASTER();
584 TRANSACTIONS_WPM_MASTER();
585 return okay;
586}
587
588void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
589 TRANSACTIONS_SLAVE_MATRIX_SLAVE();
590 TRANSACTIONS_MASTER_MATRIX_SLAVE();
591 TRANSACTIONS_ENCODERS_SLAVE();
592 TRANSACTIONS_SYNC_TIMER_SLAVE();
593 TRANSACTIONS_LAYER_STATE_SLAVE();
594 TRANSACTIONS_LED_STATE_SLAVE();
595 TRANSACTIONS_MODS_SLAVE();
596 TRANSACTIONS_BACKLIGHT_SLAVE();
597 TRANSACTIONS_RGBLIGHT_SLAVE();
598 TRANSACTIONS_LED_MATRIX_SLAVE();
599 TRANSACTIONS_RGB_MATRIX_SLAVE();
600 TRANSACTIONS_WPM_SLAVE();
601}
602
603#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
604
605void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback) {
606 // Prevent invoking RPC on QMK core sync data
607 if (transaction_id <= GET_RPC_RESP_DATA) return;
608
609 // Set the callback
610 split_transaction_table[transaction_id].slave_callback = callback;
611 split_transaction_table[transaction_id].initiator2target_offset = offsetof(split_shared_memory_t, rpc_m2s_buffer);
612 split_transaction_table[transaction_id].target2initiator_offset = offsetof(split_shared_memory_t, rpc_s2m_buffer);
613}
614
615bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
616 // Prevent invoking RPC on QMK core sync data
617 if (transaction_id <= GET_RPC_RESP_DATA) return false;
618 // Prevent sizing issues
619 if (initiator2target_buffer_size > RPC_M2S_BUFFER_SIZE) return false;
620 if (target2initiator_buffer_size > RPC_S2M_BUFFER_SIZE) return false;
621
622 // Prepare the metadata block
623 rpc_sync_info_t info = {.transaction_id = transaction_id, .m2s_length = initiator2target_buffer_size, .s2m_length = target2initiator_buffer_size};
624
625 // Make sure the local side knows that we're not sending the full block of data
626 split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = initiator2target_buffer_size;
627 split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = target2initiator_buffer_size;
628
629 // Run through the sequence:
630 // * set the transaction ID and lengths
631 // * send the request data
632 // * execute RPC callback
633 // * retrieve the response data
634 if (!transport_write(PUT_RPC_INFO, &info, sizeof(info))) {
635 return false;
636 }
637 if (!transport_write(PUT_RPC_REQ_DATA, initiator2target_buffer, initiator2target_buffer_size)) {
638 return false;
639 }
640 if (!transport_write(EXECUTE_RPC, &transaction_id, sizeof(transaction_id))) {
641 return false;
642 }
643 if (!transport_read(GET_RPC_RESP_DATA, target2initiator_buffer, target2initiator_buffer_size)) {
644 return false;
645 }
646 return true;
647}
648
649void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
650 // The RPC info block contains the intended transaction ID, as well as the sizes for both inbound and outbound data.
651 // Ignore the args -- the `split_shmem` already has the info, we just need to act upon it.
652 // We must keep the `split_transaction_table` non-const, so that it is able to be modified at runtime.
653
654 split_transaction_table[PUT_RPC_REQ_DATA].initiator2target_buffer_size = split_shmem->rpc_info.m2s_length;
655 split_transaction_table[GET_RPC_RESP_DATA].target2initiator_buffer_size = split_shmem->rpc_info.s2m_length;
656}
657
658void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer) {
659 // We can assume that the buffer lengths are correctly set, now, given that sequentially the rpc_info callback was already executed.
660 // Go through the rpc_info and execute _that_ transaction's callback, with the scratch buffers as inputs.
661 int8_t transaction_id = split_shmem->rpc_info.transaction_id;
662 if (transaction_id < NUM_TOTAL_TRANSACTIONS) {
663 split_transaction_desc_t *trans = &split_transaction_table[transaction_id];
664 if (trans->slave_callback) {
665 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);
666 }
667 }
668}
669
670#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 000000000..4306ba1d8
--- /dev/null
+++ b/quantum/split_common/transactions.h
@@ -0,0 +1,54 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#pragma once
18
19#include "stdint.h"
20#include "stdbool.h"
21
22#include "matrix.h"
23#include "transaction_id_define.h"
24#include "transport.h"
25
26typedef void (*slave_callback_t)(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
27
28// Split transaction Descriptor
29typedef struct _split_transaction_desc_t {
30 uint8_t * status;
31 uint8_t initiator2target_buffer_size;
32 uint16_t initiator2target_offset;
33 uint8_t target2initiator_buffer_size;
34 uint16_t target2initiator_offset;
35 slave_callback_t slave_callback;
36} split_transaction_desc_t;
37
38// Forward declaration for the split transactions
39extern split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS];
40
41#define split_shmem_offset_ptr(offset) ((void *)(((uint8_t *)split_shmem) + (offset)))
42#define split_trans_initiator2target_buffer(trans) (split_shmem_offset_ptr((trans)->initiator2target_offset))
43#define split_trans_target2initiator_buffer(trans) (split_shmem_offset_ptr((trans)->target2initiator_offset))
44
45// returns false if valid data not received from slave
46bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
47void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
48
49void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback);
50
51bool transaction_rpc_exec(int8_t transaction_id, uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer);
52
53#define transaction_rpc_send(transaction_id, initiator2target_buffer_size, initiator2target_buffer) transaction_rpc_exec(transaction_id, initiator2target_buffer_size, initiator2target_buffer, 0, NULL)
54#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 9ed0f7591..a711ef85f 100644
--- a/quantum/split_common/transport.c
+++ b/quantum/split_common/transport.c
@@ -1,452 +1,118 @@
1#include <string.h> 1/* Copyright 2021 QMK
2#include <stddef.h> 2 *
3 3 * This program is free software: you can redistribute it and/or modify
4#include "config.h" 4 * it under the terms of the GNU General Public License as published by
5#include "matrix.h" 5 * the Free Software Foundation, either version 2 of the License, or
6#include "quantum.h" 6 * (at your option) any later version.
7 7 *
8#define ROWS_PER_HAND (MATRIX_ROWS / 2) 8 * This program is distributed in the hope that it will be useful,
9#define SYNC_TIMER_OFFSET 2 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11#ifdef RGBLIGHT_ENABLE 11 * GNU General Public License for more details.
12# include "rgblight.h" 12 *
13#endif 13 * You should have received a copy of the GNU General Public License
14 14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15#ifdef BACKLIGHT_ENABLE 15 */
16# include "backlight.h"
17#endif
18
19#ifdef ENCODER_ENABLE
20# include "encoder.h"
21static pin_t encoders_pad[] = ENCODERS_PAD_A;
22# define NUMBER_OF_ENCODERS (sizeof(encoders_pad) / sizeof(pin_t))
23#endif
24
25#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
26# include "led_matrix.h"
27#endif
28#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
29# include "rgb_matrix.h"
30#endif
31
32#if defined(USE_I2C)
33 16
34# include "i2c_master.h" 17#include <string.h>
35# include "i2c_slave.h" 18#include <debug.h>
36
37typedef struct _I2C_slave_buffer_t {
38# ifndef DISABLE_SYNC_TIMER
39 uint32_t sync_timer;
40# endif
41# ifdef SPLIT_TRANSPORT_MIRROR
42 matrix_row_t mmatrix[ROWS_PER_HAND];
43# endif
44 matrix_row_t smatrix[ROWS_PER_HAND];
45# ifdef SPLIT_MODS_ENABLE
46 uint8_t real_mods;
47 uint8_t weak_mods;
48# ifndef NO_ACTION_ONESHOT
49 uint8_t oneshot_mods;
50# endif
51# endif
52# ifdef BACKLIGHT_ENABLE
53 uint8_t backlight_level;
54# endif
55# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
56 rgblight_syncinfo_t rgblight_sync;
57# endif
58# ifdef ENCODER_ENABLE
59 uint8_t encoder_state[NUMBER_OF_ENCODERS];
60# endif
61# ifdef WPM_ENABLE
62 uint8_t current_wpm;
63# endif
64# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
65 led_eeconfig_t led_matrix;
66 bool led_suspend_state;
67# endif
68# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
69 rgb_config_t rgb_matrix;
70 bool rgb_suspend_state;
71# endif
72} I2C_slave_buffer_t;
73 19
74static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg; 20#include "transactions.h"
21#include "transport.h"
22#include "transaction_id_define.h"
23#include "atomic_util.h"
75 24
76# define I2C_SYNC_TIME_START offsetof(I2C_slave_buffer_t, sync_timer) 25#ifdef USE_I2C
77# define I2C_KEYMAP_MASTER_START offsetof(I2C_slave_buffer_t, mmatrix)
78# define I2C_KEYMAP_SLAVE_START offsetof(I2C_slave_buffer_t, smatrix)
79# define I2C_REAL_MODS_START offsetof(I2C_slave_buffer_t, real_mods)
80# define I2C_WEAK_MODS_START offsetof(I2C_slave_buffer_t, weak_mods)
81# define I2C_ONESHOT_MODS_START offsetof(I2C_slave_buffer_t, oneshot_mods)
82# define I2C_BACKLIGHT_START offsetof(I2C_slave_buffer_t, backlight_level)
83# define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync)
84# define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state)
85# define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm)
86# define I2C_LED_MATRIX_START offsetof(I2C_slave_buffer_t, led_matrix)
87# define I2C_LED_SUSPEND_START offsetof(I2C_slave_buffer_t, led_suspend_state)
88# define I2C_RGB_MATRIX_START offsetof(I2C_slave_buffer_t, rgb_matrix)
89# define I2C_RGB_SUSPEND_START offsetof(I2C_slave_buffer_t, rgb_suspend_state)
90 26
91# define TIMEOUT 100 27# ifndef SLAVE_I2C_TIMEOUT
28# define SLAVE_I2C_TIMEOUT 100
29# endif // SLAVE_I2C_TIMEOUT
92 30
93# ifndef SLAVE_I2C_ADDRESS 31# ifndef SLAVE_I2C_ADDRESS
94# define SLAVE_I2C_ADDRESS 0x32 32# define SLAVE_I2C_ADDRESS 0x32
95# endif 33# endif
96 34
97// Get rows from other half over i2c 35# include "i2c_master.h"
98bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { 36# include "i2c_slave.h"
99 i2c_readReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_SLAVE_START, (void *)slave_matrix, sizeof(i2c_buffer->smatrix), TIMEOUT);
100# ifdef SPLIT_TRANSPORT_MIRROR
101 i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_KEYMAP_MASTER_START, (void *)master_matrix, sizeof(i2c_buffer->mmatrix), TIMEOUT);
102# endif
103 37
104 // write backlight info 38// Ensure the I2C buffer has enough space
105# ifdef BACKLIGHT_ENABLE 39_Static_assert(sizeof(split_shared_memory_t) <= I2C_SLAVE_REG_COUNT, "split_shared_memory_t too large for I2C_SLAVE_REG_COUNT");
106 uint8_t level = is_backlight_enabled() ? get_backlight_level() : 0;
107 if (level != i2c_buffer->backlight_level) {
108 if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_BACKLIGHT_START, (void *)&level, sizeof(level), TIMEOUT) >= 0) {
109 i2c_buffer->backlight_level = level;
110 }
111 }
112# endif
113 40
114# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) 41split_shared_memory_t *const split_shmem = (split_shared_memory_t *)i2c_slave_reg;
115 if (rgblight_get_change_flags()) {
116 rgblight_syncinfo_t rgblight_sync;
117 rgblight_get_syncinfo(&rgblight_sync);
118 if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_START, (void *)&rgblight_sync, sizeof(rgblight_sync), TIMEOUT) >= 0) {
119 rgblight_clear_change_flags();
120 }
121 }
122# endif
123 42
124# ifdef ENCODER_ENABLE 43void transport_master_init(void) { i2c_init(); }
125 i2c_readReg(SLAVE_I2C_ADDRESS, I2C_ENCODER_START, (void *)i2c_buffer->encoder_state, sizeof(i2c_buffer->encoder_state), TIMEOUT); 44void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); }
126 encoder_update_raw(i2c_buffer->encoder_state);
127# endif
128 45
129# ifdef WPM_ENABLE 46i2c_status_t transport_trigger_callback(int8_t id) {
130 uint8_t current_wpm = get_current_wpm(); 47 // If there's no callback, indicate that we were successful
131 if (current_wpm != i2c_buffer->current_wpm) { 48 if (!split_transaction_table[id].slave_callback) {
132 if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WPM_START, (void *)&current_wpm, sizeof(current_wpm), TIMEOUT) >= 0) { 49 return I2C_STATUS_SUCCESS;
133 i2c_buffer->current_wpm = current_wpm;
134 }
135 } 50 }
136# endif
137 51
138# ifdef SPLIT_MODS_ENABLE 52 // Kick off the "callback executor", now that data has been written to the slave
139 uint8_t real_mods = get_mods(); 53 split_shmem->transaction_id = id;
140 if (real_mods != i2c_buffer->real_mods) { 54 split_transaction_desc_t *trans = &split_transaction_table[I2C_EXECUTE_CALLBACK];
141 if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_REAL_MODS_START, (void *)&real_mods, sizeof(real_mods), TIMEOUT) >= 0) { 55 return i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), trans->initiator2target_buffer_size, SLAVE_I2C_TIMEOUT);
142 i2c_buffer->real_mods = real_mods; 56}
57
58bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) {
59 i2c_status_t status;
60 split_transaction_desc_t *trans = &split_transaction_table[id];
61 if (initiator2target_length > 0) {
62 size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length;
63 memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len);
64 if ((status = i2c_writeReg(SLAVE_I2C_ADDRESS, trans->initiator2target_offset, split_trans_initiator2target_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) {
65 return false;
143 } 66 }
144 } 67 }
145 68
146 uint8_t weak_mods = get_weak_mods(); 69 // If we need to execute a callback on the slave, do so
147 if (weak_mods != i2c_buffer->weak_mods) { 70 if ((status = transport_trigger_callback(id)) < 0) {
148 if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WEAK_MODS_START, (void *)&weak_mods, sizeof(weak_mods), TIMEOUT) >= 0) { 71 return false;
149 i2c_buffer->weak_mods = weak_mods;
150 }
151 } 72 }
152 73
153# ifndef NO_ACTION_ONESHOT 74 if (target2initiator_length > 0) {
154 uint8_t oneshot_mods = get_oneshot_mods(); 75 size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length;
155 if (oneshot_mods != i2c_buffer->oneshot_mods) { 76 if ((status = i2c_readReg(SLAVE_I2C_ADDRESS, trans->target2initiator_offset, split_trans_target2initiator_buffer(trans), len, SLAVE_I2C_TIMEOUT)) < 0) {
156 if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_ONESHOT_MODS_START, (void *)&oneshot_mods, sizeof(oneshot_mods), TIMEOUT) >= 0) { 77 return false;
157 i2c_buffer->oneshot_mods = oneshot_mods;
158 } 78 }
79 memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len);
159 } 80 }
160# endif
161# endif
162 81
163# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
164 i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_MATRIX_START, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix), TIMEOUT);
165 bool suspend_state = led_matrix_get_suspend_state();
166 i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_LED_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->led_suspend_state), TIMEOUT);
167# endif
168# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
169 i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_MATRIX_START, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix), TIMEOUT);
170 bool suspend_state = rgb_matrix_get_suspend_state();
171 i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_RGB_SUSPEND_START, (void *)suspend_state, sizeof(i2c_buffer->rgb_suspend_state), TIMEOUT);
172# endif
173
174# ifndef DISABLE_SYNC_TIMER
175 i2c_buffer->sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
176 i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_SYNC_TIME_START, (void *)&i2c_buffer->sync_timer, sizeof(i2c_buffer->sync_timer), TIMEOUT);
177# endif
178 return true; 82 return true;
179} 83}
180 84
181void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { 85#else // USE_I2C
182# ifndef DISABLE_SYNC_TIMER
183 sync_timer_update(i2c_buffer->sync_timer);
184# endif
185 // Copy matrix to I2C buffer
186 memcpy((void *)i2c_buffer->smatrix, (void *)slave_matrix, sizeof(i2c_buffer->smatrix));
187# ifdef SPLIT_TRANSPORT_MIRROR
188 memcpy((void *)master_matrix, (void *)i2c_buffer->mmatrix, sizeof(i2c_buffer->mmatrix));
189# endif
190
191// Read Backlight Info
192# ifdef BACKLIGHT_ENABLE
193 backlight_set(i2c_buffer->backlight_level);
194# endif
195
196# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
197 // Update the RGB with the new data
198 if (i2c_buffer->rgblight_sync.status.change_flags != 0) {
199 rgblight_update_sync(&i2c_buffer->rgblight_sync, false);
200 i2c_buffer->rgblight_sync.status.change_flags = 0;
201 }
202# endif
203
204# ifdef ENCODER_ENABLE
205 encoder_state_raw(i2c_buffer->encoder_state);
206# endif
207
208# ifdef WPM_ENABLE
209 set_current_wpm(i2c_buffer->current_wpm);
210# endif
211
212# ifdef SPLIT_MODS_ENABLE
213 set_mods(i2c_buffer->real_mods);
214 set_weak_mods(i2c_buffer->weak_mods);
215# ifndef NO_ACTION_ONESHOT
216 set_oneshot_mods(i2c_buffer->oneshot_mods);
217# endif
218# endif
219
220# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
221 memcpy((void *)i2c_buffer->led_matrix, (void *)led_matrix_eeconfig, sizeof(i2c_buffer->led_matrix));
222 led_matrix_set_suspend_state(i2c_buffer->led_suspend_state);
223# endif
224# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
225 memcpy((void *)i2c_buffer->rgb_matrix, (void *)rgb_matrix_config, sizeof(i2c_buffer->rgb_matrix));
226 rgb_matrix_set_suspend_state(i2c_buffer->rgb_suspend_state);
227# endif
228}
229
230void transport_master_init(void) { i2c_init(); }
231
232void transport_slave_init(void) { i2c_slave_init(SLAVE_I2C_ADDRESS); }
233
234#else // USE_SERIAL
235 86
236# include "serial.h" 87# include "serial.h"
237 88
238typedef struct _Serial_s2m_buffer_t { 89static split_shared_memory_t shared_memory;
239 // TODO: if MATRIX_COLS > 8 change to uint8_t packed_matrix[] for pack/unpack 90split_shared_memory_t *const split_shmem = &shared_memory;
240 matrix_row_t smatrix[ROWS_PER_HAND];
241
242# ifdef ENCODER_ENABLE
243 uint8_t encoder_state[NUMBER_OF_ENCODERS];
244# endif
245
246} Serial_s2m_buffer_t;
247
248typedef struct _Serial_m2s_buffer_t {
249# ifdef SPLIT_MODS_ENABLE
250 uint8_t real_mods;
251 uint8_t weak_mods;
252# ifndef NO_ACTION_ONESHOT
253 uint8_t oneshot_mods;
254# endif
255# endif
256# ifndef DISABLE_SYNC_TIMER
257 uint32_t sync_timer;
258# endif
259# ifdef SPLIT_TRANSPORT_MIRROR
260 matrix_row_t mmatrix[ROWS_PER_HAND];
261# endif
262# ifdef BACKLIGHT_ENABLE
263 uint8_t backlight_level;
264# endif
265# ifdef WPM_ENABLE
266 uint8_t current_wpm;
267# endif
268# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
269 led_eeconfig_t led_matrix;
270 bool led_suspend_state;
271# endif
272# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
273 rgb_config_t rgb_matrix;
274 bool rgb_suspend_state;
275# endif
276} Serial_m2s_buffer_t;
277
278# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
279// When MCUs on both sides drive their respective RGB LED chains,
280// it is necessary to synchronize, so it is necessary to communicate RGB
281// information. In that case, define RGBLIGHT_SPLIT with info on the number
282// of LEDs on each half.
283//
284// Otherwise, if the master side MCU drives both sides RGB LED chains,
285// there is no need to communicate.
286
287typedef struct _Serial_rgblight_t {
288 rgblight_syncinfo_t rgblight_sync;
289} Serial_rgblight_t;
290 91
291volatile Serial_rgblight_t serial_rgblight = {}; 92void transport_master_init(void) { soft_serial_initiator_init(); }
292uint8_t volatile status_rgblight = 0; 93void transport_slave_init(void) { soft_serial_target_init(); }
293# endif
294
295volatile Serial_s2m_buffer_t serial_s2m_buffer = {};
296volatile Serial_m2s_buffer_t serial_m2s_buffer = {};
297uint8_t volatile status0 = 0;
298
299enum serial_transaction_id {
300 GET_SLAVE_MATRIX = 0,
301# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
302 PUT_RGBLIGHT,
303# endif
304};
305
306SSTD_t transactions[] = {
307 [GET_SLAVE_MATRIX] =
308 {
309 (uint8_t *)&status0,
310 sizeof(serial_m2s_buffer),
311 (uint8_t *)&serial_m2s_buffer,
312 sizeof(serial_s2m_buffer),
313 (uint8_t *)&serial_s2m_buffer,
314 },
315# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
316 [PUT_RGBLIGHT] =
317 {
318 (uint8_t *)&status_rgblight, sizeof(serial_rgblight), (uint8_t *)&serial_rgblight, 0, NULL // no slave to master transfer
319 },
320# endif
321};
322
323void transport_master_init(void) { soft_serial_initiator_init(transactions, TID_LIMIT(transactions)); }
324
325void transport_slave_init(void) { soft_serial_target_init(transactions, TID_LIMIT(transactions)); }
326 94
327# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT) 95bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length) {
328 96 split_transaction_desc_t *trans = &split_transaction_table[id];
329// rgblight synchronization information communication. 97 if (initiator2target_length > 0) {
330 98 size_t len = trans->initiator2target_buffer_size < initiator2target_length ? trans->initiator2target_buffer_size : initiator2target_length;
331void transport_rgblight_master(void) { 99 memcpy(split_trans_initiator2target_buffer(trans), initiator2target_buf, len);
332 if (rgblight_get_change_flags()) {
333 rgblight_get_syncinfo((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync);
334 if (soft_serial_transaction(PUT_RGBLIGHT) == TRANSACTION_END) {
335 rgblight_clear_change_flags();
336 }
337 }
338}
339
340void transport_rgblight_slave(void) {
341 if (status_rgblight == TRANSACTION_ACCEPTED) {
342 rgblight_update_sync((rgblight_syncinfo_t *)&serial_rgblight.rgblight_sync, false);
343 status_rgblight = TRANSACTION_END;
344 } 100 }
345}
346 101
347# else 102 if (soft_serial_transaction(id) != TRANSACTION_END) {
348# define transport_rgblight_master()
349# define transport_rgblight_slave()
350# endif
351
352bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
353# ifndef SERIAL_USE_MULTI_TRANSACTION
354 if (soft_serial_transaction() != TRANSACTION_END) {
355 return false;
356 }
357# else
358 transport_rgblight_master();
359 if (soft_serial_transaction(GET_SLAVE_MATRIX) != TRANSACTION_END) {
360 return false; 103 return false;
361 } 104 }
362# endif
363 105
364 // TODO: if MATRIX_COLS > 8 change to unpack() 106 if (target2initiator_length > 0) {
365 for (int i = 0; i < ROWS_PER_HAND; ++i) { 107 size_t len = trans->target2initiator_buffer_size < target2initiator_length ? trans->target2initiator_buffer_size : target2initiator_length;
366 slave_matrix[i] = serial_s2m_buffer.smatrix[i]; 108 memcpy(target2initiator_buf, split_trans_target2initiator_buffer(trans), len);
367# ifdef SPLIT_TRANSPORT_MIRROR
368 serial_m2s_buffer.mmatrix[i] = master_matrix[i];
369# endif
370 } 109 }
371 110
372# ifdef BACKLIGHT_ENABLE
373 // Write backlight level for slave to read
374 serial_m2s_buffer.backlight_level = is_backlight_enabled() ? get_backlight_level() : 0;
375# endif
376
377# ifdef ENCODER_ENABLE
378 encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state);
379# endif
380
381# ifdef WPM_ENABLE
382 // Write wpm to slave
383 serial_m2s_buffer.current_wpm = get_current_wpm();
384# endif
385
386# ifdef SPLIT_MODS_ENABLE
387 serial_m2s_buffer.real_mods = get_mods();
388 serial_m2s_buffer.weak_mods = get_weak_mods();
389# ifndef NO_ACTION_ONESHOT
390 serial_m2s_buffer.oneshot_mods = get_oneshot_mods();
391# endif
392# endif
393
394# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
395 serial_m2s_buffer.led_matrix = led_matrix_eeconfig;
396 serial_m2s_buffer.led_suspend_state = led_matrix_get_suspend_state();
397# endif
398# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
399 serial_m2s_buffer.rgb_matrix = rgb_matrix_config;
400 serial_m2s_buffer.rgb_suspend_state = rgb_matrix_get_suspend_state();
401# endif
402
403# ifndef DISABLE_SYNC_TIMER
404 serial_m2s_buffer.sync_timer = sync_timer_read32() + SYNC_TIMER_OFFSET;
405# endif
406 return true; 111 return true;
407} 112}
408 113
409void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { 114#endif // USE_I2C
410 transport_rgblight_slave();
411# ifndef DISABLE_SYNC_TIMER
412 sync_timer_update(serial_m2s_buffer.sync_timer);
413# endif
414
415 // TODO: if MATRIX_COLS > 8 change to pack()
416 for (int i = 0; i < ROWS_PER_HAND; ++i) {
417 serial_s2m_buffer.smatrix[i] = slave_matrix[i];
418# ifdef SPLIT_TRANSPORT_MIRROR
419 master_matrix[i] = serial_m2s_buffer.mmatrix[i];
420# endif
421 }
422# ifdef BACKLIGHT_ENABLE
423 backlight_set(serial_m2s_buffer.backlight_level);
424# endif
425
426# ifdef ENCODER_ENABLE
427 encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state);
428# endif
429 115
430# ifdef WPM_ENABLE 116bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { return transactions_master(master_matrix, slave_matrix); }
431 set_current_wpm(serial_m2s_buffer.current_wpm);
432# endif
433
434# ifdef SPLIT_MODS_ENABLE
435 set_mods(serial_m2s_buffer.real_mods);
436 set_weak_mods(serial_m2s_buffer.weak_mods);
437# ifndef NO_ACTION_ONESHOT
438 set_oneshot_mods(serial_m2s_buffer.oneshot_mods);
439# endif
440# endif
441
442# if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
443 led_matrix_eeconfig = serial_m2s_buffer.led_matrix;
444 led_matrix_set_suspend_state(serial_m2s_buffer.led_suspend_state);
445# endif
446# if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
447 rgb_matrix_config = serial_m2s_buffer.rgb_matrix;
448 rgb_matrix_set_suspend_state(serial_m2s_buffer.rgb_suspend_state);
449# endif
450}
451 117
452#endif 118void 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 a9f66301b..2e07f6b25 100644
--- a/quantum/split_common/transport.h
+++ b/quantum/split_common/transport.h
@@ -1,10 +1,175 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
1#pragma once 17#pragma once
2 18
19#include "stdint.h"
20#include "stdbool.h"
21
22#include "progmem.h"
23#include "action_layer.h"
3#include "matrix.h" 24#include "matrix.h"
4 25
26#ifndef RPC_M2S_BUFFER_SIZE
27# define RPC_M2S_BUFFER_SIZE 32
28#endif // RPC_M2S_BUFFER_SIZE
29
30#ifndef RPC_S2M_BUFFER_SIZE
31# define RPC_S2M_BUFFER_SIZE 32
32#endif // RPC_S2M_BUFFER_SIZE
33
5void transport_master_init(void); 34void transport_master_init(void);
6void transport_slave_init(void); 35void transport_slave_init(void);
7 36
8// returns false if valid data not received from slave 37// returns false if valid data not received from slave
9bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); 38bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
10void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]); 39void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]);
40
41bool transport_execute_transaction(int8_t id, const void *initiator2target_buf, uint16_t initiator2target_length, void *target2initiator_buf, uint16_t target2initiator_length);
42
43#ifdef ENCODER_ENABLE
44# include "encoder.h"
45# define NUMBER_OF_ENCODERS (sizeof((pin_t[])ENCODERS_PAD_A) / sizeof(pin_t))
46#endif // ENCODER_ENABLE
47
48#ifdef BACKLIGHT_ENABLE
49# include "backlight.h"
50#endif // BACKLIGHT_ENABLE
51
52#ifdef RGBLIGHT_ENABLE
53# include "rgblight.h"
54#endif // RGBLIGHT_ENABLE
55
56typedef struct _split_slave_matrix_sync_t {
57 uint8_t checksum;
58 matrix_row_t matrix[(MATRIX_ROWS) / 2];
59} split_slave_matrix_sync_t;
60
61#ifdef SPLIT_TRANSPORT_MIRROR
62typedef struct _split_master_matrix_sync_t {
63 matrix_row_t matrix[(MATRIX_ROWS) / 2];
64} split_master_matrix_sync_t;
65#endif // SPLIT_TRANSPORT_MIRROR
66
67#ifdef ENCODER_ENABLE
68typedef struct _split_slave_encoder_sync_t {
69 uint8_t checksum;
70 uint8_t state[NUMBER_OF_ENCODERS];
71} split_slave_encoder_sync_t;
72#endif // ENCODER_ENABLE
73
74#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
75typedef struct _split_layers_sync_t {
76 layer_state_t layer_state;
77 layer_state_t default_layer_state;
78} split_layers_sync_t;
79#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
80
81#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
82# include "led_matrix.h"
83
84typedef struct _led_matrix_sync_t {
85 led_eeconfig_t led_matrix;
86 bool led_suspend_state;
87} led_matrix_sync_t;
88#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
89
90#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
91# include "rgb_matrix.h"
92
93typedef struct _rgb_matrix_sync_t {
94 rgb_config_t rgb_matrix;
95 bool rgb_suspend_state;
96} rgb_matrix_sync_t;
97#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
98
99#ifdef SPLIT_MODS_ENABLE
100typedef struct _split_mods_sync_t {
101 uint8_t real_mods;
102 uint8_t weak_mods;
103# ifndef NO_ACTION_ONESHOT
104 uint8_t oneshot_mods;
105# endif // NO_ACTION_ONESHOT
106} split_mods_sync_t;
107#endif // SPLIT_MODS_ENABLE
108
109#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
110typedef struct _rpc_sync_info_t {
111 int8_t transaction_id;
112 uint8_t m2s_length;
113 uint8_t s2m_length;
114} rpc_sync_info_t;
115#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
116
117typedef struct _split_shared_memory_t {
118#ifdef USE_I2C
119 int8_t transaction_id;
120#endif // USE_I2C
121
122 split_slave_matrix_sync_t smatrix;
123
124#ifdef SPLIT_TRANSPORT_MIRROR
125 split_master_matrix_sync_t mmatrix;
126#endif // SPLIT_TRANSPORT_MIRROR
127
128#ifdef ENCODER_ENABLE
129 split_slave_encoder_sync_t encoders;
130#endif // ENCODER_ENABLE
131
132#ifndef DISABLE_SYNC_TIMER
133 uint32_t sync_timer;
134#endif // DISABLE_SYNC_TIMER
135
136#if !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
137 split_layers_sync_t layers;
138#endif // !defined(NO_ACTION_LAYER) && defined(SPLIT_LAYER_STATE_ENABLE)
139
140#ifdef SPLIT_LED_STATE_ENABLE
141 uint8_t led_state;
142#endif // SPLIT_LED_STATE_ENABLE
143
144#ifdef SPLIT_MODS_ENABLE
145 split_mods_sync_t mods;
146#endif // SPLIT_MODS_ENABLE
147
148#ifdef BACKLIGHT_ENABLE
149 uint8_t backlight_level;
150#endif // BACKLIGHT_ENABLE
151
152#if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
153 rgblight_syncinfo_t rgblight_sync;
154#endif // defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
155
156#if defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
157 led_matrix_sync_t led_matrix_sync;
158#endif // defined(LED_MATRIX_ENABLE) && defined(LED_MATRIX_SPLIT)
159
160#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
161 rgb_matrix_sync_t rgb_matrix_sync;
162#endif // defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_SPLIT)
163
164#if defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
165 uint8_t current_wpm;
166#endif // defined(WPM_ENABLE) && defined(SPLIT_WPM_ENABLE)
167
168#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
169 rpc_sync_info_t rpc_info;
170 uint8_t rpc_m2s_buffer[RPC_M2S_BUFFER_SIZE];
171 uint8_t rpc_s2m_buffer[RPC_S2M_BUFFER_SIZE];
172#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
173} split_shared_memory_t;
174
175extern split_shared_memory_t *const split_shmem; \ No newline at end of file