diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk index fbaf087ad146..76791a22dcfd 100644 --- a/builddefs/common_features.mk +++ b/builddefs/common_features.mk @@ -905,6 +905,10 @@ ifeq ($(strip $(ENCODER_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 9c8695862554..2b7c6778b3ce 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 26b437b51335..070c5f716f98 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 02fc2bee9de6..f1a6beb2c681 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 9fc455530c68..7699f3a23529 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -417,6 +417,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 722c5f9c5dd6..e8849b0c16fe 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 000000000000..2c21599050e2 --- /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 >> 3)); + } + 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 >> 3)); + return false; +} +``` diff --git a/docs/reference_info_json.md b/docs/reference_info_json.md index 4a70a4bb6f94..9bb517f5b1f8 100644 --- a/docs/reference_info_json.md +++ b/docs/reference_info_json.md @@ -478,6 +478,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 d263ff4abf17..9892180efbf3 100644 --- a/keyboards/gmmk/numpad/config.h +++ b/keyboards/gmmk/numpad/config.h @@ -17,7 +17,7 @@ #pragma once -#define SLIDER_PIN B0 +#define POTENTIOMETER_PINS { B0 } #define MIDI_ADVANCED #define LOCKING_SUPPORT_ENABLE diff --git a/keyboards/gmmk/numpad/keymaps/default/keymap.c b/keyboards/gmmk/numpad/keymaps/default/keymap.c index aa1829cdd419..1f274103724a 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 12f124894dbc..5683813b81f6 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 5cdb34c7bd27..1dec7291acd4 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; +} \ No newline at end of file diff --git a/keyboards/gmmk/numpad/rules.mk b/keyboards/gmmk/numpad/rules.mk index d289eb81a4d1..5815083a3415 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/quantum/keyboard.c b/quantum/keyboard.c index 86a1a9fea3bf..f54a8dc161a0 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 @@ -669,6 +672,12 @@ void keyboard_task(void) { } #endif +#ifdef POTENTIOMETER_ENABLE + if (potentiometer_task()) { + 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 000000000000..ab7596628d36 --- /dev/null +++ b/quantum/potentiometer.c @@ -0,0 +1,90 @@ +// 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 +#ifndef POTENTIOMETER_ADC_MIN_VALUE +# define POTENTIOMETER_ADC_MIN_VALUE 0 +#endif +#ifndef POTENTIOMETER_ADC_MAX_VALUE +# define POTENTIOMETER_ADC_MAX_VALUE (1 << 10) +#endif + +static pin_t potentiometer_pads[] = POTENTIOMETER_PINS; +#define NUM_POTENTIOMETERS (ARRAY_SIZE(potentiometer_pads)) + +__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); +} + +__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; +} + +__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_ADC_MIN_VALUE; + uint32_t max = POTENTIOMETER_ADC_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 bool potentiometer_state[NUM_POTENTIOMETERS] = {UINT16_MAX}; + + if (value == potentiometer_state[index]) { + return false; + } + + potentiometer_state[index] = value; + return true; +} + +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 = analogReadPin(potentiometer_pads[index]); + uint16_t value = potentiometer_map(index, raw); + if (potentiometer_filter(index, value)) { + changed |= true; + + potentiometer_update_kb(index, value); + } + } + + return changed; +} \ No newline at end of file diff --git a/quantum/potentiometer.h b/quantum/potentiometer.h new file mode 100644 index 000000000000..e4f550726fb3 --- /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 e41542e4afc0..be8e1ddcb584 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