From: Dan White Date: Fri, 13 Jul 2012 01:28:20 +0000 (-0500) Subject: Add libftdi-based SPI class. X-Git-Tag: calibrations~32 X-Git-Url: http://git.whiteaudio.com/gitweb/?a=commitdiff_plain;h=f6ca7c25d339ceabddcd96cd274af895c2d16a02;p=430.git Add libftdi-based SPI class. --- diff --git a/python-lib/usbio.py b/python-lib/usbio.py index 96f9b86..77833f7 100644 --- a/python-lib/usbio.py +++ b/python-lib/usbio.py @@ -5,6 +5,7 @@ from time import sleep import ftdi import mpsse +import struct from math import log from myhdl import intbv @@ -286,6 +287,314 @@ class I2C(object): +def uint16str(v): + """Return the FTDI style little-endian string of an int.""" + return struct.pack('= 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