mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-31 05:12:33 +01:00 
			
		
		
		
	[Feature] Add support for multiple switchs/solenoids to Haptic Feedback engine (#15657)
This commit is contained in:
		
							parent
							
								
									4d107feca9
								
							
						
					
					
						commit
						f090881aeb
					
				| @ -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. | ||||||
|  | |||||||
| @ -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]); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 } | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								keyboards/handwired/onekey/keymaps/haptic/keymap.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								keyboards/handwired/onekey/keymaps/haptic/keymap.c
									
									
									
									
									
										Normal 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(); | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								keyboards/handwired/onekey/keymaps/haptic/rules.mk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								keyboards/handwired/onekey/keymaps/haptic/rules.mk
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | HAPTIC_ENABLE = yes | ||||||
|  | HAPTIC_DRIVER = SOLENOID | ||||||
| @ -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 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Drashna Jaelre
						Drashna Jaelre