diff --git a/README.md b/README.md index 90282f4..f7c7955 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ There will be some inherent error in the reference oscillator's actual frequency The calibration method is called like this: - si5351.set_correction(-6190); + si5351.set_correction(-6190, SI5351_PLL_INPUT_XO); However, you may use the third argument in the _init()_ method to specify the frequency correction and may not actually need to use the explict _set_correction()_ method in your code. @@ -219,10 +219,17 @@ Using an External Reference (Si5351C) ------------------------------------- _Please see the example sketch **si5351_ext_ref.ino**_ -The Si5351C variant has a CLKIN input (pin 6) which allows the use of an alternate external CMOS clock reference from 10 to 100 MHz. Either PLLA and/or PLLB can be locked to this external reference. The library currently only supports the calculations to use one reference signal, which is declared at initialization: +The Si5351C variant has a CLKIN input (pin 6) which allows the use of an alternate external CMOS clock reference from 10 to 100 MHz. Either PLLA and/or PLLB can be locked to this external reference. The library tracks the referenced frequencies and correction factors individually for both the crystal oscillator reference (XO) and external reference (CLKIN). - // Initialize the Si5351 to use a 10 MHz clock input on CLKIN - si5351.init(SI5351_CRYSTAL_LOAD_0PF, 10000000UL, 0); +The XO reference frequency is set during the call to _init()_. If you are going to use the external reference clock, then set its nominal frequency with the _set_ref_freq()_ method: + + // Set the CLKIN reference frequency to 10 MHz + si5351.set_ref_freq(10000000UL, SI5351_PLL_INPUT_CLKIN); + +A correction factor for the external reference clock may also now be set: + + // Apply a correction factor to CLKIN + si5351.set_correction(0, SI5351_PLL_INPUT_CLKIN); The _set_pll_input()_ method is used to set the desired PLLs to reference to the external reference signal on CLKIN instead of the XO signal: @@ -230,7 +237,7 @@ The _set_pll_input()_ method is used to set the desired PLLs to reference to the si5351.set_pll_input(SI5351_PLLA, SI5351_PLL_INPUT_CLKIN); si5351.set_pll_input(SI5351_PLLB, SI5351_PLL_INPUT_CLKIN); -Once that is set, the library can be used as you normally would, with all of the frequency calculations done based on the reference frequency set in _init()_. +Once that is set, the library can be used as you normally would, with all of the frequency calculations done based on the reference frequency set in _set_ref_freq()_. Alternate I2C Addresses @@ -313,7 +320,7 @@ uint8_t Si5351::set_freq(uint64_t freq, enum si5351_clock clk) * track that all settings are sane yourself. * * freq - Output frequency in Hz - * pll_freq - Frequency of the PLL driving the Multisynth + * pll_freq - Frequency of the PLL driving the Multisynth in Hz * 100 * clk - Clock output * (use the si5351_clock enum) */ @@ -325,7 +332,7 @@ uint8_t Si5351::set_freq(uint64_t freq, enum si5351_clock clk) * * Set the specified PLL to a specific oscillation frequency * - * pll_freq - Desired PLL frequency + * pll_freq - Desired PLL frequency in Hz * 100 * target_pll - Which PLL to set * (use the si5351_pll enum) */ @@ -391,7 +398,11 @@ void Si5351::update_status(void) ### set_correction() ``` /* - * set_correction(int32_t corr) + * set_correction(int32_t corr, enum si5351_pll_input ref_osc) + * + * corr - Correction factor in ppb + * ref_osc - Desired reference oscillator + * (use the si5351_pll_input enum) * * Use this to set the oscillator correction factor. * This value is a signed 32-bit integer of the @@ -412,7 +423,7 @@ void Si5351::update_status(void) * should not have to be done again for the same Si5351 and * crystal. */ -void Si5351::set_correction(int32_t corr) +void Si5351::set_correction(int32_t corr, enum si5351_pll_input ref_osc) ``` ### set_phase() ``` @@ -433,12 +444,16 @@ void Si5351::set_phase(enum si5351_clock clk, uint8_t phase) ### get_correction() ``` /* - * get_correction(void) + * get_correction(enum si5351_pll_input ref_osc) + * + * ref_osc - Desired reference oscillator + * 0: crystal oscillator (XO) + * 1: external clock input (CLKIN) * * Returns the oscillator correction factor stored * in RAM. */ -int32_t Si5351::get_correction(void) +int32_t Si5351::get_correction(enum si5351_pll_input ref_osc) ``` ### pll_reset() ``` @@ -570,6 +585,31 @@ void Si5351::set_clock_fanout(enum si5351_clock_fanout fanout, uint8_t enable) */ void Si5351::set_pll_input(enum si5351_pll pll, enum si5351_pll_input input) ``` +### set_vcxo() +``` +/* + * set_vcxo(uint64_t pll_freq, uint8_t ppm) + * + * pll_freq - Desired PLL base frequency in Hz * 100 + * ppm - VCXO pull limit in ppm + * + * Set the parameters for the VCXO on the Si5351B. + */ +void Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm) +``` +### set_ref_freq() +``` +/* + * set_ref_freq(uint32_t ref_freq, enum si5351_pll_input ref_osc) + * + * ref_freq - Reference oscillator frequency in Hz + * ref_osc - Which reference oscillator frequency to set + * (use the si5351_pll_input enum) + * + * Set the reference frequency value for the desired reference oscillator + */ +void Si5351::set_ref_freq(uint32_t ref_freq, enum si5351_pll_input ref_osc) +``` ### si5351_write_bulk() ``` uint8_t Si5351::si5351_write_bulk(uint8_t addr, uint8_t bytes, uint8_t *data) @@ -666,6 +706,10 @@ This library does not currently support the spread spectrum function of the Si53 Changelog --------- +* v2.1.0 + + * Add support for reference frequencies and corrections for both the XO and CLKIN + * v2.0.7 * Change _set_freq()_ behavior so that the output is only automatically enabled the very first time that _set_freq()_ is called diff --git a/examples/si5351_ext_ref/si5351_ext_ref.ino b/examples/si5351_ext_ref/si5351_ext_ref.ino index 51b46a7..a5f6221 100644 --- a/examples/si5351_ext_ref/si5351_ext_ref.ino +++ b/examples/si5351_ext_ref/si5351_ext_ref.ino @@ -28,8 +28,14 @@ void setup() // Start serial Serial.begin(57600); - // Initialize the Si5351 to use a 10 MHz clock input on CLKIN - si5351.init(SI5351_CRYSTAL_LOAD_0PF, 10000000UL, 0); + // Initialize the Si5351 to use a 25 MHz clock on the XO input + si5351.init(SI5351_CRYSTAL_LOAD_0PF, 0, 0); + + // Set the CLKIN reference frequency to 10 MHz + si5351.set_ref_freq(10000000UL, SI5351_PLL_INPUT_CLKIN); + + // Apply a correction factor to CLKIN + si5351.set_correction(0, SI5351_PLL_INPUT_CLKIN); // Set PLLA and PLLB to use the signal on CLKIN instead of the XTAL si5351.set_pll_input(SI5351_PLLA, SI5351_PLL_INPUT_CLKIN); diff --git a/keywords.txt b/keywords.txt index b3efca0..4f04380 100644 --- a/keywords.txt +++ b/keywords.txt @@ -22,6 +22,7 @@ set_clock_disable KEYWORD2 set_clock_fanout KEYWORD2 set_pll_input KEYWORD2 set_vcxo KEYWORD2 +set_ref_freq KEYWORD2 si5351_write_bulk KEYWORD2 si5351_write KEYWORD2 si5351_read KEYWORD2 @@ -32,6 +33,8 @@ clk_freq KEYWORD2 plla_freq KEYWORD2 pllb_freq KEYWORD2 xtal_freq KEYWORD2 +plla_ref_osc KEYWORD2 +pllb_ref_osc KEYWORD2 SI5351_PLL_FIXED LITERAL1 SI5351_FREQ_MULT LITERAL1 diff --git a/library.properties b/library.properties index 642dfc4..e87bcc3 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Etherkit Si5351 -version=2.0.6 +version=2.1.0 author=Jason Milldrum maintainer=Jason Milldrum sentence=A full-featured library for the Si5351 series of clock generator ICs from Silicon Labs diff --git a/src/si5351.cpp b/src/si5351.cpp index b82aa42..6ec7276 100644 --- a/src/si5351.cpp +++ b/src/si5351.cpp @@ -47,7 +47,12 @@ Si5351::Si5351(uint8_t i2c_addr): dev_int_status.LOL_A_STKY = 0; dev_int_status.LOS_STKY = 0; - xtal_freq = SI5351_XTAL_FREQ; + xtal_freq[0] = SI5351_XTAL_FREQ; + + // Start by using XO ref osc as default for each PLL + plla_ref_osc = SI5351_PLL_INPUT_XO; + pllb_ref_osc = SI5351_PLL_INPUT_XO; + clkin_div = SI5351_CLKIN_DIV_1; } /* @@ -58,12 +63,12 @@ Si5351::Si5351(uint8_t i2c_addr): * * xtal_load_c - Crystal load capacitance. Use the SI5351_CRYSTAL_LOAD_*PF * defines in the header file - * ref_osc_freq - Crystal/reference oscillator frequency in 1 Hz increments. + * xo_freq - Crystal/reference oscillator frequency in 1 Hz increments. * Defaults to 25000000 if a 0 is used here. * corr - Frequency correction constant in parts-per-billion * */ -void Si5351::init(uint8_t xtal_load_c, uint32_t ref_osc_freq, int32_t corr) +void Si5351::init(uint8_t xtal_load_c, uint32_t xo_freq, int32_t corr) { // Start I2C comms Wire.begin(); @@ -71,37 +76,18 @@ void Si5351::init(uint8_t xtal_load_c, uint32_t ref_osc_freq, int32_t corr) // Set crystal load capacitance si5351_write(SI5351_CRYSTAL_LOAD, (xtal_load_c & SI5351_CRYSTAL_LOAD_MASK) | 0b00010010); - // Change the ref osc freq if different from default - // Divide down if greater than 30 MHz - if (ref_osc_freq != 0) + // Set up the XO reference frequency + if (xo_freq != 0) { - uint8_t reg_val; - reg_val = si5351_read(SI5351_PLL_INPUT_SOURCE); - - // Clear the bits first - reg_val &= ~(SI5351_CLKIN_DIV_MASK); - - if(ref_osc_freq <= 30000000UL) - { - xtal_freq = ref_osc_freq; - reg_val |= SI5351_CLKIN_DIV_1; - } - else if(ref_osc_freq > 30000000UL && ref_osc_freq <= 60000000UL) - { - xtal_freq = ref_osc_freq / 2; - reg_val |= SI5351_CLKIN_DIV_2; - } - else if(ref_osc_freq > 60000000UL && ref_osc_freq <= 100000000UL) - { - xtal_freq = ref_osc_freq / 4; - reg_val |= SI5351_CLKIN_DIV_4; - } - - si5351_write(SI5351_PLL_INPUT_SOURCE, reg_val); + set_ref_freq(xo_freq, SI5351_PLL_INPUT_XO); + } + else + { + set_ref_freq(SI5351_XTAL_FREQ, SI5351_PLL_INPUT_XO); } - // Set the frequency calibration - set_correction(corr); + // Set the frequency calibration for the XO + set_correction(corr, SI5351_PLL_INPUT_XO); reset(); } @@ -135,7 +121,7 @@ void Si5351::reset(void) si5351_write(22, 0x0c); si5351_write(23, 0x0c); - // Set PLLA to 800 MHz for automatic tuning + // Set PLLA and PLLB to 800 MHz for automatic tuning set_pll(SI5351_PLL_FIXED, SI5351_PLLA); set_pll(SI5351_PLL_FIXED, SI5351_PLLB); @@ -445,7 +431,7 @@ uint8_t Si5351::set_freq(uint64_t freq, enum si5351_clock clk) * track that all settings are sane yourself. * * freq - Output frequency in Hz - * pll_freq - Frequency of the PLL driving the Multisynth + * pll_freq - Frequency of the PLL driving the Multisynth in Hz * 100 * clk - Clock output * (use the si5351_clock enum) */ @@ -500,7 +486,7 @@ uint8_t Si5351::set_freq_manual(uint64_t freq, uint64_t pll_freq, enum si5351_cl * * Set the specified PLL to a specific oscillation frequency * - * pll_freq - Desired PLL frequency + * pll_freq - Desired PLL frequency in Hz * 100 * target_pll - Which PLL to set * (use the si5351_pll enum) */ @@ -508,7 +494,14 @@ void Si5351::set_pll(uint64_t pll_freq, enum si5351_pll target_pll) { struct Si5351RegSet pll_reg; - pll_calc(pll_freq, &pll_reg, ref_correction, 0); + if(target_pll == SI5351_PLLA) + { + pll_calc(SI5351_PLLA, pll_freq, &pll_reg, ref_correction[plla_ref_osc], 0); + } + else + { + pll_calc(SI5351_PLLB, pll_freq, &pll_reg, ref_correction[pllb_ref_osc], 0); + } // Derive the register values to write @@ -751,7 +744,11 @@ void Si5351::update_status(void) } /* - * set_correction(int32_t corr) + * set_correction(int32_t corr, enum si5351_pll_input ref_osc) + * + * corr - Correction factor in ppb + * ref_osc - Desired reference oscillator + * (use the si5351_pll_input enum) * * Use this to set the oscillator correction factor. * This value is a signed 32-bit integer of the @@ -772,9 +769,9 @@ void Si5351::update_status(void) * should not have to be done again for the same Si5351 and * crystal. */ -void Si5351::set_correction(int32_t corr) +void Si5351::set_correction(int32_t corr, enum si5351_pll_input ref_osc) { - ref_correction = corr; + ref_correction[(uint8_t)ref_osc] = corr; // Recalculate and set PLL freqs based on correction value set_pll(plla_freq, SI5351_PLLA); @@ -802,14 +799,18 @@ void Si5351::set_phase(enum si5351_clock clk, uint8_t phase) } /* - * get_correction(void) + * get_correction(enum si5351_pll_input ref_osc) + * + * ref_osc - Desired reference oscillator + * 0: crystal oscillator (XO) + * 1: external clock input (CLKIN) * * Returns the oscillator correction factor stored * in RAM. */ -int32_t Si5351::get_correction(void) +int32_t Si5351::get_correction(enum si5351_pll_input ref_osc) { - return ref_correction; + return ref_correction[(uint8_t)ref_osc]; } /* @@ -1117,26 +1118,35 @@ void Si5351::set_pll_input(enum si5351_pll pll, enum si5351_pll_input input) uint8_t reg_val; reg_val = si5351_read(SI5351_PLL_INPUT_SOURCE); + // Clear the bits first + //reg_val &= ~(SI5351_CLKIN_DIV_MASK); + switch(pll) { case SI5351_PLLA: if(input == SI5351_PLL_INPUT_CLKIN) { reg_val |= SI5351_PLLA_SOURCE; + reg_val |= clkin_div; + plla_ref_osc = SI5351_PLL_INPUT_CLKIN; } else { reg_val &= ~(SI5351_PLLA_SOURCE); + plla_ref_osc = SI5351_PLL_INPUT_XO; } break; case SI5351_PLLB: if(input == SI5351_PLL_INPUT_CLKIN) { reg_val |= SI5351_PLLB_SOURCE; + reg_val |= clkin_div; + pllb_ref_osc = SI5351_PLL_INPUT_CLKIN; } else { reg_val &= ~(SI5351_PLLB_SOURCE); + pllb_ref_osc = SI5351_PLL_INPUT_XO; } break; default: @@ -1144,8 +1154,19 @@ void Si5351::set_pll_input(enum si5351_pll pll, enum si5351_pll_input input) } si5351_write(SI5351_PLL_INPUT_SOURCE, reg_val); + + set_pll(plla_freq, SI5351_PLLA); + set_pll(pllb_freq, SI5351_PLLB); } +/* + * set_vcxo(uint64_t pll_freq, uint8_t ppm) + * + * pll_freq - Desired PLL base frequency in Hz * 100 + * ppm - VCXO pull limit in ppm + * + * Set the parameters for the VCXO on the Si5351B. + */ void Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm) { struct Si5351RegSet pll_reg; @@ -1163,7 +1184,7 @@ void Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm) } // Set PLLB params - vcxo_param = pll_calc(pll_freq, &pll_reg, ref_correction, 1); + vcxo_param = pll_calc(SI5351_PLLB, pll_freq, &pll_reg, ref_correction[pllb_ref_osc], 1); // Derive the register values to write @@ -1220,6 +1241,58 @@ void Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm) si5351_write(SI5351_VXCO_PARAMETERS_HIGH, temp); } +/* + * set_ref_freq(uint32_t ref_freq, enum si5351_pll_input ref_osc) + * + * ref_freq - Reference oscillator frequency in Hz + * ref_osc - Which reference oscillator frequency to set + * (use the si5351_pll_input enum) + * + * Set the reference frequency value for the desired reference oscillator + */ +void Si5351::set_ref_freq(uint32_t ref_freq, enum si5351_pll_input ref_osc) +{ + // uint8_t reg_val; + //reg_val = si5351_read(SI5351_PLL_INPUT_SOURCE); + + // Clear the bits first + //reg_val &= ~(SI5351_CLKIN_DIV_MASK); + + if(ref_freq <= 30000000UL) + { + xtal_freq[(uint8_t)ref_osc] = ref_freq; + //reg_val |= SI5351_CLKIN_DIV_1; + if(ref_osc == SI5351_PLL_INPUT_CLKIN) + { + clkin_div = SI5351_CLKIN_DIV_1; + } + } + else if(ref_freq > 30000000UL && ref_freq <= 60000000UL) + { + xtal_freq[(uint8_t)ref_osc] = ref_freq / 2; + //reg_val |= SI5351_CLKIN_DIV_2; + if(ref_osc == SI5351_PLL_INPUT_CLKIN) + { + clkin_div = SI5351_CLKIN_DIV_2; + } + } + else if(ref_freq > 60000000UL && ref_freq <= 100000000UL) + { + xtal_freq[(uint8_t)ref_osc] = ref_freq / 4; + //reg_val |= SI5351_CLKIN_DIV_4; + if(ref_osc == SI5351_PLL_INPUT_CLKIN) + { + clkin_div = SI5351_CLKIN_DIV_4; + } + } + else + { + //reg_val |= SI5351_CLKIN_DIV_1; + } + + //si5351_write(SI5351_PLL_INPUT_SOURCE, reg_val); +} + uint8_t Si5351::si5351_write_bulk(uint8_t addr, uint8_t bytes, uint8_t *data) { Wire.beginTransmission(i2c_bus_addr); @@ -1262,9 +1335,18 @@ uint8_t Si5351::si5351_read(uint8_t addr) /* Private functions */ /*********************/ -uint64_t Si5351::pll_calc(uint64_t freq, struct Si5351RegSet *reg, int32_t correction, uint8_t vcxo) +uint64_t Si5351::pll_calc(enum si5351_pll pll, uint64_t freq, struct Si5351RegSet *reg, int32_t correction, uint8_t vcxo) { - uint64_t ref_freq = xtal_freq * SI5351_FREQ_MULT; + uint64_t ref_freq; + if(pll == SI5351_PLLA) + { + ref_freq = xtal_freq[(uint8_t)plla_ref_osc] * SI5351_FREQ_MULT; + } + else + { + ref_freq = xtal_freq[(uint8_t)pllb_ref_osc] * SI5351_FREQ_MULT; + } + //ref_freq = 15974400ULL * SI5351_FREQ_MULT; uint32_t a, b, c, p1, p2, p3; uint64_t lltmp; //, denom; diff --git a/src/si5351.h b/src/si5351.h index df8416d..07a51dc 100644 --- a/src/si5351.h +++ b/src/si5351.h @@ -248,7 +248,7 @@ enum si5351_clock_disable {SI5351_CLK_DISABLE_LOW, SI5351_CLK_DISABLE_HIGH, SI53 enum si5351_clock_fanout {SI5351_FANOUT_CLKIN, SI5351_FANOUT_XO, SI5351_FANOUT_MS}; -enum si5351_pll_input{SI5351_PLL_INPUT_XO, SI5351_PLL_INPUT_CLKIN}; +enum si5351_pll_input {SI5351_PLL_INPUT_XO, SI5351_PLL_INPUT_CLKIN}; /* Struct definitions */ @@ -289,9 +289,9 @@ class Si5351 void output_enable(enum si5351_clock, uint8_t); void drive_strength(enum si5351_clock, enum si5351_drive); void update_status(void); - void set_correction(int32_t); + void set_correction(int32_t, enum si5351_pll_input); void set_phase(enum si5351_clock, uint8_t); - int32_t get_correction(void); + int32_t get_correction(enum si5351_pll_input); void pll_reset(enum si5351_pll); void set_ms_source(enum si5351_clock, enum si5351_pll); void set_int(enum si5351_clock, uint8_t); @@ -302,19 +302,21 @@ class Si5351 void set_clock_fanout(enum si5351_clock_fanout, uint8_t); void set_pll_input(enum si5351_pll, enum si5351_pll_input); void set_vcxo(uint64_t, uint8_t); + void set_ref_freq(uint32_t, enum si5351_pll_input); uint8_t si5351_write_bulk(uint8_t, uint8_t, uint8_t *); uint8_t si5351_write(uint8_t, uint8_t); uint8_t si5351_read(uint8_t); struct Si5351Status dev_status; struct Si5351IntStatus dev_int_status; enum si5351_pll pll_assignment[8]; - bool clk_first_set[8]; uint64_t clk_freq[8]; uint64_t plla_freq; uint64_t pllb_freq; - uint32_t xtal_freq; + enum si5351_pll_input plla_ref_osc; + enum si5351_pll_input pllb_ref_osc; + uint32_t xtal_freq[2]; private: - uint64_t pll_calc(uint64_t, struct Si5351RegSet *, int32_t, uint8_t); + uint64_t pll_calc(enum si5351_pll, uint64_t, struct Si5351RegSet *, int32_t, uint8_t); uint64_t multisynth_calc(uint64_t, uint64_t, struct Si5351RegSet *); uint64_t multisynth67_calc(uint64_t, uint64_t, struct Si5351RegSet *); void update_sys_status(struct Si5351Status *); @@ -322,8 +324,10 @@ class Si5351 void ms_div(enum si5351_clock, uint8_t, uint8_t); uint8_t select_r_div(uint64_t *); uint8_t select_r_div_ms67(uint64_t *); - int32_t ref_correction; + int32_t ref_correction[2]; + uint8_t clkin_div; uint8_t i2c_bus_addr; + bool clk_first_set[8]; }; #endif /* SI5351_H_ */