Ejemplo n.º 1
0
    def __init__(self,
                 Interface_Type=None,
                 base_address=0x00,
                 select_line=None):
        """
        Create an instance of a generic FireFly device. The interface type will be determined
        automatically, as will the number of channels. Devices are assumed to be in POR, and at
        address 0x50 by default on instantiation, but can be supplied an alternative address to
        switch to. Select lines are also supported, but if ignored it is assumed that the device
        is the only one present on the bus and has its select line pulled low.

        :param Interface_Type:  Type of interface to use, FireFly.INTERFACE_CXP/QSFP. In most cases
                                should be able to omit this and have it detected automatically.
        :param base_address:    Address that will be set for future communication. Omit for default
        :param select_line:     GPIO line to use for active-low chip select, if used. This should be
                                a line provided by odin_devices.gpio_bus or directly via gpiod.
        """

        # Init logger for instance (likely to have multiple fireflies)
        loggername = FireFly._LOGGER_BASENAME + ('@0x%02x' % base_address)
        self._log = logging.getLogger(loggername)
        self._log.info("Init FireFly with base address 0x%02x" % base_address)

        # Determine interface type automatically or use manually chosen one if supplied
        if Interface_Type is None:  # Detect interface automatically (default)
            INTERFACE_Detect = FireFly._get_interface(select_line, 0x50,
                                                      self._log)
            if INTERFACE_Detect is None:  # Automatic detection failed
                raise I2CException(
                    "Was unable to determine interface type automatically, " +
                    "try specifying Interface_Type manually.")
        elif Interface_Type in [FireFly.INTERFACE_CXP, FireFly.INTERFACE_QSFP]:
            INTERFACE_Detect = Interface_Type
        else:
            raise I2CException("Manually specified interface type was invalid")

        # Instantiate the interface based on above manual/automatic select
        if INTERFACE_Detect == FireFly.INTERFACE_QSFP:
            self._interface = _interface_QSFP(loggername + ".QSFP+",
                                              base_address, select_line)
            self._log.info("Interface detected as QSFP+ based")
        elif INTERFACE_Detect == FireFly.INTERFACE_CXP:
            self._interface = _interface_CXP(loggername + ".CXP", base_address,
                                             select_line)
            self._log.info("Interface detected as CXP based")

        # From here onwards, interface and address specific functions can be called through
        # self._interface.???(), and address, register and select line usage will be handled.

        # Read some identifying information
        PN_ascii, VN_ascii, OUI = self.get_device_info()
        self._check_pn_fields(PN_ascii)
        self._log.info("Found device, Vendor: {} ({}),\tPN: {}".format(
            VN_ascii, OUI, PN_ascii))

        # Turn off all Tx channels initially to prevent overheat
        self.disable_tx_channels(FireFly.CHANNEL_ALL)

        temp_tx = self.get_temperature(FireFly.DIRECTION_TX)
        self._log.info("Tx Temperature: {}".format(temp_tx))
Ejemplo n.º 2
0
    def _check_pn_fields(self, pn_str):
        """
        Checks some common fields accross all common devices to make sure the PN has been read
        correctly. Some fields important to the driver's operation are designated as CRITICAL, and
        will trigger an exception if failed. Others designated with WARNING will simply show a
        warning if the value is not recognised.

        This function also populates some fields used by the driver, like number of channels, data
        direction and data rate.

        :param pn_str:  String of unformatted Part number (without ECU0 prefix)
        """

        # (CRITICAL) Check data direction (width) field
        if pn_str[0] in ['T']:
            self.direction = FireFly.DIRECTION_TX
        elif pn_str[0] in ['R']:
            self.direction = FireFly.DIRECTION_RX
        elif pn_str[0] in ['B', 'Y']:
            self.direction = FireFly.DIRECTION_DUPLEX
        elif pn_str[0] in ['U']:
            # Currently unsure what this mode means
            #TODO
            pass
        else:
            raise I2CException(
                "Data direction {} in part number field not recognised".format(
                    pn_str[0]))
        self._log.info("Device data direction: {}".format(pn_str[0]))

        # (CRITICAL) Check number of channels
        if pn_str[1:3] == '12':
            self.num_channels = 12
        elif pn_str[1:3] == '04':
            self.num_channels = 4
        else:
            raise I2CException("Unsupported number of channels: {}".format(
                pn_str[1:3]))
        self._log.info("Device channels: {}".format(self.num_channels))

        # (WARNING) Check data rate
        if pn_str[3:5] in ['14', '16', '25', '28']:
            self.data_rate_Gbps = int(pn_str[3:5])
            self._log.info("Device data rate: {}Gbps".format(
                self.data_rate_Gbps))
        else:
            self._log.warning("Device data rate: unsupported ({})".format(
                pn_str[3:5]))

        # (CRITICAL) Check static padding fields (wrong implies invalid PN)
        if pn_str[8] != '0' or pn_str[10] != '1':
            raise I2CException("Invalid PN static field(s)")

        # (WARNING) Check heat sink type
        if pn_str[9] not in "12345":
            self._log.warning("Unknown head sink type ({})".format(pn_str[9]))

        # (WARNING) Fiber type
        if pn_str[11] not in "12456":
            self._log.warning("Unknown fiber type ({})".format(pn_str[11]))
Ejemplo n.º 3
0
    def set_frequency(self, freq):
        """Sets the output frequency of the oscillator.

        :param freq: Desired frequency [10 - 945] (Megahertz)
        """

        if not 10.0 <= freq <= 945.0:
            raise I2CException("The frequency %fMHz is out of the range of this device")

        # Determine divider combination to be used
        # Min/max dividers to use based on possible oscillator frequencies
        divider_max = int(math.floor(5670.0 / freq))
        divider_min = int(math.ceil(4850.0 / freq))
        found = False

        for divider in range(divider_min, divider_max + 1):
            for hs_div in [11, 9, 7, 6, 5, 4]:
                n1 = int(float(divider) / hs_div)

                # If desired divider can be produced from HS_DIV and N1
                if n1 == float(divider) / hs_div and (n1 == 1 or n1 & 1 == 0):
                    found = True
                    self.__n1 = n1
                    self.__hs_div = hs_div
                    break
            if found:
                break
        else:
            raise I2CException("There is no possible divider combination for %f MHz" % freq)

        # Calculate RFREQ from divider choice
        self.__rfreq = freq * self.__hs_div * self.__n1 / self.__fxtal

        # Freeze the oscillator
        self.write8(137, self.readU8(137) | 0x10)

        # Update device with new values
        raw_hs_div = self.__hs_div - 4
        raw_n1 = self.__n1 - 1
        raw_rfreq = int(self.__rfreq * 2**28)
        self.writeList(
            self.__register,
            map(int, [(raw_hs_div << 5) + (raw_n1 >> 2),
                ((raw_n1 & 0b11) << 6) + ((raw_rfreq >> 32) & 0b111111),
                (raw_rfreq >> 24) & 0xff,
                (raw_rfreq >> 16) & 0xff,
                (raw_rfreq >> 8) & 0xff,
                raw_rfreq & 0xff]
            )
        )

        # Unfreeze the oscillator and set NEWFREQ flag
        self.write8(137, self.readU8(137) & 0xEF)
        self.write8(135, 0x40)
        self.__fout = (self.__fxtal * self.__rfreq) / (self.__n1 * self.__hs_div)
Ejemplo n.º 4
0
    def read_field(self, field, i2c_device):
        """
        Generic function to read from a register field (see above).

        :field:         Field instance containing information about field location and size
        :i2c_device:    I2CDevice instance that will be read from
        :return:        Array of byte values from field, with no offset. If less than the size of
                        one byte, an array of size 1 is returned.
        """
        self._log.debug(
            "Getting field starting at bit {}, length {} from register {}".
            format(field.startbit, field.length, field.register))

        # Read byte values from starting register onwards
        num_full_bytes = int(
            math.ceil((field.length + field.get_endbit()) / float(8)))
        raw_register_values = i2c_device.readList(field.register,
                                                  num_full_bytes)

        # Check resulting I2C read format and length
        if (type(raw_register_values) != list):
            raise I2CException("Failed to read byte list from I2C Device")
        if (len(raw_register_values) != num_full_bytes):
            raise I2CException("Number of bytes read incorrect. "
                               "Expected {}, got {}: {}".format(
                                   num_full_bytes, len(raw_register_values),
                                   raw_register_values))

        # Convert to a single value
        out_value = _array_to_int(raw_register_values)
        self._log.debug("\tRegister value: {:x}".format(out_value))

        # Create mask for value location
        new_mask = int(math.pow(
            2, field.length)) - 1  # Create to match field size
        new_mask = new_mask << field.get_endbit()  # Shift to correct position
        self._log.debug("\tCreated mask: {:x}".format(new_mask))

        # Mask off unwanted bits
        out_value &= new_mask
        self._log.debug("\tMasked output: {:x}".format(out_value))

        # Shift value down to endbit position 0
        out_value = out_value >> field.get_endbit()
        self._log.debug("\tShifted output:{:x}".format(out_value))

        # Calculate the number of bytes needed for output
        num_output_bytes = int(math.ceil(field.length / 8.0))

        return _int_to_array(out_value, num_output_bytes)
Ejemplo n.º 5
0
    def set_wiper(self, wiper, position):
        """Manually sets a wiper position
        :param wiper: Wiper to set 0=A, 1=B
        :param position: Target position [0-255]
        """

        if not wiper in [0, 1]:
            raise I2CException("Select either wiper 0 or wiper 1")
        if position > 255:
            raise I2CException("Value greater than 255, range is 0 - 255")
        if position < 0:
            raise I2CException("Value less than 0, range is 0 - 255")

        self.__wiper_pos[wiper] = int(position)
        self.write8(wiper, self.__wiper_pos[wiper])
Ejemplo n.º 6
0
    def attach_device(self, device, *args, **kwargs):
        """Attach an I2C device to the container.

        This method attaches a device to the container. The device must be an instance of,
        or type, derived from I2CDevice or I2CContainer. If a class is passed, a device of the
        appropriate type is initialised and returned.

        :param device: class or instance to attach to the TCA
        :param args: positional arguments to pass to device initialisation
        :param kwargs: keyword arguments to pass to device initialisation
        """
        # If passed a callable type for a device, initialise it with the appropriate arguments
        if callable(device):
            if self.pre_access is not None:
                self.pre_access(self)
            device = device(*args, **kwargs)

        # Raise an exception if the device is not and I2CDevice or I2CContainer instance
        if not isinstance(device, I2CDevice) and not isinstance(device, I2CContainer):
            raise I2CException(
                'Device %s must be of type or an instance of I2CDevice or I2CContainer' % device)

        # Append device to attached devices, set its callback and return
        self._attached_devices.append(device)
        device.pre_access = self._device_callback
        return device
Ejemplo n.º 7
0
    def __device_callback(self, device):
        """Internal device callback method.

        This method is called internally to allow attached devices to transparently
        select the appropriate TCA multiplexer channel. The TCA is accessed only if the
        device being accessed is not on the currently selected channel.

        :param device: the device for which the callback is being called.
        """
        # Check the device is attached, otherwise raise an exception
        if device not in self._attached_devices:
            raise I2CException(
                'Device %s was not properly detached from the TCA' % device)

        # Call own callback (for chained TCAs)
        if self.pre_access is not None:
            self.pre_access(self)

        # Skip accessing the TCA if the current channel is already selected
        if self._attached_devices[device] == self._selected_channel:
            return

        self._selected_channel = self._attached_devices[device]

        # Write to the TCA to select the correct channel
        self.write8(0, 1 << self._attached_devices[device])
Ejemplo n.º 8
0
    def get_temperature(self, direction=None):
        """
        Generic function to get the temperature of the firefly transmitter or receiver.

        :param direction:   Selection from DIRECTION_<TX/RX>
        :return:            float temperature of specified device
        """
        # If the supplied direction is None, but only one direction is possible for this device
        if (direction is None and (self.direction == FireFly.DIRECTION_TX
                                   or self.direction == FireFly.DIRECTION_RX)):
            direction = self.direction  # Derive temperature direction from simplex device

        if direction == FireFly.DIRECTION_TX:
            temperature_bytes = self._interface.read_field(
                self._interface.FLD_Tx_Temperature)
        elif direction == FireFly.DIRECTION_RX:
            temperature_bytes = self._interface.read_field(
                self._interface.FLD_Rx_Temperature)
        else:
            raise I2CException(
                "Invalid direction specified, and could not be derived")

        # Perform 8-bit 2's compliment conversion
        output_temp = (temperature_bytes[0] & 0b01111111) + \
            (-128 if ((temperature_bytes[0] & 0b10000000) != 0) else 0)

        return output_temp
Ejemplo n.º 9
0
    def set_clock_priority(self, top_priority_clock, check_auto_en=True):
        """
        Set the clock that takes priority if clock autoselection is enabled.

        :param top_priority_clock: Highest Priority clock selection: CLOCK_1, CLOCK_2, or CLOCK_X
        :param check_auto_en: Set False to disable checking if clock auto-selection is enabled
        """
        if ((self.get_autoselection_mode() == SI5324.AUTOMODE_Manual)
                and check_auto_en):
            logger.warning(
                "Setting priority clock without enabling auto-selection."
                " Enable autoselection for this setting to take effect.")

        if top_priority_clock == SI5324.CLOCK_1:
            self._set_register_field(SI5324._FIELD_Clock_1_Priority, 0b00,
                                     True)
            self._set_register_field(SI5324._FIELD_Clock_2_Priority, 0b01,
                                     True)
        elif top_priority_clock in [SI5324.CLOCK_2, SI5324.CLOCK_X]:
            self._set_register_field(SI5324._FIELD_Clock_1_Priority, 0b01,
                                     True)
            self._set_register_field(SI5324._FIELD_Clock_2_Priority, 0b00,
                                     True)
        else:
            raise I2CException(
                "Incorrect clock specification, choose CLOCK_1, CLOCK_2, or CLOCK_X"
            )
Ejemplo n.º 10
0
    def get_active_clock(self):
        """
        Returns the clock that has been currently selected as the input to the
        PLL. Internally this is either clock 1 or clock 2, but this function
        will also return CLOCK_X if it is found that clock 2 is in use, but the
        input has been overridden by the external oscillator.

        :return: Current PLL inputt clock: CLOCK_1, CLOCK_2, CLOCK_X, or CLOCK_NONE for not active.
        """
        raw_activeclk = self._get_register_field(SI5324._FIELD_Clock_Active)
        freerun_mode = self._get_register_field(SI5324._FIELD_Free_Run_Mode)

        if (raw_activeclk == 0b01):  # ACTV_REG Clock 1
            return SI5324.CLOCK_1
        elif (raw_activeclk == 0b10):  # ACTV_REG Clock 2
            if (freerun_mode):
                return SI5324.CLOCK_X  # Clock 2 overridden by external oscillator
            else:
                return SI5324.CLOCK_2  # Clock 2 not overridden
        elif (raw_activeclk == 0b00):  # ACTV_REG No clock
            return SI5324.CLOCK_NONE
        else:
            raise I2CException(
                "Device returned invalid ACTV_REG register reponse: 0x{:02X}".
                format(raw_activeclk))
Ejemplo n.º 11
0
    def attach_device(self, channel, device, *args, **kwargs):
        """Attach an I2C device to the TCA multiplexer.

        This method attaches a device to the TCA multiplexer on a specified TCA
        channel. The device must be an instance of, or type, derived from I2CDevice or
        I2CContainer. If a class is passed, a device of the appropriate type is
        initialised and returned.

        :param channel: TCA channel on which the device is present
        :param device: class or instance to attach to the TCA
        :param args: positional arguments to pass to device initialisation
        :param kwargs: keyword arguments to pass to device initialisation
        """
        # If passed a callable type for a device, initialise it having selected the appropriate
        # TCA channel
        if callable(device):
            self.write8(0, 1 << channel)
            self._selected_channel = channel
            device = device(*args, **kwargs)

        # Raise an exception if the device is not and I2CDevice or I2CContainer instance
        if not isinstance(device, I2CDevice) and not isinstance(
                device, I2CContainer):
            raise I2CException(
                'Device %s must be a type or an instance of I2CDevice or I2CContainer'
                % device)

        # Add device to attached devices and set its pre-access callback
        self._attached_devices[device] = channel
        device.pre_access = self.__device_callback
        return device
Ejemplo n.º 12
0
    def set_resistance(self, wiper, resistance):
        """Sets the resistance of a given wiper in rheostat mode (see datasheet)
        :param wiper: Wiper to set 0=A, 1=B
        :param resistance: Desired resistance between H- and W- (Kiloohms)
        """

        if not wiper in [0, 1]:
            raise I2CException("Select either wiper 0 or wiper 1")

        if resistance < 0 or resistance > self.__tot_resistance:
            raise I2CException(
                "Select a resistance between 0 and {:.2f}".format(
                    self.__tot_resistance))

        self.__wiper_pos[wiper] = int(resistance / self.__tot_resistance *
                                      255.0)
        self.write8(wiper, self.__wiper_pos[wiper])
Ejemplo n.º 13
0
    def _set_register_field(self, field, value, verify=False):
        """
        Write a field of <=8 bits into an 8-bit register.
        Field bits are masked to preserve other settings held within the same register.

        Some registers for this device are 'ICAL sensitive', meaning that a calibration
        procedure must be run if they are changed. This is handled automatically unless
        otherwise specified.

        :param field: _Field instance holding relevant register and location of field bits
        :param value: Unsigned byte holding unshifted value to be written to the field
        :param verify: Boolean. If true, read values back to verify correct writing.
        """
        logger.debug("Writing value {} to field {}-{} in register {}".format(
            value, field.startbit, field.get_endbit(), field.register))

        # check input fits in specified field
        if (1 << (field.length)) <= value:
            raise I2CException(
                "Value {} does not fit in specified field of length {}.".
                format(value, field.length))

        old_value = self.readU8(field.register)
        new_msk = (0xff >> (8 - field.length)) << field.get_endbit()
        logger.debug(
            "Register {}: field start: {}, field end: {} -> mask {:b}".format(
                field.register, field.startbit, field.get_endbit(), new_msk))
        new_value = (old_value & ~new_msk) | (value << field.get_endbit())
        logger.debug("Register {}: {:b} -> {:b}".format(
            field.register, old_value, new_value))
        if new_value != old_value:
            self.write8(field.register, new_value)

        if verify:
            verify_value = self._get_register_field(field)
            logger.debug(
                "Verifying value written ({:b}) against re-read: {:b}".format(
                    value, verify_value))
            if verify_value != value:
                raise I2CException(
                    "Value {} was not successfully written to Field {}".format(
                        value, field))

        if (field.register in SI5324._ICAL_sensitive_registers):
            logger.info("Register {} requires iCAL run".format(field.register))
            self.iCAL_required = True
Ejemplo n.º 14
0
 def pins_to_address(A2, A1, A0):
     """
     Return value of address that self.will be used by the device based on the
     address pin states A[2:0]. Arguments should be supplied as 1/0.
     """
     if not all(pin in [0, 1]
                for pin in [A2, A1, A0]):  # Check pins are 1 or 0
         raise I2CException("Pins should be specified as 1 or 0")
     return (0b1101000 | (A2 << 2) | (A1 << 1) | A0)
Ejemplo n.º 15
0
 def read_dac_voltage(self, dac):
     """ reads the dac value and returns it as a voltage
     @param dac : the dac to set
     """
     if dac == 1:
         return (self.read_dac_value(dac) * self.dac_mult[dac - 1])
     elif dac == 4:
         return (self.read_dac_value(dac) * self.dac_mult[dac - 1])
     else:
         raise I2CException(
             "Choose DAC 1 or 4, 2/3 not currently implemented")
Ejemplo n.º 16
0
    def set_terminal_PDs(self, wiper, low, high):
        """Sets the potential difference for H- and L- on a given wiper for set_PD()
        :param wiper: Wiper to set 0=A, 1=B
        :param low: Low PD (Volts)
        :param high: High PD (Volts)
        """

        if not wiper in [0, 1]:
            raise I2CException("Select either wiper 0 or wiper 1")

        self.__low_pd[wiper] = float(low)
        self.__high_pd[wiper] = float(high)
Ejemplo n.º 17
0
    def set_PD(self, wiper, pd):
        """Sets the potential difference of a given wiper in potential divider mode (see datasheet)
        :param wiper: Wiper to set 0=A, 1=B
        :param pd: Target potential difference (Volts)
        """

        if not wiper in [0, 1]:
            raise I2CException("Select either wiper 0 or wiper 1")

        self.__wiper_pos[wiper] = int(
            (pd - self.__low_pd[wiper]) /
            (self.__high_pd[wiper] - self.__low_pd[wiper]) * 255.0)
        self.write8(wiper, self.__wiper_pos[wiper])
Ejemplo n.º 18
0
    def remove_device(self, device):
        """Remove an I2C device from the container.

        This method removes an attached device from the container and clears the
        device callback to prevent it being called for that device.

        :param device: Device to remove from the device.
        """
        if device in self._attached_devices:
            self._attached_devices.remove(device)
            device.pre_access = None
        else:
            raise I2CException('Device %s was not attached to this I2CContainer' % device)
Ejemplo n.º 19
0
    def get_wiper(self, wiper, force=False):
        """Gets a wiper position
        :param wiper: Wiper to get 0=A, 1=B
        :returns: Current position [0-255]
        """

        if not wiper in [0, 1]:
            raise I2CException("Select either wiper 0 or wiper 1")

        if force:
            self.__wiper_pos = [self.readU8(0), self.readU8(1)]

        return self.__wiper_pos[wiper]
Ejemplo n.º 20
0
    def remove_device(self, device):
        """Remove an I2C device from the TCA multiplexer.

        This method removes an attached device from the TCA multiplexer and clears the
        device callback to prevent TCA access for that device.

        :param device: Device to remove from the TCA.
        """
        if device not in self._attached_devices:
            raise I2CException('Device %s is not attached to this TCA' %
                               device)

        self._attached_devices.pop(device)
        device.pre_access = None
Ejemplo n.º 21
0
    def set_from_voltage(self, dac, voltage):
        """ sets the dac i2c value from a voltage
        @param dac : the dac number to set
        @param voltage : the voltage value to use
        """
        if dac == 1:
            value = voltage / self.dac_mult[dac - 1]
            self.set_from_value(dac, int(value))

        elif dac == 4:
            value = voltage / self.dac_mult[dac - 1]
            self.set_from_value(dac, int(value))
        else:
            raise I2CException(
                "Choose DAC 1 or 4, 2/3 not currently implemented")
Ejemplo n.º 22
0
    def set_resistance(self, resistance):
        #Sets the resistance of a given wiper in rheostat mode (see datasheet)
        #:param wiper: Wiper to set 0=A, 1=B
        #:param resistance: Desired resistance between H- and W- (Kiloohms)
        #

        if resistance < 0 or resistance > self.__tot_resistance:
            raise I2CException(
                "Select a resistance between 0 and {:.2f}".format(
                    self.__tot_resistance))

        self.__wiper_pos = int(resistance / self.__tot_resistance *
                               self.__num_wiper_pos)
        self.write8(((self.__wiper_pos & 0xFF00) + 0x400) >> 8,
                    (self.__wiper_pos & 0xFF))
Ejemplo n.º 23
0
    def apply_register_map(self, mapfile_location, verify=True):
        """
        Write configuration from a register map generated with DSPLLsim.
        Since the map is register rather than value-based, there is no need to make use
        of the _Field access functions.

        :param mapfile_location: location of register map file to be read
        :param verify: Boolean. If true, read registers back to verify they are written correctly.
        """
        with open(mapfile_location, 'r') as f:
            for line in f.readlines():
                # The register map starts after general information is printed preceded by '#'
                if line[0] != '#':
                    # Extract register-value pairing from register map
                    register, value = line.split(',')
                    register = int(register)
                    value = int(value[1:3], 16)  # Value is in hex

                    if register == 136 and (value & 0x40):
                        logger.info(
                            "Ignoring write to iCAL, will be applied next")
                        continue

                    # Write register value
                    logger.info("Writing register {} with value {:02X}".format(
                        register, value))
                    self.write8(register, value)

                    if verify:
                        verify_value = self.readU8(register)
                        logger.debug(
                            "Verifying value written ({:b}) against re-read: {:b}"
                            .format(value, verify_value))
                        if verify_value != value:
                            raise I2CException(
                                "Write of byte to register {} failed.".format(
                                    register))

        # ICAL-sensitive registers will have been modified during this process
        self.iCAL_required = True
        self.calibrate()
Ejemplo n.º 24
0
    def set_output_scaled(self, value):
        """Set the output voltage of the DAC.
        This method sets the output voltage of the DAC, specified as a fraction of the
        full scale, i.e between values of 0.0 and 1.0.
        :param value: output voltage value (0.0-1.0)
        """
        # Check a legal output value has been specified
        if value < 0.0 or value > 1.0:
            raise I2CException(
                "Illegal output value {} specified".format(value))

        # Convert the value to ADUs
        value = int(value * 4096)
        if value == 4096:
            value = 4095

        # Extract the MSB and LSB
        msb = (value & 0xFF00) >> 8
        lsb = (value & 0xFF)

        # Write values to device
        self.write8(msb, lsb)
Ejemplo n.º 25
0
    def set_autoselection_mode(self, auto_mode):
        """
        Set the channel auto selection mode.
        In Manual, the channel select will be honored.
        In Auto-revertive, the highest priority will always be chosen.
        In Auto-non-revertive, the highest priority will be chosen if the current channel
        has an alarm.

        :param auto_mode: Mode selection: AUTOMODE_Manual, AUTOMODE_Auto_Non_Revertive,
                            or AUTOMODE_Auto_Revertive.
        """
        if auto_mode in [
                SI5324.AUTOMODE_Manual, SI5324.AUTOMODE_Auto_Revertive,
                SI5324.AUTOMODE_Auto_Non_Revertive
        ]:
            self._set_register_field(SI5324._FIELD_Autoselection, auto_mode)
        else:
            raise I2CException(
                "Incorrect Auto Selection mode specified ({}).".format(
                    auto_mode) +
                " Choose from AUTOMODE_Manual, AUTOMODE_Auto_Non_Revertive," +
                " or AUTOMODE_Auto_Revertive.")
Ejemplo n.º 26
0
    def read_input_raw(self, channel):
        """Convert and read a raw ADC value on a channel.

        This method triggers a conversion on the specified channel and
        reads back the raw 16-bit value from the device

        :param channel: channel to convert
        :return raw conversion result
        """
        # Check legal channel requested
        if channel < 0 or channel >= self.NUM_ADC_CHANNELS:
            raise I2CException("Illegal channel {} requested".format(channel))

        # Trigger a conversion on channel, setting upper 4 bits of address pointer
        self.write8(0x70 + ((channel + 1) << 4), 0)

        # Read conversion register
        data = self.readU16(0)

        # Swap bytes to correct order
        data = ((data & 0xff) << 8) + ((data & 0xff00) >> 8)

        return data
Ejemplo n.º 27
0
    def set_clock_select(self, clock_name, check_auto_en=True):
        """
        Select the clock that will be used to drive PLL input in Manual mode.
        This function will handle freerun mode to choose between which input drives
        clock 2 (the true clock 2 or external oscillator).

        :param clock_name: Selected input clock: CLOCK_1, CLOCK_2 or CLOCK_X (for external Xtal)
        :param check_auto_en: Set False to disable checking if auto-selection is disabled
        """

        # Check Manual selection mode is active.
        if ((self._get_register_field(SI5324._FIELD_Autoselection) !=
             SI5324.AUTOMODE_Manual) and check_auto_en):
            logger.warning(
                "Warning: clock selection made with auto-selection enabled."
                " This setting will not take effect.")

        # Set correct clock selection in CLKSEL, and set freerun mode accordingly for clock 2
        if clock_name == SI5324.CLOCK_1:
            self._set_register_field(SI5324._FIELD_Clock_Select, 0b00, True)
            logger.info("Clock 1 selected")
        elif clock_name == SI5324.CLOCK_2:
            self._set_register_field(SI5324._FIELD_Clock_Select, 0b01, True)
            self.set_freerun_mode(False)
            logger.info(
                "Clock 2 selected, Free Run mode disabled (external oscillator NOT overriding)"
            )
        elif clock_name == SI5324.CLOCK_X:
            self._set_register_field(SI5324._FIELD_Clock_Select, 0b01, True)
            self.set_freerun_mode(True)
            logger.info(
                "Clock 2 selected, Free Run mode enabled (external oscillator overriding)"
            )
        else:
            raise I2CException(
                "Incorrect clock specified. Choose from CLOCK_1, CLOCK_2, or CLOCK_X."
            )
Ejemplo n.º 28
0
    def get_clock_select(self):
        """
        Returns the currently selected clock (CLOCK_1, CLOCK_2, or CLOCK_X) for
        manual mode (NOT necessarily the currently active clock, see
        get_active_clock()...) by combining values read form the device CLKSEL
        register and FreeRun mode register to determine whether the external
        oscillator is overriding the clock 2 input.

        :return: Current Manual input clock selection: CLOCK_1, CLOCK_2, or CLOCK_X
        """
        raw_clksel = self._get_register_field(SI5324._FIELD_Clock_Select)
        freerun_mode = self._get_register_field(SI5324._FIELD_Free_Run_Mode)

        if (raw_clksel == 0b00):  # CLKSEL Clock 1
            return SI5324.CLOCK_1
        elif (raw_clksel == 0b01):  # CLKSEL Clock 2
            if (freerun_mode):
                return SI5324.CLOCK_X  # Clock 2 overridden by external oscillator
            else:
                return SI5324.CLOCK_2  # Clock 2 not overridden
        else:
            raise I2CException(
                "Device returned invalid CLKSEL register reponse: 0x{:02X}".
                format(raw_clksel))
Ejemplo n.º 29
0
    def write_field(self, field, values, i2c_device, verify=False):
        """
        Generic function to write a field to registers, where the field may both span multiple
        registers and start and stop at any bit (completely variable length).

        :field:         Field instance containing information about field location and size
        :value:         Array of byte values that will be written directly to the field bits only.
                        If the field is smaller than 1 byte, supply an array of size 1.
        :i2c_device:    I2CDevice instance that will be written to
        """
        # Convert array values to a single value for easier masking and shifting
        value = _array_to_int(values)

        self._log.debug(
            "Writing value {} to field {}-{} in register {}".format(
                value, field.startbit, field.get_endbit(), field.register))

        # Check input fits in specified field
        if (1 << (field.length)) <= value:
            raise I2CException(
                "Value {} does not fit in specified field of length {}.".
                format(value, field.length))

        # Align new value with register bytes
        value = value << field.get_endbit()

        # Read old value, align with register bytes
        try:
            old_values = super(type(self), self).read_field(field, i2c_device)
        except AttributeError:
            old_values = self.read_field(
                field, i2c_device)  # Probably called directly
        old_value = _array_to_int(old_values) << field.get_endbit()
        self._log.debug("\tOld register value: {:x}".format(old_value))

        # Create mask for value location
        new_mask = int(math.pow(
            2, field.length)) - 1  # Create to match field size
        new_mask = new_mask << field.get_endbit()  # Shift to correct position
        self._log.debug("\tCreated mask: {:x}".format(new_mask))

        # Apply mask to old value to clear new value space
        new_value = old_value & ~new_mask

        # Overwrite high bits from new value
        new_value |= value
        self._log.debug("\tApplied output: {:x}".format(new_value))

        # Convert an array of bytes, and write
        num_full_bytes = int(
            math.ceil((field.length + field.get_endbit()) / float(8)))
        new_value_array = _int_to_array(new_value, num_full_bytes)
        i2c_device.writeList(field.register, new_value_array)  # Perform write
        self._log.debug("\tWrite list: {}".format(new_value_array))

        # Verify
        if verify:
            try:
                verify_value = super(type(self),
                                     self).read_field(field, i2c_device)
            except AttributeError:
                verify_value = self.read_field(
                    field, i2c_device)  # Probably called directly
            verify_value_int = _array_to_int(verify_value)
            self._log.debug(
                "Verifying value written ({:b}) against re-read: {:b}".format(
                    value, verify_value_int))
            if verify_value_int != value:
                raise I2CException(
                    "Value {} was not successfully written to Field {}".format(
                        value, field))

        time.sleep(
            0.040
        )  # Write operations (especially upper02) should be separated by 40ms
Ejemplo n.º 30
0
    def _get_interface(select_line, default_address, log_instance=None):
        """
        Attempt to automatically determine the interface type (QSFP+ or CXP) that is being used by
        the FireFly device at a given I2C address, using a given select line (if used).

        :param select_line:     gpiod Line being used for CS. Provide with gpio_bus or gpiod.
        :param default_address: The address to use when attempting to communicate. Most likely this
                                is the default (0x50) since the device has not been configured.
        :param log_instance:    Because this is a static method, the self._log must be passed in
                                for logging to work.
        :return:                Interface type (FireFly.INTERFACE_*) or None if it could not be
                                determined automatically.
        """
        # Assuming the device is currently on the default address, and OUI is 0x40C880
        tempdevice = I2CDevice(default_address)

        if select_line is not None:
            # Check GPIO control is available
            if not _GPIO_AVAIL:
                raise I2CException(
                    "GPIO control is not available, cannot use CS line")

            # Check select_line is valid
            try:
                if not select_line.is_requested():
                    raise I2CException(
                        "GPIO Line supplied is not requested by user")
            except AttributeError:
                raise I2CException(
                    "Supplied line was not a valid object. Use gpiod or odin_devices.gpio_bus"
                )

            # GPIO select line low
            select_line.set_value(0)

        # Force the page to 0x00
        tempdevice.write8(127, 0x00)
        # Read bytes 168, 169, 170
        cxp_oui = tempdevice.readList(168, 3)
        # Read bytes 165, 166, 167
        qsfp_oui = tempdevice.readList(165, 3)

        if select_line is not None:
            # GPIO select line high
            select_line.set_value(1)

        if log_instance is not None:
            log_instance.debug(
                "Reading OUI fields from device at {}: CXP OUI {}, QSFP OUI {}"
                .format(default_address, cxp_oui, qsfp_oui))

        if cxp_oui == [0x04, 0xC8, 0x80]:
            # OUI found correctly, must be CXP device
            return FireFly.INTERFACE_CXP
        elif qsfp_oui == [0x04, 0xC8, 0x80]:
            # OUI found correctly, must be QSFP interface
            return FireFly.INTERFACE_QSFP
        else:
            if log_instance is not None:
                log_instance.critical(
                    "OUI not found during automatic detection")
            return None