diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index 5e93480e4d0..28f11260d3a 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -914,6 +914,10 @@ ifeq ($(strip $(DIP_SWITCH_ENABLE)), yes) endif endif +ifeq ($(strip $(POTENTIOMETER_ENABLE)), yes) + ANALOG_DRIVER_REQUIRED = yes +endif + VALID_WS2812_DRIVER_TYPES := bitbang custom i2c pwm spi vendor WS2812_DRIVER ?= bitbang diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index 9c869586255..2b7c6778b3c 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -40,6 +40,7 @@ GENERIC_FEATURES = \ MOUSEKEY \ MUSIC \ OS_DETECTION \ + POTENTIOMETER \ PROGRAMMABLE_BUTTON \ REPEAT_KEY \ SECURE \ diff --git a/data/mappings/info_config.hjson b/data/mappings/info_config.hjson index 26b437b5133..070c5f716f9 100644 --- a/data/mappings/info_config.hjson +++ b/data/mappings/info_config.hjson @@ -117,6 +117,9 @@ "ONESHOT_TIMEOUT": {"info_key": "oneshot.timeout", "value_type": "int"}, "ONESHOT_TAP_TOGGLE": {"info_key": "oneshot.tap_toggle", "value_type": "int"}, + // Potentiometer + "POTENTIOMETER_PINS": {"info_key": "potentiometer.pins", "value_type": "array"}, + // PS/2 "PS2_CLOCK_PIN": {"info_key": "ps2.clock_pin"}, "PS2_DATA_PIN": {"info_key": "ps2.data_pin"}, diff --git a/data/mappings/info_rules.hjson b/data/mappings/info_rules.hjson index 02fc2bee9de..f1a6beb2c68 100644 --- a/data/mappings/info_rules.hjson +++ b/data/mappings/info_rules.hjson @@ -32,6 +32,7 @@ "NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"}, "PIN_COMPATIBLE": {"info_key": "pin_compatible"}, "PLATFORM_KEY": {"info_key": "platform_key", "to_json": false}, + "POTENTIOMETER_ENABLE": {"info_key": "potentiometer.enabled", "value_type": "bool"}, "PS2_DRIVER": {"info_key": "ps2.driver"}, "PS2_ENABLE": {"info_key": "ps2.enabled", "value_type": "bool"}, "PS2_MOUSE_ENABLE": {"info_key": "ps2.mouse_enabled", "value_type": "bool"}, diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 32d737a1d87..9464fbc252d 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -418,6 +418,13 @@ "timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"} } }, + "potentiometer": { + "type": "object", + "properties": { + "enabled": {"type": "boolean"}, + "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"} + } + }, "led_matrix": { "type": "object", "properties": { diff --git a/docs/_summary.md b/docs/_summary.md index bae93da5b6c..334451a9a78 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -123,6 +123,7 @@ * [LED Indicators](feature_led_indicators.md) * [MIDI](feature_midi.md) * [Pointing Device](feature_pointing_device.md) + * [Potentiometer](feature_potentiometers.md) * [PS/2 Mouse](feature_ps2_mouse.md) * [Split Keyboard](feature_split_keyboard.md) * [Stenography](feature_stenography.md) diff --git a/docs/feature_potentiometers.md b/docs/feature_potentiometers.md new file mode 100644 index 00000000000..de2b81fb624 --- /dev/null +++ b/docs/feature_potentiometers.md @@ -0,0 +1,35 @@ +# Potentiometers + +Add this to your `rules.mk`: + +```make +POTENTIOMETER_ENABLE = yes +``` + +and this to your `config.h`: + +```c +#define POTENTIOMETER_PINS { B0 } +``` + +## Callbacks + +The callback functions can be inserted into your `.c`: + +```c +bool potentiometer_update_kb(uint8_t index, uint16_t value) { + if (!potentiometer_update_user(index, value)) { + midi_send_cc(&midi_device, 2, 0x3E, 0x7F + value); + } + return true; +} +``` + +or `keymap.c`: + +```c +bool potentiometer_update_user(uint8_t index, uint16_t value) { + midi_send_cc(&midi_device, 2, 0x3E, 0x7F + value); + return false; +} +``` diff --git a/docs/reference_info_json.md b/docs/reference_info_json.md index e102b9bfb94..89b942497f7 100644 --- a/docs/reference_info_json.md +++ b/docs/reference_info_json.md @@ -480,6 +480,17 @@ Configures [One Shot keys](one_shot_keys.md). * `timeout` * The amount of time before the key is released in milliseconds. +## Potentiometer :id=potentiometer + +Configures the [Potentiometer](feature_Potentiometers.md) feature. + +* `potentiometer` + * `enabled` + * Enable the Potentiometer feature. + * Default: `false` + * `pins` (Required) + * The GPIO pin(s) connected to the Potentiometer(s). + ## PS/2 :id=ps2 Configures the [PS/2](feature_ps2_mouse.md) feature. diff --git a/keyboards/gmmk/numpad/config.h b/keyboards/gmmk/numpad/config.h index 3627ab503c8..8c0d6470686 100644 --- a/keyboards/gmmk/numpad/config.h +++ b/keyboards/gmmk/numpad/config.h @@ -17,7 +17,6 @@ #pragma once -#define SLIDER_PIN B0 #define MIDI_ADVANCED #define LOCKING_SUPPORT_ENABLE diff --git a/keyboards/gmmk/numpad/info.json b/keyboards/gmmk/numpad/info.json index 83f7d840dcb..e7069abc853 100644 --- a/keyboards/gmmk/numpad/info.json +++ b/keyboards/gmmk/numpad/info.json @@ -20,6 +20,9 @@ {"pin_a": "A2", "pin_b": "A1"} ] }, + "potentiometer": { + "pins": ["B0"] + }, "rgb_matrix": { "driver": "aw20216s" }, diff --git a/keyboards/gmmk/numpad/keymaps/default/keymap.c b/keyboards/gmmk/numpad/keymaps/default/keymap.c index aa1829cdd41..1f274103724 100644 --- a/keyboards/gmmk/numpad/keymaps/default/keymap.c +++ b/keyboards/gmmk/numpad/keymaps/default/keymap.c @@ -16,8 +16,6 @@ along with this program. If not, see . */ #include QMK_KEYBOARD_H -#include "analog.h" -#include "qmk_midi.h" // clang-format off const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { @@ -43,18 +41,3 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { RGB_TOG, QK_BOOT ) }; - -// Potentiometer Slider, MIDI Control - -uint8_t divisor = 0; - -void slider(void) { - if (divisor++) { /* only run the slider function 1/256 times it's called */ - return; - } - midi_send_cc(&midi_device, 2, 0x3E, 0x7F + (analogReadPin(SLIDER_PIN) >> 3)); -} - -void housekeeping_task_user(void) { - slider(); -} \ No newline at end of file diff --git a/keyboards/gmmk/numpad/keymaps/via/keymap.c b/keyboards/gmmk/numpad/keymaps/via/keymap.c index 12f124894db..5683813b81f 100644 --- a/keyboards/gmmk/numpad/keymaps/via/keymap.c +++ b/keyboards/gmmk/numpad/keymaps/via/keymap.c @@ -13,8 +13,6 @@ along with this program. If not, see . */ #include QMK_KEYBOARD_H -#include "analog.h" -#include "qmk_midi.h" // clang-format off const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { @@ -62,18 +60,3 @@ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = { [2] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU) }, [3] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU) } }; - -// Potentiometer Slider, MIDI Control - -uint8_t divisor = 0; - -void slider(void) { - if (divisor++) { /* only run the slider function 1/256 times it's called */ - return; - } - midi_send_cc(&midi_device, 2, 0x3E, 0x7F + (analogReadPin(SLIDER_PIN) >> 3)); -} - -void housekeeping_task_user(void) { - slider(); -} \ No newline at end of file diff --git a/keyboards/gmmk/numpad/numpad.c b/keyboards/gmmk/numpad/numpad.c index 5cdb34c7bd2..c754a255b66 100644 --- a/keyboards/gmmk/numpad/numpad.c +++ b/keyboards/gmmk/numpad/numpad.c @@ -16,6 +16,7 @@ */ #include "quantum.h" +#include "qmk_midi.h" #ifdef RGB_MATRIX_ENABLE @@ -117,3 +118,10 @@ void keyboard_pre_init_user(void) { # endif #endif + +bool potentiometer_update_kb(uint8_t index, uint16_t value) { + if (!potentiometer_update_user(index, value)) { + midi_send_cc(&midi_device, 2, 0x3E, 0x7F + value); + } + return true; +} diff --git a/keyboards/gmmk/numpad/rules.mk b/keyboards/gmmk/numpad/rules.mk index d289eb81a4d..5815083a341 100644 --- a/keyboards/gmmk/numpad/rules.mk +++ b/keyboards/gmmk/numpad/rules.mk @@ -13,11 +13,10 @@ AUDIO_ENABLE = no # Audio output ENCODER_ENABLE = yes KEYBOARD_SHARED_EP = yes MIDI_ENABLE = yes +POTENTIOMETER_ENABLE = yes RGB_MATRIX_ENABLE = yes LTO_ENABLE = yes -ANALOG_DRIVER_REQUIRED = yes - SRC += matrix.c diff --git a/keyboards/handwired/onekey/keymaps/potentiometer/config.h b/keyboards/handwired/onekey/keymaps/potentiometer/config.h new file mode 100644 index 00000000000..5b6f987502e --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/potentiometer/config.h @@ -0,0 +1,6 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +// TODO: Remove reuse of pin +#define POTENTIOMETER_PINS { ADC_PIN } diff --git a/keyboards/handwired/onekey/keymaps/potentiometer/keymap.c b/keyboards/handwired/onekey/keymaps/potentiometer/keymap.c new file mode 100644 index 00000000000..71b8cbb4dee --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/potentiometer/keymap.c @@ -0,0 +1,16 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include QMK_KEYBOARD_H + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + LAYOUT_ortho_1x1(KC_A) +}; + +bool potentiometer_update_user(uint8_t index, uint16_t value) { + uprintf("ADC:%u\n", value); + return false; +} + +void keyboard_post_init_user(void) { + debug_enable=true; +} diff --git a/keyboards/handwired/onekey/keymaps/potentiometer/rules.mk b/keyboards/handwired/onekey/keymaps/potentiometer/rules.mk new file mode 100644 index 00000000000..7a559f2af85 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/potentiometer/rules.mk @@ -0,0 +1,3 @@ +CONSOLE_ENABLE = yes + +POTENTIOMETER_ENABLE = yes diff --git a/quantum/keyboard.c b/quantum/keyboard.c index b5fa1a6e2e9..2bdf906a4f1 100644 --- a/quantum/keyboard.c +++ b/quantum/keyboard.c @@ -60,6 +60,9 @@ along with this program. If not, see . #ifdef ENCODER_ENABLE # include "encoder.h" #endif +#ifdef POTENTIOMETER_ENABLE +# include "potentiometer.h" +#endif #ifdef HAPTIC_ENABLE # include "haptic.h" #endif @@ -143,6 +146,9 @@ uint32_t last_input_activity_time(void) { uint32_t last_input_activity_elapsed(void) { return sync_timer_elapsed32(last_input_modification_time); } +void last_input_activity_trigger(void) { + last_input_modification_time = sync_timer_read32(); +} static uint32_t last_matrix_modification_time = 0; uint32_t last_matrix_activity_time(void) { @@ -440,6 +446,9 @@ void keyboard_init(void) { #ifdef DIP_SWITCH_ENABLE dip_switch_init(); #endif +#ifdef POTENTIOMETER_ENABLE + potentiometer_init(); +#endif #ifdef SLEEP_LED_ENABLE sleep_led_init(); #endif @@ -669,6 +678,13 @@ void keyboard_task(void) { } #endif +#ifdef POTENTIOMETER_ENABLE + if (potentiometer_task()) { + last_input_activity_trigger(); + activity_has_occurred = true; + } +#endif + #ifdef POINTING_DEVICE_ENABLE if (pointing_device_task()) { last_pointing_device_activity_trigger(); diff --git a/quantum/potentiometer.c b/quantum/potentiometer.c new file mode 100644 index 00000000000..8faa7f6be9d --- /dev/null +++ b/quantum/potentiometer.c @@ -0,0 +1,113 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include "potentiometer.h" +#include "gpio.h" +#include "util.h" +#include "timer.h" +#include "analog.h" + +#ifndef POTENTIOMETER_THROTTLE_MS +# define POTENTIOMETER_THROTTLE_MS 1 +#endif + +#ifndef POTENTIOMETER_OUTPUT_MIN_VALUE +# define POTENTIOMETER_OUTPUT_MIN_VALUE 0 +#endif +#ifndef POTENTIOMETER_OUTPUT_MAX_VALUE +# define POTENTIOMETER_OUTPUT_MAX_VALUE 127 +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ADC Driver + +// Matches default adc range +#ifndef POTENTIOMETER_RAW_MIN_VALUE +# define POTENTIOMETER_RAW_MIN_VALUE 0 +#endif +#ifndef POTENTIOMETER_RAW_MAX_VALUE +# define POTENTIOMETER_RAW_MAX_VALUE (1 << 10) +#endif + +static pin_t potentiometer_pads[] = POTENTIOMETER_PINS; +#define NUM_POTENTIOMETERS (ARRAY_SIZE(potentiometer_pads)) + +__attribute__((weak)) void potentiometer_driver_init(void) { + // do nothing +} + +__attribute__((weak)) uint16_t potentiometer_driver_sample(uint8_t index) { + return analogReadPin(potentiometer_pads[index]); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Callbacks + +__attribute__((weak)) bool potentiometer_update_user(uint8_t index, uint16_t value) { + return true; +} + +__attribute__((weak)) bool potentiometer_update_kb(uint8_t index, uint16_t value) { + return potentiometer_update_user(index, value); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Default scanning routine + +__attribute__((weak)) uint16_t potentiometer_map(uint8_t index, uint16_t value) { + (void)index; + + uint32_t a = POTENTIOMETER_OUTPUT_MIN_VALUE; + uint32_t b = POTENTIOMETER_OUTPUT_MAX_VALUE; + uint32_t min = POTENTIOMETER_RAW_MIN_VALUE; + uint32_t max = POTENTIOMETER_RAW_MAX_VALUE; + + // Scale value to min/max using the adc range + return ((b - a) * (value - min) / (max - min)) + a; +} + +__attribute__((weak)) bool potentiometer_filter(uint8_t index, uint16_t value) { + // ADC currently limited to max of 12 bits - init to max 16 ensures + // we can correctly capture even a raw first sample at max adc bounds + static uint16_t potentiometer_state[NUM_POTENTIOMETERS] = {UINT16_MAX}; + + if (value == potentiometer_state[index]) { + return false; + } + + potentiometer_state[index] = value; + return true; +} + +__attribute__((weak)) bool potentiometer_throttle_task(void) { +#if (POTENTIOMETER_THROTTLE_MS > 0) + static uint32_t last_exec = 0; + if (timer_elapsed32(last_exec) < POTENTIOMETER_THROTTLE_MS) { + return false; + } + last_exec = timer_read32(); +#endif + return true; +} + +void potentiometer_init(void) { + potentiometer_driver_init(); +} + +bool potentiometer_task(void) { + if (!potentiometer_throttle_task()) { + return false; + } + + bool changed = false; + for (uint8_t index = 0; index < NUM_POTENTIOMETERS; index++) { + uint16_t raw = potentiometer_driver_sample(index); + uint16_t value = potentiometer_map(index, raw); + if (potentiometer_filter(index, value)) { + changed |= true; + + potentiometer_update_kb(index, value); + } + } + + return changed; +} diff --git a/quantum/potentiometer.h b/quantum/potentiometer.h new file mode 100644 index 00000000000..e4f550726fb --- /dev/null +++ b/quantum/potentiometer.h @@ -0,0 +1,18 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include + +/** \brief user hook called when sampled value has changed + */ +bool potentiometer_update_user(uint8_t index, uint16_t value); + +/** \brief keyboard hook called when sampled value has changed + */ +bool potentiometer_update_kb(uint8_t index, uint16_t value); + +/** \brief Handle various subsystem background tasks + */ +bool potentiometer_task(void); diff --git a/quantum/quantum.h b/quantum/quantum.h index 996e93a12fc..56c3b8473f9 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -207,6 +207,10 @@ extern layer_state_t layer_state; # include "encoder.h" #endif +#ifdef POTENTIOMETER_ENABLE +# include "potentiometer.h" +#endif + #ifdef POINTING_DEVICE_ENABLE # include "pointing_device.h" #endif