summaryrefslogtreecommitdiff
path: root/Adafruit_Python_GPIO/Adafruit_GPIO/SPI.py
blob: c20a32cbce0f994fe093c5ef543149fc834309e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
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