Skip to content

Commit

Permalink
Haptic feedback generalized - DRV2605 and solenoids (qmk#4939)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ishtob authored and mechmerlin committed Feb 17, 2019
1 parent a7a647b commit 2cee371
Show file tree
Hide file tree
Showing 18 changed files with 754 additions and 52 deletions.
10 changes: 10 additions & 0 deletions common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,21 @@ endif

ifeq ($(strip $(HAPTIC_ENABLE)), DRV2605L)
COMMON_VPATH += $(DRIVER_PATH)/haptic
SRC += haptic.c
SRC += DRV2605L.c
SRC += i2c_master.c
OPT_DEFS += -DHAPTIC_ENABLE
OPT_DEFS += -DDRV2605L
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)
SRC += drivers/avr/hd44780.c
OPT_DEFS += -DHD44780_ENABLE
Expand Down
147 changes: 147 additions & 0 deletions docs/feature_haptic_feedback.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 12 additions & 12 deletions drivers/haptic/DRV2605L.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include <math.h>


uint8_t DRV2605L_transfer_buffer[20];
uint8_t DRV2605L_transfer_buffer[2];
uint8_t DRV2605L_tx_register[0];
uint8_t DRV2605L_read_buffer[0];
uint8_t DRV2605L_read_register;
Expand All @@ -34,6 +34,11 @@ void DRV_write(uint8_t drv_register, uint8_t settings) {
}

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;
if (MSG_OK != i2c_transmit_receive(DRV2605L_BASE_ADDRESS << 1,
DRV2605L_tx_register, 1,
Expand All @@ -42,14 +47,13 @@ uint8_t DRV_read(uint8_t regaddress) {
printf("err reading reg \n");
}
DRV2605L_read_register = (uint8_t)DRV2605L_read_buffer[0];
#endif
return DRV2605L_read_register;
}

void DRV_init(void)
{
i2c_init();
i2c_start(DRV2605L_BASE_ADDRESS);

/* 0x07 sets DRV2605 into calibration mode */
DRV_write(DRV_MODE,0x07);

Expand Down Expand Up @@ -104,21 +108,17 @@ void DRV_init(void)
C4_SET.Bits.C4_AUTO_CAL_TIME = AUTO_CAL_TIME;
DRV_write(DRV_CTRL_4, (uint8_t) C4_SET.Byte);
DRV_write(DRV_LIB_SELECTION,LIB_SELECTION);
//start autocalibration

DRV_write(DRV_GO, 0x01);

/* 0x00 sets DRV2605 out of standby and to use internal trigger
* 0x01 sets DRV2605 out of standby and to use external trigger */
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)
Expand Down
26 changes: 18 additions & 8 deletions drivers/haptic/DRV2605L.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,6 @@
#define FB_LOOPGAIN 1 /* For Low:0, Medium:1, High:2, Very High:3 */
#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 */
#if FB_ERM_LRA == 1
#ifndef V_RMS
Expand All @@ -49,6 +42,16 @@
#ifndef F_LRA
#define F_LRA 205
#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

/* Library Selection */
Expand All @@ -60,6 +63,13 @@
#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 */
#ifndef DRIVE_TIME
#define DRIVE_TIME 25
Expand Down Expand Up @@ -162,7 +172,6 @@ void DRV_write(const uint8_t drv_register, const uint8_t settings);
uint8_t DRV_read(const uint8_t regaddress);
void DRV_pulse(const uint8_t sequence);


typedef enum DRV_EFFECT{
clear_sequence = 0,
strong_click = 1,
Expand Down Expand Up @@ -288,6 +297,7 @@ typedef enum DRV_EFFECT{
smooth_hum3_30 = 121,
smooth_hum4_20 = 122,
smooth_hum5_10 = 123,
drv_effect_max = 124,
} DRV_EFFECT;

/* Register bit array unions */
Expand Down
Loading

0 comments on commit 2cee371

Please sign in to comment.