DRV2605 Haptic Motor Driver
The DRV2605 haptic motor driver component allows the user to configure and play custom or preconfigured haptic feedback sequences via a serial interface such as I2C. It supports directly driving ECM (eccentric rotating mass) and LRA (linear resonant actuator) type haptic motors. It also supports fully custom waveforms (e.g. via using the audio, pwm / analog functions) as well as a preset library of 123 different haptic waveforms which can be played in sequences of up to 8 waveforms.
API Reference
Header File
Classes
-
class Drv2605 : public espp::BasePeripheral<>
Class for controlling the Texas Instruments DRV2605 Haptic Motor Driver. Drives ECM (eccentric rotating mass) and LRA (linear resonant actuator) types of haptic motors. The datasheet for the DRV2605 can be found here: https://www.ti.com/lit/ds/symlink/drv2605.pdf?ts=1678892742599.
DRV2605 Example
// make the I2C that we'll use to communicate espp::I2c i2c({ .port = I2C_NUM_1, .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); using Driver = espp::Drv2605; // make the actual drv2605 class Driver drv2605(Driver::Config{ .device_address = Driver::DEFAULT_ADDRESS, .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), .read_register = std::bind(&espp::I2c::read_at_register, &i2c, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), .motor_type = Driver::MotorType::LRA, .log_level = espp::Logger::Verbosity::INFO, }); std::error_code ec; // we're using an ERM motor, so select an ERM library (1-5). // drv2605.select_library(1, ec); // we're using an LRA motor, so select an LRA library (6). drv2605.select_library(Driver::Library::LRA, ec); if (ec) { logger.error("select library failed: {}", ec.message()); } // do the calibration for the LRA motor Driver::LraCalibrationSettings lra_calibration_settings{}; lra_calibration_settings.rated_voltage = 255; lra_calibration_settings.overdrive_clamp = 255; lra_calibration_settings.drive_time = Driver::lra_freq_to_drive_time(200.0f); Driver::CalibratedData calibrated_data; if (!drv2605.calibrate(lra_calibration_settings, calibrated_data, ec)) { logger.error("calibration failed: {}", ec.message()); return; } logger.info("calibration complete: {}", calibrated_data); // make the task which will cycle through all the waveforms auto task_fn = [&drv2605](std::mutex &m, std::condition_variable &cv) { static uint8_t waveform = 0; std::error_code ec; drv2605.stop(ec); if (ec) { logger.error("stop failed: {}", ec.message()); return false; } drv2605.set_waveform(0, (Driver::Waveform)waveform, ec); if (ec) { logger.error("set waveform failed: {}", ec.message()); return false; } drv2605.set_waveform(1, Driver::Waveform::END, ec); if (ec) { logger.error("set waveform failed: {}", ec.message()); return false; } drv2605.start(ec); if (ec) { logger.error("start failed: {}", ec.message()); return false; } waveform++; if (waveform > (uint8_t)Driver::Waveform::MAX) { waveform = 0; } logger.info("{}", waveform); // NOTE: sleeping in this way allows the sleep to exit early when the // task is being stopped / destroyed { using namespace std::chrono_literals; std::unique_lock<std::mutex> lk(m); cv.wait_for(lk, 1s); } // we don't want to stop, so return false return false; }; auto task = espp::Task::make_unique({.callback = task_fn, .task_config = { .name = "example", .stack_size_bytes{4 * 1024}, }, .log_level = espp::Logger::Verbosity::INFO}); task->start();
Public Types
-
enum class Mode : uint8_t
The mode of the vibration.
Values:
-
enumerator INTTRIG
Internal Trigger (call star() to start playback)
-
enumerator EXTTRIGEDGE
External edge trigger (rising edge on IN pin starts playback)
-
enumerator EXTTRIGLVL
External level trigger (playback follows state of IN pin)
-
enumerator PWMANALOG
PWM/Analog input.
-
enumerator AUDIOVIBE
Audio-to-vibe mode.
-
enumerator REALTIME
Real-time playback (RTP). Drives the actuator based on the value of the RTPIN register.
-
enumerator DIAGNOS
Diagnostics.
-
enumerator AUTOCAL
GO/START bit to start. Calibration is complete when the GO/START bit self-clears.
Auto-calibration. User must set all required input parameters then set the
-
enumerator INTTRIG
-
enum class Waveform : uint8_t
The waveforms supported by the DRV2605. It has 123 different waveforms, with waveform ID 0 being a special END identifier used when playing multiple waveforms in sequence.
Note
All of the waveform names have not been enumerated here. For a complete list, please see https://learn.adafruit.com/assets/72593
Values:
-
enumerator END
Signals this is the end of the waveforms to play.
-
enumerator STRONG_CLICK
-
enumerator SHARP_CLICK
-
enumerator SOFT_BUMP
-
enumerator DOUBLE_CLICK
-
enumerator TRIPLE_CLICK
-
enumerator SOFT_FUZZ
-
enumerator STRONG_BUZZ
-
enumerator ALERT_750MS
-
enumerator ALERT_1000MS
-
enumerator BUZZ1
-
enumerator BUZZ2
-
enumerator BUZZ3
-
enumerator BUZZ4
-
enumerator BUZZ5
-
enumerator PULSING_STRONG_1
-
enumerator PULSING_STRONG_2
-
enumerator TRANSITION_CLICK_1
-
enumerator TRANSITION_HUM_1
-
enumerator MAX
Values >= to this do not correspond to valid waveforms.
-
enumerator END
-
enum class MotorType
The type of vibration motor connected to the Drv2605.
Values:
-
enumerator ERM
Eccentric Rotating Mass (more common, therefore default)
-
enumerator LRA
Linear Resonant Actuator.
-
enumerator ERM
-
enum class Library
The library of waveforms to use.
Note
The DRV2605 has 7 different libraries of waveforms. The first library is empty, and the next 5 are ERM (eccentric rotating mass) libraries. The last library is an LRA (linear resonant actuator) library.
Values:
-
enumerator EMPTY
-
enumerator ERM_0
-
enumerator ERM_1
-
enumerator ERM_2
-
enumerator ERM_3
-
enumerator ERM_4
-
enumerator LRA
-
enumerator EMPTY
-
using ErmCalibrationSettings = BaseCalibrationSettings
Calibration Routine Settings structure for LRA motors.
-
typedef std::function<bool(uint8_t)> probe_fn
Function to probe the peripheral
- Param address:
The address to probe
- Return:
True if the peripheral is found at the given address
Public Functions
-
inline bool initialize(std::error_code &ec)
Initialize the DRV2605.
- Parameters:
ec – Error code to set if there is an error.
- Returns:
true if the initialization was successful, false if there was an error.
-
inline bool reset(std::error_code &ec)
Reset the DRV2605.
- Parameters:
ec – Error code to set if there is an error.
- Returns:
true if the reset was successful, false if there was an error.
-
inline bool set_standby(bool standby_enabled, std::error_code &ec)
Set the standby mode of the DRV2605.
- Parameters:
standby_enabled – If true, the DRV2605 will enter standby mode.
ec – Error code to set if there is an error.
- Returns:
true if the standby mode was set successfully, false if there was an error.
-
inline bool set_mode(Mode mode, std::error_code &ec)
Set the mode of the vibration.
- Parameters:
mode – Mode to set to.
ec – Error code to set if there is an error.
- Returns:
true if the mode was set successfully, false if there was an error.
-
inline bool start(std::error_code &ec)
Start playing the configured waveform / sequence.
- Parameters:
ec – Error code to set if there is an error.
- Returns:
true if the start was successful, false if there was an error.
-
inline bool stop(std::error_code &ec)
Stop playing the waveform / sequence.
- Parameters:
ec – Error code to set if there is an error.
- Returns:
true if the stop was successful, false if there was an error.
-
inline bool set_rtp_data_format_unsigned(bool use_unsigned, std::error_code &ec)
Set the RTP (Real-Time Playback) data format.
This method sets the data format for the RTP mode. The DRV2605 supports both signed and unsigned data formats for the RTP mode. The default data format is signed, but you can change it to unsigned by calling this method with
use_unsigned
set to true.Note
This is only valid when the mode is set to Mode::REALTIME.
- Parameters:
use_unsigned – If true, the data format will be set to unsigned, otherwise it will be set to signed.
ec – Error code to set if there is an error.
- Returns:
true if the data format was set successfully, false if there was an error.
-
inline bool set_rtp_pwm_unsigned(uint8_t pwm_value, std::error_code &ec)
Set the RTP (Real-Time Playback) mode PWM value as unsigned.
Note
This is only valid when the mode is set to Mode::REALTIME.
Note
This is only valid when the RTP data format is set to unsigned (requires set_rtp_data_format_unsigned(true)).
- Parameters:
pwm_value – The PWM value to set (0-255).
ec – Error code to set if there is an error.
- Returns:
true if the PWM value was set successfully, false if there was an error.
-
inline bool set_rtp_pwm_signed(int8_t pwm_value, std::error_code &ec)
Set the RTP (Real-Time Playback) mode PWM value.
Note
This is only valid when the mode is set to Mode::REALTIME.
Note
This is only valid when the RTP data format is set to signed (default).
- Parameters:
pwm_value – The PWM value to set (-128 to 127).
ec – Error code to set if there is an error.
- Returns:
true if the PWM value was set successfully, false if there was an error.
-
inline bool set_waveform(uint8_t slot, Waveform w, std::error_code &ec)
Set the waveform at slot.
Note
When calling start() to play the configured waveform slots, the driver will always start playing from slot 0 and will continue until it reaches a slot that has been configured with the Waveform::END.
- Parameters:
slot – The slot (0-8) to set.
w – The Waveform to play in slot.
ec – Error code to set if there is an error.
- Returns:
true if the waveform was set successfully, false if there was an error.
-
inline bool select_library(Library lib, std::error_code &ec)
Select the waveform library to use.
- Parameters:
lib – Library to use, 0=Empty, 1-5 are ERM, 6 is LRA
ec – Error code to set if there is an error.
- Returns:
true if the library was selected successfully, false if there was an error.
-
inline bool calibrate(const ErmCalibrationSettings &cal_conf, CalibratedData &cal_data_out, std::error_code &ec)
Calibrate the DRV2605 for an ERM motor.
This method performs the auto-calibration routine for the DRV2605. It will put the DRV2605 into auto-calibration mode, and then start the calibration process. The calibration data will be stored in the
cal_data_out
parameter.- Parameters:
cal_conf – The calibration settings to use.
cal_data_out – The structure to store the calibration data.
ec – Error code to set if there is an error.
- Returns:
true if the calibration was successful, false if there was an error.
-
inline bool calibrate(const LraCalibrationSettings &cal_conf, CalibratedData &cal_data_out, std::error_code &ec)
Calibrate the DRV2605 for an LRA motor.
This method performs the auto-calibration routine for the DRV2605. It will put the DRV2605 into auto-calibration mode, and then start the calibration process. The calibration data will be stored in the
cal_data_out
parameter.- Parameters:
cal_conf – The calibration settings to use.
cal_data_out – The structure to store the calibration data.
ec – Error code to set if there is an error.
- Returns:
true if the calibration was successful, false if there was an error.
-
inline bool read_calibration_data(CalibratedData &cal_data_out, std::error_code &ec)
Read the calibration data from the DRV2605.
This method reads the calibration data from the DRV2605. It is used to read the BEMF gain, A_CAL_COMP, and A_CAL_BEMF values.
- Parameters:
cal_data_out – The structure to store the calibration data.
ec – Error code to set if there is an error.
- Returns:
true if the calibration data was read successfully, false if there was an error.
-
inline bool set_calibration_data(const CalibratedData &cal_data, std::error_code &ec)
Set the calibration data for the DRV2605.
This method sets the calibration data for the DRV2605. It is used to set the BEMF gain, A_CAL_COMP, and A_CAL_BEMF values.
- Parameters:
cal_data – The calibration data to set.
ec – Error code to set if there is an error.
- Returns:
true if the calibration data was set successfully, false if there was an error.
-
inline bool probe(std::error_code &ec)
Probe the peripheral
Note
This function is thread safe
Note
If the probe function is not set, this function will return false and set the error code to operation_not_supported
Note
This function is only available if UseAddress is true
- Parameters:
ec – The error code to set if there is an error
- Returns:
True if the peripheral is found
-
inline void set_address(uint8_t address)
Set the address of the peripheral
Note
This function is thread safe
Note
This function is only available if UseAddress is true
- Parameters:
address – The address of the peripheral
-
inline void set_probe(const probe_fn &probe)
Set the probe function
Note
This function is thread safe
Note
This should rarely be used, as the probe function is usually set in the constructor. If you need to change the probe function, consider using the set_config function instead.
Note
This function is only available if UseAddress is true
- Parameters:
probe – The probe function
-
inline void set_write(const write_fn &write)
Set the write function
Note
This function is thread safe
Note
This should rarely be used, as the write function is usually set in the constructor. If you need to change the write function, consider using the set_config function instead.
- Parameters:
write – The write function
-
inline void set_read(const read_fn &read)
Set the read function
Note
This function is thread safe
Note
This should rarely be used, as the read function is usually set in the constructor. If you need to change the read function, consider using the set_config function instead.
- Parameters:
read – The read function
-
inline void set_read_register(const read_register_fn &read_register)
Set the read register function
Note
This function is thread safe
Note
This should rarely be used, as the read register function is usually set in the constructor. If you need to change the read register function, consider using the set_config function instead.
- Parameters:
read_register – The read register function
-
inline void set_write_then_read(const write_then_read_fn &write_then_read)
Set the write then read function
Note
This function is thread safe
Note
This should rarely be used, as the write then read function is usually set in the constructor. If you need to change the write then
- Parameters:
write_then_read – The write then read function
-
inline void set_separate_write_then_read_delay(const std::chrono::milliseconds &delay)
Set the delay between the write and read operations in write_then_read
Note
This function is thread safe
Note
This should rarely be used, as the delay is usually set in the constructor. If you need to change the delay, consider using the set_config function instead.
Note
This delay is only used if the write_then_read function is not set to a custom function and the write and read functions are separate functions.
- Parameters:
delay – The delay between the write and read operations in write_then_read
-
inline void set_config(const Config &config)
Set the configuration for the peripheral
Note
This function is thread safe
Note
The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.
- Parameters:
config – The configuration for the peripheral
-
inline void set_config(Config &&config)
Set the configuration for the peripheral
Note
This function is thread safe
Note
The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.
- Parameters:
config – The configuration for the peripheral
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns:
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters:
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters:
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters:
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns:
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters:
rate_limit – The rate limit to use for the logger
Public Static Functions
-
static inline uint8_t lra_freq_to_drive_time(float lra_freq)
Convert LRA frequency to drive time.
The drive time is half the period of the LRA frequency, in ms. The LRA frequency is in Hz, so we need to convert it to ms. The formula is:
drive_time_ms = 1000 / (2 * lra_freq).
drive_time = (drive_time_ms - 0.5) / 0.1
- Parameters:
lra_freq – The frequency of the LRA in Hz.
- Returns:
The drive time in bits (0-31).
Public Static Attributes
-
static constexpr uint8_t DEFAULT_ADDRESS = (0x5A)
Default I2C address of the DRV2605. NOTE: this cannot be changed, as the DRV2605 does not support changing its I2C address.
-
struct BaseCalibrationSettings
Calibration Routine Settings structure for the DRV2605.
Calibration Routine Settings structure for ERM motors.
Subclassed by espp::Drv2605::LraCalibrationSettings
Public Members
-
uint8_t fb_brake_factor
Feedback brake factor, 0-7.
-
uint8_t loop_gain
Loop gain, 0-3.
-
uint8_t rated_voltage
Rated voltage, 0-255.
-
uint8_t overdrive_clamp
Overdrive clamp, 0-255.
-
uint8_t auto_cal_time
Auto calibration time, 0-3.
-
uint8_t drive_time
Drive time, 0-31. For LRA it should be used as an initial guess for the half-period. E.g. if the resonance freq is 200 Hz, then drive time should be 2.5ms. For ERM it controls the rate for back-EMF sampling. Higher drive times imply lower back-EMF sampling freq which cause the feedback to react at slower rate.
-
uint8_t fb_brake_factor
-
struct CalibratedData
Structure to hold the calibrated data for the DRV2605.
-
struct Config
Configuration structure for the DRV2605.
Public Members
-
uint8_t device_address = DEFAULT_ADDRESS
I2C address of the device.
-
BasePeripheral::write_fn write
Function for writing a byte to a register on the Drv2605.
-
BasePeripheral::read_register_fn read_register
Function for reading a register from the Drv2605.
-
bool auto_init = {true}
If true, the driver will initialize the DRV2605 on construction.
-
uint8_t device_address = DEFAULT_ADDRESS
-
struct LraCalibrationSettings : public espp::Drv2605::BaseCalibrationSettings
Public Members
-
uint8_t sample_time
Sample time, 0-3.
-
uint8_t blanking_time
Blanking time, 0-3.
-
uint8_t idiss_time
IDISS time, 0-3.
-
uint8_t fb_brake_factor
Feedback brake factor, 0-7.
-
uint8_t loop_gain
Loop gain, 0-3.
-
uint8_t rated_voltage
Rated voltage, 0-255.
-
uint8_t overdrive_clamp
Overdrive clamp, 0-255.
-
uint8_t auto_cal_time
Auto calibration time, 0-3.
-
uint8_t drive_time
Drive time, 0-31. For LRA it should be used as an initial guess for the half-period. E.g. if the resonance freq is 200 Hz, then drive time should be 2.5ms. For ERM it controls the rate for back-EMF sampling. Higher drive times imply lower back-EMF sampling freq which cause the feedback to react at slower rate.
-
uint8_t sample_time
-
enum class Mode : uint8_t