class Interface(object): def __init__(self): self.baud_rate = 250000 self.data_bits = 8 self.stop_bits = 2 self.parity = 'N' self.flow_ctrl = '' self.rts_state = 0 self._is_open = False self._init_dmx() def _init_dmx(self): self.ftdi = Ftdi() try: self.ftdi.open(VENDOR, PRODUCT, 0) self.ftdi.set_baudrate(self.baud_rate) self.ftdi.set_line_property(self.data_bits, self.stop_bits, self.parity, break_=Ftdi.BREAK_OFF) self.ftdi.set_flowctrl(self.flow_ctrl) self.ftdi.purge_buffers() self.ftdi.set_rts(self.rts_state) self._is_open = True except USBError, e: print e self._is_open = False
class Sycamore (object): SYNC_FIFO_INTERFACE = 0 SYNC_FIFO_INDEX = 0 def __init__(self, idVendor, idProduct): self.vendor = idVendor self.product = idProduct self.dev = Ftdi() self.open_dev() def __del__(self): self.dev.close() def ping(self): data = Array('B', '0000'.decode('hex')) print "writing" for a in data: print "Data: %02X" % (a) self.dev.write_data(data) print "reading" time.sleep(.1) response = self.dev.read_data(4) rsp = Array('B') rsp.fromstring(response) print "rsp: " + str(rsp) # for a in rsp: # print "Data: %02X" % (a) def open_dev(self): frequency = 30.0E6 latency = 2 self.dev.open(self.vendor, self.product, 0) # Drain input buffer self.dev.purge_buffers() # Reset # Enable MPSSE mode self.dev.set_bitmode(0x00, Ftdi.BITMODE_SYNCFF) # Configure clock frequency = self.dev._set_frequency(frequency) # Set latency timer self.dev.set_latency_timer(latency) # Set chunk size self.dev.write_data_set_chunksize(0x10000) self.dev.read_data_set_chunksize(0x10000) self.dev.set_flowctrl('hw') # Configure I/O # self.write_data(Array('B', [Ftdi.SET_BITS_LOW, 0x00, 0x00])) # Disable loopback # self.write_data(Array('B', [Ftdi.LOOPBACK_END])) # self.validate_mpsse() # Drain input buffer self.dev.purge_buffers()
class I2cController: """I2c master. An I2c master should be instanciated only once for each FTDI port that supports MPSSE (one or two ports, depending on the FTDI device). Once configured, :py:func:`get_port` should be invoked to obtain an I2c port for each I2c slave to drive. I2c port should handle all I/O requests for its associated HW slave. It is not recommended to use I2cController :py:func:`read`, :py:func:`write` or :py:func:`exchange` directly. * ``SCK`` should be connected to ``A*BUS0``, and ``A*BUS7`` if clock stretching mode is enabled * ``SDA`` should be connected to ``A*BUS1`` **and** ``A*BUS2`` """ LOW = 0x00 HIGH = 0xff BIT0 = 0x01 IDLE = HIGH SCL_BIT = 0x01 #AD0 SDA_O_BIT = 0x02 #AD1 SDA_I_BIT = 0x04 #AD2 SCL_FB_BIT = 0x80 #AD7 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max HIGHEST_I2C_ADDRESS = 0x78 DEFAULT_BUS_FREQUENCY = 100000.0 HIGH_BUS_FREQUENCY = 400000.0 RETRY_COUNT = 3 I2C_MASK = SCL_BIT | SDA_O_BIT | SDA_I_BIT I2C_MASK_CS = SCL_BIT | SDA_O_BIT | SDA_I_BIT | SCL_FB_BIT I2C_DIR = SCL_BIT | SDA_O_BIT I2C_100K = I2CTimings(4.0E-6, 4.7E-6, 4.0E-6, 4.7E-6) I2C_400K = I2CTimings(0.6E-6, 0.6E-6, 0.6E-6, 1.3E-6) I2C_1M = I2CTimings(0.26E-6, 0.26E-6, 0.26E-6, 0.5E-6) def __init__(self): self._ftdi = Ftdi() self._lock = Lock() self.log = getLogger('pyftdi.i2c') self._gpio_port = None self._gpio_dir = 0 self._gpio_low = 0 self._gpio_mask = 0 self._i2c_mask = 0 self._wide_port = False self._slaves = {} self._retry_count = self.RETRY_COUNT self._frequency = 0.0 self._immediate = (Ftdi.SEND_IMMEDIATE, ) self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0) self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0) self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0) self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH) self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW) self._ck_delay = 1 self._tristate = None self._tx_size = 1 self._rx_size = 1 self._ck_hd_sta = 0 self._ck_su_sto = 0 self._ck_idle = 0 def set_retry_count(self, count): """Change the default retry count when a communication error occurs, before bailing out. :param int count: count of retries """ if not isinstance(count, int) or not 0 < count <= 16: raise ValueError('Invalid retry count') self._retry_count = count def configure(self, url, **kwargs): """Configure the FTDI interface as a I2c master. :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1' :param kwargs: options to configure the I2C bus Accepted options: * ``frequency`` float value the I2C bus frequency in Hz * ``clockstretching`` boolean value to enable clockstreching. xD7 (GPIO7) pin should be connected back to xD0 (SCK) """ for k in ('direction', 'initial'): if k in kwargs: del kwargs[k] if 'frequency' in kwargs: frequency = kwargs['frequency'] del kwargs['frequency'] else: frequency = self.DEFAULT_BUS_FREQUENCY # Fix frequency for 3-phase clock if frequency <= 100E3: timings = self.I2C_100K elif frequency <= 400E3: timings = self.I2C_100K else: timings = self.I2C_100K if 'clockstretching' in kwargs: clkstrch = bool(kwargs['clockstretching']) del kwargs['clockstretching'] else: clkstrch = False self._ck_hd_sta = self._compute_delay_cycles(timings.t_hd_sta) self._ck_su_sto = self._compute_delay_cycles(timings.t_su_sto) ck_su_sta = self._compute_delay_cycles(timings.t_su_sta) ck_buf = self._compute_delay_cycles(timings.t_buf) self._ck_idle = max(ck_su_sta, ck_buf) self._ck_delay = ck_buf # as 3-phase clock frequency mode is required for I2C mode, the # FTDI clock should be adapted to match the required frequency. frequency = self._ftdi.open_mpsse_from_url( url, direction=self.I2C_DIR, initial=self.IDLE, frequency=(3.0 * frequency) / 2.0, **kwargs) self._frequency = (2.0 * frequency) / 3.0 self._tx_size, self._rx_size = self._ftdi.fifo_sizes self._ftdi.enable_adaptive_clock(clkstrch) self._ftdi.enable_3phase_clock(True) try: self._ftdi.enable_drivezero_mode(self.SCL_BIT | self.SDA_O_BIT | self.SDA_I_BIT) except FtdiFeatureError: self._tristate = (Ftdi.SET_BITS_LOW, self.LOW, self.SCL_BIT) self._wide_port = self._ftdi.has_wide_port if clkstrch: self._i2c_mask = self.I2C_MASK_CS else: self._i2c_mask = self.I2C_MASK def terminate(self): """Close the FTDI interface. """ with self._lock: if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, address): """Obtain an I2cPort to drive an I2c slave. :param int address: the address on the I2C bus :return: an I2cPort instance :rtype: :py:class:`I2cPort` """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address not in self._slaves: self._slaves[address] = I2cPort(self, address) return self._slaves[address] def get_gpio(self): """Retrieve the GPIO port. :return GPIO port """ with self._lock: if not self._ftdi: raise I2cIOError("FTDI controller not initialized") if not self._gpio_port: self._gpio_port = I2cGpioPort(self) return self._gpio_port @property def configured(self): """Test whether the device has been properly configured. :return: True if configured """ return bool(self._ftdi) and bool(self._start) @classmethod def validate_address(cls, address): """Assert an I2C slave address is in the supported range. None is a special bypass address. :param address: the address on the I2C bus :type address: int or None :raise I2cIOError: if the I2C slave address is not supported """ if address is None: return if address > cls.HIGHEST_I2C_ADDRESS: raise I2cIOError("No such I2c slave: 0x%02x" % address) @property def frequency_max(self): """Provides the maximum I2c clock frequency. """ return self._ftdi.frequency_max @property def frequency(self): """Provides the current I2c clock frequency. :return: the I2C bus clock :rtype: float """ return self._frequency @property def direction(self): """Provide the FTDI GPIO direction""" return self.I2C_DIR | self._gpio_dir @property def gpio_pins(self): """Report the configured GPIOs as a bitfield""" with self._lock: return self._gpio_mask @property def gpio_all_pins(self): """Report the addressable GPIOs as a bitfield""" mask = (1 << self.width) - 1 with self._lock: return mask & ~self._i2c_mask @property def width(self): """Report the FTDI count of addressable pins. :return: the count of IO pins (including I2C ones). """ return 16 if self._wide_port else 8 def read(self, address, readlen=1, relax=True): """Read one or more bytes from a remote slave :param address: the address on the I2C bus, or None to discard start :type address: int or None :param int readlen: count of bytes to read out. :param bool relax: not used :return: read bytes :rtype: array :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) Most I2C devices require a register address to read out check out the exchange() method. """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 retries = self._retry_count do_epilog = True with self._lock: while True: try: self._do_prolog(i2caddress) data = self._do_read(readlen) do_epilog = relax return data except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry read') finally: if do_epilog: self._do_epilog() def write(self, address, out, relax=True): """Write one or more bytes to a remote slave :param address: the address on the I2C bus, or None to discard start :type address: int or None :param out: the byte buffer to send :type out: array or bytes or list(int) :param bool relax: whether to relax the bus (emit STOP) or not :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) Most I2C devices require a register address to write into. It should be added as the first (byte)s of the output buffer. """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH retries = self._retry_count do_epilog = True with self._lock: while True: try: self._do_prolog(i2caddress) self._do_write(out) do_epilog = relax return except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry write') finally: if do_epilog: self._do_epilog() def exchange(self, address, out, readlen=0, relax=True): """Send a byte sequence to a remote slave followed with a read request of one or more bytes. This command is useful to tell the slave what data should be read out. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param out: the byte buffer to send :type out: array or bytes or list(int) :param int readlen: count of bytes to read out. :param bool relax: whether to relax the bus (emit STOP) or not :return: read bytes :rtype: array :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if readlen < 1: raise I2cIOError('Nothing to read') if readlen > (self.PAYLOAD_MAX_LENGTH / 3 - 1): raise I2cIOError("Input payload is too large") if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH retries = self._retry_count do_epilog = True with self._lock: while True: try: self._do_prolog(i2caddress) self._do_write(out) self._do_prolog(i2caddress | self.BIT0) if readlen: data = self._do_read(readlen) do_epilog = relax return data except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry exchange') finally: if do_epilog: self._do_epilog() def poll(self, address, write=False, relax=True): """Poll a remote slave, expect ACK or NACK. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param bool write: poll in write mode (vs. read) :param bool relax: whether to relax the bus (emit STOP) or not :return: True if the slave acknowledged, False otherwise :rtype: bool """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH if not write: i2caddress |= self.BIT0 do_epilog = True with self._lock: try: self._do_prolog(i2caddress) do_epilog = relax return True except I2cNackError: self.log.info('Not ready') return False finally: if do_epilog: self._do_epilog() def poll_cond(self, address, fmt, mask, value, count, relax=True): """Poll a remove slave, watching for condition to satisfy. On each poll cycle, a repeated start condition is emitted, without releasing the I2C bus, and an ACK is returned to the slave. If relax is set, this method releases the I2C bus however it leaves. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param str fmt: struct format for poll register :param int mask: binary mask to apply on the condition register before testing for the value :param int value: value to test the masked condition register against. Condition is satisfied when register & mask == value :param int count: maximum poll count before raising a timeout :param bool relax: whether to relax the bus (emit STOP) or not :return: the polled register value, or None if poll failed :rtype: array or None """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 do_epilog = True with self._lock: try: retry = 0 while retry < count: retry += 1 size = scalc(fmt) self._do_prolog(i2caddress) data = self._do_read(size) self.log.debug("Poll data: %s", hexlify(data).decode()) cond, = sunpack(fmt, data) if (cond & mask) == value: self.log.debug('Poll condition matched') break else: data = None self.log.debug('Poll condition not fulfilled: %x/%x', cond & mask, value) do_epilog = relax if not data: self.log.warning('Poll condition failed') return data except I2cNackError: self.log.info('Not ready') return None finally: if do_epilog: self._do_epilog() def flush(self): """Flush the HW FIFOs """ if not self.configured: raise I2cIOError("FTDI controller not initialized") with self._lock: self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers() def read_gpio(self, with_output=False): """Read GPIO port :param bool with_output: set to unmask output pins :return: the GPIO port pins as a bitfield :rtype: int """ with self._lock: data = self._read_raw(self._wide_port) value = data & self._gpio_mask if not with_output: value &= ~self._gpio_dir return value def write_gpio(self, value): """Write GPIO port :param int value: the GPIO port pins as a bitfield """ with self._lock: if (value & self._gpio_dir) != value: raise I2cIOError('No such GPO pins: %04x/%04x' % (self._gpio_dir, value)) # perform read-modify-write use_high = self._wide_port and (self.direction & 0xff00) data = self._read_raw(use_high) data &= ~self._gpio_mask data |= value self._write_raw(data, use_high) self._gpio_low = data & 0xFF & ~self._i2c_mask def set_gpio_direction(self, pins, direction): """Change the direction of the GPIO pins :param int pins: which GPIO pins should be reconfigured :param int direction: direction bitfield (on for output) """ with self._lock: if pins & self._i2c_mask: raise I2cIOError('Cannot access I2C pins as GPIO') gpio_width = self._wide_port and 16 or 8 gpio_mask = (1 << gpio_width) - 1 gpio_mask &= ~self._i2c_mask if (pins & gpio_mask) != pins: raise I2cIOError('No such GPIO pin(s)') self._gpio_dir &= ~pins self._gpio_dir |= (pins & direction) self._gpio_mask = gpio_mask & pins @property def _data_lo(self): return (Ftdi.SET_BITS_LOW, self.SCL_BIT | self._gpio_low, self.I2C_DIR | (self._gpio_dir & 0xFF)) @property def _clk_lo_data_hi(self): return (Ftdi.SET_BITS_LOW, self.SDA_O_BIT | self._gpio_low, self.I2C_DIR | (self._gpio_dir & 0xFF)) @property def _clk_lo_data_lo(self): return (Ftdi.SET_BITS_LOW, self._gpio_low, self.I2C_DIR | (self._gpio_dir & 0xFF)) @property def _idle(self): return (Ftdi.SET_BITS_LOW, self.I2C_DIR | self._gpio_low, self.I2C_DIR | (self._gpio_dir & 0xFF)) @property def _start(self): return self._data_lo * self._ck_hd_sta + \ self._clk_lo_data_lo * self._ck_hd_sta @property def _stop(self): return self._clk_lo_data_hi * self._ck_hd_sta + \ self._data_lo * self._ck_su_sto + \ self._idle * self._ck_idle def _compute_delay_cycles(self, value): # approx ceiling without relying on math module # the bit delay is far from being precisely known anyway bit_delay = self._ftdi.mpsse_bit_delay return max(1, int((value + bit_delay) / bit_delay)) def _read_raw(self, read_high): if read_high: cmd = array( 'B', [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE]) fmt = '<H' else: cmd = array('B', [Ftdi.GET_BITS_LOW, Ftdi.SEND_IMMEDIATE]) fmt = 'B' self._ftdi.write_data(cmd) size = scalc(fmt) data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise I2cIOError('Cannot read GPIO') value, = sunpack(fmt, data) return value def _write_raw(self, data, write_high): direction = self.direction low_data = data & 0xFF low_dir = direction & 0xFF if write_high: high_data = (data >> 8) & 0xFF high_dir = (direction >> 8) & 0xFF cmd = array('B', [ Ftdi.SET_BITS_LOW, low_data, low_dir, Ftdi.SET_BITS_HIGH, high_data, high_dir ]) else: cmd = array('B', [Ftdi.SET_BITS_LOW, low_data, low_dir]) self._ftdi.write_data(cmd) def _do_prolog(self, i2caddress): if i2caddress is None: return self.log.debug(' prolog 0x%x', i2caddress >> 1) cmd = bytearray(self._idle) cmd.extend(self._start) cmd.extend(self._write_byte) cmd.append(i2caddress) if self._tristate: cmd.extend(self._tristate) cmd.extend(self._read_bit) cmd.extend(self._clk_lo_data_hi) else: cmd.extend(self._clk_lo_data_hi) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: raise I2cIOError('No answer from FTDI') if ack[0] & self.BIT0: self.log.warning('NACK @ 0x%02x', (i2caddress >> 1)) raise I2cNackError('NACK from slave') def _do_epilog(self): self.log.debug(' epilog') cmd = bytearray(self._stop) self._ftdi.write_data(cmd) # be sure to purge the MPSSE reply self._ftdi.read_data_bytes(1, 1) def _do_read(self, readlen): self.log.debug('- read %d byte(s)', readlen) if not readlen: # force a real read request on device, but discard any result cmd = bytearray() cmd.extend(self._immediate) self._ftdi.write_data(cmd) self._ftdi.read_data_bytes(0, 4) return bytearray() if self._tristate: read_byte = self._tristate + \ self._read_byte + \ self._clk_lo_data_hi read_not_last = \ read_byte + self._ack + self._clk_lo_data_lo * self._ck_delay read_last = \ read_byte + self._nack + self._clk_lo_data_hi * self._ck_delay else: read_not_last = \ self._read_byte + self._ack + \ self._clk_lo_data_hi * self._ck_delay read_last = \ self._read_byte + self._nack + \ self._clk_lo_data_hi * self._ck_delay # maximum RX size to fit in FTDI FIFO, minus 2 status bytes chunk_size = self._rx_size - 2 cmd_size = len(read_last) # limit RX chunk size to the count of I2C packable commands in the FTDI # TX FIFO (minus one byte for the last 'send immediate' command) tx_count = (self._tx_size - 1) // cmd_size chunk_size = min(tx_count, chunk_size) chunks = [] cmd = None rem = readlen while rem: if rem > chunk_size: if not cmd: cmd = bytearray() cmd.extend(read_not_last * chunk_size) size = chunk_size else: cmd = bytearray() cmd.extend(read_not_last * (rem - 1)) cmd.extend(read_last) cmd.extend(self._immediate) size = rem self._ftdi.write_data(cmd) buf = self._ftdi.read_data_bytes(size, 4) self.log.debug('- read %d byte(s): %s', len(buf), hexlify(buf).decode()) chunks.append(buf) rem -= size return bytearray(b''.join(chunks)) def _do_write(self, out): if not isinstance(out, array): out = bytearray(out) if not out: return self.log.debug('- write %d byte(s): %s', len(out), hexlify(out).decode()) for byte in out: cmd = bytearray(self._write_byte) cmd.append(byte) if self._tristate: cmd.extend(self._tristate) cmd.extend(self._read_bit) cmd.extend(self._clk_lo_data_hi) else: cmd.extend(self._clk_lo_data_hi) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: msg = 'No answer from FTDI' self.log.critical(msg) raise I2cIOError(msg) if ack[0] & self.BIT0: msg = 'NACK from slave' self.log.warning(msg) raise I2cNackError(msg)
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 SpiController: """SPI master. :param silent_clock: deprecated. :param int cs_count: is the number of /CS lines (one per device to drive on the SPI bus) :param boolean turbo: to be documented """ SCK_BIT = 0x01 DO_BIT = 0x02 DI_BIT = 0x04 CS_BIT = 0x08 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): self.log = getLogger('pyftdi.spi.ctrl') self._ftdi = Ftdi() self._lock = Lock() self._gpio_port = None self._gpio_dir = 0 self._gpio_mask = 0 self._gpio_low = 0 self._wide_port = False self._cs_count = cs_count self._turbo = turbo self._immediate = bytes((Ftdi.SEND_IMMEDIATE,)) self._frequency = 0.0 self._clock_phase = False @property def direction(self): """Provide the FTDI GPIO direction""" return self._spi_dir | self._gpio_dir def configure(self, url, **kwargs): """Configure the FTDI interface as a SPI master :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1' :param kwargs: options to configure the SPI bus Accepted options: * ``frequency`` the SPI bus frequency in Hz. Note that each slave may reconfigure the SPI bus with a specialized frequency. * ``cs_count`` count of chip select signals dedicated to select SPI slave devices * ``turbo`` whether to enable or disable turbo mode * ``debug`` for extra debug output """ # it is better to specify CS and turbo in configure, but the older # API where these parameters are specified at instanciation has been # preserved self._cs_count = int(kwargs.get('cs_count', self._cs_count)) if not (1 <= self._cs_count <= 5): raise ValueError('Unsupported CS line count: %d' % self._cs_count) self._turbo = bool(kwargs.get('turbo', self._turbo)) for k in ('direction', 'initial', 'cs_count', 'turbo'): if k in kwargs: del kwargs[k] 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)) self._spi_ports = [None] * self._cs_count self._spi_dir = (self._cs_bits | SpiController.DO_BIT | SpiController.SCK_BIT) self._spi_mask = self._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) self._ftdi.enable_adaptive_clock(False) self._wide_port = self._ftdi.has_wide_port def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._frequency = 0.0 def get_port(self, cs, freq=None, mode=0): """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] :rtype: SpiPort """ with self._lock: if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._spi_ports): raise SpiIOError("No such SPI port") if not (0 <= mode <= 3): raise SpiIOError("Invalid SPI mode") if (mode & 0x2) and not self._ftdi.is_H_series: raise SpiIOError("SPI with CPHA high is not supported by " "this FTDI device") if mode == 2: raise SpiIOError("SPI mode 2 has no known workaround with " "FTDI devices") if not self._spi_ports[cs]: freq = min(freq or self._frequency, self.frequency_max) hold = freq and (1+int(1E6/freq)) self._spi_ports[cs] = SpiPort(self, cs, cs_hold=hold, spi_mode=mode) self._spi_ports[cs].set_frequency(freq) self._flush() return self._spi_ports[cs] def get_gpio(self): with self._lock: if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if not self._gpio_port: self._gpio_port = SpiGpioPort(self) return self._gpio_port @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 @property def gpio_pins(self): """Report the addressable GPIOs as a bitfield""" with self._lock: return self._gpio_mask def exchange(self, frequency, out, readlen, cs_prolog=None, cs_epilog=None, cpol=False, cpha=False, duplex=False): if duplex: if readlen > len(out): tmp = array('B', out) tmp.extend([0] * (readlen - len(out))) out = tmp elif not readlen: readlen = len(out) with self._lock: if duplex: data = self._exchange_full_duplex(frequency, out, cs_prolog, cs_epilog, cpol, cpha) return data[:readlen] else: return self._exchange_half_duplex(frequency, out, readlen, cs_prolog, cs_epilog, cpol, cpha) def read_gpio(self, with_output=False): """Read GPIO port :param bool with_output: set to unmask output pins :return: the GPIO port pins as a bitfield :rtype: int """ with self._lock: data = self._read_raw(self._wide_port) value = data & self._gpio_mask if not with_output: value &= ~self._gpio_dir return value def write_gpio(self, value): """Write GPIO port :param int value: the GPIO port pins as a bitfield """ with self._lock: if (value & self._gpio_dir) != value: raise SpiIOError('No such GPO pins: %04x/%04x' % (self._gpio_dir, value)) # perform read-modify-write use_high = self._wide_port and (self.direction & 0xff00) data = self._read_raw(use_high) data &= ~self._gpio_mask data |= value self._write_raw(data, use_high) self._gpio_low = data & 0xFF & ~self._spi_mask def set_gpio_direction(self, pins, direction): """Change the direction of the GPIO pins :param int pins: which GPIO pins should be reconfigured :param int direction: direction bitfield (on for output) """ with self._lock: if pins & self._spi_mask: raise SpiIOError('Cannot access SPI pins as GPIO') gpio_width = self._wide_port and 16 or 8 gpio_mask = (1 << gpio_width) - 1 gpio_mask &= ~self._spi_mask if (pins & gpio_mask) != pins: raise SpiIOError('No such GPIO pin(s)') self._gpio_dir &= ~pins self._gpio_dir |= (pins & direction) self._gpio_mask = gpio_mask & pins def _read_raw(self, read_high): if read_high: cmd = array('B', [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE]) fmt = '<H' else: cmd = array('B', [Ftdi.GET_BITS_LOW, Ftdi.SEND_IMMEDIATE]) fmt = 'B' self._ftdi.write_data(cmd) size = scalc(fmt) data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise SpiIOError('Cannot read GPIO') value, = sunpack(fmt, data) return value def _write_raw(self, data, write_high): direction = self.direction low_data = data & 0xFF low_dir = direction & 0xFF if write_high: high_data = (data >> 8) & 0xFF high_dir = (direction >> 8) & 0xFF cmd = array('B', [Ftdi.SET_BITS_LOW, low_data, low_dir, Ftdi.SET_BITS_HIGH, high_data, high_dir]) else: cmd = array('B', [Ftdi.SET_BITS_LOW, low_data, low_dir]) self._ftdi.write_data(cmd) def _exchange_half_duplex(self, frequency, out, readlen, cs_prolog, cs_epilog, cpol, cpha): 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 cpha: # to enable CPHA, we need to use a workaround with FTDI device, # that is enable 3-phase clocking (which is usually dedicated to # I2C support). This mode use use 3 clock period instead of 2, # which implies the FTDI frequency should be fixed to match the # requested one. frequency = (3*frequency)//2 if self._frequency != frequency: self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort), # to avoid setting unavailable values on each call. self._frequency = frequency direction = self.direction & 0xFF # low bits only cmd = array('B') for ctrl in cs_prolog or []: ctrl &= self._spi_mask ctrl |= self._gpio_low cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction)) 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)) # Restore idle state cs_high = [Ftdi.SET_BITS_LOW, self._cs_bits | self._gpio_low, direction] if not self._turbo: cs_high.append(Ftdi.SEND_IMMEDIATE) epilog.extend(cs_high) writelen = len(out) if self._clock_phase != cpha: self._ftdi.enable_3phase_clock(cpha) self._clock_phase = cpha if writelen: wcmd = (cpol ^ cpha) and \ Ftdi.WRITE_BYTES_PVE_MSB or Ftdi.WRITE_BYTES_NVE_MSB write_cmd = spack('<BH', wcmd, writelen-1) cmd.frombytes(write_cmd) cmd.extend(out) if readlen: rcmd = (cpol ^ cpha) and \ Ftdi.READ_BYTES_PVE_MSB or Ftdi.READ_BYTES_NVE_MSB read_cmd = spack('<BH', rcmd, readlen-1) cmd.frombytes(read_cmd) cmd.extend(self._immediate) if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) # 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: if writelen: if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) data = array('B') return data def _exchange_full_duplex(self, frequency, out, cs_prolog, cs_epilog, cpol, cpha): 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 cpha: # to enable CPHA, we need to use a workaround with FTDI device, # that is enable 3-phase clocking (which is usually dedicated to # I2C support). This mode use use 3 clock period instead of 2, # which implies the FTDI frequency should be fixed to match the # requested one. frequency = (3*frequency)//2 if self._frequency != frequency: self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort), # to avoid setting unavailable values on each call. self._frequency = frequency direction = self.direction & 0xFF # low bits only cmd = array('B') for ctrl in cs_prolog or []: ctrl &= self._spi_mask ctrl |= self._gpio_low cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction)) 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)) # Restore idle state cs_high = [Ftdi.SET_BITS_LOW, self._cs_bits | self._gpio_low, direction] if not self._turbo: cs_high.append(Ftdi.SEND_IMMEDIATE) epilog.extend(cs_high) writelen = len(out) if self._clock_phase != cpha: self._ftdi.enable_3phase_clock(cpha) self._clock_phase = cpha wcmd = (cpol ^ cpha) and \ Ftdi.RW_BYTES_NVE_PVE_MSB or Ftdi.RW_BYTES_PVE_NVE_MSB write_cmd = spack('<BH', wcmd, writelen-1) cmd.frombytes(write_cmd) cmd.extend(out) cmd.extend(self._immediate) if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) # 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(len(out), 4) 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""" 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)
print("Device not connected!") exit() ft.set_baudrate(BAUDRATE) print("Baudrate set to {}".format(BAUDRATE)) #ft.read_data_bytes(1) N = 2 i = 0 fifo_rd = [] # print ("______________") # print( "CTS? {}\tDSR? {}\tRI? {}".format( ft.get_cts(), ft.get_dsr(), ft.get_ri() ) ) # print( "Poll Modem status? {}\tModem Status? {}".format( ft.poll_modem_status(), ft.modem_status() ) ) # print ("______________") ft.set_flowctrl('') ft.purge_buffers() ft.purge_tx_buffer() ft.purge_rx_buffer() ft.set_break(False) #ft.set_flowctrl() ft.read_data_bytes(5).tolist() fifo_wr = [] for i in range(N): aux = (i + 1) % 256 aux = randint(0, 255) fifo_wr.append(aux) print("______________") print("CTS? {}\tDSR? {}\tRI? {}".format(ft.get_cts(), ft.get_dsr(), ft.get_ri())) print("Poll Modem status? {}\tModem Status? {}".format(
class I2cController: """I2c master. An I2c master should be instanciated only once for each FTDI port that supports MPSSE (one or two ports, depending on the FTDI device). Once configured, :py:func:`get_port` should be invoked to obtain an I2c port for each I2c slave to drive. I2cport should handle all I/O requests for its associated HW slave. It is not recommended to use I2cController :py:func:`read`, :py:func:`write` or :py:func:`exchange` directly. """ LOW = 0x00 HIGH = 0xff BIT0 = 0x01 IDLE = HIGH SCL_BIT = 0x01 SDA_O_BIT = 0x02 SDA_I_BIT = 0x04 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max HIGHEST_I2C_ADDRESS = 0x78 DEFAULT_BUS_FREQUENCY = 100000.0 HIGH_BUS_FREQUENCY = 400000.0 RETRY_COUNT = 3 I2C_100K = I2CTimings(4.0E-6, 4.7E-6, 4.0E-6, 4.7E-6) I2C_400K = I2CTimings(0.6E-6, 0.6E-6, 0.6E-6, 1.3E-6) I2C_1M = I2CTimings(0.26E-6, 0.26E-6, 0.26E-6, 0.5E-6) def __init__(self, ftdi = None): if not ftdi: self._ftdi = Ftdi() else: self._ftdi = ftdi self.log = getLogger('pyftdi.i2c') self._slaves = {} self._retry_count = self.RETRY_COUNT self._frequency = 0.0 self._direction = self.SCL_BIT | self.SDA_O_BIT self._immediate = (Ftdi.SEND_IMMEDIATE,) self._idle = (Ftdi.SET_BITS_LOW, self.IDLE, self._direction) self._data_lo = (Ftdi.SET_BITS_LOW, self.IDLE & ~self.SDA_O_BIT, self._direction) self._clk_lo_data_lo = (Ftdi.SET_BITS_LOW, self.IDLE & ~(self.SDA_O_BIT | self.SCL_BIT), self._direction) self._clk_lo_data_hi = (Ftdi.SET_BITS_LOW, self.IDLE & ~self.SCL_BIT, self._direction) self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0) self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0) self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0) self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH) self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW) self._start = None self._stop = None self._ck_delay = 1 self._tristate = None self._tx_size = 1 self._rx_size = 1 def _compute_delay_cycles(self, value): # approx ceiling without relying on math module # the bit delay is far from being precisely known anyway bit_delay = self._ftdi.mpsse_bit_delay return max(1, int((value + bit_delay) / bit_delay)) def set_retry_count(self, count): """Change the default retry count when a communication error occurs, before bailing out. :param int count: count of retries """ if not isinstance(count, int) or not 0 < count <= 16: raise ValueError('Invalid retry count') self._retry_count = count def configure(self, **kwargs): """Configure the FTDI interface as a I2c master. :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1' :param kwargs: options to configure the I2C bus Accepted options: * ``frequency`` the I2C bus frequency in Hz """ for k in ('direction', 'initial'): if k in kwargs: del kwargs[k] if 'url' in kwargs: url = kwargs['url'] del kwargs['url'] else: url = None if 'frequency' in kwargs: frequency = kwargs['frequency'] del kwargs['frequency'] else: frequency = self.DEFAULT_BUS_FREQUENCY # Fix frequency for 3-phase clock if frequency <= 100E3: timings = self.I2C_100K elif frequency <= 400E3: timings = self.I2C_100K else: timings = self.I2C_100K ck_hd_sta = self._compute_delay_cycles(timings.t_hd_sta) ck_su_sta = self._compute_delay_cycles(timings.t_su_sta) ck_su_sto = self._compute_delay_cycles(timings.t_su_sto) ck_buf = self._compute_delay_cycles(timings.t_buf) ck_idle = max(ck_su_sta, ck_buf) self._ck_delay = ck_buf self._start = (self._data_lo * ck_hd_sta + self._clk_lo_data_lo * ck_hd_sta) self._stop = (self._clk_lo_data_lo * ck_hd_sta + self._data_lo*ck_su_sto + self._idle*ck_idle) frequency = (3.0*frequency)/2.0 if url: self._frequency = self._ftdi.open_mpsse_from_url( url, direction=self._direction, initial=self.IDLE, frequency=frequency, **kwargs) else: self._frequency = self._ftdi.init_mpsse(direction=self._direction, initial=self.IDLE, frequency=frequency, **kwargs) self._tx_size, self._rx_size = self._ftdi.fifo_sizes self._ftdi.enable_adaptive_clock(False) self._ftdi.enable_3phase_clock(True) try: self._ftdi.enable_drivezero_mode(self.SCL_BIT | self.SDA_O_BIT | self.SDA_I_BIT) except FtdiFeatureError: self._tristate = (Ftdi.SET_BITS_LOW, self.LOW, self.SCL_BIT) def terminate(self): """Close the FTDI interface. """ if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, address): """Obtain an I2cPort to drive an I2c slave. :param int address: the address on the I2C bus :return: an I2cPort instance :rtype: :py:class:`I2cPort` """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address not in self._slaves: self._slaves[address] = I2cPort(self, address) return self._slaves[address] @property def configured(self): """Test whether the device has been properly configured. :return: True if configured """ return bool(self._ftdi) and bool(self._start) @classmethod def validate_address(cls, address): """Assert an I2C slave address is in the supported range. None is a special bypass address. :param address: the address on the I2C bus :type address: int or None :raise I2cIOError: if the I2C slave address is not supported """ if address is None: return if address > cls.HIGHEST_I2C_ADDRESS: raise I2cIOError("No such I2c slave: 0x%02x" % address) @property def frequency_max(self): """Provides the maximum I2c clock frequency. """ return self._ftdi.frequency_max @property def frequency(self): """Provides the current I2c clock frequency. :return: the I2C bus clock :rtype: float """ return self._frequency def read(self, address, readlen=1, relax=True): """Read one or more bytes from a remote slave :param address: the address on the I2C bus, or None to discard start :type address: int or None :param int readlen: count of bytes to read out. :param bool relax: not used :return: read bytes :rtype: array :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) Most I2C devices require a register address to read out check out the exchange() method. """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 retries = self._retry_count do_epilog = True while True: try: self._do_prolog(i2caddress) data = self._do_read(readlen) do_epilog = relax return data except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry read') finally: if do_epilog: self._do_epilog() def write(self, address, out, relax=True): """Write one or more bytes to a remote slave :param address: the address on the I2C bus, or None to discard start :type address: int or None :param out: the byte buffer to send :type out: array or bytes or list(int) :param bool relax: whether to relax the bus (emit STOP) or not :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) Most I2C devices require a register address to write into. It should be added as the first (byte)s of the output buffer. """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH retries = self._retry_count do_epilog = True while True: try: self._do_prolog(i2caddress) self._do_write(out) do_epilog = relax return except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry write') finally: if do_epilog: self._do_epilog() def exchange(self, address, out, readlen=0, relax=True): """Send a byte sequence to a remote slave followed with a read request of one or more bytes. This command is useful to tell the slave what data should be read out. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param out: the byte buffer to send :type out: array or bytes or list(int) :param int readlen: count of bytes to read out. :param bool relax: whether to relax the bus (emit STOP) or not :return: read bytes :rtype: array :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if readlen < 1: raise I2cIOError('Nothing to read') if readlen > (I2cController.PAYLOAD_MAX_LENGTH/3-1): raise I2cIOError("Input payload is too large") if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH retries = self._retry_count do_epilog = True while True: try: self._do_prolog(i2caddress) self._do_write(out) self._do_prolog(i2caddress | self.BIT0) if readlen: data = self._do_read(readlen) do_epilog = relax return data except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry exchange') finally: if do_epilog: self._do_epilog() def poll(self, address, write=False, relax=True): """Poll a remote slave, expect ACK or NACK. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param bool write: poll in write mode (vs. read) :param bool relax: whether to relax the bus (emit STOP) or not :return: True if the slave acknowledged, False otherwise :rtype: bool """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH if not write: i2caddress |= self.BIT0 do_epilog = True try: self._do_prolog(i2caddress) do_epilog = relax return True except I2cNackError: self.log.info('Not ready') return False finally: if do_epilog: self._do_epilog() def poll_cond(self, address, fmt, mask, value, count, relax=True): """Poll a remove slave, watching for condition to satisfy. On each poll cycle, a repeated start condition is emitted, without releasing the I2C bus, and an ACK is returned to the slave. If relax is set, this method releases the I2C bus however it leaves. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param str fmt: struct format for poll register :param int mask: binary mask to apply on the condition register before testing for the value :param int value: value to test the masked condition register against. Condition is satisfied when register & mask == value :param int count: maximum poll count before raising a timeout :param bool relax: whether to relax the bus (emit STOP) or not :return: the polled register value, or None if poll failed :rtype: array or None """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 do_epilog = True try: retry = 0 while retry < count: retry += 1 size = scalc(fmt) self._do_prolog(i2caddress) data = self._do_read(size) self.log.debug("Poll data: %s", hexlify(data).decode()) cond, = sunpack(fmt, data) if (cond & mask) == value: self.log.debug('Poll condition matched') break else: data = None self.log.debug('Poll condition not fulfilled: %x/%x', cond & mask, value) do_epilog = relax if not data: self.log.warning('Poll condition failed') return data except I2cNackError: self.log.info('Not ready') return None finally: if do_epilog: self._do_epilog() def flush(self): """Flush the HW FIFOs """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers() def _do_prolog(self, i2caddress): if i2caddress is None: return self.log.debug(' prolog 0x%x', i2caddress >> 1) cmd = array('B', self._idle) cmd.extend(self._start) cmd.extend(self._write_byte) cmd.append(i2caddress) if self._tristate: cmd.extend(self._tristate) cmd.extend(self._read_bit) cmd.extend(self._clk_lo_data_hi) else: cmd.extend(self._clk_lo_data_hi) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: raise I2cIOError('No answer from FTDI') if ack[0] & self.BIT0: self.log.warning('NACK') raise I2cNackError('NACK from slave') def _do_epilog(self): self.log.debug(' epilog') cmd = array('B', self._stop) self._ftdi.write_data(cmd) # be sure to purge the MPSSE reply self._ftdi.read_data_bytes(1, 1) def _do_read(self, readlen): self.log.debug('- read %d byte(s)', readlen) if not readlen: # force a real read request on device, but discard any result cmd = array('B') cmd.extend(self._immediate) self._ftdi.write_data(cmd) self._ftdi.read_data_bytes(0, 4) return array('B') if self._tristate: read_byte = self._tristate + \ self._read_byte + \ self._clk_lo_data_hi read_not_last = \ read_byte + self._ack + self._clk_lo_data_lo * self._ck_delay read_last = \ read_byte + self._nack + self._clk_lo_data_hi * self._ck_delay else: read_not_last = \ self._read_byte + self._ack + \ self._clk_lo_data_hi * self._ck_delay read_last = \ self._read_byte + self._nack + \ self._clk_lo_data_hi * self._ck_delay # maximum RX size to fit in FTDI FIFO, minus 2 status bytes chunk_size = self._rx_size-2 cmd_size = len(read_last) # limit RX chunk size to the count of I2C packable commands in the FTDI # TX FIFO (minus one byte for the last 'send immediate' command) tx_count = (self._tx_size-1) // cmd_size chunk_size = min(tx_count, chunk_size) chunks = [] cmd = None rem = readlen while rem: if rem > chunk_size: if not cmd: cmd = array('B') cmd.extend(read_not_last * chunk_size) size = chunk_size else: cmd = array('B') cmd.extend(read_not_last * (rem-1)) cmd.extend(read_last) cmd.extend(self._immediate) size = rem self._ftdi.write_data(cmd) buf = self._ftdi.read_data_bytes(size, 4) self.log.debug('- read %d byte(s): %s', len(buf), hexlify(buf).decode()) chunks.append(buf) rem -= size return array('B', b''.join(chunks)) def _do_write(self, out): if not isinstance(out, array): out = array('B', out) if not out: return self.log.debug('- write %d byte(s): %s', len(out), hexlify(out).decode()) for byte in out: cmd = array('B', self._write_byte) cmd.append(byte) if self._tristate: cmd.extend(self._tristate) cmd.extend(self._read_bit) cmd.extend(self._clk_lo_data_hi) else: cmd.extend(self._clk_lo_data_hi) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: msg = 'No answer from FTDI' self.log.critical(msg) raise I2cIOError(msg) if ack[0] & self.BIT0: msg = 'NACK from slave' self.log.warning(msg) raise I2cNackError(msg)
class I2cController(object): """I2c master. """ LOW = 0x00 HIGH = 0xff BIT0 = 0x01 IDLE = HIGH SCL_BIT = 0x01 SDA_O_BIT = 0x02 SDA_I_BIT = 0x04 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max HIGHEST_I2C_ADDRESS = 0x7f DEFAULT_BUS_FREQUENCY = 100000.0 HIGH_BUS_FREQUENCY = 400000.0 RETRY_COUNT = 3 def __init__(self): self._ftdi = Ftdi() self.log = getLogger('pyftdi.i2c') self._slaves = {} self._frequency = 0.0 self._direction = I2cController.SCL_BIT | I2cController.SDA_O_BIT self._immediate = (Ftdi.SEND_IMMEDIATE, ) self._idle = (Ftdi.SET_BITS_LOW, self.IDLE, self._direction) data_low = (Ftdi.SET_BITS_LOW, self.IDLE & ~self.SDA_O_BIT, self._direction) clock_low_data_low = (Ftdi.SET_BITS_LOW, self.IDLE & ~(self.SDA_O_BIT | self.SCL_BIT), self._direction) self._clock_low_data_high = (Ftdi.SET_BITS_LOW, self.IDLE & ~self.SCL_BIT, self._direction) self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0) self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0) self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0) self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH) self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW) self._start = data_low * 4 + clock_low_data_low * 4 self._stop = clock_low_data_low * 4 + data_low * 4 + self._idle * 4 self._tx_size = 1 self._rx_size = 1 def configure(self, url, **kwargs): """Configure the FTDI interface as a I2c master. :param url: FTDI URL string, such as 'ftdi://ftdi:232h/1' :param frequency: frequency of the I2C bus. """ for k in ('direction', 'initial'): if k in kwargs: del kwargs[k] if 'frequency' in kwargs: frequency = kwargs['frequency'] del kwargs['frequency'] else: frequency = self.DEFAULT_BUS_FREQUENCY # Fix frequency for 3-phase clock frequency = (3.0 * frequency) / 2.0 self._frequency = self._ftdi.open_mpsse_from_url( url, direction=self._direction, initial=self.IDLE, frequency=frequency, **kwargs) self._tx_size, self._rx_size = self._ftdi.fifo_sizes self._ftdi.enable_adaptive_clock(False) self._ftdi.enable_3phase_clock(True) self._ftdi.enable_drivezero_mode(self.SCL_BIT | self.SDA_O_BIT | self.SDA_I_BIT) def terminate(self): """Close the FTDI interface. """ if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, address): """Obtain a I2cPort to to drive an I2c slave. :param address: the address on the I2C bus :return a I2cPort instance """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address not in self._slaves: self._slaves[address] = I2cPort(self, address) return self._slaves[address] @classmethod def validate_address(cls, address): if address > cls.HIGHEST_I2C_ADDRESS: raise I2cIOError("No such I2c slave") @property def frequency_max(self): """Returns the maximum I2c clock. """ return self._ftdi.frequency_max @property def frequency(self): """Returns the current I2c clock. """ return self._frequency def read(self, address, readlen=1): """Read one or more bytes from a remote slave :param address: the address on the I2C bus :param readlen: count of bytes to read out. Address is a logical address (0x7f max) Most I2C devices require a register address to read out check out the exchange() method. """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if readlen < 1: raise I2cIOError('Nothing to read') i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 retries = self.RETRY_COUNT while True: try: self._do_prolog(i2caddress) data = self._do_read(readlen) return bytes(data) except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry read') finally: self._do_epilog() def write(self, address, out): """Write one or more bytes to a remote slave :param address: the address on the I2C bus :param out: the byte buffer to send Address is a logical address (0x7f max) Most I2C devices require a register address to write into. It should be added as the first (byte)s of the output buffer. """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if not out or len(out) < 1: raise I2cIOError('Nothing to write') i2caddress = (address << 1) & self.HIGH retries = self.RETRY_COUNT while True: try: self._do_prolog(i2caddress) self._do_write(out) return except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry write') finally: self._do_epilog() def exchange(self, address, out, readlen=1): """Send a byte sequence to a remote slave followed with a read request of one or more bytes. This command is useful to tell the slave what data should be read out. :param address: the address on the I2C bus :param out: the byte buffer to send :param readlen: count of bytes to read out. Address is a logical address (0x7f max) """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if not out or not len(out): raise I2cIOError('Nothing to write') if readlen < 1: raise I2cIOError('Nothing to read') if readlen > (I2cController.PAYLOAD_MAX_LENGTH / 3 - 1): raise I2cIOError("Input payload is too large") i2caddress = (address << 1) & self.HIGH retries = self.RETRY_COUNT while True: try: self._do_prolog(i2caddress) self._do_write(out) self._do_prolog(i2caddress | self.BIT0) data = self._do_read(readlen) return data except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry exchange') finally: self._do_epilog() def poll(self, address): """Poll a remote slave, expect ACK or NACK. :param address: the address on the I2C bus :return: True if the slave acknowledged, False otherwise """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 self.log.debug('- poll 0x%x', i2caddress >> 1) try: self._do_prolog(i2caddress) return True except I2cNackError: self.log.info('Not ready') return False finally: self._do_epilog() def flush(self): """Flush the HW FIFOs """ self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers() def _do_prolog(self, i2caddress): self.log.debug(' prolog 0x%x', i2caddress >> 1) cmd = Array('B', self._idle) cmd.extend(self._start) cmd.extend(self._write_byte) cmd.append(i2caddress) cmd.extend(self._clock_low_data_high) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: raise I2cIOError('No answer from FTDI') if ack[0] & self.BIT0: self.log.warning('NACK') raise I2cNackError('NACK from slave') def _do_epilog(self): self.log.debug(' epilog') cmd = Array('B', self._stop) self._ftdi.write_data(cmd) # be sure to purge the MPSSE reply self._ftdi.read_data_bytes(1, 1) def _do_read(self, readlen): self.log.debug('- read %d bytes', readlen) read_not_last = self._read_byte + self._ack + self._clock_low_data_high read_last = self._read_byte + self._nack + self._clock_low_data_high chunk_size = self._rx_size - 2 cmd_size = len(read_last) # limit RX chunk size to the count of I2C packable ommands in the FTDI # TX FIFO (minus one byte for the last 'send immediate' command) tx_count = (self._tx_size - 1) // cmd_size chunk_size = min(tx_count, chunk_size) chunks = [] last_count = 0 last_cmd = None count = readlen while count: block_count = min(count, chunk_size) count -= block_count if last_count != block_count: cmd = Array('B') cmd.extend(read_not_last * (block_count - 1)) cmd.extend(read_last) last_cmd = cmd else: cmd = last_cmd if count <= 0: # only force immediate read out on last chunk cmd.extend(self._immediate) self._ftdi.write_data(cmd) buf = self._ftdi.read_data_bytes(block_count, 4) chunks.append(buf) return b''.join(chunks) def _do_write(self, out): self.log.debug('- write %d bytes: %s', len(out), hexlify(out).decode()) for byte in out: cmd = Array('B', self._write_byte) cmd.append(byte) cmd.extend(self._clock_low_data_high) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: msg = 'No answer from FTDI' self.log.critical(msg) raise I2cIOError(msg) if ack[0] & self.BIT0: msg = 'NACK from slave' self.log.warning(msg) raise I2cNackError(msg)
class SpiController(object): """SPI master. :param silent_clock: deprecated. :param int cs_count: is the number of /CS lines (one per device to drive on the SPI bus) :param boolean turbo: to be documented """ 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') # 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 self._clock_phase = False @property def direction(self): return self._direction def configure(self, url, **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_from_url( # /CS all high url, direction=self._direction, initial=self._cs_bits, **kwargs) self._ftdi.enable_adaptive_clock(False) def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, cs, freq=None, mode=0): """Obtain a SPI port to drive a SPI device selected by cs :param int cs: chip select slot, starting from 0 :param int freq: SPI bus frequency for this slave :param int mode: SPI mode [0,1,3] :rtype: SpiPort """ if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._ports): raise SpiIOError("No such SPI port") if not (0 <= mode <= 3): raise SpiIOError("Invalid SPI mode") if (mode & 0x2) and not self._ftdi.is_H_series: raise SpiIOError("SPI with CPHA high is not supported by " "this FTDI device") if mode == 2: raise SpiIOError("SPI mode 2 has no known workaround with FTDI " "devices") if not self._ports[cs]: freq = min(freq or self.frequency_max, self.frequency_max) hold = freq and (1 + int(1E6 / freq)) self._ports[cs] = SpiPort(self, cs, cs_hold=hold, spi_mode=mode) self._ports[cs].set_frequency(freq) 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, cs_release=None, cpol=False, cpha=False): """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 cs_release: the epilog sequence to send to move the /CS line back to the idle state. May be empty to 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 cpha: # to enable CPHA, we need to use a workaround with FTDI device, # that is enable 3-phase clocking (which is usually dedicated to # I2C support). This mode use use 3 clock period instead of 2, # which implies the FTDI frequency should be fixed to match the # requested one. frequency = (3 * frequency) // 2 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') if cs_release: epilog = Array('B', cs_release) epilog.extend(self._cs_high) else: epilog = None writelen = len(out) if self._clock_phase != cpha: self._ftdi.enable_3phase_clock(cpha) self._clock_phase = cpha if writelen: wcmd = (cpol ^ cpha) and \ Ftdi.WRITE_BYTES_PVE_MSB or Ftdi.WRITE_BYTES_NVE_MSB write_cmd = struct.pack('<BH', wcmd, writelen - 1) cmd.frombytes(write_cmd) cmd.extend(out) if readlen: rcmd = (cpol ^ cpha) and \ Ftdi.READ_BYTES_PVE_MSB or Ftdi.READ_BYTES_NVE_MSB read_cmd = struct.pack('<BH', rcmd, readlen - 1) cmd.frombytes(read_cmd) cmd.extend(self._immediate) if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) # 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: if writelen: if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) 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. :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()
class I2cController: """I2c master. An I2c master should be instanciated only once for each FTDI port that supports MPSSE (one or two ports, depending on the FTDI device). Once configured, :py:func:`get_port` should be invoked to obtain an I2c port for each I2c slave to drive. I2cport should handle all I/O requests for its associated HW slave. It is not recommended to use I2cController :py:func:`read`, :py:func:`write` or :py:func:`exchange` directly. """ LOW = 0x00 HIGH = 0xff BIT0 = 0x01 IDLE = HIGH SCL_BIT = 0x01 SDA_O_BIT = 0x02 SDA_I_BIT = 0x04 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max HIGHEST_I2C_ADDRESS = 0x78 DEFAULT_BUS_FREQUENCY = 100000.0 HIGH_BUS_FREQUENCY = 400000.0 RETRY_COUNT = 3 I2C_100K = I2CTimings(4.0E-6, 4.7E-6, 4.0E-6, 4.7E-6) I2C_400K = I2CTimings(0.6E-6, 0.6E-6, 0.6E-6, 1.3E-6) I2C_1M = I2CTimings(0.26E-6, 0.26E-6, 0.26E-6, 0.5E-6) def __init__(self): self._ftdi = Ftdi() self.log = getLogger('pyftdi.i2c') self._slaves = {} self._retry_count = self.RETRY_COUNT self._frequency = 0.0 self._direction = self.SCL_BIT | self.SDA_O_BIT self._immediate = (Ftdi.SEND_IMMEDIATE,) self._idle = (Ftdi.SET_BITS_LOW, self.IDLE, self._direction) self._data_lo = (Ftdi.SET_BITS_LOW, self.IDLE & ~self.SDA_O_BIT, self._direction) self._clk_lo_data_lo = (Ftdi.SET_BITS_LOW, self.IDLE & ~(self.SDA_O_BIT | self.SCL_BIT), self._direction) self._clk_lo_data_hi = (Ftdi.SET_BITS_LOW, self.IDLE & ~self.SCL_BIT, self._direction) self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0) self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0) self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0) self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH) self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW) self._start = None self._stop = None self._ck_delay = 1 self._tristate = None self._tx_size = 1 self._rx_size = 1 def _compute_delay_cycles(self, value): # approx ceiling without relying on math module # the bit delay is far from being precisely known anyway bit_delay = self._ftdi.mpsse_bit_delay return max(1, int((value + bit_delay) / bit_delay)) def set_retry_count(self, count): """Change the default retry count when a communication error occurs, before bailing out. :param int count: count of retries """ if not isinstance(count, int) or not 0 < count <= 16: raise ValueError('Invalid retry count') self._retry_count = count def configure(self, url, **kwargs): """Configure the FTDI interface as a I2c master. :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1' :param kwargs: options to configure the I2C bus Accepted options: * ``frequency`` the I2C bus frequency in Hz """ for k in ('direction', 'initial'): if k in kwargs: del kwargs[k] if 'frequency' in kwargs: frequency = kwargs['frequency'] del kwargs['frequency'] else: frequency = self.DEFAULT_BUS_FREQUENCY # Fix frequency for 3-phase clock if frequency <= 100E3: timings = self.I2C_100K elif frequency <= 400E3: timings = self.I2C_100K else: timings = self.I2C_100K ck_hd_sta = self._compute_delay_cycles(timings.t_hd_sta) ck_su_sta = self._compute_delay_cycles(timings.t_su_sta) ck_su_sto = self._compute_delay_cycles(timings.t_su_sto) ck_buf = self._compute_delay_cycles(timings.t_buf) ck_idle = max(ck_su_sta, ck_buf) self._ck_delay = ck_buf self._start = (self._data_lo * ck_hd_sta + self._clk_lo_data_lo * ck_hd_sta) self._stop = (self._clk_lo_data_lo * ck_hd_sta + self._data_lo*ck_su_sto + self._idle*ck_idle) frequency = (3.0*frequency)/2.0 self._frequency = self._ftdi.open_mpsse_from_url( url, direction=self._direction, initial=self.IDLE, frequency=frequency, **kwargs) self._tx_size, self._rx_size = self._ftdi.fifo_sizes self._ftdi.enable_adaptive_clock(False) self._ftdi.enable_3phase_clock(True) try: self._ftdi.enable_drivezero_mode(self.SCL_BIT | self.SDA_O_BIT | self.SDA_I_BIT) except FtdiFeatureError: self._tristate = (Ftdi.SET_BITS_LOW, self.LOW, self.SCL_BIT) def terminate(self): """Close the FTDI interface. """ if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, address): """Obtain an I2cPort to drive an I2c slave. :param int address: the address on the I2C bus :return: an I2cPort instance :rtype: :py:class:`I2cPort` """ if not self._ftdi: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address not in self._slaves: self._slaves[address] = I2cPort(self, address) return self._slaves[address] @property def configured(self): """Test whether the device has been properly configured. :return: True if configured """ return bool(self._ftdi) and bool(self._start) @classmethod def validate_address(cls, address): """Assert an I2C slave address is in the supported range. None is a special bypass address. :param address: the address on the I2C bus :type address: int or None :raise I2cIOError: if the I2C slave address is not supported """ if address is None: return if address > cls.HIGHEST_I2C_ADDRESS: raise I2cIOError("No such I2c slave: 0x%02x" % address) @property def frequency_max(self): """Provides the maximum I2c clock frequency. """ return self._ftdi.frequency_max @property def frequency(self): """Provides the current I2c clock frequency. :return: the I2C bus clock :rtype: float """ return self._frequency def read(self, address, readlen=1, relax=True): """Read one or more bytes from a remote slave :param address: the address on the I2C bus, or None to discard start :type address: int or None :param int readlen: count of bytes to read out. :param bool relax: not used :return: read bytes :rtype: array :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) Most I2C devices require a register address to read out check out the exchange() method. """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 retries = self._retry_count do_epilog = True while True: try: self._do_prolog(i2caddress) data = self._do_read(readlen) do_epilog = relax return data except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry read') finally: if do_epilog: self._do_epilog() def write(self, address, out, relax=True): """Write one or more bytes to a remote slave :param address: the address on the I2C bus, or None to discard start :type address: int or None :param out: the byte buffer to send :type out: array or bytes or list(int) :param bool relax: whether to relax the bus (emit STOP) or not :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) Most I2C devices require a register address to write into. It should be added as the first (byte)s of the output buffer. """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH retries = self._retry_count do_epilog = True while True: try: self._do_prolog(i2caddress) self._do_write(out) do_epilog = relax return except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry write') finally: if do_epilog: self._do_epilog() def exchange(self, address, out, readlen=0, relax=True): """Send a byte sequence to a remote slave followed with a read request of one or more bytes. This command is useful to tell the slave what data should be read out. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param out: the byte buffer to send :type out: array or bytes or list(int) :param int readlen: count of bytes to read out. :param bool relax: whether to relax the bus (emit STOP) or not :return: read bytes :rtype: array :raise I2cIOError: if device is not configured or input parameters are invalid Address is a logical slave address (0x7f max) """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if readlen < 1: raise I2cIOError('Nothing to read') if readlen > (I2cController.PAYLOAD_MAX_LENGTH/3-1): raise I2cIOError("Input payload is too large") if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH retries = self._retry_count do_epilog = True while True: try: self._do_prolog(i2caddress) self._do_write(out) self._do_prolog(i2caddress | self.BIT0) if readlen: data = self._do_read(readlen) do_epilog = relax return data except I2cNackError: retries -= 1 if not retries: raise self.log.warning('Retry exchange') finally: if do_epilog: self._do_epilog() def poll(self, address, write=False, relax=True): """Poll a remote slave, expect ACK or NACK. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param bool write: poll in write mode (vs. read) :param bool relax: whether to relax the bus (emit STOP) or not :return: True if the slave acknowledged, False otherwise :rtype: bool """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH if not write: i2caddress |= self.BIT0 do_epilog = True try: self._do_prolog(i2caddress) do_epilog = relax return True except I2cNackError: self.log.info('Not ready') return False finally: if do_epilog: self._do_epilog() def poll_cond(self, address, fmt, mask, value, count, relax=True): """Poll a remove slave, watching for condition to satisfy. On each poll cycle, a repeated start condition is emitted, without releasing the I2C bus, and an ACK is returned to the slave. If relax is set, this method releases the I2C bus however it leaves. :param address: the address on the I2C bus, or None to discard start :type address: int or None :param str fmt: struct format for poll register :param int mask: binary mask to apply on the condition register before testing for the value :param int value: value to test the masked condition register against. Condition is satisfied when register & mask == value :param int count: maximum poll count before raising a timeout :param bool relax: whether to relax the bus (emit STOP) or not :return: the polled register value, or None if poll failed :rtype: array or None """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self.validate_address(address) if address is None: i2caddress = None else: i2caddress = (address << 1) & self.HIGH i2caddress |= self.BIT0 do_epilog = True try: retry = 0 while retry < count: retry += 1 size = scalc(fmt) self._do_prolog(i2caddress) data = self._do_read(size) self.log.debug("Poll data: %s", hexlify(data).decode()) cond, = sunpack(fmt, data) if (cond & mask) == value: self.log.debug('Poll condition matched') break else: data = None self.log.debug('Poll condition not fulfilled: %x/%x', cond & mask, value) do_epilog = relax if not data: self.log.warning('Poll condition failed') return data except I2cNackError: self.log.info('Not ready') return None finally: if do_epilog: self._do_epilog() def flush(self): """Flush the HW FIFOs """ if not self.configured: raise I2cIOError("FTDI controller not initialized") self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers() def _do_prolog(self, i2caddress): if i2caddress is None: return self.log.debug(' prolog 0x%x', i2caddress >> 1) cmd = array('B', self._idle) cmd.extend(self._start) cmd.extend(self._write_byte) cmd.append(i2caddress) if self._tristate: cmd.extend(self._tristate) cmd.extend(self._read_bit) cmd.extend(self._clk_lo_data_hi) else: cmd.extend(self._clk_lo_data_hi) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: raise I2cIOError('No answer from FTDI') if ack[0] & self.BIT0: self.log.warning('NACK') raise I2cNackError('NACK from slave') def _do_epilog(self): self.log.debug(' epilog') cmd = array('B', self._stop) self._ftdi.write_data(cmd) # be sure to purge the MPSSE reply self._ftdi.read_data_bytes(1, 1) def _do_read(self, readlen): self.log.debug('- read %d byte(s)', readlen) if not readlen: # force a real read request on device, but discard any result cmd = array('B') cmd.extend(self._immediate) self._ftdi.write_data(cmd) self._ftdi.read_data_bytes(0, 4) return array('B') if self._tristate: read_byte = self._tristate + \ self._read_byte + \ self._clk_lo_data_hi read_not_last = \ read_byte + self._ack + self._clk_lo_data_lo * self._ck_delay read_last = \ read_byte + self._nack + self._clk_lo_data_hi * self._ck_delay else: read_not_last = \ self._read_byte + self._ack + \ self._clk_lo_data_hi * self._ck_delay read_last = \ self._read_byte + self._nack + \ self._clk_lo_data_hi * self._ck_delay # maximum RX size to fit in FTDI FIFO, minus 2 status bytes chunk_size = self._rx_size-2 cmd_size = len(read_last) # limit RX chunk size to the count of I2C packable commands in the FTDI # TX FIFO (minus one byte for the last 'send immediate' command) tx_count = (self._tx_size-1) // cmd_size chunk_size = min(tx_count, chunk_size) chunks = [] cmd = None rem = readlen while rem: if rem > chunk_size: if not cmd: cmd = array('B') cmd.extend(read_not_last * chunk_size) size = chunk_size else: cmd = array('B') cmd.extend(read_not_last * (rem-1)) cmd.extend(read_last) cmd.extend(self._immediate) size = rem self._ftdi.write_data(cmd) buf = self._ftdi.read_data_bytes(size, 4) self.log.debug('- read %d byte(s): %s', len(buf), hexlify(buf).decode()) chunks.append(buf) rem -= size return array('B', b''.join(chunks)) def _do_write(self, out): if not isinstance(out, array): out = array('B', out) if not out: return self.log.debug('- write %d byte(s): %s', len(out), hexlify(out).decode()) for byte in out: cmd = array('B', self._write_byte) cmd.append(byte) if self._tristate: cmd.extend(self._tristate) cmd.extend(self._read_bit) cmd.extend(self._clk_lo_data_hi) else: cmd.extend(self._clk_lo_data_hi) cmd.extend(self._read_bit) cmd.extend(self._immediate) self._ftdi.write_data(cmd) ack = self._ftdi.read_data_bytes(1, 4) if not ack: msg = 'No answer from FTDI' self.log.critical(msg) raise I2cIOError(msg) if ack[0] & self.BIT0: msg = 'NACK from slave' self.log.warning(msg) raise I2cNackError(msg)
class FifoController (object): SYNC_FIFO_INTERFACE = 1 SYNC_FIFO_INDEX = 0 def __init__(self, idVendor, idProduct): self.vendor = idVendor self.product = idProduct self.f = Ftdi() def set_sync_fifo(self, frequency=30.0E6, latency=2): """Configure the interface for synchronous FIFO mode""" # Open an FTDI interface # self.f.open(self.vendor, self.product, self.SYNC_FIFO_INTERFACE, self.SYNC_FIFO_INDEX, None, None) self.f.open(self.vendor, self.product, 0) # Drain input buffer self.f.purge_buffers() # Reset # Enable MPSSE mode self.f.set_bitmode(0x00, Ftdi.BITMODE_SYNCFF) # Configure clock frequency = self.f._set_frequency(frequency) # Set latency timer self.f.set_latency_timer(latency) # Set chunk size self.f.write_data_set_chunksize(0x10000) self.f.read_data_set_chunksize(0x10000) self.f.set_flowctrl('hw') # Configure I/O # self.write_data(Array('B', [Ftdi.SET_BITS_LOW, 0x00, 0x00])) # Disable loopback # self.write_data(Array('B', [Ftdi.LOOPBACK_END])) # self.validate_mpsse() # Drain input buffer self.f.purge_buffers() # Return the actual frequency return frequency def set_async_fifo(self, frequency=6.0E6, latency=2): """Configure the interface for synchronous FIFO mode""" # Open an FTDI interface self.f.open(self.vendor, self.product, self.SYNC_FIFO_INTERFACE, self.SYNC_FIFO_INDEX, None, None) # Set latency timer self.f.set_latency_timer(latency) # Set chunk size self.f.write_data_set_chunksize(512) self.f.read_data_set_chunksize(512) # Drain input buffer self.f.purge_buffers() # Enable MPSSE mode self.f.set_bitmode(0x00, Ftdi.BITMODE_BITBANG) # Configure clock frequency = self.f._set_frequency(frequency) # Configure I/O # self.write_data(Array('B', [Ftdi.SET_BITS_LOW, 0x00, 0x00])) # Disable loopback # self.write_data(Array('B', [Ftdi.LOOPBACK_END])) # self.validate_mpsse() # Drain input buffer self.f.purge_buffers() # Return the actual frequency return frequency
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 SpiController: """SPI master. :param silent_clock: deprecated. :param int cs_count: is the number of /CS lines (one per device to drive on the SPI bus) :param boolean turbo: to be documented """ SCK_BIT = 0x01 DO_BIT = 0x02 DI_BIT = 0x04 CS_BIT = 0x08 SPI_BITS = DI_BIT | DO_BIT | SCK_BIT PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max def __init__(self, silent_clock=False, cs_count=1, turbo=True): self.log = getLogger('pyftdi.spi.ctrl') self._ftdi = Ftdi() self._lock = Lock() self._gpio_port = None self._gpio_dir = 0 self._gpio_mask = 0 self._gpio_low = 0 self._wide_port = False self._cs_count = cs_count self._turbo = turbo self._immediate = bytes((Ftdi.SEND_IMMEDIATE, )) self._frequency = 0.0 self._clock_phase = False def configure(self, url, **kwargs): """Configure the FTDI interface as a SPI master :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1' :param kwargs: options to configure the SPI bus Accepted options: * ``frequency`` the SPI bus frequency in Hz. Note that each slave may reconfigure the SPI bus with a specialized frequency. * ``cs_count`` count of chip select signals dedicated to select SPI slave devices, starting from A*BUS3 pin * ``turbo`` whether to enable or disable turbo mode * ``debug`` for extra debug output """ # it is better to specify CS and turbo in configure, but the older # API where these parameters are specified at instanciation has been # preserved self._cs_count = int(kwargs.get('cs_count', self._cs_count)) if not (1 <= self._cs_count <= 5): raise ValueError('Unsupported CS line count: %d' % self._cs_count) self._turbo = bool(kwargs.get('turbo', self._turbo)) for k in ('direction', 'initial', 'cs_count', 'turbo'): if k in kwargs: del kwargs[k] 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)) self._spi_ports = [None] * self._cs_count self._spi_dir = (self._cs_bits | SpiController.DO_BIT | SpiController.SCK_BIT) self._spi_mask = self._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) self._ftdi.enable_adaptive_clock(False) self._wide_port = self._ftdi.has_wide_port def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._frequency = 0.0 def get_port(self, cs, freq=None, mode=0): """Obtain a SPI port to drive a SPI device selected by Chip Select. :note: SPI mode 1 and 3 are not officially 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, 2, 3] :rtype: SpiPort """ with self._lock: if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._spi_ports): if cs < 5: # increase cs_count (up to 4) to reserve more /CS channels raise SpiIOError("/CS pin %d not reserved for SPI" % cs) raise SpiIOError("No such SPI port: %d" % cs) if not (0 <= mode <= 3): raise SpiIOError("Invalid SPI mode") if (mode & 0x2) and not self._ftdi.is_H_series: raise SpiIOError("SPI with CPHA high is not supported by " "this FTDI device") if not self._spi_ports[cs]: freq = min(freq or self._frequency, self.frequency_max) hold = freq and (1 + int(1E6 / freq)) self._spi_ports[cs] = SpiPort(self, cs, cs_hold=hold, spi_mode=mode) self._spi_ports[cs].set_frequency(freq) self._flush() return self._spi_ports[cs] def get_gpio(self): with self._lock: if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if not self._gpio_port: self._gpio_port = SpiGpioPort(self) return self._gpio_port @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 @property def direction(self): """Provide the FTDI GPIO direction""" return self._spi_dir | self._gpio_dir @property def gpio_pins(self): """Report the configured GPIOs as a bitfield""" with self._lock: return self._gpio_mask @property def gpio_all_pins(self): """Report the addressable GPIOs as a bitfield""" mask = (1 << self.width) - 1 with self._lock: return mask & ~self._spi_mask @property def width(self): """Report the FTDI count of addressable pins. :return: the count of IO pins (including SPI ones). """ return 16 if self._wide_port else 8 def exchange(self, frequency, out, readlen, cs_prolog=None, cs_epilog=None, cpol=False, cpha=False, duplex=False): if duplex: if readlen > len(out): tmp = bytearray(out) tmp.extend([0] * (readlen - len(out))) out = tmp elif not readlen: readlen = len(out) with self._lock: if duplex: data = self._exchange_full_duplex(frequency, out, cs_prolog, cs_epilog, cpol, cpha) return data[:readlen] else: return self._exchange_half_duplex(frequency, out, readlen, cs_prolog, cs_epilog, cpol, cpha) def read_gpio(self, with_output=False): """Read GPIO port :param bool with_output: set to unmask output pins :return: the GPIO port pins as a bitfield :rtype: int """ with self._lock: data = self._read_raw(self._wide_port) value = data & self._gpio_mask if not with_output: value &= ~self._gpio_dir return value def write_gpio(self, value): """Write GPIO port :param int value: the GPIO port pins as a bitfield """ with self._lock: if (value & self._gpio_dir) != value: raise SpiIOError('No such GPO pins: %04x/%04x' % (self._gpio_dir, value)) # perform read-modify-write use_high = self._wide_port and (self.direction & 0xff00) data = self._read_raw(use_high) data &= ~self._gpio_mask data |= value self._write_raw(data, use_high) self._gpio_low = data & 0xFF & ~self._spi_mask def set_gpio_direction(self, pins, direction): """Change the direction of the GPIO pins :param int pins: which GPIO pins should be reconfigured :param int direction: direction bitfield (on for output) """ with self._lock: if pins & self._spi_mask: raise SpiIOError('Cannot access SPI pins as GPIO') gpio_width = self._wide_port and 16 or 8 gpio_mask = (1 << gpio_width) - 1 gpio_mask &= ~self._spi_mask if (pins & gpio_mask) != pins: raise SpiIOError('No such GPIO pin(s)') self._gpio_dir &= ~pins self._gpio_dir |= (pins & direction) self._gpio_mask = gpio_mask & pins def _read_raw(self, read_high): if read_high: cmd = bytearray( [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE]) fmt = '<H' else: cmd = bytearray([Ftdi.GET_BITS_LOW, Ftdi.SEND_IMMEDIATE]) fmt = 'B' self._ftdi.write_data(cmd) size = scalc(fmt) data = self._ftdi.read_data_bytes(size, 4) if len(data) != size: raise SpiIOError('Cannot read GPIO') value, = sunpack(fmt, data) return value def _write_raw(self, data, write_high): direction = self.direction low_data = data & 0xFF low_dir = direction & 0xFF if write_high: high_data = (data >> 8) & 0xFF high_dir = (direction >> 8) & 0xFF cmd = bytearray([ Ftdi.SET_BITS_LOW, low_data, low_dir, Ftdi.SET_BITS_HIGH, high_data, high_dir ]) else: cmd = bytearray([Ftdi.SET_BITS_LOW, low_data, low_dir]) self._ftdi.write_data(cmd) def _exchange_half_duplex(self, frequency, out, readlen, cs_prolog, cs_epilog, cpol, cpha): 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 cpha: # to enable CPHA, we need to use a workaround with FTDI device, # that is enable 3-phase clocking (which is usually dedicated to # I2C support). This mode use use 3 clock period instead of 2, # which implies the FTDI frequency should be fixed to match the # requested one. frequency = (3 * frequency) // 2 if self._frequency != frequency: self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort), # to avoid setting unavailable values on each call. self._frequency = frequency direction = self.direction & 0xFF # low bits only cmd = bytearray() for ctrl in cs_prolog or []: ctrl &= self._spi_mask ctrl |= self._gpio_low cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction)) epilog = bytearray() if cs_epilog: for ctrl in cs_epilog: ctrl &= self._spi_mask 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, direction ] if not self._turbo: cs_high.append(Ftdi.SEND_IMMEDIATE) epilog.extend(cs_high) writelen = len(out) if self._clock_phase != cpha: self._ftdi.enable_3phase_clock(cpha) self._clock_phase = cpha if writelen: wcmd = (Ftdi.WRITE_BYTES_NVE_MSB if not cpol else Ftdi.WRITE_BYTES_PVE_MSB) write_cmd = spack('<BH', wcmd, writelen - 1) cmd.append(write_cmd) cmd.extend(out) if readlen: rcmd = (Ftdi.READ_BYTES_NVE_MSB if not cpol else Ftdi.READ_BYTES_PVE_MSB) read_cmd = spack('<BH', rcmd, readlen - 1) cmd.append(read_cmd) cmd.extend(self._immediate) if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) # 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: if writelen: if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) data = bytearray() return data def _exchange_full_duplex(self, frequency, out, cs_prolog, cs_epilog, cpol, cpha): 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 cpha: # to enable CPHA, we need to use a workaround with FTDI device, # that is enable 3-phase clocking (which is usually dedicated to # I2C support). This mode use use 3 clock period instead of 2, # which implies the FTDI frequency should be fixed to match the # requested one. frequency = (3 * frequency) // 2 if self._frequency != frequency: self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort), # to avoid setting unavailable values on each call. self._frequency = frequency direction = self.direction & 0xFF # low bits only cmd = bytearray() for ctrl in cs_prolog or []: ctrl &= self._spi_mask ctrl |= self._gpio_low cmd.extend((Ftdi.SET_BITS_LOW, ctrl, direction)) epilog = bytearray() if cs_epilog: for ctrl in cs_epilog: ctrl &= self._spi_mask 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, direction ] if not self._turbo: cs_high.append(Ftdi.SEND_IMMEDIATE) epilog.extend(cs_high) writelen = len(out) if self._clock_phase != cpha: self._ftdi.enable_3phase_clock(cpha) self._clock_phase = cpha wcmd = (Ftdi.RW_BYTES_PVE_NVE_MSB if not cpol else Ftdi.RW_BYTES_NVE_PVE_MSB) write_cmd = spack('<BH', wcmd, writelen - 1) cmd.extend(write_cmd) cmd.extend(out) cmd.extend(self._immediate) if self._turbo: if epilog: cmd.extend(epilog) self._ftdi.write_data(cmd) else: self._ftdi.write_data(cmd) if epilog: self._ftdi.write_data(epilog) # 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(len(out), 4) return data def _flush(self): """Flush the HW FIFOs""" self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers()
class JtagController: """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 write and read FIFO byte lengths FTDI_WRITE_PIPE_LEN = 0 FTDI_READ_PIPE_LEN = 0 FTDI_WR_BUFFER_MAX_LEN = 0 # maximum byte length of write data to FTDI FTDI_RD_BUFFER_MAX_LEN = 0 # maximum byte length of read data from FTDI # Private API def __init__(self, trst=False, frequency=3.0E6, usb_read_timeout=5000, usb_write_timeout=5000, debug=False): """ trst uses the nTRST optional JTAG line to hard-reset the TAP controller """ self._ftdi = Ftdi() self._lock = Lock() self._ftdi.timeouts = (usb_read_timeout, usb_write_timeout) self._trst = trst self._frequency = frequency self._ftdi_opened = False self._immediate = bytes((Ftdi.SEND_IMMEDIATE,)) self.direction = (JtagController.TCK_BIT | JtagController.TDI_BIT | JtagController.TMS_BIT | (self._trst and JtagController.TRST_BIT or 0)) # all JTAG outputs are low - Additionally, setting this upper # bits as outputs and then having a specific initial value # gets the PYNQ-Z1 board working self.direction |= 0x90 self.initialout = 0xe0 #@@@#self.initialout = self.direction self._last = None # Last deferred TDO bit self._write_buff = array('B') self._debug = debug # Public API def configure(self, url): """Configure the FTDI interface as a JTAG controller""" print('Configure with Freq: {}'.format(self._frequency)) if (self._debug): FtdiLogger.log.addHandler(logging.StreamHandler(stdout)) #@@@#level = environ.get('FTDI_LOGLEVEL', 'info').upper() level = 'DEBUG' try: loglevel = getattr(logging, level) except AttributeError: raise ValueError('Invalid log level: %s', level) FtdiLogger.set_level(loglevel) print('Opening MPSSE: direction ("1" is out): 0x{:02x} Initial Output: 0x{:02x}'.format(self.direction, self.initialout)) with self._lock: self._ftdi.open_mpsse_from_url( url, direction=self.direction, frequency=self._frequency, debug=self._debug, latency=12) # @@@@@ self._ftdi_opened = True # FTDI requires to initialize all GPIOs before MPSSE kicks in cmd = array('B', (Ftdi.SET_BITS_LOW, self.initialout, self.direction)) self._ftdi.write_data(cmd) # Read the FIFO sizes and save them (self.FTDI_WRITE_PIPE_LEN, self.FTDI_READ_PIPE_LEN) = self._ftdi.fifo_sizes # Set the FTDI read/write chunksizes to be the same as the FTDI FIFO lengths self._ftdi.write_data_set_chunksize(self.FTDI_WRITE_PIPE_LEN) self._ftdi.read_data_set_chunksize(self.FTDI_READ_PIPE_LEN) # "-3" on self.FTDI_WRITE_PIPE_LEN accounts for the command # byte plus 2 length bytes which must also fit in the WRITE # FIFO. self.FTDI_WR_BUFFER_MAX_LEN = self.FTDI_WRITE_PIPE_LEN-3 # "-2" on self.FTDI_READ_PIPE_LEN accounts for the two status # bytes, even though they are not returned by the FTDI # read_bytes function. They are still taking up space in the # FTDI's READ FIFO. self.FTDI_RD_BUFFER_MAX_LEN = self.FTDI_READ_PIPE_LEN-2 def close(self): if self._ftdi_opened: self._ftdi.close() self._ftdi_opened = False def set_frequency(self, frequency): # Configure clock self._frequency = frequency print('FTDI USB Timeouts: read={} write={}'.format(self._ftdi.timeouts[0], self._ftdi.timeouts[1])) print('FTDI USB Fifo Len: read={} write={}'.format(self.FTDI_READ_PIPE_LEN, self.FTDI_WRITE_PIPE_LEN)) return self._ftdi.set_frequency(self._frequency) @property def max_byte_sizes(self): """Return the 3-tuple of maximum bytes from (TMS, TDI (output) and TDO (input)) :return: 3-tuple of write, read buffer sizes in bytes :rtype: tuple(int, int) """ ## Make the TMS buffer size the same as TDI which is the WRITE ## Buffer size. Since only send the TMS bits that are '1', ## essentially, this size doe snot really matter since Python ## will handle it. return (self.FTDI_WR_BUFFER_MAX_LEN, self.FTDI_WR_BUFFER_MAX_LEN, self.FTDI_RD_BUFFER_MAX_LEN) def purge(self): self._ftdi.purge_buffers() ## Write out the data and clear the internal buffer for more data def sync(self): if not self._ftdi: raise JtagError("FTDI controller terminated") if self._write_buff: try: with self._lock: self._write_buff.extend(self._immediate) self._ftdi.write_data(self._write_buff) self._write_buff = array('B') except usb.core.USBError: pass # FTDI should be catching the error # Concatenate cmd bytes. If cmd > Write FIFO size, write data and # clear cmd array so more bytes can be added (which will need to # be sent with a sync() outside of this function) 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) >= self.FTDI_WRITE_PIPE_LEN: self.sync() self._write_buff.extend(cmd) def write_tms_tdi_read_tdo(self, tms, tdi): """Write out TMS bits while holding TDI constant and reading back in TDO""" if not (isinstance(tms, BitStream) or isinstance(tms, BitArray)): raise JtagError('Expect a BitStream or BitArray') length = len(tms) if not (0 < length < 8): raise JtagError('Invalid TMS length') tms.reverse() # must reverse bits since only lsb write seems to be supported tms.prepend(8-len(tms)) # prepend 0's to be 8 bits long # left-most bit will be for TDI if isinstance(tdi, BitStream) or isinstance(tdi, BitArray): tms[0] = tdi[0] elif isinstance(tdi, bool): tms[0] = tdi else: raise JtagError('Incorrect type for tdi - must be BitStream, BitArray or bool') # apply the last TDI 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 ## Send the byte to the FTDI cmd = array('B', (Ftdi.RW_BITS_TMS_PVE_NVE, length-1, tms.uint)) self._stack_cmd(cmd) self.sync() ## Read the response from FTDI data = self._ftdi.read_data_bytes(1, 4) if (len(data) != 1): raise JtagError('Not all data read! Expected {} bytes but only read {} bytes'.format(1,len(data))) tdo = BitArray(data) # FTDI handles returned LSB bit data by putting the first bit # in bit 7 and shifting to the right with every new bit. So # the first bit clocked will be in the lowest bit number, but # which bit number it will be in depends on how many bits # clocked. [It is kinda stupid, if you ask me. I think the bit # order should be the same as tehe bit written, but they # aren't.] tdo = tdo[:length] # return to bitstring bit ordering with left-most bit the lsb tdo.reverse() return tdo def write_tdi_read_tdo(self, out, use_last=False): """ Output a sequence of bits to TDI while reading the TDO input bits. Automatically break any byte writes based on adapter FIFO sizes. """ if not (isinstance(out, BitStream) or isinstance(out, BitArray)): raise JtagError('Expect a BitStream or BitArray') ## @@@ Not used at the moment #if use_last: # #(out, self._last) = (out[:-1], bool(out[-1])) # self._last = out[-1] byte_count = out.len//8 pos = 8*byte_count bit_count = out.len-pos # Separate into BYTE and BIT commands tdo = BitArray() if byte_count: ## Since TDO bit length will be equal to TDI bit length, ## set max_rw_bits to the minimum of the TDI or TDO bit ## sizes. max_rw_bits = min(self.max_byte_sizes[1:3])*8 # Start head and tail at the beginning bit index head = 0 tail = 0 #@@@while(tdo.len//8 < byte_count): while(head < pos): # Set tail to either be the maximum bytes to # read/write or the final bit, pos, whichever is # smaller. These are bit indexes. tail = min((head+max_rw_bits),pos) tdo += self._write_read_bytes(out[head:tail]) head = tail if bit_count: # Do not have to deal with bit length here because already know bit_count is b/w 0 and 7 tdo += self._write_read_bits(out[pos:]) return tdo def _write_read_bits(self, out): """Output bits on TDI while reading TDO bits in""" # a bitstring.BitStream() has first bit in left-most array position (ie. msb first) length = out.len byte = BitArray(out) # copy out so can modify it byte.append(8-length) # pad 0's to be 8 bits long # Check number of bits. if not (0 < length <= 8): raise JtagError('Wrong number of bits: {}'.format(length)) #@@@#print('out: ', out, 'length: ', length, 'byte: ', byte, 'byte as uint: ', byte.uint) # length of 0 for 1 bit, length of 7 for 8 bits, etc. cmd = array('B', (Ftdi.RW_BITS_PVE_NVE_MSB, length-1, byte.uint)) #@@@#print('cmd: ', cmd) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(1, 4) if (len(data) != 1): raise JtagError('Not all data read! Expected {} bytes but only read {} bytes'.format(1,len(data))) tdo = BitArray(data) # Only pass back the same number of bits as clocked # out. Although MSB, a MSB bit read left shifts the bits # starting at bit 0. So it is right shifted MSB from reads by # left shifted MSB on bit writes. (Go Fugure?) tdo = tdo[8-length:] return tdo def _write_read_bytes(self, out): """Output bytes on TDI while reading TDO bits in""" # a bitstring.BitStream() has first bit in left-most array position (ie. msb first) bytes_ = out.bytes olen = len(bytes_) #print("WRITE {} BYTES: 0x{}".format(olen, bytes_.hex())) #print("WRITE {} BYTES".format(olen)) # Check number of bits. # # If this function was a write-only function, could smartly # handle writing more bytes than will fit into the write FIFO # by sending them in waves. However, this function writes and # reads the same number of bytes. So the byte length of the # out vector must be less than the size of both the Write and # Read FIFO. # # What's more, when writing, also need to write a command byte # and two length bytes. So account for them as well so that # the entire write will fit in the Write FIFO. This makes it # so that there is a USB read for every USB write. May not be # completely necessary but seems to be a little more # efficient. # if olen > self.FTDI_RD_BUFFER_MAX_LEN: raise JtagError("Byte length of Read data ({}) is larger than Read buffer ({})".format(olen, self.FTDI_RD_BUFFER_MAX_LEN)) if olen > self.FTDI_WR_BUFFER_MAX_LEN: raise JtagError("Byte length of Write data ({}) is larger then Write Buffer ({})".format(olen, self.FTDI_WR_BUFFER_MAX_LEN)) # The byte length to pass to the MPSSE is -1 the actual length, LSB first cmd = array('B', (Ftdi.RW_BYTES_PVE_NVE_MSB, (olen-1) & 0xff, ((olen-1) >> 8) & 0xff)) cmd.extend(bytes_) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(olen, 4) if (len(data) != olen): raise JtagError('Not all data read! Expected {} bytes but only read {} bytes'.format(olen,len(data))) #print("READ {} BYTES: 0x{}".format(len(data), data.tobytes().hex())) #print("READ {} BYTES".format(len(data))) return BitArray(data)