summaryrefslogtreecommitdiff
path: root/Adafruit_Python_GPIO/Adafruit_GPIO
diff options
context:
space:
mode:
Diffstat (limited to 'Adafruit_Python_GPIO/Adafruit_GPIO')
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/FT232H.py816
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/GPIO.py426
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/I2C.py202
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/MCP230xx.py165
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/PCA95xx.py121
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/PCF8574.py94
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/PWM.py128
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/Platform.py110
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/SPI.py328
-rw-r--r--Adafruit_Python_GPIO/Adafruit_GPIO/__init__.py3
10 files changed, 2393 insertions, 0 deletions
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/FT232H.py b/Adafruit_Python_GPIO/Adafruit_GPIO/FT232H.py
new file mode 100644
index 0000000..1874272
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/FT232H.py
@@ -0,0 +1,816 @@
+# Copyright (c) 2014 Adafruit Industries
+# Author: Tony DiCola
+#
+# 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.
+
+import atexit
+import logging
+import math
+import os
+import subprocess
+import sys
+import time
+
+import ftdi1 as ftdi
+
+import Adafruit_GPIO.GPIO as GPIO
+
+
+logger = logging.getLogger(__name__)
+
+FT232H_VID = 0x0403 # Default FTDI FT232H vendor ID
+FT232H_PID = 0x6014 # Default FTDI FT232H product ID
+
+MSBFIRST = 0
+LSBFIRST = 1
+
+_REPEAT_DELAY = 4
+
+
+def _check_running_as_root():
+ # NOTE: Checking for root with user ID 0 isn't very portable, perhaps
+ # there's a better alternative?
+ if os.geteuid() != 0:
+ raise RuntimeError('Expected to be run by root user! Try running with sudo.')
+
+def disable_FTDI_driver():
+ """Disable the FTDI drivers for the current platform. This is necessary
+ because they will conflict with libftdi and accessing the FT232H. Note you
+ can enable the FTDI drivers again by calling enable_FTDI_driver.
+ """
+ logger.debug('Disabling FTDI driver.')
+ if sys.platform == 'darwin':
+ logger.debug('Detected Mac OSX')
+ # Mac OS commands to disable FTDI driver.
+ _check_running_as_root()
+ subprocess.call('kextunload -b com.apple.driver.AppleUSBFTDI', shell=True)
+ subprocess.call('kextunload /System/Library/Extensions/FTDIUSBSerialDriver.kext', shell=True)
+ elif sys.platform.startswith('linux'):
+ logger.debug('Detected Linux')
+ # Linux commands to disable FTDI driver.
+ _check_running_as_root()
+ subprocess.call('modprobe -r -q ftdi_sio', shell=True)
+ subprocess.call('modprobe -r -q usbserial', shell=True)
+ # Note there is no need to disable FTDI drivers on Windows!
+
+def enable_FTDI_driver():
+ """Re-enable the FTDI drivers for the current platform."""
+ logger.debug('Enabling FTDI driver.')
+ if sys.platform == 'darwin':
+ logger.debug('Detected Mac OSX')
+ # Mac OS commands to enable FTDI driver.
+ _check_running_as_root()
+ subprocess.check_call('kextload -b com.apple.driver.AppleUSBFTDI', shell=True)
+ subprocess.check_call('kextload /System/Library/Extensions/FTDIUSBSerialDriver.kext', shell=True)
+ elif sys.platform.startswith('linux'):
+ logger.debug('Detected Linux')
+ # Linux commands to enable FTDI driver.
+ _check_running_as_root()
+ subprocess.check_call('modprobe -q ftdi_sio', shell=True)
+ subprocess.check_call('modprobe -q usbserial', shell=True)
+
+def use_FT232H():
+ """Disable any built in FTDI drivers which will conflict and cause problems
+ with libftdi (which is used to communicate with the FT232H). Will register
+ an exit function so the drivers are re-enabled on program exit.
+ """
+ disable_FTDI_driver()
+ atexit.register(enable_FTDI_driver)
+
+def enumerate_device_serials(vid=FT232H_VID, pid=FT232H_PID):
+ """Return a list of all FT232H device serial numbers connected to the
+ machine. You can use these serial numbers to open a specific FT232H device
+ by passing it to the FT232H initializer's serial parameter.
+ """
+ try:
+ # Create a libftdi context.
+ ctx = None
+ ctx = ftdi.new()
+ # Enumerate FTDI devices.
+ device_list = None
+ count, device_list = ftdi.usb_find_all(ctx, vid, pid)
+ if count < 0:
+ raise RuntimeError('ftdi_usb_find_all returned error {0}: {1}'.format(count, ftdi.get_error_string(self._ctx)))
+ # Walk through list of devices and assemble list of serial numbers.
+ devices = []
+ while device_list is not None:
+ # Get USB device strings and add serial to list of devices.
+ ret, manufacturer, description, serial = ftdi.usb_get_strings(ctx, device_list.dev, 256, 256, 256)
+ if serial is not None:
+ devices.append(serial)
+ device_list = device_list.next
+ return devices
+ finally:
+ # Make sure to clean up list and context when done.
+ if device_list is not None:
+ ftdi.list_free(device_list)
+ if ctx is not None:
+ ftdi.free(ctx)
+
+
+class FT232H(GPIO.BaseGPIO):
+ # Make GPIO constants that match main GPIO class for compatibility.
+ HIGH = GPIO.HIGH
+ LOW = GPIO.LOW
+ IN = GPIO.IN
+ OUT = GPIO.OUT
+
+ def __init__(self, vid=FT232H_VID, pid=FT232H_PID, serial=None):
+ """Create a FT232H object. Will search for the first available FT232H
+ device with the specified USB vendor ID and product ID (defaults to
+ FT232H default VID & PID). Can also specify an optional serial number
+ string to open an explicit FT232H device given its serial number. See
+ the FT232H.enumerate_device_serials() function to see how to list all
+ connected device serial numbers.
+ """
+ # Initialize FTDI device connection.
+ self._ctx = ftdi.new()
+ if self._ctx == 0:
+ raise RuntimeError('ftdi_new failed! Is libftdi1 installed?')
+ # Register handler to close and cleanup FTDI context on program exit.
+ atexit.register(self.close)
+ if serial is None:
+ # Open USB connection for specified VID and PID if no serial is specified.
+ self._check(ftdi.usb_open, vid, pid)
+ else:
+ # Open USB connection for VID, PID, serial.
+ self._check(ftdi.usb_open_string, 's:{0}:{1}:{2}'.format(vid, pid, serial))
+ # Reset device.
+ self._check(ftdi.usb_reset)
+ # Disable flow control. Commented out because it is unclear if this is necessary.
+ #self._check(ftdi.setflowctrl, ftdi.SIO_DISABLE_FLOW_CTRL)
+ # Change read & write buffers to maximum size, 65535 bytes.
+ self._check(ftdi.read_data_set_chunksize, 65535)
+ self._check(ftdi.write_data_set_chunksize, 65535)
+ # Clear pending read data & write buffers.
+ self._check(ftdi.usb_purge_buffers)
+ # Enable MPSSE and syncronize communication with device.
+ self._mpsse_enable()
+ self._mpsse_sync()
+ # Initialize all GPIO as inputs.
+ self._write('\x80\x00\x00\x82\x00\x00')
+ self._direction = 0x0000
+ self._level = 0x0000
+
+ def close(self):
+ """Close the FTDI device. Will be automatically called when the program ends."""
+ if self._ctx is not None:
+ ftdi.free(self._ctx)
+ self._ctx = None
+
+ def _write(self, string):
+ """Helper function to call write_data on the provided FTDI device and
+ verify it succeeds.
+ """
+ # Get modem status. Useful to enable for debugging.
+ #ret, status = ftdi.poll_modem_status(self._ctx)
+ #if ret == 0:
+ # logger.debug('Modem status {0:02X}'.format(status))
+ #else:
+ # logger.debug('Modem status error {0}'.format(ret))
+ length = len(string)
+ ret = ftdi.write_data(self._ctx, string, length)
+ # Log the string that was written in a python hex string format using a very
+ # ugly one-liner list comprehension for brevity.
+ #logger.debug('Wrote {0}'.format(''.join(['\\x{0:02X}'.format(ord(x)) for x in string])))
+ if ret < 0:
+ raise RuntimeError('ftdi_write_data failed with error {0}: {1}'.format(ret, ftdi.get_error_string(self._ctx)))
+ if ret != length:
+ raise RuntimeError('ftdi_write_data expected to write {0} bytes but actually wrote {1}!'.format(length, ret))
+
+ def _check(self, command, *args):
+ """Helper function to call the provided command on the FTDI device and
+ verify the response matches the expected value.
+ """
+ ret = command(self._ctx, *args)
+ logger.debug('Called ftdi_{0} and got response {1}.'.format(command.__name__, ret))
+ if ret != 0:
+ raise RuntimeError('ftdi_{0} failed with error {1}: {2}'.format(command.__name__, ret, ftdi.get_error_string(self._ctx)))
+
+ def _poll_read(self, expected, timeout_s=5.0):
+ """Helper function to continuously poll reads on the FTDI device until an
+ expected number of bytes are returned. Will throw a timeout error if no
+ data is received within the specified number of timeout seconds. Returns
+ the read data as a string if successful, otherwise raises an execption.
+ """
+ start = time.time()
+ # Start with an empty response buffer.
+ response = bytearray(expected)
+ index = 0
+ # Loop calling read until the response buffer is full or a timeout occurs.
+ while time.time() - start <= timeout_s:
+ ret, data = ftdi.read_data(self._ctx, expected - index)
+ # Fail if there was an error reading data.
+ if ret < 0:
+ raise RuntimeError('ftdi_read_data failed with error code {0}.'.format(ret))
+ # Add returned data to the buffer.
+ response[index:index+ret] = data[:ret]
+ index += ret
+ # Buffer is full, return the result data.
+ if index >= expected:
+ return str(response)
+ time.sleep(0.01)
+ raise RuntimeError('Timeout while polling ftdi_read_data for {0} bytes!'.format(expected))
+
+ def _mpsse_enable(self):
+ """Enable MPSSE mode on the FTDI device."""
+ # Reset MPSSE by sending mask = 0 and mode = 0
+ self._check(ftdi.set_bitmode, 0, 0)
+ # Enable MPSSE by sending mask = 0 and mode = 2
+ self._check(ftdi.set_bitmode, 0, 2)
+
+ def _mpsse_sync(self, max_retries=10):
+ """Synchronize buffers with MPSSE by sending bad opcode and reading expected
+ error response. Should be called once after enabling MPSSE."""
+ # Send a bad/unknown command (0xAB), then read buffer until bad command
+ # response is found.
+ self._write('\xAB')
+ # Keep reading until bad command response (0xFA 0xAB) is returned.
+ # Fail if too many read attempts are made to prevent sticking in a loop.
+ tries = 0
+ sync = False
+ while not sync:
+ data = self._poll_read(2)
+ if data == '\xFA\xAB':
+ sync = True
+ tries += 1
+ if tries >= max_retries:
+ raise RuntimeError('Could not synchronize with FT232H!')
+
+ def mpsse_set_clock(self, clock_hz, adaptive=False, three_phase=False):
+ """Set the clock speed of the MPSSE engine. Can be any value from 450hz
+ to 30mhz and will pick that speed or the closest speed below it.
+ """
+ # Disable clock divisor by 5 to enable faster speeds on FT232H.
+ self._write('\x8A')
+ # Turn on/off adaptive clocking.
+ if adaptive:
+ self._write('\x96')
+ else:
+ self._write('\x97')
+ # Turn on/off three phase clock (needed for I2C).
+ # Also adjust the frequency for three-phase clocking as specified in section 2.2.4
+ # of this document:
+ # http://www.ftdichip.com/Support/Documents/AppNotes/AN_255_USB%20to%20I2C%20Example%20using%20the%20FT232H%20and%20FT201X%20devices.pdf
+ if three_phase:
+ self._write('\x8C')
+ else:
+ self._write('\x8D')
+ # Compute divisor for requested clock.
+ # Use equation from section 3.8.1 of:
+ # http://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf
+ # Note equation is using 60mhz master clock instead of 12mhz.
+ divisor = int(math.ceil((30000000.0-float(clock_hz))/float(clock_hz))) & 0xFFFF
+ if three_phase:
+ divisor = int(divisor*(2.0/3.0))
+ logger.debug('Setting clockspeed with divisor value {0}'.format(divisor))
+ # Send command to set divisor from low and high byte values.
+ self._write(str(bytearray((0x86, divisor & 0xFF, (divisor >> 8) & 0xFF))))
+
+ def mpsse_read_gpio(self):
+ """Read both GPIO bus states and return a 16 bit value with their state.
+ D0-D7 are the lower 8 bits and C0-C7 are the upper 8 bits.
+ """
+ # Send command to read low byte and high byte.
+ self._write('\x81\x83')
+ # Wait for 2 byte response.
+ data = self._poll_read(2)
+ # Assemble response into 16 bit value.
+ low_byte = ord(data[0])
+ high_byte = ord(data[1])
+ logger.debug('Read MPSSE GPIO low byte = {0:02X} and high byte = {1:02X}'.format(low_byte, high_byte))
+ return (high_byte << 8) | low_byte
+
+ def mpsse_gpio(self):
+ """Return command to update the MPSSE GPIO state to the current direction
+ and level.
+ """
+ level_low = chr(self._level & 0xFF)
+ level_high = chr((self._level >> 8) & 0xFF)
+ dir_low = chr(self._direction & 0xFF)
+ dir_high = chr((self._direction >> 8) & 0xFF)
+ return str(bytearray((0x80, level_low, dir_low, 0x82, level_high, dir_high)))
+
+ def mpsse_write_gpio(self):
+ """Write the current MPSSE GPIO state to the FT232H chip."""
+ self._write(self.mpsse_gpio())
+
+ def get_i2c_device(self, address, **kwargs):
+ """Return an I2CDevice instance using this FT232H object and the provided
+ I2C address. Meant to be passed as the i2c_provider parameter to objects
+ which use the Adafruit_Python_GPIO library for I2C.
+ """
+ return I2CDevice(self, address, **kwargs)
+
+ # GPIO functions below:
+
+ def _setup_pin(self, pin, mode):
+ if pin < 0 or pin > 15:
+ raise ValueError('Pin must be between 0 and 15 (inclusive).')
+ if mode not in (GPIO.IN, GPIO.OUT):
+ raise ValueError('Mode must be GPIO.IN or GPIO.OUT.')
+ if mode == GPIO.IN:
+ # Set the direction and level of the pin to 0.
+ self._direction &= ~(1 << pin) & 0xFFFF
+ self._level &= ~(1 << pin) & 0xFFFF
+ else:
+ # Set the direction of the pin to 1.
+ self._direction |= (1 << pin) & 0xFFFF
+
+ def setup(self, pin, mode):
+ """Set the input or output mode for a specified pin. Mode should be
+ either OUT or IN."""
+ self._setup_pin(pin, mode)
+ self.mpsse_write_gpio()
+
+ def setup_pins(self, pins, values={}, write=True):
+ """Setup multiple pins as inputs or outputs at once. Pins should be a
+ dict of pin name to pin mode (IN or OUT). Optional starting values of
+ pins can be provided in the values dict (with pin name to pin value).
+ """
+ # General implementation that can be improved by subclasses.
+ for pin, mode in iter(pins.items()):
+ self._setup_pin(pin, mode)
+ for pin, value in iter(values.items()):
+ self._output_pin(pin, value)
+ if write:
+ self.mpsse_write_gpio()
+
+ def _output_pin(self, pin, value):
+ if value:
+ self._level |= (1 << pin) & 0xFFFF
+ else:
+ self._level &= ~(1 << pin) & 0xFFFF
+
+ def output(self, pin, value):
+ """Set the specified pin the provided high/low value. Value should be
+ either HIGH/LOW or a boolean (true = high)."""
+ if pin < 0 or pin > 15:
+ raise ValueError('Pin must be between 0 and 15 (inclusive).')
+ self._output_pin(pin, value)
+ self.mpsse_write_gpio()
+
+ def output_pins(self, pins, write=True):
+ """Set multiple pins high or low at once. Pins should be a dict of pin
+ name to pin value (HIGH/True for 1, LOW/False for 0). All provided pins
+ will be set to the given values.
+ """
+ for pin, value in iter(pins.items()):
+ self._output_pin(pin, value)
+ if write:
+ self.mpsse_write_gpio()
+
+ def input(self, pin):
+ """Read the specified pin and return HIGH/true if the pin is pulled high,
+ or LOW/false if pulled low."""
+ return self.input_pins([pin])[0]
+
+ def input_pins(self, pins):
+ """Read multiple pins specified in the given list and return list of pin values
+ GPIO.HIGH/True if the pin is pulled high, or GPIO.LOW/False if pulled low."""
+ if [pin for pin in pins if pin < 0 or pin > 15]:
+ raise ValueError('Pin must be between 0 and 15 (inclusive).')
+ _pins = self.mpsse_read_gpio()
+ return [((_pins >> pin) & 0x0001) == 1 for pin in pins]
+
+
+class SPI(object):
+ def __init__(self, ft232h, cs=None, max_speed_hz=1000000, mode=0, bitorder=MSBFIRST):
+ self._ft232h = ft232h
+ # Initialize chip select pin if provided to output high.
+ if cs is not None:
+ ft232h.setup(cs, GPIO.OUT)
+ ft232h.set_high(cs)
+ self._cs = cs
+ # Initialize clock, mode, and bit order.
+ self.set_clock_hz(max_speed_hz)
+ self.set_mode(mode)
+ self.set_bit_order(bitorder)
+
+ def _assert_cs(self):
+ if self._cs is not None:
+ self._ft232h.set_low(self._cs)
+
+ def _deassert_cs(self):
+ if self._cs is not None:
+ self._ft232h.set_high(self._cs)
+
+ def set_clock_hz(self, hz):
+ """Set the speed of the SPI clock in hertz. Note that not all speeds
+ are supported and a lower speed might be chosen by the hardware.
+ """
+ self._ft232h.mpsse_set_clock(hz)
+
+ def set_mode(self, mode):
+ """Set SPI mode which controls clock polarity and phase. Should be a
+ numeric value 0, 1, 2, or 3. See wikipedia page for details on meaning:
+ http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
+ """
+ if mode < 0 or mode > 3:
+ raise ValueError('Mode must be a value 0, 1, 2, or 3.')
+ if mode == 0:
+ # Mode 0 captures on rising clock, propagates on falling clock
+ self.write_clock_ve = 1
+ self.read_clock_ve = 0
+ # Clock base is low.
+ clock_base = GPIO.LOW
+ elif mode == 1:
+ # Mode 1 capture of falling edge, propagate on rising clock
+ self.write_clock_ve = 0
+ self.read_clock_ve = 1
+ # Clock base is low.
+ clock_base = GPIO.LOW
+ elif mode == 2:
+ # Mode 2 capture on rising clock, propagate on falling clock
+ self.write_clock_ve = 1
+ self.read_clock_ve = 0
+ # Clock base is high.
+ clock_base = GPIO.HIGH
+ elif mode == 3:
+ # Mode 3 capture on falling edge, propagage on rising clock
+ self.write_clock_ve = 0
+ self.read_clock_ve = 1
+ # Clock base is high.
+ clock_base = GPIO.HIGH
+ # Set clock and DO as output, DI as input. Also start clock at its base value.
+ self._ft232h.setup_pins({0: GPIO.OUT, 1: GPIO.OUT, 2: GPIO.IN}, {0: clock_base})
+
+ def set_bit_order(self, order):
+ """Set order of bits to be read/written over serial lines. Should be
+ either MSBFIRST for most-significant first, or LSBFIRST for
+ least-signifcant first.
+ """
+ if order == MSBFIRST:
+ self.lsbfirst = 0
+ elif order == LSBFIRST:
+ self.lsbfirst = 1
+ else:
+ raise ValueError('Order must be MSBFIRST or LSBFIRST.')
+
+ def write(self, data):
+ """Half-duplex SPI write. The specified array of bytes will be clocked
+ out the MOSI line.
+ """
+ # Build command to write SPI data.
+ command = 0x10 | (self.lsbfirst << 3) | self.write_clock_ve
+ logger.debug('SPI write with command {0:2X}.'.format(command))
+ # Compute length low and high bytes.
+ # NOTE: Must actually send length minus one because the MPSSE engine
+ # considers 0 a length of 1 and FFFF a length of 65536
+ length = len(data)-1
+ len_low = length & 0xFF
+ len_high = (length >> 8) & 0xFF
+ self._assert_cs()
+ # Send command and length.
+ self._ft232h._write(str(bytearray((command, len_low, len_high))))
+ # Send data.
+ self._ft232h._write(str(bytearray(data)))
+ self._deassert_cs()
+
+ def read(self, length):
+ """Half-duplex SPI read. The specified length of bytes will be clocked
+ in the MISO line and returned as a bytearray object.
+ """
+ # Build command to read SPI data.
+ command = 0x20 | (self.lsbfirst << 3) | (self.read_clock_ve << 2)
+ logger.debug('SPI read with command {0:2X}.'.format(command))
+ # Compute length low and high bytes.
+ # NOTE: Must actually send length minus one because the MPSSE engine
+ # considers 0 a length of 1 and FFFF a length of 65536
+ len_low = (length-1) & 0xFF
+ len_high = ((length-1) >> 8) & 0xFF
+ self._assert_cs()
+ # Send command and length.
+ self._ft232h._write(str(bytearray((command, len_low, len_high, 0x87))))
+ self._deassert_cs()
+ # Read response bytes.
+ return bytearray(self._ft232h._poll_read(length))
+
+ def transfer(self, data):
+ """Full-duplex SPI read and write. The specified array of bytes will be
+ clocked out the MOSI line, while simultaneously bytes will be read from
+ the MISO line. Read bytes will be returned as a bytearray object.
+ """
+ # Build command to read and write SPI data.
+ command = 0x30 | (self.lsbfirst << 3) | (self.read_clock_ve << 2) | self.write_clock_ve
+ logger.debug('SPI transfer with command {0:2X}.'.format(command))
+ # Compute length low and high bytes.
+ # NOTE: Must actually send length minus one because the MPSSE engine
+ # considers 0 a length of 1 and FFFF a length of 65536
+ length = len(data)
+ len_low = (length-1) & 0xFF
+ len_high = ((length-1) >> 8) & 0xFF
+ # Send command and length.
+ self._assert_cs()
+ self._ft232h._write(str(bytearray((command, len_low, len_high))))
+ self._ft232h._write(str(bytearray(data)))
+ self._ft232h._write('\x87')
+ self._deassert_cs()
+ # Read response bytes.
+ return bytearray(self._ft232h._poll_read(length))
+
+
+class I2CDevice(object):
+ """Class for communicating with an I2C device using the smbus library.
+ Allows reading and writing 8-bit, 16-bit, and byte array values to registers
+ on the device."""
+ # Note that most of the functions in this code are adapted from this app note:
+ # http://www.ftdichip.com/Support/Documents/AppNotes/AN_255_USB%20to%20I2C%20Example%20using%20the%20FT232H%20and%20FT201X%20devices.pdf
+ def __init__(self, ft232h, address, clock_hz=100000):
+ """Create an instance of the I2C device at the specified address on the
+ specified I2C bus number."""
+ self._address = address
+ self._ft232h = ft232h
+ # Enable clock with three phases for I2C.
+ self._ft232h.mpsse_set_clock(clock_hz, three_phase=True)
+ # Enable drive-zero mode to drive outputs low on 0 and tri-state on 1.
+ # This matches the protocol for I2C communication so multiple devices can
+ # share the I2C bus.
+ self._ft232h._write('\x9E\x07\x00')
+ self._idle()
+
+ def _idle(self):
+ """Put I2C lines into idle state."""
+ # Put the I2C lines into an idle state with SCL and SDA high.
+ self._ft232h.setup_pins({0: GPIO.OUT, 1: GPIO.OUT, 2: GPIO.IN},
+ {0: GPIO.HIGH, 1: GPIO.HIGH})
+
+ def _transaction_start(self):
+ """Start I2C transaction."""
+ # Clear command buffer and expected response bytes.
+ self._command = []
+ self._expected = 0
+
+ def _transaction_end(self):
+ """End I2C transaction and get response bytes, including ACKs."""
+ # Ask to return response bytes immediately.
+ self._command.append('\x87')
+ # Send the entire command to the MPSSE.
+ self._ft232h._write(''.join(self._command))
+ # Read response bytes and return them.
+ return bytearray(self._ft232h._poll_read(self._expected))
+
+ def _i2c_start(self):
+ """Send I2C start signal. Must be called within a transaction start/end.
+ """
+ # Set SCL high and SDA low, repeat 4 times to stay in this state for a
+ # short period of time.
+ self._ft232h.output_pins({0: GPIO.HIGH, 1: GPIO.LOW}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio() * _REPEAT_DELAY)
+ # Now drop SCL to low (again repeat 4 times for short delay).
+ self._ft232h.output_pins({0: GPIO.LOW, 1: GPIO.LOW}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio() * _REPEAT_DELAY)
+
+ def _i2c_idle(self):
+ """Set I2C signals to idle state with SCL and SDA at a high value. Must
+ be called within a transaction start/end.
+ """
+ self._ft232h.output_pins({0: GPIO.HIGH, 1: GPIO.HIGH}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio() * _REPEAT_DELAY)
+
+ def _i2c_stop(self):
+ """Send I2C stop signal. Must be called within a transaction start/end.
+ """
+ # Set SCL low and SDA low for a short period.
+ self._ft232h.output_pins({0: GPIO.LOW, 1: GPIO.LOW}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio() * _REPEAT_DELAY)
+ # Set SCL high and SDA low for a short period.
+ self._ft232h.output_pins({0: GPIO.HIGH, 1: GPIO.LOW}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio() * _REPEAT_DELAY)
+ # Finally set SCL high and SDA high for a short period.
+ self._ft232h.output_pins({0: GPIO.HIGH, 1: GPIO.HIGH}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio() * _REPEAT_DELAY)
+
+ def _i2c_read_bytes(self, length=1):
+ """Read the specified number of bytes from the I2C bus. Length is the
+ number of bytes to read (must be 1 or more).
+ """
+ for i in range(length-1):
+ # Read a byte and send ACK.
+ self._command.append('\x20\x00\x00\x13\x00\x00')
+ # Make sure pins are back in idle state with clock low and data high.
+ self._ft232h.output_pins({0: GPIO.LOW, 1: GPIO.HIGH}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio())
+ # Read last byte and send NAK.
+ self._command.append('\x20\x00\x00\x13\x00\xFF')
+ # Make sure pins are back in idle state with clock low and data high.
+ self._ft232h.output_pins({0: GPIO.LOW, 1: GPIO.HIGH}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio())
+ # Increase expected number of bytes.
+ self._expected += length
+
+ def _i2c_write_bytes(self, data):
+ """Write the specified number of bytes to the chip."""
+ for byte in data:
+ # Write byte.
+ self._command.append(str(bytearray((0x11, 0x00, 0x00, byte))))
+ # Make sure pins are back in idle state with clock low and data high.
+ self._ft232h.output_pins({0: GPIO.LOW, 1: GPIO.HIGH}, write=False)
+ self._command.append(self._ft232h.mpsse_gpio() * _REPEAT_DELAY)
+ # Read bit for ACK/NAK.
+ self._command.append('\x22\x00')
+ # Increase expected response bytes.
+ self._expected += len(data)
+
+ def _address_byte(self, read=True):
+ """Return the address byte with the specified R/W bit set. If read is
+ True the R/W bit will be 1, otherwise the R/W bit will be 0.
+ """
+ if read:
+ return (self._address << 1) | 0x01
+ else:
+ return self._address << 1
+
+ def _verify_acks(self, response):
+ """Check all the specified bytes have the ACK bit set. Throws a
+ RuntimeError exception if not all the ACKs are set.
+ """
+ for byte in response:
+ if byte & 0x01 != 0x00:
+ raise RuntimeError('Failed to find expected I2C ACK!')
+
+ def ping(self):
+ """Attempt to detect if a device at this address is present on the I2C
+ bus. Will send out the device's address for writing and verify an ACK
+ is received. Returns true if the ACK is received, and false if not.
+ """
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False)])
+ self._i2c_stop()
+ response = self._transaction_end()
+ if len(response) != 1:
+ raise RuntimeError('Expected 1 response byte but received {0} byte(s).'.format(len(response)))
+ return ((response[0] & 0x01) == 0x00)
+
+ def writeRaw8(self, value):
+ """Write an 8-bit value on the bus (without register)."""
+ value = value & 0xFF
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False), value])
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response)
+
+ def write8(self, register, value):
+ """Write an 8-bit value to the specified register."""
+ value = value & 0xFF
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False), register, value])
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response)
+
+ def write16(self, register, value, little_endian=True):
+ """Write a 16-bit value to the specified register."""
+ value = value & 0xFFFF
+ value_low = value & 0xFF
+ value_high = (value >> 8) & 0xFF
+ if not little_endian:
+ value_low, value_high = value_high, value_low
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False), register, value_low,
+ value_high])
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response)
+
+ def writeList(self, register, data):
+ """Write bytes to the specified register."""
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False), register] + data)
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response)
+
+ def readList(self, register, length):
+ """Read a length number of bytes from the specified register. Results
+ will be returned as a bytearray."""
+ if length <= 0:
+ raise ValueError("Length must be at least 1 byte.")
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(True), register])
+ self._i2c_stop()
+ self._i2c_idle()
+ self._i2c_start()
+ self._i2c_read_bytes(length)
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response[:-length])
+ return response[-length:]
+
+ def readRaw8(self):
+ """Read an 8-bit value on the bus (without register)."""
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False)])
+ self._i2c_stop()
+ self._i2c_idle()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(True)])
+ self._i2c_read_bytes(1)
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response[:-1])
+ return response[-1]
+
+ def readU8(self, register):
+ """Read an unsigned byte from the specified register."""
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False), register])
+ self._i2c_stop()
+ self._i2c_idle()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(True)])
+ self._i2c_read_bytes(1)
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response[:-1])
+ return response[-1]
+
+ def readS8(self, register):
+ """Read a signed byte from the specified register."""
+ result = self.readU8(register)
+ if result > 127:
+ result -= 256
+ return result
+
+ def readU16(self, register, little_endian=True):
+ """Read an unsigned 16-bit value from the specified register, with the
+ specified endianness (default little endian, or least significant byte
+ first)."""
+ self._idle()
+ self._transaction_start()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(False), register])
+ self._i2c_stop()
+ self._i2c_idle()
+ self._i2c_start()
+ self._i2c_write_bytes([self._address_byte(True)])
+ self._i2c_read_bytes(2)
+ self._i2c_stop()
+ response = self._transaction_end()
+ self._verify_acks(response[:-2])
+ if little_endian:
+ return (response[-1] << 8) | response[-2]
+ else:
+ return (response[-2] << 8) | response[-1]
+
+ def readS16(self, register, little_endian=True):
+ """Read a signed 16-bit value from the specified register, with the
+ specified endianness (default little endian, or least significant byte
+ first)."""
+ result = self.readU16(register, little_endian)
+ if result > 32767:
+ result -= 65536
+ return result
+
+ def readU16LE(self, register):
+ """Read an unsigned 16-bit value from the specified register, in little
+ endian byte order."""
+ return self.readU16(register, little_endian=True)
+
+ def readU16BE(self, register):
+ """Read an unsigned 16-bit value from the specified register, in big
+ endian byte order."""
+ return self.readU16(register, little_endian=False)
+
+ def readS16LE(self, register):
+ """Read a signed 16-bit value from the specified register, in little
+ endian byte order."""
+ return self.readS16(register, little_endian=True)
+
+ def readS16BE(self, register):
+ """Read a signed 16-bit value from the specified register, in big
+ endian byte order."""
+ return self.readS16(register, little_endian=False)
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/GPIO.py b/Adafruit_Python_GPIO/Adafruit_GPIO/GPIO.py
new file mode 100644
index 0000000..08e99c6
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/GPIO.py
@@ -0,0 +1,426 @@
+# Copyright (c) 2014 Adafruit Industries
+# Author: Tony DiCola
+#
+# 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.
+
+import Adafruit_GPIO.Platform as Platform
+
+
+OUT = 0
+IN = 1
+HIGH = True
+LOW = False
+
+RISING = 1
+FALLING = 2
+BOTH = 3
+
+PUD_OFF = 0
+PUD_DOWN = 1
+PUD_UP = 2
+
+class BaseGPIO(object):
+ """Base class for implementing simple digital IO for a platform.
+ Implementors are expected to subclass from this and provide an implementation
+ of the setup, output, and input functions."""
+
+ def setup(self, pin, mode, pull_up_down=PUD_OFF):
+ """Set the input or output mode for a specified pin. Mode should be
+ either OUT or IN."""
+ raise NotImplementedError
+
+ def output(self, pin, value):
+ """Set the specified pin the provided high/low value. Value should be
+ either HIGH/LOW or a boolean (true = high)."""
+ raise NotImplementedError
+
+ def input(self, pin):
+ """Read the specified pin and return HIGH/true if the pin is pulled high,
+ or LOW/false if pulled low."""
+ raise NotImplementedError
+
+ def set_high(self, pin):
+ """Set the specified pin HIGH."""
+ self.output(pin, HIGH)
+
+ def set_low(self, pin):
+ """Set the specified pin LOW."""
+ self.output(pin, LOW)
+
+ def is_high(self, pin):
+ """Return true if the specified pin is pulled high."""
+ return self.input(pin) == HIGH
+
+ def is_low(self, pin):
+ """Return true if the specified pin is pulled low."""
+ return self.input(pin) == LOW
+
+
+# Basic implementation of multiple pin methods just loops through pins and
+# processes each one individually. This is not optimal, but derived classes can
+# provide a more optimal implementation that deals with groups of pins
+# simultaneously.
+# See MCP230xx or PCF8574 classes for examples of optimized implementations.
+
+ def output_pins(self, pins):
+ """Set multiple pins high or low at once. Pins should be a dict of pin
+ name to pin value (HIGH/True for 1, LOW/False for 0). All provided pins
+ will be set to the given values.
+ """
+ # General implementation just loops through pins and writes them out
+ # manually. This is not optimized, but subclasses can choose to implement
+ # a more optimal batch output implementation. See the MCP230xx class for
+ # example of optimized implementation.
+ for pin, value in iter(pins.items()):
+ self.output(pin, value)
+
+ def setup_pins(self, pins):
+ """Setup multiple pins as inputs or outputs at once. Pins should be a
+ dict of pin name to pin type (IN or OUT).
+ """
+ # General implementation that can be optimized by derived classes.
+ for pin, value in iter(pins.items()):
+ self.setup(pin, value)
+
+ def input_pins(self, pins):
+ """Read multiple pins specified in the given list and return list of pin values
+ GPIO.HIGH/True if the pin is pulled high, or GPIO.LOW/False if pulled low.
+ """
+ # General implementation that can be optimized by derived classes.
+ return [self.input(pin) for pin in pins]
+
+
+ def add_event_detect(self, pin, edge):
+ """Enable edge detection events for a particular GPIO channel. Pin
+ should be type IN. Edge must be RISING, FALLING or BOTH.
+ """
+ raise NotImplementedError
+
+ def remove_event_detect(self, pin):
+ """Remove edge detection for a particular GPIO channel. Pin should be
+ type IN.
+ """
+ raise NotImplementedError
+
+ def add_event_callback(self, pin, callback):
+ """Add a callback for an event already defined using add_event_detect().
+ Pin should be type IN.
+ """
+ raise NotImplementedError
+
+ def event_detected(self, pin):
+ """Returns True if an edge has occured on a given GPIO. You need to
+ enable edge detection using add_event_detect() first. Pin should be
+ type IN.
+ """
+ raise NotImplementedError
+
+ def wait_for_edge(self, pin, edge):
+ """Wait for an edge. Pin should be type IN. Edge must be RISING,
+ FALLING or BOTH."""
+ raise NotImplementedError
+
+ def cleanup(self, pin=None):
+ """Clean up GPIO event detection for specific pin, or all pins if none
+ is specified.
+ """
+ raise NotImplementedError
+
+
+# helper functions useful to derived classes
+
+ def _validate_pin(self, pin):
+ # Raise an exception if pin is outside the range of allowed values.
+ if pin < 0 or pin >= self.NUM_GPIO:
+ raise ValueError('Invalid GPIO value, must be between 0 and {0}.'.format(self.NUM_GPIO))
+
+ def _bit2(self, src, bit, val):
+ bit = 1 << bit
+ return (src | bit) if val else (src & ~bit)
+
+
+class RPiGPIOAdapter(BaseGPIO):
+ """GPIO implementation for the Raspberry Pi using the RPi.GPIO library."""
+
+ def __init__(self, rpi_gpio, mode=None):
+ self.rpi_gpio = rpi_gpio
+ # Suppress warnings about GPIO in use.
+ rpi_gpio.setwarnings(False)
+ # Setup board pin mode.
+ if mode == rpi_gpio.BOARD or mode == rpi_gpio.BCM:
+ rpi_gpio.setmode(mode)
+ elif mode is not None:
+ raise ValueError('Unexpected value for mode. Must be BOARD or BCM.')
+ else:
+ # Default to BCM numbering if not told otherwise.
+ rpi_gpio.setmode(rpi_gpio.BCM)
+ # Define mapping of Adafruit GPIO library constants to RPi.GPIO constants.
+ self._dir_mapping = { OUT: rpi_gpio.OUT,
+ IN: rpi_gpio.IN }
+ self._pud_mapping = { PUD_OFF: rpi_gpio.PUD_OFF,
+ PUD_DOWN: rpi_gpio.PUD_DOWN,
+ PUD_UP: rpi_gpio.PUD_UP }
+ self._edge_mapping = { RISING: rpi_gpio.RISING,
+ FALLING: rpi_gpio.FALLING,
+ BOTH: rpi_gpio.BOTH }
+
+ def setup(self, pin, mode, pull_up_down=PUD_OFF):
+ """Set the input or output mode for a specified pin. Mode should be
+ either OUTPUT or INPUT.
+ """
+ self.rpi_gpio.setup(pin, self._dir_mapping[mode],
+ pull_up_down=self._pud_mapping[pull_up_down])
+
+ def output(self, pin, value):
+ """Set the specified pin the provided high/low value. Value should be
+ either HIGH/LOW or a boolean (true = high).
+ """
+ self.rpi_gpio.output(pin, value)
+
+ def input(self, pin):
+ """Read the specified pin and return HIGH/true if the pin is pulled high,
+ or LOW/false if pulled low.
+ """
+ return self.rpi_gpio.input(pin)
+
+ def input_pins(self, pins):
+ """Read multiple pins specified in the given list and return list of pin values
+ GPIO.HIGH/True if the pin is pulled high, or GPIO.LOW/False if pulled low.
+ """
+ # maybe rpi has a mass read... it would be more efficient to use it if it exists
+ return [self.rpi_gpio.input(pin) for pin in pins]
+
+ def add_event_detect(self, pin, edge, callback=None, bouncetime=-1):
+ """Enable edge detection events for a particular GPIO channel. Pin
+ should be type IN. Edge must be RISING, FALLING or BOTH. Callback is a
+ function for the event. Bouncetime is switch bounce timeout in ms for
+ callback
+ """
+ kwargs = {}
+ if callback:
+ kwargs['callback']=callback
+ if bouncetime > 0:
+ kwargs['bouncetime']=bouncetime
+ self.rpi_gpio.add_event_detect(pin, self._edge_mapping[edge], **kwargs)
+
+ def remove_event_detect(self, pin):
+ """Remove edge detection for a particular GPIO channel. Pin should be
+ type IN.
+ """
+ self.rpi_gpio.remove_event_detect(pin)
+
+ def add_event_callback(self, pin, callback):
+ """Add a callback for an event already defined using add_event_detect().
+ Pin should be type IN.
+ """
+ self.rpi_gpio.add_event_callback(pin, callback)
+
+ def event_detected(self, pin):
+ """Returns True if an edge has occured on a given GPIO. You need to
+ enable edge detection using add_event_detect() first. Pin should be
+ type IN.
+ """
+ return self.rpi_gpio.event_detected(pin)
+
+ def wait_for_edge(self, pin, edge):
+ """Wait for an edge. Pin should be type IN. Edge must be RISING,
+ FALLING or BOTH.
+ """
+ self.rpi_gpio.wait_for_edge(pin, self._edge_mapping[edge])
+
+ def cleanup(self, pin=None):
+ """Clean up GPIO event detection for specific pin, or all pins if none
+ is specified.
+ """
+ if pin is None:
+ self.rpi_gpio.cleanup()
+ else:
+ self.rpi_gpio.cleanup(pin)
+
+class AdafruitBBIOAdapter(BaseGPIO):
+ """GPIO implementation for the Beaglebone Black using the Adafruit_BBIO
+ library.
+ """
+
+ def __init__(self, bbio_gpio):
+ self.bbio_gpio = bbio_gpio
+ # Define mapping of Adafruit GPIO library constants to RPi.GPIO constants.
+ self._dir_mapping = { OUT: bbio_gpio.OUT,
+ IN: bbio_gpio.IN }
+ self._pud_mapping = { PUD_OFF: bbio_gpio.PUD_OFF,
+ PUD_DOWN: bbio_gpio.PUD_DOWN,
+ PUD_UP: bbio_gpio.PUD_UP }
+ self._edge_mapping = { RISING: bbio_gpio.RISING,
+ FALLING: bbio_gpio.FALLING,
+ BOTH: bbio_gpio.BOTH }
+
+ def setup(self, pin, mode, pull_up_down=PUD_OFF):
+ """Set the input or output mode for a specified pin. Mode should be
+ either OUTPUT or INPUT.
+ """
+ self.bbio_gpio.setup(pin, self._dir_mapping[mode],
+ pull_up_down=self._pud_mapping[pull_up_down])
+
+ def output(self, pin, value):
+ """Set the specified pin the provided high/low value. Value should be
+ either HIGH/LOW or a boolean (true = high).
+ """
+ self.bbio_gpio.output(pin, value)
+
+ def input(self, pin):
+ """Read the specified pin and return HIGH/true if the pin is pulled high,
+ or LOW/false if pulled low.
+ """
+ return self.bbio_gpio.input(pin)
+
+ def input_pins(self, pins):
+ """Read multiple pins specified in the given list and return list of pin values
+ GPIO.HIGH/True if the pin is pulled high, or GPIO.LOW/False if pulled low.
+ """
+ # maybe bbb has a mass read... it would be more efficient to use it if it exists
+ return [self.bbio_gpio.input(pin) for pin in pins]
+
+ def add_event_detect(self, pin, edge, callback=None, bouncetime=-1):
+ """Enable edge detection events for a particular GPIO channel. Pin
+ should be type IN. Edge must be RISING, FALLING or BOTH. Callback is a
+ function for the event. Bouncetime is switch bounce timeout in ms for
+ callback
+ """
+ kwargs = {}
+ if callback:
+ kwargs['callback']=callback
+ if bouncetime > 0:
+ kwargs['bouncetime']=bouncetime
+ self.bbio_gpio.add_event_detect(pin, self._edge_mapping[edge], **kwargs)
+
+ def remove_event_detect(self, pin):
+ """Remove edge detection for a particular GPIO channel. Pin should be
+ type IN.
+ """
+ self.bbio_gpio.remove_event_detect(pin)
+
+ def add_event_callback(self, pin, callback, bouncetime=-1):
+ """Add a callback for an event already defined using add_event_detect().
+ Pin should be type IN. Bouncetime is switch bounce timeout in ms for
+ callback
+ """
+ kwargs = {}
+ if bouncetime > 0:
+ kwargs['bouncetime']=bouncetime
+ self.bbio_gpio.add_event_callback(pin, callback, **kwargs)
+
+ def event_detected(self, pin):
+ """Returns True if an edge has occured on a given GPIO. You need to
+ enable edge detection using add_event_detect() first. Pin should be
+ type IN.
+ """
+ return self.bbio_gpio.event_detected(pin)
+
+ def wait_for_edge(self, pin, edge):
+ """Wait for an edge. Pin should be type IN. Edge must be RISING,
+ FALLING or BOTH.
+ """
+ self.bbio_gpio.wait_for_edge(pin, self._edge_mapping[edge])
+
+ def cleanup(self, pin=None):
+ """Clean up GPIO event detection for specific pin, or all pins if none
+ is specified.
+ """
+ if pin is None:
+ self.bbio_gpio.cleanup()
+ else:
+ self.bbio_gpio.cleanup(pin)
+
+class AdafruitMinnowAdapter(BaseGPIO):
+ """GPIO implementation for the Minnowboard + MAX using the mraa library"""
+
+ def __init__(self,mraa_gpio):
+ self.mraa_gpio = mraa_gpio
+ # Define mapping of Adafruit GPIO library constants to mraa constants
+ self._dir_mapping = { OUT: self.mraa_gpio.DIR_OUT,
+ IN: self.mraa_gpio.DIR_IN }
+ self._pud_mapping = { PUD_OFF: self.mraa_gpio.MODE_STRONG,
+ PUD_UP: self.mraa_gpio.MODE_HIZ,
+ PUD_DOWN: self.mraa_gpio.MODE_PULLDOWN }
+ self._edge_mapping = { RISING: self.mraa_gpio.EDGE_RISING,
+ FALLING: self.mraa_gpio.EDGE_FALLING,
+ BOTH: self.mraa_gpio.EDGE_BOTH }
+
+ def setup(self,pin,mode):
+ """Set the input or output mode for a specified pin. Mode should be
+ either DIR_IN or DIR_OUT.
+ """
+ self.mraa_gpio.Gpio.dir(self.mraa_gpio.Gpio(pin),self._dir_mapping[mode])
+
+ def output(self,pin,value):
+ """Set the specified pin the provided high/low value. Value should be
+ either 1 (ON or HIGH), or 0 (OFF or LOW) or a boolean.
+ """
+ self.mraa_gpio.Gpio.write(self.mraa_gpio.Gpio(pin), value)
+
+ def input(self,pin):
+ """Read the specified pin and return HIGH/true if the pin is pulled high,
+ or LOW/false if pulled low.
+ """
+ return self.mraa_gpio.Gpio.read(self.mraa_gpio.Gpio(pin))
+
+ def add_event_detect(self, pin, edge, callback=None, bouncetime=-1):
+ """Enable edge detection events for a particular GPIO channel. Pin
+ should be type IN. Edge must be RISING, FALLING or BOTH. Callback is a
+ function for the event. Bouncetime is switch bounce timeout in ms for
+ callback
+ """
+ kwargs = {}
+ if callback:
+ kwargs['callback']=callback
+ if bouncetime > 0:
+ kwargs['bouncetime']=bouncetime
+ self.mraa_gpio.Gpio.isr(self.mraa_gpio.Gpio(pin), self._edge_mapping[edge], **kwargs)
+
+ def remove_event_detect(self, pin):
+ """Remove edge detection for a particular GPIO channel. Pin should be
+ type IN.
+ """
+ self.mraa_gpio.Gpio.isrExit(self.mraa_gpio.Gpio(pin))
+
+ def wait_for_edge(self, pin, edge):
+ """Wait for an edge. Pin should be type IN. Edge must be RISING,
+ FALLING or BOTH.
+ """
+ self.bbio_gpio.wait_for_edge(self.mraa_gpio.Gpio(pin), self._edge_mapping[edge])
+
+def get_platform_gpio(**keywords):
+ """Attempt to return a GPIO instance for the platform which the code is being
+ executed on. Currently supports only the Raspberry Pi using the RPi.GPIO
+ library and Beaglebone Black using the Adafruit_BBIO library. Will throw an
+ exception if a GPIO instance can't be created for the current platform. The
+ returned GPIO object is an instance of BaseGPIO.
+ """
+ plat = Platform.platform_detect()
+ if plat == Platform.RASPBERRY_PI:
+ import RPi.GPIO
+ return RPiGPIOAdapter(RPi.GPIO, **keywords)
+ elif plat == Platform.BEAGLEBONE_BLACK:
+ import Adafruit_BBIO.GPIO
+ return AdafruitBBIOAdapter(Adafruit_BBIO.GPIO, **keywords)
+ elif plat == Platform.MINNOWBOARD:
+ import mraa
+ return AdafruitMinnowAdapter(mraa, **keywords)
+ elif plat == Platform.UNKNOWN:
+ raise RuntimeError('Could not determine platform.')
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/I2C.py b/Adafruit_Python_GPIO/Adafruit_GPIO/I2C.py
new file mode 100644
index 0000000..765ed82
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/I2C.py
@@ -0,0 +1,202 @@
+# Copyright (c) 2014 Adafruit Industries
+# Author: Tony DiCola
+# Based on Adafruit_I2C.py created by Kevin Townsend.
+#
+# 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.
+import logging
+import os
+import subprocess
+
+import Adafruit_GPIO.Platform as Platform
+
+
+def reverseByteOrder(data):
+ """DEPRECATED: See https://github.com/adafruit/Adafruit_Python_GPIO/issues/48"""
+ # # Courtesy Vishal Sapre
+ # byteCount = len(hex(data)[2:].replace('L','')[::2])
+ # val = 0
+ # for i in range(byteCount):
+ # val = (val << 8) | (data & 0xff)
+ # data >>= 8
+ # return val
+ raise RuntimeError('reverseByteOrder is deprecated! See: https://github.com/adafruit/Adafruit_Python_GPIO/issues/48')
+
+def get_default_bus():
+ """Return the default bus number based on the device platform. For a
+ Raspberry Pi either bus 0 or 1 (based on the Pi revision) will be returned.
+ For a Beaglebone Black the first user accessible bus, 1, will be returned.
+ """
+ plat = Platform.platform_detect()
+ if plat == Platform.RASPBERRY_PI:
+ if Platform.pi_revision() == 1:
+ # Revision 1 Pi uses I2C bus 0.
+ return 0
+ else:
+ # Revision 2 Pi uses I2C bus 1.
+ return 1
+ elif plat == Platform.BEAGLEBONE_BLACK:
+ # Beaglebone Black has multiple I2C buses, default to 1 (P9_19 and P9_20).
+ return 1
+ else:
+ raise RuntimeError('Could not determine default I2C bus for platform.')
+
+def get_i2c_device(address, busnum=None, i2c_interface=None, **kwargs):
+ """Return an I2C device for the specified address and on the specified bus.
+ If busnum isn't specified, the default I2C bus for the platform will attempt
+ to be detected.
+ """
+ if busnum is None:
+ busnum = get_default_bus()
+ return Device(address, busnum, i2c_interface, **kwargs)
+
+def require_repeated_start():
+ """Enable repeated start conditions for I2C register reads. This is the
+ normal behavior for I2C, however on some platforms like the Raspberry Pi
+ there are bugs which disable repeated starts unless explicitly enabled with
+ this function. See this thread for more details:
+ http://www.raspberrypi.org/forums/viewtopic.php?f=44&t=15840
+ """
+ plat = Platform.platform_detect()
+ if plat == Platform.RASPBERRY_PI and os.path.exists('/sys/module/i2c_bcm2708/parameters/combined'):
+ # On the Raspberry Pi there is a bug where register reads don't send a
+ # repeated start condition like the kernel smbus I2C driver functions
+ # define. As a workaround this bit in the BCM2708 driver sysfs tree can
+ # be changed to enable I2C repeated starts.
+ subprocess.check_call('chmod 666 /sys/module/i2c_bcm2708/parameters/combined', shell=True)
+ subprocess.check_call('echo -n 1 > /sys/module/i2c_bcm2708/parameters/combined', shell=True)
+ # Other platforms are a no-op because they (presumably) have the correct
+ # behavior and send repeated starts.
+
+
+class Device(object):
+ """Class for communicating with an I2C device using the adafruit-pureio pure
+ python smbus library, or other smbus compatible I2C interface. Allows reading
+ and writing 8-bit, 16-bit, and byte array values to registers
+ on the device."""
+ def __init__(self, address, busnum, i2c_interface=None):
+ """Create an instance of the I2C device at the specified address on the
+ specified I2C bus number."""
+ self._address = address
+ if i2c_interface is None:
+ # Use pure python I2C interface if none is specified.
+ import Adafruit_PureIO.smbus
+ self._bus = Adafruit_PureIO.smbus.SMBus(busnum)
+ else:
+ # Otherwise use the provided class to create an smbus interface.
+ self._bus = i2c_interface(busnum)
+ self._logger = logging.getLogger('Adafruit_I2C.Device.Bus.{0}.Address.{1:#0X}' \
+ .format(busnum, address))
+
+ def writeRaw8(self, value):
+ """Write an 8-bit value on the bus (without register)."""
+ value = value & 0xFF
+ self._bus.write_byte(self._address, value)
+ self._logger.debug("Wrote 0x%02X",
+ value)
+
+ def write8(self, register, value):
+ """Write an 8-bit value to the specified register."""
+ value = value & 0xFF
+ self._bus.write_byte_data(self._address, register, value)
+ self._logger.debug("Wrote 0x%02X to register 0x%02X",
+ value, register)
+
+ def write16(self, register, value):
+ """Write a 16-bit value to the specified register."""
+ value = value & 0xFFFF
+ self._bus.write_word_data(self._address, register, value)
+ self._logger.debug("Wrote 0x%04X to register pair 0x%02X, 0x%02X",
+ value, register, register+1)
+
+ def writeList(self, register, data):
+ """Write bytes to the specified register."""
+ self._bus.write_i2c_block_data(self._address, register, data)
+ self._logger.debug("Wrote to register 0x%02X: %s",
+ register, data)
+
+ def readList(self, register, length):
+ """Read a length number of bytes from the specified register. Results
+ will be returned as a bytearray."""
+ results = self._bus.read_i2c_block_data(self._address, register, length)
+ self._logger.debug("Read the following from register 0x%02X: %s",
+ register, results)
+ return results
+
+ def readRaw8(self):
+ """Read an 8-bit value on the bus (without register)."""
+ result = self._bus.read_byte(self._address) & 0xFF
+ self._logger.debug("Read 0x%02X",
+ result)
+ return result
+
+ def readU8(self, register):
+ """Read an unsigned byte from the specified register."""
+ result = self._bus.read_byte_data(self._address, register) & 0xFF
+ self._logger.debug("Read 0x%02X from register 0x%02X",
+ result, register)
+ return result
+
+ def readS8(self, register):
+ """Read a signed byte from the specified register."""
+ result = self.readU8(register)
+ if result > 127:
+ result -= 256
+ return result
+
+ def readU16(self, register, little_endian=True):
+ """Read an unsigned 16-bit value from the specified register, with the
+ specified endianness (default little endian, or least significant byte
+ first)."""
+ result = self._bus.read_word_data(self._address,register) & 0xFFFF
+ self._logger.debug("Read 0x%04X from register pair 0x%02X, 0x%02X",
+ result, register, register+1)
+ # Swap bytes if using big endian because read_word_data assumes little
+ # endian on ARM (little endian) systems.
+ if not little_endian:
+ result = ((result << 8) & 0xFF00) + (result >> 8)
+ return result
+
+ def readS16(self, register, little_endian=True):
+ """Read a signed 16-bit value from the specified register, with the
+ specified endianness (default little endian, or least significant byte
+ first)."""
+ result = self.readU16(register, little_endian)
+ if result > 32767:
+ result -= 65536
+ return result
+
+ def readU16LE(self, register):
+ """Read an unsigned 16-bit value from the specified register, in little
+ endian byte order."""
+ return self.readU16(register, little_endian=True)
+
+ def readU16BE(self, register):
+ """Read an unsigned 16-bit value from the specified register, in big
+ endian byte order."""
+ return self.readU16(register, little_endian=False)
+
+ def readS16LE(self, register):
+ """Read a signed 16-bit value from the specified register, in little
+ endian byte order."""
+ return self.readS16(register, little_endian=True)
+
+ def readS16BE(self, register):
+ """Read a signed 16-bit value from the specified register, in big
+ endian byte order."""
+ return self.readS16(register, little_endian=False)
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/MCP230xx.py b/Adafruit_Python_GPIO/Adafruit_GPIO/MCP230xx.py
new file mode 100644
index 0000000..3ba8b5f
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/MCP230xx.py
@@ -0,0 +1,165 @@
+# Copyright (c) 2014 Adafruit Industries
+# Author: Tony DiCola
+#
+# 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.
+import math
+
+import Adafruit_GPIO as GPIO
+import Adafruit_GPIO.I2C as I2C
+
+
+class MCP230xxBase(GPIO.BaseGPIO):
+ """Base class to represent an MCP230xx series GPIO extender. Is compatible
+ with the Adafruit_GPIO BaseGPIO class so it can be used as a custom GPIO
+ class for interacting with device.
+ """
+
+ def __init__(self, address, i2c=None, **kwargs):
+ """Initialize MCP230xx at specified I2C address and bus number. If bus
+ is not specified it will default to the appropriate platform detected bus.
+ """
+ # Create I2C device.
+ if i2c is None:
+ import Adafruit_GPIO.I2C as I2C
+ i2c = I2C
+ self._device = i2c.get_i2c_device(address, **kwargs)
+ # Assume starting in ICON.BANK = 0 mode (sequential access).
+ # Compute how many bytes are needed to store count of GPIO.
+ self.gpio_bytes = int(math.ceil(self.NUM_GPIO/8.0))
+ # Buffer register values so they can be changed without reading.
+ self.iodir = [0xFF]*self.gpio_bytes # Default direction to all inputs.
+ self.gppu = [0x00]*self.gpio_bytes # Default to pullups disabled.
+ self.gpio = [0x00]*self.gpio_bytes
+ # Write current direction and pullup buffer state.
+ self.write_iodir()
+ self.write_gppu()
+
+
+ def setup(self, pin, value):
+ """Set the input or output mode for a specified pin. Mode should be
+ either GPIO.OUT or GPIO.IN.
+ """
+ self._validate_pin(pin)
+ # Set bit to 1 for input or 0 for output.
+ if value == GPIO.IN:
+ self.iodir[int(pin/8)] |= 1 << (int(pin%8))
+ elif value == GPIO.OUT:
+ self.iodir[int(pin/8)] &= ~(1 << (int(pin%8)))
+ else:
+ raise ValueError('Unexpected value. Must be GPIO.IN or GPIO.OUT.')
+ self.write_iodir()
+
+
+ def output(self, pin, value):
+ """Set the specified pin the provided high/low value. Value should be
+ either GPIO.HIGH/GPIO.LOW or a boolean (True = HIGH).
+ """
+ self.output_pins({pin: value})
+
+ def output_pins(self, pins):
+ """Set multiple pins high or low at once. Pins should be a dict of pin
+ name to pin value (HIGH/True for 1, LOW/False for 0). All provided pins
+ will be set to the given values.
+ """
+ [self._validate_pin(pin) for pin in pins.keys()]
+ # Set each changed pin's bit.
+ for pin, value in iter(pins.items()):
+ if value:
+ self.gpio[int(pin/8)] |= 1 << (int(pin%8))
+ else:
+ self.gpio[int(pin/8)] &= ~(1 << (int(pin%8)))
+ # Write GPIO state.
+ self.write_gpio()
+
+
+ def input(self, pin):
+ """Read the specified pin and return GPIO.HIGH/True if the pin is pulled
+ high, or GPIO.LOW/False if pulled low.
+ """
+ return self.input_pins([pin])[0]
+
+ def input_pins(self, pins):
+ """Read multiple pins specified in the given list and return list of pin values
+ GPIO.HIGH/True if the pin is pulled high, or GPIO.LOW/False if pulled low.
+ """
+ [self._validate_pin(pin) for pin in pins]
+ # Get GPIO state.
+ self.gpio = self._device.readList(self.GPIO, self.gpio_bytes)
+ # Return True if pin's bit is set.
+ return [(self.gpio[int(pin/8)] & 1 << (int(pin%8))) > 0 for pin in pins]
+
+
+ def pullup(self, pin, enabled):
+ """Turn on the pull-up resistor for the specified pin if enabled is True,
+ otherwise turn off the pull-up resistor.
+ """
+ self._validate_pin(pin)
+ if enabled:
+ self.gppu[int(pin/8)] |= 1 << (int(pin%8))
+ else:
+ self.gppu[int(pin/8)] &= ~(1 << (int(pin%8)))
+ self.write_gppu()
+
+ def write_gpio(self, gpio=None):
+ """Write the specified byte value to the GPIO registor. If no value
+ specified the current buffered value will be written.
+ """
+ if gpio is not None:
+ self.gpio = gpio
+ self._device.writeList(self.GPIO, self.gpio)
+
+ def write_iodir(self, iodir=None):
+ """Write the specified byte value to the IODIR registor. If no value
+ specified the current buffered value will be written.
+ """
+ if iodir is not None:
+ self.iodir = iodir
+ self._device.writeList(self.IODIR, self.iodir)
+
+ def write_gppu(self, gppu=None):
+ """Write the specified byte value to the GPPU registor. If no value
+ specified the current buffered value will be written.
+ """
+ if gppu is not None:
+ self.gppu = gppu
+ self._device.writeList(self.GPPU, self.gppu)
+
+
+class MCP23017(MCP230xxBase):
+ """MCP23017-based GPIO class with 16 GPIO pins."""
+ # Define number of pins and registor addresses.
+ NUM_GPIO = 16
+ IODIR = 0x00
+ GPIO = 0x12
+ GPPU = 0x0C
+
+ def __init__(self, address=0x20, **kwargs):
+ super(MCP23017, self).__init__(address, **kwargs)
+
+
+class MCP23008(MCP230xxBase):
+ """MCP23008-based GPIO class with 8 GPIO pins."""
+ # Define number of pins and registor addresses.
+ NUM_GPIO = 8
+ IODIR = 0x00
+ GPIO = 0x09
+ GPPU = 0x06
+
+ def __init__(self, address=0x20, **kwargs):
+ super(MCP23008, self).__init__(address, **kwargs)
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/PCA95xx.py b/Adafruit_Python_GPIO/Adafruit_GPIO/PCA95xx.py
new file mode 100644
index 0000000..2a3b406
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/PCA95xx.py
@@ -0,0 +1,121 @@
+'''
+Adafruit compatible using BaseGPIO class to represent a PCA9555 IO expander
+Copyright (C) 2016 Matias Vidal
+Ported from: https://github.com/dberlin/PCA95XX
+
+# Copyright 2012 Daniel Berlin
+
+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.'''
+
+import Adafruit_GPIO as GPIO
+import Adafruit_GPIO.I2C as I2C
+
+# For the PCA 953X and 955X series, the chips with 8 GPIO's have these port numbers
+# The chips with 16 GPIO's have the first port for each type at double these numbers
+# IE The first config port is 6
+
+INPUT_PORT = 0
+OUTPUT_PORT = 1
+POLARITY_PORT = 2
+CONFIG_PORT = 3
+
+IN = GPIO.IN
+OUT = GPIO.OUT
+HIGH = GPIO.HIGH
+LOW = GPIO.LOW
+
+
+class PCA9555(GPIO.BaseGPIO):
+ """Class to represent a PCA9555 GPIO extender. Compatible
+ with the Adafruit_GPIO BaseGPIO class so it can be used as a custom GPIO
+ class for interacting with device.
+ """
+ NUM_GPIO = 16
+
+ def __init__(self, address=0x20, busnum=None, i2c=None, num_gpios=16, **kwargs):
+ address = int(address)
+ self.__name__ = "PCA955"
+ # Create I2C device.
+ i2c = i2c or I2C
+ busnum = busnum or i2c.get_default_bus()
+ self._device = i2c.get_i2c_device(address, busnum, **kwargs)
+ self.num_gpios = num_gpios
+
+ if self.num_gpios <= 8:
+ self.iodir = self._device.readU8(CONFIG_PORT)
+ self.outputvalue = self._device.readU8(OUTPUT_PORT)
+
+ elif self.num_gpios > 8 and self.num_gpios <= 16:
+ self.iodir = self._device.readU16(CONFIG_PORT<< 1)
+ self.outputvalue = self._device.readU16(OUTPUT_PORT << 1)
+
+ def _changebit(self, bitmap, bit, value):
+ assert value == 1 or value == 0, "Value is %s must be 1 or 0" % value
+ if value == 0:
+ return bitmap & ~(1 << bit)
+ elif value == 1:
+ return bitmap | (1 << bit)
+
+ # Change the value of bit PIN on port PORT to VALUE. If the
+ # current pin state for the port is passed in as PORTSTATE, we
+ # will avoid doing a read to get it. The port pin state must be
+ # complete if passed in (IE it should not just be the value of the
+ # single pin we are trying to change)
+ def _readandchangepin(self, port, pin, value, portstate = None):
+ assert pin >= 0 and pin < self.num_gpios, "Pin number %s is invalid, only 0-%s are valid" % (pin, self.num_gpios)
+ if not portstate:
+ if self.num_gpios <= 8:
+ portstate = self._device.readU8(port)
+ elif self.num_gpios > 8 and self.num_gpios <= 16:
+ portstate = self._device.readU16(port << 1)
+ newstate = self._changebit(portstate, pin, value)
+ if self.num_gpios <= 8:
+ self._device.write8(port, newstate)
+ else:
+ self._device.write16(port << 1, newstate)
+ return newstate
+
+ # Polarity inversion
+ def polarity(self, pin, value):
+ return self._readandchangepin(POLARITY_PORT, pin, value)
+
+ # Pin direction
+ def config(self, pin, mode):
+ self.iodir = self._readandchangepin(CONFIG_PORT, pin, mode, self.iodir)
+ return self.iodir
+
+ def output(self, pin, value):
+ assert self.iodir & (1 << pin) == 0, "Pin %s not set to output" % pin
+ self.outputvalue = self._readandchangepin(OUTPUT_PORT, pin, value, self.outputvalue)
+ return self.outputvalue
+
+ def input(self, pin):
+ assert self.iodir & (1 << pin) != 0, "Pin %s not set to input" % pin
+ if self.num_gpios <= 8:
+ value = self._device.readU8(INPUT_PORT)
+ elif self.num_gpios > 8 and self.num_gpios <= 16:
+ value = self._device.readU16(INPUT_PORT << 1)
+ return value & (1 << pin)
+
+ def setup(self, pin, mode):
+ self.config(pin, mode)
+
+ def cleanup(self, pin=None):
+ # nothing to cleanup
+ pass
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/PCF8574.py b/Adafruit_Python_GPIO/Adafruit_GPIO/PCF8574.py
new file mode 100644
index 0000000..02919d1
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/PCF8574.py
@@ -0,0 +1,94 @@
+'''
+Adafruit compatible using BaseGPIO class to represent a PCF8574/A IO expander
+Copyright (C) 2015 Sylvan Butler
+
+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.'''
+
+import Adafruit_GPIO as GPIO
+import Adafruit_GPIO.I2C as I2C
+
+
+
+IN = GPIO.IN
+OUT = GPIO.OUT
+HIGH = GPIO.HIGH
+LOW = GPIO.LOW
+
+
+class PCF8574(GPIO.BaseGPIO):
+ """Class to represent a PCF8574 or PCF8574A GPIO extender. Compatible
+ with the Adafruit_GPIO BaseGPIO class so it can be used as a custom GPIO
+ class for interacting with device.
+ """
+
+ NUM_GPIO = 8
+
+ def __init__(self, address=0x27, busnum=None, i2c=None, **kwargs):
+ address = int(address)
+ self.__name__ = \
+ "PCF8574" if address in range(0x20, 0x28) else \
+ "PCF8574A" if address in range(0x38, 0x40) else \
+ "Bad address for PCF8574(A): 0x%02X not in range [0x20..0x27, 0x38..0x3F]" % address
+ if self.__name__[0] != 'P':
+ raise ValueError(self.__name__)
+ # Create I2C device.
+ i2c = i2c or I2C
+ busnum = busnum or i2c.get_default_bus()
+ self._device = i2c.get_i2c_device(address, busnum, **kwargs)
+ # Buffer register values so they can be changed without reading.
+ self.iodir = 0xFF # Default direction to all inputs is in
+ self.gpio = 0x00
+ self._write_pins()
+
+
+ def _write_pins(self):
+ self._device.writeRaw8(self.gpio | self.iodir)
+
+ def _read_pins(self):
+ return self._device.readRaw8() & self.iodir
+
+
+ def setup(self, pin, mode):
+ self.setup_pins({pin: mode})
+
+ def setup_pins(self, pins):
+ if False in [y for x,y in [(self._validate_pin(pin),mode in (IN,OUT)) for pin,mode in pins.items()]]:
+ raise ValueError('Invalid MODE, IN or OUT')
+ for pin,mode in pins.items():
+ self.iodir = self._bit2(self.iodir, pin, mode)
+ self._write_pins()
+
+
+ def output(self, pin, value):
+ self.output_pins({pin: value})
+
+ def output_pins(self, pins):
+ [self._validate_pin(pin) for pin in pins.keys()]
+ for pin,value in pins.items():
+ self.gpio = self._bit2(self.gpio, pin, bool(value))
+ self._write_pins()
+
+
+ def input(self, pin):
+ return self.input_pins([pin])[0]
+
+ def input_pins(self, pins):
+ [self._validate_pin(pin) for pin in pins]
+ inp = self._read_pins()
+ return [bool(inp & (1<<pin)) for pin in pins]
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/PWM.py b/Adafruit_Python_GPIO/Adafruit_GPIO/PWM.py
new file mode 100644
index 0000000..1e1717a
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/PWM.py
@@ -0,0 +1,128 @@
+# Copyright (c) 2014 Adafruit Industries
+# Author: Tony DiCola
+#
+# 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.
+import Adafruit_GPIO.Platform as Platform
+
+
+class RPi_PWM_Adapter(object):
+ """PWM implementation for the Raspberry Pi using the RPi.GPIO PWM library."""
+
+ def __init__(self, rpi_gpio, mode=None):
+ self.rpi_gpio = rpi_gpio
+ # Suppress warnings about GPIO in use.
+ rpi_gpio.setwarnings(False)
+ # Set board or BCM pin numbering.
+ if mode == rpi_gpio.BOARD or mode == rpi_gpio.BCM:
+ rpi_gpio.setmode(mode)
+ elif mode is not None:
+ raise ValueError('Unexpected value for mode. Must be BOARD or BCM.')
+ else:
+ # Default to BCM numbering if not told otherwise.
+ rpi_gpio.setmode(rpi_gpio.BCM)
+ # Store reference to each created PWM instance.
+ self.pwm = {}
+
+ def start(self, pin, dutycycle, frequency_hz=2000):
+ """Enable PWM output on specified pin. Set to intiial percent duty cycle
+ value (0.0 to 100.0) and frequency (in Hz).
+ """
+ if dutycycle < 0.0 or dutycycle > 100.0:
+ raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
+ # Make pin an output.
+ self.rpi_gpio.setup(pin, self.rpi_gpio.OUT)
+ # Create PWM instance and save a reference for later access.
+ self.pwm[pin] = self.rpi_gpio.PWM(pin, frequency_hz)
+ # Start the PWM at the specified duty cycle.
+ self.pwm[pin].start(dutycycle)
+
+ def set_duty_cycle(self, pin, dutycycle):
+ """Set percent duty cycle of PWM output on specified pin. Duty cycle must
+ be a value 0.0 to 100.0 (inclusive).
+ """
+ if dutycycle < 0.0 or dutycycle > 100.0:
+ raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
+ if pin not in self.pwm:
+ raise ValueError('Pin {0} is not configured as a PWM. Make sure to first call start for the pin.'.format(pin))
+ self.pwm[pin].ChangeDutyCycle(dutycycle)
+
+ def set_frequency(self, pin, frequency_hz):
+ """Set frequency (in Hz) of PWM output on specified pin."""
+ if pin not in self.pwm:
+ raise ValueError('Pin {0} is not configured as a PWM. Make sure to first call start for the pin.'.format(pin))
+ self.pwm[pin].ChangeFrequency(frequency_hz)
+
+ def stop(self, pin):
+ """Stop PWM output on specified pin."""
+ if pin not in self.pwm:
+ raise ValueError('Pin {0} is not configured as a PWM. Make sure to first call start for the pin.'.format(pin))
+ self.pwm[pin].stop()
+ del self.pwm[pin]
+
+
+class BBIO_PWM_Adapter(object):
+ """PWM implementation for the BeagleBone Black using the Adafruit_BBIO.PWM
+ library.
+ """
+
+ def __init__(self, bbio_pwm):
+ self.bbio_pwm = bbio_pwm
+
+ def start(self, pin, dutycycle, frequency_hz=2000):
+ """Enable PWM output on specified pin. Set to intiial percent duty cycle
+ value (0.0 to 100.0) and frequency (in Hz).
+ """
+ if dutycycle < 0.0 or dutycycle > 100.0:
+ raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
+ self.bbio_pwm.start(pin, dutycycle, frequency_hz)
+
+ def set_duty_cycle(self, pin, dutycycle):
+ """Set percent duty cycle of PWM output on specified pin. Duty cycle must
+ be a value 0.0 to 100.0 (inclusive).
+ """
+ if dutycycle < 0.0 or dutycycle > 100.0:
+ raise ValueError('Invalid duty cycle value, must be between 0.0 to 100.0 (inclusive).')
+ self.bbio_pwm.set_duty_cycle(pin, dutycycle)
+
+ def set_frequency(self, pin, frequency_hz):
+ """Set frequency (in Hz) of PWM output on specified pin."""
+ self.bbio_pwm.set_frequency(pin, frequency_hz)
+
+ def stop(self, pin):
+ """Stop PWM output on specified pin."""
+ self.bbio_pwm.stop(pin)
+
+
+def get_platform_pwm(**keywords):
+ """Attempt to return a PWM instance for the platform which the code is being
+ executed on. Currently supports only the Raspberry Pi using the RPi.GPIO
+ library and Beaglebone Black using the Adafruit_BBIO library. Will throw an
+ exception if a PWM instance can't be created for the current platform. The
+ returned PWM object has the same interface as the RPi_PWM_Adapter and
+ BBIO_PWM_Adapter classes.
+ """
+ plat = Platform.platform_detect()
+ if plat == Platform.RASPBERRY_PI:
+ import RPi.GPIO
+ return RPi_PWM_Adapter(RPi.GPIO, **keywords)
+ elif plat == Platform.BEAGLEBONE_BLACK:
+ import Adafruit_BBIO.PWM
+ return BBIO_PWM_Adapter(Adafruit_BBIO.PWM, **keywords)
+ elif plat == Platform.UNKNOWN:
+ raise RuntimeError('Could not determine platform.')
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/Platform.py b/Adafruit_Python_GPIO/Adafruit_GPIO/Platform.py
new file mode 100644
index 0000000..2c041a8
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/Platform.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2014 Adafruit Industries
+# Author: Tony DiCola
+
+# 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.
+import platform
+import re
+
+# Platform identification constants.
+UNKNOWN = 0
+RASPBERRY_PI = 1
+BEAGLEBONE_BLACK = 2
+MINNOWBOARD = 3
+
+def platform_detect():
+ """Detect if running on the Raspberry Pi or Beaglebone Black and return the
+ platform type. Will return RASPBERRY_PI, BEAGLEBONE_BLACK, or UNKNOWN."""
+ # Handle Raspberry Pi
+ pi = pi_version()
+ if pi is not None:
+ return RASPBERRY_PI
+
+ # Handle Beaglebone Black
+ # TODO: Check the Beaglebone Black /proc/cpuinfo value instead of reading
+ # the platform.
+ plat = platform.platform()
+ if plat.lower().find('armv7l-with-debian') > -1:
+ return BEAGLEBONE_BLACK
+ elif plat.lower().find('armv7l-with-ubuntu') > -1:
+ return BEAGLEBONE_BLACK
+ elif plat.lower().find('armv7l-with-glibc2.4') > -1:
+ return BEAGLEBONE_BLACK
+
+ # Handle Minnowboard
+ # Assumption is that mraa is installed
+ try:
+ import mraa
+ if mraa.getPlatformName()=='MinnowBoard MAX':
+ return MINNOWBOARD
+ except ImportError:
+ pass
+
+ # Couldn't figure out the platform, just return unknown.
+ return UNKNOWN
+
+
+def pi_revision():
+ """Detect the revision number of a Raspberry Pi, useful for changing
+ functionality like default I2C bus based on revision."""
+ # Revision list available at: http://elinux.org/RPi_HardwareHistory#Board_Revision_History
+ with open('/proc/cpuinfo', 'r') as infile:
+ for line in infile:
+ # Match a line of the form "Revision : 0002" while ignoring extra
+ # info in front of the revsion (like 1000 when the Pi was over-volted).
+ match = re.match('Revision\s+:\s+.*(\w{4})$', line, flags=re.IGNORECASE)
+ if match and match.group(1) in ['0000', '0002', '0003']:
+ # Return revision 1 if revision ends with 0000, 0002 or 0003.
+ return 1
+ elif match:
+ # Assume revision 2 if revision ends with any other 4 chars.
+ return 2
+ # Couldn't find the revision, throw an exception.
+ raise RuntimeError('Could not determine Raspberry Pi revision.')
+
+
+def pi_version():
+ """Detect the version of the Raspberry Pi. Returns either 1, 2 or
+ None depending on if it's a Raspberry Pi 1 (model A, B, A+, B+),
+ Raspberry Pi 2 (model B+), or not a Raspberry Pi.
+ """
+ # Check /proc/cpuinfo for the Hardware field value.
+ # 2708 is pi 1
+ # 2709 is pi 2
+ # 2835 is pi 3 on 4.9.x kernel
+ # Anything else is not a pi.
+ with open('/proc/cpuinfo', 'r') as infile:
+ cpuinfo = infile.read()
+ # Match a line like 'Hardware : BCM2709'
+ match = re.search('^Hardware\s+:\s+(\w+)$', cpuinfo,
+ flags=re.MULTILINE | re.IGNORECASE)
+ if not match:
+ # Couldn't find the hardware, assume it isn't a pi.
+ return None
+ if match.group(1) == 'BCM2708':
+ # Pi 1
+ return 1
+ elif match.group(1) == 'BCM2709':
+ # Pi 2
+ return 2
+ elif match.group(1) == 'BCM2835':
+ # Pi 3 / Pi on 4.9.x kernel
+ return 3
+ else:
+ # Something else, not a pi.
+ return None
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/SPI.py b/Adafruit_Python_GPIO/Adafruit_GPIO/SPI.py
new file mode 100644
index 0000000..c20a32c
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/SPI.py
@@ -0,0 +1,328 @@
+# Copyright (c) 2014 Adafruit Industries
+# Author: Tony DiCola
+#
+# 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.
+
+import operator
+import time
+
+import Adafruit_GPIO as GPIO
+
+
+MSBFIRST = 0
+LSBFIRST = 1
+
+
+class SpiDev(object):
+ """Hardware-based SPI implementation using the spidev interface."""
+
+ def __init__(self, port, device, max_speed_hz=500000):
+ """Initialize an SPI device using the SPIdev interface. Port and device
+ identify the device, for example the device /dev/spidev1.0 would be port
+ 1 and device 0.
+ """
+ import spidev
+ self._device = spidev.SpiDev()
+ self._device.open(port, device)
+ self._device.max_speed_hz=max_speed_hz
+ # Default to mode 0, and make sure CS is active low.
+ self._device.mode = 0
+ self._device.cshigh = False
+
+ def set_clock_hz(self, hz):
+ """Set the speed of the SPI clock in hertz. Note that not all speeds
+ are supported and a lower speed might be chosen by the hardware.
+ """
+ self._device.max_speed_hz=hz
+
+ def set_mode(self, mode):
+ """Set SPI mode which controls clock polarity and phase. Should be a
+ numeric value 0, 1, 2, or 3. See wikipedia page for details on meaning:
+ http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
+ """
+ if mode < 0 or mode > 3:
+ raise ValueError('Mode must be a value 0, 1, 2, or 3.')
+ self._device.mode = mode
+
+ def set_bit_order(self, order):
+ """Set order of bits to be read/written over serial lines. Should be
+ either MSBFIRST for most-significant first, or LSBFIRST for
+ least-signifcant first.
+ """
+ if order == MSBFIRST:
+ self._device.lsbfirst = False
+ elif order == LSBFIRST:
+ self._device.lsbfirst = True
+ else:
+ raise ValueError('Order must be MSBFIRST or LSBFIRST.')
+
+ def close(self):
+ """Close communication with the SPI device."""
+ self._device.close()
+
+ def write(self, data):
+ """Half-duplex SPI write. The specified array of bytes will be clocked
+ out the MOSI line.
+ """
+ self._device.writebytes(data)
+
+ def read(self, length):
+ """Half-duplex SPI read. The specified length of bytes will be clocked
+ in the MISO line and returned as a bytearray object.
+ """
+ return bytearray(self._device.readbytes(length))
+
+ def transfer(self, data):
+ """Full-duplex SPI read and write. The specified array of bytes will be
+ clocked out the MOSI line, while simultaneously bytes will be read from
+ the MISO line. Read bytes will be returned as a bytearray object.
+ """
+ return bytearray(self._device.xfer2(data))
+
+class SpiDevMraa(object):
+ """Hardware SPI implementation with the mraa library on Minnowboard"""
+ def __init__(self, port, device, max_speed_hz=500000):
+ import mraa
+ self._device = mraa.Spi(0)
+ self._device.mode(0)
+
+ def set_clock_hz(self, hz):
+ """Set the speed of the SPI clock in hertz. Note that not all speeds
+ are supported and a lower speed might be chosen by the hardware.
+ """
+ self._device.frequency(hz)
+
+ def set_mode(self,mode):
+ """Set SPI mode which controls clock polarity and phase. Should be a
+ numeric value 0, 1, 2, or 3. See wikipedia page for details on meaning:
+ http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
+ """
+ if mode < 0 or mode > 3:
+ raise ValueError('Mode must be a value 0, 1, 2, or 3.')
+ self._device.mode(mode)
+
+ def set_bit_order(self, order):
+ """Set order of bits to be read/written over serial lines. Should be
+ either MSBFIRST for most-significant first, or LSBFIRST for
+ least-signifcant first.
+ """
+ if order == MSBFIRST:
+ self._device.lsbmode(False)
+ elif order == LSBFIRST:
+ self._device.lsbmode(True)
+ else:
+ raise ValueError('Order must be MSBFIRST or LSBFIRST.')
+
+ def close(self):
+ """Close communication with the SPI device."""
+ self._device.Spi()
+
+ def write(self, data):
+ """Half-duplex SPI write. The specified array of bytes will be clocked
+ out the MOSI line.
+ """
+ self._device.write(bytearray(data))
+
+class BitBang(object):
+ """Software-based implementation of the SPI protocol over GPIO pins."""
+
+ def __init__(self, gpio, sclk, mosi=None, miso=None, ss=None):
+ """Initialize bit bang (or software) based SPI. Must provide a BaseGPIO
+ class, the SPI clock, and optionally MOSI, MISO, and SS (slave select)
+ pin numbers. If MOSI is set to None then writes will be disabled and fail
+ with an error, likewise for MISO reads will be disabled. If SS is set to
+ None then SS will not be asserted high/low by the library when
+ transfering data.
+ """
+ self._gpio = gpio
+ self._sclk = sclk
+ self._mosi = mosi
+ self._miso = miso
+ self._ss = ss
+ # Set pins as outputs/inputs.
+ gpio.setup(sclk, GPIO.OUT)
+ if mosi is not None:
+ gpio.setup(mosi, GPIO.OUT)
+ if miso is not None:
+ gpio.setup(miso, GPIO.IN)
+ if ss is not None:
+ gpio.setup(ss, GPIO.OUT)
+ # Assert SS high to start with device communication off.
+ gpio.set_high(ss)
+ # Assume mode 0.
+ self.set_mode(0)
+ # Assume most significant bit first order.
+ self.set_bit_order(MSBFIRST)
+
+ def set_clock_hz(self, hz):
+ """Set the speed of the SPI clock. This is unsupported with the bit
+ bang SPI class and will be ignored.
+ """
+ pass
+
+ def set_mode(self, mode):
+ """Set SPI mode which controls clock polarity and phase. Should be a
+ numeric value 0, 1, 2, or 3. See wikipedia page for details on meaning:
+ http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
+ """
+ if mode < 0 or mode > 3:
+ raise ValueError('Mode must be a value 0, 1, 2, or 3.')
+ if mode & 0x02:
+ # Clock is normally high in mode 2 and 3.
+ self._clock_base = GPIO.HIGH
+ else:
+ # Clock is normally low in mode 0 and 1.
+ self._clock_base = GPIO.LOW
+ if mode & 0x01:
+ # Read on trailing edge in mode 1 and 3.
+ self._read_leading = False
+ else:
+ # Read on leading edge in mode 0 and 2.
+ self._read_leading = True
+ # Put clock into its base state.
+ self._gpio.output(self._sclk, self._clock_base)
+
+ def set_bit_order(self, order):
+ """Set order of bits to be read/written over serial lines. Should be
+ either MSBFIRST for most-significant first, or LSBFIRST for
+ least-signifcant first.
+ """
+ # Set self._mask to the bitmask which points at the appropriate bit to
+ # read or write, and appropriate left/right shift operator function for
+ # reading/writing.
+ if order == MSBFIRST:
+ self._mask = 0x80
+ self._write_shift = operator.lshift
+ self._read_shift = operator.rshift
+ elif order == LSBFIRST:
+ self._mask = 0x01
+ self._write_shift = operator.rshift
+ self._read_shift = operator.lshift
+ else:
+ raise ValueError('Order must be MSBFIRST or LSBFIRST.')
+
+ def close(self):
+ """Close the SPI connection. Unused in the bit bang implementation."""
+ pass
+
+ def write(self, data, assert_ss=True, deassert_ss=True):
+ """Half-duplex SPI write. If assert_ss is True, the SS line will be
+ asserted low, the specified bytes will be clocked out the MOSI line, and
+ if deassert_ss is True the SS line be put back high.
+ """
+ # Fail MOSI is not specified.
+ if self._mosi is None:
+ raise RuntimeError('Write attempted with no MOSI pin specified.')
+ if assert_ss and self._ss is not None:
+ self._gpio.set_low(self._ss)
+ for byte in data:
+ for i in range(8):
+ # Write bit to MOSI.
+ if self._write_shift(byte, i) & self._mask:
+ self._gpio.set_high(self._mosi)
+ else:
+ self._gpio.set_low(self._mosi)
+ # Flip clock off base.
+ self._gpio.output(self._sclk, not self._clock_base)
+ # Return clock to base.
+ self._gpio.output(self._sclk, self._clock_base)
+ if deassert_ss and self._ss is not None:
+ self._gpio.set_high(self._ss)
+
+ def read(self, length, assert_ss=True, deassert_ss=True):
+ """Half-duplex SPI read. If assert_ss is true, the SS line will be
+ asserted low, the specified length of bytes will be clocked in the MISO
+ line, and if deassert_ss is true the SS line will be put back high.
+ Bytes which are read will be returned as a bytearray object.
+ """
+ if self._miso is None:
+ raise RuntimeError('Read attempted with no MISO pin specified.')
+ if assert_ss and self._ss is not None:
+ self._gpio.set_low(self._ss)
+ result = bytearray(length)
+ for i in range(length):
+ for j in range(8):
+ # Flip clock off base.
+ self._gpio.output(self._sclk, not self._clock_base)
+ # Handle read on leading edge of clock.
+ if self._read_leading:
+ if self._gpio.is_high(self._miso):
+ # Set bit to 1 at appropriate location.
+ result[i] |= self._read_shift(self._mask, j)
+ else:
+ # Set bit to 0 at appropriate location.
+ result[i] &= ~self._read_shift(self._mask, j)
+ # Return clock to base.
+ self._gpio.output(self._sclk, self._clock_base)
+ # Handle read on trailing edge of clock.
+ if not self._read_leading:
+ if self._gpio.is_high(self._miso):
+ # Set bit to 1 at appropriate location.
+ result[i] |= self._read_shift(self._mask, j)
+ else:
+ # Set bit to 0 at appropriate location.
+ result[i] &= ~self._read_shift(self._mask, j)
+ if deassert_ss and self._ss is not None:
+ self._gpio.set_high(self._ss)
+ return result
+
+ def transfer(self, data, assert_ss=True, deassert_ss=True):
+ """Full-duplex SPI read and write. If assert_ss is true, the SS line
+ will be asserted low, the specified bytes will be clocked out the MOSI
+ line while bytes will also be read from the MISO line, and if
+ deassert_ss is true the SS line will be put back high. Bytes which are
+ read will be returned as a bytearray object.
+ """
+ if self._mosi is None:
+ raise RuntimeError('Write attempted with no MOSI pin specified.')
+ if self._mosi is None:
+ raise RuntimeError('Read attempted with no MISO pin specified.')
+ if assert_ss and self._ss is not None:
+ self._gpio.set_low(self._ss)
+ result = bytearray(len(data))
+ for i in range(len(data)):
+ for j in range(8):
+ # Write bit to MOSI.
+ if self._write_shift(data[i], j) & self._mask:
+ self._gpio.set_high(self._mosi)
+ else:
+ self._gpio.set_low(self._mosi)
+ # Flip clock off base.
+ self._gpio.output(self._sclk, not self._clock_base)
+ # Handle read on leading edge of clock.
+ if self._read_leading:
+ if self._gpio.is_high(self._miso):
+ # Set bit to 1 at appropriate location.
+ result[i] |= self._read_shift(self._mask, j)
+ else:
+ # Set bit to 0 at appropriate location.
+ result[i] &= ~self._read_shift(self._mask, j)
+ # Return clock to base.
+ self._gpio.output(self._sclk, self._clock_base)
+ # Handle read on trailing edge of clock.
+ if not self._read_leading:
+ if self._gpio.is_high(self._miso):
+ # Set bit to 1 at appropriate location.
+ result[i] |= self._read_shift(self._mask, j)
+ else:
+ # Set bit to 0 at appropriate location.
+ result[i] &= ~self._read_shift(self._mask, j)
+ if deassert_ss and self._ss is not None:
+ self._gpio.set_high(self._ss)
+ return result
diff --git a/Adafruit_Python_GPIO/Adafruit_GPIO/__init__.py b/Adafruit_Python_GPIO/Adafruit_GPIO/__init__.py
new file mode 100644
index 0000000..7e99604
--- /dev/null
+++ b/Adafruit_Python_GPIO/Adafruit_GPIO/__init__.py
@@ -0,0 +1,3 @@
+from __future__ import absolute_import
+
+from Adafruit_GPIO.GPIO import *