def test_gpio_loopback(self): """Check I/O. """ if self.skip_loopback: raise SkipTest('Skip loopback test on multiport device') gpio = GpioAsyncController() direction = 0xFF & ~((1 << 4) - 1) # 4 Out, 4 In gpio.configure(self.url, direction=direction, frequency=800000) for out in range(16): # print(f'Write {out:04b} -> {out << 4:08b}') gpio.write(out << 4) fback = gpio.read() lsbs = fback & ~direction msbs = fback >> 4 # check inputs match outputs self.assertEqual(lsbs, out) # check level of outputs match the ones written self.assertEqual(msbs, out) outs = list([(out & 0xf) << 4 for out in range(1000)]) gpio.write(outs) gpio.ftdi.read_data(512) for _ in range(len(outs)): _ = gpio.read(14) last = outs[-1] >> 4 for _ in range(10): fbacks = gpio.read(1000) for fback in fbacks: lsbs = fback & ~direction msbs = fback >> 4 # check inputs match last output self.assertEqual(lsbs, last) # check level of output match the last written self.assertEqual(msbs, last) gpio.close()
def test_gpio_baudate(self): # this test requires an external device (logic analyser or scope) to # check the bitbang read and bitbang write signal (BB_RD, BB_WR) and # mesure their frequency. The EEPROM should be configured to enable # those signal on some of the CBUS pins, for example. gpio = GpioAsyncController() direction = 0xFF & ~((1 << 4) - 1) # 4 Out, 4 In gpio.configure(self.url, direction=direction) buf = bytes([0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x00]) freqs = [50e3, 200e3, 1e6, 3e6] if gpio.ftdi.is_H_series: freqs.extend([6e6, 10e6, 12e6]) gpio.read(128) for freq in freqs: # set the bitbang refresh rate gpio.set_frequency(freq) self.assertEqual(gpio.frequency, freq) # be sure to leave enough time to purge buffers (HW FIFO) or # the frequency changes occur on the current buffer... gpio.write(buf) gpio.read(128) sleep(0.01) gpio.close()
def test_gpio_peek(self): """Check I/O. """ gpio_in, gpio_out = GpioAsyncController(), GpioAsyncController() gpio_in.configure(self.urls[0], direction=0x00, frequency=1e6) gpio_out.configure(self.urls[1], direction=0xFF, frequency=1e6) for out in range(256): gpio_out.write(out) outv = gpio_out.read() inv = gpio_in.read() # check inputs match outputs self.assertEqual(inv, out) # check level of outputs match the ones written self.assertEqual(outv, out) gpio_in.close() gpio_out.close()
def transmitAsync(self, halfPeriod=0.005): gpio = GpioAsyncController() gpio.configure(self.device, self.directionArray[0]) for i in range(len(self.txArray)): if (self.directionArray[i] != self.directionArray[i-1]): time.sleep(halfPeriod) gpio.close() gpio = GpioAsyncController() gpio.configure(self.device, self.directionArray[i]) time.sleep(halfPeriod) #print("new direction: "+hex(self.directionArray[i])) # read the state of the port self.rxArray.append( gpio.read() ) #print("read: "+hex(self.rxArray[-1])) # write to the outputs (0 is OK to write to inputs) gpio.write(self.txArray[i]) #print("write: "+hex(self.txArray[i])) # wait half a clock cycle time.sleep(halfPeriod) gpio.close()
def test_gpio_stream(self): """Check I/O streaming """ if VirtLoader: # this would require to synchronize virtual clock between all ports # which is not supported by the virtual framework raise SkipTest('Skip gpio stream with virtual device') gpio_in, gpio_out = GpioAsyncController(), GpioAsyncController() gpio_in.configure(self.urls[0], direction=0x00, frequency=1e4) gpio_out.configure(self.urls[1], direction=0xFF, frequency=1e4) outs = bytes(range(256)) gpio_out.write(outs) # read @ same speed (and same clock source, so no jitter), flushing # the byffer which has been filled since the port has been opened ins = gpio_in.read(len(outs)) qout = deque(outs) ifirst = ins[0] # the inout stream should be a copy of the output stream, minus a # couple of missing samples that did not get captured while output # was streaming but read command has not been yet received. while qout: if qout[0] == ifirst: break qout.popleft() # offset is the count of missed bytes offset = len(ins) - len(qout) self.assertGreater(offset, 0) # no more output than input self.assertLess(offset, 16) # seems to be in the 6..12 range # print('Offset', offset) # check that the remaining sequence match for sout, sin in zip(qout, ins): #print(f'{sout:08b} --> {sin:08b}') # check inputs match outputs self.assertEqual(sout, sin) gpio_in.close() gpio_out.close()
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()
from pyftdi.ftdi import Ftdi from pyftdi.gpio import (GpioAsyncController, GpioSyncController, GpioMpsseController) gpio1 = GpioAsyncController() gpio2 = GpioAsyncController() gpio1.configure('ftdi:///1', direction=0x95) gpio2.configure('ftdi:///2', direction=0xbb) # later, reconfigure BD2 as input and BD7 as output # gpio.set_direction(0x84, 0x80) pins1 = gpio1.read() pins1 &= ~gpio1.direction pins2 = gpio2.read() pins2 &= ~gpio2.direction print("pins1:", hex(pins1)) print("pins2:", hex(pins2)) gpio1.close() gpio2.close()