def test_output_gpio(self): """Simple test to demonstrate ouput bit-banging on CBUS. You need a CBUS-capable FTDI (FT232R/FT232H/FT230X/FT231X), whose EEPROM has been configured to support GPIOs on CBUS0 and CBUS3. Hard-wiring is required to run this test: * CBUS0 (output) should be connected to CTS (input) * CBUS3 (output) should be connected to DSR (input) """ ftdi = Ftdi() ftdi.open_from_url(self.url) # sanity check: device should support CBUS feature self.assertEqual(ftdi.has_cbus, True) eeprom = FtdiEeprom() eeprom.connect(ftdi) # sanity check: device should have been configured for CBUS GPIOs self.assertEqual(eeprom.cbus_mask & 0b1001, 0b1001) # configure CBUS0 and CBUS3 as output ftdi.set_cbus_direction(0b1001, 0b1001) # no input pin available self.assertRaises(FtdiError, ftdi.get_cbus_gpio) for cycle in range(40): value = cycle & 0x3 # CBUS0 and CBUS3 cbus = ((value & 0x2) << 2) | value & 0x1 # for now, need a digital/logic analyzer to validate output ftdi.set_cbus_gpio(cbus) # CBUS0 is connected to CTS, CBUS3 to DSR # need to inverse logical level as RS232 uses negative logic sig = int(not ftdi.get_cts()) | (int(not ftdi.get_dsr()) << 1) self.assertEqual(value, sig)
def test_input_gpio(self): """Simple test to demonstrate input bit-banging on CBUS. You need a CBUS-capable FTDI (FT232R/FT232H/FT230X/FT231X), whose EEPROM has been configured to support GPIOs on CBUS0 and CBUS3. Hard-wiring is required to run this test: * CBUS0 (input) should be connected to RTS (output) * CBUS3 (input) should be connected to DTR (output) """ ftdi = Ftdi() ftdi.open_from_url(self.url) # sanity check: device should support CBUS feature self.assertEqual(ftdi.has_cbus, True) eeprom = FtdiEeprom() eeprom.connect(ftdi) # sanity check: device should have been configured for CBUS GPIOs self.assertEqual(eeprom.cbus_mask & 0b1001, 0b1001) # configure CBUS0 and CBUS3 as input ftdi.set_cbus_direction(0b1001, 0b0000) # no output pin available self.assertRaises(FtdiError, ftdi.set_cbus_gpio, 0) for cycle in range(40): rts = bool(cycle & 0x1) dtr = bool(cycle & 0x2) ftdi.set_rts(rts) ftdi.set_dtr(dtr) # need to inverse logical level as RS232 uses negative logic cbus = ~ftdi.get_cbus_gpio() sig = (cbus & 0x1) | ((cbus & 0x8) >> 2) value = cycle & 0x3 self.assertEqual(value, sig)
def _decode_cbus_x0600(self) -> None: cbus_gpio = 0 cbus_active = 0 bix = 0 while True: value = self._parent.eeprom[0x14 + bix] low, high = value & 0x0F, value >> 4 bit = 1 << (2 * bix) if FtdiEeprom.CBUS(low).name == 'GPIO': cbus_gpio |= bit cbus_active |= bit if bix == 2: break bit <<= 1 if FtdiEeprom.CBUS(high).name == 'GPIO': cbus_gpio |= bit cbus_active |= bit bix += 1 mask = (1 << self._parent.properties.cbuswidth) - 1 self._cbusp_gpio = cbus_gpio & mask self._cbusp_force = 0 self._cbusp_active = cbus_active & mask self.log.debug('x0600 config gpio %s, force %s, active %s', f'{self._cbusp_gpio:04b}', f'{self._cbusp_force:04b}', f'{self._cbusp_active:04b}')
def test_mirror_serial(self): """Verify serial string is properly duplicated across the 2 eeprom sectors """ eeprom = FtdiEeprom() eeprom.enable_mirroring(True) eeprom.open(self.url, ignore=True) eeprom.erase() eeprom.set_serial_number(self.TEST_SN) self._check_for_mirrored_eeprom_contents(eeprom)
def test_mirror_product(self): """Verify product string is properly duplicated across the 2 eeprom sectors """ eeprom = FtdiEeprom() eeprom.enable_mirroring(True) eeprom.open(self.url, ignore=True) eeprom.erase() eeprom.set_product_name(self.TEST_PROD_NAME) self._check_for_mirrored_eeprom_contents(eeprom)
def test_mirror_manufacturer(self): """Verify manufacturer string is properly duplicated across the 2 eeprom sectors """ eeprom = FtdiEeprom() eeprom.enable_mirroring(True) eeprom.open(self.url, ignore=True) eeprom.erase() eeprom.set_manufacturer_name(self.TEST_MANU_NAME) self._check_for_mirrored_eeprom_contents(eeprom)
def _decode_cbus_x0900(self) -> None: cbus_gpio = 0 cbus_force = 0 cbus_active = 0 for bix in range(5): value = self._parent.eeprom[0x18 + bix] low, high = value & 0x0F, value >> 4 bit = 1 << 2 * bix if FtdiEeprom.CBUSH(low).name == 'GPIO': cbus_gpio |= bit cbus_active |= bit elif FtdiEeprom.CBUSH(low).name == 'DRIVE0': cbus_force &= ~bit # useless, for code symmetry cbus_active |= bit elif FtdiEeprom.CBUSH(low).name == 'DRIVE1': cbus_force |= bit cbus_active |= bit bit <<= 1 if FtdiEeprom.CBUSH(high).name == 'GPIO': cbus_gpio |= bit cbus_active |= bit elif FtdiEeprom.CBUSH(high).name == 'DRIVE0': cbus_force &= ~bit # useless, for code symmetry cbus_active |= bit elif FtdiEeprom.CBUSH(high).name == 'DRIVE1': cbus_force |= bit cbus_active |= bit mask = (1 << self._parent.properties.cbuswidth) - 1 self._cbusp_gpio = cbus_gpio & mask self._cbusp_force = cbus_force & mask self._cbusp_active = cbus_active & mask self._cbus_map = {0: 5, 1: 6, 2: 8, 3: 9} self.log.debug('x0900 config gpio %s, force %s, active %s', f'{self._cbusp_gpio:04b}', f'{self._cbusp_force:04b}', f'{self._cbusp_active:04b}')
def _decode_cbus_x1000(self) -> None: cbus_gpio = 0 cbus_force = 0 cbus_active = 0 for bix in range(4): value = self._parent.eeprom[0x1A + bix] bit = 1 << bix if FtdiEeprom.CBUSX(value).name == 'GPIO': cbus_gpio |= bit cbus_active |= bit elif FtdiEeprom.CBUSX(value).name == 'DRIVE0': cbus_force &= ~bit # useless, for code symmetry cbus_active |= bit elif FtdiEeprom.CBUSX(value).name == 'DRIVE1': cbus_force |= bit cbus_active |= bit mask = (1 << self._parent.properties.cbuswidth) - 1 self._cbusp_gpio = cbus_gpio & mask self._cbusp_force = cbus_force & mask self._cbusp_active = cbus_active & mask self.log.debug('x1000 config gpio %s, force %s, active %s', f'{self._cbusp_gpio:04b}', f'{self._cbusp_force:04b}', f'{self._cbusp_active:04b}')
def test_compute_size_detects_mirror(self): """Verify the eeproms internal _compute_size method returns the correct bool value when it detects an eeprom mirror """ eeprom = FtdiEeprom() eeprom.open(self.url, ignore=True) _, mirrored = eeprom._compute_size([]) self.assertFalse(mirrored) test_buf = bytearray(eeprom.size) sector_mid = eeprom.size // 2 for ii in range(sector_mid): test_buf[ii] = ii % 255 test_buf[sector_mid+ii] = test_buf[ii] _, mirrored = eeprom._compute_size(bytes(test_buf)) self.assertTrue(mirrored) # change one byte and confirm failure test_buf[eeprom.size - 2] = test_buf[eeprom.size - 2] - 1 _, mirrored = eeprom._compute_size(bytes(test_buf)) self.assertFalse(mirrored)
def main(): """Main routine""" debug = False try: argparser = ArgumentParser(description=modules[__name__].__doc__) argparser.add_argument('device', nargs='?', default='ftdi:///?', help='serial port device name') argparser.add_argument('-x', '--hexdump', action='store_true', help='dump EEPROM content as ASCII') argparser.add_argument('-o', '--output', type=FileType('wt'), help='output ini file to save EEPROM content') argparser.add_argument('-s', '--serial-number', help='set serial number') argparser.add_argument('-m', '--manufacturer', help='set manufacturer name') argparser.add_argument('-p', '--product', help='set product name') argparser.add_argument('-e', '--erase', action='store_true', help='erase the whole EEPROM content') argparser.add_argument('-u', '--update', action='store_true', help='perform actual update, use w/ care') argparser.add_argument('-v', '--verbose', action='count', default=0, help='increase verbosity') argparser.add_argument('-d', '--debug', action='store_true', help='enable debug mode') args = argparser.parse_args() debug = args.debug if not args.device: argparser.error('Serial device not specified') loglevel = max(DEBUG, ERROR - (10 * args.verbose)) loglevel = min(ERROR, loglevel) if debug: formatter = Formatter( '%(asctime)s.%(msecs)03d %(name)-20s ' '%(message)s', '%H:%M:%S') else: formatter = Formatter('%(message)s') FtdiLogger.set_formatter(formatter) FtdiLogger.set_level(loglevel) FtdiLogger.log.addHandler(StreamHandler(stderr)) eeprom = FtdiEeprom() eeprom.open(args.device) if args.erase: eeprom.erase() if args.serial_number: eeprom.set_serial_number(args.serial_number) if args.manufacturer: eeprom.set_manufacturer_name(args.manufacturer) if args.product: eeprom.set_product_name(args.product) if args.hexdump: print(hexdump(eeprom.data)) if args.update: eeprom.commit(False) if args.verbose > 0: eeprom.dump_config() if args.output: eeprom.save_config(args.output) except (IOError, ValueError) as exc: print('\nError: %s' % exc, file=stderr) if debug: print(format_exc(chain=False), file=stderr) exit(1) except KeyboardInterrupt: exit(2)
def test_ft232h(self): with open('pyftdi/tests/resources/ft232h_x2.yaml', 'rb') as yfp: self.loader.load(yfp) eeprom = FtdiEeprom() eeprom.open('ftdi://::FT1ABC1/1', ignore=True) eeprom.erase() eeprom.initialize() # default EEPROM config does not have any CBUS configured as GPIO self.assertEqual(eeprom.cbus_pins, []) self.assertEqual(eeprom.cbus_mask, 0) eeprom.set_property('cbus_func_6', 'gpio') eeprom.set_property('cbus_func_9', 'gpio') # not yet committed self.assertEqual(eeprom.cbus_pins, []) self.assertEqual(eeprom.cbus_mask, 0) eeprom.sync() # committed self.assertEqual(eeprom.cbus_pins, [6, 9]) self.assertEqual(eeprom.cbus_mask, 0xA) eeprom.set_property('cbus_func_5', 'gpio') eeprom.set_property('cbus_func_8', 'gpio') eeprom.sync() self.assertEqual(eeprom.cbus_pins, [5, 6, 8, 9]) self.assertEqual(eeprom.cbus_mask, 0xF) # pin1 and pin3 is not configurable as GPIO self.assertRaises(ValueError, eeprom.set_property, 'cbus_func_1', 'gpio') self.assertRaises(ValueError, eeprom.set_property, 'cbus_func_3', 'gpio') eeprom.close()
def test_mirror_properties(self): """Check FtdiEeprom properties are accurate for a device that can mirror """ # properties should work regardless of if the mirror option is set # or not eeprom = FtdiEeprom() eeprom.open(self.url, ignore=True) self.assertTrue(eeprom.has_mirroring) self.assertEqual(eeprom.size // 2, eeprom.mirror_sector) eeprom.close() mirrored_eeprom = FtdiEeprom() mirrored_eeprom.enable_mirroring(True) mirrored_eeprom.open(self.url, ignore=True) self.assertTrue(mirrored_eeprom.has_mirroring) self.assertEqual(mirrored_eeprom.size // 2, mirrored_eeprom.mirror_sector) mirrored_eeprom.close()
def test_compute_size_does_not_mirror(self): """Verify the eeproms internal _compute_size method returns the correct bool value when it detects no mirroring. """ eeprom = FtdiEeprom() eeprom.open(self.url, ignore=True) _, mirrored = eeprom._compute_size([]) self.assertFalse(mirrored) eeprom.close() eeprom = FtdiEeprom() eeprom.open(self.url, ignore=False) _, mirrored = eeprom._compute_size([]) self.assertFalse(mirrored) eeprom.close()
def test_mirror_properties(self): """Check FtdiEeprom properties are accurate for a device that can not mirror. """ # properties should work regardless of if the mirror option is set # or not eeprom = FtdiEeprom() eeprom.open(self.url, ignore=True) self.assertFalse(eeprom.has_mirroring) with self.assertRaises(FtdiError): eeprom.mirror_sector eeprom.close() # even if mirroring is enabled, should still stay false mirrored_eeprom = FtdiEeprom() mirrored_eeprom.enable_mirroring(True) mirrored_eeprom.open(self.url, ignore=True) self.assertFalse(mirrored_eeprom.has_mirroring) with self.assertRaises(FtdiError): eeprom.mirror_sector mirrored_eeprom.close()
def test_varstr_combinations(self): """Verify various combinations of var strings are properly duplicated across the 2 eeprom sectors """ eeprom = FtdiEeprom() eeprom.enable_mirroring(True) eeprom.open(self.url, ignore=True) # manu + prod str eeprom.erase() eeprom.set_manufacturer_name(self.TEST_MANU_NAME) eeprom.set_product_name(self.TEST_PROD_NAME) self._check_for_mirrored_eeprom_contents(eeprom) # manu + sn str eeprom.erase() eeprom.set_manufacturer_name(self.TEST_MANU_NAME) eeprom.set_serial_number(self.TEST_SN) self._check_for_mirrored_eeprom_contents(eeprom) # prod + sn str eeprom.erase() eeprom.set_manufacturer_name(self.TEST_PROD_NAME) eeprom.set_serial_number(self.TEST_SN) self._check_for_mirrored_eeprom_contents(eeprom) # manu + prod + sn str eeprom.erase() eeprom.set_manufacturer_name(self.TEST_MANU_NAME) eeprom.set_manufacturer_name(self.TEST_PROD_NAME) eeprom.set_serial_number(self.TEST_SN) self._check_for_mirrored_eeprom_contents(eeprom)
def test_ft230x(self): with open('pyftdi/tests/resources/ft230x.yaml', 'rb') as yfp: self.loader.load(yfp) eeprom = FtdiEeprom() eeprom.open('ftdi:///1') # default EEPROM config does not have any CBUS configured as GPIO self.assertEqual(eeprom.cbus_pins, []) self.assertEqual(eeprom.cbus_mask, 0) # enable CBUS1 and CBUS3 as GPIO eeprom.set_property('cbus_func_1', 'gpio') eeprom.set_property('cbus_func_3', 'gpio') eeprom.sync() self.assertEqual(eeprom.cbus_pins, [1, 3]) self.assertEqual(eeprom.cbus_mask, 0xA) # enable CBUS0 and CBUS2 as GPIO eeprom.set_property('cbus_func_0', 'gpio') eeprom.set_property('cbus_func_2', 'gpio') # not yet committed self.assertEqual(eeprom.cbus_pins, [1, 3]) self.assertEqual(eeprom.cbus_mask, 0xA) eeprom.sync() # committed self.assertEqual(eeprom.cbus_pins, [0, 1, 2, 3]) self.assertEqual(eeprom.cbus_mask, 0xF) # invalid CBUS pin self.assertRaises(ValueError, eeprom.set_property, 'cbus_func_4', 'gpio') # invalid pin function self.assertRaises(ValueError, eeprom.set_property, 'cbus_func_0', 'gpio_') # invalid pin self.assertRaises(ValueError, eeprom.set_property, 'cbus_func', 'gpio') # valid alternative mode eeprom.set_property('cbus_func_0', 'txled') eeprom.set_property('cbus_func_1', 'rxled') eeprom.sync() self.assertEqual(eeprom.cbus_pins, [2, 3]) self.assertEqual(eeprom.cbus_mask, 0xC) eeprom.close()
def main(): """Main routine""" debug = False try: argparser = ArgumentParser(description=modules[__name__].__doc__) argparser.add_argument('device', nargs='?', default='ftdi:///?', help='serial port device name') argparser.add_argument('-x', '--hexdump', action='store_true', help='dump EEPROM content as ASCII') argparser.add_argument('-X', '--hexblock', type=int, help='dump EEPROM as indented hexa blocks') argparser.add_argument('-i', '--input', type=FileType('rt'), help='input ini file to load EEPROM content') argparser.add_argument('-l', '--load', default='all', choices=('all', 'raw', 'values'), help='section(s) to load from input file') argparser.add_argument('-o', '--output', type=FileType('wt'), help='output ini file to save EEPROM content') argparser.add_argument('-s', '--serial-number', help='set serial number') argparser.add_argument('-m', '--manufacturer', help='set manufacturer name') argparser.add_argument('-p', '--product', help='set product name') argparser.add_argument('-c', '--config', action='append', help='change/configure a property ' 'as key=value pair') argparser.add_argument('-e', '--erase', action='store_true', help='erase the whole EEPROM content') argparser.add_argument('-u', '--update', action='store_true', help='perform actual update, use w/ care') argparser.add_argument('-P', '--vidpid', action='append', help='specify a custom VID:PID device ID, ' 'may be repeated') argparser.add_argument('-V', '--virtual', type=FileType('r'), help='use a virtual device, specified as YaML') argparser.add_argument('-v', '--verbose', action='count', default=0, help='increase verbosity') argparser.add_argument('-d', '--debug', action='store_true', help='enable debug mode') args = argparser.parse_args() debug = args.debug if not args.device: argparser.error('Serial device not specified') loglevel = max(DEBUG, ERROR - (10 * args.verbose)) loglevel = min(ERROR, loglevel) if debug: formatter = Formatter( '%(asctime)s.%(msecs)03d %(name)-20s ' '%(message)s', '%H:%M:%S') else: formatter = Formatter('%(message)s') FtdiLogger.set_formatter(formatter) FtdiLogger.set_level(loglevel) FtdiLogger.log.addHandler(StreamHandler(stderr)) if args.virtual: from pyftdi.usbtools import UsbTools # Force PyUSB to use PyFtdi test framework for USB backends UsbTools.BACKENDS = ('pyftdi.tests.backend.usbvirt', ) # Ensure the virtual backend can be found and is loaded backend = UsbTools.find_backend() loader = backend.create_loader()() loader.load(args.virtual) try: add_custom_devices(Ftdi, args.vidpid) except ValueError as exc: argparser.error(str(exc)) eeprom = FtdiEeprom() eeprom.open(args.device) if args.erase: eeprom.erase() if args.input: eeprom.load_config(args.input, args.load) if args.serial_number: eeprom.set_serial_number(args.serial_number) if args.manufacturer: eeprom.set_manufacturer_name(args.manufacturer) if args.product: eeprom.set_product_name(args.product) for conf in args.config or []: if conf == '?': helpstr = ', '.join(sorted(eeprom.properties)) print( fill(helpstr, initial_indent=' ', subsequent_indent=' ')) exit(1) for sep in ':=': if sep in conf: name, value = conf.split(sep, 1) if not value: argparser.error('Configuration %s without value' % conf) helpio = StringIO() eeprom.set_property(name, value, helpio) helpstr = helpio.getvalue() if helpstr: print( fill(helpstr, initial_indent=' ', subsequent_indent=' ')) exit(1) break else: argparser.error('Missing name:value separator in %s' % conf) if args.hexdump: print(hexdump(eeprom.data)) if args.hexblock is not None: indent = ' ' * args.hexblock for pos in range(0, len(eeprom.data), 16): hexa = ' '.join( ['%02x' % x for x in eeprom.data[pos:pos + 16]]) print(indent, hexa, sep='') if args.update: if eeprom.commit(False): eeprom.reset_device() if args.verbose > 0: eeprom.dump_config() if args.output: eeprom.save_config(args.output) except (ImportError, IOError, NotImplementedError, ValueError) as exc: print('\nError: %s' % exc, file=stderr) if debug: print(format_exc(chain=False), file=stderr) exit(1) except KeyboardInterrupt: exit(2)
def load_eeprom(vid, pid, sn): ftdi = Ftdi() eeprom = FtdiEeprom() ftdi.open(vid, pid, serial=sn) eeprom.connect(ftdi) return eeprom