This commit is contained in:
Michael Skorokhodov 2026-02-04 04:01:27 -08:00 committed by GitHub
commit 715d8685cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 334 additions and 0 deletions

View File

@ -7,6 +7,7 @@ zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DRIVER kscan_gpio.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MATRIX kscan_gpio_matrix.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_MATRIX kscan_gpio_matrix.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_CHARLIEPLEX kscan_gpio_charlieplex.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_CHARLIEPLEX kscan_gpio_charlieplex.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DIRECT kscan_gpio_direct.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_HALL kscan_gpio_hall.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_GPIO_DEMUX kscan_gpio_demux.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER kscan_mock.c)
zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c) zephyr_library_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER kscan_composite.c)

View File

@ -5,6 +5,7 @@ DT_COMPAT_ZMK_KSCAN_COMPOSITE := zmk,kscan-composite
DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux DT_COMPAT_ZMK_KSCAN_GPIO_DEMUX := zmk,kscan-gpio-demux
DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct DT_COMPAT_ZMK_KSCAN_GPIO_DIRECT := zmk,kscan-gpio-direct
DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-matrix
DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX := zmk,kscan-gpio-hall
DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIEPLEX := zmk,kscan-gpio-charlieplex DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIEPLEX := zmk,kscan-gpio-charlieplex
DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock DT_COMPAT_ZMK_KSCAN_MOCK := zmk,kscan-mock
@ -42,6 +43,11 @@ config ZMK_KSCAN_GPIO_MATRIX
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX)) default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_MATRIX))
select ZMK_KSCAN_GPIO_DRIVER select ZMK_KSCAN_GPIO_DRIVER
config ZMK_KSCAN_GPIO_HALL
bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_HALL))
select ZMK_KSCAN_GPIO_DRIVER
config ZMK_KSCAN_GPIO_CHARLIEPLEX config ZMK_KSCAN_GPIO_CHARLIEPLEX
bool bool
default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIEPLEX)) default $(dt_compat_enabled,$(DT_COMPAT_ZMK_KSCAN_GPIO_CHARLIEPLEX))

View File

@ -0,0 +1,327 @@
/*
* Copyright (c) 2025 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/util.h>
#include <stdint.h>
#include <stdlib.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DT_DRV_COMPAT zmk_kscan_gpio_hall
#define EMA_SHIFT 4
#define INST_INPUTS_LEN(n) DT_INST_PROP_LEN(n, io_channels)
#define KSCAN_ADC_GET_BY_IDX(node_id, idx) \
((struct kscan_adc){.spec = ADC_DT_SPEC_GET_BY_IDX(node_id, idx), .index = idx})
#define KSCAN_ADC_HALL_INPUT_CFG_INIT(idx, inst_idx) \
KSCAN_ADC_GET_BY_IDX(DT_DRV_INST(inst_idx), idx)
#define KSCAN_ADC_LIST(adc_array) \
((struct kscan_adc_list){.adcs = adc_array, .len = ARRAY_SIZE(adc_array)})
struct kscan_adc {
struct adc_dt_spec spec;
struct adc_channel_cfg cfg;
struct adc_sequence as;
size_t index;
};
struct kscan_adc_list {
struct kscan_adc *adcs;
size_t len;
};
struct kscan_key_state {
int last_value;
int idle_value;
int max_value;
int min_value;
int range;
bool pressed : 1;
};
struct kscan_hall_data {
const struct device *dev;
struct kscan_adc_list inputs;
kscan_callback_t callback;
struct k_work_delayable work;
/** Timestamp of the current or scheduled scan. */
int64_t scan_time;
/** Current state of the inputs as an array of length config->inputs.len */
struct kscan_key_state *state;
};
struct kscan_hall_config {
int min_trigger_value;
int max_trigger_value;
int trigger_value;
int init_range;
int idle_steps;
};
static int16_t current_value;
int kscan_adc_read(const struct kscan_adc *adc) {
int err = adc_read(adc->spec.dev, &adc->as);
if (err != 0) {
LOG_ERR("Failed to read %s, channel: %i", adc->spec.dev->name, adc->spec.channel_id);
return err;
}
return 0;
}
static int kscan_hall_read(const struct device *dev) {
struct kscan_hall_data *data = dev->data;
const struct kscan_hall_config *config = dev->config;
for (int i = 0; i < data->inputs.len; i++) {
struct kscan_adc *adc = &data->inputs.adcs[i];
const int read_err = kscan_adc_read(adc);
if (read_err != 0) {
return read_err;
}
struct kscan_key_state *state = &data->state[adc->index];
if (state->max_value == 0) {
state->max_value = state->min_value = state->last_value = current_value + 1;
continue;
}
state->last_value += (current_value - state->last_value) >> EMA_SHIFT;
if (state->max_value < state->last_value) {
state->max_value = state->last_value + 1;
int t = state->max_value - state->idle_value;
if (state->range < t)
state->range = t;
}
if (state->min_value > state->last_value) {
state->min_value = state->last_value - 1;
int t = abs(state->min_value - state->idle_value);
if (state->range < t)
state->range = t;
}
int value = (abs(state->last_value - state->idle_value) / (float)state->range) * 100;
if (value >= config->max_trigger_value) {
continue;
}
if (value <= config->min_trigger_value) {
if (state->pressed) {
state->pressed = false;
LOG_DBG("Sending event at 0,%i state %s, value %i", adc->index,
state->pressed ? "on" : "off", value);
data->callback(dev, 0, adc->index, state->pressed);
}
continue;
}
bool key_state = state->pressed;
// Press
if (!state->pressed && value >= config->trigger_value) {
key_state = true;
}
if (state->pressed != key_state) {
state->pressed = key_state;
LOG_DBG("Sending event at 0,%i state %s, value %i.", adc->index,
state->pressed ? "on" : "off", value);
data->callback(dev, 0, adc->index, state->pressed);
}
}
k_work_reschedule(&data->work, K_TIMEOUT_ABS_MS(data->scan_time));
return 0;
}
static int kscan_hall_init_input_inst(struct kscan_adc *adc) {
struct adc_dt_spec *spec = &adc->spec;
// TODO: check spec->channel_cfg_dt_node_exists
if (!device_is_ready(spec->dev)) {
LOG_ERR("ADC is not ready: %s", spec->dev->name);
return -ENODEV;
}
#ifdef CONFIG_ADC_NRFX_SAADC
adc->cfg = (struct adc_channel_cfg){
.channel_id = spec->channel_id,
.reference = ADC_REF_INTERNAL,
.gain = ADC_GAIN_1_6,
// TODO: adjust relative to the power mode
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + spec->channel_id,
};
#endif
// TODO: channel_cfg_dt_node_exists
int err = adc_channel_setup(spec->dev, &adc->cfg);
if (err != 0) {
LOG_ERR("Unable to configure channel %u of device %s. Err: %i", spec->channel_id,
spec->dev->name, err);
return err;
}
// TODO: create per device (adc1, adc2, MCP3208, etc)
adc->as = (struct adc_sequence){
.channels = BIT(spec->channel_id),
.buffer = &current_value,
.buffer_size = sizeof(current_value),
.oversampling = 2,
.calibrate = true,
.resolution = 12,
};
LOG_DBG("%s: AIN%u setup returned %d", spec->dev->name, spec->channel_id, err);
return 0;
}
static int kscan_hall_init_idle_value(const struct kscan_adc *adc, struct kscan_key_state *state,
int steps) {
int32_t sum = 1;
for (int i = 0; i < steps; i++) {
int err = kscan_adc_read(adc);
if (err) {
return err;
}
sum += current_value;
k_sleep(K_MSEC(5));
}
state->idle_value = sum / steps;
return 0;
}
static int kscan_hall_init_inputs(const struct device *dev) {
const struct kscan_hall_data *data = dev->data;
const struct kscan_hall_config *config = dev->config;
for (int i = 0; i < data->inputs.len; i++) {
struct kscan_adc *adc = &data->inputs.adcs[i];
struct kscan_key_state *state = &data->state[i];
int err = kscan_hall_init_input_inst(adc);
if (err) {
return err;
}
err = kscan_hall_init_idle_value(adc, state, config->idle_steps);
if (err) {
return err;
}
// Disable calibration. No further need.
adc->as.calibrate = false;
state->range = config->init_range;
}
return 0;
}
static void kscan_hall_work_handler(struct k_work *work) {
struct k_work_delayable *dwork = CONTAINER_OF(work, struct k_work_delayable, work);
struct kscan_hall_data *data = CONTAINER_OF(dwork, struct kscan_hall_data, work);
kscan_hall_read(data->dev);
}
static int compare_ports2(const void *a, const void *b) {
const struct kscan_adc *adc_a = a;
const struct kscan_adc *adc_b = b;
return (adc_a->spec.dev + adc_a->spec.channel_id) - (adc_b->spec.dev + adc_b->spec.channel_id);
}
void kscan_adc_list_sort_by_port(struct kscan_adc_list *list) {
qsort(list->adcs, list->len, sizeof(list->adcs[0]), compare_ports2);
}
static int kscan_hall_init(const struct device *dev) {
struct kscan_hall_data *data = dev->data;
data->dev = dev;
// Sort inputs by port so we can read each port just once per scan.
kscan_adc_list_sort_by_port(&data->inputs);
k_work_init_delayable(&data->work, kscan_hall_work_handler);
kscan_hall_init_inputs(dev);
return 0;
}
static int kscan_hall_configure(const struct device *dev, kscan_callback_t callback) {
struct kscan_hall_data *data = dev->data;
if (!callback) {
return -EINVAL;
}
data->callback = callback;
return 0;
}
static int kscan_hall_enable(const struct device *dev) {
struct kscan_hall_data *data = dev->data;
data->scan_time = k_uptime_get();
return kscan_hall_read(dev);
}
static int kscan_hall_disable(const struct device *dev) {
struct kscan_hall_data *data = dev->data;
k_work_cancel_delayable(&data->work);
return 0;
}
static const struct kscan_driver_api kscan_hall_api = {
.config = kscan_hall_configure,
.enable_callback = kscan_hall_enable,
.disable_callback = kscan_hall_disable,
};
#define KSCAN_HALL_INIT(n) \
static struct kscan_adc kscan_hall_inputs_##n[] = { \
LISTIFY(INST_INPUTS_LEN(n), KSCAN_ADC_HALL_INPUT_CFG_INIT, (, ), n)}; \
\
static struct kscan_key_state kscan_hall_state_##n[INST_INPUTS_LEN(n)]; \
\
static struct kscan_hall_data kscan_hall_data_##n = { \
.inputs = KSCAN_ADC_LIST(kscan_hall_inputs_##n), \
.state = kscan_hall_state_##n, \
}; \
\
static const struct kscan_hall_config kscan_hall_config_##n = { \
.min_trigger_value = DT_INST_PROP(n, min_trigger_value), \
.max_trigger_value = DT_INST_PROP(n, max_trigger_value), \
.trigger_value = DT_INST_PROP(n, trigger_value), \
.init_range = DT_INST_PROP(n, init_range), \
.idle_steps = DT_INST_PROP(n, idle_steps), \
}; \
DEVICE_DT_INST_DEFINE(n, &kscan_hall_init, PM_DEVICE_DT_INST_GET(n), &kscan_hall_data_##n, \
&kscan_hall_config_##n, POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, \
&kscan_hall_api);
DT_INST_FOREACH_STATUS_OKAY(KSCAN_HALL_INIT);