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

master
Drashna Jaelre 2022-05-15 04:24:35 -07:00 committed by GitHub
parent 4d107feca9
commit f090881aeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 164 additions and 70 deletions

View File

@ -50,22 +50,28 @@ Not all keycodes below will work depending on which haptic mechanism you have ch
### Solenoids ### 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.
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) [Wiring diagram provided by Adafruit](https://cdn-shop.adafruit.com/product-files/412/solenoid_driver.pdf)
For relay switches, the hardware may already contain all of that ciruitry, and just require VCC, GND and a data pin.
| Settings | Default | Description | | Settings | Default | Description |
|----------------------------|----------------------|-------------------------------------------------------| |----------------------------|----------------------|--------------------------------------------------------------|
|`SOLENOID_PIN` | *Not defined* |Configures the pin that the Solenoid is connected to. | |`SOLENOID_PIN` | *Not defined* |Configures the pin that the switch is connected to. |
|`SOLENOID_PIN_ACTIVE_LOW` | *Not defined* |If defined then the solenoid trigger pin is active low.| |`SOLENOID_PIN_ACTIVE_LOW` | *Not defined* |If defined then the switch trigger pin is active low. |
|`SOLENOID_DEFAULT_DWELL` | `12` ms |Configures the default dwell time for the solenoid. | |`SOLENOID_PINS` | *Not defined* |Configures an array of pins to be used for switch activation. |
|`SOLENOID_MIN_DWELL` | `4` ms |Sets the lower limit for the dwell. | |`SOLENOID_PINS_ACTIVE_LOW` | *Not defined* |Allows you to specify how each pin is pulled for activation. |
|`SOLENOID_MAX_DWELL` | `100` ms |Sets the upper limit for the dwell. | |`SOLENOID_RANDOM_FIRE` | *Not defined* |When there are multiple solenoids, will select a random one to fire.|
|`SOLENOID_DWELL_STEP_SIZE` | `1` ms |The step size to use when `HPT_DWL*` keycodes are sent | |`SOLENOID_DEFAULT_DWELL` | `12` ms |Configures the default dwell time for the switch. |
|`SOLENOID_DEFAULT_BUZZ` | `0` (disabled) |On HPT_RST buzz is set "on" if this is "1" | |`SOLENOID_MIN_DWELL` | `4` ms |Sets the lower limit for the dwell. |
|`SOLENOID_BUZZ_ACTUATED` | `SOLENOID_MIN_DWELL` |Actuated-time when the solenoid is in buzz mode | |`SOLENOID_MAX_DWELL` | `100` ms |Sets the upper limit for the dwell. |
|`SOLENOID_BUZZ_NONACTUATED` | `SOLENOID_MIN_DWELL` |Non-Actuated-time when the solenoid is in buzz mode | |`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 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. * 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.

View File

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

View File

@ -45,26 +45,24 @@
# define SOLENOID_BUZZ_NONACTUATED SOLENOID_MIN_DWELL # define SOLENOID_BUZZ_NONACTUATED SOLENOID_MIN_DWELL
#endif #endif
#ifndef SOLENOID_PIN #ifndef SOLENOID_PINS
# error SOLENOID_PIN not defined # ifdef SOLENOID_PIN
# define SOLENOID_PINS \
{ SOLENOID_PIN }
# else
# error SOLENOID_PINS array not defined
# endif
#endif #endif
#ifdef SOLENOID_PIN_ACTIVE_LOW void solenoidbuzz_on(void);
# 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 solenoid_buzz_off(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_set_dwell(uint8_t dwell);
void solenoid_stop(void); void solenoid_stop(uint8_t index);
void solenoid_fire(void); void solenoid_fire(uint8_t index);
void solenoid_fire_handler(void);
void solenoid_check(void); void solenoid_check(void);

View File

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

View File

@ -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();
}

View File

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

View File

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