diff --git a/SI5351.py b/SI5351.py new file mode 100644 index 0000000..ef961c5 --- /dev/null +++ b/SI5351.py @@ -0,0 +1,316 @@ +from machine import I2C +import math + +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_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_CRYSTAL_FREQ_25MHZ = 25000000 +SI5351_CRYSTAL_FREQ_27MHZ = 27000000 +SI5351_CRYSTAL_LOAD_6PF = 1<<6 +SI5351_CRYSTAL_LOAD_8PF = 2<<6 +SI5351_CRYSTAL_LOAD_10PF = 3<<6 + +si5351_15to92 = bytearray(b'\x00OOo\x80\x80\x80\x80\x80\x00\x00\x00\x05\x00\x0cf\x00\x00\x02\x02q\x00\x0c\x1a\x00\x00\x86\x00\x01\x00\x01\x00\x00\x00\x00\x00\x01\x00\x1c\x00\x00\x00\x00\x00\x01\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + +SI5351_MULTISYNTH_DIV_4 = 4 +SI5351_MULTISYNTH_DIV_6 = 6 +SI5351_MULTISYNTH_DIV_8 = 8 + +class SI5351: + def __init__( self, i2c, address=0x60, crystalFreq=25000000): + self.i2c = i2c + self.address = address + + self.initialized = False + self.crystalFreq = crystalFreq + self.crystalLoad = SI5351_CRYSTAL_LOAD_10PF + self.crystalPPM = 30 + self.plla_configured = False + self.plla_freq = 0 + self.pllb_configured = False + self.pllb_freq = 0 + return + + def write8( self, register, value): + self.i2c.start() + buffera = bytearray(1) + buffera[0] = value & 0xff + self.i2c.writeto_mem( self.address, register, buffera) + self.i2c.stop() + return + + def read8( self, register, value): + self.i2c.start() + buffera = bytearray(1) + self.i2c.readfrom_mem_into( self.address, register, buffera) + self.i2c.stop() + return + + + def begin( self): + self.write8( SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0xFF) + # Power down all output drivers */ + self.write8(SI5351_REGISTER_16_CLK0_CONTROL, 0x80) + self.write8(SI5351_REGISTER_17_CLK1_CONTROL, 0x80) + self.write8(SI5351_REGISTER_18_CLK2_CONTROL, 0x80) + self.write8(SI5351_REGISTER_19_CLK3_CONTROL, 0x80) + self.write8(SI5351_REGISTER_20_CLK4_CONTROL, 0x80) + self.write8(SI5351_REGISTER_21_CLK5_CONTROL, 0x80) + self.write8(SI5351_REGISTER_22_CLK6_CONTROL, 0x80) + self.write8(SI5351_REGISTER_23_CLK7_CONTROL, 0x80) + + # Set the load capacitance for the XTAL */ + self.write8(SI5351_REGISTER_183_CRYSTAL_INTERNAL_LOAD_CAPACITANCE, + self.crystalLoad) + + # Set interrupt masks as required (see Register 2 description in AN619). + # By default, ClockBuilder Desktop sets this register to 0x18. + # Note that the least significant nibble must remain 0x8, but the most + # significant nibble may be modified to suit your needs. + + # Reset the PLL config fields just in case we call init again + self.plla_configured = False + self.plla_freq = 0 + self.pllb_configured = False + self.pllb_freq = 0 + + # All done! + self.initialized = True + + + + return + + def setClockBuilderData(self ): + i = 0 + + # Make sure we've called init first + + assert self.initialized == True, "you have not initialized the object" + + # Disable all outputs setting CLKx_DIS high + self.write8( SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0xFF) + + # Writes configuration data to device using the register map contents + # generated by ClockBuilder Desktop (registers 15-92 + 149-170) + for i, x in enumerate( range(15,93)): + #print( x, si5351_15to92[i] ) + self.write8( x, si5351_15to92[i] ) + + for i in range(149, 171): + self.write8( i, 0x00) + + # Apply soft reset + self.write8(SI5351_REGISTER_177_PLL_RESET, 0xAC) + + # Enabled desired outputs (see Register 3) + self.write8(SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0x00) + return None + + def setupPLL( self, mult, num, denom, pllsource = 'A'): + assert self.initialized == True, "you have not initialized the object" + assert ( (mult > 14) and (mult < 91) ), "invalid mult parameter" + assert denom > 0, "denom must be > 0" + assert num <= 0xfffff, "invalid parameter num" + assert denom <= 0xfffff, "invalid parameter denom" + if num ==0: + P1 = 128*mult -512 + P2 = num + P3 = denom + else: + P1 = 128*mult + math.floor( 128 * num/denom ) -512 + P2 = 128*num - denom * math.floor( 128 * num/denom) + P3 = denom + + if pllsource == 'A': + baseaddr = 26 + else: + baseaddr = 34 + + self.write8( baseaddr, (P3 & 0x0000FF00) >> 8) + self.write8( baseaddr+1, (P3 & 0x000000FF)) + self.write8( baseaddr+2, (P1 & 0x00030000) >> 16) + self.write8( baseaddr+3, (P1 & 0x0000FF00) >> 8) + self.write8( baseaddr+4, (P1 & 0x000000FF)) + self.write8( baseaddr+5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16) ) + self.write8( baseaddr+6, (P2 & 0x0000FF00) >> 8) + self.write8( baseaddr+7, (P2 & 0x000000FF)) + + self.write8(SI5351_REGISTER_177_PLL_RESET, (1<<7) | (1<<5) ) + if pllsource =='A': + fvco = self.crystalFreq*( mult + num/denom) + self.plla_configured = True + self.plla_freq = int(math.floor( fvco)) + else: + fvco = self.crystalFreq*(mult + num/denom) + self.pllb_configured = True + self.pllb_freq = int(math.floor(fvco)) + return None + + def setupRdiv( self, output, div): + assert output in [0,1,2], "output value invalid" + assert div in [1,2,4,8,16,32,64,128], "div invalid" + divdict = {1: 0, 2: 1, 4: 2, 8: 3, 16: 4, 32: 5, 64: 6, 128: 7} + registers = [ 44, 52, 60] + Rreg = registers[output] + buf = bytearray( 1) + + self.read8(Rreg, buf) + + regval = buf[0] & 0x0F + divider = divdict[div] + divider &= 0x07 + divider <<= 4 + regval |= divider + self.write8(Rreg, regval) + + return None + + def setupMultisynth( self, output, div, num, denom, pllsource): + assert self.initialized == True, "device not initialized" + assert output in [0,1,2], "output out of range" + assert div > 3, "div out of range" + assert denom >0, "denom out of range" + assert num <= 0xfffff, "num has a 20-bit limit" + assert denom <= 0xfffff, "denom as a 20-bit limit" + if pllsource=="A": + assert self.plla_configured == True, "plla has not been configured" + else: + assert self.pllb_configured == True, 'pllb has not been configured' + + # Output Multisynth Divider Equations + # where: a = div, b = num and c = denom + # + # P1 register is an 18-bit value using following formula: + # + # P1[17:0] = 128 * a + floor(128*(b/c)) - 512 + # + # P2 register is a 20-bit value using the following formula: + # + # P2[19:0] = 128 * b - c * floor(128*(b/c)) + # + # P3 register is a 20-bit value using the following formula: + # + # P3[19:0] = c + + if num==0: + # integer mode + P1 = 128 *div -512 + P2 = num + P3 = denom + else: + # Fractional mode */ + P1 = int( 128 * div + math.floor(128 * (num/denom)) - 512 ) + P2 = int( 128 * num - denom * math.floor(128 * (num/denom))) + P3 = denom + + + baseaddrs = [ 42, 50, 58] + baseaddr = baseaddrs[output] + + self.write8( baseaddr, (P3 & 0x0000FF00) >> 8) + self.write8( baseaddr+1, (P3 & 0x000000FF)) + self.write8( baseaddr+2, (P1 & 0x00030000) >> 16) # ToDo: Add DIVBY4 (>150MHz) and R0 support (<500kHz) later */ + self.write8( baseaddr+3, (P1 & 0x0000FF00) >> 8) + self.write8( baseaddr+4, (P1 & 0x000000FF)) + self.write8( baseaddr+5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16) ) + self.write8( baseaddr+6, (P2 & 0x0000FF00) >> 8) + self.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 pllsource == 'B': + clkControlReg |= (1 << 5) # /* Uses PLLB */ + if num == 0: + clkControlReg |= (1 << 6) # Integer mode */ + + if output == 0: + self.write8(SI5351_REGISTER_16_CLK0_CONTROL, clkControlReg) + if output == 1: + self.write8(SI5351_REGISTER_17_CLK1_CONTROL, clkControlReg) + if output == 2: + self.write8(SI5351_REGISTER_18_CLK2_CONTROL, clkControlReg) + + + def enableOutputs( self, enabled=True): + assert self.initialized == True, "Error Device not initialized" + if enabled: + self.write8( SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0x00) + else: + self.write8( SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0xff) + + return + diff --git a/test_clockgen.py b/test_clockgen.py new file mode 100644 index 0000000..7e1b80f --- /dev/null +++ b/test_clockgen.py @@ -0,0 +1,51 @@ +from machine import SPI, Pin, I2C +import SI5351 +import time + +i2c = I2C( scl=Pin(5), sda=Pin(4), freq=400000) +devlist = i2c.scan() +print(devlist) + +clockgen = SI5351.SI5351( i2c) +clockgen.begin() +clockgen.setClockBuilderData() + +# INTEGER ONLY MODE --> most accurate output */ +# Setup PLLA to integer only mode @ 900MHz (must be 600..900MHz) */ +# Set Multisynth 0 to 112.5MHz using integer only mode (div by 4/6/8) */ +# 25MHz * 36 = 900 MHz, then 900 MHz / 8 = 112.5 MHz */ +print("Set PLLA to 900MHz") + +clockgen.setupPLL(36, 0, 1, pllsource='A') + +print("Set Output #0 to {:2.2f}MHz".format(900/8)) + +clockgen.setupMultisynth( output=0, div=8, num=0, denom=1, pllsource="A") + +# FRACTIONAL MODE --> More flexible but introduce clock jitter +# Setup PLLB to fractional mode @616.66667MHz (XTAL * 24 + 2/3) +# Setup Multisynth 1 to 13.55311MHz (PLLB/45.5) */ +mult = 32 +pllb = 25e6*mult +print( 'PLLB = {:5.2e} Hz'.format(pllb)) +clockgen.setupPLL( mult, 0, 1, "B") +divider = 32 +num2 = 2 +denom2 =10 +m2 = pllb/(divider+num2/denom2) +print( "Set Output #1 to {:5.4E} Hz".format(m2) ) +clockgen.setupMultisynth( 1, divider, num2, denom2, pllsource="B") + + +# Multisynth 2 is not yet used and won't be enabled, but can be */ +# Use PLLB @ 616.66667MHz, then divide by 900 -> 685.185 KHz */ +# then divide by 64 for 10.706 KHz */ +# configured using either PLL in either integer or fractional mode */ + +print("Set Output #2 to {:5.1e} Hz".format(400e6/200/2.0)) +clockgen.setupMultisynth(2, 100, 0, 1, pllsource="B") +clockgen.setupRdiv(2, 4) + +# Enable the clocks +clockgen.enableOutputs(True) +