/ 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))