mirror of
https://github.com/KevinMidboe/python-gpiozero.git
synced 2025-12-08 20:39:01 +00:00
Fix #459 - properly support remote SPI with pigpio
Sorry! Dave's messing around with the pin implementations again. Hopefully the last time. The pin_factory is now really a factory object which can be asked to produce individual pins or pin-based interfaces like SPI (which can be supported properly via pigpio).
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# vim: set fileencoding=utf-8:
|
||||
|
||||
from __future__ import (
|
||||
unicode_literals,
|
||||
absolute_import,
|
||||
@@ -6,32 +8,124 @@ from __future__ import (
|
||||
)
|
||||
str = type('')
|
||||
|
||||
import io
|
||||
|
||||
from .data import pi_info
|
||||
from ..exc import (
|
||||
PinInvalidFunction,
|
||||
PinSetInput,
|
||||
PinFixedPull,
|
||||
PinSPIUnsupported,
|
||||
PinPWMUnsupported,
|
||||
PinEdgeDetectUnsupported,
|
||||
SPIFixedClockMode,
|
||||
SPIFixedBitOrder,
|
||||
SPIFixedSelect,
|
||||
SPIFixedWordSize,
|
||||
)
|
||||
|
||||
|
||||
PINS_CLEANUP = []
|
||||
def _pins_shutdown():
|
||||
for routine in PINS_CLEANUP:
|
||||
routine()
|
||||
class Factory(object):
|
||||
"""
|
||||
Generates pins, SPI, and I2C interfaces for devices. This is an abstract
|
||||
base class for pin factories. Descendents must override:
|
||||
|
||||
* :meth:`_get_address`
|
||||
* :meth:`pin_address`
|
||||
|
||||
Descendents may override:
|
||||
|
||||
* :meth:`close`
|
||||
* :meth:`pin`
|
||||
* :meth:`spi`
|
||||
* :meth:`_get_pi_info`
|
||||
"""
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Closes the pin factory. This is expected to clean up all resources
|
||||
manipulated by the factory. It it typically called at script
|
||||
termination.
|
||||
"""
|
||||
pass
|
||||
|
||||
def pin(self, spec):
|
||||
"""
|
||||
Creates an instance of a :class:`Pin` descendent representing the
|
||||
specified pin.
|
||||
|
||||
.. warning::
|
||||
|
||||
Descendents must ensure that pin instances representing the same
|
||||
hardware are identical; i.e. two separate invocations of
|
||||
:meth:`pin` for the same pin specification must return the same
|
||||
object.
|
||||
"""
|
||||
raise PinGPIOUnsupported("GPIO not supported by this pin factory")
|
||||
|
||||
def pin_address(self, spec):
|
||||
"""
|
||||
Returns the address that a pin *would* have if constructed from the
|
||||
given *spec*.
|
||||
|
||||
This unusual method is used by the pin reservation system to check
|
||||
for conflicts *prior* to pin construction; with most implementations,
|
||||
pin construction implicitly alters the state of the pin (e.g. setting
|
||||
it to an input). This allows pin reservation to take place without
|
||||
affecting the state of other components.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def spi(self, **spi_args):
|
||||
"""
|
||||
Returns an instance of an :class:`SPI` interface, for the specified SPI
|
||||
*port* and *device*, or for the specified pins (*clock_pin*,
|
||||
*mosi_pin*, *miso_pin*, and *select_pin*). Only one of the schemes can
|
||||
be used; attempting to mix *port* and *device* with pin numbers will
|
||||
raise :exc:`SPIBadArgs`.
|
||||
"""
|
||||
raise PinSPIUnsupported('SPI not supported by this pin factory')
|
||||
|
||||
def _get_address(self):
|
||||
raise NotImplementedError
|
||||
|
||||
address = property(
|
||||
lambda self: self._get_address(),
|
||||
doc="""\
|
||||
Returns a tuple of strings representing the address of the factory.
|
||||
For the Pi itself this is a tuple of one string representing the Pi's
|
||||
address (e.g. "localhost"). Expander chips can return a tuple appending
|
||||
whatever string they require to uniquely identify the expander chip
|
||||
amongst all factories in the system.
|
||||
|
||||
.. note::
|
||||
|
||||
This property *must* return an immutable object capable of being
|
||||
used as a dictionary key.
|
||||
""")
|
||||
|
||||
def _get_pi_info(self):
|
||||
return None
|
||||
|
||||
pi_info = property(
|
||||
lambda self: self._get_pi_info(),
|
||||
doc="""\
|
||||
Returns a :class:`PiBoardInfo` instance representing the Pi that
|
||||
instances generated by this factory will be attached to.
|
||||
|
||||
If the pins represented by this class are not *directly* attached to a
|
||||
Pi (e.g. the pin is attached to a board attached to the Pi, or the pins
|
||||
are not on a Pi at all), this may return ``None``.
|
||||
""")
|
||||
|
||||
|
||||
class Pin(object):
|
||||
"""
|
||||
Abstract base class representing a GPIO pin or a pin from an IO extender.
|
||||
Abstract base class representing a pin attached to some form of controller,
|
||||
be it GPIO, SPI, ADC, etc.
|
||||
|
||||
Descendents should override property getters and setters to accurately
|
||||
represent the capabilities of pins. The following functions *must* be
|
||||
overridden:
|
||||
|
||||
* :meth:`_get_address`
|
||||
* :meth:`_get_function`
|
||||
* :meth:`_set_function`
|
||||
* :meth:`_get_state`
|
||||
@@ -39,6 +133,8 @@ class Pin(object):
|
||||
The following functions *may* be overridden if applicable:
|
||||
|
||||
* :meth:`close`
|
||||
* :meth:`output_with_state`
|
||||
* :meth:`input_with_pull`
|
||||
* :meth:`_set_state`
|
||||
* :meth:`_get_frequency`
|
||||
* :meth:`_set_frequency`
|
||||
@@ -50,20 +146,10 @@ class Pin(object):
|
||||
* :meth:`_set_edges`
|
||||
* :meth:`_get_when_changed`
|
||||
* :meth:`_set_when_changed`
|
||||
* :meth:`pi_info`
|
||||
* :meth:`output_with_state`
|
||||
* :meth:`input_with_pull`
|
||||
|
||||
.. warning::
|
||||
|
||||
Descendents must ensure that pin instances representing the same
|
||||
physical hardware are identical, right down to object identity. The
|
||||
framework relies on this to correctly clean up resources at interpreter
|
||||
shutdown.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "Abstract pin"
|
||||
return self.address[-1]
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
@@ -105,6 +191,18 @@ class Pin(object):
|
||||
self.function = 'input'
|
||||
self.pull = pull
|
||||
|
||||
def _get_address(self):
|
||||
raise NotImplementedError
|
||||
|
||||
address = property(
|
||||
lambda self: self._get_address(),
|
||||
doc="""\
|
||||
The address of the pin. This property is a tuple of strings constructed
|
||||
from the owning factory's address with the unique address of the pin
|
||||
appended to it. The tuple as a whole uniquely identifies the pin
|
||||
amongst all pins attached to the system.
|
||||
""")
|
||||
|
||||
def _get_function(self):
|
||||
return "input"
|
||||
|
||||
@@ -140,10 +238,19 @@ class Pin(object):
|
||||
doc="""\
|
||||
The state of the pin. This is 0 for low, and 1 for high. As a low level
|
||||
view of the pin, no swapping is performed in the case of pull ups (see
|
||||
:attr:`pull` for more information).
|
||||
:attr:`pull` for more information):
|
||||
|
||||
If PWM is currently active (when :attr:`frequency` is not ``None``),
|
||||
this represents the PWM duty cycle as a value between 0.0 and 1.0.
|
||||
.. code-block:: text
|
||||
|
||||
HIGH - - - - > ,----------------------
|
||||
|
|
||||
|
|
||||
LOW ----------------'
|
||||
|
||||
Descendents which implement analog, or analog-like capabilities can
|
||||
return values between 0 and 1. For example, pins implementing PWM
|
||||
(where :attr:`frequency` is not ``None``) return a value between 0.0
|
||||
and 1.0 representing the current PWM duty cycle.
|
||||
|
||||
If a pin is currently configured for input, and an attempt is made to
|
||||
set this attribute, :exc:`PinSetInput` will be raised. If an invalid
|
||||
@@ -205,6 +312,26 @@ class Pin(object):
|
||||
detection, measured in seconds. If bounce detection is not currently in
|
||||
use, this is ``None``.
|
||||
|
||||
For example, if :attr:`edge` is currently "rising", :attr:`bounce` is
|
||||
currently 5/1000 (5ms), then the waveform below will only fire
|
||||
:attr:`when_changed` on two occasions despite there being three rising
|
||||
edges:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
TIME 0...1...2...3...4...5...6...7...8...9...10..11..12 ms
|
||||
|
||||
bounce elimination |===================| |==============
|
||||
|
||||
HIGH - - - - > ,--. ,--------------. ,--.
|
||||
| | | | | |
|
||||
| | | | | |
|
||||
LOW ----------------' `-' `-' `-----------
|
||||
: :
|
||||
: :
|
||||
when_changed when_changed
|
||||
fires fires
|
||||
|
||||
If the pin does not support edge detection, attempts to set this
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`. If the pin
|
||||
supports edge detection, the class must implement bounce detection,
|
||||
@@ -223,7 +350,18 @@ class Pin(object):
|
||||
doc="""\
|
||||
The edge that will trigger execution of the function or bound method
|
||||
assigned to :attr:`when_changed`. This can be one of the strings
|
||||
"both" (the default), "rising", "falling", or "none".
|
||||
"both" (the default), "rising", "falling", or "none":
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
HIGH - - - - > ,--------------.
|
||||
| |
|
||||
| |
|
||||
LOW --------------------' `--------------
|
||||
: :
|
||||
: :
|
||||
Fires when_changed "both" "both"
|
||||
when edges is ... "rising" "falling"
|
||||
|
||||
If the pin does not support edge detection, attempts to set this
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`.
|
||||
@@ -247,48 +385,300 @@ class Pin(object):
|
||||
property will raise :exc:`PinEdgeDetectUnsupported`.
|
||||
""")
|
||||
|
||||
@classmethod
|
||||
def pi_info(cls):
|
||||
"""
|
||||
Returns a :class:`PiBoardInfo` instance representing the Pi that
|
||||
instances of this pin class will be attached to.
|
||||
|
||||
If the pins represented by this class are not *directly* attached to a
|
||||
Pi (e.g. the pin is attached to a board attached to the Pi, or the pins
|
||||
are not on a Pi at all), this may return ``None``.
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
class LocalPin(Pin):
|
||||
class SPI(object):
|
||||
"""
|
||||
Abstract base class representing pins attached locally to a Pi. This forms
|
||||
the base class for local-only pin interfaces (:class:`RPiGPIOPin`,
|
||||
:class:`RPIOPin`, and :class:`NativePin`).
|
||||
Abstract interface for `Serial Peripheral Interface`_ (SPI) implementations.
|
||||
Descendents *must* override the following:
|
||||
|
||||
* :meth:`transfer`
|
||||
* :meth:`_get_clock_mode`
|
||||
|
||||
Descendents *may* override the following methods:
|
||||
|
||||
* :meth:`read`
|
||||
* :meth:`write`
|
||||
* :meth:`_set_clock_mode`
|
||||
* :meth:`_get_lsb_first`
|
||||
* :meth:`_set_lsb_first`
|
||||
* :meth:`_get_select_high`
|
||||
* :meth:`_set_select_high`
|
||||
* :meth:`_get_bits_per_word`
|
||||
* :meth:`_set_bits_per_word`
|
||||
|
||||
.. _Serial Peripheral Interface: https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
|
||||
"""
|
||||
_PI_REVISION = None
|
||||
|
||||
@classmethod
|
||||
def pi_info(cls):
|
||||
def read(self, n):
|
||||
"""
|
||||
Returns a :class:`PiBoardInfo` instance representing the local Pi.
|
||||
The Pi's revision is determined by reading :file:`/proc/cpuinfo`. If
|
||||
no valid revision is found, returns ``None``.
|
||||
"""
|
||||
# Cache the result as we can reasonably assume it won't change during
|
||||
# runtime (this is LocalPin after all; descendents that deal with
|
||||
# remote Pis should inherit from Pin instead)
|
||||
if cls._PI_REVISION is None:
|
||||
with io.open('/proc/cpuinfo', 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('Revision'):
|
||||
revision = line.split(':')[1].strip().lower()
|
||||
overvolted = revision.startswith('100')
|
||||
if overvolted:
|
||||
revision = revision[-4:]
|
||||
cls._PI_REVISION = revision
|
||||
break
|
||||
if cls._PI_REVISION is None:
|
||||
return None # something weird going on
|
||||
return pi_info(cls._PI_REVISION)
|
||||
Read *n* words of data from the SPI interface, returning them as a
|
||||
sequence of unsigned ints, each no larger than the configured
|
||||
:attr:`bits_per_word` of the interface.
|
||||
|
||||
This method is typically used with read-only devices that feature
|
||||
half-duplex communication. See :meth:`transfer` for full duplex
|
||||
communication.
|
||||
"""
|
||||
return self.transfer((0,) * n)
|
||||
|
||||
def write(self, data):
|
||||
"""
|
||||
Write *data* to the SPI interface. *data* must be a sequence of
|
||||
unsigned integer words each of which will fit within the configured
|
||||
:attr:`bits_per_word` of the interface. The method returns the number
|
||||
of words written to the interface (which may be less than or equal to
|
||||
the length of *data*).
|
||||
|
||||
This method is typically used with write-only devices that feature
|
||||
half-duplex communication. See :meth:`transfer` for full duplex
|
||||
communication.
|
||||
"""
|
||||
return len(self.transfer(data))
|
||||
|
||||
def transfer(self, data):
|
||||
"""
|
||||
Write *data* to the SPI interface. *data* must be a sequence of
|
||||
unsigned integer words each of which will fit within the configured
|
||||
:attr:`bits_per_word` of the interface. The method returns the sequence
|
||||
of words read from the interface while writing occurred (full duplex
|
||||
communication).
|
||||
|
||||
The length of the sequence returned dictates the number of words of
|
||||
*data* written to the interface. Each word in the returned sequence
|
||||
will be an unsigned integer no larger than the configured
|
||||
:attr:`bits_per_word` of the interface.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def clock_polarity(self):
|
||||
"""
|
||||
The polarity of the SPI clock pin. If this is ``False`` (the default),
|
||||
the clock pin will idle low, and pulse high. Setting this to ``True``
|
||||
will cause the clock pin to idle high, and pulse low. On many data
|
||||
sheets this is documented as the CPOL value.
|
||||
|
||||
The following diagram illustrates the waveform when
|
||||
:attr:`clock_polarity` is ``False`` (the default), equivalent to CPOL
|
||||
0:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
on on on on on on on
|
||||
,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
||||
CLK | | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
------' `---' `---' `---' `---' `---' `---' `------
|
||||
idle off off off off off off idle
|
||||
|
||||
The following diagram illustrates the waveform when
|
||||
:attr:`clock_polarity` is ``True``, equivalent to CPOL 1:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
idle off off off off off off idle
|
||||
------. ,---. ,---. ,---. ,---. ,---. ,---. ,------
|
||||
| | | | | | | | | | | | | |
|
||||
CLK | | | | | | | | | | | | | |
|
||||
`---' `---' `---' `---' `---' `---' `---'
|
||||
on on on on on on on
|
||||
"""
|
||||
return bool(self.clock_mode & 2)
|
||||
|
||||
@clock_polarity.setter
|
||||
def clock_polarity(self, value):
|
||||
self.clock_mode = self.clock_mode & (~2) | (bool(value) << 1)
|
||||
|
||||
@property
|
||||
def clock_phase(self):
|
||||
"""
|
||||
The phase of the SPI clock pin. If this is ``False`` (the default),
|
||||
data will be read from the MISO pin when the clock pin activates.
|
||||
Setting this to ``True`` will cause data to be read from the MISO pin
|
||||
when the clock pin deactivates. On many data sheets this is documented
|
||||
as the CPHA value. Whether the clock edge is rising or falling when the
|
||||
clock is considered activated is controlled by the
|
||||
:attr:`clock_polarity` attribute (corresponding to CPOL).
|
||||
|
||||
The following diagram indicates when data is read when
|
||||
:attr:`clock_polarity` is ``False``, and :attr:`clock_phase` is
|
||||
``False`` (the default), equivalent to CPHA 0:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
||||
CLK | | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
----' `---' `---' `---' `---' `---' `---' `-------
|
||||
: : : : : : :
|
||||
MISO---. ,---. ,---. ,---. ,---. ,---. ,---.
|
||||
/ \ / \ / \ / \ / \ / \ / \\
|
||||
-{ Bit X Bit X Bit X Bit X Bit X Bit X Bit }------
|
||||
\ / \ / \ / \ / \ / \ / \ /
|
||||
`---' `---' `---' `---' `---' `---' `---'
|
||||
|
||||
The following diagram indicates when data is read when
|
||||
:attr:`clock_polarity` is ``False``, but :attr:`clock_phase` is
|
||||
``True``, equivalent to CPHA 1:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
||||
CLK | | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
----' `---' `---' `---' `---' `---' `---' `-------
|
||||
: : : : : : :
|
||||
MISO ,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
||||
/ \ / \ / \ / \ / \ / \ / \\
|
||||
-----{ Bit X Bit X Bit X Bit X Bit X Bit X Bit }--
|
||||
\ / \ / \ / \ / \ / \ / \ /
|
||||
`---' `---' `---' `---' `---' `---' `---'
|
||||
"""
|
||||
return bool(self.clock_mode & 1)
|
||||
|
||||
@clock_phase.setter
|
||||
def clock_phase(self, value):
|
||||
self.clock_mode = self.clock_mode & (~1) | bool(value)
|
||||
|
||||
def _get_clock_mode(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _set_clock_mode(self, value):
|
||||
raise SPIFixedClockMode("clock_mode cannot be changed on %r" % self)
|
||||
|
||||
clock_mode = property(
|
||||
lambda self: self._get_clock_mode(),
|
||||
lambda self, value: self._set_clock_mode(value),
|
||||
doc="""\
|
||||
Presents a value representing the :attr:`clock_polarity` and
|
||||
:attr:`clock_phase` attributes combined according to the following
|
||||
table:
|
||||
|
||||
+------+-----------------+--------------+
|
||||
| mode | polarity (CPOL) | phase (CPHA) |
|
||||
+======+=================+==============+
|
||||
| 0 | False | False |
|
||||
| 1 | False | True |
|
||||
| 2 | True | False |
|
||||
| 3 | True | True |
|
||||
+------+-----------------+--------------+
|
||||
|
||||
Adjusting this value adjusts both the :attr:`clock_polarity` and
|
||||
:attr:`clock_phase` attributes simultaneously.
|
||||
""")
|
||||
|
||||
def _get_lsb_first(self):
|
||||
return False
|
||||
|
||||
def _set_lsb_first(self, value):
|
||||
raise SPIFixedBitOrder("lsb_first cannot be changed on %r" % self)
|
||||
|
||||
lsb_first = property(
|
||||
lambda self: self._get_lsb_first(),
|
||||
lambda self, value: self._set_lsb_first(value),
|
||||
doc="""\
|
||||
Controls whether words are read and written LSB in (Least Significant
|
||||
Bit first) order. The default is ``False`` indicating that words are
|
||||
read and written in MSB (Most Significant Bit first) order.
|
||||
Effectively, this controls the `Bit endianness`_ of the connection.
|
||||
|
||||
The following diagram shows the a word containing the number 5 (binary
|
||||
0101) transmitted on MISO with :attr:`bits_per_word` set to 4, and
|
||||
:attr:`clock_mode` set to 0, when :attr:`lsb_first` is ``False`` (the
|
||||
default):
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
,---. ,---. ,---. ,---.
|
||||
CLK | | | | | | | |
|
||||
| | | | | | | |
|
||||
----' `---' `---' `---' `-----
|
||||
: ,-------. : ,-------.
|
||||
MISO: | : | : | : |
|
||||
: | : | : | : |
|
||||
----------' : `-------' : `----
|
||||
: : : :
|
||||
MSB LSB
|
||||
|
||||
And now with :attr:`lsb_first` set to ``True`` (and all other
|
||||
parameters the same):
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
,---. ,---. ,---. ,---.
|
||||
CLK | | | | | | | |
|
||||
| | | | | | | |
|
||||
----' `---' `---' `---' `-----
|
||||
,-------. : ,-------. :
|
||||
MISO: | : | : | :
|
||||
| : | : | : | :
|
||||
--' : `-------' : `-----------
|
||||
: : : :
|
||||
LSB MSB
|
||||
|
||||
.. _Bit endianness: https://en.wikipedia.org/wiki/Endianness#Bit_endianness
|
||||
""")
|
||||
|
||||
def _get_select_high(self):
|
||||
return False
|
||||
|
||||
def _set_select_high(self, value):
|
||||
raise SPIFixedSelect("select_high cannot be changed on %r" % self)
|
||||
|
||||
select_high = property(
|
||||
lambda self: self._get_select_high(),
|
||||
lambda self, value: self._set_select_high(value),
|
||||
doc="""\
|
||||
If ``False`` (the default), the chip select line is considered active
|
||||
when it is pulled low. When set to ``True``, the chip select line is
|
||||
considered active when it is driven high.
|
||||
|
||||
The following diagram shows the waveform of the chip select line, and
|
||||
the clock when :attr:`clock_polarity` is ``False``, and
|
||||
:attr:`select_high` is ``False`` (the default):
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
---. ,------
|
||||
__ | |
|
||||
CS | chip is selected, and will react to clock | idle
|
||||
`-----------------------------------------------------'
|
||||
|
||||
,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
||||
CLK | | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
----' `---' `---' `---' `---' `---' `---' `-------
|
||||
|
||||
And when :attr:`select_high` is ``True``:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
,-----------------------------------------------------.
|
||||
CS | chip is selected, and will react to clock | idle
|
||||
| |
|
||||
---' `------
|
||||
|
||||
,---. ,---. ,---. ,---. ,---. ,---. ,---.
|
||||
CLK | | | | | | | | | | | | | |
|
||||
| | | | | | | | | | | | | |
|
||||
----' `---' `---' `---' `---' `---' `---' `-------
|
||||
""")
|
||||
|
||||
def _get_bits_per_word(self):
|
||||
return 8
|
||||
|
||||
def _set_bits_per_word(self, value):
|
||||
raise SPIFixedWordSize("bits_per_word cannot be changed on %r" % self)
|
||||
|
||||
bits_per_word = property(
|
||||
lambda self: self._get_bits_per_word(),
|
||||
lambda self, value: self._set_bits_per_word(value),
|
||||
doc="""\
|
||||
Controls the number of bits that make up a word, and thus where the
|
||||
word boundaries appear in the data stream, and the maximum value of a
|
||||
word. Defaults to 8 meaning that words are effectively bytes.
|
||||
|
||||
Several implementations do not support non-byte-sized words.
|
||||
""")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user