mirror of
				https://github.com/IcedRooibos/py32f0-template.git
				synced 2025-10-30 17:32:05 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			1820 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1820 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * si5351.cpp - Si5351 library for STM32 & HAL (2020)
 | |
|  *
 | |
|  * Copyright (C) 2015 - 2019 Jason Milldrum <milldrum@gmail.com>
 | |
|  *                           Dana H. Myers <k6jq@comcast.net>
 | |
|  *
 | |
|  * STM32 port by https://github.com/bob-01.
 | |
|  *
 | |
|  * Based on commit https://github.com/etherkit/Si5351Arduino/commit/b516084e09430
 | |
|  * - Up to date as of June 2023.
 | |
|  *
 | |
|  * Some tuning algorithms derived from clk-si5351.c in the Linux kernel.
 | |
|  * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
 | |
|  * Rabeeh Khoury <rabeeh@solid-run.com>
 | |
|  *
 | |
|  * This program is free software: you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License as published by
 | |
|  * the Free Software Foundation, either version 3 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "si5351.h"
 | |
| 
 | |
| struct Si5351Status dev_status = {0, 0, 0, 0, 0};
 | |
| struct Si5351IntStatus dev_int_status = {0, 0, 0, 0};
 | |
| I2C_HandleTypeDef * hi2c;
 | |
| 
 | |
| enum si5351_pll pll_assignment[8];
 | |
| uint64_t clk_freq[8];
 | |
| uint64_t plla_freq;
 | |
| uint64_t pllb_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(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 *);
 | |
| void update_int_status(struct Si5351IntStatus *);
 | |
| 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[2];
 | |
| uint8_t clkin_div;
 | |
| uint8_t i2c_bus_addr;
 | |
| bool clk_first_set[8];
 | |
| 
 | |
| 
 | |
| /********************/
 | |
| /* Public functions */
 | |
| /********************/
 | |
| 
 | |
| /*
 | |
|  * si5351_init(I2C_HandleTypeDef *hi2c, uint8_t i2c_addr, uint8_t xtal_load_c, uint32_t ref_osc_freq, int32_t corr)
 | |
|  *
 | |
|  * Setup communications to the Si5351 and set the crystal
 | |
|  * load capacitance.
 | |
|  *
 | |
|  * xtal_load_c - Crystal load capacitance. Use the SI5351_CRYSTAL_LOAD_*PF
 | |
|  * defines in the header file
 | |
|  * 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
 | |
|  *
 | |
|  * Returns a boolean that indicates whether a device was found on the desired
 | |
|  * I2C address.
 | |
|  *
 | |
|  */
 | |
| bool si5351_init(I2C_HandleTypeDef *_hi2c, uint8_t i2c_addr, uint8_t xtal_load_c, uint32_t xo_freq, int32_t corr) {
 | |
| 	hi2c = _hi2c;
 | |
| 	i2c_bus_addr = i2c_addr;
 | |
| 	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;
 | |
| 
 | |
| 	// Check for a device on the bus, bail out if it is not there
 | |
| 	uint8_t reg_val = 0;
 | |
| 
 | |
| 	if(reg_val == 0) {
 | |
| 		// Wait for SYS_INIT flag to be clear, indicating that device is ready
 | |
| 		uint8_t status_reg = 0;
 | |
| 		do
 | |
| 		{
 | |
| 			status_reg = si5351_read(SI5351_DEVICE_STATUS);
 | |
| 		} while (status_reg >> 7 == 1);
 | |
| 
 | |
| 		// Set crystal load capacitance
 | |
| 		si5351_write(SI5351_CRYSTAL_LOAD, (xtal_load_c & SI5351_CRYSTAL_LOAD_MASK) | 0b00010010);
 | |
| 
 | |
| 		// Set up the XO reference frequency
 | |
| 		if (xo_freq != 0)
 | |
| 		{
 | |
| 			set_ref_freq(xo_freq, SI5351_PLL_INPUT_XO);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			set_ref_freq(SI5351_XTAL_FREQ, SI5351_PLL_INPUT_XO);
 | |
| 		}
 | |
| 
 | |
| 		// Set the frequency calibration for the XO
 | |
| 		set_correction(corr, SI5351_PLL_INPUT_XO);
 | |
| 
 | |
| 		si5351_reset();
 | |
| 
 | |
| 		return true;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		return false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * si5351_reset(void)
 | |
|  *
 | |
|  * Call to reset the Si5351 to the state initialized by the library.
 | |
|  *
 | |
|  */
 | |
| void si5351_reset(void) {
 | |
| 	// Initialize the CLK outputs according to flowchart in datasheet
 | |
| 	// First, turn them off
 | |
| 	si5351_write(16, 0x80);
 | |
| 	si5351_write(17, 0x80);
 | |
| 	si5351_write(18, 0x80);
 | |
| 	si5351_write(19, 0x80);
 | |
| 	si5351_write(20, 0x80);
 | |
| 	si5351_write(21, 0x80);
 | |
| 	si5351_write(22, 0x80);
 | |
| 	si5351_write(23, 0x80);
 | |
| 
 | |
| 	// Turn the clocks back on...
 | |
| 	si5351_write(16, 0x0c);
 | |
| 	si5351_write(17, 0x0c);
 | |
| 	si5351_write(18, 0x0c);
 | |
| 	si5351_write(19, 0x0c);
 | |
| 	si5351_write(20, 0x0c);
 | |
| 	si5351_write(21, 0x0c);
 | |
| 	si5351_write(22, 0x0c);
 | |
| 	si5351_write(23, 0x0c);
 | |
| 
 | |
| 	// Set PLLA and PLLB to 800 MHz for automatic tuning
 | |
| 	set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
 | |
| 	set_pll(SI5351_PLL_FIXED, SI5351_PLLB);
 | |
| 
 | |
| 	// Make PLL to CLK assignments for automatic tuning
 | |
| 	pll_assignment[0] = SI5351_PLLA;
 | |
| 	pll_assignment[1] = SI5351_PLLA;
 | |
| 	pll_assignment[2] = SI5351_PLLA;
 | |
| 	pll_assignment[3] = SI5351_PLLA;
 | |
| 	pll_assignment[4] = SI5351_PLLA;
 | |
| 	pll_assignment[5] = SI5351_PLLA;
 | |
| 	pll_assignment[6] = SI5351_PLLB;
 | |
| 	pll_assignment[7] = SI5351_PLLB;
 | |
| 
 | |
| 	set_ms_source(SI5351_CLK0, SI5351_PLLA);
 | |
| 	set_ms_source(SI5351_CLK1, SI5351_PLLA);
 | |
| 	set_ms_source(SI5351_CLK2, SI5351_PLLA);
 | |
| 	set_ms_source(SI5351_CLK3, SI5351_PLLA);
 | |
| 	set_ms_source(SI5351_CLK4, SI5351_PLLA);
 | |
| 	set_ms_source(SI5351_CLK5, SI5351_PLLA);
 | |
| 	set_ms_source(SI5351_CLK6, SI5351_PLLB);
 | |
| 	set_ms_source(SI5351_CLK7, SI5351_PLLB);
 | |
| 
 | |
| 	// Reset the VCXO param
 | |
| 	si5351_write(SI5351_VXCO_PARAMETERS_LOW, 0);
 | |
| 	si5351_write(SI5351_VXCO_PARAMETERS_MID, 0);
 | |
| 	si5351_write(SI5351_VXCO_PARAMETERS_HIGH, 0);
 | |
| 
 | |
| 	// Then reset the PLLs
 | |
| 	pll_reset(SI5351_PLLA);
 | |
| 	pll_reset(SI5351_PLLB);
 | |
| 
 | |
| 	// Set initial frequencies
 | |
| 	uint8_t i;
 | |
| 	for(i = 0; i < 8; i++)
 | |
| 	{
 | |
| 		clk_freq[i] = 0;
 | |
| 		output_enable((enum si5351_clock)i, 0);
 | |
| 		clk_first_set[i] = false;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * si5351_set_freq(uint64_t freq, enum si5351_clock clk)
 | |
|  *
 | |
|  * Sets the clock frequency of the specified CLK output.
 | |
|  * Frequency range of 8 kHz to 150 MHz
 | |
|  *
 | |
|  * freq - Output frequency in Hz
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  */
 | |
| uint8_t si5351_set_freq(uint64_t freq, enum si5351_clock clk)
 | |
| {
 | |
| 	struct Si5351RegSet ms_reg;
 | |
| 	uint64_t pll_freq;
 | |
| 	uint8_t int_mode = 0;
 | |
| 	uint8_t div_by_4 = 0;
 | |
| 	uint8_t r_div = 0;
 | |
| 
 | |
| 	// Check which Multisynth is being set
 | |
| 	if((uint8_t)clk <= (uint8_t)SI5351_CLK5)
 | |
| 	{
 | |
| 		// MS0 through MS5 logic
 | |
| 		// ---------------------
 | |
| 
 | |
| 		// Lower bounds check
 | |
| 		if(freq > 0 && freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT)
 | |
| 		{
 | |
| 			freq = SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT;
 | |
| 		}
 | |
| 
 | |
| 		// Upper bounds check
 | |
| 		if(freq > SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT)
 | |
| 		{
 | |
| 			freq = SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT;
 | |
| 		}
 | |
| 
 | |
| 		// If requested freq >100 MHz and no other outputs are already >100 MHz,
 | |
| 		// we need to recalculate PLLA and then recalculate all other CLK outputs
 | |
| 		// on same PLL
 | |
| 		if(freq > (SI5351_MULTISYNTH_SHARE_MAX * SI5351_FREQ_MULT))
 | |
| 		{
 | |
| 			// Check other clocks on same PLL
 | |
| 			uint8_t i;
 | |
| 			for(i = 0; i < 6; i++)
 | |
| 			{
 | |
| 				if(clk_freq[i] > (SI5351_MULTISYNTH_SHARE_MAX * SI5351_FREQ_MULT))
 | |
| 				{
 | |
| 					if(i != (uint8_t)clk && pll_assignment[i] == pll_assignment[clk])
 | |
| 					{
 | |
| 						return 1; // won't set if any other clks already >100 MHz
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Enable the output on first set_freq only
 | |
| 			if(clk_first_set[(uint8_t)clk] == false)
 | |
| 			{
 | |
| 				output_enable(clk, 1);
 | |
| 				clk_first_set[(uint8_t)clk] = true;
 | |
| 			}
 | |
| 
 | |
| 			// Set the freq in memory
 | |
| 			clk_freq[(uint8_t)clk] = freq;
 | |
| 
 | |
| 			// Calculate the proper PLL frequency
 | |
| 			pll_freq = multisynth_calc(freq, 0, &ms_reg);
 | |
| 
 | |
| 			// Set PLL
 | |
| 			set_pll(pll_freq, pll_assignment[clk]);
 | |
| 
 | |
| 			// Recalculate params for other synths on same PLL
 | |
| 			for(i = 0; i < 6; i++)
 | |
| 			{
 | |
| 				if(clk_freq[i] != 0)
 | |
| 				{
 | |
| 					if(pll_assignment[i] == pll_assignment[clk])
 | |
| 					{
 | |
| 						struct Si5351RegSet temp_reg;
 | |
| 						uint64_t temp_freq;
 | |
| 
 | |
| 						// Select the proper R div value
 | |
| 						temp_freq = clk_freq[i];
 | |
| 						r_div = select_r_div(&temp_freq);
 | |
| 
 | |
| 						multisynth_calc(temp_freq, pll_freq, &temp_reg);
 | |
| 
 | |
| 						// If freq > 150 MHz, we need to use DIVBY4 and integer mode
 | |
| 						if(temp_freq >= SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT)
 | |
| 						{
 | |
| 							div_by_4 = 1;
 | |
| 							int_mode = 1;
 | |
| 						}
 | |
| 						else
 | |
| 						{
 | |
| 							div_by_4 = 0;
 | |
| 							int_mode = 0;
 | |
| 						}
 | |
| 
 | |
| 						// Set multisynth registers
 | |
| 						set_ms((enum si5351_clock)i, temp_reg, int_mode, r_div, div_by_4);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			// Reset the PLL
 | |
| 			pll_reset(pll_assignment[clk]);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			clk_freq[(uint8_t)clk] = freq;
 | |
| 
 | |
| 			// Enable the output on first set_freq only
 | |
| 			if(clk_first_set[(uint8_t)clk] == false)
 | |
| 			{
 | |
| 				output_enable(clk, 1);
 | |
| 				clk_first_set[(uint8_t)clk] = true;
 | |
| 			}
 | |
| 
 | |
| 			// Select the proper R div value
 | |
| 			r_div = select_r_div(&freq);
 | |
| 
 | |
| 			// Calculate the synth parameters
 | |
| 			if(pll_assignment[clk] == SI5351_PLLA)
 | |
| 			{
 | |
| 				multisynth_calc(freq, plla_freq, &ms_reg);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				multisynth_calc(freq, pllb_freq, &ms_reg);
 | |
| 			}
 | |
| 
 | |
| 			// Set multisynth registers
 | |
| 			set_ms(clk, ms_reg, int_mode, r_div, div_by_4);
 | |
| 
 | |
| 			// Reset the PLL
 | |
| 			//pll_reset(pll_assignment[clk]);
 | |
| 		}
 | |
| 
 | |
| 		return 0;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// MS6 and MS7 logic
 | |
| 		// -----------------
 | |
| 
 | |
| 		// Lower bounds check
 | |
| 		if(freq > 0 && freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT)
 | |
| 		{
 | |
| 			freq = SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT;
 | |
| 		}
 | |
| 
 | |
| 		// Upper bounds check
 | |
| 		if(freq >= SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT)
 | |
| 		{
 | |
| 			freq = SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT - 1;
 | |
| 		}
 | |
| 
 | |
| 		// If one of CLK6 or CLK7 is already set when trying to set the other,
 | |
| 		// we have to ensure that it will also have an integer division ratio
 | |
| 		// with the same PLL, otherwise do not set it.
 | |
| 		if(clk == SI5351_CLK6)
 | |
| 		{
 | |
| 			if(clk_freq[7] != 0)
 | |
| 			{
 | |
| 				if(pllb_freq % freq == 0)
 | |
| 				{
 | |
| 					if((pllb_freq / freq) % 2 != 0)
 | |
| 					{
 | |
| 						// Not an even divide ratio, no bueno
 | |
| 						return 1;
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						// Set the freq in memory
 | |
| 						clk_freq[(uint8_t)clk] = freq;
 | |
| 
 | |
| 						// Select the proper R div value
 | |
| 						r_div = select_r_div_ms67(&freq);
 | |
| 
 | |
| 						multisynth67_calc(freq, pllb_freq, &ms_reg);
 | |
| 					}
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					// Not an integer divide ratio, no good
 | |
| 					return 1;
 | |
| 				}
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				// No previous assignment, so set PLLB based on CLK6
 | |
| 
 | |
| 				// Set the freq in memory
 | |
| 				clk_freq[(uint8_t)clk] = freq;
 | |
| 
 | |
| 				// Select the proper R div value
 | |
| 				r_div = select_r_div_ms67(&freq);
 | |
| 
 | |
| 				pll_freq = multisynth67_calc(freq, 0, &ms_reg);
 | |
| 				//pllb_freq = pll_freq;
 | |
| 				set_pll(pll_freq, SI5351_PLLB);
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			if(clk_freq[6] != 0)
 | |
| 			{
 | |
| 				if(pllb_freq % freq == 0)
 | |
| 				{
 | |
| 					if((pllb_freq / freq) % 2 != 0)
 | |
| 					{
 | |
| 						// Not an even divide ratio, no bueno
 | |
| 						return 1;
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						// Set the freq in memory
 | |
| 						clk_freq[(uint8_t)clk] = freq;
 | |
| 
 | |
| 						// Select the proper R div value
 | |
| 						r_div = select_r_div_ms67(&freq);
 | |
| 
 | |
| 						multisynth67_calc(freq, pllb_freq, &ms_reg);
 | |
| 					}
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					// Not an integer divide ratio, no good
 | |
| 					return 1;
 | |
| 				}
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				// No previous assignment, so set PLLB based on CLK7
 | |
| 
 | |
| 				// Set the freq in memory
 | |
| 				clk_freq[(uint8_t)clk] = freq;
 | |
| 
 | |
| 				// Select the proper R div value
 | |
| 				r_div = select_r_div_ms67(&freq);
 | |
| 
 | |
| 				pll_freq = multisynth67_calc(freq, 0, &ms_reg);
 | |
| 				//pllb_freq = pll_freq;
 | |
| 				set_pll(pll_freq, pll_assignment[clk]);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		div_by_4 = 0;
 | |
| 		int_mode = 0;
 | |
| 
 | |
| 		// Set multisynth registers (MS must be set before PLL)
 | |
| 		set_ms(clk, ms_reg, int_mode, r_div, div_by_4);
 | |
| 
 | |
| 		return 0;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_freq_manual(uint64_t freq, uint64_t pll_freq, enum si5351_clock clk)
 | |
|  *
 | |
|  * Sets the clock frequency of the specified CLK output using the given PLL
 | |
|  * frequency. You must ensure that the MS is assigned to the correct PLL and
 | |
|  * that the PLL is set to the correct frequency before using this method.
 | |
|  *
 | |
|  * It is important to note that if you use this method, you will have to
 | |
|  * track that all settings are sane yourself.
 | |
|  *
 | |
|  * freq - Output frequency in Hz
 | |
|  * pll_freq - Frequency of the PLL driving the Multisynth in Hz * 100
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  */
 | |
| uint8_t set_freq_manual(uint64_t freq, uint64_t pll_freq, enum si5351_clock clk)
 | |
| {
 | |
| 	struct Si5351RegSet ms_reg;
 | |
| 	uint8_t int_mode = 0;
 | |
| 	uint8_t div_by_4 = 0;
 | |
| 
 | |
| 	// Lower bounds check
 | |
| 	if(freq > 0 && freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 
 | |
| 	// Upper bounds check
 | |
| 	if(freq > SI5351_CLKOUT_MAX_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_CLKOUT_MAX_FREQ * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 
 | |
| 	uint8_t r_div;
 | |
| 
 | |
| 	clk_freq[(uint8_t)clk] = freq;
 | |
| 
 | |
| 	set_pll(pll_freq, pll_assignment[clk]);
 | |
| 
 | |
| 	// Enable the output
 | |
| 	output_enable(clk, 1);
 | |
| 
 | |
| 	// Select the proper R div value
 | |
| 	r_div = select_r_div(&freq);
 | |
| 
 | |
| 	// Calculate the synth parameters
 | |
| 	multisynth_calc(freq, pll_freq, &ms_reg);
 | |
| 
 | |
| 	// If freq > 150 MHz, we need to use DIVBY4 and integer mode
 | |
| 	if(freq >= SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		div_by_4 = 1;
 | |
| 		int_mode = 1;
 | |
| 	}
 | |
| 
 | |
| 	// Set multisynth registers (MS must be set before PLL)
 | |
| 	set_ms(clk, ms_reg, int_mode, r_div, div_by_4);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_pll(uint64_t pll_freq, enum si5351_pll target_pll)
 | |
|  *
 | |
|  * Set the specified PLL to a specific oscillation frequency
 | |
|  *
 | |
|  * pll_freq - Desired PLL frequency in Hz * 100
 | |
|  * target_pll - Which PLL to set
 | |
|  *     (use the si5351_pll enum)
 | |
|  */
 | |
| void set_pll(uint64_t pll_freq, enum si5351_pll target_pll)
 | |
| {
 | |
|   struct Si5351RegSet pll_reg;
 | |
| 
 | |
| 	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
 | |
| 
 | |
|   // Prepare an array for parameters to be written to
 | |
|   uint8_t __p[20];
 | |
|   uint8_t *params = __p;
 | |
|   uint8_t i = 0;
 | |
|   uint8_t temp;
 | |
| 
 | |
|   // Registers 26-27
 | |
|   temp = ((pll_reg.p3 >> 8) & 0xFF);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   temp = (uint8_t)(pll_reg.p3  & 0xFF);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   // Register 28
 | |
|   temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   // Registers 29-30
 | |
|   temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   temp = (uint8_t)(pll_reg.p1  & 0xFF);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   // Register 31
 | |
|   temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0);
 | |
|   temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   // Registers 32-33
 | |
|   temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   temp = (uint8_t)(pll_reg.p2  & 0xFF);
 | |
|   params[i++] = temp;
 | |
| 
 | |
|   // Write the parameters
 | |
|   if(target_pll == SI5351_PLLA)
 | |
|   {
 | |
|     si5351_write_bulk(SI5351_PLLA_PARAMETERS, i, params);
 | |
| 		plla_freq = pll_freq;
 | |
|   }
 | |
|   else if(target_pll == SI5351_PLLB)
 | |
|   {
 | |
|     si5351_write_bulk(SI5351_PLLB_PARAMETERS, i, params);
 | |
| 		pllb_freq = pll_freq;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_ms(enum si5351_clock clk, struct Si5351RegSet ms_reg, uint8_t int_mode, uint8_t r_div, uint8_t div_by_4)
 | |
|  *
 | |
|  * Set the specified multisynth parameters. Not normally needed, but public for advanced users.
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * int_mode - Set integer mode
 | |
|  *  Set to 1 to enable, 0 to disable
 | |
|  * r_div - Desired r_div ratio
 | |
|  * div_by_4 - Set Divide By 4 mode
 | |
|  *   Set to 1 to enable, 0 to disable
 | |
|  */
 | |
| void set_ms(enum si5351_clock clk, struct Si5351RegSet ms_reg, uint8_t int_mode, uint8_t r_div, uint8_t div_by_4)
 | |
| {
 | |
| 	uint8_t __p[20];
 | |
| 	uint8_t *params = __p;
 | |
| 
 | |
| 	uint8_t i = 0;
 | |
|  	uint8_t temp;
 | |
|  	uint8_t reg_val;
 | |
| 
 | |
| 
 | |
| 	if((uint8_t)clk <= (uint8_t)SI5351_CLK5)
 | |
| 	{
 | |
| 		// Registers 42-43 for CLK0
 | |
| 		temp = (uint8_t)((ms_reg.p3 >> 8) & 0xFF);
 | |
| 		params[i++] = temp;
 | |
| 
 | |
| 		temp = (uint8_t)(ms_reg.p3  & 0xFF);
 | |
| 		params[i++] = temp;
 | |
| 
 | |
| 		// Register 44 for CLK0
 | |
| 		reg_val = si5351_read((SI5351_CLK0_PARAMETERS + 2) + (clk * 8));
 | |
| 		reg_val &= ~(0x03);
 | |
| 		temp = reg_val | ((uint8_t)((ms_reg.p1 >> 16) & 0x03));
 | |
| 		params[i++] = temp;
 | |
| 
 | |
| 		// Registers 45-46 for CLK0
 | |
| 		temp = (uint8_t)((ms_reg.p1 >> 8) & 0xFF);
 | |
| 		params[i++] = temp;
 | |
| 
 | |
| 		temp = (uint8_t)(ms_reg.p1  & 0xFF);
 | |
| 		params[i++] = temp;
 | |
| 
 | |
| 		// Register 47 for CLK0
 | |
| 		temp = (uint8_t)((ms_reg.p3 >> 12) & 0xF0);
 | |
| 		temp += (uint8_t)((ms_reg.p2 >> 16) & 0x0F);
 | |
| 		params[i++] = temp;
 | |
| 
 | |
| 		// Registers 48-49 for CLK0
 | |
| 		temp = (uint8_t)((ms_reg.p2 >> 8) & 0xFF);
 | |
| 		params[i++] = temp;
 | |
| 
 | |
| 		temp = (uint8_t)(ms_reg.p2  & 0xFF);
 | |
| 		params[i++] = temp;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// MS6 and MS7 only use one register
 | |
| 		temp = ms_reg.p1;
 | |
| 	}
 | |
| 
 | |
| 	// Write the parameters
 | |
| 	switch(clk)
 | |
| 	{
 | |
| 		case SI5351_CLK0:
 | |
| 			si5351_write_bulk(SI5351_CLK0_PARAMETERS, i, params);
 | |
| 			set_int(clk, int_mode);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 		case SI5351_CLK1:
 | |
| 			si5351_write_bulk(SI5351_CLK1_PARAMETERS, i, params);
 | |
| 			set_int(clk, int_mode);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 		case SI5351_CLK2:
 | |
| 			si5351_write_bulk(SI5351_CLK2_PARAMETERS, i, params);
 | |
| 			set_int(clk, int_mode);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 		case SI5351_CLK3:
 | |
| 			si5351_write_bulk(SI5351_CLK3_PARAMETERS, i, params);
 | |
| 			set_int(clk, int_mode);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 		case SI5351_CLK4:
 | |
| 			si5351_write_bulk(SI5351_CLK4_PARAMETERS, i, params);
 | |
| 			set_int(clk, int_mode);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 		case SI5351_CLK5:
 | |
| 			si5351_write_bulk(SI5351_CLK5_PARAMETERS, i, params);
 | |
| 			set_int(clk, int_mode);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 		case SI5351_CLK6:
 | |
| 			si5351_write(SI5351_CLK6_PARAMETERS, temp);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 		case SI5351_CLK7:
 | |
| 			si5351_write(SI5351_CLK7_PARAMETERS, temp);
 | |
| 			ms_div(clk, r_div, div_by_4);
 | |
| 			break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * output_enable(enum si5351_clock clk, uint8_t enable)
 | |
|  *
 | |
|  * Enable or disable a chosen output
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * enable - Set to 1 to enable, 0 to disable
 | |
|  */
 | |
| void output_enable(enum si5351_clock clk, uint8_t enable)
 | |
| {
 | |
|   uint8_t reg_val;
 | |
| 
 | |
|   reg_val = si5351_read(SI5351_OUTPUT_ENABLE_CTRL);
 | |
| 
 | |
|   if(enable == 1)
 | |
|   {
 | |
|     reg_val &= ~(1<<(uint8_t)clk);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     reg_val |= (1<<(uint8_t)clk);
 | |
|   }
 | |
| 
 | |
|   si5351_write(SI5351_OUTPUT_ENABLE_CTRL, reg_val);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * si5351_drive_strength(enum si5351_clock clk, enum si5351_drive drive)
 | |
|  *
 | |
|  * Sets the drive strength of the specified clock output
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * drive - Desired drive level
 | |
|  *   (use the si5351_drive enum)
 | |
|  */
 | |
| void si5351_drive_strength(enum si5351_clock clk, enum si5351_drive drive) {
 | |
|   uint8_t reg_val;
 | |
|   const uint8_t mask = 0x03;
 | |
| 
 | |
|   reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
 | |
|   reg_val &= ~(mask);
 | |
| 
 | |
|   switch(drive)
 | |
|   {
 | |
|   case SI5351_DRIVE_2MA:
 | |
|     reg_val |= 0x00;
 | |
|     break;
 | |
|   case SI5351_DRIVE_4MA:
 | |
|    reg_val |= 0x01;
 | |
|     break;
 | |
|   case SI5351_DRIVE_6MA:
 | |
|     reg_val |= 0x02;
 | |
|     break;
 | |
|   case SI5351_DRIVE_8MA:
 | |
|     reg_val |= 0x03;
 | |
|     break;
 | |
|   default:
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * update_status(void)
 | |
|  *
 | |
|  * Call this to update the status structs, then access them
 | |
|  * via the dev_status and dev_int_status global members.
 | |
|  *
 | |
|  * See the header file for the struct definitions. These
 | |
|  * correspond to the flag names for registers 0 and 1 in
 | |
|  * the Si5351 datasheet.
 | |
|  */
 | |
| void update_status(void)
 | |
| {
 | |
| 	update_sys_status(&dev_status);
 | |
| 	update_int_status(&dev_int_status);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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
 | |
|  * parts-per-billion value that the actual oscillation
 | |
|  * frequency deviates from the specified frequency.
 | |
|  *
 | |
|  * The frequency calibration is done as a one-time procedure.
 | |
|  * Any desired test frequency within the normal range of the
 | |
|  * Si5351 should be set, then the actual output frequency
 | |
|  * should be measured as accurately as possible. The
 | |
|  * difference between the measured and specified frequencies
 | |
|  * should be calculated in Hertz, then multiplied by 10 in
 | |
|  * order to get the parts-per-billion value.
 | |
|  *
 | |
|  * Since the Si5351 itself has an intrinsic 0 PPM error, this
 | |
|  * correction factor is good across the entire tuning range of
 | |
|  * the Si5351. Once this calibration is done accurately, it
 | |
|  * should not have to be done again for the same Si5351 and
 | |
|  * crystal.
 | |
|  */
 | |
| void set_correction(int32_t corr, enum si5351_pll_input ref_osc)
 | |
| {
 | |
| 	ref_correction[(uint8_t)ref_osc] = corr;
 | |
| 
 | |
| 	// Recalculate and set PLL freqs based on correction value
 | |
| 	set_pll(plla_freq, SI5351_PLLA);
 | |
| 	set_pll(pllb_freq, SI5351_PLLB);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_phase(enum si5351_clock clk, uint8_t phase)
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * phase - 7-bit phase word
 | |
|  *   (in units of VCO/4 period)
 | |
|  *
 | |
|  * Write the 7-bit phase register. This must be used
 | |
|  * with a user-set PLL frequency so that the user can
 | |
|  * calculate the proper tuning word based on the PLL period.
 | |
|  */
 | |
| void set_phase(enum si5351_clock clk, uint8_t phase)
 | |
| {
 | |
| 	// Mask off the upper bit since it is reserved
 | |
| 	phase = phase & 0b01111111;
 | |
| 
 | |
| 	si5351_write(SI5351_CLK0_PHASE_OFFSET + (uint8_t)clk, phase);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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 get_correction(enum si5351_pll_input ref_osc)
 | |
| {
 | |
| 	return ref_correction[(uint8_t)ref_osc];
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * pll_reset(enum si5351_pll target_pll)
 | |
|  *
 | |
|  * target_pll - Which PLL to reset
 | |
|  *     (use the si5351_pll enum)
 | |
|  *
 | |
|  * Apply a reset to the indicated PLL.
 | |
|  */
 | |
| void pll_reset(enum si5351_pll target_pll)
 | |
| {
 | |
| 	if(target_pll == SI5351_PLLA)
 | |
|  	{
 | |
|     	si5351_write(SI5351_PLL_RESET, SI5351_PLL_RESET_A);
 | |
| 	}
 | |
| 	else if(target_pll == SI5351_PLLB)
 | |
| 	{
 | |
| 	    si5351_write(SI5351_PLL_RESET, SI5351_PLL_RESET_B);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_ms_source(enum si5351_clock clk, enum si5351_pll pll)
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * pll - Which PLL to use as the source
 | |
|  *     (use the si5351_pll enum)
 | |
|  *
 | |
|  * Set the desired PLL source for a multisynth.
 | |
|  */
 | |
| void set_ms_source(enum si5351_clock clk, enum si5351_pll pll)
 | |
| {
 | |
| 	uint8_t reg_val;
 | |
| 
 | |
| 	reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
 | |
| 
 | |
| 	if(pll == SI5351_PLLA)
 | |
| 	{
 | |
| 		reg_val &= ~(SI5351_CLK_PLL_SELECT);
 | |
| 	}
 | |
| 	else if(pll == SI5351_PLLB)
 | |
| 	{
 | |
| 		reg_val |= SI5351_CLK_PLL_SELECT;
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
 | |
| 
 | |
| 	pll_assignment[(uint8_t)clk] = pll;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_int(enum si5351_clock clk, uint8_t int_mode)
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * enable - Set to 1 to enable, 0 to disable
 | |
|  *
 | |
|  * Set the indicated multisynth into integer mode.
 | |
|  */
 | |
| void set_int(enum si5351_clock clk, uint8_t enable)
 | |
| {
 | |
| 	uint8_t reg_val;
 | |
| 	reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
 | |
| 
 | |
| 	if(enable == 1)
 | |
| 	{
 | |
| 		reg_val |= (SI5351_CLK_INTEGER_MODE);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		reg_val &= ~(SI5351_CLK_INTEGER_MODE);
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
 | |
| 
 | |
| 	// Integer mode indication
 | |
| 	/*
 | |
| 	switch(clk)
 | |
| 	{
 | |
| 	case SI5351_CLK0:
 | |
| 		clk0_int_mode = enable;
 | |
| 		break;
 | |
| 	case SI5351_CLK1:
 | |
| 		clk1_int_mode = enable;
 | |
| 		break;
 | |
| 	case SI5351_CLK2:
 | |
| 		clk2_int_mode = enable;
 | |
| 		break;
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| 	*/
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_clock_pwr(enum si5351_clock clk, uint8_t pwr)
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * pwr - Set to 1 to enable, 0 to disable
 | |
|  *
 | |
|  * Enable or disable power to a clock output (a power
 | |
|  * saving feature).
 | |
|  */
 | |
| void set_clock_pwr(enum si5351_clock clk, uint8_t pwr)
 | |
| {
 | |
| 	uint8_t reg_val; //, reg;
 | |
| 	reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
 | |
| 
 | |
| 	if(pwr == 1)
 | |
| 	{
 | |
| 		reg_val &= 0b01111111;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		reg_val |= 0b10000000;
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_clock_invert(enum si5351_clock clk, uint8_t inv)
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * inv - Set to 1 to enable, 0 to disable
 | |
|  *
 | |
|  * Enable to invert the clock output waveform.
 | |
|  */
 | |
| void set_clock_invert(enum si5351_clock clk, uint8_t inv)
 | |
| {
 | |
| 	uint8_t reg_val;
 | |
| 	reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
 | |
| 
 | |
| 	if(inv == 1)
 | |
| 	{
 | |
| 		reg_val |= (SI5351_CLK_INVERT);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		reg_val &= ~(SI5351_CLK_INVERT);
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_clock_source(enum si5351_clock clk, enum si5351_clock_source src)
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * src - Which clock source to use for the multisynth
 | |
|  *   (use the si5351_clock_source enum)
 | |
|  *
 | |
|  * Set the clock source for a multisynth (based on the options
 | |
|  * presented for Registers 16-23 in the Silicon Labs AN619 document).
 | |
|  * Choices are XTAL, CLKIN, MS0, or the multisynth associated with
 | |
|  * the clock output.
 | |
|  */
 | |
| void set_clock_source(enum si5351_clock clk, enum si5351_clock_source src)
 | |
| {
 | |
| 	uint8_t reg_val;
 | |
| 	reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
 | |
| 
 | |
| 	// Clear the bits first
 | |
| 	reg_val &= ~(SI5351_CLK_INPUT_MASK);
 | |
| 
 | |
| 	switch(src)
 | |
| 	{
 | |
| 	case SI5351_CLK_SRC_XTAL:
 | |
| 		reg_val |= (SI5351_CLK_INPUT_XTAL);
 | |
| 		break;
 | |
| 	case SI5351_CLK_SRC_CLKIN:
 | |
| 		reg_val |= (SI5351_CLK_INPUT_CLKIN);
 | |
| 		break;
 | |
| 	case SI5351_CLK_SRC_MS0:
 | |
| 		if(clk == SI5351_CLK0)
 | |
| 		{
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		reg_val |= (SI5351_CLK_INPUT_MULTISYNTH_0_4);
 | |
| 		break;
 | |
| 	case SI5351_CLK_SRC_MS:
 | |
| 		reg_val |= (SI5351_CLK_INPUT_MULTISYNTH_N);
 | |
| 		break;
 | |
| 	default:
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_clock_disable(enum si5351_clock clk, enum si5351_clock_disable dis_state)
 | |
|  *
 | |
|  * clk - Clock output
 | |
|  *   (use the si5351_clock enum)
 | |
|  * dis_state - Desired state of the output upon disable
 | |
|  *   (use the si5351_clock_disable enum)
 | |
|  *
 | |
|  * Set the state of the clock output when it is disabled. Per page 27
 | |
|  * of AN619 (Registers 24 and 25), there are four possible values: low,
 | |
|  * high, high impedance, and never disabled.
 | |
|  */
 | |
| void set_clock_disable(enum si5351_clock clk, enum si5351_clock_disable dis_state)
 | |
| {
 | |
| 	uint8_t reg_val, reg;
 | |
| 
 | |
| 	if (clk >= SI5351_CLK0 && clk <= SI5351_CLK3)
 | |
| 	{
 | |
| 		reg = SI5351_CLK3_0_DISABLE_STATE;
 | |
| 	}
 | |
| 	else if(clk >= SI5351_CLK4 && clk <= SI5351_CLK7)
 | |
| 	{
 | |
| 		reg = SI5351_CLK7_4_DISABLE_STATE;
 | |
| 	}
 | |
| 	else return;
 | |
| 
 | |
| 	reg_val = si5351_read(reg);
 | |
| 
 | |
| 	if (clk >= SI5351_CLK0 && clk <= SI5351_CLK3)
 | |
| 	{
 | |
| 		reg_val &= ~(0b11 << (clk * 2));
 | |
| 		reg_val |= dis_state << (clk * 2);
 | |
| 	}
 | |
| 	else if(clk >= SI5351_CLK4 && clk <= SI5351_CLK7)
 | |
| 	{
 | |
| 		reg_val &= ~(0b11 << ((clk - 4) * 2));
 | |
| 		reg_val |= dis_state << ((clk - 4) * 2);
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(reg, reg_val);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_clock_fanout(enum si5351_clock_fanout fanout, uint8_t enable)
 | |
|  *
 | |
|  * fanout - Desired clock fanout
 | |
|  *   (use the si5351_clock_fanout enum)
 | |
|  * enable - Set to 1 to enable, 0 to disable
 | |
|  *
 | |
|  * Use this function to enable or disable the clock fanout options
 | |
|  * for individual clock outputs. If you intend to output the XO or
 | |
|  * CLKIN on the clock outputs, enable this first.
 | |
|  *
 | |
|  * By default, only the Multisynth fanout is enabled at startup.
 | |
|  */
 | |
| void set_clock_fanout(enum si5351_clock_fanout fanout, uint8_t enable)
 | |
| {
 | |
| 	uint8_t reg_val;
 | |
| 	reg_val = si5351_read(SI5351_FANOUT_ENABLE);
 | |
| 
 | |
| 	switch(fanout)
 | |
| 	{
 | |
| 	case SI5351_FANOUT_CLKIN:
 | |
| 		if(enable)
 | |
| 		{
 | |
| 			reg_val |= SI5351_CLKIN_ENABLE;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			reg_val &= ~(SI5351_CLKIN_ENABLE);
 | |
| 		}
 | |
| 		break;
 | |
| 	case SI5351_FANOUT_XO:
 | |
| 		if(enable)
 | |
| 		{
 | |
| 			reg_val |= SI5351_XTAL_ENABLE;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			reg_val &= ~(SI5351_XTAL_ENABLE);
 | |
| 		}
 | |
| 		break;
 | |
| 	case SI5351_FANOUT_MS:
 | |
| 		if(enable)
 | |
| 		{
 | |
| 			reg_val |= SI5351_MULTISYNTH_ENABLE;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			reg_val &= ~(SI5351_MULTISYNTH_ENABLE);
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(SI5351_FANOUT_ENABLE, reg_val);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * set_pll_input(enum si5351_pll pll, enum si5351_pll_input input)
 | |
|  *
 | |
|  * pll - Which PLL to use as the source
 | |
|  *     (use the si5351_pll enum)
 | |
|  * input - Which reference oscillator to use as PLL input
 | |
|  *     (use the si5351_pll_input enum)
 | |
|  *
 | |
|  * Set the desired reference oscillator source for the given PLL.
 | |
|  */
 | |
| void 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:
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	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 set_vcxo(uint64_t pll_freq, uint8_t ppm) {
 | |
| 	struct Si5351RegSet pll_reg;
 | |
| 	uint64_t vcxo_param;
 | |
| 
 | |
| 	// Bounds check
 | |
| 	if(ppm < SI5351_VCXO_PULL_MIN)
 | |
| 	{
 | |
| 		ppm = SI5351_VCXO_PULL_MIN;
 | |
| 	}
 | |
| 
 | |
| 	if(ppm > SI5351_VCXO_PULL_MAX)
 | |
| 	{
 | |
| 		ppm = SI5351_VCXO_PULL_MAX;
 | |
| 	}
 | |
| 
 | |
| 	// Set PLLB params
 | |
| 	vcxo_param = pll_calc(SI5351_PLLB, pll_freq, &pll_reg, ref_correction[pllb_ref_osc], 1);
 | |
| 
 | |
| 	// Derive the register values to write
 | |
| 
 | |
| 	// Prepare an array for parameters to be written to
 | |
| 	uint8_t __p[20];
 | |
| 	uint8_t *params = __p;
 | |
| 
 | |
| 	uint8_t i = 0;
 | |
| 	uint8_t temp;
 | |
| 
 | |
| 	// Registers 26-27
 | |
| 	temp = ((pll_reg.p3 >> 8) & 0xFF);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	temp = (uint8_t)(pll_reg.p3  & 0xFF);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	// Register 28
 | |
| 	temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	// Registers 29-30
 | |
| 	temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	temp = (uint8_t)(pll_reg.p1  & 0xFF);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	// Register 31
 | |
| 	temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0);
 | |
| 	temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	// Registers 32-33
 | |
| 	temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	temp = (uint8_t)(pll_reg.p2  & 0xFF);
 | |
| 	params[i++] = temp;
 | |
| 
 | |
| 	// Write the parameters
 | |
| 	si5351_write_bulk(SI5351_PLLB_PARAMETERS, i, params);
 | |
| 
 | |
| 	// Write the VCXO parameters
 | |
| 	vcxo_param = ((vcxo_param * ppm * SI5351_VCXO_MARGIN) / 100ULL) / 1000000ULL;
 | |
| 
 | |
| 	temp = (uint8_t)(vcxo_param & 0xFF);
 | |
| 	si5351_write(SI5351_VXCO_PARAMETERS_LOW, temp);
 | |
| 
 | |
| 	temp = (uint8_t)((vcxo_param >> 8) & 0xFF);
 | |
| 	si5351_write(SI5351_VXCO_PARAMETERS_MID, temp);
 | |
| 
 | |
| 	temp = (uint8_t)((vcxo_param >> 16) & 0x3F);
 | |
| 	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 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);
 | |
| }
 | |
| 
 | |
| /*********************/
 | |
| /* Private functions */
 | |
| /*********************/
 | |
| 
 | |
| uint64_t pll_calc(enum si5351_pll pll, uint64_t freq, struct Si5351RegSet *reg, int32_t correction, uint8_t vcxo)
 | |
| {
 | |
| 	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;
 | |
| 
 | |
| 	// Factor calibration value into nominal crystal frequency
 | |
| 	// Measured in parts-per-billion
 | |
| 
 | |
| 	ref_freq = ref_freq + (int32_t)((((((int64_t)correction) << 31) / 1000000000LL) * ref_freq) >> 31);
 | |
| 
 | |
| 	// PLL bounds checking
 | |
| 	if (freq < SI5351_PLL_VCO_MIN * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_PLL_VCO_MIN * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 	if (freq > SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 
 | |
| 	// Determine integer part of feedback equation
 | |
| 	a = freq / ref_freq;
 | |
| 
 | |
| 	if (a < SI5351_PLL_A_MIN)
 | |
| 	{
 | |
| 		freq = ref_freq * SI5351_PLL_A_MIN;
 | |
| 	}
 | |
| 	if (a > SI5351_PLL_A_MAX)
 | |
| 	{
 | |
| 		freq = ref_freq * SI5351_PLL_A_MAX;
 | |
| 	}
 | |
| 
 | |
| 	// Find best approximation for b/c = fVCO mod fIN
 | |
| 	// denom = 1000ULL * 1000ULL;
 | |
| 	// lltmp = freq % ref_freq;
 | |
| 	// lltmp *= denom;
 | |
| 	// do_div(lltmp, ref_freq);
 | |
| 
 | |
| 	//b = (((uint64_t)(freq % ref_freq)) * RFRAC_DENOM) / ref_freq;
 | |
| 	if(vcxo)
 | |
| 	{
 | |
| 		b = (((uint64_t)(freq % ref_freq)) * 1000000ULL) / ref_freq;
 | |
| 		c = 1000000ULL;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		b = (((uint64_t)(freq % ref_freq)) * RFRAC_DENOM) / ref_freq;
 | |
| 		c = b ? RFRAC_DENOM : 1;
 | |
| 	}
 | |
| 
 | |
| 	// Calculate parameters
 | |
| 	p1 = 128 * a + ((128 * b) / c) - 512;
 | |
| 	p2 = 128 * b - c * ((128 * b) / c);
 | |
| 	p3 = c;
 | |
| 
 | |
| 	// Recalculate frequency as fIN * (a + b/c)
 | |
| 	lltmp = ref_freq;
 | |
| 	lltmp *= b;
 | |
| 	do_div(lltmp, c);
 | |
| 	freq = lltmp;
 | |
| 	freq += ref_freq * a;
 | |
| 
 | |
| 	reg->p1 = p1;
 | |
| 	reg->p2 = p2;
 | |
| 	reg->p3 = p3;
 | |
| 
 | |
| 	if(vcxo)
 | |
| 	{
 | |
| 		return (uint64_t)(128 * a * 1000000ULL + b);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		return freq;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| uint64_t multisynth_calc(uint64_t freq, uint64_t pll_freq, struct Si5351RegSet *reg)
 | |
| {
 | |
| 	uint64_t lltmp;
 | |
| 	uint32_t a, b, c, p1, p2, p3;
 | |
| 	uint8_t divby4 = 0;
 | |
| 	uint8_t ret_val = 0;
 | |
| 
 | |
| 	// Multisynth bounds checking
 | |
| 	if (freq > SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_MULTISYNTH_MAX_FREQ * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 	if (freq < SI5351_MULTISYNTH_MIN_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_MULTISYNTH_MIN_FREQ * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 
 | |
| 	if (freq >= SI5351_MULTISYNTH_DIVBY4_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		divby4 = 1;
 | |
| 	}
 | |
| 
 | |
| 	if(pll_freq == 0)
 | |
| 	{
 | |
| 		// Find largest integer divider for max
 | |
| 		// VCO frequency and given target frequency
 | |
| 		if(divby4 == 0)
 | |
| 		{
 | |
| 			lltmp = SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT; // margin needed?
 | |
| 			do_div(lltmp, freq);
 | |
| 			if(lltmp == 5)
 | |
| 			{
 | |
| 				lltmp = 4;
 | |
| 			}
 | |
| 			else if(lltmp == 7)
 | |
| 			{
 | |
| 				lltmp = 6;
 | |
| 			}
 | |
| 			a = (uint32_t)lltmp;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			a = 4;
 | |
| 		}
 | |
| 
 | |
| 		b = 0;
 | |
| 		c = 1;
 | |
| 		pll_freq = a * freq;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// Preset PLL, so return the actual freq for these params instead of PLL freq
 | |
| 		ret_val = 1;
 | |
| 
 | |
| 		// Determine integer part of feedback equation
 | |
| 		a = pll_freq / freq;
 | |
| 
 | |
| 		if (a < SI5351_MULTISYNTH_A_MIN)
 | |
| 		{
 | |
| 			freq = pll_freq / SI5351_MULTISYNTH_A_MIN;
 | |
| 		}
 | |
| 		if (a > SI5351_MULTISYNTH_A_MAX)
 | |
| 		{
 | |
| 			freq = pll_freq / SI5351_MULTISYNTH_A_MAX;
 | |
| 		}
 | |
| 
 | |
| 		b = (pll_freq % freq * RFRAC_DENOM) / freq;
 | |
| 		c = b ? RFRAC_DENOM : 1;
 | |
| 	}
 | |
| 
 | |
| 	// Calculate parameters
 | |
| 	if (divby4 == 1)
 | |
| 	{
 | |
| 		p3 = 1;
 | |
| 		p2 = 0;
 | |
| 		p1 = 0;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 	p1 = 128 * a + ((128 * b) / c) - 512;
 | |
| 	p2 = 128 * b - c * ((128 * b) / c);
 | |
| 	p3 = c;
 | |
| 	}
 | |
| 
 | |
| 	reg->p1 = p1;
 | |
| 	reg->p2 = p2;
 | |
| 	reg->p3 = p3;
 | |
| 
 | |
| 	if(ret_val == 0)
 | |
| 	{
 | |
| 		return pll_freq;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		return freq;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| uint64_t multisynth67_calc(uint64_t freq, uint64_t pll_freq, struct Si5351RegSet *reg)
 | |
| {
 | |
| 	//uint8_t p1;
 | |
| 	// uint8_t ret_val = 0;
 | |
| 	uint32_t a;
 | |
| 	uint64_t lltmp;
 | |
| 
 | |
| 	// Multisynth bounds checking
 | |
| 	if(freq > SI5351_MULTISYNTH67_MAX_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_MULTISYNTH67_MAX_FREQ * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 	if(freq < SI5351_MULTISYNTH_MIN_FREQ * SI5351_FREQ_MULT)
 | |
| 	{
 | |
| 		freq = SI5351_MULTISYNTH_MIN_FREQ * SI5351_FREQ_MULT;
 | |
| 	}
 | |
| 
 | |
| 	if(pll_freq == 0)
 | |
| 	{
 | |
| 		// Find largest integer divider for max
 | |
| 		// VCO frequency and given target frequency
 | |
| 		lltmp = (SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT) - 100000000UL; // margin needed?
 | |
| 		do_div(lltmp, freq);
 | |
| 		a = (uint32_t)lltmp;
 | |
| 
 | |
| 		// Divisor has to be even
 | |
| 		if(a % 2 != 0)
 | |
| 		{
 | |
| 			a++;
 | |
| 		}
 | |
| 
 | |
| 		// Divisor bounds check
 | |
| 		if(a < SI5351_MULTISYNTH_A_MIN)
 | |
| 		{
 | |
| 			a = SI5351_MULTISYNTH_A_MIN;
 | |
| 		}
 | |
| 		if(a > SI5351_MULTISYNTH67_A_MAX)
 | |
| 		{
 | |
| 			a = SI5351_MULTISYNTH67_A_MAX;
 | |
| 		}
 | |
| 
 | |
| 		pll_freq = a * freq;
 | |
| 
 | |
| 		// PLL bounds checking
 | |
| 		if(pll_freq > (SI5351_PLL_VCO_MAX * SI5351_FREQ_MULT))
 | |
| 		{
 | |
| 			a -= 2;
 | |
| 			pll_freq = a * freq;
 | |
| 		}
 | |
| 		else if(pll_freq < (SI5351_PLL_VCO_MIN * SI5351_FREQ_MULT))
 | |
| 		{
 | |
| 			a += 2;
 | |
| 			pll_freq = a * freq;
 | |
| 		}
 | |
| 
 | |
| 		reg->p1 = (uint8_t)a;
 | |
| 		reg->p2 = 0;
 | |
| 		reg->p3 = 0;
 | |
| 		return pll_freq;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// Multisynth frequency must be integer division of PLL
 | |
| 		if(pll_freq % freq)
 | |
| 		{
 | |
| 			// No good
 | |
| 			return 0;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			a = pll_freq / freq;
 | |
| 
 | |
| 			// Division ratio bounds check
 | |
| 			if(a < SI5351_MULTISYNTH_A_MIN || a > SI5351_MULTISYNTH67_A_MAX)
 | |
| 			{
 | |
| 				// No bueno
 | |
| 				return 0;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				reg->p1 = (uint8_t)a;
 | |
| 				reg->p2 = 0;
 | |
| 				reg->p3 = 0;
 | |
| 				return 1;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void update_sys_status(struct Si5351Status *status)
 | |
| {
 | |
|   uint8_t reg_val = 0;
 | |
| 
 | |
|   reg_val = si5351_read(SI5351_DEVICE_STATUS);
 | |
| 
 | |
|   // Parse the register
 | |
|   status->SYS_INIT = (reg_val >> 7) & 0x01;
 | |
|   status->LOL_B = (reg_val >> 6) & 0x01;
 | |
|   status->LOL_A = (reg_val >> 5) & 0x01;
 | |
|   status->LOS = (reg_val >> 4) & 0x01;
 | |
|   status->REVID = reg_val & 0x03;
 | |
| }
 | |
| 
 | |
| void update_int_status(struct Si5351IntStatus *int_status)
 | |
| {
 | |
|   uint8_t reg_val = 0;
 | |
| 
 | |
|   reg_val = si5351_read(SI5351_INTERRUPT_STATUS);
 | |
| 
 | |
|   // Parse the register
 | |
|   int_status->SYS_INIT_STKY = (reg_val >> 7) & 0x01;
 | |
|   int_status->LOL_B_STKY = (reg_val >> 6) & 0x01;
 | |
|   int_status->LOL_A_STKY = (reg_val >> 5) & 0x01;
 | |
|   int_status->LOS_STKY = (reg_val >> 4) & 0x01;
 | |
| }
 | |
| 
 | |
| void ms_div(enum si5351_clock clk, uint8_t r_div, uint8_t div_by_4)
 | |
| {
 | |
| 	uint8_t reg_val = 0;
 | |
|     uint8_t reg_addr = 0;
 | |
| 
 | |
| 	switch(clk)
 | |
| 	{
 | |
| 		case SI5351_CLK0:
 | |
| 			reg_addr = SI5351_CLK0_PARAMETERS + 2;
 | |
| 			break;
 | |
| 		case SI5351_CLK1:
 | |
| 			reg_addr = SI5351_CLK1_PARAMETERS + 2;
 | |
| 			break;
 | |
| 		case SI5351_CLK2:
 | |
| 			reg_addr = SI5351_CLK2_PARAMETERS + 2;
 | |
| 			break;
 | |
| 		case SI5351_CLK3:
 | |
| 			reg_addr = SI5351_CLK3_PARAMETERS + 2;
 | |
| 			break;
 | |
| 		case SI5351_CLK4:
 | |
| 			reg_addr = SI5351_CLK4_PARAMETERS + 2;
 | |
| 			break;
 | |
| 		case SI5351_CLK5:
 | |
| 			reg_addr = SI5351_CLK5_PARAMETERS + 2;
 | |
| 			break;
 | |
| 		case SI5351_CLK6:
 | |
| 			reg_addr = SI5351_CLK6_7_OUTPUT_DIVIDER;
 | |
| 			break;
 | |
| 		case SI5351_CLK7:
 | |
| 			reg_addr = SI5351_CLK6_7_OUTPUT_DIVIDER;
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	reg_val = si5351_read(reg_addr);
 | |
| 
 | |
| 	if(clk <= (uint8_t)SI5351_CLK5)
 | |
| 	{
 | |
| 		// Clear the relevant bits
 | |
| 		reg_val &= ~(0x7c);
 | |
| 
 | |
| 		if(div_by_4 == 0)
 | |
| 		{
 | |
| 			reg_val &= ~(SI5351_OUTPUT_CLK_DIVBY4);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			reg_val |= (SI5351_OUTPUT_CLK_DIVBY4);
 | |
| 		}
 | |
| 
 | |
| 		reg_val |= (r_div << SI5351_OUTPUT_CLK_DIV_SHIFT);
 | |
| 	}
 | |
| 	else if(clk == SI5351_CLK6)
 | |
| 	{
 | |
| 		// Clear the relevant bits
 | |
| 		reg_val &= ~(0x07);
 | |
| 
 | |
| 		reg_val |= r_div;
 | |
| 	}
 | |
| 	else if(clk == SI5351_CLK7)
 | |
| 	{
 | |
| 		// Clear the relevant bits
 | |
| 		reg_val &= ~(0x70);
 | |
| 
 | |
| 		reg_val |= (r_div << SI5351_OUTPUT_CLK_DIV_SHIFT);
 | |
| 	}
 | |
| 
 | |
| 	si5351_write(reg_addr, reg_val);
 | |
| }
 | |
| 
 | |
| uint8_t select_r_div(uint64_t *freq)
 | |
| {
 | |
| 	uint8_t r_div = SI5351_OUTPUT_CLK_DIV_1;
 | |
| 
 | |
| 	// Choose the correct R divider
 | |
| 	if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT) && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 2))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_128;
 | |
| 		*freq *= 128ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 2) && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 4))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_64;
 | |
| 		*freq *= 64ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 4) && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 8))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_32;
 | |
| 		*freq *= 32ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 8) && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 16))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_16;
 | |
| 		*freq *= 16ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 16) && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 32))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_8;
 | |
| 		*freq *= 8ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 32) && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 64))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_4;
 | |
| 		*freq *= 4ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 64) && (*freq < SI5351_CLKOUT_MIN_FREQ * SI5351_FREQ_MULT * 128))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_2;
 | |
| 		*freq *= 2ULL;
 | |
| 	}
 | |
| 
 | |
| 	return r_div;
 | |
| }
 | |
| 
 | |
| uint8_t select_r_div_ms67(uint64_t *freq)
 | |
| {
 | |
| 	uint8_t r_div = SI5351_OUTPUT_CLK_DIV_1;
 | |
| 
 | |
| 	// Choose the correct R divider
 | |
| 	if((*freq >= SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT) && (*freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 2))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_128;
 | |
| 		*freq *= 128ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 2) && (*freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 4))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_64;
 | |
| 		*freq *= 64ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 4) && (*freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 8))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_32;
 | |
| 		*freq *= 32ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 8) && (*freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 16))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_16;
 | |
| 		*freq *= 16ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 16) && (*freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 32))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_8;
 | |
| 		*freq *= 8ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 32) && (*freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 64))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_4;
 | |
| 		*freq *= 4ULL;
 | |
| 	}
 | |
| 	else if((*freq >= SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 64) && (*freq < SI5351_CLKOUT67_MIN_FREQ * SI5351_FREQ_MULT * 128))
 | |
| 	{
 | |
| 		r_div = SI5351_OUTPUT_CLK_DIV_2;
 | |
| 		*freq *= 2ULL;
 | |
| 	}
 | |
| 
 | |
| 	return r_div;
 | |
| }
 | |
| 
 | |
| // Rebuild functions for STM32
 | |
| 
 | |
| /** Write multiple bytes
 | |
|  * @param regAddr Register address to write to
 | |
|  * @param length Count Bytes
 | |
|  * @param data Value to write
 | |
|  * @return Status of operation (true = success)
 | |
|  */
 | |
| uint8_t si5351_write_bulk(uint8_t regAddr, uint8_t length, uint8_t *data) {
 | |
|   HAL_StatusTypeDef status = HAL_I2C_Mem_Write(hi2c, i2c_bus_addr << 1, regAddr, I2C_MEMADD_SIZE_8BIT, data, sizeof(uint8_t)*length, 500);
 | |
| 
 | |
|   return status == HAL_OK;
 | |
| }
 | |
| 
 | |
| /** Write single byte to an 8-bit device register.
 | |
|  * @param regAddr Register address to write to
 | |
|  * @param data New word value to write
 | |
|  * @return Status of operation (true = success)
 | |
|  */
 | |
| uint8_t si5351_write(uint8_t regAddr, uint8_t data) {
 | |
|   HAL_StatusTypeDef status = HAL_I2C_Mem_Write(hi2c, i2c_bus_addr << 1, regAddr, I2C_MEMADD_SIZE_8BIT, &data, sizeof data, 500);
 | |
|   return status == HAL_OK;
 | |
| }
 | |
| 
 | |
| uint8_t si5351_read(uint8_t regAddr) {
 | |
|   uint8_t reg_val = 0;
 | |
| 
 | |
|   HAL_I2C_Master_Transmit(hi2c, i2c_bus_addr << 1, ®Addr, 1, 100);
 | |
| 
 | |
|   if (HAL_I2C_Master_Receive(hi2c, i2c_bus_addr << 1, ®_val, sizeof(uint8_t), 100) == HAL_OK) {
 | |
|     return reg_val;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | 
