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

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.

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.

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
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 explicit Drv2605(const Config &config)

Construct and initialize the DRV2605.

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

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

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

set_log_level

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

get_log_level

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

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.

struct CalibratedData

Structure to hold the calibrated data for the DRV2605.

Public Members

uint8_t bemf_gain

Back-EMF gain, 0-3.

uint8_t a_cal_comp

Auto calibration compensation result, 0-255.

uint8_t a_cal_bemf

Auto calibration back-EMF result, 0-255.

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.

MotorType motor_type = {MotorType::ERM}

MotorType that this driver is driving.

bool auto_init = {true}

If true, the driver will initialize the DRV2605 on construction.

espp::Logger::Verbosity log_level = {espp::Logger::Verbosity::WARN}

Log verbosity for the Drv2605.

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.