feat(kscan): Improve matrix debouncing
Switched the GPIO matrix driver to debouncing using a simple integrator algorithm. Whenever a key is pressed, we now scan at a rate controlled by debounce-scan-period-ms (default 1 ms) until all keys are released, then return to either waiting for an interrupt or polling more slowly. The timers for key press and release can now be controlled separately, so debounce-period is deprecated in favor of debounce-press-ms and debounce-release-ms. Global Kconfig options ZMK_KSCAN_DEBOUNCE_PRESS_MS and ZMK_KSCAN_DEBOUNCE_RELEASE_MS are also added to make these easier to set. Added documentation for debouncing options.
This commit is contained in:
parent
5cc7c280a5
commit
f946dc6893
8 changed files with 348 additions and 64 deletions
|
@ -4,6 +4,7 @@
|
||||||
zephyr_library_named(zmk__drivers__kscan)
|
zephyr_library_named(zmk__drivers__kscan)
|
||||||
zephyr_library_include_directories(${CMAKE_SOURCE_DIR}/include)
|
zephyr_library_include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER debounce.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio_matrix.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio_matrix.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio_direct.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio_direct.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio_demux.c)
|
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio_demux.c)
|
||||||
|
|
|
@ -14,6 +14,24 @@ config ZMK_KSCAN_MATRIX_POLLING
|
||||||
config ZMK_KSCAN_DIRECT_POLLING
|
config ZMK_KSCAN_DIRECT_POLLING
|
||||||
bool "Poll for key event triggers instead of using interrupts on direct wired boards."
|
bool "Poll for key event triggers instead of using interrupts on direct wired boards."
|
||||||
|
|
||||||
|
config ZMK_KSCAN_DEBOUNCE_PRESS_MS
|
||||||
|
int "Debounce time for key press in milliseconds."
|
||||||
|
default -1
|
||||||
|
help
|
||||||
|
Global debounce time for key press in milliseconds.
|
||||||
|
If this is -1, the debounce time is controlled by the debounce-press-ms
|
||||||
|
Devicetree property, which defaults to 5 ms. Otherwise this overrides the
|
||||||
|
debounce time for all key scan drivers to the chosen value.
|
||||||
|
|
||||||
|
config ZMK_KSCAN_DEBOUNCE_RELEASE_MS
|
||||||
|
int "Debounce time for key release in milliseconds."
|
||||||
|
default -1
|
||||||
|
help
|
||||||
|
Global debounce time for key release in milliseconds.
|
||||||
|
If this is -1, the debounce time is controlled by the debounce-release-ms
|
||||||
|
Devicetree property, which defaults to 5 ms. Otherwise this overrides the
|
||||||
|
debounce time for all key scan drivers to the chosen value.
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
config ZMK_KSCAN_INIT_PRIORITY
|
config ZMK_KSCAN_INIT_PRIORITY
|
||||||
|
|
62
app/drivers/kscan/debounce.c
Normal file
62
app/drivers/kscan/debounce.c
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "debounce.h"
|
||||||
|
|
||||||
|
static uint32_t get_threshold(const struct debounce_state *state,
|
||||||
|
const struct debounce_config *config) {
|
||||||
|
return state->pressed ? config->debounce_release_ms : config->debounce_press_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void increment_counter(struct debounce_state *state, const int elapsed_ms) {
|
||||||
|
if (state->counter + elapsed_ms > DEBOUNCE_COUNTER_MAX) {
|
||||||
|
state->counter = DEBOUNCE_COUNTER_MAX;
|
||||||
|
} else {
|
||||||
|
state->counter += elapsed_ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decrement_counter(struct debounce_state *state, const int elapsed_ms) {
|
||||||
|
if (state->counter < elapsed_ms) {
|
||||||
|
state->counter = 0;
|
||||||
|
} else {
|
||||||
|
state->counter -= elapsed_ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void debounce_update(struct debounce_state *state, const bool active, const int elapsed_ms,
|
||||||
|
const struct debounce_config *config) {
|
||||||
|
// This uses a variation of the integrator debouncing described at
|
||||||
|
// https://www.kennethkuhn.com/electronics/debounce.c
|
||||||
|
// Every update where "active" does not match the current state, we increment
|
||||||
|
// a counter, otherwise we decrement it. When the counter reaches a
|
||||||
|
// threshold, the state flips and we reset the counter.
|
||||||
|
state->changed = false;
|
||||||
|
|
||||||
|
if (active == state->pressed) {
|
||||||
|
decrement_counter(state, elapsed_ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t flip_threshold = get_threshold(state, config);
|
||||||
|
|
||||||
|
if (state->counter < flip_threshold) {
|
||||||
|
increment_counter(state, elapsed_ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->pressed = !state->pressed;
|
||||||
|
state->counter = 0;
|
||||||
|
state->changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debounce_is_active(const struct debounce_state *state) {
|
||||||
|
return state->pressed || state->counter > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool debounce_is_pressed(const struct debounce_state *state) { return state->pressed; }
|
||||||
|
|
||||||
|
bool debounce_get_changed(const struct debounce_state *state) { return state->changed; }
|
56
app/drivers/kscan/debounce.h
Normal file
56
app/drivers/kscan/debounce.h
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/util.h>
|
||||||
|
|
||||||
|
#define DEBOUNCE_COUNTER_BITS 14
|
||||||
|
#define DEBOUNCE_COUNTER_MAX BIT_MASK(DEBOUNCE_COUNTER_BITS)
|
||||||
|
|
||||||
|
struct debounce_state {
|
||||||
|
bool pressed : 1;
|
||||||
|
bool changed : 1;
|
||||||
|
uint16_t counter : DEBOUNCE_COUNTER_BITS;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct debounce_config {
|
||||||
|
/** Duration a switch must be pressed to latch as pressed. */
|
||||||
|
uint32_t debounce_press_ms;
|
||||||
|
/** Duration a switch must be released to latch as released. */
|
||||||
|
uint32_t debounce_release_ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounces one switch.
|
||||||
|
*
|
||||||
|
* @param state The state for the switch to debounce.
|
||||||
|
* @param active Is the switch currently pressed?
|
||||||
|
* @param elapsed_ms Time elapsed since the previous update in milliseconds.
|
||||||
|
* @param config Debounce settings.
|
||||||
|
*/
|
||||||
|
void debounce_update(struct debounce_state *state, const bool active, const int elapsed_ms,
|
||||||
|
const struct debounce_config *config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns whether the switch is either latched as pressed or it is potentially
|
||||||
|
* pressed but the debouncer has not yet made a decision. If this returns true,
|
||||||
|
* the kscan driver should continue to poll quickly.
|
||||||
|
*/
|
||||||
|
bool debounce_is_active(const struct debounce_state *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns whether the switch is latched as pressed.
|
||||||
|
*/
|
||||||
|
bool debounce_is_pressed(const struct debounce_state *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns whether the pressed state of the switch changed in the last call to
|
||||||
|
* debounce_update.
|
||||||
|
*/
|
||||||
|
bool debounce_get_changed(const struct debounce_state *state);
|
|
@ -4,10 +4,13 @@
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "debounce.h"
|
||||||
|
|
||||||
#include <device.h>
|
#include <device.h>
|
||||||
#include <devicetree.h>
|
#include <devicetree.h>
|
||||||
#include <drivers/gpio.h>
|
#include <drivers/gpio.h>
|
||||||
#include <drivers/kscan.h>
|
#include <drivers/kscan.h>
|
||||||
|
#include <kernel.h>
|
||||||
#include <logging/log.h>
|
#include <logging/log.h>
|
||||||
#include <sys/__assert.h>
|
#include <sys/__assert.h>
|
||||||
#include <sys/util.h>
|
#include <sys/util.h>
|
||||||
|
@ -27,6 +30,20 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
#define INST_MATRIX_LEN(n) (INST_ROWS_LEN(n) * INST_COLS_LEN(n))
|
#define INST_MATRIX_LEN(n) (INST_ROWS_LEN(n) * INST_COLS_LEN(n))
|
||||||
#define INST_INPUTS_LEN(n) COND_DIODE_DIR(n, (INST_COLS_LEN(n)), (INST_ROWS_LEN(n)))
|
#define INST_INPUTS_LEN(n) COND_DIODE_DIR(n, (INST_COLS_LEN(n)), (INST_ROWS_LEN(n)))
|
||||||
|
|
||||||
|
#if CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS >= 0
|
||||||
|
#define INST_DEBOUNCE_PRESS_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS
|
||||||
|
#else
|
||||||
|
#define INST_DEBOUNCE_PRESS_MS(n) \
|
||||||
|
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_press_ms))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS >= 0
|
||||||
|
#define INST_DEBOUNCE_RELEASE_MS(n) CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS
|
||||||
|
#else
|
||||||
|
#define INST_DEBOUNCE_RELEASE_MS(n) \
|
||||||
|
DT_INST_PROP_OR(n, debounce_period, DT_INST_PROP(n, debounce_release_ms))
|
||||||
|
#endif
|
||||||
|
|
||||||
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MATRIX_POLLING)
|
#define USE_POLLING IS_ENABLED(CONFIG_ZMK_KSCAN_MATRIX_POLLING)
|
||||||
#define USE_INTERRUPTS (!USE_POLLING)
|
#define USE_INTERRUPTS (!USE_POLLING)
|
||||||
|
|
||||||
|
@ -66,26 +83,23 @@ enum kscan_diode_direction {
|
||||||
struct kscan_matrix_irq_callback {
|
struct kscan_matrix_irq_callback {
|
||||||
const struct device *dev;
|
const struct device *dev;
|
||||||
struct gpio_callback callback;
|
struct gpio_callback callback;
|
||||||
struct k_delayed_work *work;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct kscan_matrix_data {
|
struct kscan_matrix_data {
|
||||||
const struct device *dev;
|
const struct device *dev;
|
||||||
kscan_callback_t callback;
|
kscan_callback_t callback;
|
||||||
struct k_delayed_work work;
|
struct k_delayed_work work;
|
||||||
#if USE_POLLING
|
#if USE_INTERRUPTS
|
||||||
struct k_timer poll_timer;
|
|
||||||
#else
|
|
||||||
/** Array of length config->inputs.len */
|
/** Array of length config->inputs.len */
|
||||||
struct kscan_matrix_irq_callback *irqs;
|
struct kscan_matrix_irq_callback *irqs;
|
||||||
#endif
|
#endif
|
||||||
|
/** Timestamp of the current or scheduled scan. */
|
||||||
|
int64_t scan_time;
|
||||||
/**
|
/**
|
||||||
* Current state of the matrix as a flattened 2D array of length
|
* Current state of the matrix as a flattened 2D array of length
|
||||||
* (config->rows.len * config->cols.len)
|
* (config->rows.len * config->cols.len)
|
||||||
*/
|
*/
|
||||||
bool *current_state;
|
struct debounce_state *matrix_state;
|
||||||
/** Buffer for reading in the next matrix state. Parallel array to current_state. */
|
|
||||||
bool *next_state;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct kscan_gpio_list {
|
struct kscan_gpio_list {
|
||||||
|
@ -102,7 +116,8 @@ struct kscan_matrix_config {
|
||||||
struct kscan_gpio_list cols;
|
struct kscan_gpio_list cols;
|
||||||
struct kscan_gpio_list inputs;
|
struct kscan_gpio_list inputs;
|
||||||
struct kscan_gpio_list outputs;
|
struct kscan_gpio_list outputs;
|
||||||
int32_t debounce_period_ms;
|
struct debounce_config debounce_config;
|
||||||
|
int32_t debounce_scan_period_ms;
|
||||||
int32_t poll_period_ms;
|
int32_t poll_period_ms;
|
||||||
enum kscan_diode_direction diode_direction;
|
enum kscan_diode_direction diode_direction;
|
||||||
};
|
};
|
||||||
|
@ -190,19 +205,49 @@ static int kscan_matrix_interrupt_disable(const struct device *dev) {
|
||||||
#if USE_INTERRUPTS
|
#if USE_INTERRUPTS
|
||||||
static void kscan_matrix_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
|
static void kscan_matrix_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
|
||||||
const gpio_port_pins_t pin) {
|
const gpio_port_pins_t pin) {
|
||||||
struct kscan_matrix_irq_callback *data =
|
struct kscan_matrix_irq_callback *irq_data =
|
||||||
CONTAINER_OF(cb, struct kscan_matrix_irq_callback, callback);
|
CONTAINER_OF(cb, struct kscan_matrix_irq_callback, callback);
|
||||||
const struct kscan_matrix_config *config = data->dev->config;
|
struct kscan_matrix_data *data = irq_data->dev->data;
|
||||||
|
|
||||||
// Disable our interrupts temporarily to avoid re-entry while we scan.
|
// Disable our interrupts temporarily to avoid re-entry while we scan.
|
||||||
kscan_matrix_interrupt_disable(data->dev);
|
kscan_matrix_interrupt_disable(data->dev);
|
||||||
|
|
||||||
|
data->scan_time = k_uptime_get();
|
||||||
|
|
||||||
// TODO (Zephyr 2.6): use k_work_reschedule()
|
// TODO (Zephyr 2.6): use k_work_reschedule()
|
||||||
k_delayed_work_cancel(data->work);
|
k_delayed_work_cancel(&data->work);
|
||||||
k_delayed_work_submit(data->work, K_MSEC(config->debounce_period_ms));
|
k_delayed_work_submit(&data->work, K_NO_WAIT);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void kscan_matrix_read_continue(const struct device *dev) {
|
||||||
|
const struct kscan_matrix_config *config = dev->config;
|
||||||
|
struct kscan_matrix_data *data = dev->data;
|
||||||
|
|
||||||
|
data->scan_time += config->debounce_scan_period_ms;
|
||||||
|
|
||||||
|
// TODO (Zephyr 2.6): use k_work_reschedule()
|
||||||
|
k_delayed_work_cancel(&data->work);
|
||||||
|
k_delayed_work_submit(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kscan_matrix_read_end(const struct device *dev) {
|
||||||
|
#if USE_INTERRUPTS
|
||||||
|
// Return to waiting for an interrupt.
|
||||||
|
kscan_matrix_interrupt_enable(dev);
|
||||||
|
#else
|
||||||
|
struct kscan_matrix_data *data = dev->data;
|
||||||
|
const struct kscan_matrix_config *config = dev->config;
|
||||||
|
|
||||||
|
data->scan_time += config->poll_period_ms;
|
||||||
|
|
||||||
|
// Return to polling slowly.
|
||||||
|
// TODO (Zephyr 2.6): use k_work_reschedule()
|
||||||
|
k_delayed_work_cancel(&data->work);
|
||||||
|
k_delayed_work_submit(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static int kscan_matrix_read(const struct device *dev) {
|
static int kscan_matrix_read(const struct device *dev) {
|
||||||
struct kscan_matrix_data *data = dev->data;
|
struct kscan_matrix_data *data = dev->data;
|
||||||
const struct kscan_matrix_config *config = dev->config;
|
const struct kscan_matrix_config *config = dev->config;
|
||||||
|
@ -221,7 +266,10 @@ static int kscan_matrix_read(const struct device *dev) {
|
||||||
const struct kscan_gpio_dt_spec *in_gpio = &config->inputs.gpios[i];
|
const struct kscan_gpio_dt_spec *in_gpio = &config->inputs.gpios[i];
|
||||||
|
|
||||||
const int index = state_index_io(config, i, o);
|
const int index = state_index_io(config, i, o);
|
||||||
data->next_state[index] = gpio_pin_get(in_gpio->port, in_gpio->pin);
|
const bool active = gpio_pin_get(in_gpio->port, in_gpio->pin);
|
||||||
|
|
||||||
|
debounce_update(&data->matrix_state[index], active, config->debounce_scan_period_ms,
|
||||||
|
&config->debounce_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = gpio_pin_set(out_gpio->port, out_gpio->pin, 0);
|
err = gpio_pin_set(out_gpio->port, out_gpio->pin, 0);
|
||||||
|
@ -232,50 +280,36 @@ static int kscan_matrix_read(const struct device *dev) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the new state.
|
// Process the new state.
|
||||||
#if USE_INTERRUPTS
|
bool continue_scan = false;
|
||||||
bool submit_followup_read = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (int r = 0; r < config->rows.len; r++) {
|
for (int r = 0; r < config->rows.len; r++) {
|
||||||
for (int c = 0; c < config->cols.len; c++) {
|
for (int c = 0; c < config->cols.len; c++) {
|
||||||
const int index = state_index_rc(config, r, c);
|
const int index = state_index_rc(config, r, c);
|
||||||
const bool pressed = data->next_state[index];
|
struct debounce_state *state = &data->matrix_state[index];
|
||||||
|
|
||||||
|
if (debounce_get_changed(state)) {
|
||||||
|
const bool pressed = debounce_is_pressed(state);
|
||||||
|
|
||||||
// Follow up reads are needed if any key is pressed because further
|
|
||||||
// interrupts won't fire on already tripped GPIO pins.
|
|
||||||
#if USE_INTERRUPTS
|
|
||||||
submit_followup_read = submit_followup_read || pressed;
|
|
||||||
#endif
|
|
||||||
if (pressed != data->current_state[index]) {
|
|
||||||
LOG_DBG("Sending event at %i,%i state %s", r, c, pressed ? "on" : "off");
|
LOG_DBG("Sending event at %i,%i state %s", r, c, pressed ? "on" : "off");
|
||||||
data->current_state[index] = pressed;
|
|
||||||
data->callback(dev, r, c, pressed);
|
data->callback(dev, r, c, pressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continue_scan = continue_scan || debounce_is_active(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if USE_INTERRUPTS
|
if (continue_scan) {
|
||||||
if (submit_followup_read) {
|
// At least one key is pressed or the debouncer has not yet decided if
|
||||||
// At least one key is pressed. Poll until everything is released.
|
// it is pressed. Poll quickly until everything is released.
|
||||||
// TODO (Zephyr 2.6): use k_work_reschedule()
|
kscan_matrix_read_continue(dev);
|
||||||
k_delayed_work_cancel(&data->work);
|
|
||||||
k_delayed_work_submit(&data->work, K_MSEC(config->debounce_period_ms));
|
|
||||||
} else {
|
} else {
|
||||||
// All keys are released. Return to waiting for an interrupt.
|
// All keys are released. Return to normal.
|
||||||
kscan_matrix_interrupt_enable(dev);
|
kscan_matrix_read_end(dev);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if USE_POLLING
|
|
||||||
static void kscan_matrix_timer_handler(struct k_timer *timer) {
|
|
||||||
struct kscan_matrix_data *data = CONTAINER_OF(timer, struct kscan_matrix_data, poll_timer);
|
|
||||||
k_delayed_work_submit(&data->work, K_NO_WAIT);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void kscan_matrix_work_handler(struct k_work *work) {
|
static void kscan_matrix_work_handler(struct k_work *work) {
|
||||||
struct k_delayed_work *dwork = CONTAINER_OF(work, struct k_delayed_work, work);
|
struct k_delayed_work *dwork = CONTAINER_OF(work, struct k_delayed_work, work);
|
||||||
struct kscan_matrix_data *data = CONTAINER_OF(dwork, struct kscan_matrix_data, work);
|
struct kscan_matrix_data *data = CONTAINER_OF(dwork, struct kscan_matrix_data, work);
|
||||||
|
@ -294,27 +328,23 @@ static int kscan_matrix_configure(const struct device *dev, const kscan_callback
|
||||||
}
|
}
|
||||||
|
|
||||||
static int kscan_matrix_enable(const struct device *dev) {
|
static int kscan_matrix_enable(const struct device *dev) {
|
||||||
#if USE_POLLING
|
|
||||||
struct kscan_matrix_data *data = dev->data;
|
struct kscan_matrix_data *data = dev->data;
|
||||||
const struct kscan_matrix_config *config = dev->config;
|
|
||||||
|
|
||||||
k_timer_start(&data->poll_timer, K_MSEC(config->poll_period_ms),
|
data->scan_time = k_uptime_get();
|
||||||
K_MSEC(config->poll_period_ms));
|
|
||||||
return 0;
|
// Read will automatically start interrupts/polling once done.
|
||||||
#else
|
|
||||||
// Read will automatically enable interrupts once done.
|
|
||||||
return kscan_matrix_read(dev);
|
return kscan_matrix_read(dev);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int kscan_matrix_disable(const struct device *dev) {
|
static int kscan_matrix_disable(const struct device *dev) {
|
||||||
#if USE_POLLING
|
|
||||||
struct kscan_matrix_data *data = dev->data;
|
struct kscan_matrix_data *data = dev->data;
|
||||||
|
|
||||||
k_timer_stop(&data->poll_timer);
|
k_delayed_work_cancel(&data->work);
|
||||||
return 0;
|
|
||||||
#else
|
#if USE_INTERRUPTS
|
||||||
return kscan_matrix_interrupt_disable(dev);
|
return kscan_matrix_interrupt_disable(dev);
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +368,6 @@ static int kscan_matrix_init_input_inst(const struct device *dev,
|
||||||
struct kscan_matrix_irq_callback *irq = &data->irqs[index];
|
struct kscan_matrix_irq_callback *irq = &data->irqs[index];
|
||||||
|
|
||||||
irq->dev = dev;
|
irq->dev = dev;
|
||||||
irq->work = &data->work;
|
|
||||||
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->pin));
|
gpio_init_callback(&irq->callback, kscan_matrix_irq_callback_handler, BIT(gpio->pin));
|
||||||
err = gpio_add_callback(gpio->port, &irq->callback);
|
err = gpio_add_callback(gpio->port, &irq->callback);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -407,10 +436,6 @@ static int kscan_matrix_init(const struct device *dev) {
|
||||||
|
|
||||||
k_delayed_work_init(&data->work, kscan_matrix_work_handler);
|
k_delayed_work_init(&data->work, kscan_matrix_work_handler);
|
||||||
|
|
||||||
#if USE_POLLING
|
|
||||||
k_timer_init(&data->poll_timer, kscan_matrix_timer_handler, NULL);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,21 +446,24 @@ static const struct kscan_driver_api kscan_matrix_api = {
|
||||||
};
|
};
|
||||||
|
|
||||||
#define KSCAN_MATRIX_INIT(index) \
|
#define KSCAN_MATRIX_INIT(index) \
|
||||||
|
BUILD_ASSERT(INST_DEBOUNCE_PRESS_MS(index) <= DEBOUNCE_COUNTER_MAX, \
|
||||||
|
"ZMK_KSCAN_DEBOUNCE_PRESS_MS or debounce-press-ms is too large"); \
|
||||||
|
BUILD_ASSERT(INST_DEBOUNCE_RELEASE_MS(index) <= DEBOUNCE_COUNTER_MAX, \
|
||||||
|
"ZMK_KSCAN_DEBOUNCE_RELEASE_MS or debounce-release-ms is too large"); \
|
||||||
|
\
|
||||||
static const struct kscan_gpio_dt_spec kscan_matrix_rows_##index[] = { \
|
static const struct kscan_gpio_dt_spec kscan_matrix_rows_##index[] = { \
|
||||||
UTIL_LISTIFY(INST_ROWS_LEN(index), KSCAN_GPIO_ROW_CFG_INIT, index)}; \
|
UTIL_LISTIFY(INST_ROWS_LEN(index), KSCAN_GPIO_ROW_CFG_INIT, index)}; \
|
||||||
\
|
\
|
||||||
static const struct kscan_gpio_dt_spec kscan_matrix_cols_##index[] = { \
|
static const struct kscan_gpio_dt_spec kscan_matrix_cols_##index[] = { \
|
||||||
UTIL_LISTIFY(INST_COLS_LEN(index), KSCAN_GPIO_COL_CFG_INIT, index)}; \
|
UTIL_LISTIFY(INST_COLS_LEN(index), KSCAN_GPIO_COL_CFG_INIT, index)}; \
|
||||||
\
|
\
|
||||||
static bool kscan_current_state_##index[INST_MATRIX_LEN(index)]; \
|
static struct debounce_state kscan_matrix_state_##index[INST_MATRIX_LEN(index)]; \
|
||||||
static bool kscan_next_state_##index[INST_MATRIX_LEN(index)]; \
|
|
||||||
\
|
\
|
||||||
COND_INTERRUPTS((static struct kscan_matrix_irq_callback \
|
COND_INTERRUPTS((static struct kscan_matrix_irq_callback \
|
||||||
kscan_matrix_irqs_##index[INST_INPUTS_LEN(index)];)) \
|
kscan_matrix_irqs_##index[INST_INPUTS_LEN(index)];)) \
|
||||||
\
|
\
|
||||||
static struct kscan_matrix_data kscan_matrix_data_##index = { \
|
static struct kscan_matrix_data kscan_matrix_data_##index = { \
|
||||||
.current_state = kscan_current_state_##index, \
|
.matrix_state = kscan_matrix_state_##index, \
|
||||||
.next_state = kscan_next_state_##index, \
|
|
||||||
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##index, ))}; \
|
COND_INTERRUPTS((.irqs = kscan_matrix_irqs_##index, ))}; \
|
||||||
\
|
\
|
||||||
static struct kscan_matrix_config kscan_matrix_config_##index = { \
|
static struct kscan_matrix_config kscan_matrix_config_##index = { \
|
||||||
|
@ -445,7 +473,12 @@ static const struct kscan_driver_api kscan_matrix_api = {
|
||||||
COND_DIODE_DIR(index, (kscan_matrix_cols_##index), (kscan_matrix_rows_##index))), \
|
COND_DIODE_DIR(index, (kscan_matrix_cols_##index), (kscan_matrix_rows_##index))), \
|
||||||
.outputs = KSCAN_GPIO_LIST( \
|
.outputs = KSCAN_GPIO_LIST( \
|
||||||
COND_DIODE_DIR(index, (kscan_matrix_rows_##index), (kscan_matrix_cols_##index))), \
|
COND_DIODE_DIR(index, (kscan_matrix_rows_##index), (kscan_matrix_cols_##index))), \
|
||||||
.debounce_period_ms = DT_INST_PROP(index, debounce_period), \
|
.debounce_config = \
|
||||||
|
{ \
|
||||||
|
.debounce_press_ms = INST_DEBOUNCE_PRESS_MS(index), \
|
||||||
|
.debounce_release_ms = INST_DEBOUNCE_RELEASE_MS(index), \
|
||||||
|
}, \
|
||||||
|
.debounce_scan_period_ms = DT_INST_PROP(index, debounce_scan_period_ms), \
|
||||||
.poll_period_ms = DT_INST_PROP(index, poll_period_ms), \
|
.poll_period_ms = DT_INST_PROP(index, poll_period_ms), \
|
||||||
.diode_direction = INST_DIODE_DIR(index), \
|
.diode_direction = INST_DIODE_DIR(index), \
|
||||||
}; \
|
}; \
|
||||||
|
|
|
@ -15,13 +15,26 @@ properties:
|
||||||
type: phandle-array
|
type: phandle-array
|
||||||
required: true
|
required: true
|
||||||
debounce-period:
|
debounce-period:
|
||||||
|
type: int
|
||||||
|
required: false
|
||||||
|
deprecated: true
|
||||||
|
description: Deprecated. Use debounce-press-ms and debounce-release-ms instead.
|
||||||
|
debounce-press-ms:
|
||||||
type: int
|
type: int
|
||||||
default: 5
|
default: 5
|
||||||
description: Debounce time in milliseconds
|
description: Debounce time for key press in milliseconds. Use 0 for eager debouncing.
|
||||||
|
debounce-release-ms:
|
||||||
|
type: int
|
||||||
|
default: 5
|
||||||
|
description: Debounce time for key release in milliseconds.
|
||||||
|
debounce-scan-period-ms:
|
||||||
|
type: int
|
||||||
|
default: 1
|
||||||
|
description: Time between reads in milliseconds when any key is pressed.
|
||||||
poll-period-ms:
|
poll-period-ms:
|
||||||
type: int
|
type: int
|
||||||
default: 10
|
default: 10
|
||||||
description: Time between reads in milliseconds when polling is enabled
|
description: Time between reads in milliseconds when no key is pressed and ZMK_KSCAN_MATRIX_POLLING is enabled.
|
||||||
diode-direction:
|
diode-direction:
|
||||||
type: string
|
type: string
|
||||||
default: row2col
|
default: row2col
|
||||||
|
|
100
docs/docs/features/debouncing.md
Normal file
100
docs/docs/features/debouncing.md
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
---
|
||||||
|
title: Debouncing
|
||||||
|
sidebar_label: Debouncing
|
||||||
|
---
|
||||||
|
|
||||||
|
To prevent contact bounce (also known as chatter) and noise spikes from causing
|
||||||
|
unwanted key presses, ZMK uses a [cycle-based debounce algorithm](https://www.kennethkuhn.com/electronics/debounce.c),
|
||||||
|
with each key debounced independently.
|
||||||
|
|
||||||
|
By default the debounce algorithm decides that a key is pressed or released after
|
||||||
|
the input is stable for 5 milliseconds. You can decrease this to improve latency
|
||||||
|
or increase it to improve reliability.
|
||||||
|
|
||||||
|
If you are having problems with a single key press registering multiple inputs,
|
||||||
|
you can try increasing the debounce press and/or release times to compensate.
|
||||||
|
You should also check for mechanical issues that might be causing the bouncing,
|
||||||
|
such as hot swap sockets that are making poor contact. You can try replacing the
|
||||||
|
socket or using some sharp tweezers to bend the contacts back together.
|
||||||
|
|
||||||
|
## Debounce Configuration
|
||||||
|
|
||||||
|
### Global Options
|
||||||
|
|
||||||
|
You can set these options in your `.conf` file to control debouncing globally.
|
||||||
|
Values must be <= 127.
|
||||||
|
|
||||||
|
- `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS`: Debounce time for key press in milliseconds. Default = 5.
|
||||||
|
- `CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS`: Debounce time for key release in milliseconds. Default = 5.
|
||||||
|
|
||||||
|
For example, this would shorten the debounce time for both press and release:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS=3
|
||||||
|
CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS=3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-driver Options
|
||||||
|
|
||||||
|
You can add these Devicetree properties to a kscan node to control debouncing for
|
||||||
|
that instance of the driver. Values must be <= 127.
|
||||||
|
|
||||||
|
- `debounce-press-ms`: Debounce time for key press in milliseconds. Default = 5.
|
||||||
|
- `debounce-release-ms`: Debounce time for key release in milliseconds. Default = 5.
|
||||||
|
- ~~`debounce-period`~~: Deprecated. Sets both press and release debounce times.
|
||||||
|
- `debounce-scan-period-ms`: Time between reads in milliseconds when any key is pressed. Default = 1.
|
||||||
|
|
||||||
|
If one of the global options described above is set, it overrides the corresponding
|
||||||
|
per-driver option.
|
||||||
|
|
||||||
|
For example, if your board/shield has a kscan driver labeled `kscan0` in its
|
||||||
|
`.overlay`, `.dts`, or `.dtsi` files,
|
||||||
|
|
||||||
|
```devicetree
|
||||||
|
kscan0: kscan {
|
||||||
|
compatible = "zmk,kscan-gpio-matrix";
|
||||||
|
...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
then you could add this to your `.keymap`:
|
||||||
|
|
||||||
|
```devicetree
|
||||||
|
&kscan0 {
|
||||||
|
debounce-press-ms = <3>;
|
||||||
|
debounce-release-ms = <3>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This must be placed outside of any blocks surrounded by curly braces (`{...}`).
|
||||||
|
|
||||||
|
`debounce-scan-period-ms` determines how often the keyboard scans while debouncing. It defaults to 1 ms, but it can be increased to reduce power use. Note that the debounce press/release timers are rounded up to the next multiple of the scan period. For example, if the scan period is 2 ms and debounce timer is 5 ms, key presses will take 6 ms to register instead of 5.
|
||||||
|
|
||||||
|
## Eager Debouncing
|
||||||
|
|
||||||
|
Eager debouncing means reporting a key change immediately and then ignoring
|
||||||
|
further changes for the debounce time. This eliminates latency but it is not
|
||||||
|
noise-resistant.
|
||||||
|
|
||||||
|
ZMK does not currently support true eager debouncing, but you can get something
|
||||||
|
very close by setting the time to detect a key press to zero and the time to detect
|
||||||
|
a key release to a larger number. This will detect a key press immediately, then
|
||||||
|
debounce the key release.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS=0
|
||||||
|
CONFIG_ZMK_KSCAN_DEBOUNCE_RELEASE_MS=5
|
||||||
|
```
|
||||||
|
|
||||||
|
Also consider setting `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS=1` instead, which adds
|
||||||
|
one millisecond of latency but protects against short noise spikes.
|
||||||
|
|
||||||
|
## Comparison With QMK
|
||||||
|
|
||||||
|
ZMK's default debouncing is similar to QMK's `sym_defer_pk` algorithm.
|
||||||
|
|
||||||
|
Setting `CONFIG_ZMK_KSCAN_DEBOUNCE_PRESS_MS=0` for eager debouncing would be similar
|
||||||
|
to QMK's (unimplemented as of this writing) `asym_eager_defer_pk`.
|
||||||
|
|
||||||
|
See [QMK's Debounce API documentation](https://beta.docs.qmk.fm/using-qmk/software-features/feature_debounce_type)
|
||||||
|
for more information.
|
|
@ -11,6 +11,7 @@ module.exports = {
|
||||||
Features: [
|
Features: [
|
||||||
"features/keymaps",
|
"features/keymaps",
|
||||||
"features/combos",
|
"features/combos",
|
||||||
|
"features/debouncing",
|
||||||
"features/displays",
|
"features/displays",
|
||||||
"features/encoders",
|
"features/encoders",
|
||||||
"features/underglow",
|
"features/underglow",
|
||||||
|
|
Loading…
Reference in a new issue