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

This commit is contained in:
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
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)
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 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 |
| 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.

View File

@ -20,11 +20,22 @@
#include "haptic.h"
#include "gpio.h"
#include "usb_device_state.h"
#include <stdlib.h>
bool solenoid_on = false;
bool solenoid_buzzing = false;
uint16_t solenoid_start = 0;
uint8_t solenoid_dwell = SOLENOID_DEFAULT_DWELL;
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 = true;
solenoid_buzzing = true;
solenoid_start = timer_read();
SOLENOID_PIN_WRITE_ACTIVE();
solenoid_on[index] = true;
solenoid_buzzing[index] = true;
solenoid_start[index] = timer_read();
writePin(solenoid_pads[index], solenoid_active_state[index]);
}
void solenoid_check(void) {
uint16_t elapsed = 0;
if (!solenoid_on) return;
elapsed = timer_elapsed(solenoid_start);
// Check if it's time to finish this solenoid click cycle
if (elapsed > solenoid_dwell) {
solenoid_stop();
return;
/**
* @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
}
// 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();
/**
* @brief Checks active solenoid to stop them, and to handle buzz mode
*
*/
void solenoid_check(void) {
uint16_t elapsed[NUMBER_OF_SOLENOIDS] = {0};
for (uint8_t i = 0; i < NUMBER_OF_SOLENOIDS; i++) {
if (!solenoid_on[i]) continue;
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) {
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]);
}
}

View File

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

View File

@ -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 }

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);
#endif
#ifdef SOLENOID_ENABLE
solenoid_fire();
solenoid_fire_handler();
#endif
}