Quellcode durchsuchen

[Feature] Add support for multiple switchs/solenoids to Haptic Feedback engine (#15657)

Drashna Jaelre vor 3 Jahren
Ursprung
Commit
f090881aeb

+ 19 - 13
docs/feature_haptic_feedback.md

@@ -50,22 +50,28 @@ Not all keycodes below will work depending on which haptic mechanism you have ch
 
 ### Solenoids
 
-First you will need a build a circuit to drive the solenoid through a mosfet as most MCU will not be able to provide the current needed to drive the coil in the solenoid.
+The solenoid code supports relay switches, and similar hardware, as well as solenoids. 
 
-[Wiring diagram provided by Adafruit](https://cdn-shop.adafruit.com/product-files/412/solenoid_driver.pdf)
+For a regular solenoid,  you will need a build a circuit to drive the solenoid through a mosfet as most MCU will not be able to provide the current needed to drive the coil in the solenoid.
 
+[Wiring diagram provided by Adafruit](https://cdn-shop.adafruit.com/product-files/412/solenoid_driver.pdf)
 
-| Settings                   | Default              | Description                                           |
-|----------------------------|----------------------|-------------------------------------------------------|
-|`SOLENOID_PIN`              | *Not defined*        |Configures the pin that the Solenoid is connected to.  |
-|`SOLENOID_PIN_ACTIVE_LOW`   | *Not defined*        |If defined then the solenoid trigger pin is active low.|
-|`SOLENOID_DEFAULT_DWELL`    | `12` ms              |Configures the default dwell time for the solenoid.    |
-|`SOLENOID_MIN_DWELL`        | `4` ms               |Sets the lower limit for the dwell.                    |
-|`SOLENOID_MAX_DWELL`        | `100` ms             |Sets the upper limit for the dwell.                    |
-|`SOLENOID_DWELL_STEP_SIZE`  | `1` ms               |The step size to use when `HPT_DWL*` keycodes are sent |
-|`SOLENOID_DEFAULT_BUZZ`     | `0` (disabled)       |On HPT_RST buzz is set "on" if this is "1"             |
-|`SOLENOID_BUZZ_ACTUATED`    | `SOLENOID_MIN_DWELL` |Actuated-time when the solenoid is in buzz mode        |
-|`SOLENOID_BUZZ_NONACTUATED` | `SOLENOID_MIN_DWELL` |Non-Actuated-time when the solenoid is in buzz mode    |
+For relay switches, the hardware may already contain all of that ciruitry, and just require VCC, GND and a data pin.
+
+| Settings                   | Default              | Description                                                  |
+|----------------------------|----------------------|--------------------------------------------------------------|
+|`SOLENOID_PIN`              | *Not defined*        |Configures the pin that the switch  is connected to.          |
+|`SOLENOID_PIN_ACTIVE_LOW`   | *Not defined*        |If defined then the switch trigger pin is active low.         |
+|`SOLENOID_PINS`             | *Not defined*        |Configures an array of pins to be used for switch activation. |
+|`SOLENOID_PINS_ACTIVE_LOW`  | *Not defined*        |Allows you to specify how each pin is pulled for activation.  |
+|`SOLENOID_RANDOM_FIRE`      | *Not defined*        |When there are multiple solenoids, will select a random one to fire.|
+|`SOLENOID_DEFAULT_DWELL`    | `12` ms              |Configures the default dwell time for the switch.             |
+|`SOLENOID_MIN_DWELL`        | `4` ms               |Sets the lower limit for the dwell.                           |
+|`SOLENOID_MAX_DWELL`        | `100` ms             |Sets the upper limit for the dwell.                           |
+|`SOLENOID_DWELL_STEP_SIZE`  | `1` ms               |The step size to use when `HPT_DWL*` keycodes are sent.       |
+|`SOLENOID_DEFAULT_BUZZ`     | `0` (disabled)       |On HPT_RST buzz is set "on" if this is "1"                    |
+|`SOLENOID_BUZZ_ACTUATED`    | `SOLENOID_MIN_DWELL` |Actuated-time when the switch is in buzz mode.                |
+|`SOLENOID_BUZZ_NONACTUATED` | `SOLENOID_MIN_DWELL` |Non-Actuated-time when the switch is in buzz mode.            |
 
 * If solenoid buzz is off, then dwell time is how long the "plunger" stays activated. The dwell time changes how the solenoid sounds.
 * If solenoid buzz is on, then dwell time sets the length of the buzz, while `SOLENOID_BUZZ_ACTUATED` and `SOLENOID_BUZZ_NONACTUATED` set the (non-)actuation times withing the buzz period.

+ 114 - 41
drivers/haptic/solenoid.c

@@ -20,11 +20,22 @@
 #include "haptic.h"
 #include "gpio.h"
 #include "usb_device_state.h"
-
-bool     solenoid_on      = false;
-bool     solenoid_buzzing = false;
-uint16_t solenoid_start   = 0;
-uint8_t  solenoid_dwell   = SOLENOID_DEFAULT_DWELL;
+#include <stdlib.h>
+
+uint8_t      solenoid_dwell  = SOLENOID_DEFAULT_DWELL;
+static pin_t solenoid_pads[] = SOLENOID_PINS;
+#define NUMBER_OF_SOLENOIDS (sizeof(solenoid_pads) / sizeof(pin_t))
+bool     solenoid_on[NUMBER_OF_SOLENOIDS]      = {false};
+bool     solenoid_buzzing[NUMBER_OF_SOLENOIDS] = {false};
+uint16_t solenoid_start[NUMBER_OF_SOLENOIDS]   = {0};
+#ifdef SOLENOID_PIN_ACTIVE_LOW
+#    define low true
+#    define high false
+#else
+#    define low false
+#    define high true
+#endif
+static bool solenoid_active_state[NUMBER_OF_SOLENOIDS];
 
 extern haptic_config_t haptic_config;
 
@@ -36,7 +47,7 @@ void solenoid_buzz_off(void) {
     haptic_set_buzz(0);
 }
 
-void solenoid_set_buzz(int buzz) {
+void solenoid_set_buzz(uint8_t buzz) {
     haptic_set_buzz(buzz);
 }
 
@@ -44,59 +55,121 @@ void solenoid_set_dwell(uint8_t dwell) {
     solenoid_dwell = dwell;
 }
 
-void solenoid_stop(void) {
-    SOLENOID_PIN_WRITE_INACTIVE();
-    solenoid_on      = false;
-    solenoid_buzzing = false;
+/**
+ * @brief Stops a specific solenoid
+ *
+ * @param index select which solenoid to check/stop
+ */
+void solenoid_stop(uint8_t index) {
+    writePin(solenoid_pads[index], !solenoid_active_state[index]);
+    solenoid_on[index]      = false;
+    solenoid_buzzing[index] = false;
 }
 
-void solenoid_fire(void) {
-    if (!haptic_config.buzz && solenoid_on) return;
-    if (haptic_config.buzz && solenoid_buzzing) return;
+/**
+ * @brief Fires off a specific solenoid
+ *
+ * @param index Selects which solenoid to fire
+ */
+void solenoid_fire(uint8_t index) {
+    if (!haptic_config.buzz && solenoid_on[index]) return;
+    if (haptic_config.buzz && solenoid_buzzing[index]) return;
+
+    solenoid_on[index]      = true;
+    solenoid_buzzing[index] = true;
+    solenoid_start[index]   = timer_read();
+    writePin(solenoid_pads[index], solenoid_active_state[index]);
+}
 
-    solenoid_on      = true;
-    solenoid_buzzing = true;
-    solenoid_start   = timer_read();
-    SOLENOID_PIN_WRITE_ACTIVE();
+/**
+ * @brief Handles selecting a non-active solenoid, and firing it.
+ *
+ */
+void solenoid_fire_handler(void) {
+#ifndef SOLENOID_RANDOM_FIRE
+    if (NUMBER_OF_SOLENOIDS > 1) {
+        uint8_t i = rand() % NUMBER_OF_SOLENOIDS;
+        if (!solenoid_on[i]) {
+            solenoid_fire(i);
+        }
+    } else {
+        solenoid_fire(0);
+    }
+#else
+    for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
+        if (!solenoid_on[i]) {
+            solenoid_fire(i);
+            break;
+        }
+    }
+#endif
 }
 
+/**
+ * @brief Checks active solenoid to stop them, and to handle buzz mode
+ *
+ */
 void solenoid_check(void) {
-    uint16_t elapsed = 0;
+    uint16_t elapsed[NUMBER_OF_SOLENOIDS] = {0};
 
-    if (!solenoid_on) return;
+    for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
+        if (!solenoid_on[i]) continue;
 
-    elapsed = timer_elapsed(solenoid_start);
+        elapsed[i] = timer_elapsed(solenoid_start[i]);
 
-    // Check if it's time to finish this solenoid click cycle
-    if (elapsed > solenoid_dwell) {
-        solenoid_stop();
-        return;
-    }
+        // Check if it's time to finish this solenoid click cycle
+        if (elapsed[i] > solenoid_dwell) {
+            solenoid_stop(i);
+            continue;
+        }
 
-    // Check whether to buzz the solenoid on and off
-    if (haptic_config.buzz) {
-        if ((elapsed % (SOLENOID_BUZZ_ACTUATED + SOLENOID_BUZZ_NONACTUATED)) < SOLENOID_BUZZ_ACTUATED) {
-            if (!solenoid_buzzing) {
-                solenoid_buzzing = true;
-                SOLENOID_PIN_WRITE_ACTIVE();
-            }
-        } else {
-            if (solenoid_buzzing) {
-                solenoid_buzzing = false;
-                SOLENOID_PIN_WRITE_INACTIVE();
+        // Check whether to buzz the solenoid on and off
+        if (haptic_config.buzz) {
+            if ((elapsed[i] % (SOLENOID_BUZZ_ACTUATED + SOLENOID_BUZZ_NONACTUATED)) < SOLENOID_BUZZ_ACTUATED) {
+                if (!solenoid_buzzing[i]) {
+                    solenoid_buzzing[i] = true;
+                    writePin(solenoid_pads[i], solenoid_active_state[i]);
+                }
+            } else {
+                if (solenoid_buzzing[i]) {
+                    solenoid_buzzing[i] = false;
+                    writePin(solenoid_pads[i], !solenoid_active_state[i]);
+                }
             }
         }
     }
 }
 
+/**
+ * @brief Initial configuration for solenoids
+ *
+ */
 void solenoid_setup(void) {
-    SOLENOID_PIN_WRITE_INACTIVE();
-    setPinOutput(SOLENOID_PIN);
-    if ((!HAPTIC_OFF_IN_LOW_POWER) || (usb_device_state == USB_DEVICE_STATE_CONFIGURED)) {
-        solenoid_fire();
+#ifdef SOLENOID_PINS_ACTIVE_STATE
+    bool    state_temp[] = SOLENOID_PINS_ACTIVE_STATE;
+    uint8_t bound_check  = (sizeof(state_temp) / sizeof(bool));
+#endif
+
+    for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
+#ifdef SOLENOID_PINS_ACTIVE_STATE
+        solenoid_active_state[i] = (bound_check - i) ? state_temp[i] : high;
+#else
+        solenoid_active_state[i] = high;
+#endif
+        writePin(solenoid_pads[i], !solenoid_active_state[i]);
+        setPinOutput(solenoid_pads[i]);
+        if ((!HAPTIC_OFF_IN_LOW_POWER) || (usb_device_state == USB_DEVICE_STATE_CONFIGURED)) {
+            solenoid_fire(i);
+        }
     }
 }
 
+/**
+ * @brief stops solenoids prior to device reboot, to prevent them from being locked on
+ *
+ */
 void solenoid_shutdown(void) {
-    SOLENOID_PIN_WRITE_INACTIVE();
+    for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
+        writePin(solenoid_pads[i], !solenoid_active_state[i]);
+    }
 }

+ 12 - 14
drivers/haptic/solenoid.h

@@ -45,26 +45,24 @@
 #    define SOLENOID_BUZZ_NONACTUATED SOLENOID_MIN_DWELL
 #endif
 
-#ifndef SOLENOID_PIN
-#    error SOLENOID_PIN not defined
+#ifndef SOLENOID_PINS
+#    ifdef SOLENOID_PIN
+#        define SOLENOID_PINS \
+            { SOLENOID_PIN }
+#    else
+#        error SOLENOID_PINS array not defined
+#    endif
 #endif
 
-#ifdef SOLENOID_PIN_ACTIVE_LOW
-#    define SOLENOID_PIN_WRITE_ACTIVE() writePinLow(SOLENOID_PIN)
-#    define SOLENOID_PIN_WRITE_INACTIVE() writePinHigh(SOLENOID_PIN)
-#else
-#    define SOLENOID_PIN_WRITE_ACTIVE() writePinHigh(SOLENOID_PIN)
-#    define SOLENOID_PIN_WRITE_INACTIVE() writePinLow(SOLENOID_PIN)
-#endif
-
-void solenoid_buzz_on(void);
+void solenoidbuzz_on(void);
 void solenoid_buzz_off(void);
-void solenoid_set_buzz(int buzz);
+void solenoid_set_buzz(uint8_t buzz);
 
 void solenoid_set_dwell(uint8_t dwell);
 
-void solenoid_stop(void);
-void solenoid_fire(void);
+void solenoid_stop(uint8_t index);
+void solenoid_fire(uint8_t index);
+void solenoid_fire_handler(void);
 
 void solenoid_check(void);
 

+ 4 - 0
keyboards/handwired/onekey/blackpill_f411/config.h

@@ -31,3 +31,7 @@
 #define RGB_DI_PIN A1
 
 #define ADC_PIN A0
+
+#define SOLENOID_PIN B12
+#define SOLENOID_PINS { B12, B13, B14, B15 }
+#define SOLENOID_PINS_ACTIVE_STATE { high, high, low }

+ 11 - 0
keyboards/handwired/onekey/keymaps/haptic/keymap.c

@@ -0,0 +1,11 @@
+#include QMK_KEYBOARD_H
+
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+    LAYOUT_ortho_1x1(KC_A)
+};
+
+void haptic_enable(void);
+
+void keyboard_post_init_user(void) {
+    haptic_enable();
+}

+ 2 - 0
keyboards/handwired/onekey/keymaps/haptic/rules.mk

@@ -0,0 +1,2 @@
+HAPTIC_ENABLE = yes
+HAPTIC_DRIVER = SOLENOID

+ 1 - 1
quantum/haptic.c

@@ -321,7 +321,7 @@ void haptic_play(void) {
     DRV_pulse(play_eff);
 #endif
 #ifdef SOLENOID_ENABLE
-    solenoid_fire();
+    solenoid_fire_handler();
 #endif
 }