Browse Source

Initial potentiometer support

zvecr 2 years ago
parent
commit
929da00c2f

+ 4 - 0
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

+ 1 - 0
builddefs/generic_features.mk

@@ -40,6 +40,7 @@ GENERIC_FEATURES = \
     MOUSEKEY \
     MUSIC \
     OS_DETECTION \
+    POTENTIOMETER \
     PROGRAMMABLE_BUTTON \
     REPEAT_KEY \
     SECURE \

+ 3 - 0
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"},

+ 1 - 0
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"},

+ 7 - 0
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": {

+ 1 - 0
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)

+ 35 - 0
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 `<keyboard>.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;
+}
+```

+ 11 - 0
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.

+ 0 - 1
keyboards/gmmk/numpad/config.h

@@ -17,7 +17,6 @@
 
 #pragma once
 
-#define SLIDER_PIN B0
 #define MIDI_ADVANCED
 
 #define LOCKING_SUPPORT_ENABLE

+ 3 - 0
keyboards/gmmk/numpad/info.json

@@ -20,6 +20,9 @@
             {"pin_a": "A2", "pin_b": "A1"}
         ]
     },
+    "potentiometer": {
+        "pins": ["B0"]
+    },
     "rgb_matrix": {
         "driver": "aw20216s"
     },

+ 0 - 17
keyboards/gmmk/numpad/keymaps/default/keymap.c

@@ -16,8 +16,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #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();
-}

+ 0 - 17
keyboards/gmmk/numpad/keymaps/via/keymap.c

@@ -13,8 +13,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #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();
-}

+ 8 - 0
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;
+}

+ 1 - 2
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

+ 6 - 0
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 }

+ 16 - 0
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;
+}

+ 3 - 0
keyboards/handwired/onekey/keymaps/potentiometer/rules.mk

@@ -0,0 +1,3 @@
+CONSOLE_ENABLE = yes
+
+POTENTIOMETER_ENABLE = yes

+ 16 - 0
quantum/keyboard.c

@@ -60,6 +60,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #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();

+ 113 - 0
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;
+}

+ 18 - 0
quantum/potentiometer.h

@@ -0,0 +1,18 @@
+// Copyright 2023 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/** \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);

+ 4 - 0
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