Si5351.py 13.9 KB
from Adafruit_I2C import Adafruit_I2C
import smbus
import time

SI5351_REGISTER_0_DEVICE_STATUS                       = 0
SI5351_REGISTER_1_INTERRUPT_STATUS_STICKY             = 1
SI5351_REGISTER_2_INTERRUPT_STATUS_MASK               = 2
SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL               = 3
SI5351_REGISTER_9_OEB_PIN_ENABLE_CONTROL              = 9
SI5351_REGISTER_15_PLL_INPUT_SOURCE                   = 15
SI5351_REGISTER_16_CLK0_CONTROL                       = 16
SI5351_REGISTER_17_CLK1_CONTROL                       = 17
SI5351_REGISTER_18_CLK2_CONTROL                       = 18
SI5351_REGISTER_19_CLK3_CONTROL                       = 19
SI5351_REGISTER_20_CLK4_CONTROL                       = 20
SI5351_REGISTER_21_CLK5_CONTROL                       = 21
SI5351_REGISTER_22_CLK6_CONTROL                       = 22
SI5351_REGISTER_23_CLK7_CONTROL                       = 23
SI5351_REGISTER_24_CLK3_0_DISABLE_STATE               = 24
SI5351_REGISTER_25_CLK7_4_DISABLE_STATE               = 25
SI5351_REGISTER_42_MULTISYNTH0_PARAMETERS_1           = 42
SI5351_REGISTER_43_MULTISYNTH0_PARAMETERS_2           = 43
SI5351_REGISTER_44_MULTISYNTH0_PARAMETERS_3           = 44
SI5351_REGISTER_45_MULTISYNTH0_PARAMETERS_4           = 45
SI5351_REGISTER_46_MULTISYNTH0_PARAMETERS_5           = 46
SI5351_REGISTER_47_MULTISYNTH0_PARAMETERS_6           = 47
SI5351_REGISTER_48_MULTISYNTH0_PARAMETERS_7           = 48
SI5351_REGISTER_49_MULTISYNTH0_PARAMETERS_8           = 49
SI5351_REGISTER_50_MULTISYNTH1_PARAMETERS_1           = 50
SI5351_REGISTER_51_MULTISYNTH1_PARAMETERS_2           = 51
SI5351_REGISTER_52_MULTISYNTH1_PARAMETERS_3           = 52
SI5351_REGISTER_53_MULTISYNTH1_PARAMETERS_4           = 53
SI5351_REGISTER_54_MULTISYNTH1_PARAMETERS_5           = 54
SI5351_REGISTER_55_MULTISYNTH1_PARAMETERS_6           = 55
SI5351_REGISTER_56_MULTISYNTH1_PARAMETERS_7           = 56
SI5351_REGISTER_57_MULTISYNTH1_PARAMETERS_8           = 57
SI5351_REGISTER_58_MULTISYNTH2_PARAMETERS_1           = 58
SI5351_REGISTER_59_MULTISYNTH2_PARAMETERS_2           = 59
SI5351_REGISTER_60_MULTISYNTH2_PARAMETERS_3           = 60
SI5351_REGISTER_61_MULTISYNTH2_PARAMETERS_4           = 61
SI5351_REGISTER_62_MULTISYNTH2_PARAMETERS_5           = 62
SI5351_REGISTER_63_MULTISYNTH2_PARAMETERS_6           = 63
SI5351_REGISTER_64_MULTISYNTH2_PARAMETERS_7           = 64
SI5351_REGISTER_65_MULTISYNTH2_PARAMETERS_8           = 65
SI5351_REGISTER_66_MULTISYNTH3_PARAMETERS_1           = 66
SI5351_REGISTER_67_MULTISYNTH3_PARAMETERS_2           = 67
SI5351_REGISTER_68_MULTISYNTH3_PARAMETERS_3           = 68
SI5351_REGISTER_69_MULTISYNTH3_PARAMETERS_4           = 69
SI5351_REGISTER_70_MULTISYNTH3_PARAMETERS_5           = 70
SI5351_REGISTER_71_MULTISYNTH3_PARAMETERS_6           = 71
SI5351_REGISTER_72_MULTISYNTH3_PARAMETERS_7           = 72
SI5351_REGISTER_73_MULTISYNTH3_PARAMETERS_8           = 73
SI5351_REGISTER_74_MULTISYNTH4_PARAMETERS_1           = 74
SI5351_REGISTER_75_MULTISYNTH4_PARAMETERS_2           = 75
SI5351_REGISTER_76_MULTISYNTH4_PARAMETERS_3           = 76
SI5351_REGISTER_77_MULTISYNTH4_PARAMETERS_4           = 77
SI5351_REGISTER_78_MULTISYNTH4_PARAMETERS_5           = 78
SI5351_REGISTER_79_MULTISYNTH4_PARAMETERS_6           = 79
SI5351_REGISTER_80_MULTISYNTH4_PARAMETERS_7           = 80
SI5351_REGISTER_81_MULTISYNTH4_PARAMETERS_8           = 81
SI5351_REGISTER_82_MULTISYNTH5_PARAMETERS_1           = 82
SI5351_REGISTER_83_MULTISYNTH5_PARAMETERS_2           = 83
SI5351_REGISTER_84_MULTISYNTH5_PARAMETERS_3           = 84
SI5351_REGISTER_85_MULTISYNTH5_PARAMETERS_4           = 85
SI5351_REGISTER_86_MULTISYNTH5_PARAMETERS_5           = 86
SI5351_REGISTER_87_MULTISYNTH5_PARAMETERS_6           = 87
SI5351_REGISTER_88_MULTISYNTH5_PARAMETERS_7           = 88
SI5351_REGISTER_89_MULTISYNTH5_PARAMETERS_8           = 89
SI5351_REGISTER_90_MULTISYNTH6_PARAMETERS             = 90
SI5351_REGISTER_91_MULTISYNTH7_PARAMETERS             = 91
SI5351_REGISTER_092_CLOCK_6_7_OUTPUT_DIVIDER          = 92
SI5351_REGISTER_SPREAD_SPECTRUM                       = 149
SI5351_REGISTER_165_CLK0_INITIAL_PHASE_OFFSET         = 165
SI5351_REGISTER_166_CLK1_INITIAL_PHASE_OFFSET         = 166
SI5351_REGISTER_167_CLK2_INITIAL_PHASE_OFFSET         = 167
SI5351_REGISTER_168_CLK3_INITIAL_PHASE_OFFSET         = 168
SI5351_REGISTER_169_CLK4_INITIAL_PHASE_OFFSET         = 169
SI5351_REGISTER_170_CLK5_INITIAL_PHASE_OFFSET         = 170
SI5351_REGISTER_177_PLL_RESET                         = 177
SI5351_REGISTER_183_CRYSTAL_INTERNAL_LOAD_CAPACITANCE = 183

SI5351_I2C_ADDRESS_DEFAULT = 0x60

SI5351_CRYSTAL_LOAD_6PF  = (1<<6)
SI5351_CRYSTAL_LOAD_8PF  = (2<<6)
SI5351_CRYSTAL_LOAD_10PF = (3<<6)

SI5351_CRYSTAL_FREQ_25MHZ = 25000000
SI5351_CRYSTAL_FREQ_27MHZ = 27000000


class Si5351(object):

    PLL_A = 0
    PLL_B = 1

    R_DIV_1   = 0
    R_DIV_2   = 1
    R_DIV_4   = 2
    R_DIV_8   = 3
    R_DIV_16  = 4
    R_DIV_32  = 5
    R_DIV_64  = 6
    R_DIV_128 = 7


    def __init__(self, address = SI5351_I2C_ADDRESS_DEFAULT, busnum=-1):

        self.crystalFreq     = SI5351_CRYSTAL_FREQ_25MHZ
        self.crystalLoad     = SI5351_CRYSTAL_LOAD_10PF
        self.crystalPPM      = 30
        self.plla_freq       = 0
        self.pllb_freq       = 0

        self.i2c = Adafruit_I2C(address=address, busnum=busnum, debug=True)
        self.address = address

        # Disable all outputs setting CLKx_DIS high
        self.i2c.write8(SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0xFF)

        # Power down all output drivers
        self.i2c.write8(SI5351_REGISTER_16_CLK0_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_17_CLK1_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_18_CLK2_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_19_CLK3_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_20_CLK4_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_21_CLK5_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_22_CLK6_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_23_CLK7_CONTROL, 0x80)
        self.i2c.write8(SI5351_REGISTER_SPREAD_SPECTRUM, 0x00)

        # Set the load capacitance for the XTAL
        self.i2c.write8(SI5351_REGISTER_183_CRYSTAL_INTERNAL_LOAD_CAPACITANCE, self.crystalLoad)


    def setupPLL(self, pll, mult, num=0, denom=1):

        # @brief  Sets the multiplier for the specified PLL
        # @param  pll   The PLL to configure, which must be one of the following:
        #               - SI5351_PLL_A
        #               - SI5351_PLL_B
        # @param  mult  The PLL integer multiplier (must be between 15 and 90)
        # @param  num   The 20-bit numerator for fractional output (0..1,048,575).
        #               Set this to '0' for integer output.
        # @param  denom The 20-bit denominator for fractional output (1..1,048,575).
        #               Set this to '1' or higher to avoid divider by zero errors.
        # @section PLL Configuration
        #     fVCO is the PLL output, and must be between 600..900MHz, where:
        #     fVCO = fXTAL * (a+(b/c))
        #     fXTAL = the crystal input frequency
        #     a     = an integer between 15 and 90
        #     b     = the fractional numerator (0..1,048,575)
        #     c     = the fractional denominator (1..1,048,575)
        # @note Try to use integers whenever possible to avoid clock jitter
        #     (only use the a part, setting b to '0' and c to '1').
        #     See: http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
        #
        # Feedback Multisynth Divider Equation
        # where: a = mult, b = num and c = denom
        # P1[17:0] = 128 * mult + floor(128*(num/denom)) - 512
        # P2[19:0] = 128 * num - denom * floor(128*(num/denom))
        # P3[19:0] = denom

        # Set the main PLL config registers
        P1 = 128 * mult + int(128.0 * num / denom) - 512
        P2 = 128 * num - denom * int(128.0 * num / denom)
        P3 = denom

        print(str(P1)+" "+str(P2)+" "+str(P3))
        # Get the appropriate starting point for the PLL registers
        baseaddr = 26 if pll == self.PLL_A else 34

        # The datasheet is a nightmare of typos and inconsistencies here!
        self.i2c.write8(baseaddr,   (P3 & 0x0000FF00) >> 8)
        self.i2c.write8(baseaddr + 1, (P3 & 0x000000FF))
        self.i2c.write8(baseaddr + 2, (P1 & 0x00030000) >> 16)
        self.i2c.write8(baseaddr + 3, (P1 & 0x0000FF00) >> 8)
        self.i2c.write8(baseaddr + 4, (P1 & 0x000000FF))
        self.i2c.write8(baseaddr + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16) )
        self.i2c.write8(baseaddr + 6, (P2 & 0x0000FF00) >> 8)
        self.i2c.write8(baseaddr + 7, (P2 & 0x000000FF))

        # Reset both PLLs
        self.i2c.write8(SI5351_REGISTER_177_PLL_RESET, (1<<7) | (1<<5))

        # Store the frequency settings for use with the Multisynth helper
        fvco = int(self.crystalFreq * (mult + float(num) / denom))
        if pll == self.PLL_A:
            self.plla_freq = fvco
        else:
            self.pllb_freq = fvco


    def setupRdiv(self, output, div):

        if output == 0: Rreg = SI5351_REGISTER_44_MULTISYNTH0_PARAMETERS_3
        if output == 1: Rreg = SI5351_REGISTER_52_MULTISYNTH1_PARAMETERS_3
        if output == 2: Rreg = SI5351_REGISTER_60_MULTISYNTH2_PARAMETERS_3
        return self.i2c.write8(Rreg, (div & 0x07) << 4)


    def setupMultisynth(self, output, pll, div, num=0, denom=1):

        # @brief  Configures the Multisynth divider, which determines the
        #         output clock frequency based on the specified PLL input.
        # 
        # @param  output    The output channel to use (0..2)
        # @param  pll       The PLL input source to use, which must be one of:
        #                   - SI5351_PLL_A
        #                   - SI5351_PLL_B
        # @param  div       The integer divider for the Multisynth output.
        #                   If pure integer values are used, this value must
        #                   be one of:
        #                   - SI5351_MULTISYNTH_DIV_4
        #                   - SI5351_MULTISYNTH_DIV_6
        #                   - SI5351_MULTISYNTH_DIV_8
        #                   If fractional output is used, this value must be
        #                   between 8 and 900.
        # @param  num       The 20-bit numerator for fractional output
        #                   (0..1,048,575). Set this to '0' for integer output.
        # @param  denom     The 20-bit denominator for fractional output
        #                   (1..1,048,575). Set this to '1' or higher to
        #                   avoid divide by zero errors.
        # 
        # @section Output Clock Configuration
        # 
        # The multisynth dividers are applied to the specified PLL output,
        # and are used to reduce the PLL output to a valid range (500kHz
        # to 160MHz). The relationship can be seen in this formula, where
        # fVCO is the PLL output frequency and MSx is the multisynth
        # divider:
        #     fOUT = fVCO / MSx
        # Valid multisynth dividers are 4, 6, or 8 when using integers,
        # or any fractional values between 8 + 1/1,048,575 and 900 + 0/1
        # The following formula is used for the fractional mode divider:
        #     a + b / c
        # a = The integer value, which must be 4, 6 or 8 in integer mode (MSx_INT=1)
        #     or 8..900 in fractional mode (MSx_INT=0).
        # b = The fractional numerator (0..1,048,575)
        # c = The fractional denominator (1..1,048,575)
        # @note   Try to use integers whenever possible to avoid clock jitter
        # @note   For output frequencies > 150MHz, you must set the divider
        #         to 4 and adjust to PLL to generate the frequency (for example
        #         a PLL of 640 to generate a 160MHz output clock). This is not
        #         yet supported in the driver, which limits frequencies to
        #         500kHz .. 150MHz.
        # @note   For frequencies below 500kHz (down to 8kHz) Rx_DIV must be
        #         used, but this isn't currently implemented in the driver.
        #
        # Output Multisynth Divider Equations
        # where: a = div, b = num and c = denom
        # P1[17:0] = 128 * a + floor(128*(b/c)) - 512
        # P2[19:0] = 128 * b - c * floor(128*(b/c))
        # P3[19:0] = c

        # Set the main PLL config registers
        P1 = 128 * div + int(128.0 * num / denom) - 512
        P2 = 128 * num - denom * int(128.0 * num / denom)
        P3 = denom

        # Get the appropriate starting point for the PLL registers
        if output == 0: baseaddr = SI5351_REGISTER_42_MULTISYNTH0_PARAMETERS_1
        if output == 1: baseaddr = SI5351_REGISTER_50_MULTISYNTH1_PARAMETERS_1
        if output == 2: baseaddr = SI5351_REGISTER_58_MULTISYNTH2_PARAMETERS_1

        # Set the MSx config registers
        self.i2c.write8(baseaddr,   (P3 & 0x0000FF00) >> 8)
        self.i2c.write8(baseaddr + 1, (P3 & 0x000000FF))
        self.i2c.write8(baseaddr + 2, (P1 & 0x00030000) >> 16)	# ToDo: Add DIVBY4 (>150MHz) and R0 support (<500kHz) later
        self.i2c.write8(baseaddr + 3, (P1 & 0x0000FF00) >> 8)
        self.i2c.write8(baseaddr + 4, (P1 & 0x000000FF))
        self.i2c.write8(baseaddr + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16) )
        self.i2c.write8(baseaddr + 6, (P2 & 0x0000FF00) >> 8)
        self.i2c.write8(baseaddr + 7, (P2 & 0x000000FF))

        # Configure the clk control and enable the output
        clkControlReg = 0x0F                              # 8mA drive strength, MS0 as CLK0 source, Clock not inverted, powered up
        if pll == self.PLL_B: clkControlReg |= (1 << 5)   # Uses PLLB 
        if num == 0: clkControlReg |= (1 << 6)            # Integer mode
        if output == 0: self.i2c.write8(SI5351_REGISTER_16_CLK0_CONTROL, clkControlReg)
        if output == 1: self.i2c.write8(SI5351_REGISTER_17_CLK1_CONTROL, clkControlReg)
        if output == 2: self.i2c.write8(SI5351_REGISTER_18_CLK2_CONTROL, clkControlReg)


    def enableOutputs(self, enabled):

        # Enabled desired outputs (see Register 3)
        val = 0x00 if enabled else 0xFF
        self.i2c.write8(SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, val)