Skip to content

Commit

Permalink
feat(kscan): Improve matrix debouncing
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
joelspadin authored and petejohanson committed Oct 27, 2021
1 parent 5cc7c28 commit f946dc6
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 64 deletions.
1 change: 1 addition & 0 deletions app/drivers/kscan/CMakeLists.txt
Expand Up @@ -4,6 +4,7 @@
zephyr_library_named(zmk__drivers__kscan)
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_direct.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio_demux.c)
Expand Down
18 changes: 18 additions & 0 deletions app/drivers/kscan/Kconfig
Expand Up @@ -14,6 +14,24 @@ config ZMK_KSCAN_MATRIX_POLLING
config ZMK_KSCAN_DIRECT_POLLING
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

config ZMK_KSCAN_INIT_PRIORITY
Expand Down
62 changes: 62 additions & 0 deletions app/drivers/kscan/debounce.c
@@ -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 changes: 56 additions & 0 deletions app/drivers/kscan/debounce.h
@@ -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);

0 comments on commit f946dc6

Please sign in to comment.