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 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 test_gpio_values(self): """Simple test to demonstrate bit-banging. """ if self.skip_loopback: raise SkipTest('Skip loopback test on multiport device') direction = 0xFF & ~((1 << 4) - 1) # 4 Out, 4 In gpio = GpioAsyncController() gpio.configure(self.url, direction=direction, frequency=1e6, initial=0x0) port = gpio.get_gpio() # useless, for API duck typing # legacy API: peek mode, 1 byte ingress = port.read() self.assertIsInstance(ingress, int) # peek mode always gives a single byte output ingress = port.read(peek=True) self.assertIsInstance(ingress, int) # stream mode always gives a bytes buffer port.write([0xaa for _ in range(256)]) ingress = port.read(100, peek=False, noflush=False) self.assertIsInstance(ingress, bytes) if not VirtLoader: # the virtual task may sometimes not be triggered soon enough self.assertGreater(len(ingress), 2) # direct mode is not available with multi-byte mode self.assertRaises(ValueError, port.read, 3, True) ingress = port.read(3) self.assertIsInstance(ingress, bytes) if not VirtLoader: # the virtual task may sometimes not be triggered soon enough self.assertGreater(len(ingress), 0) self.assertLessEqual(len(ingress), 3) port.write(0x00) port.write(0xFF) # only 8 bit values are accepted self.assertRaises(ValueError, port.write, 0x100) port.write([0x00, 0xFF, 0x00]) port.write(bytes([0x00, 0xFF, 0x00])) # only 8 bit values are accepted self.assertRaises(ValueError, port.write, [0x00, 0x100, 0x00]) # check direction API port.set_direction(0xFF, 0xFF & ~direction) 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()
def test_gpio_initial(self): """Check initial values. """ if self.skip_loopback: raise SkipTest('Skip loopback test on multiport device') direction = 0xFF & ~((1 << 4) - 1) # 4 Out, 4 In vftdi = self.loader.get_virtual_ftdi(1, 1) vport = vftdi.get_port(1) gpio = GpioAsyncController() for initial in (0xaf, 0xf0, 0x13, 0x00): gpio.configure(self.url, direction=direction, frequency=1e6, initial=initial) expect = (initial & 0xF0) | (initial >> 4) self.assertEqual(vport.gpio, expect) gpio.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()
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_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 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()
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()
def test_gpio_freeze(self): """Simple test to demonstrate freeze on close. For now, it requires a logic analyzer to verify the output, this is not automatically validated by SW """ direction = 0xFF & ~((1 << 4) - 1) # 4 Out, 4 In gpio = GpioAsyncController() gpio.configure(self.url, direction=direction, frequency=1e3, initial=0x0) port = gpio.get_gpio() # emit a sequence as a visual marker on b3,b2,b1,b0 port.write([x << 4 for x in range(16)]) sleep(0.01) # write 0b0110 to the port port.write(0x6 << 4) sleep(0.001) # close w/o freeze: all the outputs should be reset (usually 0b1111) # it might need pull up (or pull down) to observe the change as # output are not high-Z. gpio.close() sleep(0.01) gpio.configure(self.url, direction=direction, frequency=1e3, initial=0x0) port = gpio.get_gpio() # emit a sequence as a visual marker with on b3 and b1 port.write([(x << 4) & 0x90 for x in range(16)]) sleep(0.01) # write 0b0110 to the port port.write(0x6 << 4) sleep(0.01) # close w/ freeze: outputs should not be reset (usually 0b0110) gpio.close(True)
def readconfig(self): Settings.PinStatesMax = 7 Settings.PinStates = ["Default","Input","Reserved","Reserved","Output","Output-Low","Output-High","Special","Reserved"] getLogger('pyftdi.i2c').setLevel(ERROR) for b in range(len(Settings.Pinout)): if Settings.Pinout[b]["altfunc"] != 0 and Settings.Pinout[b]["startupstate"]>0 and Settings.Pinout[b]["startupstate"]<7: Settings.Pinout[b]["startupstate"] = -1 # set to default correctdependencies() self.gpioctrl = [] # ftdi gpio self.gpios = [] # rpigpio compatible layer self.i2cctrl = [] # ftdi i2c self.spictrl = [] # ftdi spi self.pinhandlers = [] devlist = get_ftdi_devices(2) devs = get_ftdi_configured_devices() for cd in range(len(devs)): notfound = True for rd in range(len(devlist)): if devlist[rd][0] == devs[cd]: notfound = False break if notfound: # configured device missing? if is_serialnumber(devs[cd]): # delete if serial based self.removedevpinout(devs[cd]) devs = get_ftdi_configured_devices() else:# replace if address based dt = "" rep = "" for p in range(len(Settings.Pinout)): if Settings.Pinout[p]["ftdevice"]==devs[cd]: dt = Settings.Pinout[p]["ftdevtype"] break if dt != "": for rd in range(len(devlist)): if not devlist[rd][0] in devs: # if not configured if (devlist[rd][1] == dt) and (is_serialnumber(devlist[rd][0])==False): # if found a similar, not configured one rep = devlist[rd][0] break misc.addLog(rpieGlobals.LOG_LEVEL_INFO,"Missing "+devs[cd]+" replaced with "+rep) if rep != "": # dynamic replacement for p in range(len(Settings.Pinout)): if Settings.Pinout[p]["ftdevice"]==devs[cd]: Settings.Pinout[p]["ftdevice"] = rep devs = get_ftdi_configured_devices() if len(devs)>0: for d in range(len(devs)): gtype = 0 hconfpin = 0 dirpin = 0 cter = 0 cpins = 0 for p in range(len(Settings.Pinout)): if Settings.Pinout[p]["ftdevice"]==devs[d]: try: if Settings.Pinout[p]["startupstate"]>-1: hconfpin = Settings.Pinout[p]["realpin"] if Settings.Pinout[p]["startupstate"] in [4,5,6]: dirpin = set_bit(dirpin,hconfpin,1) cpins |= (1 << int(c)) elif Settings.Pinout[p]["startupstate"] == 1: cpins |= (1 << int(c)) if Settings.Pinout[p]["altfunc"]>0: gtype = Settings.Pinout[p]["altfunc"] cter += 1 except: pass # print("readconfig ",gtype,hconfpin,dirpin)#debug self.gpioctrl.append({"ftdevice":devs[d],"o":None}) try: if gtype==0: if hconfpin<8: # old style will be enough self.gpioctrl[d]["o"] = GpioAsyncController() else: # wide style needed self.gpioctrl[d]["o"] = GpioMpsseController() try: reqfreq = 1.0E5 self.gpioctrl[d]["o"].configure(devs[d],direction=dirpin,frequency=reqfreq) except: reqfreq = 0 if reqfreq==0: self.gpioctrl[d]["o"].configure(devs[d],direction=dirpin) elif gtype==2: # spi cter = cter - 3 if cter<1: cter = 1 elif cter>5: cter = 5 self.spictrl.append({"ftdevice":devs[d],"o":None}) self.spictrl[-1]["o"] = SpiController(cs_count=cter) self.spictrl[-1]["o"].configure(devs[d],direction=dirpin) self.gpioctrl[d]["o"] = self.spictrl[-1]["o"].get_gpio() self.gpioctrl[d]["o"].set_direction(cpins,dirpin) elif gtype==3: # i2c self.i2cctrl.append({"ftdevice":devs[d],"o":None}) self.i2cctrl[-1]["o"] = I2cController() self.i2cctrl[-1]["o"].configure(devs[d],direction=dirpin) self.gpioctrl[d]["o"] = self.i2cctrl[-1]["o"].get_gpio() self.gpioctrl[d]["o"].set_direction(cpins,dirpin) self.gpios.append({"ftdevice":devs[d],"o":None}) if self.gpioctrl[d]["o"] is not None: try: freq = self.gpioctrl[d]["o"].frequency except: freq = 1.0E4 self.gpios[d]["o"] = FTDIGPIO(self.gpioctrl[d]["o"],freq) except Exception as e: print("gpio init err",e) self.pinhandlers = [] self.pinhandlers.append(None) for p in range(len(Settings.Pinout)): for d in range(len(devs)): if Settings.Pinout[p]["ftdevice"]==devs[d]: # if Settings.Pinout[p]["altfunc"]==2:#spi # self.pinhandlers.append(None) # elif Settings.Pinout[p]["altfunc"]==3:#i2c # self.pinhandlers.append(None) # else: self.pinhandlers.append(self.gpios[d]["o"]) if self.get_first_i2c()>-1: rpieGlobals.extender = 256 else: rpieGlobals.extender = 128 return True