Eager Per Row Debouncing added (added to Ergodox) (#5498)

* Implemented Eager Per Row debouncing algorithm.

Good for when fingers can only press one row at a time (e.g. when keyboard is wired so that "rows" are vertical)

* Added documentation for eager_pr

* Ported ergodox_ez to eager_pr debouncing.

* Removed check for changes in matrix_scan.

* Added further clarification in docs.

* Accidental merge with ergodox_ez

* Small cleanup in eager_pr

* Forgot to debounce_init - this would probably cause seg-faults.
master
Alex Ong 2019-04-04 08:45:55 +11:00 committed by Drashna Jaelre
parent e1e08a494b
commit 17e7762de7
5 changed files with 300 additions and 262 deletions

View File

@ -33,7 +33,10 @@ The debounce code is compatible with split keyboards.
# Changing between included debouncing methods # Changing between included debouncing methods
You can either use your own code, by including your own debounce.c, or switch to another included one. You can either use your own code, by including your own debounce.c, or switch to another included one.
Included debounce methods are: Included debounce methods are:
* eager_pk - debouncing per key. On any state change, response is immediate, followed by ```DEBOUNCE_DELAY``` millseconds of no further input for that key * eager_pr - debouncing per row. On any state change, response is immediate, followed by locking the row ```DEBOUNCE_DELAY``` milliseconds of no further input for that row.
For use in keyboards where refreshing ```NUM_KEYS``` 8-bit counters is computationally expensive / low scan rate, and fingers usually only hit one row at a time. This could be
appropriate for the ErgoDox models; the matrix is rotated 90°, and hence its "rows" are really columns, and each finger only hits a single "row" at a time in normal use.
* eager_pk - debouncing per key. On any state change, response is immediate, followed by ```DEBOUNCE_DELAY``` milliseconds of no further input for that key
* sym_g - debouncing per keyboard. On any state change, a global timer is set. When ```DEBOUNCE_DELAY``` milliseconds of no changes has occured, all input changes are pushed. * sym_g - debouncing per keyboard. On any state change, a global timer is set. When ```DEBOUNCE_DELAY``` milliseconds of no changes has occured, all input changes are pushed.

View File

@ -33,14 +33,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "debug.h" #include "debug.h"
#include "util.h" #include "util.h"
#include "matrix.h" #include "matrix.h"
#include "debounce.h"
#include QMK_KEYBOARD_H #include QMK_KEYBOARD_H
#ifdef DEBUG_MATRIX_SCAN_RATE #ifdef DEBUG_MATRIX_SCAN_RATE
#include "timer.h" # include "timer.h"
#endif #endif
/* /*
* This constant define not debouncing time in msecs, but amount of matrix * This constant define not debouncing time in msecs, assuming eager_pr.
* scan loops which should be made to get stable debounced results.
* *
* On Ergodox matrix scan rate is relatively low, because of slow I2C. * On Ergodox matrix scan rate is relatively low, because of slow I2C.
* Now it's only 317 scans/second, or about 3.15 msec/scan. * Now it's only 317 scans/second, or about 3.15 msec/scan.
@ -52,26 +52,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef DEBOUNCE #ifndef DEBOUNCE
# define DEBOUNCE 5 # define DEBOUNCE 5
#endif #endif
/* matrix state(1:on, 0:off) */ /* matrix state(1:on, 0:off) */
static matrix_row_t matrix[MATRIX_ROWS]; static matrix_row_t raw_matrix[MATRIX_ROWS]; // raw values
/* static matrix_row_t matrix[MATRIX_ROWS]; // debounced values
* matrix state(1:on, 0:off)
* contains the raw values without debounce filtering of the last read cycle.
*/
static matrix_row_t raw_matrix[MATRIX_ROWS];
// Debouncing: store for each key the number of scans until it's eligible to
// change. When scanning the matrix, ignore any changes in keys that have
// already changed in the last DEBOUNCE scans.
static uint8_t debounce_matrix[MATRIX_ROWS * MATRIX_COLS];
static matrix_row_t read_cols(uint8_t row); static matrix_row_t read_cols(uint8_t row);
static void init_cols(void); static void init_cols(void);
static void unselect_rows(void); static void unselect_rows(void);
static void select_row(uint8_t row); static void select_row(uint8_t row);
static uint8_t mcp23018_reset_loop; static uint8_t mcp23018_reset_loop;
// static uint16_t mcp23018_reset_loop; // static uint16_t mcp23018_reset_loop;
@ -81,197 +72,137 @@ uint32_t matrix_timer;
uint32_t matrix_scan_count; uint32_t matrix_scan_count;
#endif #endif
__attribute__((weak)) void matrix_init_user(void) {}
__attribute__ ((weak)) __attribute__((weak)) void matrix_scan_user(void) {}
void matrix_init_user(void) {}
__attribute__ ((weak)) __attribute__((weak)) void matrix_init_kb(void) { matrix_init_user(); }
void matrix_scan_user(void) {}
__attribute__ ((weak)) __attribute__((weak)) void matrix_scan_kb(void) { matrix_scan_user(); }
void matrix_init_kb(void) {
matrix_init_user();
}
__attribute__ ((weak)) inline uint8_t matrix_rows(void) { return MATRIX_ROWS; }
void matrix_scan_kb(void) {
matrix_scan_user();
}
inline inline uint8_t matrix_cols(void) { return MATRIX_COLS; }
uint8_t matrix_rows(void)
{
return MATRIX_ROWS;
}
inline void matrix_init(void) {
uint8_t matrix_cols(void) // initialize row and col
{
return MATRIX_COLS;
}
void matrix_init(void) mcp23018_status = init_mcp23018();
{
// initialize row and col
mcp23018_status = init_mcp23018(); unselect_rows();
init_cols();
// initialize matrix state: all keys off
unselect_rows(); for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
init_cols(); matrix[i] = 0;
raw_matrix[i] = 0;
// initialize matrix state: all keys off }
for (uint8_t i=0; i < MATRIX_ROWS; i++) {
matrix[i] = 0;
raw_matrix[i] = 0;
for (uint8_t j=0; j < MATRIX_COLS; ++j) {
debounce_matrix[i * MATRIX_COLS + j] = 0;
}
}
#ifdef DEBUG_MATRIX_SCAN_RATE #ifdef DEBUG_MATRIX_SCAN_RATE
matrix_timer = timer_read32(); matrix_timer = timer_read32();
matrix_scan_count = 0; matrix_scan_count = 0;
#endif #endif
debounce_init(MATRIX_ROWS);
matrix_init_quantum(); matrix_init_quantum();
} }
void matrix_power_up(void) { void matrix_power_up(void) {
mcp23018_status = init_mcp23018(); mcp23018_status = init_mcp23018();
unselect_rows(); unselect_rows();
init_cols(); init_cols();
// initialize matrix state: all keys off // initialize matrix state: all keys off
for (uint8_t i=0; i < MATRIX_ROWS; i++) { for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
matrix[i] = 0; matrix[i] = 0;
} }
#ifdef DEBUG_MATRIX_SCAN_RATE #ifdef DEBUG_MATRIX_SCAN_RATE
matrix_timer = timer_read32(); matrix_timer = timer_read32();
matrix_scan_count = 0; matrix_scan_count = 0;
#endif #endif
} }
// Returns a matrix_row_t whose bits are set if the corresponding key should be uint8_t matrix_scan(void) {
// eligible to change in this scan. if (mcp23018_status) { // if there was an error
matrix_row_t debounce_mask(matrix_row_t rawcols, uint8_t row) { if (++mcp23018_reset_loop == 0) {
matrix_row_t result = 0; // if (++mcp23018_reset_loop >= 1300) {
matrix_row_t change = rawcols ^ raw_matrix[row]; // since mcp23018_reset_loop is 8 bit - we'll try to reset once in 255 matrix scans
raw_matrix[row] = rawcols; // this will be approx bit more frequent than once per second
for (uint8_t i = 0; i < MATRIX_COLS; ++i) { print("trying to reset mcp23018\n");
if (debounce_matrix[row * MATRIX_COLS + i]) { mcp23018_status = init_mcp23018();
--debounce_matrix[row * MATRIX_COLS + i]; if (mcp23018_status) {
} else { print("left side not responding\n");
result |= (1 << i); } else {
} print("left side attached\n");
if (change & (1 << i)) { ergodox_blink_all_leds();
debounce_matrix[row * MATRIX_COLS + i] = DEBOUNCE; }
} }
} }
return result;
}
matrix_row_t debounce_read_cols(uint8_t row) {
// Read the row without debouncing filtering and store it for later usage.
matrix_row_t cols = read_cols(row);
// Get the Debounce mask.
matrix_row_t mask = debounce_mask(cols, row);
// debounce the row and return the result.
return (cols & mask) | (matrix[row] & ~mask);;
}
uint8_t matrix_scan(void)
{
if (mcp23018_status) { // if there was an error
if (++mcp23018_reset_loop == 0) {
// if (++mcp23018_reset_loop >= 1300) {
// since mcp23018_reset_loop is 8 bit - we'll try to reset once in 255 matrix scans
// this will be approx bit more frequent than once per second
print("trying to reset mcp23018\n");
mcp23018_status = init_mcp23018();
if (mcp23018_status) {
print("left side not responding\n");
} else {
print("left side attached\n");
ergodox_blink_all_leds();
}
}
}
#ifdef DEBUG_MATRIX_SCAN_RATE #ifdef DEBUG_MATRIX_SCAN_RATE
matrix_scan_count++; matrix_scan_count++;
uint32_t timer_now = timer_read32(); uint32_t timer_now = timer_read32();
if (TIMER_DIFF_32(timer_now, matrix_timer)>1000) { if (TIMER_DIFF_32(timer_now, matrix_timer) > 1000) {
print("matrix scan frequency: "); print("matrix scan frequency: ");
pdec(matrix_scan_count); pdec(matrix_scan_count);
print("\n"); print("\n");
matrix_timer = timer_now; matrix_timer = timer_now;
matrix_scan_count = 0; matrix_scan_count = 0;
} }
#endif #endif
#ifdef LEFT_LEDS #ifdef LEFT_LEDS
mcp23018_status = ergodox_left_leds_update(); mcp23018_status = ergodox_left_leds_update();
#endif // LEFT_LEDS #endif // LEFT_LEDS
for (uint8_t i = 0; i < MATRIX_ROWS_PER_SIDE; i++) { for (uint8_t i = 0; i < MATRIX_ROWS_PER_SIDE; i++) {
select_row(i); // select rows from left and right hands
// and select on left hand select_row(i);
select_row(i + MATRIX_ROWS_PER_SIDE); select_row(i + MATRIX_ROWS_PER_SIDE);
// we don't need a 30us delay anymore, because selecting a
// left-hand row requires more than 30us for i2c.
// grab cols from left hand // we don't need a 30us delay anymore, because selecting a
matrix[i] = debounce_read_cols(i); // left-hand row requires more than 30us for i2c.
// grab cols from right hand
matrix[i + MATRIX_ROWS_PER_SIDE] = debounce_read_cols(i + MATRIX_ROWS_PER_SIDE);
unselect_rows(); // grab left + right cols.
} raw_matrix[i] = read_cols(i);
raw_matrix[i+MATRIX_ROWS_PER_SIDE] = read_cols(i+MATRIX_ROWS_PER_SIDE);
unselect_rows();
}
debounce(raw_matrix, matrix, MATRIX_ROWS, true);
matrix_scan_quantum();
matrix_scan_quantum(); return 1;
return 1;
} }
bool matrix_is_modified(void) // deprecated and evidently not called. bool matrix_is_modified(void) // deprecated and evidently not called.
{ {
return true; return true;
} }
inline inline bool matrix_is_on(uint8_t row, uint8_t col) { return (matrix[row] & ((matrix_row_t)1 << col)); }
bool matrix_is_on(uint8_t row, uint8_t col)
{ inline matrix_row_t matrix_get_row(uint8_t row) { return matrix[row]; }
return (matrix[row] & ((matrix_row_t)1<<col));
void matrix_print(void) {
print("\nr/c 0123456789ABCDEF\n");
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
phex(row);
print(": ");
pbin_reverse16(matrix_get_row(row));
print("\n");
}
} }
inline uint8_t matrix_key_count(void) {
matrix_row_t matrix_get_row(uint8_t row) uint8_t count = 0;
{ for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
return matrix[row]; count += bitpop16(matrix[i]);
} }
return count;
void matrix_print(void)
{
print("\nr/c 0123456789ABCDEF\n");
for (uint8_t row = 0; row < MATRIX_ROWS; row++) {
phex(row); print(": ");
pbin_reverse16(matrix_get_row(row));
print("\n");
}
}
uint8_t matrix_key_count(void)
{
uint8_t count = 0;
for (uint8_t i = 0; i < MATRIX_ROWS; i++) {
count += bitpop16(matrix[i]);
}
return count;
} }
/* Column pin configuration /* Column pin configuration
@ -284,43 +215,45 @@ uint8_t matrix_key_count(void)
* col: 0 1 2 3 4 5 * col: 0 1 2 3 4 5
* pin: B5 B4 B3 B2 B1 B0 * pin: B5 B4 B3 B2 B1 B0
*/ */
static void init_cols(void) static void init_cols(void) {
{ // init on mcp23018
// init on mcp23018 // not needed, already done as part of init_mcp23018()
// not needed, already done as part of init_mcp23018()
// init on teensy // init on teensy
// Input with pull-up(DDR:0, PORT:1) // Input with pull-up(DDR:0, PORT:1)
DDRF &= ~(1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<1 | 1<<0); DDRF &= ~(1 << 7 | 1 << 6 | 1 << 5 | 1 << 4 | 1 << 1 | 1 << 0);
PORTF |= (1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<1 | 1<<0); PORTF |= (1 << 7 | 1 << 6 | 1 << 5 | 1 << 4 | 1 << 1 | 1 << 0);
} }
static matrix_row_t read_cols(uint8_t row) static matrix_row_t read_cols(uint8_t row) {
{ if (row < 7) {
if (row < 7) { if (mcp23018_status) { // if there was an error
if (mcp23018_status) { // if there was an error return 0;
return 0;
} else {
uint8_t data = 0;
mcp23018_status = i2c_start(I2C_ADDR_WRITE, ERGODOX_EZ_I2C_TIMEOUT); if (mcp23018_status) goto out;
mcp23018_status = i2c_write(GPIOB, ERGODOX_EZ_I2C_TIMEOUT); if (mcp23018_status) goto out;
mcp23018_status = i2c_start(I2C_ADDR_READ, ERGODOX_EZ_I2C_TIMEOUT); if (mcp23018_status) goto out;
mcp23018_status = i2c_read_nack(ERGODOX_EZ_I2C_TIMEOUT); if (mcp23018_status < 0) goto out;
data = ~((uint8_t)mcp23018_status);
mcp23018_status = I2C_STATUS_SUCCESS;
out:
i2c_stop();
return data;
}
} else { } else {
/* read from teensy uint8_t data = 0;
* bitmask is 0b11110011, but we want those all mcp23018_status = i2c_start(I2C_ADDR_WRITE, ERGODOX_EZ_I2C_TIMEOUT);
* in the lower six bits. if (mcp23018_status) goto out;
* we'll return 1s for the top two, but that's harmless. mcp23018_status = i2c_write(GPIOB, ERGODOX_EZ_I2C_TIMEOUT);
*/ if (mcp23018_status) goto out;
mcp23018_status = i2c_start(I2C_ADDR_READ, ERGODOX_EZ_I2C_TIMEOUT);
return ~((PINF & 0x03) | ((PINF & 0xF0) >> 2)); if (mcp23018_status) goto out;
mcp23018_status = i2c_read_nack(ERGODOX_EZ_I2C_TIMEOUT);
if (mcp23018_status < 0) goto out;
data = ~((uint8_t)mcp23018_status);
mcp23018_status = I2C_STATUS_SUCCESS;
out:
i2c_stop();
return data;
} }
} else {
/* read from teensy
* bitmask is 0b11110011, but we want those all
* in the lower six bits.
* we'll return 1s for the top two, but that's harmless.
*/
return ~((PINF & 0x03) | ((PINF & 0xF0) >> 2));
}
} }
/* Row pin configuration /* Row pin configuration
@ -333,69 +266,70 @@ static matrix_row_t read_cols(uint8_t row)
* row: 0 1 2 3 4 5 6 * row: 0 1 2 3 4 5 6
* pin: A0 A1 A2 A3 A4 A5 A6 * pin: A0 A1 A2 A3 A4 A5 A6
*/ */
static void unselect_rows(void) static void unselect_rows(void) {
{ // no need to unselect on mcp23018, because the select step sets all
// no need to unselect on mcp23018, because the select step sets all // the other row bits high, and it's not changing to a different
// the other row bits high, and it's not changing to a different // direction
// direction
// unselect on teensy // unselect on teensy
// Hi-Z(DDR:0, PORT:0) to unselect // Hi-Z(DDR:0, PORT:0) to unselect
DDRB &= ~(1<<0 | 1<<1 | 1<<2 | 1<<3); DDRB &= ~(1 << 0 | 1 << 1 | 1 << 2 | 1 << 3);
PORTB &= ~(1<<0 | 1<<1 | 1<<2 | 1<<3); PORTB &= ~(1 << 0 | 1 << 1 | 1 << 2 | 1 << 3);
DDRD &= ~(1<<2 | 1<<3); DDRD &= ~(1 << 2 | 1 << 3);
PORTD &= ~(1<<2 | 1<<3); PORTD &= ~(1 << 2 | 1 << 3);
DDRC &= ~(1<<6); DDRC &= ~(1 << 6);
PORTC &= ~(1<<6); PORTC &= ~(1 << 6);
} }
static void select_row(uint8_t row) static void select_row(uint8_t row) {
{ if (row < 7) {
if (row < 7) { // select on mcp23018
// select on mcp23018 if (mcp23018_status) { // if there was an error
if (mcp23018_status) { // if there was an error // do nothing
// do nothing
} else {
// set active row low : 0
// set other rows hi-Z : 1
mcp23018_status = i2c_start(I2C_ADDR_WRITE, ERGODOX_EZ_I2C_TIMEOUT); if (mcp23018_status) goto out;
mcp23018_status = i2c_write(GPIOA, ERGODOX_EZ_I2C_TIMEOUT); if (mcp23018_status) goto out;
mcp23018_status = i2c_write(0xFF & ~(1<<row), ERGODOX_EZ_I2C_TIMEOUT); if (mcp23018_status) goto out;
out:
i2c_stop();
}
} else { } else {
// select on teensy // set active row low : 0
// Output low(DDR:1, PORT:0) to select // set other rows hi-Z : 1
switch (row) { mcp23018_status = i2c_start(I2C_ADDR_WRITE, ERGODOX_EZ_I2C_TIMEOUT);
case 7: if (mcp23018_status) goto out;
DDRB |= (1<<0); mcp23018_status = i2c_write(GPIOA, ERGODOX_EZ_I2C_TIMEOUT);
PORTB &= ~(1<<0); if (mcp23018_status) goto out;
break; mcp23018_status = i2c_write(0xFF & ~(1 << row), ERGODOX_EZ_I2C_TIMEOUT);
case 8: if (mcp23018_status) goto out;
DDRB |= (1<<1); out:
PORTB &= ~(1<<1); i2c_stop();
break;
case 9:
DDRB |= (1<<2);
PORTB &= ~(1<<2);
break;
case 10:
DDRB |= (1<<3);
PORTB &= ~(1<<3);
break;
case 11:
DDRD |= (1<<2);
PORTD &= ~(1<<2);
break;
case 12:
DDRD |= (1<<3);
PORTD &= ~(1<<3);
break;
case 13:
DDRC |= (1<<6);
PORTC &= ~(1<<6);
break;
}
} }
} else {
// select on teensy
// Output low(DDR:1, PORT:0) to select
switch (row) {
case 7:
DDRB |= (1 << 0);
PORTB &= ~(1 << 0);
break;
case 8:
DDRB |= (1 << 1);
PORTB &= ~(1 << 1);
break;
case 9:
DDRB |= (1 << 2);
PORTB &= ~(1 << 2);
break;
case 10:
DDRB |= (1 << 3);
PORTB &= ~(1 << 3);
break;
case 11:
DDRD |= (1 << 2);
PORTD &= ~(1 << 2);
break;
case 12:
DDRD |= (1 << 3);
PORTD &= ~(1 << 3);
break;
case 13:
DDRC |= (1 << 6);
PORTC &= ~(1 << 6);
break;
}
}
} }

View File

@ -83,6 +83,7 @@ SLEEP_LED_ENABLE = no
API_SYSEX_ENABLE = no API_SYSEX_ENABLE = no
RGBLIGHT_ENABLE = yes RGBLIGHT_ENABLE = yes
RGB_MATRIX_ENABLE = no # enable later RGB_MATRIX_ENABLE = no # enable later
DEBOUNCE_TYPE = eager_pr
ifeq ($(strip $(RGB_MATRIX_ENABLE)), no) ifeq ($(strip $(RGB_MATRIX_ENABLE)), no)
SRC += i2c_master.c SRC += i2c_master.c

View File

@ -0,0 +1,100 @@
/*
Copyright 2019 Alex Ong<the.onga@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Basic per-row algorithm. Uses an 8-bit counter per row.
After pressing a key, it immediately changes state, and sets a counter.
No further inputs are accepted until DEBOUNCE milliseconds have occurred.
*/
#include "matrix.h"
#include "timer.h"
#include "quantum.h"
#include <stdlib.h>
#ifndef DEBOUNCE
#define DEBOUNCE 5
#endif
#define debounce_counter_t uint8_t
static debounce_counter_t *debounce_counters;
#define DEBOUNCE_ELAPSED 251
#define MAX_DEBOUNCE (DEBOUNCE_ELAPSED - 1)
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
void debounce_init(uint8_t num_rows)
{
debounce_counters = (debounce_counter_t*)malloc(num_rows*sizeof(debounce_counter_t));
for (uint8_t r = 0; r < num_rows; r++)
{
debounce_counters[r] = DEBOUNCE_ELAPSED;
}
}
void debounce(matrix_row_t raw[], matrix_row_t cooked[], uint8_t num_rows, bool changed)
{
uint8_t current_time = timer_read() % MAX_DEBOUNCE;
update_debounce_counters(num_rows, current_time);
transfer_matrix_values(raw, cooked, num_rows, current_time);
}
//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)
{
debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++)
{
if (*debounce_pointer != DEBOUNCE_ELAPSED)
{
if (TIMER_DIFF(current_time, *debounce_pointer, MAX_DEBOUNCE) >= DEBOUNCE) {
*debounce_pointer = DEBOUNCE_ELAPSED;
}
}
debounce_pointer++;
}
}
// 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)
{
debounce_counter_t *debounce_pointer = debounce_counters;
for (uint8_t row = 0; row < num_rows; row++)
{
matrix_row_t existing_row = cooked[row];
matrix_row_t raw_row = raw[row];
//determine new value basd on debounce pointer + raw value
if (*debounce_pointer == DEBOUNCE_ELAPSED &&
(existing_row != raw_row))
{
*debounce_pointer = current_time;
existing_row = raw_row;
}
cooked[row] = existing_row;
debounce_pointer++;
}
}
bool debounce_active(void)
{
return true;
}

View File

@ -22,7 +22,7 @@ Here are a few that could be implemented:
sym_g.c sym_g.c
sym_pk.c sym_pk.c
sym_pr.c sym_pr.c
sym_pr_cycles.c //currently used in ergo-dox sym_pr_cycles.c
eager_g.c eager_g.c
eager_pk.c eager_pk.c
eager_pr.c //could be used in ergo-dox! eager_pr.c //could be used in ergo-dox!