From 8f72457ba3ac19af2a1635b8a0334146831f475c Mon Sep 17 00:00:00 2001 From: Andrew Richards Date: Wed, 31 Aug 2022 23:01:24 +0100 Subject: [PATCH] First draft PR for analogue joystick support --- app/drivers/sensor/CMakeLists.txt | 3 +- app/drivers/sensor/Kconfig | 3 +- app/drivers/sensor/joystick/CMakeLists.txt | 8 + app/drivers/sensor/joystick/Kconfig | 9 + app/drivers/sensor/joystick/joystick.c | 234 +++++++++++++++++++++ app/drivers/sensor/joystick/joystick.h | 50 +++++ 6 files changed, 305 insertions(+), 2 deletions(-) create mode 100644 app/drivers/sensor/joystick/CMakeLists.txt create mode 100644 app/drivers/sensor/joystick/Kconfig create mode 100644 app/drivers/sensor/joystick/joystick.c create mode 100644 app/drivers/sensor/joystick/joystick.h diff --git a/app/drivers/sensor/CMakeLists.txt b/app/drivers/sensor/CMakeLists.txt index b549320f..37d25a36 100644 --- a/app/drivers/sensor/CMakeLists.txt +++ b/app/drivers/sensor/CMakeLists.txt @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT add_subdirectory_ifdef(CONFIG_ZMK_BATTERY battery) -add_subdirectory_ifdef(CONFIG_EC11 ec11) \ No newline at end of file +add_subdirectory_ifdef(CONFIG_EC11 ec11) +add_subdirectory_ifdef(CONFIG_JOYSTICK joystick) diff --git a/app/drivers/sensor/Kconfig b/app/drivers/sensor/Kconfig index a828f6c6..d6d36e25 100644 --- a/app/drivers/sensor/Kconfig +++ b/app/drivers/sensor/Kconfig @@ -2,4 +2,5 @@ # SPDX-License-Identifier: MIT rsource "battery/Kconfig" -rsource "ec11/Kconfig" \ No newline at end of file +rsource "ec11/Kconfig" +rsource "joystick/Kconfig" diff --git a/app/drivers/sensor/joystick/CMakeLists.txt b/app/drivers/sensor/joystick/CMakeLists.txt new file mode 100644 index 00000000..80698d35 --- /dev/null +++ b/app/drivers/sensor/joystick/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +zephyr_include_directories(.) + +zephyr_library() + +zephyr_library_sources(joystick.c) diff --git a/app/drivers/sensor/joystick/Kconfig b/app/drivers/sensor/joystick/Kconfig new file mode 100644 index 00000000..d57608cb --- /dev/null +++ b/app/drivers/sensor/joystick/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2020 The ZMK Contributors +# SPDX-License-Identifier: MIT + +menuconfig JOYSTICK + bool "JOYSTICK Analogue Sensor" + select ADC + help + Enable driver for Joystick analogue sensors. + diff --git a/app/drivers/sensor/joystick/joystick.c b/app/drivers/sensor/joystick/joystick.c new file mode 100644 index 00000000..471f5c90 --- /dev/null +++ b/app/drivers/sensor/joystick/joystick.c @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT joystick + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../../include/drivers/ext_power.h" + +#include "joystick.h" + +static void zmk_joy_work(struct k_work *work); + +LOG_MODULE_REGISTER(JOYSTICK, CONFIG_ZMK_LOG_LEVEL); + +static const struct device *ext_power; + +static int joy_get_state(const struct device *dev) { + struct joy_data *drv_data = dev->data; + const struct joy_config *drv_cfg = dev->config; + struct adc_sequence *as = &drv_data->as; + + int disable_power = 0; + + if (drv_data->adc==NULL) + return 0; + + if (ext_power != NULL) { + int power = ext_power_get(ext_power); + if (!power) { + // power is off but must be turned on for ADC + int rc = ext_power_enable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to enable EXT_POWER: %d", rc); + } + disable_power = 1; + } + } + + int rc = adc_read(drv_data->adc, as); + as->calibrate = false; + + if (disable_power) { + int rc = ext_power_disable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to disable EXT_POWER: %d", rc); + } + } + + if (rc == 0) { + int32_t val = drv_data->adc_raw; + if (val > 4096) + val = 4096; + return val; + } else { + LOG_DBG("Joy failed to read ADC: %d", rc); + return 0; + } +} + +static int joy_sample_fetch(const struct device *dev, enum sensor_channel chan) { + struct joy_data *drv_data = dev->data; + const struct joy_config *drv_cfg = dev->config; + int val; + + val = joy_get_state(dev); + + val -= drv_data->zero_value; + if (drv_cfg->reverse) { + val = -val; + } + drv_data->delta = val - drv_data->value; + drv_data->value = val; + + if (abs (val) >= drv_cfg->min_on) { + LOG_DBG ("Joystick chan: %d = %d", drv_cfg->io_channel, val); + } + + return 0; +} + +static int joy_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) { + struct joy_data *drv_data = dev->data; + const struct joy_config *drv_cfg = dev->config; + + int value = drv_data->value; + + if (chan == SENSOR_CHAN_ROTATION) { + val->val1 = 0; + val->val2 = 0; + if (value >= drv_data->last_rotate + drv_cfg->resolution) { + drv_data->last_rotate += drv_cfg->resolution; + val->val1 = 1; + } else if (value <= drv_data->last_rotate - drv_cfg->resolution) { + drv_data->last_rotate -= drv_cfg->resolution; + val->val1 = -1; + } + return 0; + } else if (chan == SENSOR_CHAN_PRESS) { + val->val1 = 0; // calibration adjusted + val->val2 = value; // raw value + if (value >= drv_cfg->min_on) { + val->val1 = 1 + value - drv_cfg->min_on; + } else if (value <= -drv_cfg->min_on) { + val->val1 = -1 + value + drv_cfg->min_on; + } + return 0; + } + + return -ENOTSUP; +} + +static void zmk_joy_work(struct k_work *work) { + struct joy_data *drv_data = CONTAINER_OF(work, struct joy_data, work); + + if (drv_data->setup) { + int rc = joy_sample_fetch (drv_data->dev, 0); // I think this might be unnecessary + if (rc != 0) { + LOG_DBG("Failed to update joystick value: %d.", rc); + } + drv_data->handler (drv_data->dev, drv_data->trigger); + } +} + +static void zmk_joy_timer(struct k_timer *timer) { + const struct device *dev = k_timer_user_data_get(timer); + struct joy_data *drv_data = CONTAINER_OF(timer, struct joy_data, timer); + k_work_submit(&drv_data->work); +} + +int joy_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) { + struct joy_data *drv_data = dev->data; + const struct joy_config *drv_cfg = dev->config; + + k_timer_stop (&drv_data->timer); + + drv_data->trigger = trig; + drv_data->handler = handler; + + k_work_init (&drv_data->work, zmk_joy_work); + k_timer_init (&drv_data->timer, zmk_joy_timer, NULL); + k_timer_user_data_set (&drv_data->timer, dev); + k_timer_start(&drv_data->timer, K_MSEC(1000/drv_cfg->frequency), K_MSEC(1000/drv_cfg->frequency)); + + return 0; +} + + +static const struct sensor_driver_api joy_driver_api = { + .trigger_set = joy_trigger_set, + .sample_fetch = joy_sample_fetch, + .channel_get = joy_channel_get, +}; + + +int joy_init(const struct device *dev) { + struct joy_data *drv_data = dev->data; + const struct joy_config *drv_cfg = dev->config; + + drv_data->dev = dev; + drv_data->setup = false; + drv_data->adc = drv_cfg->adc; + if (drv_data->adc == NULL) { + LOG_ERR("Joy: Failed to get pointer to ADC device"); + return -EINVAL; + } + + drv_data->as = (struct adc_sequence){ + .channels = BIT(drv_cfg->io_channel+1), /* Has to be channel +1 because channel 0 is used for the battery */ + .buffer = &drv_data->adc_raw, + .buffer_size = sizeof(drv_data->adc_raw), + .oversampling = 4, + .calibrate = true, + }; + +#ifdef CONFIG_ADC_NRFX_SAADC + drv_data->acc = (struct adc_channel_cfg){ + .gain = ADC_GAIN_1_4, + .reference = ADC_REF_VDD_1_4, + .acquisition_time = ADC_ACQ_TIME_DEFAULT, + .input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + drv_cfg->io_channel, + .channel_id = drv_cfg->io_channel+1 + }; + + drv_data->as.resolution = 12; +#else +#error Unsupported ADC +#endif + + int rc = adc_channel_setup(drv_data->adc, &drv_data->acc); + LOG_DBG("Joy AIN%u setup returned %d", drv_cfg->io_channel, rc); + + ext_power = device_get_binding("EXT_POWER"); + if (ext_power == NULL) { + LOG_ERR("Unable to retrieve ext_power device: EXT_POWER"); + } + + drv_data->setup = true; + + drv_data->zero_value = drv_data->value = joy_get_state(dev); + drv_data->delta = 0; + drv_data->last_rotate = 0; + + return 0; +} + +#define JOY_INST(n) \ + struct joy_data joy_data_##n; \ + const struct joy_config joy_cfg_##n = { \ + .io_channel = DT_INST_IO_CHANNELS_INPUT(n), \ + .adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(DT_DRV_INST(n))), \ + COND_CODE_0(DT_INST_NODE_HAS_PROP(n, resolution), (1), (DT_INST_PROP(n, resolution))), \ + COND_CODE_0(DT_INST_NODE_HAS_PROP(n, min_on), (1), (DT_INST_PROP(n, min_on))), \ + COND_CODE_0(DT_INST_NODE_HAS_PROP(n, frequency), (1), (DT_INST_PROP(n, frequency))), \ + COND_CODE_0(DT_INST_NODE_HAS_PROP(n, reverse), (1), (DT_INST_PROP(n, reverse))), \ + }; \ + DEVICE_DT_INST_DEFINE(n, joy_init, device_pm_control_nop, &joy_data_##n, &joy_cfg_##n, \ + POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &joy_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(JOY_INST) + diff --git a/app/drivers/sensor/joystick/joystick.h b/app/drivers/sensor/joystick/joystick.h new file mode 100644 index 00000000..71c1a7c8 --- /dev/null +++ b/app/drivers/sensor/joystick/joystick.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include + +struct io_channel_config { + const char *label; + uint8_t channel; +}; + +struct joy_config { + int io_channel; + const struct device *adc; + int resolution; + int min_on; + int frequency; + bool reverse; +}; + +struct joy_data { + const struct device *adc; + int setup; + + struct adc_channel_cfg acc; + struct adc_sequence as; + uint16_t adc_raw; + + int zero_value; + int value; + int delta; + int last_rotate; + int last_press; + + const struct device *dev; + + sensor_trigger_handler_t handler; + const struct sensor_trigger *trigger; + + struct k_timer timer; + struct k_work work; + +}; +