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
def __init__(self): # Instanciate a SPI controller spi = SpiController() spi.configure('ftdi://ftdi:232h:FT0KGOUA/1') # The sensor AMT203-V is the slave # https://www.cui.com/product/resource/amt20.pdf self.slave = spi.get_port(cs=0, freq=spi.frequency / 32, mode=0)
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 get_flash_device(url, cs=0, freq=None): """Obtain an instance of the detected flash device""" ctrl = SpiController(silent_clock=False) ctrl.configure(url) spi = ctrl.get_port(cs, freq) jedec = SerialFlashManager.read_jedec_id(spi) if not jedec: # it is likely that the latency setting is too low if this # condition is encountered raise SerialFlashUnknownJedec("Unable to read JEDEC Id") flash = SerialFlashManager._get_flash(spi, jedec) flash.set_spi_frequency(freq) return flash
def main(argv): """ Entry point for bme280-monitor.py Arguments: argv: command line arguments """ now = datetime.utcnow().strftime("%FT%TZ") args = process_arguments(argv) # Connect to the sensor. ctrl = SpiController() ctrl.configure(args.device) spi = ctrl.get_port(Port[args.cs].value) spi.set_frequency(args.frequency) try: bme280 = Bme280spi(spi) except RuntimeError as err: print(err) sys.exit(1) if "{}" in args.path: filename = args.path.format(now) else: filename = args.path # Write datafile header. with open(filename, "a") as datafile: datafile.write("# BME280 data.\n# Started monitoring at {}.\n".format(now)) datafile.write("# Per line, the data items are:\n") datafile.write("# * UTC date and time in ISO8601 format\n") datafile.write("# * Temperature in °C\n") datafile.write("# * Pressure in Pa\n") datafile.write("# * Relative humidity in %.\n") # Read and write the data. try: # Throw the first read away; bogis pressure value. bme280.read() while True: now = datetime.utcnow().strftime("%FT%TZ") temperature, pressure, humidity = bme280.read() line = "{} {:.2f} {:.0f} {:.2f}\n".format( now, temperature, pressure, humidity ) with open(filename, "a") as datafile: datafile.write(line) time.sleep(args.interval) except KeyboardInterrupt: sys.exit(1)
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 BoardControlBase(object): def __init__(self, addr='ftdi://ftdi:2232h/1', spi_frequency=30e6, spi_cs=None): # SPI link self.spi_frequency = spi_frequency self.spi = SpiController(cs_count=3) self.spi.configure(addr) if spi_cs is not None: self.slave = self.spi.get_port(cs=spi_cs, freq=self.spi_frequency, mode=0) else: self.slave = self._spi_probe() def _spi_probe(self): for cs in [0, 2]: port = self.spi.get_port(cs=cs, freq=self.spi_frequency, mode=0) r = port.exchange(b'\x00', duplex=True)[0] if r != 0xff: return port raise RunttimeError('Automatic SPI CS probe failed') def reg_w16(self, reg, v): self.slave.exchange(struct.pack('>BH', reg, v)) def reg_w8(self, reg, v): self.slave.exchange(struct.pack('>BB', reg, v)) def reg_burst(self, reg, data): self.slave.exchange(bytearray([reg]) + data) def read_status(self): rv = self.slave.exchange(bytearray(2), duplex=True) return rv[0] | rv[1]
def setConfigure(ftdi_interface, ccp_header_file): spi = SpiController() spi.configure(ftdi_interface) global slave slave = spi.get_port(cs=0, freq=12E6, mode=0) global ccp_header ccp_header = cpp_header_parser.read_header(ccp_header_file) def makeup_data(keylist): return [ReadFromCppHeader(key) for key in keylist] blockWrite(ccp_header["gpo_data_15_to_0"], makeup_data(configDesignVals)) blockWrite(ccp_header["port_cfg_00"], makeup_data(portConfigDesignVals)) blockWrite(ccp_header["dac_data_port_00"], makeup_data(dacDesignVals))
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
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()
def main(argv): """ Entry point for bmp280-monitor.py Arguments: argv: command line arguments """ now = datetime.utcnow().strftime('%FT%TZ') args = process_arguments(argv) # Connect to the sensor. ctrl = SpiController() ctrl.configure(args.device) spi = ctrl.get_port(Port[args.cs].value) spi.set_frequency(args.frequency) try: bmp280 = Bmp280spi(spi) except RuntimeError as err: print(err) sys.exit(1) if '{}' in args.path: filename = args.path.format(now) else: filename = args.path # Write datafile header. with open(filename, 'a') as datafile: datafile.write( '# BMP280 data.\n# Started monitoring at {}.\n'.format(now)) datafile.write('# Per line, the data items are:\n') datafile.write('# * UTC date and time in ISO8601 format\n') datafile.write('# * Temperature in °C\n') datafile.write('# * Pressure in Pa\n') # Read and write the data. try: while True: now = datetime.utcnow().strftime('%FT%TZ') temperature, pressure = bmp280.read() line = '{} {:.2f} {:.0f}\n'.format(now, temperature, pressure) with open(filename, 'a') as datafile: datafile.write(line) time.sleep(args.interval) except KeyboardInterrupt: sys.exit(1)
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 PanelControl(object): def __init__(self, spi_frequency=5e6): # Params self.spi_frequency = spi_frequency # SPI link self.spi = SpiController(cs_count=3) self.spi.configure('ftdi://ftdi:2232h/1') self.slave = self.spi.get_port(cs=2, freq=self.spi_frequency, mode=0) def reg_w16(self, reg, v): self.slave.exchange([reg, v >> 8, v & 0xff]) def reg_w8(self, reg, v): self.slave.exchange([reg, v]) def read_status(self): rv = self.slave.exchange([0x00, 0x00], duplex=True) return rv[0] | rv[1] def send_data(self, data): self.slave.exchange([0x80] + data) def send_frame(self, frame, width=64, height=64, bits=16): # Size of a line ll = width * bits // 8 # Scan all line for y in range(height): # Send write command to line buffer self.send_data(list(frame[y*ll:(y+1)*ll])) # Swap line buffer & Write it to line y of back frame buffer self.reg_w8(0x03, y) # Send frame swap command panel.reg_w8(0x04, 0x00) # Wait for the frame swap to occur while (panel.read_status() & 0x02 == 0): pass
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 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 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 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 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()
def lecture_rx(): rx_len = read_one(0x13) #REG_13_RX_NB_BYTES -> taille du packet recu cur_adr = read_one(0x10) #REG_10_FIFO_RX_CURRENT_ADDR print("RX_LEN: %s - CUR_ADR: %s" % (str(rx_len), hex(cur_adr))) write_one(0x0d, cur_adr) #REG_0D_FIFO_ADDR_PTR "FIFO SPI pointer" out = slave.exchange([0x00], rx_len) #REG_00_FIFO = 0x00 "FIFO r/w access" if chr( out[0] ) == 's': #protection par lecture du premier byte (pour ne pas Rx des msgs d'autres senders print(out) #initialisation SPI spi = SpiController() spi.configure('ftdi://ftdi:2232h/1') slave = spi.get_port(cs=0, freq=10E6, mode=0) print("OP_MODE=", format(read_one(0x01), '#010b')) write_one( 0x01, 0x80 ) #0x01 = REG_01_OP_MODE -> 0b10000000 -> long range mode (LoRa) (p 108 ) #sleep(0.1) write_one(0x01, 0x01) #mode STDBY (p108) #sleep(0.1) #set frequency frf = int((frf * 1000000.0) / FSTEP) write_one(0x06, (frf >> 16) & 0xff) write_one(0x07, (frf >> 8) & 0xff) write_one(0x08, frf & 0xff) #sleep(0.1)
if len(sys.argv) < 2: 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://::/1') slave = spi.get_port(cs=0, freq=12E6, mode=0) # Chip select is 0 -- corresponds to D3 slave.write([CMD_RESET_CHIP]) jedec = slave.exchange([CMD_JEDEC_DATA], 3) print("JEDEC = {}".format(binascii.hexlify(jedec))) if jedec[0:1] != bytes.fromhex('ef'): print("Winbond SRAM not found") sys.exit() report_status(jedec) print("Erasing chip...") slave.write([CMD_WRITE_ENABLE]) slave.write([CMD_ERASE_CHIP])
class SerialFlashManager(object): """Serial flash manager. Automatically detects and instanciate the proper flash device class based on the JEDEC identifier which is read out from the device itself. """ CMD_JEDEC_ID = 0x9F def __init__(self, vendor, product, interface=1): self._ctrl = SpiController(silent_clock=False) self._ctrl.configure(vendor, product, interface) def get_flash_device(self, cs=0): """Obtain an instance of the detected flash device""" spi = self._ctrl.get_port(cs) jedec = SerialFlashManager.read_jedec_id(spi) if not jedec: # it is likely that the latency setting is too low if this # condition is encountered raise SerialFlashUnknownJedec("Unable to read JEDEC Id") return SerialFlashManager._get_flash(spi, jedec) @staticmethod def read_jedec_id(spi): """Read flash device JEDEC identifier (3 bytes)""" jedec_cmd = Array('B', [SerialFlashManager.CMD_JEDEC_ID]) return spi.exchange(jedec_cmd, 3).tostring() @staticmethod def _get_flash(spi, jedec): contents = sys.modules[__name__].__dict__ devices = [] plugin_dir = os.path.dirname(os.path.realpath(__file__)) plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if (x.endswith(".py") and not x.startswith("__init__"))] sys.path.insert(0, plugin_dir) for p in plugin_files: mod = __import__(p) # print "mod: " + str(mod) sf = getattr(mod, "SerialFlash") # for sp in sf.__subclasses__(): # print "subclass: " + str(sp) # print "plugin file: " + str(p) # print "sf: " + str(sf) for name in dir (mod): if name.startswith('_'): continue obj = getattr(mod, name) if obj not in sf.__subclasses__(): continue # for p in SerialFlash.__subclasses__(): # print "subclass: " + p # print "name: " + name # print "base classes: " + str(inspect.getmro(obj)) # print "\tclass: %s is class %s" % (str(obj), str(SerialFlash)) # if not isinstance (obj, SerialFlash): # continue # if ("SerialFlash" in str(inspect.getmro(obj))): # print "found subclass" # # for bn in inspect.getmro(obj): # print "base name: " + str(bn) # # print "type: " + str(type(obj)) # print "sf type: " + str(type(SerialFlash)) # # if isclass(obj) and issubclass(obj, SerialFlash) and obj is not SerialFLash : # if isclass(obj) and "SerialFLash" not in str(obj) : # print "\t\tappend name: " + name devices.append(obj) for device in devices: if device.match(jedec): return device(spi, jedec) raise SerialFlashUnknownJedec(jedec)
return get_status(device) & SR_WIP if len(sys.argv) < 2: 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() spi.configure('ftdi://::/1') slave = spi.get_port(cs=0) # Chip select is 0 -- corresponds to D3 vendor = slave.exchange([RAVENNA_REG_READ, 0x01], 1) print("vendor = {}".format(binascii.hexlify(vendor))) mfg = slave.exchange([RAVENNA_REG_READ, 0x02], 1) print("mfg = {}".format(binascii.hexlify(mfg))) product = slave.exchange([RAVENNA_REG_READ, 0x03], 1) print("product = {}".format(binascii.hexlify(product))) slave.write([RAVENNA_REG_WRITE, 0x07, 0x01]) slave.write([RAVENNA_REG_WRITE, 0x07, 0x00]) slave.write([RAVENNA_PASSTHRU, CMD_RESET_CHIP])
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()
import time import sys from pyftdi.spi import SpiController import FTDISPI import JSONFile spi = SpiController() spi.configure('ftdi:///2') slave = spi.get_port( \ cs=0, \ freq=1e6, \ mode=0 \ ) dac = FTDISPI.Interface( \ FTDISPI.MPSSE(slave), \ defaultMap = "DAC38RF8x.json", \ currentState = "DAC_current_state.json", \ previousState = "DAC_previous_state.json", ) def VCO_OK(Tj, LFVOLT): if (Tj >= 108 and (LFVOLT == 5 or LFVOLT == 6)): return True elif (Tj >= 92 and (LFVOLT == 4 or LFVOLT == 5)):
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)
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:]