class EpdFtdiPort: """ """ DC_PIN = 1 << 5 RESET_PIN = 1 << 6 BUSY_PIN = 1 << 7 I_PINS = BUSY_PIN O_PINS = DC_PIN | RESET_PIN IO_PINS = I_PINS | O_PINS def __init__(self, debug=False): self._debug = debug self._spi = SpiController(cs_count=2) self._spi_port = None self._io_port = None self._io = 0 def open(self, url=None): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', url or 'ftdi:///1') self._spi.configure(url, debug=self._debug) self._spi_port = self._spi.get_port(0, freq=10E6, mode=0) self._io_port = self._spi.get_gpio() self._io_port.set_direction(self.IO_PINS, self.O_PINS) def close(self): """Close the SPI connection""" self._spi.terminate() def reset(self): self._io = self.RESET_PIN self._io_port.write(self._io) sleep(0.2) self._io = 0 self._io_port.write(self._io) sleep(0.2) self._io = self.RESET_PIN self._io_port.write(self._io) sleep(0.2) def write_command(self, cmd): if isinstance(cmd, int): data = bytes([cmd]) self._io &= ~self.DC_PIN self._io_port.write(self._io) self._spi_port.write(data) def write_data(self, data): if isinstance(data, int): data = bytes([data]) self._io |= self.DC_PIN self._io_port.write(self._io) self._spi_port.write(data) def wait_ready(self): start = now() while self._io_port.read() & self.BUSY_PIN: sleep(0.05) return now() - start
def initialize(): global spi global gpio # Configure controller with one CS ctrl = SpiController(cs_count=1, turbo=True) # ctrl.configure('ftdi:///?') # Use this if you're not sure which device to use # Windows users: make sure you've loaded libusb-win32 using Zadig try: ctrl.configure(config.FTDI_URL) except: print("Can't configure FTDI. Possible reasons:") print( " 1. As a current Linux user you don't have an access to the device.\n" " Solution: https://eblot.github.io/pyftdi/installation.html\n" " 2. You use the wrong FTDI URL. Replace FTDI_URL in config.py with one of the following:\n" ) ctrl.configure('ftdi:///?') sys.exit(1) # Get SPI slave # CS0, 10MHz, Mode 0 (CLK is low by default, latch on the rising edge) spi = ctrl.get_port(cs=0, freq=10E6, mode=0) # Get GPIO gpio = ctrl.get_gpio() gpio.set_direction(0x10, 0x10)
class SPI: MSB = 0 def __init__(self): from pyftdi.spi import SpiController self._spi = SpiController(cs_count=1) self._spi.configure('ftdi://ftdi:ft232h/1') self._port = self._spi.get_port(0) print(self._port) dir(self._port) self._port.set_frequency(100000) self._port._cpol = 0 self._port._cpha = 0 # Change GPIO controller to SPI Pin.ft232h_gpio = self._spi.get_gpio() def init(self, baudrate=100000, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None): self._port.set_frequency(baudrate) # FTDI device can only support mode 0 and mode 2 # due to the limitation of MPSSE engine. # This means CPHA must = 0 self._port._cpol = polarity if phase != 0: raise ValueError("Only SPI phase 0 is supported by FT232H.") self._port._cpha = phase @property def frequency(self): return self._port.frequency def write(self, buf, start=0, end=None): end = end if end else len(buf) chunks, rest = divmod(end - start, self._spi.PAYLOAD_MAX_LENGTH) for i in range(chunks): chunk_start = start + i * self._spi.PAYLOAD_MAX_LENGTH chunk_end = chunk_start + self._spi.PAYLOAD_MAX_LENGTH self._port.write(buf[chunk_start:chunk_end]) if rest: self._port.write(buf[-1*rest:]) def readinto(self, buf, start=0, end=None, write_value=0): end = end if end else len(buf) result = self._port.read(end-start) for i, b in enumerate(result): buf[start+i] = b def write_readinto(self, buffer_out, buffer_in, out_start=0, out_end=None, in_start=0, in_end=None): out_end = out_end if out_end else len(buffer_out) in_end = in_end if in_end else len(buffer_in) result = self._port.exchange(buffer_out[out_start:out_end], in_end-in_start, duplex=True) for i, b in enumerate(result): buffer_in[in_start+i] = b
def ftdi_spi(device='ftdi://::/1', bus_speed_hz=12000000, gpio_CS=3, gpio_DC=5, gpio_RST=6, reset_hold_time=0, reset_release_time=0): """ Bridges an `SPI <https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus>`_ (Serial Peripheral Interface) bus over an FTDI USB device to provide :py:func:`data` and :py:func:`command` methods. :param device: A URI describing the location of the FTDI device. If ``None`` is supplied (default), ``ftdi://::/1`` is used. See `pyftdi <https://pypi.org/project/pyftdi>`_ for further details of the naming scheme used. :type device: string :param bus_speed_hz: SPI bus speed, defaults to 12MHz. :type bus_speed_hz: int :param gpio_CS: The ADx pin to connect chip select (CS) to (defaults to 3). :type gpio_CS: int :param gpio_DC: The ADx pin to connect data/command select (DC) to (defaults to 5). :type gpio_DC: int :param gpio_RST: The ADx pin to connect reset (RES / RST) to (defaults to 6). :type gpio_RST: int :param reset_hold_time: The number of seconds to hold reset active. Some devices may require a duration of 100ms or more to fully reset the display (default:0) :type reset_hold_time: float :param reset_release_time: The number of seconds to delay afer reset. Some devices may require a duration of 150ms or more after reset was triggered before the device can accept the initialization sequence (default:0) :type reset_release_time: float .. versionadded:: 1.9.0 """ from pyftdi.spi import SpiController controller = SpiController(cs_count=1) controller.configure(device) slave = controller.get_port(cs=gpio_CS - 3, freq=bus_speed_hz, mode=0) gpio = controller.get_gpio() # RESET and DC configured as outputs pins = _ftdi_pin(gpio_RST) | _ftdi_pin(gpio_DC) gpio.set_direction(pins, pins & 0xFF) serial = spi( __FTDI_WRAPPER_SPI(controller, slave), __FTDI_WRAPPER_GPIO(gpio), gpio_DC=gpio_DC, gpio_RST=gpio_RST, reset_hold_time=reset_hold_time, reset_release_time=reset_release_time) serial._managed = True return serial
class SPI: MSB = 0 def __init__(self): from pyftdi.spi import SpiController self._spi = SpiController(cs_count=1) self._spi.configure('ftdi:///1') self._port = self._spi.get_port(0) self._port.set_frequency(100000) self._port._cpol = 0 self._port._cpha = 0 # Change GPIO controller to SPI Pin.ft232h_gpio = self._spi.get_gpio() def init(self, baudrate=100000, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None): self._port.set_frequency(baudrate) self._port._cpol = polarity self._port._cpha = phase @property def frequency(self): return self._port.frequency def write(self, buf, start=0, end=None): end = end if end else len(buf) chunks, rest = divmod(end - start, self._spi.PAYLOAD_MAX_LENGTH) for i in range(chunks): chunk_start = start + i * self._spi.PAYLOAD_MAX_LENGTH chunk_end = chunk_start + self._spi.PAYLOAD_MAX_LENGTH self._port.write(buf[chunk_start:chunk_end]) if rest: self._port.write(buf[-1*rest:]) def readinto(self, buf, start=0, end=None, write_value=0): end = end if end else len(buf) result = self._port.read(end-start) for i, b in enumerate(result): buf[start+i] = b def write_readinto(self, buffer_out, buffer_in, out_start=0, out_end=None, in_start=0, in_end=None): out_end = out_end if out_end else len(buffer_out) in_end = in_end if in_end else len(buffer_in) result = self._port.exchange(buffer_out[out_start:out_end], in_end-in_start, duplex=True) for i, b in enumerate(result): buffer_in[in_start+i] = b
def ftdi_spi(device='ftdi://::/1', bus_speed_hz=12000000, gpio_CS=3, gpio_DC=5, gpio_RST=6): """ Bridges an `SPI <https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus>`_ (Serial Peripheral Interface) bus over an FTDI USB device to provide :py:func:`data` and :py:func:`command` methods. :param device: A URI describing the location of the FTDI device. If ``None`` is supplied (default), ``ftdi://::/1`` is used. See `pyftdi <https://pypi.python.org/pypi/pyftdi>`_ for further details of the naming scheme used. :type device: string :param bus_speed_hz: SPI bus speed, defaults to 12MHz. :type bus_speed_hz: int :param gpio_CS: The ADx pin to connect chip select (CS) to (defaults to 3). :type gpio_CS: int :param gpio_DC: The ADx pin to connect data/command select (DC) to (defaults to 5). :type gpio_DC: int :param gpio_RST: The ADx pin to connect reset (RES / RST) to (defaults to 6). :type gpio_RST: int .. versionadded:: 1.9.0 """ from pyftdi.spi import SpiController controller = SpiController(cs_count=1) controller.configure(device) slave = controller.get_port(cs=gpio_CS - 3, freq=bus_speed_hz, mode=0) gpio = controller.get_gpio() # RESET and DC configured as outputs pins = _ftdi_pin(gpio_RST) | _ftdi_pin(gpio_DC) gpio.set_direction(pins, pins & 0xFF) serial = spi(__FTDI_WRAPPER_SPI(controller, slave), __FTDI_WRAPPER_GPIO(gpio), gpio_DC=gpio_DC, gpio_RST=gpio_RST) serial._managed = True return serial
class KU10405: """This class controls the KU10405 chip via an FT232H SPI controller. Users should only concern themselves with the constructor and set_tap method. Example usage: from ku10405 import KU10405 dut = KU10405() dut.set_tap(0, 100, 200) """ # The apply pin both applies changes and indicates trim bits being set. When CS is high, a # rising edge of the apply pin indicates that settings should be applied. Trim bits are # programmed when the apply pin is high while CS is low during the rising edge of the first # clock cycle. APPLY_PIN = 7 def __init__(self, readback=True, ftdi_url='ftdi://ftdi:232h/1'): """An FT232H chip must be connected to your computer for this constructor to succeed. It sets up the SPI/GPIO interface and configures readback. When readback is enabled, set_tap will throw an error if the readback of any write does not match what was programmed. Readback does have an impact on performance because it requires an extra SPI transaction for every call to set_tap, but for safety it should not be disabled unless speed is your top priority. Note that for readback to work, the appropriate switch on the board must be set. Args: readback (bool, optional): Set to False to disable readback error checking """ self._readback = readback # Setup SPI interface self._spi_controller = SpiController() self._spi_controller.configure(ftdi_url) self._spi = self._spi_controller.get_port(cs=0, mode=0, freq=1e6) # Setup GPIO interface self._gpio = self._spi_controller.get_gpio() self._gpio.set_direction(1 << self.APPLY_PIN, 1 << self.APPLY_PIN) # make APLS an output self._set_apply(False) # We're always going to use address 0. This is here just in case that changes. self._addr = 0 def set_tap(self, channel, mag, phase, enable=True, apply=True): """Sets the tap at the given channel to the given magnitude and phase. Magnitude and phase values control bits, not logical values. Disable the channel by setting enable to False. Changes are only applied if apply is set to True. The end result is the same if you always set apply to True or only set it to True on the last call of a sequence. Args: channel (int): Which channel to set (range: [0-3]) mag (int): 14-bit attenuation (range: [0, 2^14)) phase (int): 16-bit phase (range: [0, 2^16)) enable (bool, optional): Set to False to disable this channel apply (bool, optional): Set to False if you don't want to apply these changes yet Raises: TypeError: raised if any arguments have the wrong type ValueError: raised if any arguments are out-of-range """ if not all(isinstance(val, int) for val in (channel, mag, phase)): raise TypeError('Channel, magnitude, and phase must be integers') if not all(isinstance(val, bool) for val in (enable, apply)): raise TypeError('Enable and apply must be bools') if not 0 <= channel <= 3: raise ValueError('Address and channel must be in range [0, 3]') if not 0 <= mag <= 16383: raise ValueError('Magnitude must be in range [0, 16383]') if not 0 <= phase <= 65535: raise ValueError('Phase must be in range [0, 65535]') coarse_write, _ = \ self._write(channel, 'coarse', mag >> 9, phase >> 11, enable) fine_write, coarse_read = \ self._write(channel, 'fine', (mag >> 4) & 0x1F, (phase >> 5) & 0x3F) trim_write, fine_read = \ self._write(channel, 'trim', mag & 0xF, phase & 0x1F) if self._readback: # Do a dummy write so we can get the trim readback. Note that this assumes we are not # using address 3! trim_read = self._spi.exchange((0xFFFF).to_bytes(2, byteorder='big'), duplex=True) trim_read = (trim_read[0] << 8) | trim_read[1] if coarse_write != coarse_read or fine_write != fine_read or trim_write != trim_read: raise IOError( 'Readbacks do not match! Wrote coarse {}, fine {}, trim {}, but ' 'read coarse {}, fine {}, trim {}'.format( coarse_write, fine_write, trim_write, coarse_read, fine_read, trim_read)) if apply: self._set_apply(True) self._set_apply(False) def _write(self, channel, reg_type, mag, phase, enable=None): if reg_type not in ('coarse', 'fine', 'trim'): raise ValueError('Unrecognized register type {}'.format(reg_type)) if reg_type == 'coarse' and enable is None: raise ValueError('Enable must be specified for coarse write') if reg_type in ('coarse', 'fine') and not 0 <= mag <= 31: raise ValueError('Coarse/fine magnitudes must be in range [0, 31]') if reg_type == 'trim' and not 0 <= mag <= 15: raise ValueError('Trim magnitude must be in range [0, 15]') if reg_type in ('coarse', 'trim') and not 0 <= phase <= 31: raise ValueError('Coarse/trim phases must be in range [0, 31]') if reg_type == 'fine' and not 0 <= phase <= 63: raise ValueError('Fine phase must be in range [0, 63]') wdata = (self._addr << 14) | (channel << 12) if reg_type == 'coarse': wdata |= (enable << 10) | (phase << 5) | mag elif reg_type == 'fine': wdata |= (1 << 11) | (phase << 5) | mag else: # trim wdata |= (phase << 5) | mag self._set_apply(True) rdata = self._spi.exchange(wdata.to_bytes(2, byteorder='big'), duplex=True) rdata = (rdata[0] << 8) | rdata[1] if reg_type == 'trim': self._set_apply(False) return wdata, rdata def _set_apply(self, value): if not isinstance(value, bool): raise TypeError('GPIO value must be a bool.') self._gpio.write(value << self.APPLY_PIN)
class FtdiDevice: """FTDI FT2232H SPI master to access FPGA Control/Status Registers (CSR) plus some GPIO control. SPI parameters: - FTDI channel B: * BDBUS0 - SCLK * BDBUS1 - MOSI * BDBUS2 - MISO * BDBUS3 - CSn - slave; - mode 0 only; - most significant bit transmitted first; - byte order from high to low; - SCK frequency must at least 8 lower than system frequency. There are 2 types of SPI transactions: - incremental burst -- address increments internally after every data (array) - fixed burst -- one adreess, multiple data (FIFO) Transaction is done with 8-bit address and 16 bit data words. Transaction format: - control word (3 bytes): * bit 23 -- write (1) or read (0) * bit 22 -- burst incremental (1) or fixed (0) * bit 21 .. 8 -- 14 bit length (0 - 1 data word, 1 - 2 data words, etc) * bits 7 .. 0 -- 8 bit address - data word (2 bytes) 1 .. N: * bits 15 .. 0 -- data to be written or readen GPIO parameters: - ADBUS7 - output - active low reset for FPGA configuration (ICE_RESET) - BDBUS7 - output - active high reset for FPGA logic (ICE_RESET_FT) """ GPIO_RESET_LOGIC_POS = 7 GPIO_RESET_CONFIG_POS = 7 def __init__(self, ftdi_url, spi_freq=1E6): """Configure the FTDI interface. Keyword arguments: ftdi_url -- device url, which can be obtained by Ftdi.show_devices() freq -- SPI frequency up to 8E6 (for FPGA running on 64 MHz) """ # Configure SPI master self._spi_ctrl = SpiController() self._spi_ctrl.configure(ftdi_url + '2') # second port - channel B self._spi_port = self._spi_ctrl.get_port(cs=0, freq=spi_freq, mode=0) # Configure FPGA logic reset (ICE_RESET_FT) self._spi_gpio = self._spi_ctrl.get_gpio() self._spi_gpio.set_direction(1 << self.GPIO_RESET_LOGIC_POS, 1 << self.GPIO_RESET_LOGIC_POS) self._spi_gpio.write(0) # Configure FPGA configuration reset (ICE_RESET) self._gpio_ctrl = GpioAsyncController() self._gpio_ctrl.configure( ftdi_url + '1', # first port - channel A direction=(1 << self.GPIO_RESET_CONFIG_POS), frequency=1e6, initial=(1 << self.GPIO_RESET_CONFIG_POS)) self._gpio_ctrl.write(1 << self.GPIO_RESET_CONFIG_POS) def _int_to_bytes(self, i, length=2): """Convert integer to bytes""" return int.to_bytes(i, length=length, byteorder='big', signed=False) def _words_to_bytes(self, words_list): """Convert list with 16 bit words to bytes""" bytes_str_list = [self._int_to_bytes(w, length=2) for w in words_list] return b''.join(bytes_str_list) # concatenate all strings def _bytes_to_words(self, bytes_str): """Convert bytes string to list with 16 bit words""" return [ int.from_bytes(bytes_str[b * 2:b * 2 + 2], byteorder='big', signed=False) for b in range(len(bytes_str) // 2) ] def _prepare_ctrl_word(self, addr, len, burst, wr): """Prepare control word for exchange. Keyword arguments: addr -- 8 bit address len -- number of 16 bit data words to write/read (2^14 max) burst -- 'fixed' address the same for every data, 'incr' - address + 1 for every next data word wr -- 1 for write operation, 0 - for read """ ctrl_word = 0 ctrl_word |= (wr << 23) ctrl_word |= ((burst == 'incr') << 22) ctrl_word |= (((len - 1) & 0x3FFF) << 8) ctrl_word |= ((addr & 0xFF) << 0) return ctrl_word def spi_read(self, addr, len=1, burst='fixed'): """Read data from address via SPI. Keyword arguments: addr -- 8 bit address len -- number of 16 bit data words to write/read (2^14 max) burst -- 'fixed' address the same for every data, 'incr' - address + 1 for every next data word Return: list of size 'len' with 16 bit data words """ ctrl_word = self._prepare_ctrl_word(addr, len, burst, wr=0) rbytes = self._spi_port.exchange(self._int_to_bytes(ctrl_word, 3), len * 2) return self._bytes_to_words(rbytes) def spi_write(self, addr, data, burst='fixed'): """Write data to address via SPI. Keyword arguments: addr -- 8 bit address data -- list with 16 bit data words to write (list length 2^14 max) burst -- 'fixed' address the same for every data, 'incr' - address + 1 for every next data word """ ctrl_word = self._prepare_ctrl_word(addr, len(data), burst, wr=1) wbytes = self._int_to_bytes(ctrl_word, 3) + self._words_to_bytes(data) self._spi_port.exchange(wbytes) def reset_logic_on(self): """Activate reset pin ICE_RESET_FT""" self._spi_gpio.write((1 << self.GPIO_RESET_LOGIC_POS) | self._spi_gpio.read()) def reset_logic_off(self): """Deactivate reset pin ICE_RESET_FT""" self._spi_gpio.write(~(1 << self.GPIO_RESET_LOGIC_POS) & self._spi_gpio.read()) def reset_config_on(self): """Activate reset pin ICE_RESET""" self._gpio_ctrl.write(~(1 << self.GPIO_RESET_CONFIG_POS) & self._gpio_ctrl.read()) def reset_config_off(self): """Deactivate reset pin ICE_RESET""" self._gpio_ctrl.write((1 << self.GPIO_RESET_LOGIC_POS) | self._gpio_ctrl.read()) def close_connection(self): """Close FTDI interface""" self._spi_ctrl.terminate() self._gpio_ctrl.close()
class SpiGpioTestCase(unittest.TestCase): """Basic test for GPIO access w/ SPI mode It expects the following I/O setup: AD4 connected t0 AC0 AD5 connected t0 AC1 AD6 connected t0 AC2 AD7 connected t0 AC3 """ # AD0: SCLK, AD1: MOSI, AD2: MISO, AD3: /CS AD_OFFSET = 4 AC_OFFSET = 8 PIN_COUNT = 4 def setUp(self): self._spi = SpiController(cs_count=1) url = environ.get('FTDI_DEVICE', 'ftdi://ftdi:2232h/1') self._spi.configure(url) self._port = self._spi.get_port(0, freq=1E6, mode=0) self._io = self._spi.get_gpio() def tearDown(self): """Close the SPI connection""" self._spi.terminate() def test_ac_to_ad(self): ad_pins = ((1 << self.PIN_COUNT) - 1) << self.AD_OFFSET # input ac_pins = ((1 << self.PIN_COUNT) - 1) << self.AC_OFFSET # output io_pins = ad_pins | ac_pins def ac_to_ad(ac_output): ac_output &= ac_pins ac_output >>= self.AC_OFFSET - self.AD_OFFSET return ac_output & ad_pins self._io.set_direction(io_pins, ac_pins) for ac in range(1 << self.PIN_COUNT): ac_out = ac << self.AC_OFFSET ad_in = ac_to_ad(ac_out) self._io.write(ac_out) # random SPI exchange to ensure SPI does not change GPIO self._port.exchange([0x00, 0xff], 2) rd = self._io.read() self.assertEqual(rd, ad_in) self.assertRaises(SpiIOError, self._io.write, ad_pins) def test_ad_to_ac(self): ad_pins = ((1 << self.PIN_COUNT) - 1) << self.AD_OFFSET # output ac_pins = ((1 << self.PIN_COUNT) - 1) << self.AC_OFFSET # input io_pins = ad_pins | ac_pins def ad_to_ac(ad_output): ad_output &= ad_pins ad_output <<= self.AC_OFFSET - self.AD_OFFSET return ad_output & ac_pins self._io.set_direction(io_pins, ad_pins) for ad in range(1 << self.PIN_COUNT): ad_out = ad << self.AD_OFFSET ac_in = ad_to_ac(ad_out) self._io.write(ad_out) # random SPI exchange to ensure SPI does not change GPIO self._port.exchange([0x00, 0xff], 2) rd = self._io.read() self.assertEqual(rd, ac_in) self.assertRaises(SpiIOError, self._io.write, ac_pins)
def main(): done_stdin = False parser = argparse.ArgumentParser( prog="spitest", formatter_class=argparse.RawDescriptionHelpFormatter, usage=USAGE, description=__doc__) parser.add_argument('--version', action='store_true', help='Show version and exit') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output during processing') parser.add_argument( '-f', '--flippy', action='store_true', help='Flip the SPI/JTAG control GPIO 10 times and exit') parser.add_argument( '-l', '--length', type=int, action='store', help='Construct and send a message of specified length') parser.add_argument('-j', '--jtag', action='store_true', help='Set SPI/JTAG control to JTAG and exit') parser.add_argument('message', nargs='*', metavar='input', default='1234', help='message to send in 4 byte chunks') args = parser.parse_args() if args.version: show_and_exit(__file__, ["pyftdi"]) if (args.verbose): log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) else: log.basicConfig(format="%(levelname)s: %(message)s") # Instanciate a SPI controller spi = SpiController(cs_count=1) # interfaces start from 1 here, so this is Channel A (called 0 in jtag) spi.configure('ftdi://ftdi:2232h/1') # Get a port to a SPI slave w/ /CS on A*BUS3 and SPI mode 0 @ 1MHz slave = spi.get_port(cs=0, freq=1E6, mode=0) # Get GPIO port to manage extra pins # BUS4 = JTAG TRST_N, BUS5 = JTAG SRST_N, BUS6 = JTAG_SPIN # Note: something makes FTDI default to BUS6 low, selected that for SPI # otherwise SRST being default low holds the chip in reset # pyftdi Set Direction also forces the output to zero # so initially make SRST an input w/pullup in FPGA in case SPI/JTAG was # initially JTAG gpio = spi.get_gpio() gpio.set_direction(0x40, 0x40) time.sleep(1) gpio.set_direction(0x70, 0x70) if args.jtag: gpio.write(0x70) return gpio.write(0x30) if args.flippy: for i in range(10): print("Select SPI") gpio.write(0x30) time.sleep(2) print("Select JTAG") gpio.write(0x70) time.sleep(2) return print("Select SPI") gpio.write(0x30) # Synchronous exchange with the remote SPI slave if args.length: s = '' for i in range(args.length): s += hex(i & 15)[-1] else: s = '' for m in args.message: s += m + ' ' s = s[:-1] # remove extra space put on end # pad to ensure multiple of 4 bytes filled = len(s) % 4 if filled: s += '....'[filled:] while len(s): write_buf = bytes(s[:4], encoding='utf8') read_buf = slave.exchange(write_buf, duplex=True).tobytes() print("Got " + str(read_buf)) s = s[4:]
class SPI: """Custom SPI Class for FT232H""" MSB = 0 def __init__(self): # pylint: disable=import-outside-toplevel from pyftdi.spi import SpiController # pylint: enable=import-outside-toplevel self._spi = SpiController(cs_count=1) self._spi.configure(get_ftdi_url()) self._port = self._spi.get_port(0) self._port.set_frequency(100000) self._port._cpol = 0 self._port._cpha = 0 # Change GPIO controller to SPI Pin.ft232h_gpio = self._spi.get_gpio() # pylint: disable=too-many-arguments,unused-argument def init( self, baudrate=100000, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None, ): """Initialize the Port""" self._port.set_frequency(baudrate) # FTDI device can only support mode 0 and mode 2 # due to the limitation of MPSSE engine. # This means CPHA must = 0 self._port._cpol = polarity if phase != 0: raise ValueError("Only SPI phase 0 is supported by FT232H.") self._port._cpha = phase # pylint: enable=too-many-arguments @property def frequency(self): """Return the current frequency""" return self._port.frequency def write(self, buf, start=0, end=None): """Write data from the buffer to SPI""" end = end if end else len(buf) chunks, rest = divmod(end - start, self._spi.PAYLOAD_MAX_LENGTH) for i in range(chunks): chunk_start = start + i * self._spi.PAYLOAD_MAX_LENGTH chunk_end = chunk_start + self._spi.PAYLOAD_MAX_LENGTH self._port.write(buf[chunk_start:chunk_end]) if rest: rest_start = start + chunks * self._spi.PAYLOAD_MAX_LENGTH self._port.write(buf[rest_start:end]) # pylint: disable=unused-argument def readinto(self, buf, start=0, end=None, write_value=0): """Read data from SPI and into the buffer""" end = end if end else len(buf) buffer_out = [write_value] * (end - start) result = self._port.exchange(buffer_out, end - start, duplex=True) for i, b in enumerate(result): buf[start + i] = b # pylint: enable=unused-argument # pylint: disable=too-many-arguments def write_readinto(self, buffer_out, buffer_in, out_start=0, out_end=None, in_start=0, in_end=None): """Perform a half-duplex write from buffer_out and then read data into buffer_in """ out_end = out_end if out_end else len(buffer_out) in_end = in_end if in_end else len(buffer_in) result = self._port.exchange(buffer_out[out_start:out_end], in_end - in_start, duplex=True) for i, b in enumerate(result): buffer_in[in_start + i] = b
print("Usage: raptor_flash.py <file>") sys.exit() file_path = sys.argv[1] if not os.path.isfile(file_path): print("File not found.") sys.exit() spi = SpiController(cs_count=1, turbo=True) # spi.configure(vendor=0x0403, product=0x6014, interface=1) spi.configure('ftdi://ftdi:232h:0/1') slave = spi.get_port(cs=0, freq=12E6, mode=0) # Chip select is 0 -- corresponds to D3 gpio = spi.get_gpio() gpio.set_direction(0x0100, 0x0100) gpio.write(0x0000) # time.sleep(1.0) slave.write([CMD_RESET_CHIP]) jedec = slave.exchange([CMD_JEDEC_DATA], 3) print("JEDEC = {}".format(binascii.hexlify(jedec))) if jedec[0] != int('ef', 16) and jedec[0] != int('01', 16) and jedec[0] != int( 'bf', 16): print("Winbond, Microchip or Cypress SRAM not found") sys.exit()
class SPIDriver: """ Class that contains the driver for the FTDI SPI + GPIO. """ def __init__(self): self.ctrl = None self.slave = None print('Starting driver ...') self.begin() def begin(self): try: self.ctrl = SpiController(cs_count=1) s = 'ftdi://0x0403:0x6011/1' self.ctrl.configure(s) self.slave = self.ctrl.get_port(cs=0, freq=6E6, mode=3) self.slave.set_frequency(8000000) self.gpio = self.ctrl.get_gpio() self.gpio.set_direction( 0x10, 0x10) # direction for the fet controller is OUTPUT (1) self.gpio.write(0x10) time.sleep(0.1) # to account for using separate ports for power and comms: self.backup_ctrl = SpiController(cs_count=1) self.backup_gpio = None if not s == 'ftdi://0x0403:0x6011/1': backup_s = 'ftdi://0x0403:0x6011/1' self.backup_ctrl.configure(backup_s) self.backup_gpio = self.backup_ctrl.get_gpio() self.backup_gpio.set_direction(0x10, 0x10) self.backup_gpio.write(0x10) time.sleep(1) except Exception as err: print('Error in initialising the FTDI driver ...:', err) def write_data_out(self, buffer): if not isinstance(buffer, bytes): raise Exception # TODO define exception try: self.slave.write(buffer) return True except Exception as err: #TODO convert to Logging print('Error in writing data out to FTDI SPI...:', err) return False def set_power_on(self): self.gpio.write(0x10) if self.backup_gpio is not None: self.backup_gpio.write(0x10) def set_power_off(self): self.gpio.write(0x00) if self.backup_gpio is not None: self.backup_gpio.write(0x00) def terminate(self): self.ctrl.terminate() print('terminated')
class SPI: MSB = 0 baudrate = 100000 mode = 0 bits = 8 def __init__(self): # change GPIO controller to SPI from pyftdi.spi import SpiController self._spi = SpiController(cs_count=1) self._spi.configure('ftdi:///1') Pin.ft232h_gpio = self._spi.get_gpio() def init(self, baudrate=100000, polarity=0, phase=0, bits=8, firstbit=MSB, sck=None, mosi=None, miso=None): self.cs = 0 self.freq = baudrate if polarity == 0 and phase == 0: self.mode = 0 elif polarity == 0 and phase == 1: self.mode = 1 elif polarity == 1 and phase == 0: raise ValueError("SPI mode 2 is not supported.") elif polarity == 1 and phase == 1: self.mode = 3 else: raise ValueError("Unknown SPI mode.") def write(self, buf, start=0, end=None): end = end if end else len(buf) port = self._spi.get_port(self.cs, self.freq, self.mode) port.write(buf[start:end]) def readinto(self, buf, start=0, end=None, write_value=0): end = end if end else len(buf) port = self._spi.get_port(self.cs, self.freq, self.mode) result = port.read(end - start) for i, b in enumerate(result): buf[start + i] = b def write_readinto(self, buffer_out, buffer_in, out_start=0, out_end=None, in_start=0, in_end=None): out_end = out_end if out_end else len(buffer_out) in_end = in_end if in_end else len(buffer_in) port = self._spi.get_port(self.cs, self.freq, self.mode) result = port.exchange(buffer_out[out_start:out_end], in_end - in_start) for i, b in enumerate(result): buffer_in[in_start + i] = b
class SPILink: BIT_REV = [int('{:08b}'.format(n)[::-1], 2) for n in range(0,256)] def __init__(self, device='ftdi://ftdi:232h/1', debug=False): self.device_str = device self.debug = debug self.ctrl = SpiController(silent_clock=False) self.ctrl.configure(device, cs_count=1) self.spi = self.ctrl.get_port(cs=0, freq=100000) self.gpio = self.ctrl.get_gpio() direction = self.gpio.direction self.gpio.set_direction(0x30, 0x10) # Add reset as output self.gpio.write(0x10) def reset(self): self.gpio.write(0x00) # Pull reset line low time.sleep(0.1) self.gpio.write(0x10) # Reset line high time.sleep(0.8) # Wait for cc111x to boot def flush(self): # Send interrupt command _, available = self.exchange([0x99, 1], readlen=2) data = self.exchange([], readlen=max(1,available)) # Discard any remaining data _, available = self.exchange([0x99, 0], readlen=2) while (available > 0): data = self.exchange([], readlen=available) if self.debug: print("discarding %s" % "".join(["%02x" % x for x in data])) _, available = self.exchange([0x99, 0], readlen=2) def reverse_bits(self, data): return [SPILink.BIT_REV[x] for x in data] def as_hex(self, data): return "".join(["%02x" % x for x in data]) def exchange(self, data, readlen=0): lsb_send = self.reverse_bits(data) lsb_rcv = self.spi.exchange(lsb_send,readlen=readlen, duplex=True) return self.reverse_bits(lsb_rcv) def send_command(self, command): data = command.data() if len(data) == 0: print("Empty command!") return if self.debug: print("send: %s" % self.as_hex(data)) response = None _, available = self.exchange([0x99, len(data)], readlen=2) if available > 0: print("Unexpected data available from client while sending command.") return self.exchange(data, readlen=0) def wait_for_response(self, response_type, timeout=1): end_time = time.time() + timeout response = None while(time.time() < end_time and response == None): _, available = self.exchange([0x99, 0], readlen=2) if (available > 0): if self.debug: print("response: available = %d" % available) rdata = self.exchange([], readlen=available) else: time.sleep(0.01) if available > 0: response = rdata data = [] if response == None: raise SPITimeout() if self.debug: print("recv: %s" % self.as_hex(response)) response_obj = response_type(response) return response_obj def do_command(self, command, timeout=1): self.send_command(command) response = self.wait_for_response(command.response_type(), timeout) if response.response_code != ResponseCode.COMMAND_INTERRUPTED: return response else: print("Command interrupted...") return self.wait_for_response(command.response_type(), timeout) def update_register(self, register, value): return self.do_command(UpdateRegisterCommand(register, value)) def set_base_frequency(self, freq): val = int(freq / (RILEYLINK_XTAL_FREQUENCY / pow(2,16))) self.update_register(Register.FREQ0, val & 0xff) self.update_register(Register.FREQ1, (val >> 8) & 0xff) self.update_register(Register.FREQ2, (val >> 16) & 0xff)