diff --git a/docs/images/spi_device_hierarchy.dot b/docs/images/spi_device_hierarchy.dot
index aa0769a..a205b03 100644
--- a/docs/images/spi_device_hierarchy.dot
+++ b/docs/images/spi_device_hierarchy.dot
@@ -11,6 +11,7 @@ digraph classes {
MCP3xxx;
MCP30xx;
MCP32xx;
+ MCP3xx2;
MCP33xx;
/* Concrete classes */
@@ -21,6 +22,8 @@ digraph classes {
MCP30xx->MCP3xxx;
MCP32xx->MCP3xxx;
MCP33xx->MCP3xxx;
+ MCP3xx2->MCP3xxx;
+
MCP3001->MCP30xx;
MCP3002->MCP30xx;
MCP3004->MCP30xx;
@@ -29,6 +32,8 @@ digraph classes {
MCP3202->MCP32xx;
MCP3204->MCP32xx;
MCP3208->MCP32xx;
+ MCP3002->MCP3xx2;
+ MCP3202->MCP3xx2;
MCP3301->MCP33xx;
MCP3302->MCP33xx;
MCP3304->MCP33xx;
diff --git a/docs/images/spi_device_hierarchy.pdf b/docs/images/spi_device_hierarchy.pdf
index 9a75196..65ba0cd 100644
Binary files a/docs/images/spi_device_hierarchy.pdf and b/docs/images/spi_device_hierarchy.pdf differ
diff --git a/docs/images/spi_device_hierarchy.png b/docs/images/spi_device_hierarchy.png
index c52cbf3..7639917 100644
Binary files a/docs/images/spi_device_hierarchy.png and b/docs/images/spi_device_hierarchy.png differ
diff --git a/docs/images/spi_device_hierarchy.svg b/docs/images/spi_device_hierarchy.svg
index 0412455..b801f8e 100644
--- a/docs/images/spi_device_hierarchy.svg
+++ b/docs/images/spi_device_hierarchy.svg
@@ -1,7 +1,7 @@
-
Device
-
-Device
+
+Device
SPIDevice
-
-SPIDevice
+
+SPIDevice
SPIDevice->Device
-
-
+
+
AnalogInputDevice
-
-AnalogInputDevice
+
+AnalogInputDevice
AnalogInputDevice->SPIDevice
-
-
+
+
MCP3xxx
-
-MCP3xxx
+
+MCP3xxx
MCP3xxx->AnalogInputDevice
-
-
+
+
MCP30xx
@@ -51,138 +51,158 @@
MCP30xx->MCP3xxx
-
-
+
+
MCP32xx
-
-MCP32xx
+
+MCP32xx
MCP32xx->MCP3xxx
-
-
+
+
+
+
+MCP3xx2
+
+MCP3xx2
+
+
+MCP3xx2->MCP3xxx
+
+
-MCP33xx
+MCP33xx
MCP33xx
MCP33xx->MCP3xxx
-
-
+
+
-MCP3001
-
-MCP3001
+MCP3001
+
+MCP3001
-MCP3001->MCP30xx
-
-
-
-
-MCP3002
-
-MCP3002
-
-
-MCP3002->MCP30xx
+MCP3001->MCP30xx
+
+MCP3002
+
+MCP3002
+
+
+MCP3002->MCP30xx
+
+
+
+
+MCP3002->MCP3xx2
+
+
+
-MCP3004
+MCP3004
MCP3004
-MCP3004->MCP30xx
+MCP3004->MCP30xx
-MCP3008
-
-MCP3008
+MCP3008
+
+MCP3008
-MCP3008->MCP30xx
-
-
+MCP3008->MCP30xx
+
+
-MCP3201
-
-MCP3201
+MCP3201
+
+MCP3201
-MCP3201->MCP32xx
-
-
+MCP3201->MCP32xx
+
+
-MCP3202
-
-MCP3202
+MCP3202
+
+MCP3202
-MCP3202->MCP32xx
-
-
+MCP3202->MCP32xx
+
+
+
+
+MCP3202->MCP3xx2
+
+
-MCP3204
-
-MCP3204
+MCP3204
+
+MCP3204
-MCP3204->MCP32xx
-
-
+MCP3204->MCP32xx
+
+
-MCP3208
-
-MCP3208
+MCP3208
+
+MCP3208
-MCP3208->MCP32xx
-
-
+MCP3208->MCP32xx
+
+
-MCP3301
-
-MCP3301
+MCP3301
+
+MCP3301
-MCP3301->MCP33xx
-
-
-
-
-MCP3302
-
-MCP3302
-
-
-MCP3302->MCP33xx
+MCP3301->MCP33xx
-
-MCP3304
+
+MCP3302
-MCP3304
+MCP3302
-
-MCP3304->MCP33xx
+
+MCP3302->MCP33xx
+
+MCP3304
+
+MCP3304
+
+
+MCP3304->MCP33xx
+
+
+
diff --git a/gpiozero/exc.py b/gpiozero/exc.py
index 1afd214..aedc96f 100644
--- a/gpiozero/exc.py
+++ b/gpiozero/exc.py
@@ -49,6 +49,9 @@ class SPIError(GPIOZeroError):
class SPIBadArgs(SPIError, ValueError):
"Error raised when invalid arguments are given while constructing :class:`SPIDevice`"
+class SPIBadChannel(SPIError, ValueError):
+ "Error raised when an invalid channel is given to an :class:`AnalogInputDevice`"
+
class GPIODeviceError(GPIOZeroError):
"Base class for errors specific to the GPIODevice hierarchy"
diff --git a/gpiozero/pins/mock.py b/gpiozero/pins/mock.py
index f865c30..f12c8cc 100644
--- a/gpiozero/pins/mock.py
+++ b/gpiozero/pins/mock.py
@@ -54,7 +54,8 @@ class MockPin(Pin):
self._when_changed = None
self.clear_states()
return self
- if old_pin.__class__ != cls:
+ # Ensure the pin class expected supports PWM (or not)
+ if issubclass(cls, MockPWMPin) != isinstance(old_pin, MockPWMPin):
raise ValueError('pin %d is already in use as a %s' % (number, old_pin.__class__.__name__))
return old_pin
@@ -249,7 +250,6 @@ class MockPWMPin(MockPin):
"""
This derivative of :class:`MockPin` adds PWM support.
"""
-
def __init__(self, number):
super(MockPWMPin, self).__init__()
self._frequency = None
@@ -275,3 +275,141 @@ class MockPWMPin(MockPin):
if value is None:
self._change_state(0.0)
+
+class MockSPIClockPin(MockPin):
+ """
+ This derivative of :class:`MockPin` is intended to be used as the clock pin
+ of a mock SPI device. It is not intended for direct construction in tests;
+ rather, construct a :class:`MockSPIDevice` with various pin numbers, and
+ this class will be used for the clock pin.
+ """
+ def __init__(self, number):
+ super(MockSPIClockPin, self).__init__()
+ if not hasattr(self, 'spi_devices'):
+ self.spi_devices = []
+
+ def _set_state(self, value):
+ super(MockSPIClockPin, self)._set_state(value)
+ for dev in self.spi_devices:
+ dev.on_clock()
+
+
+class MockSPISelectPin(MockPin):
+ """
+ This derivative of :class:`MockPin` is intended to be used as the select
+ pin of a mock SPI device. It is not intended for direct construction in
+ tests; rather, construct a :class:`MockSPIDevice` with various pin numbers,
+ and this class will be used for the select pin.
+ """
+ def __init__(self, number):
+ super(MockSPISelectPin, self).__init__()
+ if not hasattr(self, 'spi_device'):
+ self.spi_device = None
+
+ def _set_state(self, value):
+ super(MockSPISelectPin, self)._set_state(value)
+ if self.spi_device:
+ self.spi_device.on_select()
+
+
+class MockSPIDevice(object):
+ def __init__(
+ self, clock_pin, mosi_pin, miso_pin, select_pin=None,
+ clock_polarity=False, clock_phase=False, lsb_first=False,
+ bits_per_word=8, select_high=False):
+ self.clock_pin = MockSPIClockPin(clock_pin)
+ self.mosi_pin = None if mosi_pin is None else MockPin(mosi_pin)
+ self.miso_pin = None if miso_pin is None else MockPin(miso_pin)
+ self.select_pin = None if select_pin is None else MockSPISelectPin(select_pin)
+ self.clock_polarity = clock_polarity
+ self.clock_phase = clock_phase
+ self.lsb_first = lsb_first
+ self.bits_per_word = bits_per_word
+ self.select_high = select_high
+ self.rx_bit = 0
+ self.rx_buf = []
+ self.tx_buf = []
+ self.clock_pin.spi_devices.append(self)
+ self.select_pin.spi_device = self
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, exc_tb):
+ self.close()
+
+ def close(self):
+ if self in self.clock_pin.spi_devices:
+ self.clock_pin.spi_devices.remove(self)
+ if self.select_pin is not None:
+ self.select_pin.spi_device = None
+
+ def on_select(self):
+ if self.select_pin.state == self.select_high:
+ self.on_start()
+
+ def on_clock(self):
+ # Don't do anything if this SPI device isn't currently selected
+ if self.select_pin is None or self.select_pin.state == self.select_high:
+ # The XOR of the clock pin's values, polarity and phase indicates
+ # whether we're meant to be acting on this edge
+ if self.clock_pin.state ^ self.clock_polarity ^ self.clock_phase:
+ self.rx_bit += 1
+ if self.mosi_pin is not None:
+ self.rx_buf.append(self.mosi_pin.state)
+ if self.miso_pin is not None:
+ try:
+ tx_value = self.tx_buf.pop(0)
+ except IndexError:
+ tx_value = 0
+ if tx_value:
+ self.miso_pin.drive_high()
+ else:
+ self.miso_pin.drive_low()
+ self.on_bit()
+
+ def on_start(self):
+ """
+ Override this in descendents to detect when the mock SPI device's
+ select line is activated.
+ """
+ self.rx_bit = 0
+ self.rx_buf = []
+ self.tx_buf = []
+
+ def on_bit(self):
+ """
+ Override this in descendents to react to receiving a bit.
+
+ The :attr:`rx_bit` attribute gives the index of the bit received (this
+ is reset to 0 by default by :meth:`on_select`). The :attr:`rx_buf`
+ sequence gives the sequence of 1s and 0s that have been recevied so
+ far. The :attr:`tx_buf` sequence gives the sequence of 1s and 0s to
+ transmit on the next clock pulses. All these attributes can be modified
+ within this method.
+
+ The :meth:`rx_word` and :meth:`tx_word` methods can also be used to
+ read and append to the buffers using integers instead of bool bits.
+ """
+ pass
+
+ def rx_word(self):
+ result = 0
+ bits = reversed(self.rx_buf) if self.lsb_first else self.rx_buf
+ for bit in bits:
+ result <<= 1
+ result |= bit
+ return result
+
+ def tx_word(self, value, bits_per_word=None):
+ if bits_per_word is None:
+ bits_per_word = self.bits_per_word
+ bits = [0] * bits_per_word
+ for bit in range(bits_per_word):
+ bits[bit] = value & 1
+ value >>= 1
+ assert not value
+ if not self.lsb_first:
+ bits = reversed(bits)
+ self.tx_buf.extend(bits)
+
diff --git a/gpiozero/spi.py b/gpiozero/spi.py
index 431c73e..e89ab98 100644
--- a/gpiozero/spi.py
+++ b/gpiozero/spi.py
@@ -85,16 +85,16 @@ class SPIHardwareInterface(Device):
self._device.mode = value
def _get_clock_polarity(self):
- return bool(self.mode & 2)
+ return bool(self.clock_mode & 2)
def _set_clock_polarity(self, value):
- self.mode = self.mode & (~2) | (bool(value) << 1)
+ self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1)
def _get_clock_phase(self):
- return bool(self.mode & 1)
+ return bool(self.clock_mode & 1)
def _set_clock_phase(self, value):
- self.mode = self.mode & (~1) | bool(value)
+ self.clock_mode = self.clock_mode & (~1) | bool(value)
def _get_lsb_first(self):
return self._device.lsbfirst
@@ -130,9 +130,6 @@ class SPISoftwareBus(SharedMixin, Device):
self.miso = None
super(SPISoftwareBus, self).__init__()
self.lock = RLock()
- self.clock_phase = False
- self.lsb_first = False
- self.bits_per_word = 8
try:
self.clock = OutputDevice(clock_pin, active_high=True)
if mosi_pin is not None:
@@ -166,13 +163,7 @@ class SPISoftwareBus(SharedMixin, Device):
def _shared_key(cls, clock_pin, mosi_pin, miso_pin):
return (clock_pin, mosi_pin, miso_pin)
- def read(self, n):
- return self.transfer((0,) * n)
-
- def write(self, data):
- return len(self.transfer(data))
-
- def transfer(self, data):
+ def transfer(self, data, clock_phase=False, lsb_first=False, bits_per_word=8):
"""
Writes data (a list of integer words where each word is assumed to have
:attr:`bits_per_word` bits or less) to the SPI interface, and reads an
@@ -180,19 +171,19 @@ class SPISoftwareBus(SharedMixin, Device):
"""
result = []
with self.lock:
- shift = operator.lshift if self.lsb_first else operator.rshift
+ shift = operator.lshift if lsb_first else operator.rshift
for write_word in data:
- mask = 1 if self.lsb_first else 1 << (self.bits_per_word - 1)
+ mask = 1 if lsb_first else 1 << (bits_per_word - 1)
read_word = 0
- for _ in range(self.bits_per_word):
+ for _ in range(bits_per_word):
if self.mosi is not None:
self.mosi.value = bool(write_word & mask)
self.clock.on()
- if self.miso is not None and not self.clock_phase:
+ if self.miso is not None and not clock_phase:
if self.miso.value:
read_word |= mask
self.clock.off()
- if self.miso is not None and self.clock_phase:
+ if self.miso is not None and clock_phase:
if self.miso.value:
read_word |= mask
mask = shift(mask, 1)
@@ -205,6 +196,9 @@ class SPISoftwareInterface(OutputDevice):
self._bus = None
super(SPISoftwareInterface, self).__init__(select_pin, active_high=False)
try:
+ self._clock_phase = False
+ self._lsb_first = False
+ self._bits_per_word = 8
self._bus = SPISoftwareBus(clock_pin, mosi_pin, miso_pin)
except:
self.close()
@@ -230,16 +224,17 @@ class SPISoftwareInterface(OutputDevice):
return "software SPI closed"
def read(self, n):
- return self._bus.read(n)
+ return self.transfer((0,) * n)
def write(self, data):
- return self._bus.write(data)
+ return len(self.transfer(data))
def transfer(self, data):
with self._bus.lock:
self.on()
try:
- return self._bus.transfer(data)
+ return self._bus.transfer(
+ data, self._clock_phase, self._lsb_first, self._bits_per_word)
finally:
self.off()
@@ -250,40 +245,37 @@ class SPISoftwareInterface(OutputDevice):
value = int(value)
if not 0 <= value <= 3:
raise ValueError('clock_mode must be a value between 0 and 3 inclusive')
- with self._bus.lock:
- self._bus.clock.active_high = not (value & 2)
- self._bus.clock.off()
- self._bus.clock_phase = bool(value & 1)
+ self.clock_polarity = bool(value & 2)
+ self.clock_phase = bool(value & 1)
def _get_clock_polarity(self):
- return not self._bus.clock.active_high
+ with self._bus.lock:
+ return not self._bus.clock.active_high
def _set_clock_polarity(self, value):
with self._bus.lock:
self._bus.clock.active_high = not value
+ self._bus.clock.off()
def _get_clock_phase(self):
- return self._bus.clock_phase
+ return self._clock_phase
def _set_clock_phase(self, value):
- with self._bus.lock:
- self._bus.clock_phase = bool(value)
+ self._clock_phase = bool(value)
def _get_lsb_first(self):
- return self._bus.lsb_first
+ return self._lsb_first
def _set_lsb_first(self, value):
- with self._bus.lock:
- self._bus.lsb_first = bool(value)
+ self._lsb_first = bool(value)
def _get_bits_per_word(self):
- return self._bus.bits_per_word
+ return self._bits_per_word
def _set_bits_per_word(self, value):
if value < 1:
raise ValueError('bits_per_word must be positive')
- with self._bus.lock:
- self._bus.bits_per_word = int(value)
+ self._bits_per_word = int(value)
def _get_select_high(self):
return self.active_high
diff --git a/gpiozero/spi_devices.py b/gpiozero/spi_devices.py
index d866b0c..7aff666 100644
--- a/gpiozero/spi_devices.py
+++ b/gpiozero/spi_devices.py
@@ -7,7 +7,14 @@ from __future__ import (
str = type('')
-from .exc import DeviceClosed, InputDeviceError
+from math import log, ceil
+from operator import or_
+try:
+ from functools import reduce
+except ImportError:
+ pass # py2's reduce is built-in
+
+from .exc import DeviceClosed, SPIBadChannel
from .devices import Device
from .spi import SPI
@@ -34,6 +41,38 @@ class SPIDevice(Device):
def closed(self):
return self._spi is None
+ def _int_to_words(self, pattern):
+ """
+ Given a bit-pattern expressed an integer number, return a sequence of
+ the individual words that make up the pattern. The number of bits per
+ word will be obtained from the internal SPI interface.
+ """
+ try:
+ bits_required = int(ceil(log(pattern, 2))) + 1
+ except ValueError:
+ # pattern == 0 (technically speaking, no bits are required to
+ # transmit the value zero ;)
+ bits_required = 1
+ shifts = range(0, bits_required, self._spi.bits_per_word)[::-1]
+ mask = 2 ** self._spi.bits_per_word - 1
+ return [(pattern >> shift) & mask for shift in shifts]
+
+ def _words_to_int(self, words, expected_bits=None):
+ """
+ Given a sequence of words which each fit in the internal SPI
+ interface's number of bits per word, returns the value obtained by
+ concatenating each word into a single bit-string.
+
+ If *expected_bits* is specified, it limits the size of the output to
+ the specified number of bits (by masking off bits above the expected
+ number). If unspecified, no limit will be applied.
+ """
+ if expected_bits is None:
+ expected_bits = len(words) * self._spi.bits_per_word
+ shifts = range(0, expected_bits, self._spi.bits_per_word)[::-1]
+ mask = 2 ** expected_bits - 1
+ return reduce(or_, (word << shift for word, shift in zip(words, shifts))) & mask
+
def __repr__(self):
try:
self._check_open()
@@ -72,10 +111,10 @@ class AnalogInputDevice(SPIDevice):
.. _analog to digital converters: https://en.wikipedia.org/wiki/Analog-to-digital_converter
"""
- def __init__(self, bits=None, **spi_args):
- if bits is None:
- raise InputDeviceError('you must specify the bit resolution of the device')
+ def __init__(self, bits, **spi_args):
self._bits = bits
+ self._min_value = -(2 ** bits)
+ self._range = 2 ** (bits + 1) - 1
super(AnalogInputDevice, self).__init__(shared=True, **spi_args)
@property
@@ -92,9 +131,9 @@ class AnalogInputDevice(SPIDevice):
def value(self):
"""
The current value read from the device, scaled to a value between 0 and
- 1.
+ 1 (or -1 to +1 for certain devices operating in differential mode).
"""
- return self._read() / (2**self.bits - 1)
+ return (2 * (self._read() - self._min_value) / self._range) - 1
@property
def raw_value(self):
@@ -128,10 +167,9 @@ class MCP3xxx(AnalogInputDevice):
@property
def differential(self):
"""
- If ``True``, the device is operated in pseudo-differential mode. In
- this mode one channel (specified by the channel attribute) is read
- relative to the value of a second channel (implied by the chip's
- design).
+ If ``True``, the device is operated in differential mode. In this mode
+ one channel (specified by the channel attribute) is read relative to
+ the value of a second channel (implied by the chip's design).
Please refer to the device data-sheet to determine which channel is
used as the relative base value (for example, when using an
@@ -141,28 +179,73 @@ class MCP3xxx(AnalogInputDevice):
return self._differential
def _read(self):
- # MCP3008/04 or MCP3208/04 protocol looks like the following:
+ return self._words_to_int(
+ self._spi.transfer(self._send())[-2:], self.bits
+ )
+
+ def _send(self):
+ # MCP3004/08 protocol looks like the following:
#
# Byte 0 1 2
# ==== ======== ======== ========
- # Tx 0001MCCC xxxxxxxx xxxxxxxx
- # Rx xxxxxxxx x0RRRRRR RRRRxxxx for the 3004/08
- # Rx xxxxxxxx x0RRRRRR RRRRRRxx for the 3204/08
+ # Tx 00000001 MCCCxxxx xxxxxxxx
+ # Rx xxxxxxxx xxxxx0RR RRRRRRRR
#
- # The transmit bits start with 3 preamble bits "000" (to warm up), a
- # start bit "1" followed by the single/differential bit (M) which is 1
- # for single-ended read, and 0 for differential read, followed by
- # 3-bits for the channel (C). The remainder of the transmission are
- # "don't care" bits (x).
+ # MCP3204/08 protocol looks like the following:
#
- # The first byte received and the top 1 bit of the second byte are
- # don't care bits (x). These are followed by a null bit (0), and then
- # the result bits (R). 10 bits for the MCP300x, 12 bits for the
- # MCP320x.
+ # Byte 0 1 2
+ # ==== ======== ======== ========
+ # Tx 000001MC CCxxxxxx xxxxxxxx
+ # Rx xxxxxxxx xxx0RRRR RRRRRRRR
#
- # XXX Differential mode still requires testing
- data = self._spi.transfer([16 + [8, 0][self.differential] + self.channel, 0, 0])
- return ((data[1] & 63) << (self.bits - 6)) | (data[2] >> (14 - self.bits))
+ # The transmit bits start with several preamble "0" bits, the number
+ # of which is determined by the amount required to align the last byte
+ # of the result with the final byte of output. A start "1" bit is then
+ # transmitted, followed by the single/differential bit (M); 1 for
+ # single-ended read, 0 for differential read. Next comes three bits for
+ # channel (C).
+ #
+ # Read-out begins with a don't care bit (x), then a null bit (0)
+ # followed by the result bits (R). All other bits are don't care (x).
+ #
+ # The 3x01 variant of the chips always operates in differential mode
+ # and effectively only has one channel (composed of an IN+ and IN-). As
+ # such it requires no input, just output.
+ return self._int_to_words(
+ (0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 2)
+ )
+
+
+class MCP3xx2(MCP3xxx):
+ def _send(self):
+ # MCP3002 protocol looks like the following:
+ #
+ # Byte 0 1
+ # ==== ======== ========
+ # Tx 01MCLxxx xxxxxxxx
+ # Rx xxxxx0RR RRRRRRRR for the 3002
+ #
+ # MCP3202 protocol looks like the following:
+ #
+ # Byte 0 1 2
+ # ==== ======== ======== ========
+ # Tx 00000001 MCLxxxxx xxxxxxxx
+ # Rx xxxxxxxx xxx0RRRR RRRRRRRR
+ #
+ # The transmit bits start with several preamble "0" bits, the number of
+ # which is determined by the amount required to align the last byte of
+ # the result with the final byte of output. A start "1" bit is then
+ # transmitted, followed by the single/differential bit (M); 1 for
+ # single-ended read, 0 for differential read. Next comes a single bit
+ # for channel (M) then the MSBF bit (L) which selects whether the data
+ # will be read out in MSB form only (1) or whether LSB read-out will
+ # occur after MSB read-out (0).
+ #
+ # Read-out begins with a null bit (0) followed by the result bits (R).
+ # All other bits are don't care (x).
+ return self._int_to_words(
+ (0b1001 | (not self.differential) << 2 | self.channel << 1) << (self.bits + 1)
+ )
class MCP30xx(MCP3xxx):
@@ -196,20 +279,32 @@ class MCP33xx(MCP3xxx):
super(MCP33xx, self).__init__(channel, 12, differential, **spi_args)
def _read(self):
- # MCP3304/02 protocol looks like the following:
+ if self.differential:
+ result = self._words_to_int(
+ self._spi.transfer(self._send())[-2:], self.bits + 1)
+ # Account for the sign bit
+ if result > 4095:
+ return -(8192 - result)
+ else:
+ return result
+ else:
+ return super(MCP33xx, self)._read()
+
+ def _send(self):
+ # MCP3302/04 protocol looks like the following:
#
# Byte 0 1 2
# ==== ======== ======== ========
- # Tx 0001MCCC xxxxxxxx xxxxxxxx
- # Rx xxxxxxxx x0SRRRRR RRRRRRRx
+ # Tx 00001MCC Cxxxxxxx xxxxxxxx
+ # Rx xxxxxxxx xx0SRRRR RRRRRRRR
#
- # The transmit bits start with 3 preamble bits "000" (to warm up), a
- # start bit "1" followed by the single/differential bit (M) which is 1
- # for single-ended read, and 0 for differential read, followed by
- # 3-bits for the channel (C). The remainder of the transmission are
- # "don't care" bits (x).
+ # The transmit bits start with 4 preamble bits "0000", a start bit "1"
+ # followed by the single/differential bit (M) which is 1 for
+ # single-ended read, and 0 for differential read, followed by 3-bits
+ # for the channel (C). The remainder of the transmission are "don't
+ # care" bits (x).
#
- # The first byte received and the top 1 bit of the second byte are
+ # The first byte received and the top 2 bits of the second byte are
# don't care bits (x). These are followed by a null bit (0), then the
# sign bit (S), and then the 12 result bits (R).
#
@@ -217,22 +312,11 @@ class MCP33xx(MCP3xxx):
# result is effectively 12-bits. In differential mode, the sign bit is
# significant and the result is a two's-complement 13-bit value.
#
- # The MCP3301 variant of the chip always operates in differential
- # mode and effectively only has one channel (composed of an IN+ and
- # IN-). As such it requires no input, just output. This is the reason
- # we split out _send() below; so that MCP3301 can override it.
- data = self._spi.transfer(self._send())
- # Extract the last two bytes (again, for MCP3301)
- data = data[-2:]
- result = ((data[0] & 63) << 7) | (data[1] >> 1)
- # Account for the sign bit
- if self.differential and result > 4095:
- result = -(8192 - result)
- assert -4096 <= result < 4096
- return result
-
- def _send(self):
- return [16 + [8, 0][self.differential] + self.channel, 0, 0]
+ # The MCP3301 variant operates similarly to the other MCP3x01 variants;
+ # no input, just output and always differential.
+ return self._int_to_words(
+ (0b10000 | (not self.differential) << 3 | self.channel) << (self.bits + 3)
+ )
@property
def differential(self):
@@ -259,15 +343,25 @@ class MCP33xx(MCP3xxx):
class MCP3001(MCP30xx):
"""
- The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel
+ The `MCP3001`_ is a 10-bit analog to digital converter with 1 channel.
+ Please note that the MCP3001 always operates in differential mode,
+ measuring the value of IN+ relative to IN-.
.. _MCP3001: http://www.farnell.com/datasheets/630400.pdf
"""
def __init__(self, **spi_args):
super(MCP3001, self).__init__(0, differential=True, **spi_args)
+ def _read(self):
+ # MCP3001 protocol looks like the following:
+ #
+ # Byte 0 1
+ # ==== ======== ========
+ # Rx xx0RRRRR RRRRRxxx
+ return self._words_to_int(self._spi.read(2), 13) >> 3
-class MCP3002(MCP30xx):
+
+class MCP3002(MCP30xx, MCP3xx2):
"""
The `MCP3002`_ is a 10-bit analog to digital converter with 2 channels
(0-1).
@@ -276,7 +370,7 @@ class MCP3002(MCP30xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 2:
- raise InputDeviceError('channel must be 0 or 1')
+ raise SPIBadChannel('channel must be 0 or 1')
super(MCP3002, self).__init__(channel, differential, **spi_args)
@@ -289,7 +383,7 @@ class MCP3004(MCP30xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 4:
- raise InputDeviceError('channel must be between 0 and 3')
+ raise SPIBadChannel('channel must be between 0 and 3')
super(MCP3004, self).__init__(channel, differential, **spi_args)
@@ -302,21 +396,31 @@ class MCP3008(MCP30xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 8:
- raise InputDeviceError('channel must be between 0 and 7')
+ raise SPIBadChannel('channel must be between 0 and 7')
super(MCP3008, self).__init__(channel, differential, **spi_args)
class MCP3201(MCP32xx):
"""
- The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel
+ The `MCP3201`_ is a 12-bit analog to digital converter with 1 channel.
+ Please note that the MCP3201 always operates in differential mode,
+ measuring the value of IN+ relative to IN-.
.. _MCP3201: http://www.farnell.com/datasheets/1669366.pdf
"""
def __init__(self, **spi_args):
super(MCP3201, self).__init__(0, differential=True, **spi_args)
+ def _read(self):
+ # MCP3201 protocol looks like the following:
+ #
+ # Byte 0 1
+ # ==== ======== ========
+ # Rx xx0RRRRR RRRRRRRx
+ return self._words_to_int(self._spi.read(2), 13) >> 1
-class MCP3202(MCP32xx):
+
+class MCP3202(MCP32xx, MCP3xx2):
"""
The `MCP3202`_ is a 12-bit analog to digital converter with 2 channels
(0-1).
@@ -325,7 +429,7 @@ class MCP3202(MCP32xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 2:
- raise InputDeviceError('channel must be 0 or 1')
+ raise SPIBadChannel('channel must be 0 or 1')
super(MCP3202, self).__init__(channel, differential, **spi_args)
@@ -338,7 +442,7 @@ class MCP3204(MCP32xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 4:
- raise InputDeviceError('channel must be between 0 and 3')
+ raise SPIBadChannel('channel must be between 0 and 3')
super(MCP3204, self).__init__(channel, differential, **spi_args)
@@ -351,23 +455,33 @@ class MCP3208(MCP32xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 8:
- raise InputDeviceError('channel must be between 0 and 7')
+ raise SPIBadChannel('channel must be between 0 and 7')
super(MCP3208, self).__init__(channel, differential, **spi_args)
class MCP3301(MCP33xx):
"""
The `MCP3301`_ is a signed 13-bit analog to digital converter. Please note
- that the MCP3301 always operates in differential mode between its two
- channels and the output value is scaled from -1 to +1.
+ that the MCP3301 always operates in differential mode measuring the
+ difference between IN+ and IN-. Its output value is scaled from -1 to +1.
.. _MCP3301: http://www.farnell.com/datasheets/1669397.pdf
"""
def __init__(self, **spi_args):
super(MCP3301, self).__init__(0, differential=True, **spi_args)
- def _send(self):
- return [0, 0]
+ def _read(self):
+ # MCP3301 protocol looks like the following:
+ #
+ # Byte 0 1
+ # ==== ======== ========
+ # Rx xx0SRRRR RRRRRRRR
+ result = self._words_to_int(self._spi.read(2), 13)
+ # Account for the sign bit
+ if result > 4095:
+ return -(8192 - result)
+ else:
+ return result
class MCP3302(MCP33xx):
@@ -382,7 +496,7 @@ class MCP3302(MCP33xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 4:
- raise InputDeviceError('channel must be between 0 and 4')
+ raise SPIBadChannel('channel must be between 0 and 4')
super(MCP3302, self).__init__(channel, differential, **spi_args)
@@ -398,6 +512,6 @@ class MCP3304(MCP33xx):
"""
def __init__(self, channel=0, differential=False, **spi_args):
if not 0 <= channel < 8:
- raise InputDeviceError('channel must be between 0 and 7')
+ raise SPIBadChannel('channel must be between 0 and 7')
super(MCP3304, self).__init__(channel, differential, **spi_args)
diff --git a/tests/test_spi.py b/tests/test_spi.py
new file mode 100644
index 0000000..f035bde
--- /dev/null
+++ b/tests/test_spi.py
@@ -0,0 +1,170 @@
+from __future__ import (
+ unicode_literals,
+ absolute_import,
+ print_function,
+ division,
+ )
+str = type('')
+
+
+import sys
+import mock
+import pytest
+from collections import namedtuple
+
+from gpiozero import *
+from gpiozero.pins.mock import MockPin, MockSPIDevice
+from gpiozero.spi import *
+
+
+def setup_function(function):
+ import gpiozero.devices
+ gpiozero.devices.pin_factory = MockPin
+
+def teardown_function(function):
+ MockPin.clear_pins()
+
+
+def test_spi_hardware_params():
+ with mock.patch('gpiozero.spi.SpiDev') as spidev:
+ with SPI() as device:
+ assert isinstance(device, SPIHardwareInterface)
+ with SPI(port=0, device=0) as device:
+ assert isinstance(device, SPIHardwareInterface)
+ with SPI(port=0, device=1) as device:
+ assert isinstance(device, SPIHardwareInterface)
+ with SPI(clock_pin=11) as device:
+ assert isinstance(device, SPIHardwareInterface)
+ with SPI(clock_pin=11, mosi_pin=10, select_pin=8) as device:
+ assert isinstance(device, SPIHardwareInterface)
+ with SPI(clock_pin=11, mosi_pin=10, select_pin=7) as device:
+ assert isinstance(device, SPIHardwareInterface)
+ with SPI(shared=True) as device:
+ assert isinstance(device, SharedSPIHardwareInterface)
+ with pytest.raises(ValueError):
+ SPI(port=1)
+ with pytest.raises(ValueError):
+ SPI(device=2)
+ with pytest.raises(ValueError):
+ SPI(port=0, clock_pin=12)
+ with pytest.raises(ValueError):
+ SPI(foo='bar')
+
+def test_spi_software_params():
+ with mock.patch('gpiozero.spi.SpiDev') as spidev:
+ with SPI(select_pin=6) as device:
+ assert isinstance(device, SPISoftwareInterface)
+ with SPI(clock_pin=11, mosi_pin=9, miso_pin=10) as device:
+ assert isinstance(device, SPISoftwareInterface)
+ with SPI(select_pin=6, shared=True) as device:
+ assert isinstance(device, SharedSPISoftwareInterface)
+ # Ensure software fallback works when SpiDev isn't present
+ with SPI() as device:
+ assert isinstance(device, SPISoftwareInterface)
+
+def test_spi_hardware_conflict():
+ with mock.patch('gpiozero.spi.SpiDev') as spidev:
+ with LED(11) as led:
+ with pytest.raises(GPIOPinInUse):
+ SPI(port=0, device=0)
+
+def test_spi_hardware_read():
+ with mock.patch('gpiozero.spi.SpiDev') as spidev:
+ spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)]
+ with SPI() as device:
+ assert device.read(3) == [0, 1, 2]
+ assert device.read(6) == list(range(6))
+
+def test_spi_hardware_write():
+ with mock.patch('gpiozero.spi.SpiDev') as spidev:
+ spidev.return_value.xfer2.side_effect = lambda data: list(range(10))[:len(data)]
+ with SPI() as device:
+ assert device.write([0, 1, 2]) == 3
+ assert spidev.return_value.xfer2.called_with([0, 1, 2])
+ assert device.write(list(range(6))) == 6
+ assert spidev.return_value.xfer2.called_with(list(range(6)))
+
+def test_spi_hardware_modes():
+ with mock.patch('gpiozero.spi.SpiDev') as spidev:
+ spidev.return_value.mode = 0
+ spidev.return_value.lsbfirst = False
+ spidev.return_value.cshigh = True
+ spidev.return_value.bits_per_word = 8
+ with SPI() as device:
+ assert device.clock_mode == 0
+ assert not device.clock_polarity
+ assert not device.clock_phase
+ device.clock_polarity = False
+ assert device.clock_mode == 0
+ device.clock_polarity = True
+ assert device.clock_mode == 2
+ device.clock_phase = True
+ assert device.clock_mode == 3
+ assert not device.lsb_first
+ assert device.select_high
+ assert device.bits_per_word == 8
+ device.select_high = False
+ device.lsb_first = True
+ device.bits_per_word = 12
+ assert not spidev.return_value.cshigh
+ assert spidev.return_value.lsbfirst
+ assert spidev.return_value.bits_per_word == 12
+
+def test_spi_software_read():
+ class SPISlave(MockSPIDevice):
+ def on_start(self):
+ super(SPISlave, self).on_start()
+ for i in range(10):
+ self.tx_word(i)
+ with SPISlave(11, 10, 9, 8) as slave, SPI() as master:
+ assert master.read(3) == [0, 1, 2]
+ assert master.read(6) == [0, 1, 2, 3, 4, 5]
+ slave.clock_phase = True
+ master.clock_phase = True
+ assert master.read(3) == [0, 1, 2]
+ assert master.read(6) == [0, 1, 2, 3, 4, 5]
+
+def test_spi_software_write():
+ with MockSPIDevice(11, 10, 9, 8) as test_device, SPI() as master:
+ master.write([0])
+ assert test_device.rx_word() == 0
+ master.write([2, 0])
+ assert test_device.rx_word() == 512
+ master.write([0, 1, 1])
+ assert test_device.rx_word() == 257
+
+def test_spi_software_clock_mode():
+ with SPI() as master:
+ assert master.clock_mode == 0
+ assert not master.clock_polarity
+ assert not master.clock_phase
+ master.clock_polarity = False
+ assert master.clock_mode == 0
+ master.clock_polarity = True
+ assert master.clock_mode == 2
+ master.clock_phase = True
+ assert master.clock_mode == 3
+ master.clock_mode = 0
+ assert not master.clock_polarity
+ assert not master.clock_phase
+ with pytest.raises(ValueError):
+ master.clock_mode = 5
+
+def test_spi_software_attr():
+ with SPI() as master:
+ assert not master.lsb_first
+ assert not master.select_high
+ assert master.bits_per_word == 8
+ master.bits_per_word = 12
+ assert master.bits_per_word == 12
+ master.lsb_first = True
+ assert master.lsb_first
+ master.select_high = True
+ assert master.select_high
+ with pytest.raises(ValueError):
+ master.bits_per_word = 0
+
+
+# XXX Test two simultaneous SPI devices sharing clock, MOSI, and MISO, with
+# separate select pins (including threaded tests which attempt simultaneous
+# reading/writing)
diff --git a/tests/test_spi_devices.py b/tests/test_spi_devices.py
new file mode 100644
index 0000000..ecf0ca8
--- /dev/null
+++ b/tests/test_spi_devices.py
@@ -0,0 +1,339 @@
+from __future__ import (
+ unicode_literals,
+ absolute_import,
+ print_function,
+ division,
+ )
+str = type('')
+
+
+import sys
+import pytest
+from collections import namedtuple
+try:
+ from math import isclose
+except ImportError:
+ from gpiozero.compat import isclose
+
+from gpiozero import *
+from gpiozero.pins.mock import MockSPIDevice, MockPin
+
+
+def setup_function(function):
+ import gpiozero.devices
+ gpiozero.devices.pin_factory = MockPin
+
+def teardown_function(function):
+ MockPin.clear_pins()
+
+def clamp(v, min_value, max_value):
+ return min(max_value, max(min_value, v))
+
+def scale(v, ref, bits):
+ v /= ref
+ vmin = -(2 ** bits)
+ vmax = -vmin - 1
+ vrange = vmax - vmin
+ return int(((v + 1) / 2.0) * vrange + vmin)
+
+
+class MockMCP3xxx(MockSPIDevice):
+ def __init__(
+ self, clock_pin, mosi_pin, miso_pin, select_pin=None,
+ channels=8, bits=10):
+ super(MockMCP3xxx, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin)
+ self.vref = 3.3
+ self.channels = [0.0] * channels
+ self.channel_bits = 3
+ self.bits = bits
+ self.state = 'idle'
+
+ def on_start(self):
+ super(MockMCP3xxx, self).on_start()
+ self.state = 'idle'
+
+ def on_bit(self):
+ if self.state == 'idle':
+ if self.rx_buf[-1]:
+ self.state = 'mode'
+ self.rx_buf = []
+ elif self.state == 'mode':
+ if self.rx_buf[-1]:
+ self.state = 'single'
+ else:
+ self.state = 'diff'
+ self.rx_buf = []
+ elif self.state in ('single', 'diff'):
+ if len(self.rx_buf) == self.channel_bits:
+ self.on_result(self.state == 'diff', self.rx_word())
+ self.state = 'result'
+ elif self.state == 'result':
+ if not self.tx_buf:
+ self.state = 'idle'
+ self.rx_buf = []
+ else:
+ assert False
+
+ def on_result(self, differential, channel):
+ if differential:
+ pos_channel = channel
+ neg_channel = pos_channel ^ 1
+ result = self.channels[pos_channel] - self.channels[neg_channel]
+ result = clamp(result, 0, self.vref)
+ else:
+ result = clamp(self.channels[channel], 0, self.vref)
+ result = scale(result, self.vref, self.bits)
+ self.tx_word(result, self.bits + 2)
+
+
+class MockMCP3xx1(MockMCP3xxx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None, bits=10):
+ super(MockMCP3xx1, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits)
+
+ def on_start(self):
+ super(MockMCP3xx1, self).on_start()
+ result = self.channels[0] - self.channels[1]
+ result = clamp(result, 0, self.vref)
+ result = scale(result, self.vref, self.bits)
+ self.tx_word(result, self.bits + 3)
+
+ def on_bit(self):
+ pass
+
+
+class MockMCP3xx2(MockMCP3xxx):
+ def __init__(
+ self, clock_pin, mosi_pin, miso_pin, select_pin=None,
+ bits=10):
+ super(MockMCP3xx2, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=bits)
+ self.channel_bits = 1
+
+
+class MockMCP33xx(MockMCP3xxx):
+ def __init__(
+ self, clock_pin, mosi_pin, miso_pin, select_pin=None,
+ channels=8):
+ super(MockMCP33xx, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels, 12)
+
+ def on_result(self, differential, channel):
+ if differential:
+ pos_channel = channel
+ neg_channel = pos_channel ^ 1
+ result = self.channels[pos_channel] - self.channels[neg_channel]
+ result = clamp(result, -self.vref, self.vref)
+ else:
+ result = clamp(self.channels[channel], 0, self.vref)
+ result = scale(result, self.vref, self.bits)
+ if result < 0:
+ result += 8192
+ self.tx_word(result, self.bits + 3)
+
+
+class MockMCP3001(MockMCP3xx1):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3001, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, bits=10)
+
+
+class MockMCP3002(MockMCP3xx2):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3002, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, bits=10)
+
+
+class MockMCP3004(MockMCP3xxx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3004, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=10)
+
+
+class MockMCP3008(MockMCP3xxx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3008, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=10)
+
+
+class MockMCP3201(MockMCP3xx1):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3201, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, bits=12)
+
+
+class MockMCP3202(MockMCP3xx2):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3202, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, bits=12)
+
+
+class MockMCP3204(MockMCP3xxx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3204, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=4, bits=12)
+
+
+class MockMCP3208(MockMCP3xxx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3208, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=8, bits=12)
+
+
+class MockMCP3301(MockMCP3xxx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3301, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=2, bits=12)
+
+ def on_start(self):
+ super(MockMCP3301, self).on_start()
+ result = self.channels[0] - self.channels[1]
+ result = clamp(result, -self.vref, self.vref)
+ result = scale(result, self.vref, self.bits)
+ if result < 0:
+ result += 8192
+ self.tx_word(result, self.bits + 4)
+
+
+class MockMCP3302(MockMCP33xx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3302, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=4)
+
+
+class MockMCP3304(MockMCP33xx):
+ def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin=None):
+ super(MockMCP3304, self).__init__(
+ clock_pin, mosi_pin, miso_pin, select_pin, channels=8)
+
+
+def single_mcp_test(mock, pot, channel, bits):
+ scale = 2**bits
+ tolerance = 1 / scale
+ mock.channels[channel] = 0.0
+ assert pot.raw_value == 0
+ assert isclose(pot.value, 0.0, abs_tol=tolerance)
+ mock.channels[channel] = mock.vref / 2
+ assert pot.raw_value == (scale / 2) - 1
+ assert isclose(pot.value, 0.5, abs_tol=tolerance)
+ mock.channels[channel] = mock.vref
+ assert pot.raw_value == scale - 1
+ assert isclose(pot.value, 1.0, abs_tol=tolerance)
+
+def differential_mcp_test(mock, pot, pos_channel, neg_channel, bits, full=False):
+ scale = 2**bits
+ tolerance = 1 / scale
+ mock.channels[pos_channel] = 0.0
+ mock.channels[neg_channel] = 0.0
+ assert pot.raw_value == 0
+ assert isclose(pot.value, 0.0, abs_tol=tolerance)
+ mock.channels[pos_channel] = mock.vref / 2
+ assert pot.raw_value == (scale / 2) - 1
+ assert isclose(pot.value, 0.5, abs_tol=tolerance)
+ mock.channels[pos_channel] = mock.vref
+ assert pot.raw_value == scale - 1
+ assert isclose(pot.value, 1.0, abs_tol=tolerance)
+ mock.channels[neg_channel] = mock.vref / 2
+ assert pot.raw_value == (scale / 2) - 1
+ assert isclose(pot.value, 0.5, abs_tol=tolerance)
+ mock.channels[pos_channel] = mock.vref / 2
+ assert pot.raw_value == 0
+ assert isclose(pot.value, 0.0, abs_tol=tolerance)
+ mock.channels[pos_channel] = 0.0
+ mock.channels[neg_channel] = mock.vref
+ if full:
+ assert pot.raw_value == -scale
+ assert isclose(pot.value, -1.0, abs_tol=tolerance)
+ else:
+ assert pot.raw_value == 0
+ assert isclose(pot.value, 0.0, abs_tol=tolerance)
+
+
+def test_MCP3001():
+ mock = MockMCP3001(11, 10, 9, 8)
+ with MCP3001() as pot:
+ differential_mcp_test(mock, pot, 0, 1, 10)
+
+def test_MCP3002():
+ mock = MockMCP3002(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3002(channel=5)
+ with MCP3002(channel=1) as pot:
+ single_mcp_test(mock, pot, 1, 10)
+ with MCP3002(channel=1, differential=True) as pot:
+ differential_mcp_test(mock, pot, 1, 0, 10)
+
+def test_MCP3004():
+ mock = MockMCP3004(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3004(channel=5)
+ with MCP3004(channel=3) as pot:
+ single_mcp_test(mock, pot, 3, 10)
+ with MCP3004(channel=3, differential=True) as pot:
+ differential_mcp_test(mock, pot, 3, 2, 10)
+
+def test_MCP3008():
+ mock = MockMCP3008(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3008(channel=9)
+ with MCP3008(channel=0) as pot:
+ single_mcp_test(mock, pot, 0, 10)
+ with MCP3008(channel=0, differential=True) as pot:
+ differential_mcp_test(mock, pot, 0, 1, 10)
+
+def test_MCP3201():
+ mock = MockMCP3201(11, 10, 9, 8)
+ with MCP3201() as pot:
+ differential_mcp_test(mock, pot, 0, 1, 12)
+
+def test_MCP3202():
+ mock = MockMCP3202(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3202(channel=5)
+ with MCP3202(channel=1) as pot:
+ single_mcp_test(mock, pot, 1, 12)
+ with MCP3202(channel=1, differential=True) as pot:
+ differential_mcp_test(mock, pot, 1, 0, 12)
+
+def test_MCP3204():
+ mock = MockMCP3204(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3204(channel=5)
+ with MCP3204(channel=1) as pot:
+ single_mcp_test(mock, pot, 1, 12)
+ with MCP3204(channel=1, differential=True) as pot:
+ differential_mcp_test(mock, pot, 1, 0, 12)
+
+def test_MCP3208():
+ mock = MockMCP3208(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3208(channel=9)
+ with MCP3208(channel=7) as pot:
+ single_mcp_test(mock, pot, 7, 12)
+ with MCP3208(channel=7, differential=True) as pot:
+ differential_mcp_test(mock, pot, 7, 6, 12)
+
+def test_MCP3301():
+ mock = MockMCP3301(11, 10, 9, 8)
+ with MCP3301() as pot:
+ differential_mcp_test(mock, pot, 0, 1, 12, full=True)
+
+def test_MCP3302():
+ mock = MockMCP3302(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3302(channel=4)
+ with MCP3302(channel=0) as pot:
+ single_mcp_test(mock, pot, 0, 12)
+ with MCP3302(channel=0, differential=True) as pot:
+ differential_mcp_test(mock, pot, 0, 1, 12, full=True)
+
+def test_MCP3304():
+ mock = MockMCP3304(11, 10, 9, 8)
+ with pytest.raises(ValueError):
+ MCP3304(channel=9)
+ with MCP3304(channel=5) as pot:
+ single_mcp_test(mock, pot, 5, 12)
+ with MCP3304(channel=5, differential=True) as pot:
+ differential_mcp_test(mock, pot, 5, 4, 12, full=True)
+