Пример #1
0
class I2cController:
    """I2c master.

       An I2c master should be instanciated only once for each FTDI port that
       supports MPSSE (one or two ports, depending on the FTDI device).

       Once configured, :py:func:`get_port` should be invoked to obtain an I2c
       port for each I2c slave to drive. I2c port should handle all I/O requests
       for its associated HW slave.

       It is not recommended to use I2cController :py:func:`read`,
       :py:func:`write` or :py:func:`exchange` directly.

       * ``SCK`` should be connected to ``A*BUS0``, and ``A*BUS7`` if clock
           stretching mode is enabled
       * ``SDA`` should be connected to ``A*BUS1`` **and** ``A*BUS2``
    """

    LOW = 0x00
    HIGH = 0xff
    BIT0 = 0x01
    IDLE = HIGH
    SCL_BIT = 0x01  #AD0
    SDA_O_BIT = 0x02  #AD1
    SDA_I_BIT = 0x04  #AD2
    SCL_FB_BIT = 0x80  #AD7
    PAYLOAD_MAX_LENGTH = 0x10000  # 16 bits max
    HIGHEST_I2C_ADDRESS = 0x78
    DEFAULT_BUS_FREQUENCY = 100000.0
    HIGH_BUS_FREQUENCY = 400000.0
    RETRY_COUNT = 3

    I2C_MASK = SCL_BIT | SDA_O_BIT | SDA_I_BIT
    I2C_MASK_CS = SCL_BIT | SDA_O_BIT | SDA_I_BIT | SCL_FB_BIT
    I2C_DIR = SCL_BIT | SDA_O_BIT

    I2C_100K = I2CTimings(4.0E-6, 4.7E-6, 4.0E-6, 4.7E-6)
    I2C_400K = I2CTimings(0.6E-6, 0.6E-6, 0.6E-6, 1.3E-6)
    I2C_1M = I2CTimings(0.26E-6, 0.26E-6, 0.26E-6, 0.5E-6)

    def __init__(self):
        self._ftdi = Ftdi()
        self._lock = Lock()
        self.log = getLogger('pyftdi.i2c')
        self._gpio_port = None
        self._gpio_dir = 0
        self._gpio_low = 0
        self._gpio_mask = 0
        self._i2c_mask = 0
        self._wide_port = False
        self._slaves = {}
        self._retry_count = self.RETRY_COUNT
        self._frequency = 0.0
        self._immediate = (Ftdi.SEND_IMMEDIATE, )
        self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0)
        self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0)
        self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0)
        self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH)
        self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW)
        self._ck_delay = 1
        self._tristate = None
        self._tx_size = 1
        self._rx_size = 1
        self._ck_hd_sta = 0
        self._ck_su_sto = 0
        self._ck_idle = 0

    def set_retry_count(self, count):
        """Change the default retry count when a communication error occurs,
           before bailing out.
           :param int count: count of retries
        """
        if not isinstance(count, int) or not 0 < count <= 16:
            raise ValueError('Invalid retry count')
        self._retry_count = count

    def configure(self, url, **kwargs):
        """Configure the FTDI interface as a I2c master.

           :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1'
           :param kwargs: options to configure the I2C bus

           Accepted options:

           * ``frequency`` float value the I2C bus frequency in Hz
           * ``clockstretching`` boolean value to enable clockstreching.
             xD7 (GPIO7) pin should be connected back to xD0 (SCK)
        """
        for k in ('direction', 'initial'):
            if k in kwargs:
                del kwargs[k]
        if 'frequency' in kwargs:
            frequency = kwargs['frequency']
            del kwargs['frequency']
        else:
            frequency = self.DEFAULT_BUS_FREQUENCY
        # Fix frequency for 3-phase clock
        if frequency <= 100E3:
            timings = self.I2C_100K
        elif frequency <= 400E3:
            timings = self.I2C_100K
        else:
            timings = self.I2C_100K
        if 'clockstretching' in kwargs:
            clkstrch = bool(kwargs['clockstretching'])
            del kwargs['clockstretching']
        else:
            clkstrch = False
        self._ck_hd_sta = self._compute_delay_cycles(timings.t_hd_sta)
        self._ck_su_sto = self._compute_delay_cycles(timings.t_su_sto)
        ck_su_sta = self._compute_delay_cycles(timings.t_su_sta)
        ck_buf = self._compute_delay_cycles(timings.t_buf)
        self._ck_idle = max(ck_su_sta, ck_buf)
        self._ck_delay = ck_buf
        # as 3-phase clock frequency mode is required for I2C mode, the
        # FTDI clock should be adapted to match the required frequency.
        frequency = self._ftdi.open_mpsse_from_url(
            url,
            direction=self.I2C_DIR,
            initial=self.IDLE,
            frequency=(3.0 * frequency) / 2.0,
            **kwargs)
        self._frequency = (2.0 * frequency) / 3.0
        self._tx_size, self._rx_size = self._ftdi.fifo_sizes
        self._ftdi.enable_adaptive_clock(clkstrch)
        self._ftdi.enable_3phase_clock(True)
        try:
            self._ftdi.enable_drivezero_mode(self.SCL_BIT | self.SDA_O_BIT
                                             | self.SDA_I_BIT)
        except FtdiFeatureError:
            self._tristate = (Ftdi.SET_BITS_LOW, self.LOW, self.SCL_BIT)
        self._wide_port = self._ftdi.has_wide_port
        if clkstrch:
            self._i2c_mask = self.I2C_MASK_CS
        else:
            self._i2c_mask = self.I2C_MASK

    def terminate(self):
        """Close the FTDI interface.
        """
        with self._lock:
            if self._ftdi:
                self._ftdi.close()
                self._ftdi = None

    def get_port(self, address):
        """Obtain an I2cPort to drive an I2c slave.

           :param int address: the address on the I2C bus
           :return: an I2cPort instance
           :rtype: :py:class:`I2cPort`
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address not in self._slaves:
            self._slaves[address] = I2cPort(self, address)
        return self._slaves[address]

    def get_gpio(self):
        """Retrieve the GPIO port.

           :return GPIO port
        """
        with self._lock:
            if not self._ftdi:
                raise I2cIOError("FTDI controller not initialized")
            if not self._gpio_port:
                self._gpio_port = I2cGpioPort(self)
            return self._gpio_port

    @property
    def configured(self):
        """Test whether the device has been properly configured.

           :return: True if configured
        """
        return bool(self._ftdi) and bool(self._start)

    @classmethod
    def validate_address(cls, address):
        """Assert an I2C slave address is in the supported range.
           None is a special bypass address.

           :param address: the address on the I2C bus
           :type address: int or None
           :raise I2cIOError: if the I2C slave address is not supported
        """
        if address is None:
            return
        if address > cls.HIGHEST_I2C_ADDRESS:
            raise I2cIOError("No such I2c slave: 0x%02x" % address)

    @property
    def frequency_max(self):
        """Provides the maximum I2c clock frequency.
        """
        return self._ftdi.frequency_max

    @property
    def frequency(self):
        """Provides the current I2c clock frequency.

           :return: the I2C bus clock
           :rtype: float
        """
        return self._frequency

    @property
    def direction(self):
        """Provide the FTDI GPIO direction"""
        return self.I2C_DIR | self._gpio_dir

    @property
    def gpio_pins(self):
        """Report the configured GPIOs as a bitfield"""
        with self._lock:
            return self._gpio_mask

    @property
    def gpio_all_pins(self):
        """Report the addressable GPIOs as a bitfield"""
        mask = (1 << self.width) - 1
        with self._lock:
            return mask & ~self._i2c_mask

    @property
    def width(self):
        """Report the FTDI count of addressable pins.

           :return: the count of IO pins (including I2C ones).
        """
        return 16 if self._wide_port else 8

    def read(self, address, readlen=1, relax=True):
        """Read one or more bytes from a remote slave

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param int readlen: count of bytes to read out.
           :param bool relax: not used
           :return: read bytes
           :rtype: array
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)

           Most I2C devices require a register address to read out
           check out the exchange() method.
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            i2caddress |= self.BIT0
        retries = self._retry_count
        do_epilog = True
        with self._lock:
            while True:
                try:
                    self._do_prolog(i2caddress)
                    data = self._do_read(readlen)
                    do_epilog = relax
                    return data
                except I2cNackError:
                    retries -= 1
                    if not retries:
                        raise
                    self.log.warning('Retry read')
                finally:
                    if do_epilog:
                        self._do_epilog()

    def write(self, address, out, relax=True):
        """Write one or more bytes to a remote slave

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param out: the byte buffer to send
           :type out: array or bytes or list(int)
           :param bool relax: whether to relax the bus (emit STOP) or not
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)

           Most I2C devices require a register address to write into. It should
           be added as the first (byte)s of the output buffer.
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
        retries = self._retry_count
        do_epilog = True
        with self._lock:
            while True:
                try:
                    self._do_prolog(i2caddress)
                    self._do_write(out)
                    do_epilog = relax
                    return
                except I2cNackError:
                    retries -= 1
                    if not retries:
                        raise
                    self.log.warning('Retry write')
                finally:
                    if do_epilog:
                        self._do_epilog()

    def exchange(self, address, out, readlen=0, relax=True):
        """Send a byte sequence to a remote slave followed with
           a read request of one or more bytes.

           This command is useful to tell the slave what data
           should be read out.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param out: the byte buffer to send
           :type out: array or bytes or list(int)
           :param int readlen: count of bytes to read out.
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: read bytes
           :rtype: array
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if readlen < 1:
            raise I2cIOError('Nothing to read')
        if readlen > (self.PAYLOAD_MAX_LENGTH / 3 - 1):
            raise I2cIOError("Input payload is too large")
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
        retries = self._retry_count
        do_epilog = True
        with self._lock:
            while True:
                try:
                    self._do_prolog(i2caddress)
                    self._do_write(out)
                    self._do_prolog(i2caddress | self.BIT0)
                    if readlen:
                        data = self._do_read(readlen)
                    do_epilog = relax
                    return data
                except I2cNackError:
                    retries -= 1
                    if not retries:
                        raise
                    self.log.warning('Retry exchange')
                finally:
                    if do_epilog:
                        self._do_epilog()

    def poll(self, address, write=False, relax=True):
        """Poll a remote slave, expect ACK or NACK.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param bool write: poll in write mode (vs. read)
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: True if the slave acknowledged, False otherwise
           :rtype: bool
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            if not write:
                i2caddress |= self.BIT0
        do_epilog = True
        with self._lock:
            try:
                self._do_prolog(i2caddress)
                do_epilog = relax
                return True
            except I2cNackError:
                self.log.info('Not ready')
                return False
            finally:
                if do_epilog:
                    self._do_epilog()

    def poll_cond(self, address, fmt, mask, value, count, relax=True):
        """Poll a remove slave, watching for condition to satisfy.
           On each poll cycle, a repeated start condition is emitted, without
           releasing the I2C bus, and an ACK is returned to the slave.

           If relax is set, this method releases the I2C bus however it leaves.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param str fmt: struct format for poll register
           :param int mask: binary mask to apply on the condition register
                before testing for the value
           :param int value: value to test the masked condition register
                against. Condition is satisfied when register & mask == value
           :param int count: maximum poll count before raising a timeout
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: the polled register value, or None if poll failed
           :rtype: array or None
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            i2caddress |= self.BIT0
        do_epilog = True
        with self._lock:
            try:
                retry = 0
                while retry < count:
                    retry += 1
                    size = scalc(fmt)
                    self._do_prolog(i2caddress)
                    data = self._do_read(size)
                    self.log.debug("Poll data: %s", hexlify(data).decode())
                    cond, = sunpack(fmt, data)
                    if (cond & mask) == value:
                        self.log.debug('Poll condition matched')
                        break
                    else:
                        data = None
                        self.log.debug('Poll condition not fulfilled: %x/%x',
                                       cond & mask, value)
                do_epilog = relax
                if not data:
                    self.log.warning('Poll condition failed')
                return data
            except I2cNackError:
                self.log.info('Not ready')
                return None
            finally:
                if do_epilog:
                    self._do_epilog()

    def flush(self):
        """Flush the HW FIFOs
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        with self._lock:
            self._ftdi.write_data(self._immediate)
            self._ftdi.purge_buffers()

    def read_gpio(self, with_output=False):
        """Read GPIO port

           :param bool with_output: set to unmask output pins
           :return: the GPIO port pins as a bitfield
           :rtype: int
        """
        with self._lock:
            data = self._read_raw(self._wide_port)
        value = data & self._gpio_mask
        if not with_output:
            value &= ~self._gpio_dir
        return value

    def write_gpio(self, value):
        """Write GPIO port

           :param int value: the GPIO port pins as a bitfield
        """
        with self._lock:
            if (value & self._gpio_dir) != value:
                raise I2cIOError('No such GPO pins: %04x/%04x' %
                                 (self._gpio_dir, value))
            # perform read-modify-write
            use_high = self._wide_port and (self.direction & 0xff00)
            data = self._read_raw(use_high)
            data &= ~self._gpio_mask
            data |= value
            self._write_raw(data, use_high)
            self._gpio_low = data & 0xFF & ~self._i2c_mask

    def set_gpio_direction(self, pins, direction):
        """Change the direction of the GPIO pins

           :param int pins: which GPIO pins should be reconfigured
           :param int direction: direction bitfield (on for output)
        """
        with self._lock:
            if pins & self._i2c_mask:
                raise I2cIOError('Cannot access I2C pins as GPIO')
            gpio_width = self._wide_port and 16 or 8
            gpio_mask = (1 << gpio_width) - 1
            gpio_mask &= ~self._i2c_mask
            if (pins & gpio_mask) != pins:
                raise I2cIOError('No such GPIO pin(s)')
            self._gpio_dir &= ~pins
            self._gpio_dir |= (pins & direction)
            self._gpio_mask = gpio_mask & pins

    @property
    def _data_lo(self):
        return (Ftdi.SET_BITS_LOW, self.SCL_BIT | self._gpio_low,
                self.I2C_DIR | (self._gpio_dir & 0xFF))

    @property
    def _clk_lo_data_hi(self):
        return (Ftdi.SET_BITS_LOW, self.SDA_O_BIT | self._gpio_low,
                self.I2C_DIR | (self._gpio_dir & 0xFF))

    @property
    def _clk_lo_data_lo(self):
        return (Ftdi.SET_BITS_LOW, self._gpio_low,
                self.I2C_DIR | (self._gpio_dir & 0xFF))

    @property
    def _idle(self):
        return (Ftdi.SET_BITS_LOW, self.I2C_DIR | self._gpio_low,
                self.I2C_DIR | (self._gpio_dir & 0xFF))

    @property
    def _start(self):
        return self._data_lo * self._ck_hd_sta + \
               self._clk_lo_data_lo * self._ck_hd_sta

    @property
    def _stop(self):
        return self._clk_lo_data_hi * self._ck_hd_sta + \
               self._data_lo * self._ck_su_sto + \
               self._idle * self._ck_idle

    def _compute_delay_cycles(self, value):
        # approx ceiling without relying on math module
        # the bit delay is far from being precisely known anyway
        bit_delay = self._ftdi.mpsse_bit_delay
        return max(1, int((value + bit_delay) / bit_delay))

    def _read_raw(self, read_high):
        if read_high:
            cmd = array(
                'B',
                [Ftdi.GET_BITS_LOW, Ftdi.GET_BITS_HIGH, Ftdi.SEND_IMMEDIATE])
            fmt = '<H'
        else:
            cmd = array('B', [Ftdi.GET_BITS_LOW, Ftdi.SEND_IMMEDIATE])
            fmt = 'B'
        self._ftdi.write_data(cmd)
        size = scalc(fmt)
        data = self._ftdi.read_data_bytes(size, 4)
        if len(data) != size:
            raise I2cIOError('Cannot read GPIO')
        value, = sunpack(fmt, data)
        return value

    def _write_raw(self, data, write_high):
        direction = self.direction
        low_data = data & 0xFF
        low_dir = direction & 0xFF
        if write_high:
            high_data = (data >> 8) & 0xFF
            high_dir = (direction >> 8) & 0xFF
            cmd = array('B', [
                Ftdi.SET_BITS_LOW, low_data, low_dir, Ftdi.SET_BITS_HIGH,
                high_data, high_dir
            ])
        else:
            cmd = array('B', [Ftdi.SET_BITS_LOW, low_data, low_dir])
        self._ftdi.write_data(cmd)

    def _do_prolog(self, i2caddress):
        if i2caddress is None:
            return
        self.log.debug('   prolog 0x%x', i2caddress >> 1)
        cmd = bytearray(self._idle)
        cmd.extend(self._start)
        cmd.extend(self._write_byte)
        cmd.append(i2caddress)
        if self._tristate:
            cmd.extend(self._tristate)
            cmd.extend(self._read_bit)
            cmd.extend(self._clk_lo_data_hi)
        else:
            cmd.extend(self._clk_lo_data_hi)
            cmd.extend(self._read_bit)
        cmd.extend(self._immediate)
        self._ftdi.write_data(cmd)
        ack = self._ftdi.read_data_bytes(1, 4)
        if not ack:
            raise I2cIOError('No answer from FTDI')
        if ack[0] & self.BIT0:
            self.log.warning('NACK @ 0x%02x', (i2caddress >> 1))
            raise I2cNackError('NACK from slave')

    def _do_epilog(self):
        self.log.debug('   epilog')
        cmd = bytearray(self._stop)
        self._ftdi.write_data(cmd)
        # be sure to purge the MPSSE reply
        self._ftdi.read_data_bytes(1, 1)

    def _do_read(self, readlen):
        self.log.debug('- read %d byte(s)', readlen)
        if not readlen:
            # force a real read request on device, but discard any result
            cmd = bytearray()
            cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            self._ftdi.read_data_bytes(0, 4)
            return bytearray()
        if self._tristate:
            read_byte = self._tristate + \
                        self._read_byte + \
                        self._clk_lo_data_hi
            read_not_last = \
                read_byte + self._ack + self._clk_lo_data_lo * self._ck_delay
            read_last = \
                read_byte + self._nack + self._clk_lo_data_hi * self._ck_delay
        else:
            read_not_last = \
                self._read_byte + self._ack + \
                self._clk_lo_data_hi * self._ck_delay
            read_last = \
                self._read_byte + self._nack + \
                self._clk_lo_data_hi * self._ck_delay
        # maximum RX size to fit in FTDI FIFO, minus 2 status bytes
        chunk_size = self._rx_size - 2
        cmd_size = len(read_last)
        # limit RX chunk size to the count of I2C packable commands in the FTDI
        # TX FIFO (minus one byte for the last 'send immediate' command)
        tx_count = (self._tx_size - 1) // cmd_size
        chunk_size = min(tx_count, chunk_size)
        chunks = []
        cmd = None
        rem = readlen
        while rem:
            if rem > chunk_size:
                if not cmd:
                    cmd = bytearray()
                    cmd.extend(read_not_last * chunk_size)
                    size = chunk_size
            else:
                cmd = bytearray()
                cmd.extend(read_not_last * (rem - 1))
                cmd.extend(read_last)
                cmd.extend(self._immediate)
                size = rem
            self._ftdi.write_data(cmd)
            buf = self._ftdi.read_data_bytes(size, 4)
            self.log.debug('- read %d byte(s): %s', len(buf),
                           hexlify(buf).decode())
            chunks.append(buf)
            rem -= size
        return bytearray(b''.join(chunks))

    def _do_write(self, out):
        if not isinstance(out, array):
            out = bytearray(out)
        if not out:
            return
        self.log.debug('- write %d byte(s): %s', len(out),
                       hexlify(out).decode())
        for byte in out:
            cmd = bytearray(self._write_byte)
            cmd.append(byte)
            if self._tristate:
                cmd.extend(self._tristate)
                cmd.extend(self._read_bit)
                cmd.extend(self._clk_lo_data_hi)
            else:
                cmd.extend(self._clk_lo_data_hi)
                cmd.extend(self._read_bit)
            cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            ack = self._ftdi.read_data_bytes(1, 4)
            if not ack:
                msg = 'No answer from FTDI'
                self.log.critical(msg)
                raise I2cIOError(msg)
            if ack[0] & self.BIT0:
                msg = 'NACK from slave'
                self.log.warning(msg)
                raise I2cNackError(msg)
Пример #2
0
class I2cController:
    """I2c master.

       An I2c master should be instanciated only once for each FTDI port that
       supports MPSSE (one or two ports, depending on the FTDI device).

       Once configured, :py:func:`get_port` should be invoked to obtain an I2c
       port for each I2c slave to drive. I2cport should handle all I/O requests
       for its associated HW slave.

       It is not recommended to use I2cController :py:func:`read`,
       :py:func:`write` or :py:func:`exchange` directly.
    """

    LOW = 0x00
    HIGH = 0xff
    BIT0 = 0x01
    IDLE = HIGH
    SCL_BIT = 0x01
    SDA_O_BIT = 0x02
    SDA_I_BIT = 0x04
    PAYLOAD_MAX_LENGTH = 0x10000  # 16 bits max
    HIGHEST_I2C_ADDRESS = 0x78
    DEFAULT_BUS_FREQUENCY = 100000.0
    HIGH_BUS_FREQUENCY = 400000.0
    RETRY_COUNT = 3

    I2C_100K = I2CTimings(4.0E-6, 4.7E-6, 4.0E-6, 4.7E-6)
    I2C_400K = I2CTimings(0.6E-6, 0.6E-6, 0.6E-6, 1.3E-6)
    I2C_1M = I2CTimings(0.26E-6, 0.26E-6, 0.26E-6, 0.5E-6)

    def __init__(self):
        self._ftdi = Ftdi()
        self.log = getLogger('pyftdi.i2c')
        self._slaves = {}
        self._retry_count = self.RETRY_COUNT
        self._frequency = 0.0
        self._direction = self.SCL_BIT | self.SDA_O_BIT
        self._immediate = (Ftdi.SEND_IMMEDIATE,)
        self._idle = (Ftdi.SET_BITS_LOW, self.IDLE, self._direction)
        self._data_lo = (Ftdi.SET_BITS_LOW,
                         self.IDLE & ~self.SDA_O_BIT, self._direction)
        self._clk_lo_data_lo = (Ftdi.SET_BITS_LOW,
                                self.IDLE & ~(self.SDA_O_BIT | self.SCL_BIT),
                                self._direction)
        self._clk_lo_data_hi = (Ftdi.SET_BITS_LOW,
                                self.IDLE & ~self.SCL_BIT,
                                self._direction)
        self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0)
        self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0)
        self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0)
        self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH)
        self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW)
        self._start = None
        self._stop = None
        self._ck_delay = 1
        self._tristate = None
        self._tx_size = 1
        self._rx_size = 1

    def _compute_delay_cycles(self, value):
        # approx ceiling without relying on math module
        # the bit delay is far from being precisely known anyway
        bit_delay = self._ftdi.mpsse_bit_delay
        return max(1, int((value + bit_delay) / bit_delay))

    def set_retry_count(self, count):
        """Change the default retry count when a communication error occurs,
           before bailing out.
           :param int count: count of retries
        """
        if not isinstance(count, int) or not 0 < count <= 16:
            raise ValueError('Invalid retry count')
        self._retry_count = count

    def configure(self, url, **kwargs):
        """Configure the FTDI interface as a I2c master.

           :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1'
           :param kwargs: options to configure the I2C bus

           Accepted options:

           * ``frequency`` the I2C bus frequency in Hz
        """
        for k in ('direction', 'initial'):
            if k in kwargs:
                del kwargs[k]
        if 'frequency' in kwargs:
            frequency = kwargs['frequency']
            del kwargs['frequency']
        else:
            frequency = self.DEFAULT_BUS_FREQUENCY
        # Fix frequency for 3-phase clock
        if frequency <= 100E3:
            timings = self.I2C_100K
        elif frequency <= 400E3:
            timings = self.I2C_100K
        else:
            timings = self.I2C_100K
        ck_hd_sta = self._compute_delay_cycles(timings.t_hd_sta)
        ck_su_sta = self._compute_delay_cycles(timings.t_su_sta)
        ck_su_sto = self._compute_delay_cycles(timings.t_su_sto)
        ck_buf = self._compute_delay_cycles(timings.t_buf)
        ck_idle = max(ck_su_sta, ck_buf)
        self._ck_delay = ck_buf
        self._start = (self._data_lo * ck_hd_sta +
                       self._clk_lo_data_lo * ck_hd_sta)
        self._stop = (self._clk_lo_data_lo * ck_hd_sta +
                      self._data_lo*ck_su_sto +
                      self._idle*ck_idle)
        frequency = (3.0*frequency)/2.0
        self._frequency = self._ftdi.open_mpsse_from_url(
            url, direction=self._direction, initial=self.IDLE,
            frequency=frequency, **kwargs)
        self._tx_size, self._rx_size = self._ftdi.fifo_sizes
        self._ftdi.enable_adaptive_clock(False)
        self._ftdi.enable_3phase_clock(True)
        try:
            self._ftdi.enable_drivezero_mode(self.SCL_BIT |
                                             self.SDA_O_BIT |
                                             self.SDA_I_BIT)
        except FtdiFeatureError:
            self._tristate = (Ftdi.SET_BITS_LOW, self.LOW, self.SCL_BIT)

    def terminate(self):
        """Close the FTDI interface.
        """
        if self._ftdi:
            self._ftdi.close()
            self._ftdi = None

    def get_port(self, address):
        """Obtain an I2cPort to drive an I2c slave.

           :param int address: the address on the I2C bus
           :return: an I2cPort instance
           :rtype: :py:class:`I2cPort`
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address not in self._slaves:
            self._slaves[address] = I2cPort(self, address)
        return self._slaves[address]

    @property
    def configured(self):
        """Test whether the device has been properly configured.

           :return: True if configured
        """
        return bool(self._ftdi) and bool(self._start)

    @classmethod
    def validate_address(cls, address):
        """Assert an I2C slave address is in the supported range.
           None is a special bypass address.

           :param address: the address on the I2C bus
           :type address: int or None
           :raise I2cIOError: if the I2C slave address is not supported
        """
        if address is None:
            return
        if address > cls.HIGHEST_I2C_ADDRESS:
            raise I2cIOError("No such I2c slave: 0x%02x" % address)

    @property
    def frequency_max(self):
        """Provides the maximum I2c clock frequency.
        """
        return self._ftdi.frequency_max

    @property
    def frequency(self):
        """Provides the current I2c clock frequency.

           :return: the I2C bus clock
           :rtype: float
        """
        return self._frequency

    def read(self, address, readlen=1, relax=True):
        """Read one or more bytes from a remote slave

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param int readlen: count of bytes to read out.
           :param bool relax: not used
           :return: read bytes
           :rtype: array
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)

           Most I2C devices require a register address to read out
           check out the exchange() method.
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            i2caddress |= self.BIT0
        retries = self._retry_count
        do_epilog = True
        while True:
            try:
                self._do_prolog(i2caddress)
                data = self._do_read(readlen)
                do_epilog = relax
                return data
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry read')
            finally:
                if do_epilog:
                    self._do_epilog()

    def write(self, address, out, relax=True):
        """Write one or more bytes to a remote slave

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param out: the byte buffer to send
           :type out: array or bytes or list(int)
           :param bool relax: whether to relax the bus (emit STOP) or not
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)

           Most I2C devices require a register address to write into. It should
           be added as the first (byte)s of the output buffer.
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
        retries = self._retry_count
        do_epilog = True
        while True:
            try:
                self._do_prolog(i2caddress)
                self._do_write(out)
                do_epilog = relax
                return
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry write')
            finally:
                if do_epilog:
                    self._do_epilog()

    def exchange(self, address, out, readlen=0, relax=True):
        """Send a byte sequence to a remote slave followed with
           a read request of one or more bytes.

           This command is useful to tell the slave what data
           should be read out.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param out: the byte buffer to send
           :type out: array or bytes or list(int)
           :param int readlen: count of bytes to read out.
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: read bytes
           :rtype: array
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if readlen < 1:
            raise I2cIOError('Nothing to read')
        if readlen > (I2cController.PAYLOAD_MAX_LENGTH/3-1):
            raise I2cIOError("Input payload is too large")
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
        retries = self._retry_count
        do_epilog = True
        while True:
            try:
                self._do_prolog(i2caddress)
                self._do_write(out)
                self._do_prolog(i2caddress | self.BIT0)
                if readlen:
                    data = self._do_read(readlen)
                do_epilog = relax
                return data
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry exchange')
            finally:
                if do_epilog:
                    self._do_epilog()

    def poll(self, address, write=False, relax=True):
        """Poll a remote slave, expect ACK or NACK.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param bool write: poll in write mode (vs. read)
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: True if the slave acknowledged, False otherwise
           :rtype: bool
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            if not write:
                i2caddress |= self.BIT0
        do_epilog = True
        try:
            self._do_prolog(i2caddress)
            do_epilog = relax
            return True
        except I2cNackError:
            self.log.info('Not ready')
            return False
        finally:
            if do_epilog:
                self._do_epilog()

    def poll_cond(self, address, fmt, mask, value, count, relax=True):
        """Poll a remove slave, watching for condition to satisfy.
           On each poll cycle, a repeated start condition is emitted, without
           releasing the I2C bus, and an ACK is returned to the slave.

           If relax is set, this method releases the I2C bus however it leaves.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param str fmt: struct format for poll register
           :param int mask: binary mask to apply on the condition register
                before testing for the value
           :param int value: value to test the masked condition register
                against. Condition is satisfied when register & mask == value
           :param int count: maximum poll count before raising a timeout
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: the polled register value, or None if poll failed
           :rtype: array or None
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            i2caddress |= self.BIT0
        do_epilog = True
        try:
            retry = 0
            while retry < count:
                retry += 1
                size = scalc(fmt)
                self._do_prolog(i2caddress)
                data = self._do_read(size)
                self.log.debug("Poll data: %s", hexlify(data).decode())
                cond, = sunpack(fmt, data)
                if (cond & mask) == value:
                    self.log.debug('Poll condition matched')
                    break
                else:
                    data = None
                    self.log.debug('Poll condition not fulfilled: %x/%x',
                                   cond & mask, value)
            do_epilog = relax
            if not data:
                self.log.warning('Poll condition failed')
            return data
        except I2cNackError:
            self.log.info('Not ready')
            return None
        finally:
            if do_epilog:
                self._do_epilog()

    def flush(self):
        """Flush the HW FIFOs
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self._ftdi.write_data(self._immediate)
        self._ftdi.purge_buffers()

    def _do_prolog(self, i2caddress):
        if i2caddress is None:
            return
        self.log.debug('   prolog 0x%x', i2caddress >> 1)
        cmd = array('B', self._idle)
        cmd.extend(self._start)
        cmd.extend(self._write_byte)
        cmd.append(i2caddress)
        if self._tristate:
            cmd.extend(self._tristate)
            cmd.extend(self._read_bit)
            cmd.extend(self._clk_lo_data_hi)
        else:
            cmd.extend(self._clk_lo_data_hi)
            cmd.extend(self._read_bit)
        cmd.extend(self._immediate)
        self._ftdi.write_data(cmd)
        ack = self._ftdi.read_data_bytes(1, 4)
        if not ack:
            raise I2cIOError('No answer from FTDI')
        if ack[0] & self.BIT0:
            self.log.warning('NACK')
            raise I2cNackError('NACK from slave')

    def _do_epilog(self):
        self.log.debug('   epilog')
        cmd = array('B', self._stop)
        self._ftdi.write_data(cmd)
        # be sure to purge the MPSSE reply
        self._ftdi.read_data_bytes(1, 1)

    def _do_read(self, readlen):
        self.log.debug('- read %d byte(s)', readlen)
        if not readlen:
            # force a real read request on device, but discard any result
            cmd = array('B')
            cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            self._ftdi.read_data_bytes(0, 4)
            return array('B')
        if self._tristate:
            read_byte = self._tristate + \
                        self._read_byte + \
                        self._clk_lo_data_hi
            read_not_last = \
                read_byte + self._ack + self._clk_lo_data_lo * self._ck_delay
            read_last = \
                read_byte + self._nack + self._clk_lo_data_hi * self._ck_delay
        else:
            read_not_last = \
                self._read_byte + self._ack + \
                self._clk_lo_data_hi * self._ck_delay
            read_last = \
                self._read_byte + self._nack + \
                self._clk_lo_data_hi * self._ck_delay
        # maximum RX size to fit in FTDI FIFO, minus 2 status bytes
        chunk_size = self._rx_size-2
        cmd_size = len(read_last)
        # limit RX chunk size to the count of I2C packable commands in the FTDI
        # TX FIFO (minus one byte for the last 'send immediate' command)
        tx_count = (self._tx_size-1) // cmd_size
        chunk_size = min(tx_count, chunk_size)
        chunks = []
        cmd = None
        rem = readlen
        while rem:
            if rem > chunk_size:
                if not cmd:
                    cmd = array('B')
                    cmd.extend(read_not_last * chunk_size)
                    size = chunk_size
            else:
                cmd = array('B')
                cmd.extend(read_not_last * (rem-1))
                cmd.extend(read_last)
                cmd.extend(self._immediate)
                size = rem
            self._ftdi.write_data(cmd)
            buf = self._ftdi.read_data_bytes(size, 4)
            self.log.debug('- read %d byte(s): %s',
                           len(buf), hexlify(buf).decode())
            chunks.append(buf)
            rem -= size
        return array('B', b''.join(chunks))

    def _do_write(self, out):
        if not isinstance(out, array):
            out = array('B', out)
        if not out:
            return
        self.log.debug('- write %d byte(s): %s',
                       len(out), hexlify(out).decode())
        for byte in out:
            cmd = array('B', self._write_byte)
            cmd.append(byte)
            if self._tristate:
                cmd.extend(self._tristate)
                cmd.extend(self._read_bit)
                cmd.extend(self._clk_lo_data_hi)
            else:
                cmd.extend(self._clk_lo_data_hi)
                cmd.extend(self._read_bit)
            cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            ack = self._ftdi.read_data_bytes(1, 4)
            if not ack:
                msg = 'No answer from FTDI'
                self.log.critical(msg)
                raise I2cIOError(msg)
            if ack[0] & self.BIT0:
                msg = 'NACK from slave'
                self.log.warning(msg)
                raise I2cNackError(msg)
Пример #3
0
class I2cController:
    """I2c master.

       An I2c master should be instanciated only once for each FTDI port that
       supports MPSSE (one or two ports, depending on the FTDI device).

       Once configured, :py:func:`get_port` should be invoked to obtain an I2c
       port for each I2c slave to drive. I2cport should handle all I/O requests
       for its associated HW slave.

       It is not recommended to use I2cController :py:func:`read`,
       :py:func:`write` or :py:func:`exchange` directly.
    """

    LOW = 0x00
    HIGH = 0xff
    BIT0 = 0x01
    IDLE = HIGH
    SCL_BIT = 0x01
    SDA_O_BIT = 0x02
    SDA_I_BIT = 0x04
    PAYLOAD_MAX_LENGTH = 0x10000  # 16 bits max
    HIGHEST_I2C_ADDRESS = 0x78
    DEFAULT_BUS_FREQUENCY = 100000.0
    HIGH_BUS_FREQUENCY = 400000.0
    RETRY_COUNT = 3

    I2C_100K = I2CTimings(4.0E-6, 4.7E-6, 4.0E-6, 4.7E-6)
    I2C_400K = I2CTimings(0.6E-6, 0.6E-6, 0.6E-6, 1.3E-6)
    I2C_1M = I2CTimings(0.26E-6, 0.26E-6, 0.26E-6, 0.5E-6)

    def __init__(self, ftdi = None):
        if not ftdi:
            self._ftdi = Ftdi()
        else:
            self._ftdi = ftdi

        self.log = getLogger('pyftdi.i2c')
        self._slaves = {}
        self._retry_count = self.RETRY_COUNT
        self._frequency = 0.0
        self._direction = self.SCL_BIT | self.SDA_O_BIT
        self._immediate = (Ftdi.SEND_IMMEDIATE,)
        self._idle = (Ftdi.SET_BITS_LOW, self.IDLE, self._direction)
        self._data_lo = (Ftdi.SET_BITS_LOW,
                         self.IDLE & ~self.SDA_O_BIT, self._direction)
        self._clk_lo_data_lo = (Ftdi.SET_BITS_LOW,
                                self.IDLE & ~(self.SDA_O_BIT | self.SCL_BIT),
                                self._direction)
        self._clk_lo_data_hi = (Ftdi.SET_BITS_LOW,
                                self.IDLE & ~self.SCL_BIT,
                                self._direction)
        self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0)
        self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0)
        self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0)
        self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH)
        self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW)
        self._start = None
        self._stop = None
        self._ck_delay = 1
        self._tristate = None
        self._tx_size = 1
        self._rx_size = 1

    def _compute_delay_cycles(self, value):
        # approx ceiling without relying on math module
        # the bit delay is far from being precisely known anyway
        bit_delay = self._ftdi.mpsse_bit_delay
        return max(1, int((value + bit_delay) / bit_delay))

    def set_retry_count(self, count):
        """Change the default retry count when a communication error occurs,
           before bailing out.
           :param int count: count of retries
        """
        if not isinstance(count, int) or not 0 < count <= 16:
            raise ValueError('Invalid retry count')
        self._retry_count = count

    def configure(self, **kwargs):
        """Configure the FTDI interface as a I2c master.

           :param str url: FTDI URL string, such as 'ftdi://ftdi:232h/1'
           :param kwargs: options to configure the I2C bus

           Accepted options:

           * ``frequency`` the I2C bus frequency in Hz
        """
        for k in ('direction', 'initial'):
            if k in kwargs:
                del kwargs[k]
        if 'url' in kwargs:
            url = kwargs['url']
            del kwargs['url']
        else:
            url = None
        if 'frequency' in kwargs:
            frequency = kwargs['frequency']
            del kwargs['frequency']
        else:
            frequency = self.DEFAULT_BUS_FREQUENCY
        # Fix frequency for 3-phase clock
        if frequency <= 100E3:
            timings = self.I2C_100K
        elif frequency <= 400E3:
            timings = self.I2C_100K
        else:
            timings = self.I2C_100K
        ck_hd_sta = self._compute_delay_cycles(timings.t_hd_sta)
        ck_su_sta = self._compute_delay_cycles(timings.t_su_sta)
        ck_su_sto = self._compute_delay_cycles(timings.t_su_sto)
        ck_buf = self._compute_delay_cycles(timings.t_buf)
        ck_idle = max(ck_su_sta, ck_buf)
        self._ck_delay = ck_buf
        self._start = (self._data_lo * ck_hd_sta +
                       self._clk_lo_data_lo * ck_hd_sta)
        self._stop = (self._clk_lo_data_lo * ck_hd_sta +
                      self._data_lo*ck_su_sto +
                      self._idle*ck_idle)
        frequency = (3.0*frequency)/2.0
        if url:
            self._frequency = self._ftdi.open_mpsse_from_url(
                url, direction=self._direction, initial=self.IDLE,
                frequency=frequency, **kwargs)
        else:
            self._frequency = self._ftdi.init_mpsse(direction=self._direction,
                    initial=self.IDLE, frequency=frequency, **kwargs)
        self._tx_size, self._rx_size = self._ftdi.fifo_sizes
        self._ftdi.enable_adaptive_clock(False)
        self._ftdi.enable_3phase_clock(True)
        try:
            self._ftdi.enable_drivezero_mode(self.SCL_BIT |
                                             self.SDA_O_BIT |
                                             self.SDA_I_BIT)
        except FtdiFeatureError:
            self._tristate = (Ftdi.SET_BITS_LOW, self.LOW, self.SCL_BIT)

    def terminate(self):
        """Close the FTDI interface.
        """
        if self._ftdi:
            self._ftdi.close()
            self._ftdi = None

    def get_port(self, address):
        """Obtain an I2cPort to drive an I2c slave.

           :param int address: the address on the I2C bus
           :return: an I2cPort instance
           :rtype: :py:class:`I2cPort`
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address not in self._slaves:
            self._slaves[address] = I2cPort(self, address)
        return self._slaves[address]

    @property
    def configured(self):
        """Test whether the device has been properly configured.

           :return: True if configured
        """
        return bool(self._ftdi) and bool(self._start)

    @classmethod
    def validate_address(cls, address):
        """Assert an I2C slave address is in the supported range.
           None is a special bypass address.

           :param address: the address on the I2C bus
           :type address: int or None
           :raise I2cIOError: if the I2C slave address is not supported
        """
        if address is None:
            return
        if address > cls.HIGHEST_I2C_ADDRESS:
            raise I2cIOError("No such I2c slave: 0x%02x" % address)

    @property
    def frequency_max(self):
        """Provides the maximum I2c clock frequency.
        """
        return self._ftdi.frequency_max

    @property
    def frequency(self):
        """Provides the current I2c clock frequency.

           :return: the I2C bus clock
           :rtype: float
        """
        return self._frequency

    def read(self, address, readlen=1, relax=True):
        """Read one or more bytes from a remote slave

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param int readlen: count of bytes to read out.
           :param bool relax: not used
           :return: read bytes
           :rtype: array
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)

           Most I2C devices require a register address to read out
           check out the exchange() method.
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            i2caddress |= self.BIT0
        retries = self._retry_count
        do_epilog = True
        while True:
            try:
                self._do_prolog(i2caddress)
                data = self._do_read(readlen)
                do_epilog = relax
                return data
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry read')
            finally:
                if do_epilog:
                    self._do_epilog()

    def write(self, address, out, relax=True):
        """Write one or more bytes to a remote slave

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param out: the byte buffer to send
           :type out: array or bytes or list(int)
           :param bool relax: whether to relax the bus (emit STOP) or not
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)

           Most I2C devices require a register address to write into. It should
           be added as the first (byte)s of the output buffer.
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
        retries = self._retry_count
        do_epilog = True
        while True:
            try:
                self._do_prolog(i2caddress)
                self._do_write(out)
                do_epilog = relax
                return
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry write')
            finally:
                if do_epilog:
                    self._do_epilog()

    def exchange(self, address, out, readlen=0, relax=True):
        """Send a byte sequence to a remote slave followed with
           a read request of one or more bytes.

           This command is useful to tell the slave what data
           should be read out.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param out: the byte buffer to send
           :type out: array or bytes or list(int)
           :param int readlen: count of bytes to read out.
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: read bytes
           :rtype: array
           :raise I2cIOError: if device is not configured or input parameters
                              are invalid

           Address is a logical slave address (0x7f max)
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if readlen < 1:
            raise I2cIOError('Nothing to read')
        if readlen > (I2cController.PAYLOAD_MAX_LENGTH/3-1):
            raise I2cIOError("Input payload is too large")
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
        retries = self._retry_count
        do_epilog = True
        while True:
            try:
                self._do_prolog(i2caddress)
                self._do_write(out)
                self._do_prolog(i2caddress | self.BIT0)
                if readlen:
                    data = self._do_read(readlen)
                do_epilog = relax
                return data
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry exchange')
            finally:
                if do_epilog:
                    self._do_epilog()

    def poll(self, address, write=False, relax=True):
        """Poll a remote slave, expect ACK or NACK.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param bool write: poll in write mode (vs. read)
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: True if the slave acknowledged, False otherwise
           :rtype: bool
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            if not write:
                i2caddress |= self.BIT0
        do_epilog = True
        try:
            self._do_prolog(i2caddress)
            do_epilog = relax
            return True
        except I2cNackError:
            self.log.info('Not ready')
            return False
        finally:
            if do_epilog:
                self._do_epilog()

    def poll_cond(self, address, fmt, mask, value, count, relax=True):
        """Poll a remove slave, watching for condition to satisfy.
           On each poll cycle, a repeated start condition is emitted, without
           releasing the I2C bus, and an ACK is returned to the slave.

           If relax is set, this method releases the I2C bus however it leaves.

           :param address: the address on the I2C bus, or None to discard start
           :type address: int or None
           :param str fmt: struct format for poll register
           :param int mask: binary mask to apply on the condition register
                before testing for the value
           :param int value: value to test the masked condition register
                against. Condition is satisfied when register & mask == value
           :param int count: maximum poll count before raising a timeout
           :param bool relax: whether to relax the bus (emit STOP) or not
           :return: the polled register value, or None if poll failed
           :rtype: array or None
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address is None:
            i2caddress = None
        else:
            i2caddress = (address << 1) & self.HIGH
            i2caddress |= self.BIT0
        do_epilog = True
        try:
            retry = 0
            while retry < count:
                retry += 1
                size = scalc(fmt)
                self._do_prolog(i2caddress)
                data = self._do_read(size)
                self.log.debug("Poll data: %s", hexlify(data).decode())
                cond, = sunpack(fmt, data)
                if (cond & mask) == value:
                    self.log.debug('Poll condition matched')
                    break
                else:
                    data = None
                    self.log.debug('Poll condition not fulfilled: %x/%x',
                                   cond & mask, value)
            do_epilog = relax
            if not data:
                self.log.warning('Poll condition failed')
            return data
        except I2cNackError:
            self.log.info('Not ready')
            return None
        finally:
            if do_epilog:
                self._do_epilog()

    def flush(self):
        """Flush the HW FIFOs
        """
        if not self.configured:
            raise I2cIOError("FTDI controller not initialized")
        self._ftdi.write_data(self._immediate)
        self._ftdi.purge_buffers()

    def _do_prolog(self, i2caddress):
        if i2caddress is None:
            return
        self.log.debug('   prolog 0x%x', i2caddress >> 1)
        cmd = array('B', self._idle)
        cmd.extend(self._start)
        cmd.extend(self._write_byte)
        cmd.append(i2caddress)
        if self._tristate:
            cmd.extend(self._tristate)
            cmd.extend(self._read_bit)
            cmd.extend(self._clk_lo_data_hi)
        else:
            cmd.extend(self._clk_lo_data_hi)
            cmd.extend(self._read_bit)
        cmd.extend(self._immediate)
        self._ftdi.write_data(cmd)
        ack = self._ftdi.read_data_bytes(1, 4)
        if not ack:
            raise I2cIOError('No answer from FTDI')
        if ack[0] & self.BIT0:
            self.log.warning('NACK')
            raise I2cNackError('NACK from slave')

    def _do_epilog(self):
        self.log.debug('   epilog')
        cmd = array('B', self._stop)
        self._ftdi.write_data(cmd)
        # be sure to purge the MPSSE reply
        self._ftdi.read_data_bytes(1, 1)

    def _do_read(self, readlen):
        self.log.debug('- read %d byte(s)', readlen)
        if not readlen:
            # force a real read request on device, but discard any result
            cmd = array('B')
            cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            self._ftdi.read_data_bytes(0, 4)
            return array('B')
        if self._tristate:
            read_byte = self._tristate + \
                        self._read_byte + \
                        self._clk_lo_data_hi
            read_not_last = \
                read_byte + self._ack + self._clk_lo_data_lo * self._ck_delay
            read_last = \
                read_byte + self._nack + self._clk_lo_data_hi * self._ck_delay
        else:
            read_not_last = \
                self._read_byte + self._ack + \
                self._clk_lo_data_hi * self._ck_delay
            read_last = \
                self._read_byte + self._nack + \
                self._clk_lo_data_hi * self._ck_delay
        # maximum RX size to fit in FTDI FIFO, minus 2 status bytes
        chunk_size = self._rx_size-2
        cmd_size = len(read_last)
        # limit RX chunk size to the count of I2C packable commands in the FTDI
        # TX FIFO (minus one byte for the last 'send immediate' command)
        tx_count = (self._tx_size-1) // cmd_size
        chunk_size = min(tx_count, chunk_size)
        chunks = []
        cmd = None
        rem = readlen
        while rem:
            if rem > chunk_size:
                if not cmd:
                    cmd = array('B')
                    cmd.extend(read_not_last * chunk_size)
                    size = chunk_size
            else:
                cmd = array('B')
                cmd.extend(read_not_last * (rem-1))
                cmd.extend(read_last)
                cmd.extend(self._immediate)
                size = rem
            self._ftdi.write_data(cmd)
            buf = self._ftdi.read_data_bytes(size, 4)
            self.log.debug('- read %d byte(s): %s',
                           len(buf), hexlify(buf).decode())
            chunks.append(buf)
            rem -= size
        return array('B', b''.join(chunks))

    def _do_write(self, out):
        if not isinstance(out, array):
            out = array('B', out)
        if not out:
            return
        self.log.debug('- write %d byte(s): %s',
                       len(out), hexlify(out).decode())
        for byte in out:
            cmd = array('B', self._write_byte)
            cmd.append(byte)
            if self._tristate:
                cmd.extend(self._tristate)
                cmd.extend(self._read_bit)
                cmd.extend(self._clk_lo_data_hi)
            else:
                cmd.extend(self._clk_lo_data_hi)
                cmd.extend(self._read_bit)
            cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            ack = self._ftdi.read_data_bytes(1, 4)
            if not ack:
                msg = 'No answer from FTDI'
                self.log.critical(msg)
                raise I2cIOError(msg)
            if ack[0] & self.BIT0:
                msg = 'NACK from slave'
                self.log.warning(msg)
                raise I2cNackError(msg)
Пример #4
0
class I2cController(object):
    """I2c master.
    """

    LOW = 0x00
    HIGH = 0xff
    BIT0 = 0x01
    IDLE = HIGH
    SCL_BIT = 0x01
    SDA_O_BIT = 0x02
    SDA_I_BIT = 0x04
    PAYLOAD_MAX_LENGTH = 0x10000  # 16 bits max
    HIGHEST_I2C_ADDRESS = 0x7f
    DEFAULT_BUS_FREQUENCY = 100000.0
    HIGH_BUS_FREQUENCY = 400000.0
    RETRY_COUNT = 3

    def __init__(self):
        self._ftdi = Ftdi()
        self.log = getLogger('pyftdi.i2c')
        self._slaves = {}
        self._frequency = 0.0
        self._direction = I2cController.SCL_BIT | I2cController.SDA_O_BIT
        self._immediate = (Ftdi.SEND_IMMEDIATE, )
        self._idle = (Ftdi.SET_BITS_LOW, self.IDLE, self._direction)
        data_low = (Ftdi.SET_BITS_LOW, self.IDLE & ~self.SDA_O_BIT,
                    self._direction)
        clock_low_data_low = (Ftdi.SET_BITS_LOW,
                              self.IDLE & ~(self.SDA_O_BIT | self.SCL_BIT),
                              self._direction)
        self._clock_low_data_high = (Ftdi.SET_BITS_LOW,
                                     self.IDLE & ~self.SCL_BIT,
                                     self._direction)
        self._read_bit = (Ftdi.READ_BITS_PVE_MSB, 0)
        self._read_byte = (Ftdi.READ_BYTES_PVE_MSB, 0, 0)
        self._write_byte = (Ftdi.WRITE_BYTES_NVE_MSB, 0, 0)
        self._nack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.HIGH)
        self._ack = (Ftdi.WRITE_BITS_NVE_MSB, 0, self.LOW)
        self._start = data_low * 4 + clock_low_data_low * 4
        self._stop = clock_low_data_low * 4 + data_low * 4 + self._idle * 4
        self._tx_size = 1
        self._rx_size = 1

    def configure(self, url, **kwargs):
        """Configure the FTDI interface as a I2c master.

            :param url: FTDI URL string, such as 'ftdi://ftdi:232h/1'
            :param frequency: frequency of the I2C bus.
        """
        for k in ('direction', 'initial'):
            if k in kwargs:
                del kwargs[k]
        if 'frequency' in kwargs:
            frequency = kwargs['frequency']
            del kwargs['frequency']
        else:
            frequency = self.DEFAULT_BUS_FREQUENCY
        # Fix frequency for 3-phase clock
        frequency = (3.0 * frequency) / 2.0
        self._frequency = self._ftdi.open_mpsse_from_url(
            url,
            direction=self._direction,
            initial=self.IDLE,
            frequency=frequency,
            **kwargs)
        self._tx_size, self._rx_size = self._ftdi.fifo_sizes
        self._ftdi.enable_adaptive_clock(False)
        self._ftdi.enable_3phase_clock(True)
        self._ftdi.enable_drivezero_mode(self.SCL_BIT | self.SDA_O_BIT
                                         | self.SDA_I_BIT)

    def terminate(self):
        """Close the FTDI interface.
        """
        if self._ftdi:
            self._ftdi.close()
            self._ftdi = None

    def get_port(self, address):
        """Obtain a I2cPort to to drive an I2c slave.

           :param address: the address on the I2C bus
           :return a I2cPort instance
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if address not in self._slaves:
            self._slaves[address] = I2cPort(self, address)
        return self._slaves[address]

    @classmethod
    def validate_address(cls, address):
        if address > cls.HIGHEST_I2C_ADDRESS:
            raise I2cIOError("No such I2c slave")

    @property
    def frequency_max(self):
        """Returns the maximum I2c clock.
        """
        return self._ftdi.frequency_max

    @property
    def frequency(self):
        """Returns the current I2c clock.
        """
        return self._frequency

    def read(self, address, readlen=1):
        """Read one or more bytes from a remote slave

           :param address: the address on the I2C bus
           :param readlen: count of bytes to read out.

           Address is a logical address (0x7f max)

           Most I2C devices require a register address to read out
           check out the exchange() method.
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if readlen < 1:
            raise I2cIOError('Nothing to read')
        i2caddress = (address << 1) & self.HIGH
        i2caddress |= self.BIT0
        retries = self.RETRY_COUNT
        while True:
            try:
                self._do_prolog(i2caddress)
                data = self._do_read(readlen)
                return bytes(data)
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry read')
            finally:
                self._do_epilog()

    def write(self, address, out):
        """Write one or more bytes to a remote slave

           :param address: the address on the I2C bus
           :param out: the byte buffer to send

           Address is a logical address (0x7f max)

           Most I2C devices require a register address to write
           into. It should be added as the first (byte)s of the
           output buffer.
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if not out or len(out) < 1:
            raise I2cIOError('Nothing to write')
        i2caddress = (address << 1) & self.HIGH
        retries = self.RETRY_COUNT
        while True:
            try:
                self._do_prolog(i2caddress)
                self._do_write(out)
                return
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry write')
            finally:
                self._do_epilog()

    def exchange(self, address, out, readlen=1):
        """Send a byte sequence to a remote slave followed with
           a read request of one or more bytes.

           This command is useful to tell the slave what data
           should be read out.

           :param address: the address on the I2C bus
           :param out: the byte buffer to send
           :param readlen: count of bytes to read out.

           Address is a logical address (0x7f max)
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        if not out or not len(out):
            raise I2cIOError('Nothing to write')
        if readlen < 1:
            raise I2cIOError('Nothing to read')
        if readlen > (I2cController.PAYLOAD_MAX_LENGTH / 3 - 1):
            raise I2cIOError("Input payload is too large")
        i2caddress = (address << 1) & self.HIGH
        retries = self.RETRY_COUNT
        while True:
            try:
                self._do_prolog(i2caddress)
                self._do_write(out)
                self._do_prolog(i2caddress | self.BIT0)
                data = self._do_read(readlen)
                return data
            except I2cNackError:
                retries -= 1
                if not retries:
                    raise
                self.log.warning('Retry exchange')
            finally:
                self._do_epilog()

    def poll(self, address):
        """Poll a remote slave, expect ACK or NACK.

           :param address: the address on the I2C bus
           :return: True if the slave acknowledged, False otherwise
        """
        if not self._ftdi:
            raise I2cIOError("FTDI controller not initialized")
        self.validate_address(address)
        i2caddress = (address << 1) & self.HIGH
        i2caddress |= self.BIT0
        self.log.debug('- poll 0x%x', i2caddress >> 1)
        try:
            self._do_prolog(i2caddress)
            return True
        except I2cNackError:
            self.log.info('Not ready')
            return False
        finally:
            self._do_epilog()

    def flush(self):
        """Flush the HW FIFOs
        """
        self._ftdi.write_data(self._immediate)
        self._ftdi.purge_buffers()

    def _do_prolog(self, i2caddress):
        self.log.debug('   prolog 0x%x', i2caddress >> 1)
        cmd = Array('B', self._idle)
        cmd.extend(self._start)
        cmd.extend(self._write_byte)
        cmd.append(i2caddress)
        cmd.extend(self._clock_low_data_high)
        cmd.extend(self._read_bit)
        cmd.extend(self._immediate)
        self._ftdi.write_data(cmd)
        ack = self._ftdi.read_data_bytes(1, 4)
        if not ack:
            raise I2cIOError('No answer from FTDI')
        if ack[0] & self.BIT0:
            self.log.warning('NACK')
            raise I2cNackError('NACK from slave')

    def _do_epilog(self):
        self.log.debug('   epilog')
        cmd = Array('B', self._stop)
        self._ftdi.write_data(cmd)
        # be sure to purge the MPSSE reply
        self._ftdi.read_data_bytes(1, 1)

    def _do_read(self, readlen):
        self.log.debug('- read %d bytes', readlen)
        read_not_last = self._read_byte + self._ack + self._clock_low_data_high
        read_last = self._read_byte + self._nack + self._clock_low_data_high
        chunk_size = self._rx_size - 2
        cmd_size = len(read_last)
        # limit RX chunk size to the count of I2C packable ommands in the FTDI
        # TX FIFO (minus one byte for the last 'send immediate' command)
        tx_count = (self._tx_size - 1) // cmd_size
        chunk_size = min(tx_count, chunk_size)
        chunks = []
        last_count = 0
        last_cmd = None
        count = readlen
        while count:
            block_count = min(count, chunk_size)
            count -= block_count
            if last_count != block_count:
                cmd = Array('B')
                cmd.extend(read_not_last * (block_count - 1))
                cmd.extend(read_last)
                last_cmd = cmd
            else:
                cmd = last_cmd
            if count <= 0:
                # only force immediate read out on last chunk
                cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            buf = self._ftdi.read_data_bytes(block_count, 4)
            chunks.append(buf)
        return b''.join(chunks)

    def _do_write(self, out):
        self.log.debug('- write %d bytes: %s', len(out), hexlify(out).decode())
        for byte in out:
            cmd = Array('B', self._write_byte)
            cmd.append(byte)
            cmd.extend(self._clock_low_data_high)
            cmd.extend(self._read_bit)
            cmd.extend(self._immediate)
            self._ftdi.write_data(cmd)
            ack = self._ftdi.read_data_bytes(1, 4)
            if not ack:
                msg = 'No answer from FTDI'
                self.log.critical(msg)
                raise I2cIOError(msg)
            if ack[0] & self.BIT0:
                msg = 'NACK from slave'
                self.log.warning(msg)
                raise I2cNackError(msg)