Haptic feedback generalized - DRV2605 and solenoids (#4939)

* initial work to add eeprom to haptic feedback and decouple the feedback process from keyboards

* Haptic feedback enhancements: on/off toggle working, feedback order working
todo:
-work on modes switching
-get modes switching to save to eeprom

* haptic enhancement - eeprom and modes added

* Added set and get functions for haptic feedback

* initial implementation of solenoids under haptic feedback

* changed eeprom to 32 bits to reserve blocks for future features

* start documentation of haptic feedback

* change keycode per comment from reviewers

* typo fixes

* added eeprom for solenoid configs

* added solenoid and docs

* Add/fix default parameters configs, improve docs

* more doc cleanup

* add in solenoid buzz toggle, clean up doc

* some fixes for error in compiling solenoid

* fix a chibios specific i2c read function and added one for AVR controllers in DRV2605L.c

* fixes for avr side issues

* update keymap

* fix keymap compile error

* fix bugs found during solenoid testing

* set pin that is not powered during bootloader

* added warning about certain pins on the MCU may trip solenoid during DFU/bootloader
This commit is contained in:
ishtob 2019-02-16 21:39:30 -05:00 committed by MechMerlin
parent a7a647b7f6
commit 2cee371bf1
18 changed files with 750 additions and 48 deletions

View file

@ -246,11 +246,21 @@ endif
ifeq ($(strip $(HAPTIC_ENABLE)), DRV2605L) ifeq ($(strip $(HAPTIC_ENABLE)), DRV2605L)
COMMON_VPATH += $(DRIVER_PATH)/haptic COMMON_VPATH += $(DRIVER_PATH)/haptic
SRC += haptic.c
SRC += DRV2605L.c SRC += DRV2605L.c
SRC += i2c_master.c SRC += i2c_master.c
OPT_DEFS += -DHAPTIC_ENABLE
OPT_DEFS += -DDRV2605L OPT_DEFS += -DDRV2605L
endif endif
ifeq ($(strip $(HAPTIC_ENABLE)), SOLENOID)
COMMON_VPATH += $(DRIVER_PATH)/haptic
SRC += haptic.c
SRC += solenoid.c
OPT_DEFS += -DHAPTIC_ENABLE
OPT_DEFS += -DSOLENOID_ENABLE
endif
ifeq ($(strip $(HD44780_ENABLE)), yes) ifeq ($(strip $(HD44780_ENABLE)), yes)
SRC += drivers/avr/hd44780.c SRC += drivers/avr/hd44780.c
OPT_DEFS += -DHD44780_ENABLE OPT_DEFS += -DHD44780_ENABLE

View file

@ -0,0 +1,147 @@
# Haptic Feedback
## Haptic feedback rules.mk options
The following options are currently available for haptic feedback in `rule.mk`:
`HAPTIC_ENABLE += DRV2605L`
`HAPTIC_ENABLE += SOLENOID`
## Known Supported Hardware
| Name | Description |
|--------------------|-------------------------------------------------|
| [LV061228B-L65-A](https://www.digikey.com/product-detail/en/jinlong-machinery-electronics-inc/LV061228B-L65-A/1670-1050-ND/7732325) | z-axis 2v LRA |
| [Mini Motor Disc](https://www.adafruit.com/product/1201) | small 2-5v ERM |
## Haptic Keycodes
Not all keycodes below will work depending on which haptic mechanism you have chosen.
| Name | Description |
|-----------|-------------------------------------------------------|
|`HPT_ON` | Turn haptic feedback on |
|`HPT_OFF` | Turn haptic feedback on |
|`HPT_TOG` | Toggle haptic feedback on/off |
|`HPT_RST` | Reset haptic feedback config to default |
|`HPT_FBK` | Toggle feedback to occur on keypress, release or both |
|`HPT_BUZ` | Toggle solenoid buzz on/off |
|`HPT_MODI` | Go to next DRV2605L waveform |
|`HPT_MODD` | Go to previous DRV2605L waveform |
|`HPT_DWLI` | Increase Solenoid dwell time |
|`HPT_DWLD` | Decrease Solenoid dwell time |
### 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.
[Wiring diagram provided by Adafruit](https://playground.arduino.cc/uploads/Learning/solenoid_driver.pdf)
Select a pin that has PWM for the signal pin
```
#define SOLENOID_PIN *pin*
```
Beware that some pins may be powered during bootloader (ie. A13 on the STM32F303 chip) and will result in the solenoid kept in the on state through the whole flashing process. This may overheat and damage the solenoid. If you find that the pin the solenoid is connected to is triggering the solenoid during bootloader/DFU, select another pin.
### DRV2605L
DRV2605L is controlled over i2c protocol, and has to be connected to the SDA and SCL pins, these varies depending on the MCU in use.
#### Feedback motor setup
This driver supports 2 different feedback motors. Set the following in your `config.h` based on which motor you have selected.
##### ERM
Eccentric Rotating Mass vibration motors (ERM) is motor with a off-set weight attached so when drive signal is attached, the off-set weight spins and causes a sinusoidal wave that translate into vibrations.
```
#define FB_ERM_LRA 0
#define FB_BRAKEFACTOR 3 /* For 1x:0, 2x:1, 3x:2, 4x:3, 6x:4, 8x:5, 16x:6, Disable Braking:7 */
#define FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */
/* Please refer to your datasheet for the optimal setting for your specific motor. */
#define RATED_VOLTAGE 3
#define V_PEAK 5
```
##### LRA
Linear resonant actuators (LRA, also know as a linear vibrator) works different from a ERM. A LRA has a weight and magnet suspended by springs and a voice coil. When the drive signal is applied, the weight would be vibrate on a single axis (side to side or up and down). Since the weight is attached to a spring, there is a resonance effect at a specific frequency. This frequency is where the LRA will operate the most efficiently. Refer to the motor's datasheet for the recommanded range for this frequency.
```
#define FB_ERM_LRA 1
#define FB_BRAKEFACTOR 3 /* For 1x:0, 2x:1, 3x:2, 4x:3, 6x:4, 8x:5, 16x:6, Disable Braking:7 */
#define FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */
/* Please refer to your datasheet for the optimal setting for your specific motor. */
#define RATED_VOLTAGE 2
#define V_PEAK 2.8
#define V_RMS 2.0
#define V_PEAK 2.1
#define F_LRA 205 /* resonance freq */
```
#### DRV2605L waveform library
DRV2605L comes with preloaded library of various waveform sequences that can be called and played. If writing a macro, these waveforms can be played using `DRV_pulse(*sequence name or number*)`
List of waveform sequences from the datasheet:
|seq# | Sequence name |seq# | Sequence name |seq# |Sequence name |
|-----|---------------------|-----|-----------------------------------|-----|--------------------------------------|
| 1 | strong_click | 43 | lg_dblclick_med_60 | 85 | transition_rampup_med_smooth2 |
| 2 | strong_click_60 | 44 | lg_dblsharp_tick | 86 | transition_rampup_short_smooth1 |
| 3 | strong_click_30 | 45 | lg_dblsharp_tick_80 | 87 | transition_rampup_short_smooth2 |
| 4 | sharp_click | 46 | lg_dblsharp_tick_60 | 88 | transition_rampup_long_sharp1 |
| 5 | sharp_click_60 | 47 | buzz | 89 | transition_rampup_long_sharp2 |
| 6 | sharp_click_30 | 48 | buzz_80 | 90 | transition_rampup_med_sharp1 |
| 7 | soft_bump | 49 | buzz_60 | 91 | transition_rampup_med_sharp2 |
| 8 | soft_bump_60 | 50 | buzz_40 | 92 | transition_rampup_short_sharp1 |
| 9 | soft_bump_30 | 51 | buzz_20 | 93 | transition_rampup_short_sharp2 |
| 10 | dbl_click | 52 | pulsing_strong | 94 | transition_rampdown_long_smooth1_50 |
| 11 | dbl_click_60 | 53 | pulsing_strong_80 | 95 | transition_rampdown_long_smooth2_50 |
| 12 | trp_click | 54 | pulsing_medium | 96 | transition_rampdown_med_smooth1_50 |
| 13 | soft_fuzz | 55 | pulsing_medium_80 | 97 | transition_rampdown_med_smooth2_50 |
| 14 | strong_buzz | 56 | pulsing_sharp | 98 | transition_rampdown_short_smooth1_50 |
| 15 | alert_750ms | 57 | pulsing_sharp_80 | 99 | transition_rampdown_short_smooth2_50 |
| 16 | alert_1000ms | 58 | transition_click | 100 | transition_rampdown_long_sharp1_50 |
| 17 | strong_click1 | 59 | transition_click_80 | 101 | transition_rampdown_long_sharp2_50 |
| 18 | strong_click2_80 | 60 | transition_click_60 | 102 | transition_rampdown_med_sharp1_50 |
| 19 | strong_click3_60 | 61 | transition_click_40 | 103 | transition_rampdown_med_sharp2_50 |
| 20 | strong_click4_30 | 62 | transition_click_20 | 104 | transition_rampdown_short_sharp1_50 |
| 21 | medium_click1 | 63 | transition_click_10 | 105 | transition_rampdown_short_sharp2_50 |
| 22 | medium_click2_80 | 64 | transition_hum | 106 | transition_rampup_long_smooth1_50 |
| 23 | medium_click3_60 | 65 | transition_hum_80 | 107 | transition_rampup_long_smooth2_50 |
| 24 | sharp_tick1 | 66 | transition_hum_60 | 108 | transition_rampup_med_smooth1_50 |
| 25 | sharp_tick2_80 | 67 | transition_hum_40 | 109 | transition_rampup_med_smooth2_50 |
| 26 | sharp_tick3_60 | 68 | transition_hum_20 | 110 | transition_rampup_short_smooth1_50 |
| 27 | sh_dblclick_str | 69 | transition_hum_10 | 111 | transition_rampup_short_smooth2_50 |
| 28 | sh_dblclick_str_80 | 70 | transition_rampdown_long_smooth1 | 112 | transition_rampup_long_sharp1_50 |
| 29 | sh_dblclick_str_60 | 71 | transition_rampdown_long_smooth2 | 113 | transition_rampup_long_sharp2_50 |
| 30 | sh_dblclick_str_30 | 72 | transition_rampdown_med_smooth1 | 114 | transition_rampup_med_sharp1_50 |
| 31 | sh_dblclick_med | 73 | transition_rampdown_med_smooth2 | 115 | transition_rampup_med_sharp2_50 |
| 32 | sh_dblclick_med_80 | 74 | transition_rampdown_short_smooth1 | 116 | transition_rampup_short_sharp1_50 |
| 33 | sh_dblclick_med_60 | 75 | transition_rampdown_short_smooth2 | 117 | transition_rampup_short_sharp2_50 |
| 34 | sh_dblsharp_tick | 76 | transition_rampdown_long_sharp1 | 118 | long_buzz_for_programmatic_stopping |
| 35 | sh_dblsharp_tick_80 | 77 | transition_rampdown_long_sharp2 | 119 | smooth_hum1_50 |
| 36 | sh_dblsharp_tick_60 | 78 | transition_rampdown_med_sharp1 | 120 | smooth_hum2_40 |
| 37 | lg_dblclick_str | 79 | transition_rampdown_med_sharp2 | 121 | smooth_hum3_30 |
| 38 | lg_dblclick_str_80 | 80 | transition_rampdown_short_sharp1 | 122 | smooth_hum4_20 |
| 39 | lg_dblclick_str_60 | 81 | transition_rampdown_short_sharp2 | 123 | smooth_hum5_10 |
| 40 | lg_dblclick_str_30 | 82 | transition_rampup_long_smooth1 | | |
| 41 | lg_dblclick_med | 83 | transition_rampup_long_smooth2 | | |
| 42 | lg_dblclick_med_80 | 84 | transition_rampup_med_smooth1 | | |
### Optional DRV2605L defines
```
#define DRV_GREETING *sequence name or number*
```
If haptic feedback is enabled, the keyboard will vibrate to a specific sqeuence during startup. That can be selected using the following define:
```
#define DRV_MODE_DEFAULT *sequence name or number*
```
This will set what sequence HPT_RST will set as the active mode. If not defined, mode will be set to 1 when HPT_RST is pressed.

View file

@ -21,7 +21,7 @@
#include <math.h> #include <math.h>
uint8_t DRV2605L_transfer_buffer[20]; uint8_t DRV2605L_transfer_buffer[2];
uint8_t DRV2605L_tx_register[0]; uint8_t DRV2605L_tx_register[0];
uint8_t DRV2605L_read_buffer[0]; uint8_t DRV2605L_read_buffer[0];
uint8_t DRV2605L_read_register; uint8_t DRV2605L_read_register;
@ -34,6 +34,11 @@ void DRV_write(uint8_t drv_register, uint8_t settings) {
} }
uint8_t DRV_read(uint8_t regaddress) { uint8_t DRV_read(uint8_t regaddress) {
#ifdef __AVR__
i2c_readReg(DRV2605L_BASE_ADDRESS << 1,
regaddress, DRV2605L_read_buffer, 1, 100);
DRV2605L_read_register = (uint8_t)DRV2605L_read_buffer[0];
#else
DRV2605L_tx_register[0] = regaddress; DRV2605L_tx_register[0] = regaddress;
if (MSG_OK != i2c_transmit_receive(DRV2605L_BASE_ADDRESS << 1, if (MSG_OK != i2c_transmit_receive(DRV2605L_BASE_ADDRESS << 1,
DRV2605L_tx_register, 1, DRV2605L_tx_register, 1,
@ -42,14 +47,13 @@ uint8_t DRV_read(uint8_t regaddress) {
printf("err reading reg \n"); printf("err reading reg \n");
} }
DRV2605L_read_register = (uint8_t)DRV2605L_read_buffer[0]; DRV2605L_read_register = (uint8_t)DRV2605L_read_buffer[0];
#endif
return DRV2605L_read_register; return DRV2605L_read_register;
} }
void DRV_init(void) void DRV_init(void)
{ {
i2c_init(); i2c_init();
i2c_start(DRV2605L_BASE_ADDRESS);
/* 0x07 sets DRV2605 into calibration mode */ /* 0x07 sets DRV2605 into calibration mode */
DRV_write(DRV_MODE,0x07); DRV_write(DRV_MODE,0x07);
@ -104,21 +108,17 @@ void DRV_init(void)
C4_SET.Bits.C4_AUTO_CAL_TIME = AUTO_CAL_TIME; C4_SET.Bits.C4_AUTO_CAL_TIME = AUTO_CAL_TIME;
DRV_write(DRV_CTRL_4, (uint8_t) C4_SET.Byte); DRV_write(DRV_CTRL_4, (uint8_t) C4_SET.Byte);
DRV_write(DRV_LIB_SELECTION,LIB_SELECTION); DRV_write(DRV_LIB_SELECTION,LIB_SELECTION);
//start autocalibration
DRV_write(DRV_GO, 0x01); DRV_write(DRV_GO, 0x01);
/* 0x00 sets DRV2605 out of standby and to use internal trigger /* 0x00 sets DRV2605 out of standby and to use internal trigger
* 0x01 sets DRV2605 out of standby and to use external trigger */ * 0x01 sets DRV2605 out of standby and to use external trigger */
DRV_write(DRV_MODE,0x00); DRV_write(DRV_MODE,0x00);
/* 0x06: LRA library */
DRV_write(DRV_WAVEFORM_SEQ_1, 0x01);
/* 0xB9: LRA, 4x brake factor, medium gain, 7.5x back EMF
* 0x39: ERM, 4x brake factor, medium gain, 1.365x back EMF */
/* TODO: setup auto-calibration as part of initiation */
//Play greeting sequence
DRV_write(DRV_GO, 0x00);
DRV_write(DRV_WAVEFORM_SEQ_1, DRV_GREETING);
DRV_write(DRV_GO, 0x01);
} }
void DRV_pulse(uint8_t sequence) void DRV_pulse(uint8_t sequence)

View file

@ -31,13 +31,6 @@
#define FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */ #define FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */
#endif #endif
#ifndef RATED_VOLTAGE
#define RATED_VOLTAGE 2 /* 2v as safe range in case device voltage is not set */
#ifndef V_PEAK
#define V_PEAK 2.8
#endif
#endif
/* LRA specific settings */ /* LRA specific settings */
#if FB_ERM_LRA == 1 #if FB_ERM_LRA == 1
#ifndef V_RMS #ifndef V_RMS
@ -49,6 +42,16 @@
#ifndef F_LRA #ifndef F_LRA
#define F_LRA 205 #define F_LRA 205
#endif #endif
#ifndef RATED_VOLTAGE
#define RATED_VOLTAGE 2 /* 2v as safe range in case device voltage is not set */
#endif
#endif
#ifndef RATED_VOLTAGE
#define RATED_VOLTAGE 2 /* 2v as safe range in case device voltage is not set */
#endif
#ifndef V_PEAK
#define V_PEAK 2.8
#endif #endif
/* Library Selection */ /* Library Selection */
@ -60,6 +63,13 @@
#endif #endif
#endif #endif
#ifndef DRV_GREETING
#define DRV_GREETING alert_750ms
#endif
#ifndef DRV_MODE_DEFAULT
#define DRV_MODE_DEFAULT strong_click1
#endif
/* Control 1 register settings */ /* Control 1 register settings */
#ifndef DRIVE_TIME #ifndef DRIVE_TIME
#define DRIVE_TIME 25 #define DRIVE_TIME 25
@ -162,7 +172,6 @@ void DRV_write(const uint8_t drv_register, const uint8_t settings);
uint8_t DRV_read(const uint8_t regaddress); uint8_t DRV_read(const uint8_t regaddress);
void DRV_pulse(const uint8_t sequence); void DRV_pulse(const uint8_t sequence);
typedef enum DRV_EFFECT{ typedef enum DRV_EFFECT{
clear_sequence = 0, clear_sequence = 0,
strong_click = 1, strong_click = 1,
@ -288,6 +297,7 @@ typedef enum DRV_EFFECT{
smooth_hum3_30 = 121, smooth_hum3_30 = 121,
smooth_hum4_20 = 122, smooth_hum4_20 = 122,
smooth_hum5_10 = 123, smooth_hum5_10 = 123,
drv_effect_max = 124,
} DRV_EFFECT; } DRV_EFFECT;
/* Register bit array unions */ /* Register bit array unions */

248
drivers/haptic/haptic.c Normal file
View file

@ -0,0 +1,248 @@
/* Copyright 2019 ishtob
* Driver for haptic feedback written for QMK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "haptic.h"
#include "eeconfig.h"
#include "progmem.h"
#include "debug.h"
#ifdef DRV2605L
#include "DRV2605L.h"
#endif
#ifdef SOLENOID_ENABLE
#include "solenoid.h"
#endif
haptic_config_t haptic_config;
void haptic_init(void) {
debug_enable = 1; //Debug is ON!
if (!eeconfig_is_enabled()) {
eeconfig_init();
}
haptic_config.raw = eeconfig_read_haptic();
if (haptic_config.mode < 1){
haptic_config.mode = 1;
}
if (!haptic_config.mode){
dprintf("No haptic config found in eeprom, setting default configs\n");
haptic_reset();
}
#ifdef SOLENOID_ENABLE
solenoid_setup();
dprintf("Solenoid driver initialized\n");
#endif
#ifdef DRV2605L
DRV_init();
dprintf("DRV2605 driver initialized\n");
#endif
eeconfig_debug_haptic();
}
void haptic_task(void) {
#ifdef SOLENOID_ENABLE
solenoid_check();
#endif
}
void eeconfig_debug_haptic(void) {
dprintf("haptic_config eprom\n");
dprintf("haptic_config.enable = %d\n", haptic_config.enable);
dprintf("haptic_config.mode = %d\n", haptic_config.mode);
}
void haptic_enable(void) {
haptic_config.enable = 1;
xprintf("haptic_config.enable = %u\n", haptic_config.enable);
eeconfig_update_haptic(haptic_config.raw);
}
void haptic_disable(void) {
haptic_config.enable = 0;
xprintf("haptic_config.enable = %u\n", haptic_config.enable);
eeconfig_update_haptic(haptic_config.raw);
}
void haptic_toggle(void) {
if (haptic_config.enable) {
haptic_disable();
} else {
haptic_enable();
}
eeconfig_update_haptic(haptic_config.raw);
}
void haptic_feedback_toggle(void){
haptic_config.feedback++;
if (haptic_config.feedback >= HAPTIC_FEEDBACK_MAX)
haptic_config.feedback = KEY_PRESS;
xprintf("haptic_config.feedback = %u\n", !haptic_config.feedback);
eeconfig_update_haptic(haptic_config.raw);
}
void haptic_buzz_toggle(void) {
bool buzz_stat = !haptic_config.buzz;
haptic_config.buzz = buzz_stat;
haptic_set_buzz(buzz_stat);
}
void haptic_mode_increase(void) {
uint8_t mode = haptic_config.mode + 1;
#ifdef DRV2605L
if (haptic_config.mode >= drv_effect_max) {
mode = 1;
}
#endif
haptic_set_mode(mode);
}
void haptic_mode_decrease(void) {
uint8_t mode = haptic_config.mode -1;
#ifdef DRV2605L
if (haptic_config.mode < 1) {
mode = (drv_effect_max - 1);
}
#endif
haptic_set_mode(mode);
}
void haptic_dwell_increase(void) {
uint8_t dwell = haptic_config.dwell + 1;
#ifdef SOLENOID_ENABLE
if (haptic_config.dwell >= SOLENOID_MAX_DWELL) {
dwell = 1;
}
solenoid_set_dwell(dwell);
#endif
haptic_set_dwell(dwell);
}
void haptic_dwell_decrease(void) {
uint8_t dwell = haptic_config.dwell -1;
#ifdef SOLENOID_ENABLE
if (haptic_config.dwell < SOLENOID_MIN_DWELL) {
dwell = SOLENOID_MAX_DWELL;
}
solenoid_set_dwell(dwell);
#endif
haptic_set_dwell(dwell);
}
void haptic_reset(void){
haptic_config.enable = true;
uint8_t feedback = HAPTIC_FEEDBACK_DEFAULT;
haptic_config.feedback = feedback;
#ifdef DRV2605L
uint8_t mode = HAPTIC_MODE_DEFAULT;
haptic_config.mode = mode;
#endif
#ifdef SOLENOID_ENABLE
uint8_t dwell = SOLENOID_DEFAULT_DWELL;
haptic_config.dwell = dwell;
#endif
eeconfig_update_haptic(haptic_config.raw);
xprintf("haptic_config.feedback = %u\n", haptic_config.feedback);
xprintf("haptic_config.mode = %u\n", haptic_config.mode);
}
void haptic_set_feedback(uint8_t feedback) {
haptic_config.feedback = feedback;
eeconfig_update_haptic(haptic_config.raw);
xprintf("haptic_config.feedback = %u\n", haptic_config.feedback);
}
void haptic_set_mode(uint8_t mode) {
haptic_config.mode = mode;
eeconfig_update_haptic(haptic_config.raw);
xprintf("haptic_config.mode = %u\n", haptic_config.mode);
}
void haptic_set_buzz(uint8_t buzz) {
haptic_config.buzz = buzz;
eeconfig_update_haptic(haptic_config.raw);
xprintf("haptic_config.buzz = %u\n", haptic_config.buzz);
}
void haptic_set_dwell(uint8_t dwell) {
haptic_config.dwell = dwell;
eeconfig_update_haptic(haptic_config.raw);
xprintf("haptic_config.dwell = %u\n", haptic_config.dwell);
}
uint8_t haptic_get_mode(void) {
if (!haptic_config.enable){
return false;
}
return haptic_config.mode;
}
uint8_t haptic_get_feedback(void) {
if (!haptic_config.enable){
return false;
}
return haptic_config.feedback;
}
uint8_t haptic_get_dwell(void) {
if (!haptic_config.enable){
return false;
}
return haptic_config.dwell;
}
void haptic_play(void) {
#ifdef DRV2605L
uint8_t play_eff = 0;
play_eff = haptic_config.mode;
DRV_pulse(play_eff);
#endif
#ifdef SOLENOID_ENABLE
solenoid_fire();
#endif
}
bool process_haptic(uint16_t keycode, keyrecord_t *record) {
if (keycode == HPT_ON && record->event.pressed) { haptic_enable(); }
if (keycode == HPT_OFF && record->event.pressed) { haptic_disable(); }
if (keycode == HPT_TOG && record->event.pressed) { haptic_toggle(); }
if (keycode == HPT_RST && record->event.pressed) { haptic_reset(); }
if (keycode == HPT_FBK && record->event.pressed) { haptic_feedback_toggle(); }
if (keycode == HPT_BUZ && record->event.pressed) { haptic_buzz_toggle(); }
if (keycode == HPT_MODI && record->event.pressed) { haptic_mode_increase(); }
if (keycode == HPT_MODD && record->event.pressed) { haptic_mode_decrease(); }
if (keycode == HPT_DWLI && record->event.pressed) { haptic_dwell_increase(); }
if (keycode == HPT_DWLD && record->event.pressed) { haptic_dwell_decrease(); }
if (haptic_config.enable) {
if ( record->event.pressed ) {
// keypress
if (haptic_config.feedback < 2) {
haptic_play();
}
} else {
//keyrelease
if (haptic_config.feedback > 0) {
haptic_play();
}
}
}
return true;
}
void haptic_shutdown(void) {
#ifdef SOLENOID_ENABLE
solenoid_shutdown();
#endif
}

82
drivers/haptic/haptic.h Normal file
View file

@ -0,0 +1,82 @@
/* Copyright 2019 ishtob
* Driver for haptic feedback written for QMK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "quantum.h"
#ifdef DRV2605L
#include "DRV2605L.h"
#endif
#ifndef HAPTIC_FEEDBACK_DEFAULT
#define HAPTIC_FEEDBACK_DEFAULT 0
#endif
#ifndef HAPTIC_MODE_DEFAULT
#define HAPTIC_MODE_DEFAULT DRV_MODE_DEFAULT
#endif
/* EEPROM config settings */
typedef union {
uint32_t raw;
struct {
bool enable :1;
uint8_t feedback :2;
uint8_t mode :7;
bool buzz :1;
uint8_t dwell :7;
uint16_t reserved :16;
};
} haptic_config_t;
typedef enum HAPTIC_FEEDBACK{
KEY_PRESS,
KEY_PRESS_RELEASE,
KEY_RELEASE,
HAPTIC_FEEDBACK_MAX,
} HAPTIC_FEEDBACK;
bool process_haptic(uint16_t keycode, keyrecord_t *record);
void haptic_init(void);
void haptic_task(void);
void eeconfig_debug_haptic(void);
void haptic_enable(void);
void haptic_disable(void);
void haptic_toggle(void);
void haptic_feedback_toggle(void);
void haptic_mode_increase(void);
void haptic_mode_decrease(void);
void haptic_mode(uint8_t mode);
void haptic_reset(void);
void haptic_set_feedback(uint8_t feedback);
void haptic_set_mode(uint8_t mode);
void haptic_set_dwell(uint8_t dwell);
void haptic_set_buzz(uint8_t buzz);
void haptic_buzz_toggle(void);
uint8_t haptic_get_mode(void);
uint8_t haptic_get_feedback(void);
void haptic_dwell_increase(void);
void haptic_dwell_decrease(void);
void haptic_play(void);
void haptic_shutdown(void);

109
drivers/haptic/solenoid.c Normal file
View file

@ -0,0 +1,109 @@
/* Copyright 2018 mtdjr - modified by ishtob
* Driver for solenoid written for QMK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <timer.h>
#include "solenoid.h"
#include "haptic.h"
bool solenoid_on = false;
bool solenoid_buzzing = false;
uint16_t solenoid_start = 0;
uint8_t solenoid_dwell = SOLENOID_DEFAULT_DWELL;
extern haptic_config_t haptic_config;
void solenoid_buzz_on(void) {
haptic_set_buzz(1);
}
void solenoid_buzz_off(void) {
haptic_set_buzz(0);
}
void solenoid_set_buzz(int buzz) {
haptic_set_buzz(buzz);
}
void solenoid_dwell_minus(uint8_t solenoid_dwell) {
if (solenoid_dwell > 0) solenoid_dwell--;
}
void solenoid_dwell_plus(uint8_t solenoid_dwell) {
if (solenoid_dwell < SOLENOID_MAX_DWELL) solenoid_dwell++;
}
void solenoid_set_dwell(uint8_t dwell) {
solenoid_dwell = dwell;
}
void solenoid_stop(void) {
writePinLow(SOLENOID_PIN);
solenoid_on = false;
solenoid_buzzing = false;
}
void solenoid_fire(void) {
if (!haptic_config.buzz && solenoid_on) return;
if (haptic_config.buzz && solenoid_buzzing) return;
solenoid_on = true;
solenoid_buzzing = true;
solenoid_start = timer_read();
writePinHigh(SOLENOID_PIN);
}
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;
}
//Check whether to buzz the solenoid on and off
if (haptic_config.buzz) {
if (elapsed / SOLENOID_MIN_DWELL % 2 == 0){
if (!solenoid_buzzing) {
solenoid_buzzing = true;
writePinHigh(SOLENOID_PIN);
}
}
else {
if (solenoid_buzzing) {
solenoid_buzzing = false;
writePinLow(SOLENOID_PIN);
}
}
}
}
void solenoid_setup(void) {
setPinOutput(SOLENOID_PIN);
solenoid_fire();
}
void solenoid_shutdown(void) {
writePinLow(SOLENOID_PIN);
}

54
drivers/haptic/solenoid.h Normal file
View file

@ -0,0 +1,54 @@
/* Copyright 2018 mtdjr - modified by ishtob
* Driver for solenoid written for QMK
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef SOLENOID_DEFAULT_DWELL
#define SOLENOID_DEFAULT_DWELL 12
#endif
#ifndef SOLENOID_MAX_DWELL
#define SOLENOID_MAX_DWELL 100
#endif
#ifndef SOLENOID_MIN_DWELL
#define SOLENOID_MIN_DWELL 4
#endif
#ifndef SOLENOID_ACTIVE
#define SOLENOID_ACTIVE false
#endif
#ifndef SOLENOID_PIN
#define SOLENOID_PIN F6
#endif
void solenoid_buzz_on(void);
void solenoid_buzz_off(void);
void solenoid_set_buzz(int buzz);
void solenoid_dwell_minus(uint8_t solenoid_dwell);
void solenoid_dwell_plus(uint8_t solenoid_dwell);
void solenoid_set_dwell(uint8_t dwell);
void solenoid_stop(void);
void solenoid_fire(void);
void solenoid_check(void);
void solenoid_setup(void);
void solenoid_shutdown(void);

View file

@ -192,4 +192,7 @@
//#define DRIVER_LED_TOTAL RGBLED_NUM //#define DRIVER_LED_TOTAL RGBLED_NUM
//#define RGB_MATRIX_KEYPRESSES //#define RGB_MATRIX_KEYPRESSES
#define SOLENOID_PIN A14
#endif #endif

View file

@ -190,8 +190,8 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
* `--------------------------------------------------------------------------------------------------------' * `--------------------------------------------------------------------------------------------------------'
*/ */
[_ADJUST] = LAYOUT( [_ADJUST] = LAYOUT(
KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, \ RESET, HPT_TOG, HPT_FBK, HPT_MODI, HPT_MODD, HPT_RST , _______, _______, _______, _______, _______, _______, \
RESET, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, _______, _______, _______, _______, _______, KC_DEL, \ _______, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, _______, _______, _______, _______, _______, KC_DEL, \
_______, _______, _______, AU_ON, AU_OFF, AG_NORM, _______, _______, _______, AG_SWAP, QWERTY, COLEMAK, _______, _______, _______, \ _______, _______, _______, AU_ON, AU_OFF, AG_NORM, _______, _______, _______, AG_SWAP, QWERTY, COLEMAK, _______, _______, _______, \
_______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, _______, _______, _______, _______, BL_DEC, BL_INC, BL_STEP, BL_TOGG, \ _______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, _______, _______, _______, _______, BL_DEC, BL_INC, BL_STEP, BL_TOGG, \
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, CK_RST, CK_DOWN, CK_UP, CK_TOGG\ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, CK_RST, CK_DOWN, CK_UP, CK_TOGG\

View file

@ -165,7 +165,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
* `--------------------------------------------------------------------------------------------------------' * `--------------------------------------------------------------------------------------------------------'
*/ */
[_ADJUST] = LAYOUT_wrapper( [_ADJUST] = LAYOUT_wrapper(
KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, \ _______, HPT_TOG, HPT_FBK, HPT_MODI, HPT_MODD, HPT_RST, _______, _______, _______, _______, _______, _______, \
RESET, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, _______, _______, _______, _______, _______, KC_DEL, \ RESET, RGB_TOG, RGB_MOD, RGB_HUI, RGB_HUD, RGB_SAI, RGB_SAD, RGB_VAI, RGB_VAD, _______, _______, _______, _______, _______, KC_DEL, \
_______, MAGIC_TOGGLE_NKRO, _______, AU_ON, AU_OFF, AG_NORM, _______, _______, _______, AG_SWAP, QWERTY, COLEMAK, _______, _______, _______, \ _______, MAGIC_TOGGLE_NKRO, _______, AU_ON, AU_OFF, AG_NORM, _______, _______, _______, AG_SWAP, QWERTY, COLEMAK, _______, _______, _______, \
_______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, _______, _______, _______, _______, BL_DEC, BL_INC, BL_STEP, BL_TOGG, \ _______, MUV_DE, MUV_IN, MU_ON, MU_OFF, MI_ON, MI_OFF, _______, _______, _______, _______, BL_DEC, BL_INC, BL_STEP, BL_TOGG, \

View file

@ -52,6 +52,6 @@ NKRO_ENABLE = yes # USB Nkey Rollover
CUSTOM_MATRIX = yes # Custom matrix file CUSTOM_MATRIX = yes # Custom matrix file
AUDIO_ENABLE = yes AUDIO_ENABLE = yes
RGBLIGHT_ENABLE = no RGBLIGHT_ENABLE = no
HAPTIC_ENABLE = DRV2605L HAPTIC_ENABLE += DRV2605L
QWIIC_ENABLE += MICRO_OLED QWIIC_ENABLE += MICRO_OLED
# SERIAL_LINK_ENABLE = yes # SERIAL_LINK_ENABLE = yes

View file

@ -17,7 +17,7 @@
#include "qwiic.h" #include "qwiic.h"
#include "action_layer.h" #include "action_layer.h"
#include "matrix.h" #include "matrix.h"
#include "DRV2605L.h" #include "haptic.h"
#ifdef QWIIC_MICRO_OLED_ENABLE #ifdef QWIIC_MICRO_OLED_ENABLE
@ -167,20 +167,12 @@ void encoder_update_kb(uint8_t index, bool clockwise) {
#endif #endif
void matrix_init_kb(void) { void matrix_init_kb(void) {
#ifdef DRV2605L
DRV_init();
#endif
queue_for_send = true; queue_for_send = true;
matrix_init_user(); matrix_init_user();
} }
void matrix_scan_kb(void) { void matrix_scan_kb(void) {
if (queue_for_send) { if (queue_for_send) {
#ifdef DRV2605L
DRV_EFFECT play_eff = strong_click;
DRV_pulse(play_eff);
#endif
#ifdef QWIIC_MICRO_OLED_ENABLE #ifdef QWIIC_MICRO_OLED_ENABLE
read_host_led_state(); read_host_led_state();
draw_ui(); draw_ui();

View file

@ -47,6 +47,9 @@ extern backlight_config_t backlight_config;
#include "process_midi.h" #include "process_midi.h"
#endif #endif
#ifdef HAPTIC_ENABLE
#include "haptic.h"
#endif
#ifdef ENCODER_ENABLE #ifdef ENCODER_ENABLE
#include "encoder.h" #include "encoder.h"
@ -179,6 +182,9 @@ void reset_keyboard(void) {
shutdown_user(); shutdown_user();
wait_ms(250); wait_ms(250);
#endif #endif
#ifdef HAPTIC_ENABLE
haptic_shutdown();
#endif
// this is also done later in bootloader.c - not sure if it's neccesary here // this is also done later in bootloader.c - not sure if it's neccesary here
#ifdef BOOTLOADER_CATERINA #ifdef BOOTLOADER_CATERINA
*(uint16_t *)0x0800 = 0x7777; // these two are a-star-specific *(uint16_t *)0x0800 = 0x7777; // these two are a-star-specific
@ -257,6 +263,9 @@ bool process_record_quantum(keyrecord_t *record) {
#if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY) #if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY)
process_clicky(keycode, record) && process_clicky(keycode, record) &&
#endif //AUDIO_CLICKY #endif //AUDIO_CLICKY
#ifdef HAPTIC_ENABLE
process_haptic(keycode, record) &&
#endif //HAPTIC_ENABLE
process_record_kb(keycode, record) && process_record_kb(keycode, record) &&
#if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_KEYPRESSES) #if defined(RGB_MATRIX_ENABLE) && defined(RGB_MATRIX_KEYPRESSES)
process_rgb_matrix(keycode, record) && process_rgb_matrix(keycode, record) &&
@ -1049,6 +1058,9 @@ void matrix_init_quantum() {
#if defined(UNICODE_ENABLE) || defined(UNICODEMAP_ENABLE) || defined(UCIS_ENABLE) #if defined(UNICODE_ENABLE) || defined(UNICODEMAP_ENABLE) || defined(UCIS_ENABLE)
unicode_input_mode_init(); unicode_input_mode_init();
#endif #endif
#ifdef HAPTIC_ENABLE
haptic_init();
#endif
matrix_init_kb(); matrix_init_kb();
} }
@ -1091,6 +1103,10 @@ void matrix_scan_quantum() {
encoder_read(); encoder_read();
#endif #endif
#ifdef HAPTIC_ENABLE
haptic_task();
#endif
matrix_scan_kb(); matrix_scan_kb();
} }
#if defined(BACKLIGHT_ENABLE) && defined(BACKLIGHT_PIN) #if defined(BACKLIGHT_ENABLE) && defined(BACKLIGHT_PIN)

View file

@ -139,6 +139,10 @@ extern uint32_t default_layer_state;
#include "hd44780.h" #include "hd44780.h"
#endif #endif
#ifdef HAPTIC_ENABLE
#include "haptic.h"
#endif
//Function substitutions to ease GPIO manipulation //Function substitutions to ease GPIO manipulation
#ifdef __AVR__ #ifdef __AVR__
#define PIN_ADDRESS(p, offset) _SFR_IO8(ADDRESS_BASE + (p >> PORT_SHIFTER) + offset) #define PIN_ADDRESS(p, offset) _SFR_IO8(ADDRESS_BASE + (p >> PORT_SHIFTER) + offset)

View file

@ -446,25 +446,36 @@ enum quantum_keycodes {
KC_LOCK, KC_LOCK,
#endif #endif
#ifdef TERMINAL_ENABLE #ifdef TERMINAL_ENABLE
TERM_ON, TERM_ON,
TERM_OFF, TERM_OFF,
#endif #endif
EEPROM_RESET, EEPROM_RESET,
UNICODE_MODE_FORWARD, UNICODE_MODE_FORWARD,
UNICODE_MODE_REVERSE, UNICODE_MODE_REVERSE,
UNICODE_MODE_OSX, UNICODE_MODE_OSX,
UNICODE_MODE_LNX, UNICODE_MODE_LNX,
UNICODE_MODE_WIN, UNICODE_MODE_WIN,
UNICODE_MODE_BSD, UNICODE_MODE_BSD,
UNICODE_MODE_WINC, UNICODE_MODE_WINC,
// always leave at the end HPT_ON,
SAFE_RANGE HPT_OFF,
}; HPT_TOG,
HPT_RST,
HPT_FBK,
HPT_BUZ,
HPT_MODI,
HPT_MODD,
HPT_DWLI,
HPT_DWLD,
// always leave at the end
SAFE_RANGE
};
// Ability to use mods in layouts // Ability to use mods in layouts
#define LCTL(kc) (QK_LCTL | (kc)) #define LCTL(kc) (QK_LCTL | (kc))

View file

@ -45,6 +45,7 @@ void eeconfig_init_quantum(void) {
eeprom_update_byte(EECONFIG_AUDIO, 0xFF); // On by default eeprom_update_byte(EECONFIG_AUDIO, 0xFF); // On by default
eeprom_update_dword(EECONFIG_RGBLIGHT, 0); eeprom_update_dword(EECONFIG_RGBLIGHT, 0);
eeprom_update_byte(EECONFIG_STENOMODE, 0); eeprom_update_byte(EECONFIG_STENOMODE, 0);
eeprom_update_dword(EECONFIG_HAPTIC, 0);
eeconfig_init_kb(); eeconfig_init_kb();
} }
@ -177,3 +178,11 @@ uint32_t eeconfig_read_user(void) { return eeprom_read_dword(EECONFIG_USER)
void eeconfig_update_user(uint32_t val) { eeprom_update_dword(EECONFIG_USER, val); } void eeconfig_update_user(uint32_t val) { eeprom_update_dword(EECONFIG_USER, val); }
uint32_t eeconfig_read_haptic(void) { return eeprom_read_dword(EECONFIG_HAPTIC); }
/** \brief eeconfig update user
*
* FIXME: needs doc
*/
void eeconfig_update_haptic(uint32_t val) { eeprom_update_dword(EECONFIG_HAPTIC, val); }

View file

@ -41,6 +41,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define EECONFIG_KEYBOARD (uint32_t *)15 #define EECONFIG_KEYBOARD (uint32_t *)15
#define EECONFIG_USER (uint32_t *)19 #define EECONFIG_USER (uint32_t *)19
#define EECONFIG_HAPTIC (uint32_t*)24
/* debug bit */ /* debug bit */
#define EECONFIG_DEBUG_ENABLE (1<<0) #define EECONFIG_DEBUG_ENABLE (1<<0)
#define EECONFIG_DEBUG_MATRIX (1<<1) #define EECONFIG_DEBUG_MATRIX (1<<1)
@ -94,4 +96,9 @@ void eeconfig_update_kb(uint32_t val);
uint32_t eeconfig_read_user(void); uint32_t eeconfig_read_user(void);
void eeconfig_update_user(uint32_t val); void eeconfig_update_user(uint32_t val);
#ifdef HAPTIC_ENABLE
uint32_t eeconfig_read_haptic(void);
void eeconfig_update_haptic(uint32_t val);
#endif
#endif #endif