Silicon Laboratories Inc. Si5351A-B-GT I2C-PROGRAMMABLE ANY-FREQUENCY CMOS CLOCK GENERATOR
Dependents: clockGenerator Check_Si5351A_Clock_generator t2d Thing2Do ... more
Test program:
/users/kenjiArai/code/Check_Si5351A_Clock_generator/
Diff: si5351a.cpp
- Revision:
- 0:47b9bfa03730
- Child:
- 1:a2309757c450
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/si5351a.cpp Sun Jan 01 01:21:34 2017 +0000 @@ -0,0 +1,1948 @@ +/* + * mbed Library / Silicon Laboratories Inc. Si5351A-B-GT + * I2C-PROGRAMMABLE ANY-FREQUENCY CMOS CLOCK GENERATOR + * https://www.silabs.com/products/ + * timing/clock-generator/si535x/pages/Si5351A-B-GM.aspx + * + * Checked on Nucleo-F411RE & F401RE mbed board + * + * Original & Reference program: + * 1) + * https://github.com/adafruit/Adafruit_Si5351_Library + * see original source (bottom part of si5351a.cpp file) + * Software License Agreement (BSD License) + * Copyright (c) 2014, Adafruit Industries All rights reserved. + * 2) + * https://gist.github.com/edy555/f1ee7ef44fe4f5c6f7618ac4cbbe66fb + * made by TT@Hokkaido-san (edy555) + * http://ttrftech.tumblr.com/ + * http://ttrftech.tumblr.com/post/150247113216/ + * si5351a-configuration-how-to-and-signal-quality + * + * Modified by Kenji Arai / JH1PJL + * http://www.page.sannet.ne.jp/kenjia/index.html + * http://mbed.org/users/kenjiArai/ + * + * Started: December 24th, 2016 + * Revised: January 1st, 2017 + * + */ + +#include "mbed.h" +#include "si5351a.h" + +#if 0 // Debug mode = 1 +#define DEBUG +#endif + +#if defined(DEBUG) +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) {;} +#endif + +#define PLL_N 32 + +SI5351A::SI5351A (PinName p_sda, PinName p_scl, + uint32_t base_clk_freq, + uint8_t xtal_cap, + uint8_t drive_current +) + : _i2c(p_sda, p_scl) +{ + base_freq = base_clk_freq; + x_cap = xtal_cap; + drv_current = drive_current; + si5351_init(); +} + +SI5351A::SI5351A (I2C& p_i2c, + uint32_t base_clk_freq, + uint8_t xtal_cap, + uint8_t drive_current +) + : _i2c(p_i2c) +{ + base_freq = base_clk_freq; + x_cap = xtal_cap; + drv_current = drive_current; + si5351_init(); +} + +/* + * 1~100MHz fixed PLL (XTAL * PLL_N)MHz, fractional divider + * 100~150MHz fractional PLL 600-900MHz, fixed divider 6 + * 150~200MHz fractional PLL 600-900MHz, fixed divider 4 + */ +uint32_t SI5351A::set_frequency(uint8_t channel, uint32_t freq) +{ + uint8_t pll; + double f; + uint8_t state; + if (channel == SI5351_CLK0){ + pll = SI5351_PLL_A; + } else { // SI5351_CLK1 & SI5351_CLK2 + pll = SI5351_PLL_B; + } + si5351_disable_output(); + if (freq <= FREQ_100MHZ){ + if((channel == SI5351_CLK1) && (clk2_state == CLK_OUT_FIXEDDIV)){ + DBG("DBG: Error CLK2 uses as over 100MHz!!\r\n"); + return 0; + } else if((channel == SI5351_CLK2) && (clk1_state == CLK_OUT_FIXEDDIV)){ + DBG("DBG: Error CLK1 uses as over 100MHz!!\r\n"); + return 0; + } + DBG("DBG: Passed condition\r\n"); + if (freq > FREQ_450KHZ){ // over 450KHz + si5351_setupPLL(pll, PLL_N, 0, 1); + f = si5351_set_frequency_fixedpll(channel, pll, pll_freq, freq, 0); + } else if (freq > FREQ_75KHZ){ + si5351_setupPLL(pll, PLL_N, 0, 1); + f = si5351_set_frequency_fixedpll( + channel, pll, pll_freq, freq * 8, SI5351_R_DIV_8); + f /= 8.0f; + } else if (freq > FREQ_20KHZ){ + si5351_setupPLL(pll, PLL_N, 0, 1); + f = si5351_set_frequency_fixedpll( + channel, pll, pll_freq, freq * 32, SI5351_R_DIV_32); + f /= 32.0f; + } else { + si5351_setupPLL(pll, PLL_N, 0, 1); + f = si5351_set_frequency_fixedpll( + channel, pll, pll_freq, freq * 128, SI5351_R_DIV_128); + f /= 128.0f; + } + state = CLK_OUT_FIXEDPLL; + } else { + DBG("DBG: Set over 100MHz clock\r\n"); + if((channel == SI5351_CLK1) && (clk2_state == CLK_OUT_FIXEDPLL)){ + DBG("DBG: Error CLK2 uses as under 100MHz!!\r\n"); + return 0; + } else if((channel == SI5351_CLK2) && (clk1_state == CLK_OUT_FIXEDPLL)){ + DBG("DBG: Error CLK1 uses as under 100MHz!!\r\n"); + return 0; + } + DBG("DBG: Passed condition\r\n"); + if (freq < FREQ_150MHZ) { + DBG("DBG: Set 100MHz to 150MHz clock\r\n"); + f = si5351_set_frequency_fixeddiv(channel, pll, freq, 6); + } else { + DBG("DBG: Set over 150MHz clock\r\n"); + f = si5351_set_frequency_fixeddiv(channel, pll, freq, 4); + } + state = CLK_OUT_FIXEDDIV; + } + si5351_enable_output(); + if (channel == SI5351_CLK0){ + clk0_state = state; + clk0_freq = f; + } else if (channel == SI5351_CLK1){ + clk1_state = state; + clk1_freq = f; + } else { + clk2_state = state; + clk2_freq = f; + } + DBG("DBG: freq./ Target=%u,Set=%0.1f,diff=%.0f\r\n", + freq, f, (double)freq - f); + return (uint32_t)f; +} + +uint32_t SI5351A::shift_freq(uint8_t channel, int32_t diff) +{ + double f; + uint32_t f_err; + // Check current status + if (channel == SI5351_CLK0){ + f = clk0_freq; + f_err = (uint32_t)f; + if ((clk0_state == CLK_OUT_NOT_USED) + || (clk0_state == CLK_OUT_FIXEDDIV)){ + DBG("DBG: error over 100MHz\r\n"); + return f_err; // return current frequency + } + } else if (channel == SI5351_CLK1){ + f = clk1_freq; + f_err = (uint32_t)f; + if ((clk1_state == CLK_OUT_NOT_USED) + || (clk1_state == CLK_OUT_FIXEDDIV)){ + DBG("DBG: error over 100MHz\r\n"); + return f_err; // return current frequency + } + } else if (channel == SI5351_CLK2){ + f = clk2_freq; + f_err = (uint32_t)f; + if ((clk2_state == CLK_OUT_NOT_USED) + || (clk2_state == CLK_OUT_FIXEDDIV)){ + DBG("DBG: error over 100MHz\r\n"); + return f_err; // return current frequency + } + } else { + return 0; + } + // set new frequency + double f_tatget = f + (double)diff; + uint32_t freq = (uint32_t)f_tatget; + DBG("DBG: Target F=%u\r\n", freq); + // only range between 1MHz to 100MHz + if ((freq > FREQ_100MHZ) || (freq < FREQ_450KHZ)){// between 450KHz-100MHz + DBG("DBG: Out of range\r\n"); + return f_err; // return current frequency + } + uint32_t div = (floor)((double)pll_freq / (double)freq); + uint32_t num = pll_freq - freq * div; + uint32_t denom = freq; + uint32_t k = gcd(num, denom); + num /= k; + denom /= k; + while (denom >= (1<<20)) { + num >>= 1; + denom >>= 1; + } + if (denom == 0){ // Avoid divide by zero + DBG("DBG: error demon=0\r\n"); + return f_err; // return current frequency + } + if (num >=0x100000){ // 20-bit limit + DBG("DBG: error num over 20bit\r\n"); + return f_err; // return current frequency + } + if (denom >=0x100000){ // 20-bit limit + DBG("DBG: error denom over 20bit\r\n"); + return f_err; // return current frequency + } + uint32_t P1, P2, P3; + uint32_t multisynth_vale; + double multisynth_double; + const uint8_t msreg_base[] = { + SI5351_REG_42_MULTISYNTH0, + SI5351_REG_50_MULTISYNTH1, + SI5351_REG_58_MULTISYNTH2, + }; + uint8_t baseaddr = msreg_base[channel]; + // Fractional mode + multisynth_vale = (floor)((double)num /(double)denom); + P1 = 128 * div + 128 * multisynth_vale - 512; + P2 = 128 * num - denom * 128 * multisynth_vale; + P3 = denom; + multisynth_double = div + (double)num /(double)denom; + // a + b/c between 6..1800 + if ((multisynth_double < 6.0f) || (multisynth_double > 1800.0f)){ + DBG("DBG: error multisynth less 6 or over 1800\r\n"); + return f_err; // return current frequency + } + DBG("DBG: CLK%u: PLL=%u,div=%u,num=%u,denom=%u\r\n", + channel, pll_freq, div, num, denom); + // Set the MSx config registers + si5351_write(baseaddr, (P3 & 0x0000ff00) >> 8); + si5351_write(baseaddr+1, (P3 & 0x000000ff)); + si5351_write(baseaddr+2, (P1 & 0x00030000) >> 16); + si5351_write(baseaddr+3, (P1 & 0x0000ff00) >> 8); + si5351_write(baseaddr+4, (P1 & 0x000000ff)); + si5351_write(baseaddr+5, + ((P3 & 0x000f0000) >> 12) | ((P2 & 0x000f0000) >> 16)); + si5351_write(baseaddr+6, (P2 & 0x0000ff00) >> 8); + si5351_write(baseaddr+7, (P2 & 0x000000ff)); + f = (double)pll_freq / multisynth_double; + if (channel == SI5351_CLK0){ + clk0_freq = f; + } else if (channel == SI5351_CLK1){ + clk1_freq = f; + } else { // SI5351_CLK2 + clk2_freq = f; + } + return (uint32_t)f; +} + +uint32_t SI5351A::read_freq(uint8_t channel) +{ + if (channel == SI5351_CLK0){ + return (uint32_t)clk0_freq; + } else if (channel == SI5351_CLK1){ + return (uint32_t)clk1_freq; + } else { // SI5351_CLK2 + return (uint32_t)clk2_freq; + } +} + +static const uint8_t reg_table[] = { + 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170 +}; + +void SI5351A::all_reset(void) +{ + plla_freq = 0; + pllb_freq = 0; + clk0_freq = 0; + clk1_freq = 0; + clk2_freq = 0; + clk0_state = 0; + clk1_state = 0; + clk2_state = 0; + /* Disable all outputs setting CLKx_DIS high */ + si5351_write(SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0xff); + for (uint16_t i = 0; i < sizeof(reg_table); i++){ + si5351_write(reg_table[i], 0); + } + /* Apply soft reset */ + si5351_write(SI5351_REG_177_PLL_RESET, 0xac); +} + +////////////// Configration for Initialization ///////////////////////////////// +// length, register addr, data, ... +const uint8_t si5351_configs[] = { + // 0xff = all outputs are disabled. + 2, SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0xff, + // CLK0,CLK1(REG_17),CLK2(REG_18) -> Power down mode + 4, SI5351_REG_16_CLK0_CONTROL, + SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN, SI5351_CLK_POWERDOWN, + // Dummy data for PLL + 9, SI5351_REG_26_PLL_A, /*P3*/0, 0, /*P1*/0, 0, 0, /*P3/P2*/0, 0, 0, + // RESET PLL (Both PLLA & PLLB) + 2, SI5351_REG_177_PLL_RESET, (SI5351_PLL_RESET_A | SI5351_PLL_RESET_B), + // Dummy data for MULTISYNTH + 9, SI5351_REG_58_MULTISYNTH2, /*P3*/0, 0, /*P1*/0, 0, 0, /*P2|P3*/0, 0, 0, + // 0 = enable / CLK0,1,2 (bit 0,1,2 = 0) + 2, SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0xf8, + 0 // sentinel +}; + +void SI5351A::si5351_init(void) +{ + addr = SI5351_I2C_ADDR; + pll_freq = base_freq * PLL_N; + clk0_freq = 0; + clk1_freq = 0; + clk2_freq = 0; + clk0_state = CLK_OUT_NOT_USED; + clk1_state = CLK_OUT_NOT_USED; + clk2_state = CLK_OUT_NOT_USED; + si5351_reset_pll(); + si5351_write(SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0xff); + // Total load capacitance less than or equal to 12 pF (See AN551) + si5351_write(SI5351_REG_183_CRYSTAL_LOAD, x_cap); + const uint8_t *p = si5351_configs; + while (*p) { + uint8_t len = *p++; + si5351_bulk_write(p, len); + p += len; + } + // CONTROL for CLK0,1,2 + uint8_t dt = (drv_current | \ + SI5351_CLK_INPUT_MULTISYNTH_N | \ + SI5351_CLK_INTEGER_MODE); + si5351_write(SI5351_REG_16_CLK0_CONTROL, dt); + si5351_write(SI5351_REG_17_CLK1_CONTROL, dt); + si5351_write(SI5351_REG_18_CLK2_CONTROL, dt); + si5351_disable_all_output(); // Disable all output +} + +void SI5351A::si5351_disable_output(void) +{ + si5351_write(SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0xff); +} + +void SI5351A::si5351_disable_all_output(void) +{ + si5351_write(SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0xff); + si5351_write(SI5351_REG_16_CLK0_CONTROL, 0x80); + si5351_write(SI5351_REG_17_CLK1_CONTROL, 0x80); + si5351_write(SI5351_REG_18_CLK2_CONTROL, 0x80); +} + +void SI5351A::si5351_enable_output(void) +{ + si5351_write(SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0xf8); +} + +void SI5351A::si5351_reset_pll(void) +{ + si5351_write(SI5351_REG_177_PLL_RESET, 0xa0); +} + +/**************************************************************************/ +/*! + @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 +*/ +/**************************************************************************/ +void SI5351A::si5351_setupPLL( + uint8_t pll, + uint8_t mult, + uint32_t num, + uint32_t denom) +{ + uint32_t P1; /* PLL config register P1 */ + uint32_t P2; /* PLL config register P2 */ + uint32_t P3; /* PLL config register P3 */ + + /* Basic validation */ + DBG("DBG: Enter si5351_setupPLL\r\n"); + DBG("DBG: pll=%u, mult=%u, num=%u, denom=%u\r\n", pll, mult, num, denom); + bool err = false; + if (mult <= 14){ /* mult = 15..90 */ + DBG("DBG: mult lower value error\r\n"); + err = true; + } + if (mult >= 91){ + DBG("DBG: mult bigger value error\r\n"); + err = true; + } + if (denom == 0){ /* Avoid divide by zero */ + DBG("DBG: denom = 0 error\r\n"); + err = true; + } + if (num >=0x100000){ /* 20-bit limit */ + DBG("DBG: num value error\r\n"); + err = true; + } + if (denom >=0x100000){ /* 20-bit limit */ + DBG("DBG: denom value error\r\n"); + err = true; + } + if (err == true){ + if (pll == SI5351_PLL_A){ + plla_freq = 0; + } else { // SI5351_PLL_B + pllb_freq = 0; + } + DBG("DBG: return by error\r\n"); + return; + } + /* Feedback Multisynth Divider Equation + * where: a = mult, b = num and c = denom + * P1 register is an 18-bit value using following formula: + * P1[17:0] = 128 * mult + floor(128*(num/denom)) - 512 + * P2 register is a 20-bit value using the following formula: + * P2[19:0] = 128 * num - denom * floor(128*(num/denom)) + * P3 register is a 20-bit value using the following formula: + * P3[19:0] = denom + */ + /* Set the main PLL config registers */ + if (num == 0) { + DBG("DBG: num = 0\r\n"); + /* Integer mode */ + P1 = 128 * mult - 512; + P2 = num; + P3 = denom; + } else { + /* Fractional mode */ + DBG("DBG: Fractional mode\r\n"); + P1 = (uint32_t) + (128 * mult + floor(128 * ((double)num/(double)denom)) - 512); + P2 = (uint32_t) + (128 * num - denom * floor(128 * ((double)num/(double)denom))); + P3 = denom; + } + /* Get the appropriate starting point for the PLL registers */ + const uint8_t pllreg_base[] = { + SI5351_REG_26_PLL_A, + SI5351_REG_34_PLL_B + }; + uint8_t baseaddr = pllreg_base[pll]; + /* The datasheet is a nightmare of typos and inconsistencies here! */ + si5351_write(baseaddr, (P3 & 0x0000ff00) >> 8); + si5351_write(baseaddr+1, (P3 & 0x000000ff)); + si5351_write(baseaddr+2, (P1 & 0x00030000) >> 16); + si5351_write(baseaddr+3, (P1 & 0x0000ff00) >> 8); + si5351_write(baseaddr+4, (P1 & 0x000000ff)); + si5351_write(baseaddr+5, + ((P3 & 0x000f0000) >> 12) | ((P2 & 0x000f0000) >> 16) ); + si5351_write(baseaddr+6, (P2 & 0x0000ff00) >> 8); + si5351_write(baseaddr+7, (P2 & 0x000000ff)); + /* Reset both PLLs */ + si5351_write(SI5351_REG_177_PLL_RESET, + SI5351_PLL_RESET_B | SI5351_PLL_RESET_A); + /* Store the frequency settings for use with the Multisynth helper */ + DBG("DBG: Use 26(PLLA) or 34(PLLB)) ->%u and write data\r\n", baseaddr); + double f = base_freq * (mult + ((double)num / (double)denom)); + DBG("DBG: PLL f=%u\r\n", (uint32_t)f); + if (pll == SI5351_PLL_A){ + plla_freq = f; + } else { // SI5351_PLL_B + pllb_freq = f; + } +} + +/**************************************************************************/ +/*! + @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 pllSource 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 6..1800 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. +*/ +/**************************************************************************/ +double SI5351A::si5351_setupMultisynth( + uint8_t output, + uint8_t pllSource, + uint32_t div, + uint32_t num, + uint32_t denom, + uint8_t factor) +{ + uint32_t P1; /* Multisynth config register P1 */ + uint32_t P2; /* Multisynth config register P2 */ + uint32_t P3; /* Multisynth config register P3 */ + uint32_t div4 = 0; + uint32_t multisynth_vale; + double multisynth_double; + + DBG("DBG: Enter si5351_setupMultisynth\r\n"); + DBG("DBG: ch=%u, pll=%u, div=%u, num=%u, denom=%u, factor=%u\r\n", + output, pllSource, div, num, denom, factor); + if (output > 3){ + return 0; + } /* Channel range */ + if ((div <= 3) || (div >= 1801)){ /* Divider integer 6..1800 */ + DBG("DBG: div out of range error\r\n"); + return 0; + } + if (denom == 0){ /* Avoid divide by zero */ + DBG("DBG: denom=0 error\r\n"); + return 0; + } + if (num >=0x100000){ /* 20-bit limit */ + DBG("DBG: num bigger error\r\n"); + return 0; + } + if (denom >=0x100000){ /* 20-bit limit */ + DBG("DBG: denom bigger error\r\n"); + return 0; + } + DBG("DBG: Passed range check\r\n"); + /* Get the appropriate starting point for the PLL registers */ + const uint8_t msreg_base[] = { + SI5351_REG_42_MULTISYNTH0, + SI5351_REG_50_MULTISYNTH1, + SI5351_REG_58_MULTISYNTH2, + }; + uint8_t baseaddr = msreg_base[output]; + /* 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 + */ + /* Set the main PLL config registers */ + if (div == 4) { + DBG("DBG: enter div==4\r\n"); + div4 = SI5351_DIVBY4; + P1 = P2 = 0; + P3 = 1; + multisynth_double = 4.0f; + } else if (num == 0) { + DBG("DBG: enter num==0\r\n"); + /* Integer mode */ + P1 = 128 * div - 512; + P2 = 0; + P3 = 1; + multisynth_double = (double)div; + } else { + /* Fractional mode */ + DBG("DBG: enter Fractional mode\r\n"); + multisynth_vale = (floor)((double)num /(double)denom); + P1 = 128 * div + 128 * multisynth_vale - 512; + P2 = 128 * num - denom * 128 * multisynth_vale; + P3 = denom; + multisynth_double = div + (double)num /(double)denom; + /* a + b/c between 6..1800 */ + if ((multisynth_double < 6.0f) || (multisynth_double > 1800.0f)){ + return 0; + } + } + /* Set the MSx config registers */ + si5351_write(baseaddr, (P3 & 0x0000ff00) >> 8); + si5351_write(baseaddr+1, (P3 & 0x000000ff)); + si5351_write(baseaddr+2, ((P1 & 0x00030000) >> 16) | div4 | factor); + si5351_write(baseaddr+3, (P1 & 0x0000ff00) >> 8); + si5351_write(baseaddr+4, (P1 & 0x000000ff)); + si5351_write(baseaddr+5, + ((P3 & 0x000f0000) >> 12) | ((P2 & 0x000f0000) >> 16)); + si5351_write(baseaddr+6, (P2 & 0x0000ff00) >> 8); + si5351_write(baseaddr+7, (P2 & 0x000000ff)); + /* Configure the clk control and enable the output */ + const uint8_t clkctrl[] = { + SI5351_REG_16_CLK0_CONTROL, + SI5351_REG_17_CLK1_CONTROL, + SI5351_REG_18_CLK2_CONTROL + }; + uint8_t dat; + dat = drv_current | SI5351_CLK_INPUT_MULTISYNTH_N; + if (pllSource == SI5351_PLL_B){ + dat |= SI5351_CLK_PLL_SELECT_B; + } + if (num == 0){ + dat |= SI5351_CLK_INTEGER_MODE; + } + DBG("DBG: CLK%u: reg_%u=0x%02x\r\n", output, clkctrl[output], dat); + si5351_write(clkctrl[output], dat); + DBG("DBG: a+b/c=%8.2f\r\n", multisynth_double); + return multisynth_double; +} + +uint32_t SI5351A::gcd(uint32_t x, uint32_t y) +{ + int32_t z; + while (y != 0) { + z = x % y; + x = y; + y = z; + } + return x; +} + +double SI5351A::si5351_set_frequency_fixedpll( + uint8_t channel, + uint32_t pll, + uint32_t pllfreq, + uint32_t freq, + uint8_t factor) +{ + DBG("DBG: Enter si5351_set_frequency_fixedpll\r\n"); + uint32_t div = (floor)((double)pllfreq / (double)freq); // range: 8 ~ 1800 + uint32_t num = pllfreq - freq * div; + uint32_t denom = freq; + //int32_t k = freq / (1<<20) + 1; + uint32_t k = gcd(num, denom); + num /= k; + denom /= k; + while (denom >= (1<<20)) { + num >>= 1; + denom >>= 1; + } + DBG("DBG: CLK%u: PLL=%u,div=%u,num=%u,denom=%u\r\n", + channel, pll, div, num, denom); + double x = si5351_setupMultisynth(channel, pll, div, num, denom, factor); + if (x == 0.0f){ return 0;} + x = (double)pll_freq / x; + DBG("DBG: Freqency=%.0f[Hz]\r\n", x); + return x; +} + +double SI5351A::si5351_set_frequency_fixeddiv( + uint8_t channel, + uint32_t pll, + uint32_t freq, + uint32_t div) +{ + DBG("DBG: si5351_set_frequency_fixeddiv\r\n"); + uint32_t pllfreq = freq * div; + uint32_t multi = pllfreq / base_freq; + uint32_t num = pllfreq - multi * base_freq; + uint32_t denom = base_freq; + uint32_t k = gcd(num, denom); + num /= k; + denom /= k; + while (denom >= (1<<20)) { + num >>= 1; + denom >>= 1; + } + si5351_setupPLL(pll, multi, num, denom); + num = 0; + double x = si5351_setupMultisynth(channel, pll, div, num, denom, 0); + if (x == 0.0f){ return 0.0f;} + if (pll == SI5351_PLL_A){ + x = plla_freq / x; + } else { // SI5351_PLL_B + x = pllb_freq / x; + } + DBG("DBG: Freqency=%.0f[Hz]\r\n", x); + return x; +} + +void SI5351A::si5351_bulk_write(const uint8_t *buf, uint8_t len) +{ + _i2c.write(addr, (const char*)buf, len, false); +} + +void SI5351A::si5351_write(uint8_t reg, uint8_t dat) +{ + uint8_t buf[] = { reg, dat }; + _i2c.write(addr,(const char *)buf, 2, false); +} + +void SI5351A::si5351_read(const uint8_t *buf) +{ + int n = (int)buf[1]; + _i2c.write(addr,(const char *)buf, 1, true); + _i2c.read(addr, (char *)buf, n, false); +} + +//----------- DEBUG PURPOSE ---------------------------------------------------- +// Print memory contents +void SI5351A::put_dump (const uint8_t *buff, uint8_t ofs, uint8_t cnt) +{ + printf("%03u ", ofs); + for(int n = 0; n < cnt; n++) { // show hex + printf(" %02x", buff[n]); + } + printf("\r\n"); +} + +void SI5351A::prnt_reg(uint8_t offset, uint8_t n) +{ + uint8_t buf[16]; + buf[0] = offset; + buf[1] = n; + si5351_read(buf); + put_dump(buf, offset, n); +} + +void SI5351A::debug_reg_print(void) +{ + printf("Show Si5351A registers\r\n"); + printf("reg 0 1 2 3 4 5 6 7 8 9\r\n"); + prnt_reg(0, 4); // reg0 to reg3 + prnt_reg(9, 1); // reg9 + prnt_reg(15, 4); // reg15 to reg18 + prnt_reg(24, 10); // reg24 to reg33 + prnt_reg(34, 10); // reg34 to reg43 + prnt_reg(44, 10); // reg44 to reg53 + prnt_reg(54, 10); // reg54 to reg63 + prnt_reg(64, 2); // reg64 to reg65 + prnt_reg(149, 10); // reg149 to reg158 + prnt_reg(159, 3); // reg159 to reg161 + prnt_reg(165, 3); // reg165 to reg167 + prnt_reg(177, 1); // reg177 + prnt_reg(183, 1); // reg183 + prnt_reg(187, 1); // reg187 +} + +void SI5351A::debug_current_config(void) +{ + uint8_t buf[16]; + uint8_t dt0; + uint8_t dt1; + //Step1 + printf("[BASE CLOCK] --> "); + buf[0] = 183; // register address + buf[1] = 1; // # of reading bytes + si5351_read(buf); + printf("Xtal=%u[Hz] with internal cap=", base_freq); + dt0 = buf[0] & 0xc0; + switch (dt0){ + case 0xc0: + printf("10"); + break; + case 0x80: + printf("8"); + break; + case 0x40: + printf("6"); + break; + default: + printf("?(bad config)"); + break; + } + printf("[pF]\r\n"); + //Step2 + printf("[PLLn] --> "); + buf[0] = 15; + buf[1] = 1; + si5351_read(buf); + dt0 = buf[0]; + dt1 = dt0 >> 6; + printf("Clock in divide by %u", 1U << dt1); + printf(" PLLA clock source is "); + if (dt0 & 0x04){ + printf("none XTAL(bad config)"); + } else { + printf("XTAL"); + } + printf(" & PLLB = "); + if (dt0 & 0x08){ + printf("none XTAL(bad config)"); + } else { + printf("XTAL"); + } + printf("\r\n"); + //Step3 + printf("[CLK0,1,2] --> "); + printf("CLKn output E:enable/D:disable * "); + buf[0] = 9; // register address + buf[1] = 1; // # of reading bytes + si5351_read(buf); + dt0 = buf[0] & 0x07; + buf[0] = 3; // register address + buf[1] = 1; // # of reading bytes + si5351_read(buf); + dt1 = buf[0] & 0x07; + printf("CLK2:"); + if ((dt0 & 0x04) && (dt1 & 0x04)){ + printf("D"); + } else { + printf("E"); + } + printf(", CLK1:"); + if ((dt0 & 0x02) && (dt1 & 0x02)){ + printf("D"); + } else { + printf("E"); + } + printf(", CLK0:"); + if ((dt0 & 0x01) && (dt1 & 0x01)){ + printf("D"); + } else { + printf("E"); + } + printf("\r\n"); + //Step4 + buf[0] = 16; + buf[1] = 3; + si5351_read(buf); + printf("--> CLK0 * "); + reg_16_17_18(buf[0]); + printf("--> CLK1 * "); + reg_16_17_18(buf[1]); + printf("--> CLK2 * "); + reg_16_17_18(buf[2]); + //Step5 + printf("[PLLn P1,P2,P3]\r\n"); + printf("--> PLLA * "); + buf[0] = 26; // register address + buf[1] = 8; // # of reading bytes + si5351_read(buf); + reg_pll_8bytes(buf); + printf("--> PLLB * "); + buf[0] = 34; // register address + buf[1] = 8; // # of reading bytes + si5351_read(buf); + reg_pll_8bytes(buf); + printf("[MultiSynth-n P1,P2,P3]\r\n"); + printf("--> Mltsyn0 * "); + buf[0] = 42; // register address + buf[1] = 8; // # of reading bytes + si5351_read(buf); + reg_mltisyc_8bytes(buf); + printf("--> Mltsyn1 * "); + buf[0] = 50; // register address + buf[1] = 8; // # of reading bytes + si5351_read(buf); + reg_mltisyc_8bytes(buf); + printf("--> Mltsyn2 * "); + buf[0] = 58; // register address + buf[1] = 8; // # of reading bytes + si5351_read(buf); + reg_mltisyc_8bytes(buf); +} + +void SI5351A::reg_pll_8bytes(uint8_t *buf) +{ + uint32_t dt = ((uint32_t)(buf[5] & 0xf0) << 12) + ((uint32_t)buf[0] << 8) + + (uint32_t)buf[1]; + printf("P3=%6u (0x%05x), ", dt, dt); + dt = ((uint32_t)(buf[5] & 0x0f) << 16) + ((uint32_t)buf[6] << 8) + + (uint32_t)buf[7]; + printf("P2=%6u (0x%05x), ", dt, dt); + dt = ((uint32_t)(buf[2] & 0x03) << 16) + ((uint32_t)buf[3] << 8) + + (uint32_t)buf[4]; + printf("P1=%6u (0x%05x)\r\n", dt, dt); +} + +void SI5351A::reg_mltisyc_8bytes(uint8_t *buf) +{ + uint32_t dt = ((uint32_t)(buf[5] & 0xf0) << 12) + ((uint32_t)buf[0] << 8) + + (uint32_t)buf[1]; + printf("P3=%6u (0x%05x), ", dt, dt); + dt = ((uint32_t)(buf[5] & 0x0f) << 16) + ((uint32_t)buf[6] << 8) + + (uint32_t)buf[7]; + printf("P2=%6u (0x%05x), ", dt, dt); + dt = ((uint32_t)(buf[2] & 0x03) << 16) + ((uint32_t)buf[3] << 8) + + (uint32_t)buf[4]; + printf("P1=%6u (0x%05x), ", dt, dt); + uint8_t d = buf[2]; + if ((d & 0x0c) == 0x0c){ + printf("Divided by"); + } else { + printf("Div >"); + } + printf(" 4 mode, "); + dt >>= 4; + dt &=0x07; + printf("R-reg: Divided by %u\r\n", 1U << dt); +} + +void SI5351A::reg_16_17_18(uint8_t dt) +{ + printf("Power "); + if (dt & 0x80){ + printf("down(not used)"); + } else { + printf("up(running)"); + } + printf(", MultiSynth-> "); + if (dt & 0x40){ + printf("Integer"); + } else { + printf("Fractional"); + } + printf(" mode, PLL-> "); + if (dt & 0x20){ + printf("PLLB"); + } else { + printf("PLLA"); + } + printf(", Clock-> "); + if (dt & 0x20){ + printf("inverted"); + } else { + printf("not inverted"); + } + printf(", Drive strength-> "); + printf("%umA\r\n", 2 + 2 * (dt & 0x03)); +} + +/**************************************************************************/ +/*! + @brief Configures the Si5351 with config settings generated in + ClockBuilder. You can use this function to make sure that + your HW is properly configure and that there are no problems + with the board itself. + + Running this function should provide the following output: + * Channel 0: 120.00 MHz + * Channel 1: 12.00 MHz + * Channel 2: 13.56 MHz + @note This will overwrite all of the config registers! +*/ +/**************************************************************************/ +/* Test setup from SI5351 ClockBuilder + * ----------------------------------- + * XTAL: 25 MHz + * Channel 0: 120.00 MHz + * Channel 1: 12.00 MHz + * Channel 2: 13.56 MHz + */ +static const uint8_t m_si5351_regs_15to92_149to170[100][2] = +{ + { 15, 0x00 }, /* Input source = crystal for PLLA and PLLB */ + /* CLK0 Control: 8mA drive, Multisynth 0 as CLK0 source, Clock not inverted, + Source = PLLA, Multisynth 0 in integer mode, clock powered up */ + { 16, 0x4F }, + /* CLK1 Control: 8mA drive, Multisynth 1 as CLK1 source, Clock not inverted, + Source = PLLA, Multisynth 1 in integer mode, clock powered up */ + { 17, 0x4F }, + /* CLK2 Control: 8mA drive, Multisynth 2 as CLK2 source, Clock not inverted, + Source = PLLB, Multisynth 2 in integer mode, clock powered up */ + { 18, 0x6F }, + { 19, 0x80 }, /* CLK3 Control: Not used ... clock powered down */ + { 20, 0x80 }, /* CLK4 Control: Not used ... clock powered down */ + { 21, 0x80 }, /* CLK5 Control: Not used ... clock powered down */ + { 22, 0x80 }, /* CLK6 Control: Not used ... clock powered down */ + { 23, 0x80 }, /* CLK7 Control: Not used ... clock powered down */ + { 24, 0x00 }, /* Clock disable state 0..3 (low when disabled) */ + { 25, 0x00 }, /* Clock disable state 4..7 (low when disabled) */ + /* PLL_A Setup */ + { 26, 0x00 }, { 27, 0x05 }, { 28, 0x00 }, { 29, 0x0C }, + { 30, 0x66 }, { 31, 0x00 }, { 32, 0x00 }, { 33, 0x02 }, + /* PLL_B Setup */ + { 34, 0x02 }, { 35, 0x71 }, { 36, 0x00 }, { 37, 0x0C }, + { 38, 0x1A }, { 39, 0x00 }, { 40, 0x00 }, { 41, 0x86 }, + /* Multisynth Setup */ + { 42, 0x00 }, { 43, 0x01 }, { 44, 0x00 }, { 45, 0x01 }, + { 46, 0x00 }, { 47, 0x00 }, { 48, 0x00 }, { 49, 0x00 }, + { 50, 0x00 }, { 51, 0x01 }, { 52, 0x00 }, { 53, 0x1C }, + { 54, 0x00 }, { 55, 0x00 }, { 56, 0x00 }, { 57, 0x00 }, + { 58, 0x00 }, { 59, 0x01 }, { 60, 0x00 }, { 61, 0x18 }, + { 62, 0x00 }, { 63, 0x00 }, { 64, 0x00 }, { 65, 0x00 }, + { 66, 0x00 }, { 67, 0x00 }, { 68, 0x00 }, { 69, 0x00 }, + { 70, 0x00 }, { 71, 0x00 }, { 72, 0x00 }, { 73, 0x00 }, + { 74, 0x00 }, { 75, 0x00 }, { 76, 0x00 }, { 77, 0x00 }, + { 78, 0x00 }, { 79, 0x00 }, { 80, 0x00 }, { 81, 0x00 }, + { 82, 0x00 }, { 83, 0x00 }, { 84, 0x00 }, { 85, 0x00 }, + { 86, 0x00 }, { 87, 0x00 }, { 88, 0x00 }, { 89, 0x00 }, + { 90, 0x00 }, { 91, 0x00 }, { 92, 0x00 }, + /* Misc Config Register */ + { 149, 0x00 }, { 150, 0x00 }, { 151, 0x00 }, { 152, 0x00 }, + { 153, 0x00 }, { 154, 0x00 }, { 155, 0x00 }, { 156, 0x00 }, + { 157, 0x00 }, { 158, 0x00 }, { 159, 0x00 }, { 160, 0x00 }, + { 161, 0x00 }, { 162, 0x00 }, { 163, 0x00 }, { 164, 0x00 }, + { 165, 0x00 }, { 166, 0x00 }, { 167, 0x00 }, { 168, 0x00 }, + { 169, 0x00 }, { 170, 0x00 } +}; + +void SI5351A::debug_example_clock(void) +{ + /* Disable all outputs setting CLKx_DIS high */ + si5351_write(SI5351_REG_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 (uint16_t i = 0; i < sizeof(m_si5351_regs_15to92_149to170)/2; i++){ + si5351_write(m_si5351_regs_15to92_149to170[i][0], + m_si5351_regs_15to92_149to170[i][1]); + } + /* Apply soft reset */ + si5351_write(SI5351_REG_177_PLL_RESET, 0xac); + /* Enabled desired outputs (see Register 3) */ + si5351_write(SI5351_REG_3_OUTPUT_ENABLE_CONTROL, 0x00); + printf("Please check following output\r\n"); + printf("CLK0: 120.00MHz, CLK1: 12.00MHz, CLK2: 13.56MHz\r\n"); +} + +//--------------------------------------------------------------------------------------------------------------------- +// Original & reference files +//--------------------------------------------------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Adafruit_SI5351.cpp +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#if 0 +/**************************************************************************/ +/*! + @file Adafruit_SI5351.cpp + @author K. Townsend (Adafruit Industries) + + @brief Driver for the SI5351 160MHz Clock Gen + + @section REFERENCES + + Si5351A/B/C Datasheet: + http://www.silabs.com/Support%20Documents/TechnicalDocs/Si5351.pdf + + Manually Generating an Si5351 Register Map: + http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2014, Adafruit Industries + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ +#if defined(__AVR__) +#include <avr/pgmspace.h> +#include <util/delay.h> +#else +#include "pgmspace.h" +#endif +#include <stdlib.h> + +#include <Adafruit_SI5351.h> + +/**************************************************************************/ +/*! + Constructor +*/ +/**************************************************************************/ +Adafruit_SI5351::Adafruit_SI5351(void) +{ + m_si5351Config.initialised = false; + m_si5351Config.crystalFreq = SI5351_CRYSTAL_FREQ_25MHZ; + m_si5351Config.crystalLoad = SI5351_CRYSTAL_LOAD_10PF; + m_si5351Config.crystalPPM = 30; + m_si5351Config.plla_configured = false; + m_si5351Config.plla_freq = 0; + m_si5351Config.pllb_configured = false; + m_si5351Config.pllb_freq = 0; +} + +/**************************************************************************/ +/*! + Initializes I2C and configures the breakout (call this function before + doing anything else) +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::begin(void) +{ + /* Initialise I2C */ + Wire.begin(); + + /* ToDo: Make sure we're actually connected */ + + /* Disable all outputs setting CLKx_DIS high */ + ASSERT_STATUS(write8(SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0xFF)); + + /* Power down all output drivers */ + ASSERT_STATUS(write8(SI5351_REGISTER_16_CLK0_CONTROL, 0x80)); + ASSERT_STATUS(write8(SI5351_REGISTER_17_CLK1_CONTROL, 0x80)); + ASSERT_STATUS(write8(SI5351_REGISTER_18_CLK2_CONTROL, 0x80)); + ASSERT_STATUS(write8(SI5351_REGISTER_19_CLK3_CONTROL, 0x80)); + ASSERT_STATUS(write8(SI5351_REGISTER_20_CLK4_CONTROL, 0x80)); + ASSERT_STATUS(write8(SI5351_REGISTER_21_CLK5_CONTROL, 0x80)); + ASSERT_STATUS(write8(SI5351_REGISTER_22_CLK6_CONTROL, 0x80)); + ASSERT_STATUS(write8(SI5351_REGISTER_23_CLK7_CONTROL, 0x80)); + + /* Set the load capacitance for the XTAL */ + ASSERT_STATUS(write8(SI5351_REGISTER_183_CRYSTAL_INTERNAL_LOAD_CAPACITANCE, + m_si5351Config.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 */ + m_si5351Config.plla_configured = false; + m_si5351Config.plla_freq = 0; + m_si5351Config.pllb_configured = false; + m_si5351Config.pllb_freq = 0; + + /* All done! */ + m_si5351Config.initialised = true; + + return ERROR_NONE; +} + +/**************************************************************************/ +/*! + @brief Configures the Si5351 with config settings generated in + ClockBuilder. You can use this function to make sure that + your HW is properly configure and that there are no problems + with the board itself. + + Running this function should provide the following output: + * Channel 0: 120.00 MHz + * Channel 1: 12.00 MHz + * Channel 2: 13.56 MHz + + @note This will overwrite all of the config registers! +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::setClockBuilderData(void) +{ + uint16_t i = 0; + + /* Make sure we've called init first */ + ASSERT(m_si5351Config.initialised, ERROR_DEVICENOTINITIALISED); + + /* Disable all outputs setting CLKx_DIS high */ + ASSERT_STATUS(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=0; i<sizeof(m_si5351_regs_15to92_149to170)/2; i++) + { + ASSERT_STATUS(write8( m_si5351_regs_15to92_149to170[i][0], + m_si5351_regs_15to92_149to170[i][1] )); + } + + /* Apply soft reset */ + ASSERT_STATUS(write8(SI5351_REGISTER_177_PLL_RESET, 0xAC)); + + /* Enabled desired outputs (see Register 3) */ + ASSERT_STATUS(write8(SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, 0x00)); + + return ERROR_NONE; +} + +/**************************************************************************/ +/*! + @brief Sets the multiplier for the specified PLL using integer values + + @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) +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::setupPLLInt(si5351PLL_t pll, uint8_t mult) +{ + return setupPLL(pll, mult, 0, 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 +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::setupPLL(si5351PLL_t pll, + uint8_t mult, + uint32_t num, + uint32_t denom) +{ + uint32_t P1; /* PLL config register P1 */ + uint32_t P2; /* PLL config register P2 */ + uint32_t P3; /* PLL config register P3 */ + + /* Basic validation */ + ASSERT( m_si5351Config.initialised, ERROR_DEVICENOTINITIALISED ); + ASSERT( (mult > 14) && (mult < 91), ERROR_INVALIDPARAMETER ); /* mult = 15..90 */ + ASSERT( denom > 0, ERROR_INVALIDPARAMETER ); /* Avoid divide by zero */ + ASSERT( num <= 0xFFFFF, ERROR_INVALIDPARAMETER ); /* 20-bit limit */ + ASSERT( denom <= 0xFFFFF, ERROR_INVALIDPARAMETER ); /* 20-bit limit */ + + /* Feedback Multisynth Divider Equation + * + * where: a = mult, b = num and c = denom + * + * P1 register is an 18-bit value using following formula: + * + * P1[17:0] = 128 * mult + floor(128*(num/denom)) - 512 + * + * P2 register is a 20-bit value using the following formula: + * + * P2[19:0] = 128 * num - denom * floor(128*(num/denom)) + * + * P3 register is a 20-bit value using the following formula: + * + * P3[19:0] = denom + */ + + /* Set the main PLL config registers */ + if (num == 0) + { + /* Integer mode */ + P1 = 128 * mult - 512; + P2 = num; + P3 = denom; + } + else + { + /* Fractional mode */ + P1 = (uint32_t)(128 * mult + floor(128 * ((float)num/(float)denom)) - 512); + P2 = (uint32_t)(128 * num - denom * floor(128 * ((float)num/(float)denom))); + P3 = denom; + } + + /* Get the appropriate starting point for the PLL registers */ + uint8_t baseaddr = (pll == SI5351_PLL_A ? 26 : 34); + + /* The datasheet is a nightmare of typos and inconsistencies here! */ + ASSERT_STATUS( write8( baseaddr, (P3 & 0x0000FF00) >> 8)); + ASSERT_STATUS( write8( baseaddr+1, (P3 & 0x000000FF))); + ASSERT_STATUS( write8( baseaddr+2, (P1 & 0x00030000) >> 16)); + ASSERT_STATUS( write8( baseaddr+3, (P1 & 0x0000FF00) >> 8)); + ASSERT_STATUS( write8( baseaddr+4, (P1 & 0x000000FF))); + ASSERT_STATUS( write8( baseaddr+5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16) )); + ASSERT_STATUS( write8( baseaddr+6, (P2 & 0x0000FF00) >> 8)); + ASSERT_STATUS( write8( baseaddr+7, (P2 & 0x000000FF))); + + /* Reset both PLLs */ + ASSERT_STATUS( write8(SI5351_REGISTER_177_PLL_RESET, (1<<7) | (1<<5) )); + + /* Store the frequency settings for use with the Multisynth helper */ + if (pll == SI5351_PLL_A) + { + float fvco = m_si5351Config.crystalFreq * (mult + ( (float)num / (float)denom )); + m_si5351Config.plla_configured = true; + m_si5351Config.plla_freq = (uint32_t)floor(fvco); + } + else + { + float fvco = m_si5351Config.crystalFreq * (mult + ( (float)num / (float)denom )); + m_si5351Config.pllb_configured = true; + m_si5351Config.pllb_freq = (uint32_t)floor(fvco); + } + + return ERROR_NONE; +} + +/**************************************************************************/ +/*! + @brief Configures the Multisynth divider using integer output. + + @param output The output channel to use (0..2) + @param pllSource 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, + which must be one of the following values: + - SI5351_MULTISYNTH_DIV_4 + - SI5351_MULTISYNTH_DIV_6 + - SI5351_MULTISYNTH_DIV_8 +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::setupMultisynthInt(uint8_t output, + si5351PLL_t pllSource, + si5351MultisynthDiv_t div) +{ + return setupMultisynth(output, pllSource, div, 0, 1); +} + + +err_t Adafruit_SI5351::setupRdiv(uint8_t output, si5351RDiv_t div) { + ASSERT( output < 3, ERROR_INVALIDPARAMETER); /* Channel range */ + + uint8_t Rreg, regval; + + 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; + + read8(Rreg, ®val); + + regval &= 0x0F; + uint8_t divider = div; + divider &= 0x07; + divider <<= 4; + regval |= divider; + return write8(Rreg, regval); +} + +/**************************************************************************/ +/*! + @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 pllSource 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. +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::setupMultisynth(uint8_t output, + si5351PLL_t pllSource, + uint32_t div, + uint32_t num, + uint32_t denom) +{ + uint32_t P1; /* Multisynth config register P1 */ + uint32_t P2; /* Multisynth config register P2 */ + uint32_t P3; /* Multisynth config register P3 */ + + /* Basic validation */ + ASSERT( m_si5351Config.initialised, ERROR_DEVICENOTINITIALISED); + ASSERT( output < 3, ERROR_INVALIDPARAMETER); /* Channel range */ + ASSERT( div > 3, ERROR_INVALIDPARAMETER); /* Divider integer value */ + ASSERT( div < 901, ERROR_INVALIDPARAMETER); /* Divider integer value */ + ASSERT( denom > 0, ERROR_INVALIDPARAMETER ); /* Avoid divide by zero */ + ASSERT( num <= 0xFFFFF, ERROR_INVALIDPARAMETER ); /* 20-bit limit */ + ASSERT( denom <= 0xFFFFF, ERROR_INVALIDPARAMETER ); /* 20-bit limit */ + + /* Make sure the requested PLL has been initialised */ + if (pllSource == SI5351_PLL_A) + { + ASSERT(m_si5351Config.plla_configured = true, ERROR_INVALIDPARAMETER); + } + else + { + ASSERT(m_si5351Config.pllb_configured = true, ERROR_INVALIDPARAMETER); + } + + /* 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 + */ + + /* Set the main PLL config registers */ + if (num == 0) + { + /* Integer mode */ + P1 = 128 * div - 512; + P2 = num; + P3 = denom; + } + else + { + /* Fractional mode */ + P1 = (uint32_t)(128 * div + floor(128 * ((float)num/(float)denom)) - 512); + P2 = (uint32_t)(128 * num - denom * floor(128 * ((float)num/(float)denom))); + P3 = denom; + } + + /* Get the appropriate starting point for the PLL registers */ + uint8_t baseaddr = 0; + switch (output) + { + case 0: + baseaddr = SI5351_REGISTER_42_MULTISYNTH0_PARAMETERS_1; + break; + case 1: + baseaddr = SI5351_REGISTER_50_MULTISYNTH1_PARAMETERS_1; + break; + case 2: + baseaddr = SI5351_REGISTER_58_MULTISYNTH2_PARAMETERS_1; + break; + } + + /* Set the MSx config registers */ + ASSERT_STATUS( write8( baseaddr, (P3 & 0x0000FF00) >> 8)); + ASSERT_STATUS( write8( baseaddr+1, (P3 & 0x000000FF))); + ASSERT_STATUS( write8( baseaddr+2, (P1 & 0x00030000) >> 16)); /* ToDo: Add DIVBY4 (>150MHz) and R0 support (<500kHz) later */ + ASSERT_STATUS( write8( baseaddr+3, (P1 & 0x0000FF00) >> 8)); + ASSERT_STATUS( write8( baseaddr+4, (P1 & 0x000000FF))); + ASSERT_STATUS( write8( baseaddr+5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16) )); + ASSERT_STATUS( write8( baseaddr+6, (P2 & 0x0000FF00) >> 8)); + ASSERT_STATUS( write8( baseaddr+7, (P2 & 0x000000FF))); + + /* Configure the clk control and enable the output */ + uint8_t clkControlReg = 0x0F; /* 8mA drive strength, MS0 as CLK0 source, Clock not inverted, powered up */ + if (pllSource == SI5351_PLL_B) clkControlReg |= (1 << 5); /* Uses PLLB */ + if (num == 0) clkControlReg |= (1 << 6); /* Integer mode */ + switch (output) + { + case 0: + ASSERT_STATUS(write8(SI5351_REGISTER_16_CLK0_CONTROL, clkControlReg)); + break; + case 1: + ASSERT_STATUS(write8(SI5351_REGISTER_17_CLK1_CONTROL, clkControlReg)); + break; + case 2: + ASSERT_STATUS(write8(SI5351_REGISTER_18_CLK2_CONTROL, clkControlReg)); + break; + } + + return ERROR_NONE; +} + +/**************************************************************************/ +/*! + @brief Enables or disables all clock outputs +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::enableOutputs(bool enabled) +{ + /* Make sure we've called init first */ + ASSERT(m_si5351Config.initialised, ERROR_DEVICENOTINITIALISED); + + /* Enabled desired outputs (see Register 3) */ + ASSERT_STATUS(write8(SI5351_REGISTER_3_OUTPUT_ENABLE_CONTROL, enabled ? 0x00: 0xFF)); + + return ERROR_NONE; +} + +/* ---------------------------------------------------------------------- */ +/* PRUVATE FUNCTIONS */ +/* ---------------------------------------------------------------------- */ + +/**************************************************************************/ +/*! + @brief Writes a register and an 8 bit value over I2C +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::write8 (uint8_t reg, uint8_t value) +{ + Wire.beginTransmission(SI5351_ADDRESS); + #if ARDUINO >= 100 + Wire.write(reg); + Wire.write(value & 0xFF); + #else + Wire.send(reg); + Wire.send(value & 0xFF); + #endif + Wire.endTransmission(); + + // ToDo: Check for I2C errors + + return ERROR_NONE; +} + +/**************************************************************************/ +/*! + @brief Reads an 8 bit value over I2C +*/ +/**************************************************************************/ +err_t Adafruit_SI5351::read8(uint8_t reg, uint8_t *value) +{ + Wire.beginTransmission(SI5351_ADDRESS); + #if ARDUINO >= 100 + Wire.write(reg); + #else + Wire.send(reg); + #endif + Wire.endTransmission(); + + Wire.requestFrom(SI5351_ADDRESS, 1); + #if ARDUINO >= 100 + *value = Wire.read(); + #else + *value = Wire.read(); + #endif + + // ToDo: Check for I2C errors + + return ERROR_NONE; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Adafruit_SI5351.h +//////////////////////////////////////////////////////////////////////////////// +#if 0 +/**************************************************************************/ +/*! + @file Adafruit_SI5351.h + @author K. Townsend (Adafruit Industries) + + @section LICENSE + + Software License Agreement (BSD License) + + Copyright (c) 2014, Adafruit Industries + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ +#ifndef _SI5351_H_ +#define _SI5351_H_ + +#if ARDUINO >= 100 + #include <Arduino.h> +#else + #include <WProgram.h> +#endif +//#include <Adafruit_Sensor.h> +#include <Wire.h> + +#include "errors.h" +#include "asserts.h" + +#define SI5351_ADDRESS (0x60) // Assumes ADDR pin = low +#define SI5351_READBIT (0x01) + +/* Test setup from SI5351 ClockBuilder + * ----------------------------------- + * XTAL: 25 MHz + * Channel 0: 120.00 MHz + * Channel 1: 12.00 MHz + * Channel 2: 13.56 MHz + */ +static const uint8_t m_si5351_regs_15to92_149to170[100][2] = +{ + { 15, 0x00 }, /* Input source = crystal for PLLA and PLLB */ + { 16, 0x4F }, /* CLK0 Control: 8mA drive, Multisynth 0 as CLK0 source, Clock not inverted, Source = PLLA, Multisynth 0 in integer mode, clock powered up */ + { 17, 0x4F }, /* CLK1 Control: 8mA drive, Multisynth 1 as CLK1 source, Clock not inverted, Source = PLLA, Multisynth 1 in integer mode, clock powered up */ + { 18, 0x6F }, /* CLK2 Control: 8mA drive, Multisynth 2 as CLK2 source, Clock not inverted, Source = PLLB, Multisynth 2 in integer mode, clock powered up */ + { 19, 0x80 }, /* CLK3 Control: Not used ... clock powered down */ + { 20, 0x80 }, /* CLK4 Control: Not used ... clock powered down */ + { 21, 0x80 }, /* CLK5 Control: Not used ... clock powered down */ + { 22, 0x80 }, /* CLK6 Control: Not used ... clock powered down */ + { 23, 0x80 }, /* CLK7 Control: Not used ... clock powered down */ + { 24, 0x00 }, /* Clock disable state 0..3 (low when disabled) */ + { 25, 0x00 }, /* Clock disable state 4..7 (low when disabled) */ + /* PLL_A Setup */ + { 26, 0x00 }, + { 27, 0x05 }, + { 28, 0x00 }, + { 29, 0x0C }, + { 30, 0x66 }, + { 31, 0x00 }, + { 32, 0x00 }, + { 33, 0x02 }, + /* PLL_B Setup */ + { 34, 0x02 }, + { 35, 0x71 }, + { 36, 0x00 }, + { 37, 0x0C }, + { 38, 0x1A }, + { 39, 0x00 }, + { 40, 0x00 }, + { 41, 0x86 }, + /* Multisynth Setup */ + { 42, 0x00 }, + { 43, 0x01 }, + { 44, 0x00 }, + { 45, 0x01 }, + { 46, 0x00 }, + { 47, 0x00 }, + { 48, 0x00 }, + { 49, 0x00 }, + { 50, 0x00 }, + { 51, 0x01 }, + { 52, 0x00 }, + { 53, 0x1C }, + { 54, 0x00 }, + { 55, 0x00 }, + { 56, 0x00 }, + { 57, 0x00 }, + { 58, 0x00 }, + { 59, 0x01 }, + { 60, 0x00 }, + { 61, 0x18 }, + { 62, 0x00 }, + { 63, 0x00 }, + { 64, 0x00 }, + { 65, 0x00 }, + { 66, 0x00 }, + { 67, 0x00 }, + { 68, 0x00 }, + { 69, 0x00 }, + { 70, 0x00 }, + { 71, 0x00 }, + { 72, 0x00 }, + { 73, 0x00 }, + { 74, 0x00 }, + { 75, 0x00 }, + { 76, 0x00 }, + { 77, 0x00 }, + { 78, 0x00 }, + { 79, 0x00 }, + { 80, 0x00 }, + { 81, 0x00 }, + { 82, 0x00 }, + { 83, 0x00 }, + { 84, 0x00 }, + { 85, 0x00 }, + { 86, 0x00 }, + { 87, 0x00 }, + { 88, 0x00 }, + { 89, 0x00 }, + { 90, 0x00 }, + { 91, 0x00 }, + { 92, 0x00 }, + /* Misc Config Register */ + { 149, 0x00 }, + { 150, 0x00 }, + { 151, 0x00 }, + { 152, 0x00 }, + { 153, 0x00 }, + { 154, 0x00 }, + { 155, 0x00 }, + { 156, 0x00 }, + { 157, 0x00 }, + { 158, 0x00 }, + { 159, 0x00 }, + { 160, 0x00 }, + { 161, 0x00 }, + { 162, 0x00 }, + { 163, 0x00 }, + { 164, 0x00 }, + { 165, 0x00 }, + { 166, 0x00 }, + { 167, 0x00 }, + { 168, 0x00 }, + { 169, 0x00 }, + { 170, 0x00 } +}; + +/* See http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf for registers 26..41 */ +enum +{ + 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 +}; + +typedef enum +{ + SI5351_PLL_A = 0, + SI5351_PLL_B, +} si5351PLL_t; + +typedef enum +{ + SI5351_CRYSTAL_LOAD_6PF = (1<<6), + SI5351_CRYSTAL_LOAD_8PF = (2<<6), + SI5351_CRYSTAL_LOAD_10PF = (3<<6) +} si5351CrystalLoad_t; + +typedef enum +{ + SI5351_CRYSTAL_FREQ_25MHZ = (25000000), + SI5351_CRYSTAL_FREQ_27MHZ = (27000000) +} si5351CrystalFreq_t; + +typedef enum +{ + SI5351_MULTISYNTH_DIV_4 = 4, + SI5351_MULTISYNTH_DIV_6 = 6, + SI5351_MULTISYNTH_DIV_8 = 8 +} si5351MultisynthDiv_t; + +typedef enum +{ + SI5351_R_DIV_1 = 0, + SI5351_R_DIV_2 = 1, + SI5351_R_DIV_4 = 2, + SI5351_R_DIV_8 = 3, + SI5351_R_DIV_16 = 4, + SI5351_R_DIV_32 = 5, + SI5351_R_DIV_64 = 6, + SI5351_R_DIV_128 = 7, +} si5351RDiv_t; + +typedef struct +{ + bool initialised; + si5351CrystalFreq_t crystalFreq; + si5351CrystalLoad_t crystalLoad; + uint32_t crystalPPM; + bool plla_configured; + uint32_t plla_freq; + bool pllb_configured; + uint32_t pllb_freq; +} si5351Config_t; + +class Adafruit_SI5351 +{ + public: + Adafruit_SI5351(void); + + err_t begin(void); + err_t setClockBuilderData(void); + err_t setupPLL(si5351PLL_t pll, uint8_t mult, uint32_t num, uint32_t denom); + err_t setupPLLInt(si5351PLL_t pll, uint8_t mult); + err_t setupMultisynth(uint8_t output, si5351PLL_t pllSource, uint32_t div, uint32_t num, uint32_t denom); + err_t setupMultisynthInt(uint8_t output, si5351PLL_t pllSource, si5351MultisynthDiv_t div); + err_t enableOutputs(bool enabled); + err_t setupRdiv(uint8_t output, si5351RDiv_t div); + + private: + si5351Config_t m_si5351Config; + + err_t write8(uint8_t reg, uint8_t value); + err_t read8(uint8_t reg, uint8_t *value); +}; + +#endif + +#endif +//