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,
    });
    // make the actual drv2605 class
    espp::Drv2605 drv2605(espp::Drv2605::Config{
        .device_address = espp::Drv2605::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 = espp::Drv2605::MotorType::LRA});
    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(espp::Drv2605::Library::LRA, ec);
    if (ec) {
      logger.error("select library failed: {}", ec.message());
    }
    // make the task which will cycle through all the waveforms
    auto task_fn = [&drv2605](std::mutex &m, std::condition_variable &cv) {
      static auto start = std::chrono::high_resolution_clock::now();
      auto now = std::chrono::high_resolution_clock::now();
      auto elapsed = std::chrono::duration<float>(now - start).count();
      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, (espp::Drv2605::Waveform)waveform, ec);
      if (ec) {
        logger.error("set waveform failed: {}", ec.message());
        return false;
      }
      drv2605.set_waveform(1, espp::Drv2605::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)espp::Drv2605::Waveform::MAX) {
        waveform = 0;
      }
      logger.info("{:.3f}, {}", elapsed, 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)

enumerator DIAGNOS

Diagnostics.

enumerator AUTOCAL

Auto-calibration.

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
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 void initialize(std::error_code &ec)

Initialize the DRV2605.

Parameters

ec – Error code to set if there is an error.

inline void start(std::error_code &ec)

Start playing the configured waveform / sequence.

Parameters

ec – Error code to set if there is an error.

inline void stop(std::error_code &ec)

Stop playing the waveform / sequence.

Parameters

ec – Error code to set if there is an error.

inline void 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.

inline void set_waveform(uint8_t slot, Waveform w, std::error_code &ec)

Set the waveform slot at slot to \w.

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 slot.

  • ec – Error code to set if there is an error.

inline void 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.

inline bool probe (std::error_code &ec) requires(UseAddress)

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) requires(UseAddress)

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) requires(UseAddress)

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

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.