import ftdi
import mpsse
+import struct
from math import log
from myhdl import intbv
+def uint16str(v):
+ """Return the FTDI style little-endian string of an int."""
+ return struct.pack('<H', v)
+
+
+
+class SPI(object):
+ # Known (possible) issues:
+ # -calculations for GPIO pins imply 8-bit values
+
+ # shifting command setup for spi modes edge for: read, write
+ MPSSE_SPI_MODE_CONFIG = (
+ 0x00 | ftdi.MPSSE_WRITE_NEG, #rise, fall
+ ftdi.MPSSE_READ_NEG | 0x00 , #fall, rise
+ ftdi.MPSSE_READ_NEG | 0x00 , #fall, rise
+ 0x00 | ftdi.MPSSE_WRITE_NEG, #rise, fall
+ )
+ MPSSE_WRITE = ftdi.MPSSE_DO_WRITE
+ MPSSE_READ = ftdi.MPSSE_DO_READ
+
+ # weird mode bits, here for those "non-standard" days...
+ MPSSE_BITWISE_MODE = ftdi.MPSSE_BITMODE
+ MPSSE_LSB_FIRST = ftdi.MPSSE_LSB
+
+ def __init__(self, interface=ftdi.INTERFACE_A, csidle=0, cs=None,
+ mode=0,
+ freq=1e6,
+ vid=0x0403, pid=0x6011,
+ pindir=0xc0, pinstate=0xc0,
+ latency=1,
+ delay=0.001, timeout=100):
+ """SPI port using the MPSSE on compatible FTDI devices with flexible
+ chip-select and composite command sequences. Uses the libftdi python
+ bindings.
+
+ Target application uses a CS decoder between FT4232H and SPI-controlled
+ chips, no other python libs can deal with this without deep mods.
+ Hence this class..
+
+ interface = ftdi.INTERFACE_x or [1,2,3,4]
+ csidle = 8-bit port state for no chip-selects asserted
+ cs = dict specifying pin states to affect chip-select lines
+ special keys are '_idle': no-cs-asserted pin state
+ '_mask': bitmask with 1's in CS-related pin bits
+ other keys specify the pin states to assert that device's CS pin
+ mode = SPI mode [0,1,2,3]; bitfield of [CPOL, CPHA]
+ pindir = initial bitfield of pin directions
+ pinstate = initial pin state
+ """
+ self.interface = interface
+ self.csidle = csidle
+ self.cs = cs
+ #defer mode until chip is init'd
+ #defer freq until chip is init'd
+ self.vid = vid
+ self.pid = pid
+ #self._pindir = pindir
+ #self._pinstate = pinstate
+
+ self.DELAY = delay
+ self.TIMEOUT = timeout
+
+ # setup the driver for this interface, init usb
+ self.context = ftdi.new()
+ ftdi.set_interface(self.context, interface)
+
+ # TODO: what should chunksize be? 4k is libftdi default,
+ # 4232h uses 2k FIFOs
+ # 2232h uses 4k FIFOs
+ #ftdi.write_data_set_chunksize(self.context, 4096)
+ #ftdi.read_data_set_chunksize(self.context, 4096)
+
+ # libftdi has several ways of selecting the desired device, see its
+ # source
+ e = ftdi.usb_open(self.context, vid, pid)
+ if e != 0:
+ raise Exception, ftdi.get_error_string(self.context)
+
+ ftdi.set_bitmode(self.context, int(self.pindir), ftdi.BITMODE_RESET)
+ ftdi.set_bitmode(self.context, int(self.pindir), ftdi.BITMODE_MPSSE)
+ self.set_mode(mode)
+ self.set_freq(freq)
+ self.set_pins(pinstate, pindir)
+ ftdi.usb_purge_buffers(self.context)
+
+ def _raw_write(self, data):
+ """Write data as
+ """
+ #translate data to a string
+ if not isinstance(data, str):
+ data = ''.join(map(chr, data))
+ ret = ftdi.write_data(self.context, data, len(data))
+ if ret != 0:
+ raise Exception, ftdi.get_error_string(self.context)
+
+ def _raw_read(self, n):
+ """Read n bytes from buffer."""
+ nread = 0
+ r = ''
+ while len(r) < n:
+ s = ftdi.read_data(self.context, n)
+ #does this really catch errors??
+ if not s:
+ raise Exception, ftdi.get_error_string(self.context)
+ r += s
+ ftdi.usb_purge_rx_buffer(self.context)
+ return r
+
+ def set_freq(self, freq):
+ """Setup interface to the requested clock frequency. Calculates the
+ closest divisor settings and returns the actual frequency setting.
+ """
+ # calc rates for both x5 and /5 modes and choose the closest, prefer
+ # the /5 mode if a tie
+ f = CLK / ((1+divisor)*2)
+ options = [] # (prescale, divisor, actual)
+ CLK = 12000000
+ div5 = max(int(round((CLK / (2.0 * freq)))) - 1, 0) #ensure >= 0
+ div1 = max(int(round((5*CLK / (2.0 * freq)))) - 1, 0)
+ act5 = CLK / (2.0 * (1 + div5))
+ act1 = 5*CLK / (2.0 * (1 + div5))
+
+ if abs(freq - act5) <= abs(freq - act1):
+ div = div5
+ act = act5
+ #reset state has 60 MHz / 5 prescaler enabled
+ #ftdi.write_data(self.context, chr(ftdi.EN_DIV_5), 1)
+ else:
+ div = div1
+ act = act1
+ ftdi.write_data(self.context, chr(ftdi.DIS_DIV_5), 1)
+
+ cmd = chr(ftdi.TCK_DIVISOR) + uint16str(div)
+ ftdi.write_data(self.context, cmd, len(cmd))
+ self._freq = act
+ return act
+
+ @property
+ def freq(self):
+ """Actual clock frequency from the current chip settings. See
+ set_freq() to change.
+ """
+ return self._freq
+
+ def set_mode(self, mode):
+ if mode in range(4):
+ #ensure pin 0/SK starts in its idle state in case this is a mode change
+ #yes, the check is done in set_pins(), but paranoia never hurts..
+ p = self._pinstate
+ if self.mode in (0, 1):
+ #cpol = 0
+ p &= 0xFE
+ else:
+ #cpol = 1
+ p |= 0x01
+ self.set_pins(p)
+ self._mode = mode
+ else:
+ raise Exception, 'Invalid mode %i, must be in range(4)' % mode
+
+ @property
+ def mode(self):
+ """Return the current SPI mode. See set_mode() to change it.
+ """
+ return self._mode
+
+ def set_pins(pinstate=None, pindir=None, audit_cs=True):
+ """Change the GPIO pins [3..7] to the given state and direction.
+ Argument values of None mean "do not change". Audits the values to
+ keep the SPI SK/DO/DI pins and configured chip-select pins in correct
+ states (idle mode). If audit_cs==False, allows setting the output
+ state of the configured chip-select pins.
+ """
+ if pindir is not None:
+ self._pindir = pindir
+ #we are changing pin directions, audit them
+ self._pindir |= 0x03 # pins 0/SK and 1/DO are outputs for sure
+ self._pindir |= self._cs_mask # chip-select pins are always outputs
+ self._pindir &= 0xFB # pin 2/DI is an input for sure
+ # most implementations force pin 3/CS to an output, IT DOES NOT
+ # NEED DO BE. The top 5 pins are available for GPIO, CS, or both
+ # in SPI mode. With a 5-bit one-cold decoder, we can control up to
+ # (2**5 - 1) = 31 chips with this port.
+
+ if pinstate is not None:
+ if audit_cs:
+ self._pinstate = ((pinstate & ~self._cs_mask) +
+ (self.csidle & self._cs_mask))
+ else:
+ self._pinstate = pinstate
+
+ #ensure pin 0/SK stays in its idle state, 1/DO stays as-was
+ if self.mode in (0, 1):
+ #cpol = 0
+ self._pinstate &= 0xFE
+ else:
+ #cpol = 1
+ self._pinstate |= 0x01
+
+ cmd = chr(ftdi.SET_BITS_LOW) + chr(self._pinstate) + chr(self._pindir)
+ self._raw_write(cmd)
+
+ def get_pins(self):
+ cmd = chr(ftdi.GET_BITS_LOW) + chr(ftdi.SEND_IMMEDIATE)
+ self._raw_write(cmd)
+ p = self._read_raw(1)
+ self._pinstate = p
+ return p
+
+ @property
+ def pinstate(self):
+ """Returns the last-known pin state. Get a fresh version with
+ get_pins().
+ """
+ return self._pinstate
+
+ @property
+ def pindir(self):
+ """Returns the last-set pin direction bitfield."""
+ return self._pindir
+
+ def _cs_cmd(self, device):
+ """Construct a pin-setting command which asserts the given device. See
+ __init__() for more info.
+ """
+ mask = self.cs['_mask']
+ p = self._pinstate & ~mask
+ p += self.cs[device] & mask
+
+ self._pinstate = p
+ cmd = chr(ftdi.SET_BITS_LOW) + chr(p) + chr(self._pindir)
+ return cmd
+
+ def _write_cmd(self, data):
+ """Return a MPSSE command string which sends data out via the current SPI
+ mode. data is either a string or converted by map(chr, data).
+ """
+ cmd = chr(self.MPSSE_SPI_MODE_CONFIG[self.mode] |
+ self.MPSSE_WRITE)
+ cmd += uint16str(len(data) - 1)
+
+ #translate data to a string
+ if not isinstance(data, str):
+ data = ''.join(map(chr, data))
+
+ cmd += data
+ return cmd
+
+ def _exchange_cmd(self, data):
+ """Return a MPSSE command string which sends data out via the current SPI
+ mode and reads the same amount back. data is either a string or
+ converted by map(chr, data). Interface read buffer contains the read
+ data.
+ """
+ cmd = chr(self.MPSSE_SPI_MODE_CONFIG[self.mode] |
+ self.MPSSE_WRITE |
+ self.MPSSE_READ)
+ cmd += uint16str(len(data) - 1)
+
+ #translate data to a string
+ if not isinstance(data, str):
+ data = ''.join(map(chr, data))
+
+ cmd += data
+ cmd += chr(ftdi.SEND_IMMEDIATE)
+ return cmd
+
+ def _read_cmd(self, n):
+ """Return a MPSSE command string which reads n-bytes via the current
+ SPI mode. NB: The DO line stays in its last state.
+ """
+ cmd = chr(self.MPSSE_SPI_MODE_CONFIG[self.mode] |
+ self.MPSSE_READ)
+ cmd += uint16str(n - 1)
+ cmd += chr(ftdi.SEND_IMMEDIATE)
+ return cmd
+
+ def write(self, data, device):
+ """Write data out to the selected device via the current SPI mode.
+ The device is used to select the desired CS pin state as cs_active =
+ self.cs[device].
+ """
+ cmd = (self._cs_cmd(device) + self._write_cmd(data) +
+ self._cs_cmd('_idle'))
+ self._raw_write(cmd)
+
+ def read(self, n, device):
+ """Read data from the selected device, DO pin stays in its last
+ state.
+ """
+ cmd = (self._cs_cmd(device) + self._read_cmd(n) +
+ self._cs_cmd('_idle'))
+ self._raw_write(cmd)
+ return self._raw_read(n)
+
+ def exchange(self, data, device):
+ """Write data out to the selected device while also reading data in via
+ the current SPI mode. The device is used to select the desired CS pin
+ state as cs_active = self.cs[device].
+ """
+ cmd = (self._cs_cmd(device) + self._exchange_cmd(data) +
+ self._cs_cmd('_idle'))
+ self._raw_write(cmd)
+ return self._raw_read(n)
+
+
+
+
class NCO(object):
RST_POS = 14
FCW_WIDTH = 14