class SpiRfda2125Test: """Basic test for a RFDA2125 Digital Controlled Variable Gain Amplifier selected as CS2, SPI mode 0 """ def __init__(self): self._spi = SpiController(cs_count=3) self._port = None def open(self): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', 'ftdi:///1') debug = to_bool(environ.get('FTDI_DEBUG', 'off')) self._spi.configure(url, debug=debug) self._port = self._spi.get_port(2, freq=1E6, mode=0) def change_attenuation(self, value): if not 0.0 <= value <= 31.5: print('Out-of-bound attenuation', file=stderr) intval = 63 - int(value * 2) self._port.write(bytes([intval]), 1) def close(self): """Close the SPI connection""" self._spi.terminate()
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
class SpiCsForceTestCase(unittest.TestCase): """Basic test for exercing direct /CS control. It requires a scope or a digital analyzer to validate the signal waveforms. """ @classmethod def setUpClass(cls): cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1') cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off')) def setUp(self): self._spi = SpiController(cs_count=1) self._spi.configure(self.url, debug=self.debug) self._port = self._spi.get_port(0, freq=1E6, mode=0) def tearDown(self): """Close the SPI connection""" self._spi.terminate() def test_cs_default_pulse(self): for _ in range(5): self._port.force_select() def test_cs_long_pulse(self): for _ in range(5): self._port.force_select(cs_hold=200) def test_cs_manual_pulse(self): for _ in range(5): self._port.force_select(level=False) self._port.force_select(level=True) # beware that random USB bus access does not allow to create # precise delays. This is only the shorter bound, longer one is # not defined sleep(100e-6) def test_cs_pulse_write(self): self._port.force_select() self._port.write([0x00, 0x01, 0x02]) def test_cs_default_pulse_rev_clock(self): if not self._spi.is_inverted_cpha_supported: self.skipTest('FTDI does not support mode 3') self._port.set_mode(3) for _ in range(5): self._port.force_select()
class SpiAccelTest(object): """Basic test for an ADXL345 device selected as CS1, SPI mode 3 """ def __init__(self): self._spi = SpiController() def open(self): """Open an I2c connection to a slave""" self._spi.configure('ftdi://ftdi:2232h/1') def read_device_id(self): port = self._spi.get_port(1, freq=6E6, mode=3) device_id = port.exchange([0x00], 1).tobytes() hex_device_id = hexlify(device_id).decode() print('DEVICE ID:', hex_device_id) return hex_device_id def close(self): """Close the I2C connection""" self._spi.terminate()
class SpiDataFlashTest(object): """Basic test for a MX25L1606E data flash device selected as CS0, SPI mode 0 """ def __init__(self): self._spi = SpiController() def open(self): """Open an I2c connection to a slave""" self._spi.configure('ftdi://ftdi:2232h/1') def read_jedec_id(self): port = self._spi.get_port(0, freq=3E6, mode=0) jedec_id = port.exchange([0x9f], 3).tobytes() hex_jedec_id = hexlify(jedec_id).decode() print('JEDEC ID:', hex_jedec_id) return hex_jedec_id def close(self): """Close the I2C connection""" self._spi.terminate()
class SpiRfda2125Test(object): """Basic test for a RFDA2125 Digital Controlled Variable Gain Amplifier selected as CS2, SPI mode 0 """ def __init__(self): self._spi = SpiController() def open(self): """Open an I2c connection to a slave""" self._spi.configure('ftdi://ftdi:2232h/1') self._port = self._spi.get_port(2, freq=1E6, mode=0) def change_attenuation(self, value): if not (0.0 <= value <= 31.5): print('Out-of-bound attenuation', file=stderr) intval = 63 - int(value * 2) self._port.write(bytes([intval]), 1) def close(self): """Close the I2C connection""" self._spi.terminate()
class SpiAccelTest: """Basic test for an ADXL345 device selected as CS1, SPI mode 3 """ def __init__(self): self._spi = SpiController(cs_count=3) def open(self): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', 'ftdi:///1') debug = to_bool(environ.get('FTDI_DEBUG', 'off')) self._spi.configure(url, debug=debug) def read_device_id(self): port = self._spi.get_port(1, freq=6E6, mode=3) device_id = port.exchange([0x00], 1) hex_device_id = hexlify(device_id).decode() print('DEVICE ID:', hex_device_id) return hex_device_id def close(self): """Close the SPI connection""" self._spi.terminate()
class SpiDataFlashTest: """Basic test for a MX25L1606E data flash device selected as CS0, SPI mode 0 """ def __init__(self): self._spi = SpiController(cs_count=3) def open(self): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', 'ftdi:///1') debug = to_bool(environ.get('FTDI_DEBUG', 'off')) self._spi.configure(url, debug=debug) def read_jedec_id(self): port = self._spi.get_port(0, freq=3E6, mode=0) jedec_id = port.exchange([0x9f], 3) hex_jedec_id = hexlify(jedec_id).decode() print('JEDEC ID:', hex_jedec_id) return hex_jedec_id def close(self): """Close the SPI connection""" self._spi.terminate()
class SpiDataFlashTest(object): """Basic test for a MX25L1606E data flash device selected as CS0, SPI mode 0 """ def __init__(self): self._spi = SpiController(cs_count=3) def open(self): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', 'ftdi://ftdi:2232h/1') self._spi.configure(url) def read_jedec_id(self): port = self._spi.get_port(0, freq=3E6, mode=0) jedec_id = port.exchange([0x9f], 3).tobytes() hex_jedec_id = hexlify(jedec_id).decode() print('JEDEC ID:', hex_jedec_id) return hex_jedec_id def close(self): """Close the SPI connection""" self._spi.terminate()
class SpiAccelTest(object): """Basic test for an ADXL345 device selected as CS1, SPI mode 3 """ def __init__(self): self._spi = SpiController(cs_count=3) def open(self): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', 'ftdi://ftdi:2232h/1') self._spi.configure(url) def read_device_id(self): port = self._spi.get_port(1, freq=6E6, mode=3) device_id = port.exchange([0x00], 1).tobytes() hex_device_id = hexlify(device_id).decode() print('DEVICE ID:', hex_device_id) return hex_device_id def close(self): """Close the SPI connection""" self._spi.terminate()
class SpiRfda2125Test(object): """Basic test for a RFDA2125 Digital Controlled Variable Gain Amplifier selected as CS2, SPI mode 0 """ def __init__(self): self._spi = SpiController(cs_count=3) def open(self): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', 'ftdi://ftdi:2232h/1') self._spi.configure(url) self._port = self._spi.get_port(2, freq=1E6, mode=0) def change_attenuation(self, value): if not (0.0 <= value <= 31.5): print('Out-of-bound attenuation', file=stderr) intval = 63-int(value*2) self._port.write(bytes([intval]), 1) def close(self): """Close the SPI connection""" self._spi.terminate()
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)
class SpiData93LC56BTest(object): """Basic class for a Microchip 93LC56B data flash device selected as CS0, SPI mode 0, Active High polarity for CS and Bi-directional data. Test setup: UM232H connected to the EEPROM on a FT4232H-56Q Mini Module. The FT4232H is forced into reset by connecting the RT# pin (CN2-8) to GND (CN2-6). Then make the following connections between the boards: UM232H FT4232H-56Q ====== =========== GND - GND D0 (CN2-1) - ECL (CN3-6) D1 (CN2-2) - EDA (CN3-7) D2 (CN2-3) - EDA (CN3-7) D3 (CN2-4) - ECS (CN3-5) NOTE: D1 & D2 are indeed both tied to the same EDA pin. """ def __init__(self): self._spi = SpiController(cs_count=1, cs_pol=0) self._freq = 1E6 self._mode = 0 self._bidir = True # Maximum number of read cycles to wait while looking for the # Ready status after each write self._write_timeout_cnt = 25 # According to the datasheet, the maximum write time is 6 ms self._Twc = 0.006 # The opcodes are a full byte to make it easy to use with the # byte interface of SpiController. These opcodes also include # the start bit (SB), which is simply the left-most '1' # bit. The actual 2-bit opcode (OC) follows this start bit. # # The instructions ERAL, EWDS, EWEN and WRAL require a special # address byte to complete the opcode. So they are 2 element # lists whereas the others are single element lists. self._SBOC_erase = [0x07] self._SBOC_eral = [0x04, 0x80] # requires EEPROM Vcc >= 4.5V self._SBOC_ewds = [0x04, 0x00] self._SBOC_ewen = [0x04, 0xc0] self._SBOC_read = [0x06] self._SBOC_write = [0x05] self._SBOC_wral = [0x04, 0x40] # requires EEPROM Vcc >= 4.5V def open(self): """Open an SPI connection to a slave""" url = environ.get('FTDI_DEVICE', 'ftdi://ftdi:232h/1') self._spi.configure(url) def read_word(self, addr): # NOTE: Using SPI Mode 0. This really should have the FTDI # clock the read bits in on the rising edge, at least based on # my understanding of SPI. However, spi.py reads the bits on # the falling edge of the clock. For the 93LC56B, this is # exactly what we want. However, if spi.py ever gets changed, # will need to do writes and reads seperately with reads in # SPI Mode 1. port = self._spi.get_port(0, freq=self._freq, mode=self._mode, bidir=self._bidir) # byteswap() is to handle little endian data word = port.exchange(self._SBOC_read + [(addr & 0xFF)], 2) word = word.byteswap().tobytes() return word def read_all(self, readlen): # NOTE: Using SPI Mode 0. This really should have the FTDI # clock the read bits in on the rising edge, at least base don # my understanding of SPI. However, spi.py reads the bits on # the falling edge of the clock. For the 93LC56B, this is # exactly what we want. However, if spi.py ever gets changed, # will need to do writes and reads seperately with reads in # SPI Mode 1. port = self._spi.get_port(0, freq=self._freq, mode=self._mode, bidir=self._bidir) # readlen is byte len but extend it to the nearest 16-bit # boundary. readlen = ((readlen + 1) // 2) * 2 data = port.exchange(self._SBOC_read + [0x00], readlen) # byte swap to handle data in little endian (from: # https://stackoverflow.com/questions/36096292/ # efficient-way-to-swap-bytes-in-python) data[0::2], data[1::2] = data[1::2], data[0::2] #print('DATA: ', data) #words = spack('H'*(len(data)//2), data) return data.tobytes() def write_word(self, addr, word): port = self._spi.get_port(0, freq=self._freq, mode=self._mode, bidir=self._bidir) # Must first enable Erase/Write port.exchange(self._SBOC_ewen) # Send the word, LSB first (little endian) port.exchange(self._SBOC_write + [(addr & 0xFF), word & 0x0000ff, (word & 0x00ff00) >> 8]) # Wait the write time sleep(self._Twc) # send a stop condition if sent at least 1 read with stop # False. Data is thrown away. status = port.read(1) print('Status: {}'.format(status)) # Check the last bit of the last byte to make sure it is high # for Ready if ((status[-1] & 0x01) == 0x00): raise SpiIOError('ERROR: SPI Write never completed!') # Now disable Erase/Write since done with this write port.exchange(self._SBOC_ewds) # Write multiple bytes starting at byte address, addr. Length of # data must be a multiple of 2 since the EEPROM is 16-bits. So # extend data by 1 byte if this is not the case. def write(self, addr, data): if not isinstance(data, bytes): data = data.tobytes() # If addr is odd, raise an exception since it must be even if (addr & 0x01): err = "write addr must be even - the EEPROM is a 16-bit device" raise SpiIOError(err) wd_addr = (addr >> 1) # convert to word address # if the byte data is an odd number of bytes, force it to be # on 16-bit divisions if (len(data) & 0x01): err = "data length must be even - the EEPROM is a 16-bit device" raise SpiIOError(err) port = self._spi.get_port(0, freq=self._freq, mode=self._mode, bidir=self._bidir) # Must first enable Erase/Write port.exchange(self._SBOC_ewen) for idx in range(0, len(data), 2): # Send the word, MSB first port.exchange(self._SBOC_write + [(wd_addr & 0xFF), data[idx + 1], data[idx]]) # Wait the write time sleep(self._Twc) # send a stop condition if sent at least 1 read with stop # False. Data is thrown away. status = port.read(1) # Check the last bit of the last byte to make sure it is # high for Ready if ((status[-1] & 0x01) == 0x00): print('ERROR: Last write never completed! Aborting!') break # increment to the next word address wd_addr += 1 # Now disable Erase/Write since done with this write port.exchange(self._SBOC_ewds) def close(self): """Close the SPI connection""" self._spi.terminate()
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')
##Rx #write_one(0x0f , 0x00) #REG_0F_FIFO_RX_BASE_ADDR --> p.35. tte la memory FIFO assignee au Rx #write_one(0x12 , 0xff) #REG_12_IRQ_FLAGS clear ses flags #write_one(0x01 , 0x05) #mode RxContinuous #while True: #print("IRQ FLAGS:", format(read_one(0x12), '#010b')) #REG_12_IRQ_FLAGS # if(read_one(0x12) & 0x40): # RX_DONE flag is up! # lecture_rx() # write_one(0x12 , 0xff) #REG_12_IRQ_FLAGS clear ses flags # sleep(1) ###Tx payload = bytearray(b'9105dc77b881dbcca8b') write_one(0x0e, 0x00) #REG_0E_FIFO_TX_BASE_ADDR cf.p35 (si Rx en même temps???) write_one(0x0d, 0x00) #REG_0D_FIFO_ADDR_PTR slave.exchange( b'\x80' + payload ) #premier byte = FIFO_ADDR8_PTR (0x00) oré avec write (0x80) cf p.80 write_one(0x22, len(payload)) #REG_22_PAYLOAD_LENGTH write_one(0x01, 0x03) #mode Tx sleep(1) #attendre que le Tx se fasse write_one( 0x01, 0x01 ) #revenir en mode STDBY (0x01) (En Tx dapres datasheet p.36 devrait se faire seul) #print(hex(read_one(0x06))) #print(format(read_one(0x06), '#010b')) spi.terminate()
class SpiUnalignedTestCase(unittest.TestCase): """Basic test for SPI with non 8-bit multiple transfer It expects the following I/O setup: MOSI (AD1) connected to MISO (AD2) """ @classmethod def setUpClass(cls): cls.url = environ.get('FTDI_DEVICE', 'ftdi:///1') cls.debug = to_bool(environ.get('FTDI_DEBUG', 'off')) def setUp(self): self._spi = SpiController(cs_count=1) self._spi.configure(self.url, debug=self.debug) self._port = self._spi.get_port(0, freq=1E6, mode=0) def tearDown(self): """Close the SPI connection""" self._spi.terminate() def test_invalid_write(self): buf = b'\xff\xff' self.assertRaises(ValueError, self._port.write, buf, droptail=8) def test_bit_write(self): buf = b'\x0f' for loop in range(7): self._port.write(buf, droptail=loop + 1) def test_bytebit_write(self): buf = b'\xff\xff\x0f' for loop in range(7): self._port.write(buf, droptail=loop + 1) def test_invalid_read(self): self.assertRaises(ValueError, self._port.read, 1, droptail=8) self.assertRaises(ValueError, self._port.read, 2, droptail=8) def test_bit_read(self): # make MOSI stay to low level, so MISO samples 0 self._port.write([0x00]) for loop in range(7): data = self._port.read(1, droptail=loop + 1) self.assertEqual(len(data), 1) # make MOSI stay to high level, so MISO samples 1 self._port.write([0x01]) for loop in range(7): data = self._port.read(1, droptail=loop + 1) self.assertEqual(len(data), 1) def test_bytebit_read(self): self._port.write([0x00]) for loop in range(7): data = self._port.read(3, droptail=loop + 1) self.assertEqual(len(data), 3) self.assertEqual(data[-1], 0) self._port.write([0x01]) for loop in range(7): data = self._port.read(3, droptail=loop + 1) self.assertEqual(len(data), 3) def test_invalid_duplex(self): buf = b'\xff\xff' self.assertRaises(ValueError, self._port.exchange, buf, duplex=False, droptail=8) self.assertRaises(ValueError, self._port.exchange, buf, duplex=False, droptail=8) self.assertRaises(ValueError, self._port.exchange, buf, duplex=True, droptail=8) self.assertRaises(ValueError, self._port.exchange, buf, duplex=True, droptail=8) def test_bit_duplex(self): buf = b'\xcf' for loop in range(7): data = self._port.exchange(buf, duplex=True, droptail=loop + 1) self.assertEqual(len(data), 1) exp = buf[0] & ~((1 << (loop + 1)) - 1) # print(f'{data[0]:08b} {exp:08b}') self.assertEqual(data[0], exp) def test_bytebit_duplex(self): buf = b'\xff\xcf' for loop in range(7): data = self._port.exchange(buf, duplex=True, droptail=loop + 1) self.assertEqual(len(data), 2) exp = buf[-1] & ~((1 << (loop + 1)) - 1) # print(f'{data[-1]:08b} {exp:08b}') self.assertEqual(data[0], 0xFF) self.assertEqual(data[-1], exp)
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()