Beispiel #1
0
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()
Beispiel #2
0
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
Beispiel #3
0
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()
Beispiel #4
0
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()
Beispiel #5
0
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()
Beispiel #6
0
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()
Beispiel #7
0
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()
Beispiel #8
0
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()
Beispiel #9
0
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()
Beispiel #10
0
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()
Beispiel #11
0
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()
Beispiel #12
0
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)
Beispiel #13
0
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()
Beispiel #14
0
class SPIDriver:
    """
    Class that contains the driver for the FTDI SPI + GPIO.
    """
    def __init__(self):
        self.ctrl = None
        self.slave = None
        print('Starting driver ...')
        self.begin()

    def begin(self):
        try:
            self.ctrl = SpiController(cs_count=1)
            s = 'ftdi://0x0403:0x6011/1'
            self.ctrl.configure(s)

            self.slave = self.ctrl.get_port(cs=0, freq=6E6, mode=3)
            self.slave.set_frequency(8000000)

            self.gpio = self.ctrl.get_gpio()
            self.gpio.set_direction(
                0x10, 0x10)  # direction for the fet controller is OUTPUT (1)
            self.gpio.write(0x10)
            time.sleep(0.1)
            # to account for using separate ports for power and comms:
            self.backup_ctrl = SpiController(cs_count=1)
            self.backup_gpio = None
            if not s == 'ftdi://0x0403:0x6011/1':
                backup_s = 'ftdi://0x0403:0x6011/1'
                self.backup_ctrl.configure(backup_s)
                self.backup_gpio = self.backup_ctrl.get_gpio()
                self.backup_gpio.set_direction(0x10, 0x10)
                self.backup_gpio.write(0x10)
                time.sleep(1)
        except Exception as err:
            print('Error in initialising the FTDI driver ...:', err)

    def write_data_out(self, buffer):
        if not isinstance(buffer, bytes):
            raise Exception  # TODO define exception
        try:
            self.slave.write(buffer)
            return True
        except Exception as err:
            #TODO convert to Logging
            print('Error in writing data out to FTDI SPI...:', err)
            return False

    def set_power_on(self):
        self.gpio.write(0x10)
        if self.backup_gpio is not None:
            self.backup_gpio.write(0x10)

    def set_power_off(self):
        self.gpio.write(0x00)
        if self.backup_gpio is not None:
            self.backup_gpio.write(0x00)

    def terminate(self):
        self.ctrl.terminate()
        print('terminated')
Beispiel #15
0
##Rx
#write_one(0x0f , 0x00) #REG_0F_FIFO_RX_BASE_ADDR --> p.35. tte la memory FIFO assignee au Rx
#write_one(0x12 , 0xff) #REG_12_IRQ_FLAGS clear ses flags
#write_one(0x01 , 0x05) #mode RxContinuous
#while True:
#print("IRQ FLAGS:", format(read_one(0x12), '#010b'))  #REG_12_IRQ_FLAGS
#	if(read_one(0x12) & 0x40): # RX_DONE flag is up!
#		lecture_rx()
#		write_one(0x12 , 0xff) #REG_12_IRQ_FLAGS clear ses flags
#	sleep(1)

###Tx
payload = bytearray(b'9105dc77b881dbcca8b')
write_one(0x0e,
          0x00)  #REG_0E_FIFO_TX_BASE_ADDR cf.p35 (si Rx en même temps???)
write_one(0x0d, 0x00)  #REG_0D_FIFO_ADDR_PTR
slave.exchange(
    b'\x80' + payload
)  #premier byte = FIFO_ADDR8_PTR (0x00) oré avec write (0x80) cf p.80
write_one(0x22, len(payload))  #REG_22_PAYLOAD_LENGTH
write_one(0x01, 0x03)  #mode Tx
sleep(1)  #attendre que le Tx se fasse
write_one(
    0x01, 0x01
)  #revenir en mode STDBY (0x01) (En Tx dapres datasheet p.36 devrait se faire seul)

#print(hex(read_one(0x06)))
#print(format(read_one(0x06), '#010b'))

spi.terminate()
Beispiel #16
0
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)
Beispiel #17
0
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()
Beispiel #18
0
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)