Avoid 8-bit timer overflows in debounce algorithms (#12240)

* Add fast_timer_t that is 16-bit or 32-bit based on architecture

A 16-bit timer will overflow sooner but be faster to compare on AVR.

* Avoid 8-bit timer overflows in debounce algorithms

Count down remaining elapsed time instead of trying to do 8-bit timer
comparisons.

Add a "none" implementation that is automatically used if DEBOUNCE is
0 otherwise it will break the _pk/_pr count down.

* Avoid unnecessary polling of the entire matrix in sym_eager_pk

The matrix only needs to be updated when a debounce timer expires.

* Avoid unnecessary polling of the entire matrix in sym_eager_pr

The matrix only needs to be updated when a debounce timer expires.

The use of the "needed_update" variable is trying to do what
"matrix_need_update" was added to fix but didn't work because it only
applied when all keys finished debouncing.

* Fix sym_defer_g timing inconsistency compared to other debounce algorithms

DEBOUNCE=5 should process the key after 5ms, not 6ms

* Add debounce tests
master
Simon Arlott 2021-06-09 08:23:21 +01:00 committed by GitHub
parent f287597c19
commit b829a1d264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1588 additions and 92 deletions

View File

@ -49,6 +49,7 @@ endif
include common_features.mk include common_features.mk
include $(TMK_PATH)/common.mk include $(TMK_PATH)/common.mk
include $(QUANTUM_PATH)/debounce/tests/rules.mk
include $(QUANTUM_PATH)/sequencer/tests/rules.mk include $(QUANTUM_PATH)/sequencer/tests/rules.mk
include $(QUANTUM_PATH)/serial_link/tests/rules.mk include $(QUANTUM_PATH)/serial_link/tests/rules.mk
ifneq ($(filter $(FULL_TESTS),$(TEST)),) ifneq ($(filter $(FULL_TESTS),$(TEST)),)

View File

@ -9,3 +9,5 @@ void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool
bool debounce_active(void); bool debounce_active(void);
void debounce_init(uint8_t num_rows); void debounce_init(uint8_t num_rows);
void debounce_free(void);

View File

@ -0,0 +1,31 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "matrix.h"
#include "quantum.h"
#include <stdlib.h>
void debounce_init(uint8_t num_rows) {}
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
for (int i = 0; i < num_rows; i++) {
cooked[i] = raw[i];
}
}
bool debounce_active(void) { return false; }
void debounce_free(void) {}

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Alex Ong<the.onga@gmail.com> Copyright 2017 Alex Ong<the.onga@gmail.com>
Copyright 2021 Simon Arlott
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or the Free Software Foundation, either version 2 of the License, or
@ -23,30 +24,29 @@ When no state changes have occured for DEBOUNCE milliseconds, we push the state.
# define DEBOUNCE 5 # define DEBOUNCE 5
#endif #endif
void debounce_init(uint8_t num_rows) {}
static bool debouncing = false;
#if DEBOUNCE > 0 #if DEBOUNCE > 0
static uint16_t debouncing_time; static bool debouncing = false;
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { static fast_timer_t debouncing_time;
void debounce_init(uint8_t num_rows) {}
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
if (changed) { if (changed) {
debouncing = true; debouncing = true;
debouncing_time = timer_read(); debouncing_time = timer_read_fast();
} }
if (debouncing && timer_elapsed(debouncing_time) > DEBOUNCE) { if (debouncing && timer_elapsed_fast(debouncing_time) >= DEBOUNCE) {
for (int i = 0; i < num_rows; i++) { for (int i = 0; i < num_rows; i++) {
cooked[i] = raw[i]; cooked[i] = raw[i];
} }
debouncing = false; debouncing = false;
} }
} }
#else // no debouncing.
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
for (int i = 0; i < num_rows; i++) {
cooked[i] = raw[i];
}
}
#endif
bool debounce_active(void) { return debouncing; } bool debounce_active(void) { return debouncing; }
void debounce_free(void) {}
#else // no debouncing.
# include "none.c"
#endif

View File

@ -1,6 +1,7 @@
/* /*
Copyright 2017 Alex Ong<the.onga@gmail.com> Copyright 2017 Alex Ong<the.onga@gmail.com>
Copyright 2020 Andrei Purdea<andrei@purdea.ro> Copyright 2020 Andrei Purdea<andrei@purdea.ro>
Copyright 2021 Simon Arlott
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or the Free Software Foundation, either version 2 of the License, or
@ -33,28 +34,25 @@ When no state changes have occured for DEBOUNCE milliseconds, we push the state.
# define DEBOUNCE 5 # define DEBOUNCE 5
#endif #endif
// Maximum debounce: 255ms
#if DEBOUNCE > UINT8_MAX
# undef DEBOUNCE
# define DEBOUNCE UINT8_MAX
#endif
#define ROW_SHIFTER ((matrix_row_t)1) #define ROW_SHIFTER ((matrix_row_t)1)
#define debounce_counter_t uint8_t typedef uint8_t debounce_counter_t;
#if DEBOUNCE > 0
static debounce_counter_t *debounce_counters; static debounce_counter_t *debounce_counters;
static fast_timer_t last_time;
static bool counters_need_update; static bool counters_need_update;
#define DEBOUNCE_ELAPSED 251 #define DEBOUNCE_ELAPSED 0
#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1)
static uint8_t wrapping_timer_read(void) { static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time);
static uint16_t time = 0; static void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows);
static uint8_t last_result = 0;
uint16_t new_time = timer_read();
uint16_t diff = new_time - time;
time = new_time;
last_result = (last_result + diff) % (MAX_DEBOUNCE + 1);
return last_result;
}
void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time);
void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time);
// we use num_rows rather than MATRIX_ROWS to support split keyboards // we use num_rows rather than MATRIX_ROWS to support split keyboards
void debounce_init(uint8_t num_rows) { void debounce_init(uint8_t num_rows) {
@ -67,27 +65,49 @@ void debounce_init(uint8_t num_rows) {
} }
} }
void debounce_free(void) {
free(debounce_counters);
debounce_counters = NULL;
}
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
uint8_t current_time = wrapping_timer_read(); bool updated_last = false;
if (counters_need_update) { if (counters_need_update) {
update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, current_time); fast_timer_t now = timer_read_fast();
fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time);
last_time = now;
updated_last = true;
if (elapsed_time > UINT8_MAX) {
elapsed_time = UINT8_MAX;
}
if (elapsed_time > 0) {
update_debounce_counters_and_transfer_if_expired(raw, cooked, num_rows, elapsed_time);
}
} }
if (changed) { if (changed) {
start_debounce_counters(raw, cooked, num_rows, current_time); if (!updated_last) {
last_time = timer_read_fast();
}
start_debounce_counters(raw, cooked, num_rows);
} }
} }
void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { static void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t elapsed_time) {
counters_need_update = false; counters_need_update = false;
debounce_counter_t *debounce_pointer = debounce_counters; debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t row = 0; row < num_rows; row++) {
for (uint8_t col = 0; col < MATRIX_COLS; col++) { for (uint8_t col = 0; col < MATRIX_COLS; col++) {
if (*debounce_pointer != DEBOUNCE_ELAPSED) { if (*debounce_pointer != DEBOUNCE_ELAPSED) {
if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { if (*debounce_pointer <= elapsed_time) {
*debounce_pointer = DEBOUNCE_ELAPSED; *debounce_pointer = DEBOUNCE_ELAPSED;
cooked[row] = (cooked[row] & ~(ROW_SHIFTER << col)) | (raw[row] & (ROW_SHIFTER << col)); cooked[row] = (cooked[row] & ~(ROW_SHIFTER << col)) | (raw[row] & (ROW_SHIFTER << col));
} else { } else {
*debounce_pointer -= elapsed_time;
counters_need_update = true; counters_need_update = true;
} }
} }
@ -96,14 +116,14 @@ void update_debounce_counters_and_transfer_if_expired(matrix_row_t raw[], matrix
} }
} }
void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { static void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) {
debounce_counter_t *debounce_pointer = debounce_counters; debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t row = 0; row < num_rows; row++) {
matrix_row_t delta = raw[row] ^ cooked[row]; matrix_row_t delta = raw[row] ^ cooked[row];
for (uint8_t col = 0; col < MATRIX_COLS; col++) { for (uint8_t col = 0; col < MATRIX_COLS; col++) {
if (delta & (ROW_SHIFTER << col)) { if (delta & (ROW_SHIFTER << col)) {
if (*debounce_pointer == DEBOUNCE_ELAPSED) { if (*debounce_pointer == DEBOUNCE_ELAPSED) {
*debounce_pointer = current_time; *debounce_pointer = DEBOUNCE;
counters_need_update = true; counters_need_update = true;
} }
} else { } else {
@ -115,3 +135,6 @@ void start_debounce_counters(matrix_row_t raw[], matrix_row_t cooked[], uint8_t
} }
bool debounce_active(void) { return true; } bool debounce_active(void) { return true; }
#else
# include "none.c"
#endif

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Alex Ong<the.onga@gmail.com> Copyright 2017 Alex Ong<the.onga@gmail.com>
Copyright 2021 Simon Arlott
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or the Free Software Foundation, either version 2 of the License, or
@ -33,29 +34,26 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred.
# define DEBOUNCE 5 # define DEBOUNCE 5
#endif #endif
// Maximum debounce: 255ms
#if DEBOUNCE > UINT8_MAX
# undef DEBOUNCE
# define DEBOUNCE UINT8_MAX
#endif
#define ROW_SHIFTER ((matrix_row_t)1) #define ROW_SHIFTER ((matrix_row_t)1)
#define debounce_counter_t uint8_t typedef uint8_t debounce_counter_t;
#if DEBOUNCE > 0
static debounce_counter_t *debounce_counters; static debounce_counter_t *debounce_counters;
static fast_timer_t last_time;
static bool counters_need_update; static bool counters_need_update;
static bool matrix_need_update; static bool matrix_need_update;
#define DEBOUNCE_ELAPSED 251 #define DEBOUNCE_ELAPSED 0
#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1)
static uint8_t wrapping_timer_read(void) { static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time);
static uint16_t time = 0; static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows);
static uint8_t last_result = 0;
uint16_t new_time = timer_read();
uint16_t diff = new_time - time;
time = new_time;
last_result = (last_result + diff) % (MAX_DEBOUNCE + 1);
return last_result;
}
void update_debounce_counters(uint8_t num_rows, uint8_t current_time);
void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time);
// we use num_rows rather than MATRIX_ROWS to support split keyboards // we use num_rows rather than MATRIX_ROWS to support split keyboards
void debounce_init(uint8_t num_rows) { void debounce_init(uint8_t num_rows) {
@ -68,27 +66,51 @@ void debounce_init(uint8_t num_rows) {
} }
} }
void debounce_free(void) {
free(debounce_counters);
debounce_counters = NULL;
}
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
uint8_t current_time = wrapping_timer_read(); bool updated_last = false;
if (counters_need_update) { if (counters_need_update) {
update_debounce_counters(num_rows, current_time); fast_timer_t now = timer_read_fast();
fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time);
last_time = now;
updated_last = true;
if (elapsed_time > UINT8_MAX) {
elapsed_time = UINT8_MAX;
}
if (elapsed_time > 0) {
update_debounce_counters(num_rows, elapsed_time);
}
} }
if (changed || matrix_need_update) { if (changed || matrix_need_update) {
transfer_matrix_values(raw, cooked, num_rows, current_time); if (!updated_last) {
last_time = timer_read_fast();
}
transfer_matrix_values(raw, cooked, num_rows);
} }
} }
// If the current time is > debounce counter, set the counter to enable input. // If the current time is > debounce counter, set the counter to enable input.
void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time) {
counters_need_update = false; counters_need_update = false;
matrix_need_update = false;
debounce_counter_t *debounce_pointer = debounce_counters; debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t row = 0; row < num_rows; row++) {
for (uint8_t col = 0; col < MATRIX_COLS; col++) { for (uint8_t col = 0; col < MATRIX_COLS; col++) {
if (*debounce_pointer != DEBOUNCE_ELAPSED) { if (*debounce_pointer != DEBOUNCE_ELAPSED) {
if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { if (*debounce_pointer <= elapsed_time) {
*debounce_pointer = DEBOUNCE_ELAPSED; *debounce_pointer = DEBOUNCE_ELAPSED;
matrix_need_update = true;
} else { } else {
*debounce_pointer -= elapsed_time;
counters_need_update = true; counters_need_update = true;
} }
} }
@ -98,8 +120,7 @@ void update_debounce_counters(uint8_t num_rows, uint8_t current_time) {
} }
// upload from raw_matrix to final matrix; // upload from raw_matrix to final matrix;
void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) {
matrix_need_update = false;
debounce_counter_t *debounce_pointer = debounce_counters; debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t row = 0; row < num_rows; row++) {
matrix_row_t delta = raw[row] ^ cooked[row]; matrix_row_t delta = raw[row] ^ cooked[row];
@ -108,11 +129,9 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n
matrix_row_t col_mask = (ROW_SHIFTER << col); matrix_row_t col_mask = (ROW_SHIFTER << col);
if (delta & col_mask) { if (delta & col_mask) {
if (*debounce_pointer == DEBOUNCE_ELAPSED) { if (*debounce_pointer == DEBOUNCE_ELAPSED) {
*debounce_pointer = current_time; *debounce_pointer = DEBOUNCE;
counters_need_update = true; counters_need_update = true;
existing_row ^= col_mask; // flip the bit. existing_row ^= col_mask; // flip the bit.
} else {
matrix_need_update = true;
} }
} }
debounce_pointer++; debounce_pointer++;
@ -122,3 +141,6 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n
} }
bool debounce_active(void) { return true; } bool debounce_active(void) { return true; }
#else
# include "none.c"
#endif

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2019 Alex Ong<the.onga@gmail.com> Copyright 2019 Alex Ong<the.onga@gmail.com>
Copyright 2021 Simon Arlott
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or the Free Software Foundation, either version 2 of the License, or
@ -33,27 +34,25 @@ No further inputs are accepted until DEBOUNCE milliseconds have occurred.
# define DEBOUNCE 5 # define DEBOUNCE 5
#endif #endif
#define debounce_counter_t uint8_t // Maximum debounce: 255ms
#if DEBOUNCE > UINT8_MAX
# undef DEBOUNCE
# define DEBOUNCE UINT8_MAX
#endif
typedef uint8_t debounce_counter_t;
#if DEBOUNCE > 0
static bool matrix_need_update; static bool matrix_need_update;
static debounce_counter_t *debounce_counters; static debounce_counter_t *debounce_counters;
static fast_timer_t last_time;
static bool counters_need_update; static bool counters_need_update;
#define DEBOUNCE_ELAPSED 251 #define DEBOUNCE_ELAPSED 0
#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1)
static uint8_t wrapping_timer_read(void) { static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time);
static uint16_t time = 0; static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows);
static uint8_t last_result = 0;
uint16_t new_time = timer_read();
uint16_t diff = new_time - time;
time = new_time;
last_result = (last_result + diff) % (MAX_DEBOUNCE + 1);
return last_result;
}
void update_debounce_counters(uint8_t num_rows, uint8_t current_time);
void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time);
// we use num_rows rather than MATRIX_ROWS to support split keyboards // we use num_rows rather than MATRIX_ROWS to support split keyboards
void debounce_init(uint8_t num_rows) { void debounce_init(uint8_t num_rows) {
@ -63,27 +62,50 @@ void debounce_init(uint8_t num_rows) {
} }
} }
void debounce_free(void) {
free(debounce_counters);
debounce_counters = NULL;
}
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) { void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed) {
uint8_t current_time = wrapping_timer_read(); bool updated_last = false;
bool needed_update = counters_need_update;
if (counters_need_update) { if (counters_need_update) {
update_debounce_counters(num_rows, current_time); fast_timer_t now = timer_read_fast();
fast_timer_t elapsed_time = TIMER_DIFF_FAST(now, last_time);
last_time = now;
updated_last = true;
if (elapsed_time > UINT8_MAX) {
elapsed_time = UINT8_MAX;
}
if (elapsed_time > 0) {
update_debounce_counters(num_rows, elapsed_time);
}
} }
if (changed || (needed_update && !counters_need_update) || matrix_need_update) { if (changed || matrix_need_update) {
transfer_matrix_values(raw, cooked, num_rows, current_time); if (!updated_last) {
last_time = timer_read_fast();
}
transfer_matrix_values(raw, cooked, num_rows);
} }
} }
// If the current time is > debounce counter, set the counter to enable input. // If the current time is > debounce counter, set the counter to enable input.
void update_debounce_counters(uint8_t num_rows, uint8_t current_time) { static void update_debounce_counters(uint8_t num_rows, uint8_t elapsed_time) {
counters_need_update = false; counters_need_update = false;
matrix_need_update = false;
debounce_counter_t *debounce_pointer = debounce_counters; debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t row = 0; row < num_rows; row++) {
if (*debounce_pointer != DEBOUNCE_ELAPSED) { if (*debounce_pointer != DEBOUNCE_ELAPSED) {
if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) { if (*debounce_pointer <= elapsed_time) {
*debounce_pointer = DEBOUNCE_ELAPSED; *debounce_pointer = DEBOUNCE_ELAPSED;
matrix_need_update = true;
} else { } else {
*debounce_pointer -= elapsed_time;
counters_need_update = true; counters_need_update = true;
} }
} }
@ -92,8 +114,7 @@ void update_debounce_counters(uint8_t num_rows, uint8_t current_time) {
} }
// upload from raw_matrix to final matrix; // upload from raw_matrix to final matrix;
void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, uint8_t current_time) { static void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows) {
matrix_need_update = false;
debounce_counter_t *debounce_pointer = debounce_counters; debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++) { for (uint8_t row = 0; row < num_rows; row++) {
matrix_row_t existing_row = cooked[row]; matrix_row_t existing_row = cooked[row];
@ -102,11 +123,9 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n
// determine new value basd on debounce pointer + raw value // determine new value basd on debounce pointer + raw value
if (existing_row != raw_row) { if (existing_row != raw_row) {
if (*debounce_pointer == DEBOUNCE_ELAPSED) { if (*debounce_pointer == DEBOUNCE_ELAPSED) {
*debounce_pointer = current_time; *debounce_pointer = DEBOUNCE;
cooked[row] = raw_row; cooked[row] = raw_row;
counters_need_update = true; counters_need_update = true;
} else {
matrix_need_update = true;
} }
} }
debounce_pointer++; debounce_pointer++;
@ -114,3 +133,6 @@ void transfer_matrix_values(matrix_row_t raw[], matrix_row_t cooked[], uint8_t n
} }
bool debounce_active(void) { return true; } bool debounce_active(void) { return true; }
#else
# include "none.c"
#endif

View File

@ -0,0 +1,229 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gtest/gtest.h"
#include "debounce_test_common.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
extern "C" {
#include "quantum.h"
#include "timer.h"
#include "debounce.h"
void set_time(uint32_t t);
void advance_time(uint32_t ms);
}
void DebounceTest::addEvents(std::initializer_list<DebounceTestEvent> events) {
events_.insert(events_.end(), events.begin(), events.end());
}
void DebounceTest::runEvents() {
/* Run the test multiple times, from 1kHz to 10kHz scan rate */
for (extra_iterations_ = 0; extra_iterations_ < 10; extra_iterations_++) {
if (time_jumps_) {
/* Don't advance time smoothly, jump to the next event (some tests require this) */
auto_advance_time_ = false;
runEventsInternal();
} else {
/* Run the test with both smooth and irregular time; it must produce the same result */
auto_advance_time_ = true;
runEventsInternal();
auto_advance_time_ = false;
runEventsInternal();
}
}
}
void DebounceTest::runEventsInternal() {
fast_timer_t previous = 0;
bool first = true;
/* Initialise keyboard with start time (offset to avoid testing at 0) and all keys UP */
debounce_init(MATRIX_ROWS);
set_time(time_offset_);
std::fill(std::begin(input_matrix_), std::end(input_matrix_), 0);
std::fill(std::begin(output_matrix_), std::end(output_matrix_), 0);
for (auto &event : events_) {
if (!auto_advance_time_) {
/* Jump to the next event */
set_time(time_offset_ + event.time_);
} else if (!first && event.time_ == previous + 1) {
/* This event immediately follows the previous one, don't make extra debounce() calls */
advance_time(1);
} else {
/* Fast forward to the time for this event, calling debounce() with no changes */
ASSERT_LT((time_offset_ + event.time_) - timer_read_fast(), 60000) << "Test tries to advance more than 1 minute of time";
while (timer_read_fast() != time_offset_ + event.time_) {
runDebounce(false);
checkCookedMatrix(false, "debounce() modified cooked matrix");
advance_time(1);
}
}
first = false;
previous = event.time_;
/* Prepare input matrix */
for (auto &input : event.inputs_) {
matrixUpdate(input_matrix_, "input", input);
}
/* Call debounce */
runDebounce(!event.inputs_.empty());
/* Prepare output matrix */
for (auto &output : event.outputs_) {
matrixUpdate(output_matrix_, "output", output);
}
/* Check output matrix has expected change events */
for (auto &output : event.outputs_) {
EXPECT_EQ(!!(cooked_matrix_[output.row_] & (1U << output.col_)), directionValue(output.direction_))
<< "Missing event at " << strTime()
<< " expected key " << output.row_ << "," << output.col_ << " " << directionLabel(output.direction_)
<< "\ninput_matrix: changed=" << !event.inputs_.empty() << "\n" << strMatrix(input_matrix_)
<< "\nexpected_matrix:\n" << strMatrix(output_matrix_)
<< "\nactual_matrix:\n" << strMatrix(cooked_matrix_);
}
/* Check output matrix has no other changes */
checkCookedMatrix(!event.inputs_.empty(), "debounce() cooked matrix does not match expected output matrix");
/* Perform some extra iterations of the matrix scan with no changes */
for (int i = 0; i < extra_iterations_; i++) {
runDebounce(false);
checkCookedMatrix(false, "debounce() modified cooked matrix");
}
}
/* Check that no further changes happen for 1 minute */
for (int i = 0; i < 60000; i++) {
runDebounce(false);
checkCookedMatrix(false, "debounce() modified cooked matrix");
advance_time(1);
}
debounce_free();
}
void DebounceTest::runDebounce(bool changed) {
std::copy(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_));
std::copy(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_));
debounce(raw_matrix_, cooked_matrix_, MATRIX_ROWS, changed);
if (!std::equal(std::begin(input_matrix_), std::end(input_matrix_), std::begin(raw_matrix_))) {
FAIL() << "Fatal error: debounce() modified raw matrix at " << strTime()
<< "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_)
<< "\nraw_matrix:\n" << strMatrix(raw_matrix_);
}
}
void DebounceTest::checkCookedMatrix(bool changed, const std::string &error_message) {
if (!std::equal(std::begin(output_matrix_), std::end(output_matrix_), std::begin(cooked_matrix_))) {
FAIL() << "Unexpected event: " << error_message << " at " << strTime()
<< "\ninput_matrix: changed=" << changed << "\n" << strMatrix(input_matrix_)
<< "\nexpected_matrix:\n" << strMatrix(output_matrix_)
<< "\nactual_matrix:\n" << strMatrix(cooked_matrix_);
}
}
std::string DebounceTest::strTime() {
std::stringstream text;
text << "time " << (timer_read_fast() - time_offset_)
<< " (extra_iterations=" << extra_iterations_
<< ", auto_advance_time=" << auto_advance_time_ << ")";
return text.str();
}
std::string DebounceTest::strMatrix(matrix_row_t matrix[]) {
std::stringstream text;
text << "\t" << std::setw(3) << "";
for (int col = 0; col < MATRIX_COLS; col++) {
text << " " << std::setw(2) << col;
}
text << "\n";
for (int row = 0; row < MATRIX_ROWS; row++) {
text << "\t" << std::setw(2) << row << ":";
for (int col = 0; col < MATRIX_COLS; col++) {
text << ((matrix[row] & (1U << col)) ? " XX" : " __");
}
text << "\n";
}
return text.str();
}
bool DebounceTest::directionValue(Direction direction) {
switch (direction) {
case DOWN:
return true;
case UP:
return false;
}
}
std::string DebounceTest::directionLabel(Direction direction) {
switch (direction) {
case DOWN:
return "DOWN";
case UP:
return "UP";
}
}
/* Modify a matrix and verify that events always specify a change */
void DebounceTest::matrixUpdate(matrix_row_t matrix[], const std::string &name, const MatrixTestEvent &event) {
ASSERT_NE(!!(matrix[event.row_] & (1U << event.col_)), directionValue(event.direction_))
<< "Test " << name << " at " << strTime()
<< " sets key " << event.row_ << "," << event.col_ << " " << directionLabel(event.direction_)
<< " but it is already " << directionLabel(event.direction_)
<< "\n" << name << "_matrix:\n" << strMatrix(matrix);
switch (event.direction_) {
case DOWN:
matrix[event.row_] |= (1U << event.col_);
break;
case UP:
matrix[event.row_] &= ~(1U << event.col_);
break;
}
}
DebounceTestEvent::DebounceTestEvent(fast_timer_t time,
std::initializer_list<MatrixTestEvent> inputs,
std::initializer_list<MatrixTestEvent> outputs)
: time_(time), inputs_(inputs), outputs_(outputs) {
}
MatrixTestEvent::MatrixTestEvent(int row, int col, Direction direction)
: row_(row), col_(col), direction_(direction) {
}

View File

@ -0,0 +1,83 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gtest/gtest.h"
#include <initializer_list>
#include <list>
#include <string>
extern "C" {
#include "quantum.h"
#include "timer.h"
}
enum Direction {
DOWN,
UP,
};
class MatrixTestEvent {
public:
MatrixTestEvent(int row, int col, Direction direction);
const int row_;
const int col_;
const Direction direction_;
};
class DebounceTestEvent {
public:
// 0, {{0, 1, DOWN}}, {{0, 1, DOWN}})
DebounceTestEvent(fast_timer_t time,
std::initializer_list<MatrixTestEvent> inputs,
std::initializer_list<MatrixTestEvent> outputs);
const fast_timer_t time_;
const std::list<MatrixTestEvent> inputs_;
const std::list<MatrixTestEvent> outputs_;
};
class DebounceTest : public ::testing::Test {
protected:
void addEvents(std::initializer_list<DebounceTestEvent> events);
void runEvents();
fast_timer_t time_offset_ = 7777;
bool time_jumps_ = false;
private:
static bool directionValue(Direction direction);
static std::string directionLabel(Direction direction);
void runEventsInternal();
void runDebounce(bool changed);
void checkCookedMatrix(bool changed, const std::string &error_message);
void matrixUpdate(matrix_row_t matrix[], const std::string &name, const MatrixTestEvent &event);
std::string strTime();
std::string strMatrix(matrix_row_t matrix[]);
std::list<DebounceTestEvent> events_;
matrix_row_t input_matrix_[MATRIX_ROWS];
matrix_row_t raw_matrix_[MATRIX_ROWS];
matrix_row_t cooked_matrix_[MATRIX_ROWS];
matrix_row_t output_matrix_[MATRIX_ROWS];
int extra_iterations_;
bool auto_advance_time_;
};

View File

@ -0,0 +1,39 @@
# Copyright 2021 Simon Arlott
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
DEBOUNCE_COMMON_DEFS := -DMATRIX_ROWS=4 -DMATRIX_COLS=10 -DDEBOUNCE=5
DEBOUNCE_COMMON_SRC := $(QUANTUM_PATH)/debounce/tests/debounce_test_common.cpp \
$(TMK_PATH)/common/test/timer.c
debounce_sym_defer_g_DEFS := $(DEBOUNCE_COMMON_DEFS)
debounce_sym_defer_g_SRC := $(DEBOUNCE_COMMON_SRC) \
$(QUANTUM_PATH)/debounce/sym_defer_g.c \
$(QUANTUM_PATH)/debounce/tests/sym_defer_g_tests.cpp
debounce_sym_defer_pk_DEFS := $(DEBOUNCE_COMMON_DEFS)
debounce_sym_defer_pk_SRC := $(DEBOUNCE_COMMON_SRC) \
$(QUANTUM_PATH)/debounce/sym_defer_pk.c \
$(QUANTUM_PATH)/debounce/tests/sym_defer_pk_tests.cpp
debounce_sym_eager_pk_DEFS := $(DEBOUNCE_COMMON_DEFS)
debounce_sym_eager_pk_SRC := $(DEBOUNCE_COMMON_SRC) \
$(QUANTUM_PATH)/debounce/sym_eager_pk.c \
$(QUANTUM_PATH)/debounce/tests/sym_eager_pk_tests.cpp
debounce_sym_eager_pr_DEFS := $(DEBOUNCE_COMMON_DEFS)
debounce_sym_eager_pr_SRC := $(DEBOUNCE_COMMON_SRC) \
$(QUANTUM_PATH)/debounce/sym_eager_pr.c \
$(QUANTUM_PATH)/debounce/tests/sym_eager_pr_tests.cpp

View File

@ -0,0 +1,223 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gtest/gtest.h"
#include "debounce_test_common.h"
TEST_F(DebounceTest, OneKeyShort1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
/* 0ms delay (fast scan rate) */
{5, {{0, 1, UP}}, {}},
{10, {}, {{0, 1, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
/* 1ms delay */
{6, {{0, 1, UP}}, {}},
{11, {}, {{0, 1, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
/* 2ms delay */
{7, {{0, 1, UP}}, {}},
{12, {}, {{0, 1, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyTooQuick1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Release key exactly on the debounce time */
{5, {{0, 1, UP}}, {}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyTooQuick2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{6, {{0, 1, UP}}, {}},
/* Press key exactly on the debounce time */
{11, {{0, 1, DOWN}}, {}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{1, {{0, 1, UP}}, {}},
{2, {{0, 1, DOWN}}, {}},
{3, {{0, 1, UP}}, {}},
{4, {{0, 1, DOWN}}, {}},
{5, {{0, 1, UP}}, {}},
{6, {{0, 1, DOWN}}, {}},
{11, {}, {{0, 1, DOWN}}}, /* 5ms after DOWN at time 7 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{6, {{0, 1, UP}}, {}},
{7, {{0, 1, DOWN}}, {}},
{8, {{0, 1, UP}}, {}},
{9, {{0, 1, DOWN}}, {}},
{10, {{0, 1, UP}}, {}},
{15, {}, {{0, 1, UP}}}, /* 5ms after UP at time 10 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyLong) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{25, {{0, 1, UP}}, {}},
{30, {}, {{0, 1, UP}}},
{50, {{0, 1, DOWN}}, {}},
{55, {}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysShort) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{1, {{0, 2, DOWN}}, {}},
{6, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
{7, {{0, 1, UP}}, {}},
{8, {{0, 2, UP}}, {}},
{13, {}, {{0, 1, UP}, {0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysSimultaneous1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}, {0, 2, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
{6, {{0, 1, UP}, {0, 2, UP}}, {}},
{11, {}, {{0, 1, UP}, {0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysSimultaneous2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{1, {{0, 2, DOWN}}, {}},
{5, {}, {}},
{6, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
{7, {{0, 1, UP}}, {}},
{8, {{0, 2, UP}}, {}},
{13, {}, {{0, 1, UP}, {0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Processing is very late */
{300, {}, {{0, 1, DOWN}}},
/* Immediately release key */
{300, {{0, 1, UP}}, {}},
{305, {}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Processing is very late */
{300, {}, {{0, 1, DOWN}}},
/* Release key after 1ms */
{301, {{0, 1, UP}}, {}},
{306, {}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Release key before debounce expires */
{300, {{0, 1, UP}}, {}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan4) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Processing is a bit late */
{50, {}, {{0, 1, DOWN}}},
/* Release key after 1ms */
{51, {{0, 1, UP}}, {}},
{56, {}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}

View File

@ -0,0 +1,225 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gtest/gtest.h"
#include "debounce_test_common.h"
TEST_F(DebounceTest, OneKeyShort1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
/* 0ms delay (fast scan rate) */
{5, {{0, 1, UP}}, {}},
{10, {}, {{0, 1, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
/* 1ms delay */
{6, {{0, 1, UP}}, {}},
{11, {}, {{0, 1, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
/* 2ms delay */
{7, {{0, 1, UP}}, {}},
{12, {}, {{0, 1, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyTooQuick1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Release key exactly on the debounce time */
{5, {{0, 1, UP}}, {}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyTooQuick2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{6, {{0, 1, UP}}, {}},
/* Press key exactly on the debounce time */
{11, {{0, 1, DOWN}}, {}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{1, {{0, 1, UP}}, {}},
{2, {{0, 1, DOWN}}, {}},
{3, {{0, 1, UP}}, {}},
{4, {{0, 1, DOWN}}, {}},
{5, {{0, 1, UP}}, {}},
{6, {{0, 1, DOWN}}, {}},
{11, {}, {{0, 1, DOWN}}}, /* 5ms after DOWN at time 7 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{6, {{0, 1, UP}}, {}},
{7, {{0, 1, DOWN}}, {}},
{8, {{0, 1, UP}}, {}},
{9, {{0, 1, DOWN}}, {}},
{10, {{0, 1, UP}}, {}},
{15, {}, {{0, 1, UP}}}, /* 5ms after UP at time 10 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyLong) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{25, {{0, 1, UP}}, {}},
{30, {}, {{0, 1, UP}}},
{50, {{0, 1, DOWN}}, {}},
{55, {}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysShort) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{1, {{0, 2, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{6, {}, {{0, 2, DOWN}}},
{7, {{0, 1, UP}}, {}},
{8, {{0, 2, UP}}, {}},
{12, {}, {{0, 1, UP}}},
{13, {}, {{0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysSimultaneous1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}, {0, 2, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}, {0, 2, DOWN}}},
{6, {{0, 1, UP}, {0, 2, UP}}, {}},
{11, {}, {{0, 1, UP}, {0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysSimultaneous2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
{1, {{0, 2, DOWN}}, {}},
{5, {}, {{0, 1, DOWN}}},
{6, {{0, 1, UP}}, {{0, 2, DOWN}}},
{7, {{0, 2, UP}}, {}},
{11, {}, {{0, 1, UP}}},
{12, {}, {{0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Processing is very late */
{300, {}, {{0, 1, DOWN}}},
/* Immediately release key */
{300, {{0, 1, UP}}, {}},
{305, {}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Processing is very late */
{300, {}, {{0, 1, DOWN}}},
/* Release key after 1ms */
{301, {{0, 1, UP}}, {}},
{306, {}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Release key before debounce expires */
{300, {{0, 1, UP}}, {}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan4) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {}},
/* Processing is a bit late */
{50, {}, {{0, 1, DOWN}}},
/* Release key after 1ms */
{51, {{0, 1, UP}}, {}},
{56, {}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}

View File

@ -0,0 +1,237 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gtest/gtest.h"
#include "debounce_test_common.h"
TEST_F(DebounceTest, OneKeyShort1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 2ms delay (debounce has not yet finished) */
{7, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 3ms delay (debounce has not yet finished) */
{8, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort4) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 4ms delay (debounce has not yet finished) */
{9, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort5) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 5ms delay (debounce has finished) */
{10, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort6) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key after after 6ms delay (debounce has finished) */
{11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{2, {{0, 1, DOWN}}, {}},
{3, {{0, 1, UP}}, {}},
{4, {{0, 1, DOWN}}, {}},
{5, {{0, 1, UP}}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Change twice in the same time period */
{1, {{0, 1, UP}}, {}},
{1, {{0, 1, DOWN}}, {}},
/* Change three times in the same time period */
{2, {{0, 1, UP}}, {}},
{2, {{0, 1, DOWN}}, {}},
{2, {{0, 1, UP}}, {}},
/* Change three times in the same time period */
{3, {{0, 1, DOWN}}, {}},
{3, {{0, 1, UP}}, {}},
{3, {{0, 1, DOWN}}, {}},
/* Change twice in the same time period */
{4, {{0, 1, UP}}, {}},
{4, {{0, 1, DOWN}}, {}},
{5, {{0, 1, UP}}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyLong) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{25, {{0, 1, UP}}, {{0, 1, UP}}},
{50, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysShort) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{2, {{0, 2, DOWN}}, {{0, 2, DOWN}}},
{3, {{0, 2, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{7, {}, {{0, 2, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{9, {{0, 2, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
{12, {}, {{0, 2, DOWN}}}, /* 5ms after UP at time 7 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted */
{300, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1 scan delay */
{300, {}, {}},
{300, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1ms delay */
{300, {}, {}},
{301, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan4) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is a bit late but the change will now be accepted */
{50, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan5) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1 scan delay */
{50, {}, {}},
{50, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan6) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1ms delay */
{50, {}, {}},
{51, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}

View File

@ -0,0 +1,280 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gtest/gtest.h"
#include "debounce_test_common.h"
TEST_F(DebounceTest, OneKeyShort1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 2ms delay (debounce has not yet finished) */
{7, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 3ms delay (debounce has not yet finished) */
{8, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort4) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 4ms delay (debounce has not yet finished) */
{9, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort5) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 5ms delay (debounce has finished) */
{10, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyShort6) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key after after 6ms delay (debounce has finished) */
{11, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{2, {{0, 1, DOWN}}, {}},
{3, {{0, 1, UP}}, {}},
{4, {{0, 1, DOWN}}, {}},
{5, {{0, 1, UP}}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyBouncing2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Change twice in the same time period */
{1, {{0, 1, UP}}, {}},
{1, {{0, 1, DOWN}}, {}},
/* Change three times in the same time period */
{2, {{0, 1, UP}}, {}},
{2, {{0, 1, DOWN}}, {}},
{2, {{0, 1, UP}}, {}},
/* Change three times in the same time period */
{3, {{0, 1, DOWN}}, {}},
{3, {{0, 1, UP}}, {}},
{3, {{0, 1, DOWN}}, {}},
/* Change twice in the same time period */
{4, {{0, 1, UP}}, {}},
{4, {{0, 1, DOWN}}, {}},
{5, {{0, 1, UP}}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
});
runEvents();
}
TEST_F(DebounceTest, OneKeyLong) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{25, {{0, 1, UP}}, {{0, 1, UP}}},
{50, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoRowsShort) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
{2, {{2, 0, DOWN}}, {{2, 0, DOWN}}},
{3, {{2, 0, UP}}, {}},
{5, {}, {{0, 1, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{6, {{0, 1, DOWN}}, {}},
{7, {}, {{2, 0, UP}}},
/* Press key again after 1ms delay (debounce has not yet finished) */
{9, {{2, 0, DOWN}}, {}},
{10, {}, {{0, 1, DOWN}}}, /* 5ms after UP at time 5 */
{12, {}, {{2, 0, DOWN}}}, /* 5ms after UP at time 7 */
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysOverlap) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
{1, {{0, 1, UP}}, {}},
/* Press a second key during the first debounce */
{2, {{0, 2, DOWN}}, {}},
/* Key registers as soon as debounce finishes, 5ms after time 0 */
{5, {}, {{0, 1, UP}, {0, 2, DOWN}}},
{6, {{0, 1, DOWN}}, {}},
/* Key registers as soon as debounce finishes, 5ms after time 5 */
{10, {}, {{0, 1, DOWN}}},
/* Release both keys */
{11, {{0, 1, UP}}, {}},
{12, {{0, 2, UP}}, {}},
/* Keys register as soon as debounce finishes, 5ms after time 10 */
{15, {}, {{0, 1, UP}, {0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysSimultaneous1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}, {0, 2, DOWN}}, {{0, 1, DOWN}, {0, 2, DOWN}}},
{20, {{0, 1, UP}}, {{0, 1, UP}}},
{21, {{0, 2, UP}}, {}},
/* Key registers as soon as debounce finishes, 5ms after time 20 */
{25, {}, {{0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, TwoKeysSimultaneous2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}, {0, 2, DOWN}}, {{0, 1, DOWN}, {0, 2, DOWN}}},
{20, {{0, 1, UP}, {0, 2, UP}}, {{0, 1, UP}, {0, 2, UP}}},
});
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan1) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted */
{300, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan2) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1 scan delay */
{300, {}, {}},
{300, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan3) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1ms delay */
{300, {}, {}},
{301, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan4) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is a bit late but the change will now be accepted */
{50, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan5) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1 scan delay */
{50, {}, {}},
{50, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}
TEST_F(DebounceTest, OneKeyDelayedScan6) {
addEvents({ /* Time, Inputs, Outputs */
{0, {{0, 1, DOWN}}, {{0, 1, DOWN}}},
/* Processing is very late but the change will now be accepted even with a 1ms delay */
{50, {}, {}},
{51, {{0, 1, UP}}, {{0, 1, UP}}},
});
time_jumps_ = true;
runEvents();
}

View File

@ -0,0 +1,5 @@
TEST_LIST += \
debounce_sym_defer_g \
debounce_sym_defer_pk \
debounce_sym_eager_pk \
debounce_sym_eager_pr

View File

@ -1,6 +1,7 @@
TEST_LIST = $(notdir $(patsubst %/rules.mk,%,$(wildcard $(ROOT_DIR)/tests/*/rules.mk))) TEST_LIST = $(notdir $(patsubst %/rules.mk,%,$(wildcard $(ROOT_DIR)/tests/*/rules.mk)))
FULL_TESTS := $(TEST_LIST) FULL_TESTS := $(TEST_LIST)
include $(ROOT_DIR)/quantum/debounce/tests/testlist.mk
include $(ROOT_DIR)/quantum/sequencer/tests/testlist.mk include $(ROOT_DIR)/quantum/sequencer/tests/testlist.mk
include $(ROOT_DIR)/quantum/serial_link/tests/testlist.mk include $(ROOT_DIR)/quantum/serial_link/tests/testlist.mk

View File

@ -0,0 +1,19 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// The platform is 32-bit, so prefer 32-bit timers to avoid overflow
#define FAST_TIMER_T_SIZE 32

View File

@ -0,0 +1,19 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// The platform is 8-bit, so prefer 16-bit timers to reduce code size
#define FAST_TIMER_T_SIZE 16

View File

@ -0,0 +1,19 @@
/* Copyright 2021 Simon Arlott
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// The platform is 32-bit, so prefer 32-bit timers to avoid overflow
#define FAST_TIMER_T_SIZE 32

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2011 Jun Wako <wakojun@gmail.com> Copyright 2011 Jun Wako <wakojun@gmail.com>
Copyright 2021 Simon Arlott
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -17,13 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once #pragma once
#if __has_include_next("_timer.h")
# include_next "_timer.h" /* Include the platform's _timer.h */
#endif
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#if defined(__AVR__)
# include "avr/timer_avr.h"
#endif
#define TIMER_DIFF(a, b, max) ((max == UINT8_MAX) ? ((uint8_t)((a) - (b))) : ((max == UINT16_MAX) ? ((uint16_t)((a) - (b))) : ((max == UINT32_MAX) ? ((uint32_t)((a) - (b))) : ((a) >= (b) ? (a) - (b) : (max) + 1 - (b) + (a))))) #define TIMER_DIFF(a, b, max) ((max == UINT8_MAX) ? ((uint8_t)((a) - (b))) : ((max == UINT16_MAX) ? ((uint16_t)((a) - (b))) : ((max == UINT32_MAX) ? ((uint32_t)((a) - (b))) : ((a) >= (b) ? (a) - (b) : (max) + 1 - (b) + (a)))))
#define TIMER_DIFF_8(a, b) TIMER_DIFF(a, b, UINT8_MAX) #define TIMER_DIFF_8(a, b) TIMER_DIFF(a, b, UINT8_MAX)
#define TIMER_DIFF_16(a, b) TIMER_DIFF(a, b, UINT16_MAX) #define TIMER_DIFF_16(a, b) TIMER_DIFF(a, b, UINT16_MAX)
@ -47,6 +48,21 @@ uint32_t timer_elapsed32(uint32_t last);
#define timer_expired(current, future) ((uint16_t)(current - future) < UINT16_MAX / 2) #define timer_expired(current, future) ((uint16_t)(current - future) < UINT16_MAX / 2)
#define timer_expired32(current, future) ((uint32_t)(current - future) < UINT32_MAX / 2) #define timer_expired32(current, future) ((uint32_t)(current - future) < UINT32_MAX / 2)
// Use an appropriate timer integer size based on architecture (16-bit will overflow sooner)
#if FAST_TIMER_T_SIZE < 32
# define TIMER_DIFF_FAST(a, b) TIMER_DIFF_16(a, b)
# define timer_expired_fast(current, future) timer_expired(current, future)
typedef uint16_t fast_timer_t;
fast_timer_t inline timer_read_fast(void) { return timer_read(); }
fast_timer_t inline timer_elapsed_fast(fast_timer_t last) { return timer_elapsed(last); }
#else
# define TIMER_DIFF_FAST(a, b) TIMER_DIFF_32(a, b)
# define timer_expired_fast(current, future) timer_expired32(current, future)
typedef uint32_t fast_timer_t;
fast_timer_t inline timer_read_fast(void) { return timer_read32(); }
fast_timer_t inline timer_elapsed_fast(fast_timer_t last) { return timer_elapsed32(last); }
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif