1.0
This commit is contained in:
219
venv/lib/python3.11/site-packages/gpiozero/pins/spi.py
Normal file
219
venv/lib/python3.11/site-packages/gpiozero/pins/spi.py
Normal file
@@ -0,0 +1,219 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
#
|
||||
# GPIO Zero: a library for controlling the Raspberry Pi's GPIO pins
|
||||
#
|
||||
# Copyright (c) 2016-2023 Dave Jones <dave@waveform.org.uk>
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
import operator
|
||||
from threading import RLock
|
||||
|
||||
from . import SPI
|
||||
from ..devices import Device, SharedMixin
|
||||
from ..input_devices import InputDevice
|
||||
from ..output_devices import OutputDevice
|
||||
from ..exc import DeviceClosed, SPIInvalidClockMode
|
||||
|
||||
|
||||
class SPISoftware(SPI):
|
||||
"""
|
||||
A software bit-banged implementation of the :class:`gpiozero.pins.SPI`
|
||||
interface.
|
||||
|
||||
This is a reasonable basis for a *local* SPI software implementation, but
|
||||
be aware that it's unlikely to be usable for remote operation (a dedicated
|
||||
daemon that locally handles SPI transactions should be used for such
|
||||
operations). Instances will happily share their clock, mosi, and miso pins
|
||||
with other instances provided each has a distinct select pin.
|
||||
|
||||
See :class:`~gpiozero.pins.spi.SPISoftwareBus` for the actual SPI
|
||||
transfer logic.
|
||||
"""
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, select_pin, *,
|
||||
pin_factory):
|
||||
self._bus = None
|
||||
self._select = None
|
||||
super().__init__(pin_factory=pin_factory)
|
||||
try:
|
||||
# XXX We *should* be storing clock_mode locally, not clock_phase;
|
||||
# after all different users of the bus can disagree about the
|
||||
# clock's polarity and even select pin polarity
|
||||
self._clock_phase = False
|
||||
self._lsb_first = False
|
||||
self._bits_per_word = 8
|
||||
self._bus = SPISoftwareBus(
|
||||
clock_pin, mosi_pin, miso_pin, pin_factory=pin_factory)
|
||||
self._select = OutputDevice(
|
||||
select_pin, active_high=False, pin_factory=pin_factory)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def _conflicts_with(self, other):
|
||||
if isinstance(other, SoftwareSPI):
|
||||
return self._select.pin.info.name == other._select.pin.info.name
|
||||
else:
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
if self._select:
|
||||
self._select.close()
|
||||
self._select = None
|
||||
if self._bus is not None:
|
||||
self._bus.close()
|
||||
self._bus = None
|
||||
super().close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._bus is None
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
self._check_open()
|
||||
return (
|
||||
f'SPI(clock_pin={self._bus.clock.pin.info.name!r}, '
|
||||
f'mosi_pin={self._bus.mosi.pin.info.name!r}, '
|
||||
f'miso_pin={self._bus.miso.pin.info.name!r}, '
|
||||
f'select_pin={self._select.pin.info.name!r})')
|
||||
except DeviceClosed:
|
||||
return 'SPI(closed)'
|
||||
|
||||
def transfer(self, data):
|
||||
with self._bus.lock:
|
||||
self._select.on()
|
||||
try:
|
||||
return self._bus.transfer(
|
||||
data, self._clock_phase, self._lsb_first,
|
||||
self._bits_per_word)
|
||||
finally:
|
||||
self._select.off()
|
||||
|
||||
def _get_clock_mode(self):
|
||||
with self._bus.lock:
|
||||
return (not self._bus.clock.active_high) << 1 | self._clock_phase
|
||||
|
||||
def _set_clock_mode(self, value):
|
||||
if not (0 <= value < 4):
|
||||
raise SPIInvalidClockMode(f"{value} is not a valid clock mode")
|
||||
with self._bus.lock:
|
||||
self._bus.clock.active_high = not (value & 2)
|
||||
self._clock_phase = bool(value & 1)
|
||||
|
||||
def _get_lsb_first(self):
|
||||
return self._lsb_first
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
self._lsb_first = bool(value)
|
||||
|
||||
def _get_bits_per_word(self):
|
||||
return self._bits_per_word
|
||||
|
||||
def _set_bits_per_word(self, value):
|
||||
if value < 1:
|
||||
raise ValueError('bits_per_word must be positive')
|
||||
self._bits_per_word = int(value)
|
||||
|
||||
def _get_select_high(self):
|
||||
return self._select.active_high
|
||||
|
||||
def _set_select_high(self, value):
|
||||
with self._bus.lock:
|
||||
self._select.active_high = value
|
||||
self._select.off()
|
||||
|
||||
|
||||
class SPISoftwareBus(SharedMixin, Device):
|
||||
"""
|
||||
A software bit-banged SPI bus implementation, used by
|
||||
:class:`~gpiozero.pins.spi.SPISoftware` to implement shared SPI interfaces.
|
||||
|
||||
.. warning::
|
||||
|
||||
This implementation has no rate control; it simply clocks out data as
|
||||
fast as it can as Python isn't terribly quick on a Pi anyway, and the
|
||||
extra logic required for rate control is liable to reduce the maximum
|
||||
achievable data rate quite substantially.
|
||||
"""
|
||||
def __init__(self, clock_pin, mosi_pin, miso_pin, *, pin_factory):
|
||||
self.lock = None
|
||||
self.clock = None
|
||||
self.mosi = None
|
||||
self.miso = None
|
||||
super().__init__()
|
||||
# XXX Should probably just use CompositeDevice for this; would make
|
||||
# close() a bit cleaner - any implications with the RLock?
|
||||
self.lock = RLock()
|
||||
try:
|
||||
self.clock = OutputDevice(
|
||||
clock_pin, active_high=True, pin_factory=pin_factory)
|
||||
if mosi_pin is not None:
|
||||
self.mosi = OutputDevice(mosi_pin, pin_factory=pin_factory)
|
||||
if miso_pin is not None:
|
||||
self.miso = InputDevice(miso_pin, pin_factory=pin_factory)
|
||||
except:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
if getattr(self, 'lock', None):
|
||||
with self.lock:
|
||||
if self.miso is not None:
|
||||
self.miso.close()
|
||||
self.miso = None
|
||||
if self.mosi is not None:
|
||||
self.mosi.close()
|
||||
self.mosi = None
|
||||
if self.clock is not None:
|
||||
self.clock.close()
|
||||
self.clock = None
|
||||
self.lock = None
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self.lock is None
|
||||
|
||||
@classmethod
|
||||
def _shared_key(cls, clock_pin, mosi_pin, miso_pin, *, pin_factory=None):
|
||||
return (clock_pin, mosi_pin, miso_pin)
|
||||
|
||||
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
|
||||
equivalent number of words, returning them as a list of integers.
|
||||
"""
|
||||
result = []
|
||||
with self.lock:
|
||||
# See https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
|
||||
# (specifically the section "Example of bit-banging the master
|
||||
# protocol") for a simpler C implementation of this which ignores
|
||||
# clock polarity, phase, variable word-size, and multiple input
|
||||
# words
|
||||
if lsb_first:
|
||||
shift = operator.lshift
|
||||
init_mask = 1
|
||||
else:
|
||||
shift = operator.rshift
|
||||
init_mask = 1 << (bits_per_word - 1)
|
||||
for write_word in data:
|
||||
mask = init_mask
|
||||
read_word = 0
|
||||
for _ in range(bits_per_word):
|
||||
if self.mosi is not None:
|
||||
self.mosi.value = bool(write_word & mask)
|
||||
# read bit on clock activation
|
||||
self.clock.on()
|
||||
if not clock_phase:
|
||||
if self.miso is not None and self.miso.value:
|
||||
read_word |= mask
|
||||
# read bit on clock deactivation
|
||||
self.clock.off()
|
||||
if clock_phase:
|
||||
if self.miso is not None and self.miso.value:
|
||||
read_word |= mask
|
||||
mask = shift(mask, 1)
|
||||
result.append(read_word)
|
||||
return result
|
||||
Reference in New Issue
Block a user