Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions pyftdi/doc/api/spi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,46 @@ Example: communication with a SPI device and an extra GPIO
pin = bool(gpio.read() & 0x20)


Example: communication with a MicroWire 93LC56B data flash (half-duplex,
bi-directional data, active high CS example).

NOTE: This is the EEPROM used by many FTDI devices. If accessing a
93LC56B attached to a FTDI device, be sure that the FTDI device is
forced into reset by grounding its RESET# signal.

.. code-block:: python

# Import SpiController & hexdump
from pyftdi.spi import SpiController
from pyftdi.misc import hexdump

# Instanciate a SPI controller
mw = SpiController(cs_count=1,cs_act_hi=True)

# Configure the second interface (IF/2) of the FTDI device as a SPI master
mw.configure('ftdi://ftdi:2232h/2')

# Get a port to a SPI slave w/ CS on A*BUS3 and SPI mode 0 @ 1MHz,
# bi-directional data (ie. a single data line like I2C)
slave = mw.get_port(cs=0, freq=1E6, mode=0, bidir=True)

# Read 256 bytes from EEPROM starting at address 0
addr = 0
eeprom = slave.exchange([0x06, addr], 256)

# byte swap to handle data in little endian
eeprom[0::2], eeprom[1::2] = eeprom[1::2], eeprom[0::2]

# Print contents of eeprom
print(hexdump(eeprom))

# Convert to a byte string, if desired
eepromB = eeprom.tobytes()

# Close the SPI Controller
mw.terminate()


Classes
~~~~~~~

Expand All @@ -97,6 +137,9 @@ SPI sample tests expect:
* ADXL345 device on /CS 1, SPI mode 3
* RFDA2125 device on /CS 2, SPI mode 0

MicroWireSPI sample tests expect:
* 93LC56B device on CS 0, SPI mode 0, bi-directional data

Checkout a fresh copy from PyFtdi_ github repository.

See :doc:`../pinout` for FTDI wiring.
Expand Down
Empty file modified pyftdi/serialext/tests/pyterm.py
100755 → 100644
Empty file.
95 changes: 74 additions & 21 deletions pyftdi/spi.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,23 @@ class SpiPort:
>>> out.extend(spi.exchange([], 2, False, True))
"""

def __init__(self, controller, cs, cs_hold=3, spi_mode=0):
def __init__(self, controller, cs, cs_hold=3, spi_mode=0,
cs_count=4, cs_act_hi=False, bidir=False):
self._controller = controller
self._cpol = spi_mode & 0x1
self._cpha = spi_mode & 0x2
cs_clock = 0xFF & ~((int(not self._cpol) and SpiController.SCK_BIT) |
SpiController.DO_BIT)
cs_select = 0xFF & ~((SpiController.CS_BIT << cs) |

cs_bits = (((SpiController.CS_BIT << cs_count) - 1) &
~(SpiController.CS_BIT - 1))

cs_bit_sel = (SpiController.CS_BIT << cs) ^ (cs_bits * cs_act_hi)
cs_select = 0xFF & ~(cs_bit_sel |
(int(not self._cpol) and SpiController.SCK_BIT) |
SpiController.DO_BIT)
self._cs_prolog = bytes([cs_clock, cs_select])
self._bidir = bidir and cs_select or None
self._cs_epilog = bytes([cs_select] + [cs_clock] * int(cs_hold))
self._frequency = self._controller.frequency

Expand All @@ -95,7 +102,8 @@ def exchange(self, out=b'', readlen=0, start=True, stop=True,
Use False if the transaction should complete with a
further call to exchange()
:param duplex: perform a full-duplex exchange (vs. half-duplex),
i.e. bits are clocked in and out at once.
i.e. bits are clocked in and out at once.
Note: forced to False if self._bidir is not None.
:return: an array of bytes containing the data read out from the
slave
:rtype: array
Expand All @@ -104,7 +112,8 @@ def exchange(self, out=b'', readlen=0, start=True, stop=True,
start and self._cs_prolog,
stop and self._cs_epilog,
self._cpol, self._cpha,
duplex=duplex)
duplex=(not self._bidir and duplex),
bidir=self._bidir)

def read(self, readlen=0, start=True, stop=True):
"""Read out bytes from the slave
Expand All @@ -124,7 +133,8 @@ def read(self, readlen=0, start=True, stop=True):
return self._controller.exchange(self._frequency, [], readlen,
start and self._cs_prolog,
stop and self._cs_epilog,
self._cpol, self._cpha)
self._cpol, self._cpha,
bidir=self._bidir)

def write(self, out, start=True, stop=True):
"""Write bytes to the slave
Expand All @@ -142,7 +152,8 @@ def write(self, out, start=True, stop=True):
return self._controller.exchange(self._frequency, out, 0,
start and self._cs_prolog,
stop and self._cs_epilog,
self._cpol, self._cpha)
self._cpol, self._cpha,
bidir=self._bidir)

def flush(self):
"""Force the flush of the HW FIFOs"""
Expand Down Expand Up @@ -243,7 +254,8 @@ class SpiController:
SPI_BITS = DI_BIT | DO_BIT | SCK_BIT
PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max

def __init__(self, silent_clock=False, cs_count=4, turbo=True):
def __init__(self, silent_clock=False, cs_count=4, turbo=True,
cs_act_hi=False):
self._ftdi = Ftdi()
self._lock = Lock()
self._gpio_port = None
Expand All @@ -256,6 +268,18 @@ def __init__(self, silent_clock=False, cs_count=4, turbo=True):
self._immediate = bytes((Ftdi.SEND_IMMEDIATE,))
self._frequency = 0.0
self._clock_phase = False
self._cs_idle = 0

# If True CS, starts LOW and then goes HIGH to select device
# (ie. Active High)
# If False CS, starts HIGH and then goes LOW to select device
# (ie. Active Low)
#
# This is here instead of in get_port, where mode is set, is
# because the initial setting of CS is done in configure(), so
# need to save cs_act_hi here and use it during configure().
self._cs_act_hi = cs_act_hi


@property
def direction(self):
Expand Down Expand Up @@ -291,16 +315,22 @@ def configure(self, url, **kwargs):
with self._lock:
if self._frequency > 0.0:
raise SpiIOError('Already configured')
self._cs_bits = (((SpiController.CS_BIT << self._cs_count) - 1) &
~(SpiController.CS_BIT - 1))

cs_bits = (((SpiController.CS_BIT << self._cs_count) - 1) &
~(SpiController.CS_BIT - 1))

# If CS is Active Low, CS idle state is all high
# If CS is Active High, CS idle state is all low (0x00)
self._cs_idle = (not self._cs_act_hi) and cs_bits or 0x00

self._spi_ports = [None] * self._cs_count
self._spi_dir = (self._cs_bits |
self._spi_dir = (cs_bits |
SpiController.DO_BIT |
SpiController.SCK_BIT)
self._spi_mask = self._cs_bits | self.SPI_BITS
self._spi_mask = cs_bits | self.SPI_BITS
self._frequency = self._ftdi.open_mpsse_from_url(
# /CS all high
url, direction=self._spi_dir, initial=self._cs_bits, **kwargs)
# /CS inactive
url, direction=self._spi_dir, initial=self._cs_idle, **kwargs)
self._ftdi.enable_adaptive_clock(False)
self._wide_port = self._ftdi.has_wide_port

Expand All @@ -310,14 +340,16 @@ def terminate(self):
self._ftdi.close()
self._frequency = 0.0

def get_port(self, cs, freq=None, mode=0):
def get_port(self, cs, freq=None, mode=0, bidir=False):
"""Obtain a SPI port to drive a SPI device selected by Chip Select.

:note: SPI mode 2 is not supported.

:param int cs: chip select slot, starting from 0
:param float freq: SPI bus frequency for this slave in Hz
:param int mode: SPI mode [0,1,3]
:param bool bidir: If True, Data is a single, bi-directional line;
If False, two uni-directional data lines
:rtype: SpiPort
"""
with self._lock:
Expand All @@ -337,7 +369,10 @@ def get_port(self, cs, freq=None, mode=0):
freq = min(freq or self.frequency_max, self.frequency_max)
hold = freq and (1+int(1E6/freq))
self._spi_ports[cs] = SpiPort(self, cs, cs_hold=hold,
spi_mode=mode)
spi_mode=mode,
cs_count=self._cs_count,
cs_act_hi=self._cs_act_hi,
bidir=bidir)
self._spi_ports[cs].set_frequency(freq)
self._flush()
return self._spi_ports[cs]
Expand Down Expand Up @@ -368,7 +403,8 @@ def gpio_pins(self):

def exchange(self, frequency, out, readlen,
cs_prolog=None, cs_epilog=None,
cpol=False, cpha=False, duplex=False):
cpol=False, cpha=False, duplex=False,
bidir=None):
if duplex:
if readlen > len(out):
tmp = array('B', out)
Expand All @@ -385,7 +421,7 @@ def exchange(self, frequency, out, readlen,
else:
return self._exchange_half_duplex(frequency, out, readlen,
cs_prolog, cs_epilog,
cpol, cpha)
cpol, cpha, bidir)

def read_gpio(self, with_output=False):
"""Read GPIO port
Expand Down Expand Up @@ -468,7 +504,7 @@ def _write_raw(self, data, write_high):
self._ftdi.write_data(cmd)

def _exchange_half_duplex(self, frequency, out, readlen,
cs_prolog, cs_epilog, cpol, cpha):
cs_prolog, cs_epilog, cpol, cpha, bidir=None):
if not self._ftdi:
raise SpiIOError("FTDI controller not initialized")
if len(out) > SpiController.PAYLOAD_MAX_LENGTH:
Expand All @@ -493,14 +529,29 @@ def _exchange_half_duplex(self, frequency, out, readlen,
ctrl &= self._spi_mask
ctrl |= self._gpio_low
cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction))
if bidir:
# set DO to an input during turnaround for bi-directional
# implementations (single DI/DO line). Must also handle
# gpio in bctrl.
bctrl = bidir
bctrl &= self._spi_mask
bctrl |= self._gpio_low
turnaround = array('B', [Ftdi.SET_BITS_LOW,
bctrl,
direction & (~SpiController.DO_BIT)])
epilog = array('B')
if cs_epilog:
for ctrl in cs_epilog:
ctrl &= self._spi_mask
ctrl |= self._gpio_low
epilog.extend((Ftdi.SET_BITS_LOW, ctrl, direction))
# if bidir in not None and data is to be read (readlen != 0), keep
# the DO Bit as an input
bitdir = (((bidir != None) and readlen)
and (direction & (~SpiController.DO_BIT))
or direction)
epilog.extend((Ftdi.SET_BITS_LOW, ctrl, bitdir))
# Restore idle state
cs_high = [Ftdi.SET_BITS_LOW, self._cs_bits | self._gpio_low,
cs_high = [Ftdi.SET_BITS_LOW, self._cs_idle | self._gpio_low,
direction]
if not self._turbo:
cs_high.append(Ftdi.SEND_IMMEDIATE)
Expand All @@ -516,6 +567,8 @@ def _exchange_half_duplex(self, frequency, out, readlen,
cmd.frombytes(write_cmd)
cmd.extend(out)
if readlen:
if bidir:
cmd.extend(turnaround)
rcmd = (cpol ^ cpha) and \
Ftdi.READ_BYTES_PVE_MSB or Ftdi.READ_BYTES_NVE_MSB
read_cmd = spack('<BH', rcmd, readlen-1)
Expand Down Expand Up @@ -577,7 +630,7 @@ def _exchange_full_duplex(self, frequency, out,
ctrl |= self._gpio_low
epilog.extend((Ftdi.SET_BITS_LOW, ctrl, direction))
# Restore idle state
cs_high = [Ftdi.SET_BITS_LOW, self._cs_bits | self._gpio_low,
cs_high = [Ftdi.SET_BITS_LOW, self._cs_idle | self._gpio_low,
direction]
if not self._turbo:
cs_high.append(Ftdi.SEND_IMMEDIATE)
Expand Down
Empty file modified pyftdi/tests/bits.py
100755 → 100644
Empty file.
Empty file modified pyftdi/tests/ftdi.py
100755 → 100644
Empty file.
Empty file modified pyftdi/tests/gpio.py
100755 → 100644
Empty file.
Empty file modified pyftdi/tests/i2c.py
100755 → 100644
Empty file.
Empty file modified pyftdi/tests/jtag.py
100755 → 100644
Empty file.
Loading