mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-10-29 17:50:37 +00:00
Fix #421
Added SPI tests, simplified the shared SPI software bus implementation, and fixed several protocol errors in our MCP3xxx classes (the x2 and x1 protocols were wrong)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user