/ 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