/ adafruit_crickit.py
adafruit_crickit.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2018 Dan Halbert 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_crickit`
 24  ==========================
 25  
 26  Convenience library for using the Adafruit Crickit robotics boards.
 27  
 28  * Author(s): Dan Halbert
 29  
 30  Implementation Notes
 31  --------------------
 32  
 33  **Hardware:**
 34  
 35     `Adafruit Crickit for Circuit Playground Express <https://www.adafruit.com/3093>`_
 36     `Adafruit Crickit FeatherWing <https://www.adafruit.com/3343>`_
 37  
 38  **Software and Dependencies:**
 39  
 40  * Adafruit CircuitPython firmware for the supported boards:
 41    https://github.com/adafruit/circuitpython/releases
 42  """
 43  
 44  import sys
 45  
 46  import board
 47  
 48  from micropython import const
 49  
 50  # pylint: disable=wrong-import-position
 51  try:
 52      lib_index = sys.path.index("/lib")  # pylint: disable=invalid-name
 53      if lib_index < sys.path.index(".frozen"):
 54          # Prefer frozen modules over those in /lib.
 55          sys.path.insert(lib_index, ".frozen")
 56  except ValueError:
 57      # Don't change sys.path if it doesn't contain "lib" or ".frozen".
 58      pass
 59  
 60  from adafruit_seesaw.seesaw import Seesaw
 61  from adafruit_seesaw.crickit import Crickit_Pinmap
 62  from adafruit_seesaw.pwmout import PWMOut
 63  from adafruit_motor.servo import Servo, ContinuousServo
 64  from adafruit_motor.motor import DCMotor
 65  from adafruit_motor.stepper import StepperMotor
 66  
 67  __version__ = "0.0.0-auto.0"
 68  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Crickit.git"
 69  
 70  
 71  _SERVO1 = const(17)
 72  _SERVO2 = const(16)
 73  _SERVO3 = const(15)
 74  _SERVO4 = const(14)
 75  
 76  _MOTOR1 = (22, 23)
 77  _MOTOR2 = (19, 18)
 78  # Order as needed for steppers.
 79  _MOTOR_STEPPER = _MOTOR1 + _MOTOR2
 80  
 81  _DRIVE1 = const(13)
 82  _DRIVE2 = const(12)
 83  _DRIVE3 = const(43)
 84  _DRIVE4 = const(42)
 85  
 86  # Order as needed for steppers.
 87  _DRIVE_STEPPER = (_DRIVE1, _DRIVE3, _DRIVE2, _DRIVE4)
 88  
 89  
 90  _TOUCH1 = const(4)
 91  _TOUCH2 = const(5)
 92  _TOUCH3 = const(6)
 93  _TOUCH4 = const(7)
 94  
 95  _NEOPIXEL = const(20)
 96  _SS_PIXEL = const(27)
 97  
 98  # pylint: disable=too-few-public-methods
 99  class CrickitTouchIn:
100      """Imitate touchio.TouchIn."""
101  
102      def __init__(self, seesaw, pin):
103          self._seesaw = seesaw
104          self._pin = pin
105          self.threshold = self.raw_value + 100
106  
107      @property
108      def raw_value(self):
109          """The raw touch measurement as an `int`. (read-only)"""
110          return self._seesaw.touch_read(self._pin)
111  
112      @property
113      def value(self):
114          """Whether the touch pad is being touched or not. (read-only)"""
115          return self.raw_value > self.threshold
116  
117  
118  # pylint: disable=too-many-public-methods
119  class Crickit:
120      """Represents a Crickit board. Provides a number of devices available via properties, such as
121      ``servo_1``. Devices are created on demand the first time they are referenced.
122  
123      It's fine to refer a device multiple times via its property, but it's faster and results
124      in more compact code to assign a device to a variable.
125  
126      .. code-block:: python
127  
128        import time
129        from adafruit_crickit import crickit
130  
131        # This is fine:
132        crickit.servo_1.angle = 0
133        time.sleep(1)
134        crickit.servo_1.angle = 90
135        time.sleep(1)
136  
137        # This is slightly faster and more compact:
138        servo_1 = crickit.servo_1
139        servo_1.angle = 0
140        time.sleep(1)
141        servo_1.angle = 90
142        time.sleep(1)
143      """
144  
145      SIGNAL1 = 2
146      """Signal 1 terminal"""
147      SIGNAL2 = 3
148      """Signal 2 terminal"""
149      SIGNAL3 = 40
150      """Signal 3 terminal"""
151      SIGNAL4 = 41
152      """Signal 4 terminal"""
153      SIGNAL5 = 11
154      """Signal 5 terminal"""
155      SIGNAL6 = 10
156      """Signal 6 terminal"""
157      SIGNAL7 = 9
158      """Signal 7 terminal"""
159      SIGNAL8 = 8
160      """Signal 8 terminal"""
161  
162      def __init__(self, seesaw):
163          self._seesaw = seesaw
164          self._seesaw.pin_mapping = Crickit_Pinmap
165          # Associate terminal(s) with certain devices.
166          # Used to find existing devices.
167          self._devices = dict()
168          self._neopixel = None
169          self._onboard_pixel = None
170  
171      @property
172      def seesaw(self):
173          """The Seesaw object that talks to the Crickit. Use this object to manipulate the
174          signal pins that correspond to Crickit terminals.
175  
176          .. code-block:: python
177  
178            from adafruit_crickit import crickit
179  
180            ss = crickit.seesaw
181            ss.pin_mode(crickit.SIGNAL4, ss.OUTPUT)
182            ss.digital_write(crickit.SIGNAL4], True)
183          """
184  
185          return self._seesaw
186  
187      @property
188      def servo_1(self):
189          """``adafruit_motor.servo.Servo`` object on Servo 1 terminal"""
190          return self._servo(_SERVO1, Servo)
191  
192      @property
193      def servo_2(self):
194          """``adafruit_motor.servo.Servo`` object on Servo 2 terminal"""
195          return self._servo(_SERVO2, Servo)
196  
197      @property
198      def servo_3(self):
199          """``adafruit_motor.servo.Servo`` object on Servo 3 terminal"""
200          return self._servo(_SERVO3, Servo)
201  
202      @property
203      def servo_4(self):
204          """``adafruit_motor.servo.Servo`` object on Servo 4 terminal"""
205          return self._servo(_SERVO4, Servo)
206  
207      @property
208      def continuous_servo_1(self):
209          """``adafruit_motor.servo.ContinuousServo`` object on Servo 1 terminal"""
210          return self._servo(_SERVO1, ContinuousServo)
211  
212      @property
213      def continuous_servo_2(self):
214          """``adafruit_motor.servo.ContinuousServo`` object on Servo 2 terminal"""
215          return self._servo(_SERVO2, ContinuousServo)
216  
217      @property
218      def continuous_servo_3(self):
219          """``adafruit_motor.servo.ContinuousServo`` object on Servo 3 terminal"""
220          return self._servo(_SERVO3, ContinuousServo)
221  
222      @property
223      def continuous_servo_4(self):
224          """``adafruit_motor.servo.ContinuousServo`` object on Servo 4 terminal"""
225          return self._servo(_SERVO4, ContinuousServo)
226  
227      def _servo(self, terminal, servo_class):
228          device = self._devices.get(terminal, None)
229          if not isinstance(device, servo_class):
230              pwm = PWMOut(self._seesaw, terminal)
231              pwm.frequency = 50
232              device = servo_class(pwm)
233              self._devices[terminal] = device
234          return device
235  
236      @property
237      def dc_motor_1(self):
238          """``adafruit_motor.motor.DCMotor`` object on Motor 1 terminals"""
239          return self._motor(_MOTOR1, DCMotor)
240  
241      @property
242      def dc_motor_2(self):
243          """``adafruit_motor.motor.DCMotor`` object on Motor 2 terminals"""
244          return self._motor(_MOTOR2, DCMotor)
245  
246      @property
247      def stepper_motor(self):
248          """``adafruit_motor.motor.StepperMotor`` object on Motor 1 and Motor 2 terminals"""
249          return self._motor(_MOTOR_STEPPER, StepperMotor)
250  
251      @property
252      def drive_stepper_motor(self):
253          """``adafruit_motor.motor.StepperMotor`` object on Drive terminals"""
254          return self._motor(_DRIVE_STEPPER, StepperMotor)
255  
256      @property
257      def feather_drive_stepper_motor(self):
258          """``adafruit_motor.motor.StepperMotor`` object on Drive terminals on Crickit FeatherWing"""
259          return self._motor(tuple(reversed(_DRIVE_STEPPER)), StepperMotor)
260  
261      def _motor(self, terminals, motor_class):
262          device = self._devices.get(terminals, None)
263          if not isinstance(device, motor_class):
264              device = motor_class(
265                  *(PWMOut(self._seesaw, terminal) for terminal in terminals)
266              )
267              self._devices[terminals] = device
268          return device
269  
270      @property
271      def drive_1(self):
272          """``adafruit_seesaw.pwmout.PWMOut`` object on Drive 1 terminal, with ``frequency=1000``"""
273          return self._drive(_DRIVE1)
274  
275      @property
276      def drive_2(self):
277          """``adafruit_seesaw.pwmout.PWMOut`` object on Drive 2 terminal, with ``frequency=1000``"""
278          return self._drive(_DRIVE2)
279  
280      @property
281      def drive_3(self):
282          """``adafruit_seesaw.pwmout.PWMOut`` object on Drive 3 terminal, with ``frequency=1000``"""
283          return self._drive(_DRIVE3)
284  
285      @property
286      def drive_4(self):
287          """``adafruit_seesaw.pwmout.PWMOut`` object on Drive 4 terminal, with ``frequency=1000``"""
288          return self._drive(_DRIVE4)
289  
290      feather_drive_1 = drive_4
291      """``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 1 terminal,
292      with ``frequency=1000``
293      """
294      feather_drive_2 = drive_3
295      """``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 2 terminal,
296      with ``frequency=1000``
297      """
298      feather_drive_3 = drive_2
299      """``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 3 terminal,
300      with ``frequency=1000``
301      """
302      feather_drive_4 = drive_1
303      """``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 4 terminal,
304      with ``frequency=1000``
305      """
306  
307      def _drive(self, terminal):
308          device = self._devices.get(terminal, None)
309          if not isinstance(device, PWMOut):
310              device = PWMOut(self._seesaw, terminal)
311              device.frequency = 1000
312              self._devices[terminal] = device
313          return device
314  
315      @property
316      def touch_1(self):
317          """``adafruit_crickit.CrickitTouchIn`` object on Touch 1 terminal"""
318          return self._touch(_TOUCH1)
319  
320      @property
321      def touch_2(self):
322          """``adafruit_crickit.CrickitTouchIn`` object on Touch 2 terminal"""
323          return self._touch(_TOUCH2)
324  
325      @property
326      def touch_3(self):
327          """``adafruit_crickit.CrickitTouchIn`` object on Touch 3 terminal"""
328          return self._touch(_TOUCH3)
329  
330      @property
331      def touch_4(self):
332          """``adafruit_crickit.CrickitTouchIn`` object on Touch 4 terminal"""
333          return self._touch(_TOUCH4)
334  
335      def _touch(self, terminal):
336          touch_in = self._devices.get(terminal, None)
337          if not touch_in:
338              touch_in = CrickitTouchIn(self._seesaw, terminal)
339              self._devices[terminal] = touch_in
340          return touch_in
341  
342      @property
343      def neopixel(self):
344          """```adafruit_seesaw.neopixel`` object on NeoPixel terminal.
345          Raises ValueError if ``init_neopixel`` has not been called.
346          """
347          if not self._neopixel:
348              raise ValueError("Call init_neopixel first")
349          return self._neopixel
350  
351      def init_neopixel(
352          self, n, *, bpp=3, brightness=1.0, auto_write=True, pixel_order=None
353      ):
354          """Set up a seesaw.NeoPixel object
355  
356          .. note:: On the CPX Crickit board, the NeoPixel terminal is by default
357            controlled by CPX pin A1, and is not controlled by seesaw. So this object
358            will not be usable. Instead, use the regular NeoPixel library
359            and specify ``board.A1`` as the pin.
360  
361          You can change the jumper connection on the bottom of the CPX Crickit board
362          to move control of the NeoPixel terminal to seesaw pin #20 (terminal.NEOPIXEL).
363          In addition, the Crickit FeatherWing always uses seesaw pin #20.
364          In either of those cases, this object will work.
365  
366          .. code-block:: python
367  
368            from adafruit_crickit.crickit import crickit
369  
370            crickit.init_neopixel(24)
371            crickit.neopixel.fill((100, 0, 0))
372          """
373          from adafruit_seesaw.neopixel import (  # pylint: disable=import-outside-toplevel
374              NeoPixel,
375          )
376  
377          self._neopixel = NeoPixel(
378              self._seesaw,
379              _NEOPIXEL,
380              n,
381              bpp=bpp,
382              brightness=brightness,
383              auto_write=auto_write,
384              pixel_order=pixel_order,
385          )
386  
387      @property
388      def onboard_pixel(self):
389          """```adafruit_seesaw.neopixel`` object on the Seesaw on-board NeoPixel.
390          Initialize on-board NeoPixel and clear upon first use.
391          """
392          if not self._onboard_pixel:
393              from adafruit_seesaw.neopixel import (  # pylint: disable=import-outside-toplevel
394                  NeoPixel,
395              )
396  
397              self._onboard_pixel = NeoPixel(
398                  self._seesaw,
399                  _SS_PIXEL,
400                  1,
401                  bpp=3,
402                  brightness=1.0,
403                  auto_write=True,
404                  pixel_order=None,
405              )
406              self._onboard_pixel.fill((0, 0, 0))
407          return self._onboard_pixel
408  
409      def reset(self):
410          """Reset the whole Crickit board."""
411          self._seesaw.sw_reset()
412  
413  
414  crickit = None  # pylint: disable=invalid-name
415  """A singleton instance to control a single Crickit board, controlled by the default I2C pins."""
416  
417  # Sphinx's board is missing real pins so skip the constructor in that case.
418  if "I2C" in dir(board):
419      crickit = Crickit(Seesaw(board.I2C()))  # pylint: disable=invalid-name