mirror of
				https://github.com/mfulz/qmk_firmware.git
				synced 2025-10-30 21:02:32 +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 | ||||
| 
 | ||||
| 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. | ||||
|  | ||||
| @ -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]); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -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
									
								
								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); | ||||
| #endif | ||||
| #ifdef SOLENOID_ENABLE | ||||
|     solenoid_fire(); | ||||
|     solenoid_fire_handler(); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Drashna Jaelre
						Drashna Jaelre