diff options
Diffstat (limited to 'Adafruit_Python_GPIO/Adafruit_GPIO/SPI.py')
-rw-r--r-- | Adafruit_Python_GPIO/Adafruit_GPIO/SPI.py | 328 |
1 files changed, 328 insertions, 0 deletions
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 |