class SpiController(object): """SPI master. :param silent_clock: should be set to avoid clocking out SCLK when all /CS signals are released. This clock beat is used to enforce a delay between /CS signal activation. When weird SPI devices are used, SCLK beating may cause trouble. In this case, silent_clock should be set but beware that SCLK line should be fitted with a pull-down resistor, as SCLK is high-Z during this short period of time. :param cs_count: is the number of /CS lines (one per device to drive on the SPI bus) """ SCK_BIT = 0x01 DO_BIT = 0x02 DI_BIT = 0x04 CS_BIT = 0x08 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max def __init__(self, silent_clock=False, cs_count=4, turbo=True): self._ftdi = Ftdi() self._cs_bits = ((SpiController.CS_BIT << cs_count) - 1) & ~(SpiController.CS_BIT - 1) self._ports = [None] * cs_count self._direction = self._cs_bits | SpiController.DO_BIT | SpiController.SCK_BIT self._turbo = turbo self._cs_high = Array("B") if self._turbo: if silent_clock: # Set SCLK as input to avoid emitting clock beats self._cs_high.extend((Ftdi.SET_BITS_LOW, self._cs_bits, self._direction & ~SpiController.SCK_BIT)) # /CS to SCLK delay, use 8 clock cycles as a HW tempo self._cs_high.extend((Ftdi.WRITE_BITS_TMS_NVE, 8 - 1, 0xFF)) # Restore idle state self._cs_high.extend((Ftdi.SET_BITS_LOW, self._cs_bits, self._direction)) if not self._turbo: self._cs_high.append(Ftdi.SEND_IMMEDIATE) self._immediate = Array("B", (Ftdi.SEND_IMMEDIATE,)) self._frequency = 0.0 def configure(self, vendor, product, interface, **kwargs): """Configure the FTDI interface as a SPI master""" for k in ("direction", "initial"): if k in kwargs: del kwargs[k] self._frequency = self._ftdi.open_mpsse( vendor, product, interface, direction=self._direction, initial=self._cs_bits, **kwargs # /CS all high ) def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, cs): """Obtain a SPI port to drive a SPI device selected by cs""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._ports): raise SpiIOError("No such SPI port") if not self._ports[cs]: cs_state = 0xFF & ~((SpiController.CS_BIT << cs) | SpiController.SCK_BIT | SpiController.DO_BIT) cs_cmd = Array("B", (Ftdi.SET_BITS_LOW, cs_state, self._direction)) self._ports[cs] = SpiPort(self, cs_cmd) self._flush() return self._ports[cs] @property def frequency_max(self): """Returns the maximum SPI clock""" return self._ftdi.frequency_max @property def frequency(self): """Returns the current SPI clock""" return self._frequency def _exchange(self, frequency, out, readlen, cs_cmd=None, complete=True): """Perform a half-duplex exchange or transaction with the SPI slave :param frequency: SPI bus clock :param out: an array of bytes to send to the SPI slave, may be empty to only read out data from the slave :param readlen: count of bytes to read out from the slave, may be zero to only write to the slave :param cs_cmd: the prolog sequence to activate the /CS line on the SPI bus. May be empty to resume a previously started transaction :param complete: whether to send the epilog sequence to move the /CS line back to the idle state. May be force to False if another part of a transaction is expected :return: an array of bytes containing the data read out from the slave """ if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if len(out) > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Output payload is too large") if readlen > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Input payload is too large") if self._frequency != frequency: self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort) self._frequency = frequency cmd = cs_cmd and Array("B", cs_cmd) or Array("B") writelen = len(out) if writelen: write_cmd = struct.pack("<BH", Ftdi.WRITE_BYTES_NVE_MSB, writelen - 1) if PY3: cmd.frombytes(write_cmd) else: cmd.fromstring(write_cmd) cmd.extend(out) if readlen: read_cmd = struct.pack("<BH", Ftdi.READ_BYTES_NVE_MSB, readlen - 1) if PY3: cmd.frombytes(read_cmd) else: cmd.fromstring(read_cmd) cmd.extend(self._immediate) if self._turbo: if complete: cmd.extend(self._cs_high) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if complete: self._ftdi.write_data(self._cs_high) # USB read cycle may occur before the FTDI device has actually # sent the data, so try to read more than once if no data is # actually received data = self._ftdi.read_data_bytes(readlen, 4) elif writelen: if self._turbo: if complete: cmd.extend(self._cs_high) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if complete: self._ftdi.write_data(self._cs_high) data = Array("B") return data def _flush(self): """Flush the HW FIFOs""" self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers()
class JtagController(object): """JTAG master of an FTDI device""" TCK_BIT = 0x01 # FTDI output TDI_BIT = 0x02 # FTDI output TDO_BIT = 0x04 # FTDI input TMS_BIT = 0x08 # FTDI output TRST_BIT = 0x10 # FTDI output, not available on 2232 JTAG debugger JTAG_MASK = 0x1F FTDI_PIPE_LEN = 512 # Private API def __init__(self, trst=False, frequency=3.0e6): """ trst uses the nTRST optional JTAG line to hard-reset the TAP controller """ self._ftdi = Ftdi() self._trst = trst self._frequency = frequency self.direction = ( JtagController.TCK_BIT | JtagController.TDI_BIT | JtagController.TMS_BIT | (self._trst and JtagController.TRST_BIT or 0) ) self._last = None # Last deferred TDO bit self._write_buff = Array("B") def __del__(self): self.close() # Public API def configure(self, vendor, product, interface): """Configure the FTDI interface as a JTAG controller""" self._ftdi.open_mpsse(vendor, product, interface, direction=self.direction, frequency=self._frequency) # FTDI requires to initialize all GPIOs before MPSSE kicks in cmd = Array("B", (Ftdi.SET_BITS_LOW, 0x0, self.direction)) self._ftdi.write_data(cmd) def close(self): if self._ftdi: self._ftdi.close() self._ftdi = None def purge(self): self._ftdi.purge_buffers() def reset(self, sync=False): """Reset the attached TAP controller. sync sends the command immediately (no caching) """ # we can either send a TRST HW signal or perform 5 cycles with TMS=1 # to move the remote TAP controller back to 'test_logic_reset' state # do both for now if not self._ftdi: raise JtagError("FTDI controller terminated") if self._trst: # nTRST value = 0 cmd = Array("B", (Ftdi.SET_BITS_LOW, value, self.direction)) self._ftdi.write_data(cmd) time.sleep(0.1) # nTRST should be left to the high state value = JtagController.TRST_BIT cmd = Array("B", (Ftdi.SET_BITS_LOW, value, self.direction)) self._ftdi.write_data(cmd) time.sleep(0.1) # TAP reset (even with HW reset, could be removed though) self.write_tms(BitSequence("11111")) if sync: self.sync() def sync(self): if not self._ftdi: raise JtagError("FTDI controller terminated") if self._write_buff: self._ftdi.write_data(self._write_buff) self._write_buff = Array("B") def write_tms(self, tms): """Change the TAP controller state""" if not isinstance(tms, BitSequence): raise JtagError("Expect a BitSequence") length = len(tms) if not (0 < length < 8): raise JtagError("Invalid TMS length") out = BitSequence(tms, length=8) # apply the last TDO bit if self._last is not None: out[7] = self._last # print_("TMS", tms, (self._last is not None) and 'w/ Last' or '') # reset last bit self._last = None cmd = Array("B", (Ftdi.WRITE_BITS_TMS_NVE, length - 1, out.tobyte())) self._stack_cmd(cmd) self.sync() def read(self, length): """Read out a sequence of bits from TDO""" byte_count = length // 8 bit_count = length - 8 * byte_count bs = BitSequence() if byte_count: bytes_ = self._read_bytes(byte_count) bs.append(bytes_) if bit_count: bits = self._read_bits(bit_count) bs.append(bits) return bs def write(self, out, use_last=True): """Write a sequence of bits to TDI""" if isinstance(out, str): if len(out) > 1: self._write_bytes_raw(out[:-1]) out = out[-1] out = BitSequence(bytes_=out) elif not isinstance(out, BitSequence): out = BitSequence(out) if use_last: (out, self._last) = (out[:-1], bool(out[-1])) byte_count = len(out) // 8 pos = 8 * byte_count bit_count = len(out) - pos if byte_count: self._write_bytes(out[:pos]) if bit_count: self._write_bits(out[pos:]) def shift_register(self, out, use_last=False): """Shift a BitSequence into the current register and retrieve the register output""" if not isinstance(out, BitSequence): return JtagError("Expect a BitSequence") length = len(out) if use_last: (out, self._last) = (out[:-1], int(out[-1])) byte_count = len(out) // 8 pos = 8 * byte_count bit_count = len(out) - pos if not byte_count and not bit_count: raise JtagError("Nothing to shift") if byte_count: blen = byte_count - 1 # print_("RW OUT %s" % out[:pos]) cmd = Array("B", (Ftdi.RW_BYTES_PVE_NVE_LSB, blen, (blen >> 8) & 0xFF)) cmd.extend(out[:pos].tobytes(msby=True)) self._stack_cmd(cmd) # print_("push %d bytes" % byte_count) if bit_count: # print_("RW OUT %s" % out[pos:]) cmd = Array("B", (Ftdi.RW_BITS_PVE_NVE_LSB, bit_count - 1)) cmd.append(out[pos:].tobyte()) self._stack_cmd(cmd) # print_("push %d bits" % bit_count) self.sync() bs = BitSequence() byte_count = length // 8 pos = 8 * byte_count bit_count = length - pos if byte_count: data = self._ftdi.read_data_bytes(byte_count, 4) if not data: raise JtagError("Unable to read data from FTDI") byteseq = BitSequence(bytes_=data, length=8 * byte_count) # print_("RW IN %s" % byteseq) bs.append(byteseq) # print_("pop %d bytes" % byte_count) if bit_count: data = self._ftdi.read_data_bytes(1, 4) if not data: raise JtagError("Unable to read data from FTDI") byte = data[0] # need to shift bits as they are shifted in from the MSB in FTDI byte >>= 8 - bit_count bitseq = BitSequence(byte, length=bit_count) bs.append(bitseq) # print_("pop %d bits" % bit_count) if len(bs) != length: raise ValueError("Internal error") return bs def _stack_cmd(self, cmd): if not isinstance(cmd, Array): raise TypeError("Expect a byte array") if not self._ftdi: raise JtagError("FTDI controller terminated") # Currrent buffer + new command + send_immediate if (len(self._write_buff) + len(cmd) + 1) >= JtagController.FTDI_PIPE_LEN: self.sync() self._write_buff.extend(cmd) def _read_bits(self, length): """Read out bits from TDO""" if length > 8: raise JtagError("Cannot fit into FTDI fifo") cmd = Array("B", (Ftdi.READ_BITS_NVE_LSB, length - 1)) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(1, 4) # need to shift bits as they are shifted in from the MSB in FTDI byte = data[0] >> 8 - length bs = BitSequence(byte, length=length) # print_("READ BITS %s" % bs) return bs def _write_bits(self, out): """Output bits on TDI""" length = len(out) byte = out.tobyte() # print_("WRITE BITS %s" % out) cmd = Array("B", (Ftdi.WRITE_BITS_NVE_LSB, length - 1, byte)) self._stack_cmd(cmd) def _read_bytes(self, length): """Read out bytes from TDO""" if length > JtagController.FTDI_PIPE_LEN: raise JtagError("Cannot fit into FTDI fifo") alen = length - 1 cmd = Array("B", (Ftdi.READ_BYTES_NVE_LSB, alen & 0xFF, (alen >> 8) & 0xFF)) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(length, 4) bs = BitSequence(bytes_=data, length=8 * length) # print_("READ BYTES %s" % bs) return bs def _write_bytes(self, out): """Output bytes on TDI""" bytes_ = out.tobytes(msby=True) # don't ask... olen = len(bytes_) - 1 # print_("WRITE BYTES %s" % out) cmd = Array("B", (Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xFF, (olen >> 8) & 0xFF)) cmd.extend(bytes_) self._stack_cmd(cmd) def _write_bytes_raw(self, out): """Output bytes on TDI""" olen = len(out) - 1 cmd = Array("B", (Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xFF, (olen >> 8) & 0xFF)) cmd.extend(out) self._stack_cmd(cmd)
class JtagController(object): """JTAG master of an FTDI device""" TCK_BIT = 0x01 # FTDI output TDI_BIT = 0x02 # FTDI output TDO_BIT = 0x04 # FTDI input TMS_BIT = 0x08 # FTDI output TRST_BIT = 0x10 # FTDI output, not available on 2232 JTAG debugger JTAG_MASK = 0x1f FTDI_PIPE_LEN = 512 # Private API def __init__(self, trst=False, frequency=3.0E6): """ trst uses the nTRST optional JTAG line to hard-reset the TAP controller """ self._ftdi = Ftdi() self._trst = trst self._frequency = frequency self.direction = JtagController.TCK_BIT | \ JtagController.TDI_BIT | \ JtagController.TMS_BIT | \ (self._trst and JtagController.TRST_BIT or 0) self._last = None # Last deferred TDO bit self._write_buff = Array('B') def __del__(self): self.close() # Public API def configure(self, vendor, product, interface): """Configure the FTDI interface as a JTAG controller""" curfreq = self._ftdi.open_mpsse( vendor, product, interface, direction=self.direction, #initial=0x0, frequency=self._frequency) # FTDI requires to initialize all GPIOs before MPSSE kicks in cmd = Array('B', [Ftdi.SET_BITS_LOW, 0x0, self.direction]) self._ftdi.write_data(cmd) def close(self): if self._ftdi: self._ftdi.close() self._ftdi = None def purge(self): self._ftdi.purge_buffers() def reset(self, sync=False): """Reset the attached TAP controller. sync sends the command immediately (no caching) """ # we can either send a TRST HW signal or perform 5 cycles with TMS=1 # to move the remote TAP controller back to 'test_logic_reset' state # do both for now if not self._ftdi: raise JtagError("FTDI controller terminated") if self._trst: # nTRST value = 0 cmd = Array('B', [Ftdi.SET_BITS_LOW, value, self.direction]) self._ftdi.write_data(cmd) time.sleep(0.1) # nTRST should be left to the high state value = JtagController.TRST_BIT cmd = Array('B', [Ftdi.SET_BITS_LOW, value, self.direction]) self._ftdi.write_data(cmd) time.sleep(0.1) # TAP reset (even with HW reset, could be removed though) self.write_tms(BitSequence('11111')) if sync: self.sync() def sync(self): if not self._ftdi: raise JtagError("FTDI controller terminated") if self._write_buff: self._ftdi.write_data(self._write_buff) self._write_buff = Array('B') def write_tms(self, tms): """Change the TAP controller state""" if not isinstance(tms, BitSequence): raise JtagError('Expect a BitSequence') length = len(tms) if not (0 < length < 8): raise JtagError('Invalid TMS length') out = BitSequence(tms, length=8) # apply the last TDO bit if self._last is not None: out[7] = self._last # print "TMS", tms, (self._last is not None) and 'w/ Last' or '' # reset last bit self._last = None cmd = Array('B', [Ftdi.WRITE_BITS_TMS_NVE, length - 1, out.tobyte()]) self._stack_cmd(cmd) self.sync() #self._ftdi.validate_mpsse() def read(self, length): """Read out a sequence of bits from TDO""" byte_count = length // 8 bit_count = length - 8 * byte_count bs = BitSequence() if byte_count: bytes = self._read_bytes(byte_count) bs.append(bytes) if bit_count: bits = self._read_bits(bit_count) bs.append(bits) return bs def write(self, out, use_last=True): """Write a sequence of bits to TDI""" if isinstance(out, str): if len(out) > 1: self._write_bytes_raw(out[:-1]) out = out[-1] out = BitSequence(bytes_=out) elif not isinstance(out, BitSequence): out = BitSequence(out) if use_last: (out, self._last) = (out[:-1], bool(out[-1])) byte_count = len(out) // 8 pos = 8 * byte_count bit_count = len(out) - pos if byte_count: self._write_bytes(out[:pos]) if bit_count: self._write_bits(out[pos:]) def shift_register(self, out, use_last=False): """Shift a BitSequence into the current register and retrieve the register output""" if not isinstance(out, BitSequence): return JtagError('Expect a BitSequence') length = len(out) if use_last: (out, self._last) = (out[:-1], int(out[-1])) byte_count = len(out) // 8 pos = 8 * byte_count bit_count = len(out) - pos if not byte_count and not bit_count: raise JtagError("Nothing to shift") if byte_count: blen = byte_count - 1 #print "RW OUT %s" % out[:pos] cmd = Array('B', [Ftdi.RW_BYTES_PVE_NVE_LSB, blen, (blen >> 8) & 0xff]) cmd.extend(out[:pos].tobytes(msby=True)) self._stack_cmd(cmd) #print "push %d bytes" % byte_count if bit_count: #print "RW OUT %s" % out[pos:] cmd = Array('B', [Ftdi.RW_BITS_PVE_NVE_LSB, bit_count - 1]) cmd.append(out[pos:].tobyte()) self._stack_cmd(cmd) #print "push %d bits" % bit_count self.sync() bs = BitSequence() byte_count = length // 8 pos = 8 * byte_count bit_count = length - pos if byte_count: data = self._ftdi.read_data_bytes(byte_count, 4) if not data: raise JtagError('Unable to read data from FTDI') byteseq = BitSequence(bytes_=data, length=8 * byte_count) #print "RW IN %s" % byteseq bs.append(byteseq) #print "pop %d bytes" % byte_count if bit_count: data = self._ftdi.read_data_bytes(1, 4) if not data: raise JtagError('Unable to read data from FTDI') byte = data[0] # need to shift bits as they are shifted in from the MSB in FTDI byte >>= 8 - bit_count bitseq = BitSequence(byte, length=bit_count) bs.append(bitseq) #print "pop %d bits" % bit_count if len(bs) != length: raise AssertionError("Internal error") #self._ftdi.validate_mpsse() return bs def _stack_cmd(self, cmd): if not isinstance(cmd, Array): raise TypeError('Expect a byte array') if not self._ftdi: raise JtagError("FTDI controller terminated") # Currrent buffer + new command + send_immediate if (len(self._write_buff) + len(cmd) + 1) >= JtagController.FTDI_PIPE_LEN: self.sync() self._write_buff.extend(cmd) def _read_bits(self, length): """Read out bits from TDO""" data = '' if length > 8: raise JtagError("Cannot fit into FTDI fifo") cmd = Array('B', [Ftdi.READ_BITS_NVE_LSB, length - 1]) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(1, 4) # need to shift bits as they are shifted in from the MSB in FTDI byte = ord(data) >> 8 - bit_count bs = BitSequence(byte, length=length) #print "READ BITS %s" % (bs) return bs def _write_bits(self, out): """Output bits on TDI""" length = len(out) byte = out.tobyte() #print "WRITE BITS %s" % out cmd = Array('B', [Ftdi.WRITE_BITS_NVE_LSB, length - 1, byte]) self._stack_cmd(cmd) def _read_bytes(self, length): """Read out bytes from TDO""" data = '' if length > JtagController.FTDI_PIPE_LEN: raise JtagError("Cannot fit into FTDI fifo") alen = length - 1 cmd = Array('B', [Ftdi.READ_BYTES_NVE_LSB, alen & 0xff, (alen >> 8) & 0xff]) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(length, 4) bs = BitSequence(bytes_=data, length=8 * length) #print "READ BYTES %s" % bs return bs def _write_bytes(self, out): """Output bytes on TDI""" bytes_ = out.tobytes(msby=True) # don't ask... olen = len(bytes_) - 1 #print "WRITE BYTES %s" % out cmd = Array( 'B', [Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xff, (olen >> 8) & 0xff]) cmd.extend(bytes_) self._stack_cmd(cmd) def _write_bytes_raw(self, out): """Output bytes on TDI""" olen = len(out) - 1 cmd = Array( 'B', [Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xff, (olen >> 8) & 0xff]) cmd.fromstring(out) self._stack_cmd(cmd)
class SpiController(object): """SPI master. :param silent_clock: should be set to avoid clocking out SCLK when all /CS signals are released. This clock beat is used to enforce a delay between /CS signal activation. When weird SPI devices are used, SCLK beating may cause trouble. In this case, silent_clock should be set but beware that SCLK line should be fitted with a pull-down resistor, as SCLK is high-Z during this short period of time. :param cs_count: is the number of /CS lines (one per device to drive on the SPI bus) """ SCK_BIT = 0x01 DO_BIT = 0x02 DI_BIT = 0x04 CS_BIT = 0x08 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max def __init__(self, silent_clock=False, cs_count=4): self._ftdi = Ftdi() self._cs_bits = ((SpiController.CS_BIT << cs_count) - 1) & ~(SpiController.CS_BIT - 1) self._ports = [None] * cs_count self._direction = self._cs_bits | SpiController.DO_BIT | SpiController.SCK_BIT self._cs_high = Array("B") if silent_clock: # Set SCLK as input to avoid emitting clock beats self._cs_high.extend([Ftdi.SET_BITS_LOW, self._cs_bits, self._direction & ~SpiController.SCK_BIT]) # /CS to SCLK delay, use 8 clock cycles as a HW tempo self._cs_high.extend([Ftdi.WRITE_BITS_TMS_NVE, 8 - 1, 0xFF]) # Restore idle state self._cs_high.extend([Ftdi.SET_BITS_LOW, self._cs_bits, self._direction]) self._immediate = Array("B", [Ftdi.SEND_IMMEDIATE]) self._frequency = 0.0 def configure(self, vendor, product, interface, frequency=6.0e6): """Configure the FTDI interface as a SPI master""" self._frequency = self._ftdi.open_mpsse( vendor, product, interface, direction=self._direction, initial=self._cs_bits, # /CS all high frequency=frequency, ) def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, cs): """Obtain a SPI port to drive a SPI device selected by cs""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._ports): raise SpiIOError("No such SPI port") if not self._ports[cs]: cs_state = 0xFF & ~((SpiController.CS_BIT << cs) | SpiController.SCK_BIT | SpiController.DO_BIT) cs_cmd = Array("B", [Ftdi.SET_BITS_LOW, cs_state, self._direction]) self._ports[cs] = SpiPort(self, cs_cmd) self._flush() return self._ports[cs] @property def frequency_max(self): """Returns the maximum SPI clock""" return self._ftdi.frequency_max @property def frequency(self): """Returns the current SPI clock""" return self._frequency def _exchange(self, frequency, cs_cmd, out, readlen): """Perform a half-duplex transaction with the SPI slave""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if len(out) > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Output payload is too large") if readlen > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Input payload is too large") if self._frequency != frequency: freq = self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort) self._frequency = frequency write_cmd = struct.pack("<BH", Ftdi.WRITE_BYTES_NVE_MSB, len(out) - 1) if readlen: read_cmd = struct.pack("<BH", Ftdi.READ_BYTES_NVE_MSB, readlen - 1) cmd = Array("B", cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) cmd.fromstring(read_cmd) cmd.extend(self._immediate) cmd.extend(self._cs_high) self._ftdi.write_data(cmd) # USB read cycle may occur before the FTDI device has actually # sent the data, so try to read more than once if no data is # actually received data = self._ftdi.read_data_bytes(readlen, 4) else: cmd = Array("B", cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) cmd.extend(self._cs_high) self._ftdi.write_data(cmd) data = Array("B") return data def _flush(self): """Flush the HW FIFOs""" self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers()
class SpiController(object): """SPI master. :param silent_clock: should be set to avoid clocking out SCLK when all /CS signals are released. This clock beat is used to enforce a delay between /CS signal activation. When weird SPI devices are used, SCLK beating may cause trouble. In this case, silent_clock should be set but beware that SCLK line should be fitted with a pull-down resistor, as SCLK is high-Z during this short period of time. Note that in this mode, it is recommended to use an external pull-down on SCLK :param cs_count: is the number of /CS lines (one per device to drive on the SPI bus) """ SCK_BIT = 0x01 DO_BIT = 0x02 DI_BIT = 0x04 CS_BIT = 0x08 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max def __init__(self, silent_clock=False, cs_count=4, turbo=True): self._ftdi = Ftdi() self._cs_bits = (((SpiController.CS_BIT << cs_count) - 1) & ~(SpiController.CS_BIT - 1)) self._ports = [None] * cs_count self._direction = (self._cs_bits | SpiController.DO_BIT | SpiController.SCK_BIT) self._turbo = turbo self._cs_high = Array('B') if self._turbo: if silent_clock: # Set SCLK as input to avoid emitting clock beats self._cs_high.extend([ Ftdi.SET_BITS_LOW, self._cs_bits, self._direction & ~SpiController.SCK_BIT ]) # /CS to SCLK delay, use 8 clock cycles as a HW tempo self._cs_high.extend([Ftdi.WRITE_BITS_TMS_NVE, 8 - 1, 0xff]) # Restore idle state self._cs_high.extend( [Ftdi.SET_BITS_LOW, self._cs_bits, self._direction]) if not self._turbo: self._cs_high.append(Ftdi.SEND_IMMEDIATE) self._immediate = Array('B', [Ftdi.SEND_IMMEDIATE]) self._frequency = 0.0 def configure(self, vendor, product, interface, **kwargs): """Configure the FTDI interface as a SPI master""" for k in ('direction', 'initial'): if k in kwargs: del kwargs[k] self._frequency = \ self._ftdi.open_mpsse(vendor, product, interface, direction=self._direction, initial=self._cs_bits, # /CS all high **kwargs) def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, cs): """Obtain a SPI port to drive a SPI device selected by cs""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._ports): raise SpiIOError("No such SPI port") if not self._ports[cs]: cs_state = 0xFF & ~((SpiController.CS_BIT << cs) | SpiController.SCK_BIT | SpiController.DO_BIT) cs_cmd = Array('B', [Ftdi.SET_BITS_LOW, cs_state, self._direction]) self._ports[cs] = SpiPort(self, cs_cmd) self._flush() return self._ports[cs] @property def frequency_max(self): """Returns the maximum SPI clock""" return self._ftdi.frequency_max @property def frequency(self): """Returns the current SPI clock""" return self._frequency def _exchange(self, frequency, cs_cmd, out, readlen): """Perform a half-duplex transaction with the SPI slave""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if len(out) > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Output payload is too large") if readlen > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Input payload is too large") if self._frequency != frequency: self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort) self._frequency = frequency write_cmd = struct.pack('<BH', Ftdi.WRITE_BYTES_NVE_MSB, len(out) - 1) if readlen: read_cmd = struct.pack('<BH', Ftdi.READ_BYTES_NVE_MSB, readlen - 1) cmd = Array('B', cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) cmd.fromstring(read_cmd) cmd.extend(self._immediate) if self._turbo: cmd.extend(self._cs_high) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) self._ftdi.write_data(self._cs_high) # USB read cycle may occur before the FTDI device has actually # sent the data, so try to read more than once if no data is # actually received data = self._ftdi.read_data_bytes(readlen, 4) else: cmd = Array('B', cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) if self._turbo: cmd.extend(self._cs_high) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) self._ftdi.write_data(self._cs_high) data = Array('B') return data def _flush(self): """Flush the HW FIFOs""" self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers()
import sys from array import array # this library is jacked up, should not have to do this # I'll probably fork/rewrite it, saving the bits that work # for now it is quick to start from pyftdi.ftdi import Ftdi f = Ftdi() # reset the FTDI chip # HA! this can interrupt a continous clock forever mode # to do something new f.open_mpsse(0x403, 0x6014) f.usb_dev.reset() f.close() print "reset!" # here's the normal open f.open_mpsse(0x403, 0x6014) if len(sys.argv) > 1: freq = int(sys.argv[1]) else: freq = 1000 # or whatever print "set freq: %d" % (freq,) f.set_frequency(freq)