/ adafruit_drv2605.py
adafruit_drv2605.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 Tony DiCola for Adafruit Industries 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 `adafruit_drv2605` 24 ==================================================== 25 26 CircuitPython module for the DRV2605 haptic feedback motor driver. See 27 examples/simpletest.py for a demo of the usage. 28 29 * Author(s): Tony DiCola 30 """ 31 from micropython import const 32 33 from adafruit_bus_device.i2c_device import I2CDevice 34 35 __version__ = "0.0.0-auto.0" 36 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DRV2605.git" 37 38 39 # pylint: disable=bad-whitespace 40 # Internal constants: 41 _DRV2605_ADDR = const(0x5A) 42 _DRV2605_REG_STATUS = const(0x00) 43 _DRV2605_REG_MODE = const(0x01) 44 _DRV2605_REG_RTPIN = const(0x02) 45 _DRV2605_REG_LIBRARY = const(0x03) 46 _DRV2605_REG_WAVESEQ1 = const(0x04) 47 _DRV2605_REG_WAVESEQ2 = const(0x05) 48 _DRV2605_REG_WAVESEQ3 = const(0x06) 49 _DRV2605_REG_WAVESEQ4 = const(0x07) 50 _DRV2605_REG_WAVESEQ5 = const(0x08) 51 _DRV2605_REG_WAVESEQ6 = const(0x09) 52 _DRV2605_REG_WAVESEQ7 = const(0x0A) 53 _DRV2605_REG_WAVESEQ8 = const(0x0B) 54 _DRV2605_REG_GO = const(0x0C) 55 _DRV2605_REG_OVERDRIVE = const(0x0D) 56 _DRV2605_REG_SUSTAINPOS = const(0x0E) 57 _DRV2605_REG_SUSTAINNEG = const(0x0F) 58 _DRV2605_REG_BREAK = const(0x10) 59 _DRV2605_REG_AUDIOCTRL = const(0x11) 60 _DRV2605_REG_AUDIOLVL = const(0x12) 61 _DRV2605_REG_AUDIOMAX = const(0x13) 62 _DRV2605_REG_RATEDV = const(0x16) 63 _DRV2605_REG_CLAMPV = const(0x17) 64 _DRV2605_REG_AUTOCALCOMP = const(0x18) 65 _DRV2605_REG_AUTOCALEMP = const(0x19) 66 _DRV2605_REG_FEEDBACK = const(0x1A) 67 _DRV2605_REG_CONTROL1 = const(0x1B) 68 _DRV2605_REG_CONTROL2 = const(0x1C) 69 _DRV2605_REG_CONTROL3 = const(0x1D) 70 _DRV2605_REG_CONTROL4 = const(0x1E) 71 _DRV2605_REG_VBAT = const(0x21) 72 _DRV2605_REG_LRARESON = const(0x22) 73 74 # User-facing mode value constants: 75 MODE_INTTRIG = 0x00 76 MODE_EXTTRIGEDGE = 0x01 77 MODE_EXTTRIGLVL = 0x02 78 MODE_PWMANALOG = 0x03 79 MODE_AUDIOVIBE = 0x04 80 MODE_REALTIME = 0x05 81 MODE_DIAGNOS = 0x06 82 MODE_AUTOCAL = 0x07 83 LIBRARY_EMPTY = 0x00 84 LIBRARY_TS2200A = 0x01 85 LIBRARY_TS2200B = 0x02 86 LIBRARY_TS2200C = 0x03 87 LIBRARY_TS2200D = 0x04 88 LIBRARY_TS2200E = 0x05 89 LIBRARY_LRA = 0x06 90 # pylint: enable=bad-whitespace 91 92 93 class DRV2605: 94 """TI DRV2605 haptic feedback motor driver module.""" 95 96 # Class-level buffer for reading and writing data with the sensor. 97 # This reduces memory allocations but means the code is not re-entrant or 98 # thread safe! 99 _BUFFER = bytearray(2) 100 101 def __init__(self, i2c, address=_DRV2605_ADDR): 102 self._device = I2CDevice(i2c, address) 103 # Check chip ID is 3 or 7 (DRV2605 or DRV2605L). 104 status = self._read_u8(_DRV2605_REG_STATUS) 105 device_id = (status >> 5) & 0x07 106 if device_id not in (3, 7): 107 raise RuntimeError("Failed to find DRV2605, check wiring!") 108 # Configure registers to initialize chip. 109 self._write_u8(_DRV2605_REG_MODE, 0x00) # out of standby 110 self._write_u8(_DRV2605_REG_RTPIN, 0x00) # no real-time-playback 111 self._write_u8(_DRV2605_REG_WAVESEQ1, 1) # strong click 112 self._write_u8(_DRV2605_REG_WAVESEQ2, 0) 113 self._write_u8(_DRV2605_REG_OVERDRIVE, 0) # no overdrive 114 self._write_u8(_DRV2605_REG_SUSTAINPOS, 0) 115 self._write_u8(_DRV2605_REG_SUSTAINNEG, 0) 116 self._write_u8(_DRV2605_REG_BREAK, 0) 117 self._write_u8(_DRV2605_REG_AUDIOMAX, 0x64) 118 # Set ERM open-loop mode. 119 self.use_ERM() 120 # turn on ERM_OPEN_LOOP 121 control3 = self._read_u8(_DRV2605_REG_CONTROL3) 122 self._write_u8(_DRV2605_REG_CONTROL3, control3 | 0x20) 123 # Default to internal trigger mode and TS2200 A library. 124 self.mode = MODE_INTTRIG 125 self.library = LIBRARY_TS2200A 126 self._sequence = _DRV2605_Sequence(self) 127 128 def _read_u8(self, address): 129 # Read an 8-bit unsigned value from the specified 8-bit address. 130 with self._device as i2c: 131 self._BUFFER[0] = address & 0xFF 132 i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=1) 133 return self._BUFFER[0] 134 135 def _write_u8(self, address, val): 136 # Write an 8-bit unsigned value to the specified 8-bit address. 137 with self._device as i2c: 138 self._BUFFER[0] = address & 0xFF 139 self._BUFFER[1] = val & 0xFF 140 i2c.write(self._BUFFER, end=2) 141 142 def play(self): 143 """Play back the select effect(s) on the motor.""" 144 self._write_u8(_DRV2605_REG_GO, 1) 145 146 def stop(self): 147 """Stop vibrating the motor.""" 148 self._write_u8(_DRV2605_REG_GO, 0) 149 150 @property 151 def mode(self): 152 """ 153 The mode of the chip. Should be a value of: 154 155 - MODE_INTTRIG: Internal triggering, vibrates as soon as you call 156 play(). Default mode. 157 - MODE_EXTTRIGEDGE: External triggering, edge mode. 158 - MODE_EXTTRIGLVL: External triggering, level mode. 159 - MODE_PWMANALOG: PWM/analog input mode. 160 - MODE_AUDIOVIBE: Audio-to-vibration mode. 161 - MODE_REALTIME: Real-time playback mode. 162 - MODE_DIAGNOS: Diagnostics mode. 163 - MODE_AUTOCAL: Auto-calibration mode. 164 165 See the datasheet for the meaning of modes beyond MODE_INTTRIG. 166 """ 167 return self._read_u8(_DRV2605_REG_MODE) 168 169 @mode.setter 170 def mode(self, val): 171 if not 0 <= val <= 7: 172 raise ValueError("Mode must be a value within 0-7!") 173 self._write_u8(_DRV2605_REG_MODE, val) 174 175 @property 176 def library(self): 177 """ 178 The library selected for waveform playback. Should be 179 a value of: 180 181 - LIBRARY_EMPTY: Empty 182 - LIBRARY_TS2200A: TS2200 library A (the default) 183 - LIBRARY_TS2200B: TS2200 library B 184 - LIBRARY_TS2200C: TS2200 library C 185 - LIBRARY_TS2200D: TS2200 library D 186 - LIBRARY_TS2200E: TS2200 library E 187 - LIBRARY_LRA: LRA library 188 189 See the datasheet for the meaning and description of effects in each 190 library. 191 """ 192 return self._read_u8(_DRV2605_REG_LIBRARY) & 0x07 193 194 @library.setter 195 def library(self, val): 196 if not 0 <= val <= 6: 197 raise ValueError("Library must be a value within 0-6!") 198 self._write_u8(_DRV2605_REG_LIBRARY, val) 199 200 @property 201 def sequence(self): 202 """List-like sequence of waveform effects. 203 Get or set an effect waveform for slot 0-6 by indexing the sequence 204 property with the slot number. A slot must be set to either an Effect() 205 or Pause() class. See the datasheet for a complete table of effect ID 206 values and the associated waveform / effect. 207 208 E.g. 'slot_0_effect = drv.sequence[0]', 'drv.sequence[0] = Effect(88)' 209 """ 210 return self._sequence 211 212 def set_waveform(self, effect_id, slot=0): 213 """Select an effect waveform for the specified slot (default is slot 0, 214 but up to 7 effects can be combined with slot values 0 to 6). See the 215 datasheet for a complete table of effect ID values and the associated 216 waveform / effect. 217 """ 218 if not 0 <= effect_id <= 123: 219 raise ValueError("Effect ID must be a value within 0-123!") 220 if not 0 <= slot <= 6: 221 raise ValueError("Slot must be a value within 0-6!") 222 self._write_u8(_DRV2605_REG_WAVESEQ1 + slot, effect_id) 223 224 # pylint: disable=invalid-name 225 def use_ERM(self): 226 """Use an eccentric rotating mass motor (the default).""" 227 feedback = self._read_u8(_DRV2605_REG_FEEDBACK) 228 self._write_u8(_DRV2605_REG_FEEDBACK, feedback & 0x7F) 229 230 # pylint: disable=invalid-name 231 def use_LRM(self): 232 """Use a linear resonance actuator motor.""" 233 feedback = self._read_u8(_DRV2605_REG_FEEDBACK) 234 self._write_u8(_DRV2605_REG_FEEDBACK, feedback | 0x80) 235 236 237 class Effect: 238 """DRV2605 waveform sequence effect.""" 239 240 def __init__(self, effect_id): 241 self._effect_id = 0 242 # pylint: disable=invalid-name 243 self.id = effect_id 244 245 @property 246 def raw_value(self): 247 """Raw effect ID.""" 248 return self._effect_id 249 250 @property 251 # pylint: disable=invalid-name 252 def id(self): 253 """Effect ID.""" 254 return self._effect_id 255 256 @id.setter 257 # pylint: disable=invalid-name 258 def id(self, effect_id): 259 """Set the effect ID.""" 260 if not 0 <= effect_id <= 123: 261 raise ValueError("Effect ID must be a value within 0-123!") 262 self._effect_id = effect_id 263 264 def __repr__(self): 265 return "{}({})".format(type(self).__qualname__, self.id) 266 267 268 class Pause: 269 """DRV2605 waveform sequence timed delay.""" 270 271 def __init__(self, duration): 272 # Bit 7 must be set for a slot to be interpreted as a delay 273 self._duration = 0x80 274 self.duration = duration 275 276 @property 277 def raw_value(self): 278 """Raw pause duration.""" 279 return self._duration 280 281 @property 282 def duration(self): 283 """Pause duration in seconds.""" 284 # Remove wait time flag bit and convert duration to seconds 285 return (self._duration & 0x7F) / 100.0 286 287 @duration.setter 288 def duration(self, duration): 289 """Sets the pause duration in seconds.""" 290 if not 0.0 <= duration <= 1.27: 291 raise ValueError("Pause duration must be a value within 0.0-1.27!") 292 # Add wait time flag bit and convert duration to centiseconds 293 self._duration = 0x80 | round(duration * 100.0) 294 295 def __repr__(self): 296 return "{}({})".format(type(self).__qualname__, self.duration) 297 298 299 class _DRV2605_Sequence: 300 """Class to enable List-like indexing of the waveform sequence slots.""" 301 302 def __init__(self, DRV2605_instance): 303 self._drv2605 = DRV2605_instance 304 305 def __setitem__(self, slot, effect): 306 """Write an Effect or Pause to a slot.""" 307 if not 0 <= slot <= 6: 308 raise IndexError("Slot must be a value within 0-6!") 309 if not isinstance(effect, (Effect, Pause)): 310 raise TypeError("Effect must be either an Effect() or Pause()!") 311 # pylint: disable=protected-access 312 self._drv2605._write_u8(_DRV2605_REG_WAVESEQ1 + slot, effect.raw_value) 313 314 def __getitem__(self, slot): 315 """Read an effect ID from a slot. Returns either a Pause or Effect class.""" 316 if not 0 <= slot <= 6: 317 raise IndexError("Slot must be a value within 0-6!") 318 # pylint: disable=protected-access 319 slot_contents = self._drv2605._read_u8(_DRV2605_REG_WAVESEQ1 + slot) 320 if slot_contents & 0x80: 321 return Pause((slot_contents & 0x7F) / 100.0) 322 return Effect(slot_contents) 323 324 def __iter__(self): 325 """Returns an iterator over the waveform sequence slots.""" 326 for slot in range(0, 7): 327 yield self[slot] 328 329 def __repr__(self): 330 """Return a string representation of all slot's effects.""" 331 return repr(list(self))