Adafruit_CircuitPython_Crickit/adafruit_crickit.py
2020-03-16 15:04:50 -04:00

419 lines
13 KiB
Python
Executable file

# The MIT License (MIT)
#
# Copyright (c) 2018 Dan Halbert for Adafruit Industries
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
`adafruit_crickit`
==========================
Convenience library for using the Adafruit Crickit robotics boards.
* Author(s): Dan Halbert
Implementation Notes
--------------------
**Hardware:**
`Adafruit Crickit for Circuit Playground Express <https://www.adafruit.com/3093>`_
`Adafruit Crickit FeatherWing <https://www.adafruit.com/3343>`_
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
import sys
import board
from micropython import const
# pylint: disable=wrong-import-position
try:
lib_index = sys.path.index("/lib") # pylint: disable=invalid-name
if lib_index < sys.path.index(".frozen"):
# Prefer frozen modules over those in /lib.
sys.path.insert(lib_index, ".frozen")
except ValueError:
# Don't change sys.path if it doesn't contain "lib" or ".frozen".
pass
from adafruit_seesaw.seesaw import Seesaw
from adafruit_seesaw.crickit import Crickit_Pinmap
from adafruit_seesaw.pwmout import PWMOut
from adafruit_motor.servo import Servo, ContinuousServo
from adafruit_motor.motor import DCMotor
from adafruit_motor.stepper import StepperMotor
__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Crickit.git"
_SERVO1 = const(17)
_SERVO2 = const(16)
_SERVO3 = const(15)
_SERVO4 = const(14)
_MOTOR1 = (22, 23)
_MOTOR2 = (19, 18)
# Order as needed for steppers.
_MOTOR_STEPPER = _MOTOR1 + _MOTOR2
_DRIVE1 = const(13)
_DRIVE2 = const(12)
_DRIVE3 = const(43)
_DRIVE4 = const(42)
# Order as needed for steppers.
_DRIVE_STEPPER = (_DRIVE1, _DRIVE3, _DRIVE2, _DRIVE4)
_TOUCH1 = const(4)
_TOUCH2 = const(5)
_TOUCH3 = const(6)
_TOUCH4 = const(7)
_NEOPIXEL = const(20)
_SS_PIXEL = const(27)
# pylint: disable=too-few-public-methods
class CrickitTouchIn:
"""Imitate touchio.TouchIn."""
def __init__(self, seesaw, pin):
self._seesaw = seesaw
self._pin = pin
self.threshold = self.raw_value + 100
@property
def raw_value(self):
"""The raw touch measurement as an `int`. (read-only)"""
return self._seesaw.touch_read(self._pin)
@property
def value(self):
"""Whether the touch pad is being touched or not. (read-only)"""
return self.raw_value > self.threshold
# pylint: disable=too-many-public-methods
class Crickit:
"""Represents a Crickit board. Provides a number of devices available via properties, such as
``servo_1``. Devices are created on demand the first time they are referenced.
It's fine to refer a device multiple times via its property, but it's faster and results
in more compact code to assign a device to a variable.
.. code-block:: python
import time
from adafruit_crickit import crickit
# This is fine:
crickit.servo_1.angle = 0
time.sleep(1)
crickit.servo_1.angle = 90
time.sleep(1)
# This is slightly faster and more compact:
servo_1 = crickit.servo_1
servo_1.angle = 0
time.sleep(1)
servo_1.angle = 90
time.sleep(1)
"""
SIGNAL1 = 2
"""Signal 1 terminal"""
SIGNAL2 = 3
"""Signal 2 terminal"""
SIGNAL3 = 40
"""Signal 3 terminal"""
SIGNAL4 = 41
"""Signal 4 terminal"""
SIGNAL5 = 11
"""Signal 5 terminal"""
SIGNAL6 = 10
"""Signal 6 terminal"""
SIGNAL7 = 9
"""Signal 7 terminal"""
SIGNAL8 = 8
"""Signal 8 terminal"""
def __init__(self, seesaw):
self._seesaw = seesaw
self._seesaw.pin_mapping = Crickit_Pinmap
# Associate terminal(s) with certain devices.
# Used to find existing devices.
self._devices = dict()
self._neopixel = None
self._onboard_pixel = None
@property
def seesaw(self):
"""The Seesaw object that talks to the Crickit. Use this object to manipulate the
signal pins that correspond to Crickit terminals.
.. code-block:: python
from adafruit_crickit import crickit
ss = crickit.seesaw
ss.pin_mode(crickit.SIGNAL4, ss.OUTPUT)
ss.digital_write(crickit.SIGNAL4], True)
"""
return self._seesaw
@property
def servo_1(self):
"""``adafruit_motor.servo.Servo`` object on Servo 1 terminal"""
return self._servo(_SERVO1, Servo)
@property
def servo_2(self):
"""``adafruit_motor.servo.Servo`` object on Servo 2 terminal"""
return self._servo(_SERVO2, Servo)
@property
def servo_3(self):
"""``adafruit_motor.servo.Servo`` object on Servo 3 terminal"""
return self._servo(_SERVO3, Servo)
@property
def servo_4(self):
"""``adafruit_motor.servo.Servo`` object on Servo 4 terminal"""
return self._servo(_SERVO4, Servo)
@property
def continuous_servo_1(self):
"""``adafruit_motor.servo.ContinuousServo`` object on Servo 1 terminal"""
return self._servo(_SERVO1, ContinuousServo)
@property
def continuous_servo_2(self):
"""``adafruit_motor.servo.ContinuousServo`` object on Servo 2 terminal"""
return self._servo(_SERVO2, ContinuousServo)
@property
def continuous_servo_3(self):
"""``adafruit_motor.servo.ContinuousServo`` object on Servo 3 terminal"""
return self._servo(_SERVO3, ContinuousServo)
@property
def continuous_servo_4(self):
"""``adafruit_motor.servo.ContinuousServo`` object on Servo 4 terminal"""
return self._servo(_SERVO4, ContinuousServo)
def _servo(self, terminal, servo_class):
device = self._devices.get(terminal, None)
if not isinstance(device, servo_class):
pwm = PWMOut(self._seesaw, terminal)
pwm.frequency = 50
device = servo_class(pwm)
self._devices[terminal] = device
return device
@property
def dc_motor_1(self):
"""``adafruit_motor.motor.DCMotor`` object on Motor 1 terminals"""
return self._motor(_MOTOR1, DCMotor)
@property
def dc_motor_2(self):
"""``adafruit_motor.motor.DCMotor`` object on Motor 2 terminals"""
return self._motor(_MOTOR2, DCMotor)
@property
def stepper_motor(self):
"""``adafruit_motor.motor.StepperMotor`` object on Motor 1 and Motor 2 terminals"""
return self._motor(_MOTOR_STEPPER, StepperMotor)
@property
def drive_stepper_motor(self):
"""``adafruit_motor.motor.StepperMotor`` object on Drive terminals"""
return self._motor(_DRIVE_STEPPER, StepperMotor)
@property
def feather_drive_stepper_motor(self):
"""``adafruit_motor.motor.StepperMotor`` object on Drive terminals on Crickit FeatherWing"""
return self._motor(tuple(reversed(_DRIVE_STEPPER)), StepperMotor)
def _motor(self, terminals, motor_class):
device = self._devices.get(terminals, None)
if not isinstance(device, motor_class):
device = motor_class(
*(PWMOut(self._seesaw, terminal) for terminal in terminals)
)
self._devices[terminals] = device
return device
@property
def drive_1(self):
"""``adafruit_seesaw.pwmout.PWMOut`` object on Drive 1 terminal, with ``frequency=1000``"""
return self._drive(_DRIVE1)
@property
def drive_2(self):
"""``adafruit_seesaw.pwmout.PWMOut`` object on Drive 2 terminal, with ``frequency=1000``"""
return self._drive(_DRIVE2)
@property
def drive_3(self):
"""``adafruit_seesaw.pwmout.PWMOut`` object on Drive 3 terminal, with ``frequency=1000``"""
return self._drive(_DRIVE3)
@property
def drive_4(self):
"""``adafruit_seesaw.pwmout.PWMOut`` object on Drive 4 terminal, with ``frequency=1000``"""
return self._drive(_DRIVE4)
feather_drive_1 = drive_4
"""``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 1 terminal,
with ``frequency=1000``
"""
feather_drive_2 = drive_3
"""``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 2 terminal,
with ``frequency=1000``
"""
feather_drive_3 = drive_2
"""``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 3 terminal,
with ``frequency=1000``
"""
feather_drive_4 = drive_1
"""``adafruit_seesaw.pwmout.PWMOut`` object on Crickit Featherwing Drive 4 terminal,
with ``frequency=1000``
"""
def _drive(self, terminal):
device = self._devices.get(terminal, None)
if not isinstance(device, PWMOut):
device = PWMOut(self._seesaw, terminal)
device.frequency = 1000
self._devices[terminal] = device
return device
@property
def touch_1(self):
"""``adafruit_crickit.CrickitTouchIn`` object on Touch 1 terminal"""
return self._touch(_TOUCH1)
@property
def touch_2(self):
"""``adafruit_crickit.CrickitTouchIn`` object on Touch 2 terminal"""
return self._touch(_TOUCH2)
@property
def touch_3(self):
"""``adafruit_crickit.CrickitTouchIn`` object on Touch 3 terminal"""
return self._touch(_TOUCH3)
@property
def touch_4(self):
"""``adafruit_crickit.CrickitTouchIn`` object on Touch 4 terminal"""
return self._touch(_TOUCH4)
def _touch(self, terminal):
touch_in = self._devices.get(terminal, None)
if not touch_in:
touch_in = CrickitTouchIn(self._seesaw, terminal)
self._devices[terminal] = touch_in
return touch_in
@property
def neopixel(self):
"""```adafruit_seesaw.neopixel`` object on NeoPixel terminal.
Raises ValueError if ``init_neopixel`` has not been called.
"""
if not self._neopixel:
raise ValueError("Call init_neopixel first")
return self._neopixel
def init_neopixel(
self, n, *, bpp=3, brightness=1.0, auto_write=True, pixel_order=None
):
"""Set up a seesaw.NeoPixel object
.. note:: On the CPX Crickit board, the NeoPixel terminal is by default
controlled by CPX pin A1, and is not controlled by seesaw. So this object
will not be usable. Instead, use the regular NeoPixel library
and specify ``board.A1`` as the pin.
You can change the jumper connection on the bottom of the CPX Crickit board
to move control of the NeoPixel terminal to seesaw pin #20 (terminal.NEOPIXEL).
In addition, the Crickit FeatherWing always uses seesaw pin #20.
In either of those cases, this object will work.
.. code-block:: python
from adafruit_crickit.crickit import crickit
crickit.init_neopixel(24)
crickit.neopixel.fill((100, 0, 0))
"""
from adafruit_seesaw.neopixel import ( # pylint: disable=import-outside-toplevel
NeoPixel,
)
self._neopixel = NeoPixel(
self._seesaw,
_NEOPIXEL,
n,
bpp=bpp,
brightness=brightness,
auto_write=auto_write,
pixel_order=pixel_order,
)
@property
def onboard_pixel(self):
"""```adafruit_seesaw.neopixel`` object on the Seesaw on-board NeoPixel.
Initialize on-board NeoPixel and clear upon first use.
"""
if not self._onboard_pixel:
from adafruit_seesaw.neopixel import ( # pylint: disable=import-outside-toplevel
NeoPixel,
)
self._onboard_pixel = NeoPixel(
self._seesaw,
_SS_PIXEL,
1,
bpp=3,
brightness=1.0,
auto_write=True,
pixel_order=None,
)
self._onboard_pixel.fill((0, 0, 0))
return self._onboard_pixel
def reset(self):
"""Reset the whole Crickit board."""
self._seesaw.sw_reset()
crickit = None # pylint: disable=invalid-name
"""A singleton instance to control a single Crickit board, controlled by the default I2C pins."""
# Sphinx's board is missing real pins so skip the constructor in that case.
if "I2C" in dir(board):
crickit = Crickit(Seesaw(board.I2C())) # pylint: disable=invalid-name