482 lines
15 KiB
C
482 lines
15 KiB
C
/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */
|
|
/************************************************************************************
|
|
** **
|
|
** UHSDR **
|
|
** a powerful firmware for STM32 based SDR transceivers **
|
|
** **
|
|
**---------------------------------------------------------------------------------**
|
|
** **
|
|
** File name: **
|
|
** Description: **
|
|
** Last Modified: **
|
|
** Licence: GNU GPLv3 **
|
|
************************************************************************************/
|
|
|
|
// Common
|
|
#include "uhsdr_board.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "uhsdr_hw_i2c.h"
|
|
#include "osc_si5351a.h"
|
|
|
|
#ifdef USE_OSC_SI5351A
|
|
// Will be removed and made a dynamic config element
|
|
#ifdef UI_BRD_OVI40
|
|
#define TEST_QUADRATURE
|
|
#endif
|
|
|
|
// reference oscillator xtal frequency
|
|
#define SI5351_XTAL_FREQ 27000000 // Crystal frequency
|
|
#define SI5351_MIN_PLL 405000000 // Min PLL frequency, officially it is 600 Mhz, but 405 seems to work just fine.
|
|
#define SI5351_MAX_PLL 900000000 // Max PLL frequency, officially it is 900 Mhz, tested with ~1168 Mhz
|
|
|
|
#define SI5351_MAX_DIVIDER 900
|
|
|
|
#define SI5351_MAX_DIVIDER_PHASE90 126
|
|
// Max value for a divider if we want 90 degree phased clock, must be even and <=127
|
|
#define SI5351_MIN_FREQ_PHASE90 ((SI5351_MIN_PLL/SI5351_MAX_DIVIDER_PHASE90)+1)
|
|
// Min PLL frequency / Max Divider for Phase 90 -> minimal frequency we can have 90 degree phase shift
|
|
|
|
// Register definitions
|
|
#define SI5351_CLK0_CONTROL 16
|
|
#define SI5351_CLK1_CONTROL 17
|
|
#define SI5351_CLK2_CONTROL 18
|
|
#define SI5351_SYNTH_PLL_A 26
|
|
#define SI5351_SYNTH_PLL_B 34
|
|
#define SI5351_SYNTH_MS_0 42
|
|
#define SI5351_SYNTH_MS_1 50
|
|
#define SI5351_SYNTH_MS_2 58
|
|
|
|
#define SI5351_CLK0_PHASE_OFFSET 165
|
|
#define SI5351_CLK1_PHASE_OFFSET 166
|
|
#define SI5351_CLK2_PHASE_OFFSET 167
|
|
#define SI5351_PLL_RESET 177
|
|
|
|
// R-division ratio definitions
|
|
#define SI5351_R_DIV_1 0b00000000
|
|
#define SI5351_R_DIV_2 0b00010000
|
|
#define SI5351_R_DIV_4 0b00100000
|
|
#define SI5351_R_DIV_8 0b00110000
|
|
#define SI5351_R_DIV_16 0b01000000
|
|
#define SI5351_R_DIV_32 0b01010000
|
|
#define SI5351_R_DIV_64 0b01100000
|
|
#define SI5351_R_DIV_128 0b01110000
|
|
#define SI5351_R_DIV_MASK 0b01110000
|
|
|
|
#define SI5351_DIV_BY_4 0b00001100
|
|
|
|
#define SI5351_CLK_SRC_PLL_A 0b00000000
|
|
#define SI5351_CLK_SRC_PLL_B 0b00100000
|
|
|
|
// Commands
|
|
#define SI5351_OUTPUT_OFF 0x80
|
|
#define SI5351_OUTPUT_ON 0x4F // see AN619 for details: this sets many options!
|
|
#define SI5351_PLLA_RESET 0x20
|
|
#define SI5351_PLLB_RESET 0x80
|
|
|
|
|
|
#define SI5351_I2C_WRITE 192 // I2C address for writing
|
|
#define SI5351_I2C_READ 193 // I2C address for reading
|
|
|
|
|
|
#define MAX_UINT20 1048575
|
|
|
|
|
|
static uint16_t Si5351a_WriteRegister(uint8_t reg, uint8_t val)
|
|
{
|
|
return UhsdrHw_I2C_WriteRegister(SI5351A_I2C, SI5351_I2C_WRITE, reg, 1, val);
|
|
}
|
|
|
|
static uint16_t Si5351a_WriteRegisters(uint8_t reg, uint8_t* val_p, uint32_t size)
|
|
{
|
|
return UhsdrHw_I2C_WriteBlock(SI5351A_I2C,SI5351_I2C_WRITE, reg, 1, val_p, size);
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
uint32_t frequency;
|
|
uint32_t pll_num;
|
|
uint32_t pll_denom;
|
|
uint32_t multisynth_divider;
|
|
uint8_t multisynth_rdiv;
|
|
uint32_t pll_mult;
|
|
bool phasedOutput;
|
|
bool pllreset;
|
|
|
|
} Si5351a_Config_t;
|
|
|
|
typedef struct
|
|
{
|
|
bool is_present;
|
|
Si5351a_Config_t current;
|
|
Si5351a_Config_t next;
|
|
uint32_t xtal_freq;
|
|
} Si5351a_State_t;
|
|
|
|
Si5351a_State_t si5351a_state;
|
|
|
|
|
|
// Set up PLL with mult, num and denom
|
|
// mult is 15...90
|
|
// num is 0...1048575
|
|
// denom is 0...1048575
|
|
static bool Si5351a_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
|
|
|
|
|
|
uint32_t fract = 128.0 * ((float32_t)num / (float32_t)denom);
|
|
|
|
P1 = 128 * (uint32_t)(mult) + fract - 512;
|
|
P2 = 128 * num - denom * fract;
|
|
P3 = denom;
|
|
|
|
uint8_t pll_data[8] =
|
|
{
|
|
(P3 & 0x0000FF00) >> 8,
|
|
P3 & 0x000000FF,
|
|
(P1 & 0x00030000) >> 16,
|
|
(P1 & 0x0000FF00) >> 8,
|
|
P1 & 0x000000FF,
|
|
((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16),
|
|
(P2 & 0x0000FF00) >> 8,
|
|
P2 & 0x000000FF
|
|
};
|
|
|
|
return Si5351a_WriteRegisters(pll, pll_data, 8) == HAL_OK;
|
|
}
|
|
|
|
|
|
// Set up MultiSynth with integer divider and R multisynth_divider
|
|
static bool Si5351a_SetupMultisynthInteger(uint8_t synth, uint32_t divider, uint8_t rDiv)
|
|
{
|
|
uint32_t P1; // Synth config register P1
|
|
uint32_t P2; // Synth config register P2
|
|
uint32_t P3; // Synth config register P3
|
|
|
|
P1 = 128 * divider - 512;
|
|
P2 = 0; // P2 = 0, P3 = 1 forces an integer value for the multisynth_divider
|
|
P3 = 1;
|
|
|
|
uint8_t synth_data[8] =
|
|
{
|
|
(P3 & 0x0000FF00) >> 8,
|
|
(P3 & 0x000000FF),
|
|
((P1 & 0x00030000) >> 16) | rDiv | (divider == 4?SI5351_DIV_BY_4:0),
|
|
(P1 & 0x0000FF00) >> 8,
|
|
(P1 & 0x000000FF),
|
|
((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16),
|
|
(P2 & 0x0000FF00) >> 8,
|
|
(P2 & 0x000000FF)
|
|
};
|
|
|
|
return Si5351a_WriteRegisters(synth, synth_data, 8) == HAL_OK;
|
|
}
|
|
|
|
static bool Si5351a_ValidateConfig(Si5351a_Config_t* config)
|
|
{
|
|
// we check against the data sheet and known limitations.
|
|
bool retval = (config->multisynth_divider == 4 || config->multisynth_divider == 6 || (config->multisynth_divider >= 8 && config->multisynth_divider <= SI5351_MAX_DIVIDER ));
|
|
|
|
|
|
if (retval == true && config->phasedOutput == true)
|
|
{
|
|
retval = config->multisynth_divider <= SI5351_MAX_DIVIDER_PHASE90 ;
|
|
|
|
}
|
|
|
|
// we check that only the relevant bits are set and no others, this will ensure no problems when setting the register using or.
|
|
// the value encodes the exponent of rdiv in the bits [6:4] , i.e. 0 -> 1, 7 -> 128
|
|
|
|
if (retval == true)
|
|
{
|
|
retval = (config->multisynth_rdiv & ~SI5351_R_DIV_MASK ) == 0;
|
|
|
|
}
|
|
|
|
if (retval == true)
|
|
{
|
|
retval = ((config->pll_mult >= 15 && config->pll_mult <= 90) && (config->pll_num <= MAX_UINT20) && (config->pll_denom <= MAX_UINT20));
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static bool Si5351a_CalculateConfigForDivider(Si5351a_Config_t* new_config, uint32_t divider)
|
|
{
|
|
|
|
uint32_t pllFreq = divider * new_config->frequency; // Calculate the pllFrequency: the multisynth_divider * desired output frequency
|
|
|
|
|
|
uint32_t l = pllFreq % si5351a_state.xtal_freq; // It has three parts:
|
|
float32_t f = l; // mult is an integer that must be in the range 15...90
|
|
f *= MAX_UINT20; // num and denom are the fractional parts, the numerator and denominator
|
|
f /= si5351a_state.xtal_freq; // each is 20 bits (range 0...1048575)
|
|
|
|
new_config->pll_mult = pllFreq / si5351a_state.xtal_freq; // Determine the multiplier to get to the required pllFrequency
|
|
new_config->pll_num = f; // the actual multiplier is pll_mult + pll_num / denom
|
|
new_config->pll_denom = MAX_UINT20; // For simplicity we set the denominator to the maximum 1048575
|
|
new_config->multisynth_divider = divider;
|
|
new_config->multisynth_rdiv = SI5351_R_DIV_1; // TODO: For lower frequencies we need to use R_DIV
|
|
return Si5351a_ValidateConfig(new_config);
|
|
}
|
|
|
|
static bool Si5351a_CalculateConfig(uint32_t frequency, Si5351a_Config_t* new_config, Si5351a_Config_t* cur_config)
|
|
{
|
|
bool retval = false;
|
|
new_config->frequency = frequency;
|
|
|
|
uint32_t divider_max = SI5351_MAX_PLL / frequency; // Calculate the division ratio
|
|
|
|
if ((new_config->phasedOutput == true || divider_max < 8) && divider_max % 2)
|
|
{
|
|
divider_max--; // Ensure an even integer division ratio
|
|
}
|
|
|
|
uint32_t divider_min = SI5351_MIN_PLL / frequency; // Calculate the division ratio
|
|
if (SI5351_MIN_PLL % frequency)
|
|
{
|
|
divider_min++;
|
|
}
|
|
|
|
if ( (new_config->phasedOutput == true || divider_min < 8) && divider_min % 2)
|
|
{
|
|
divider_min++; // Ensure an even integer division ratio
|
|
}
|
|
|
|
|
|
// In quadrature mode we cannot have dividers higher than SI5351_MAX_PHASE90_DIVIDER == 126
|
|
const uint32_t divider_limit = new_config->phasedOutput?SI5351_MAX_DIVIDER_PHASE90:SI5351_MAX_DIVIDER;
|
|
|
|
// reuse current divider if possible, theory is that we can get away without PLLRESET in this case
|
|
// TODO: Validate theory and validate need for skipping PLLRESET
|
|
if (cur_config->phasedOutput == new_config->phasedOutput && cur_config->multisynth_divider >= divider_min && cur_config->multisynth_divider <= divider_max)
|
|
{
|
|
retval = Si5351a_CalculateConfigForDivider(new_config, cur_config->multisynth_divider);
|
|
}
|
|
|
|
if ( retval == false && divider_max <= divider_limit)
|
|
{
|
|
retval = Si5351a_CalculateConfigForDivider(new_config, divider_max);
|
|
}
|
|
|
|
if (retval == false && divider_min >= 4)
|
|
{
|
|
retval = Si5351a_CalculateConfigForDivider(new_config, divider_min);
|
|
}
|
|
|
|
if (retval == false)
|
|
{
|
|
new_config->multisynth_divider = 0;
|
|
}
|
|
|
|
switch(ts.debug_si5351a_pllreset)
|
|
{
|
|
case 0:
|
|
new_config->pllreset = true;
|
|
break;
|
|
case 1:
|
|
new_config->pllreset = cur_config->multisynth_divider != new_config->multisynth_divider;
|
|
break;
|
|
case 2:
|
|
if (new_config->phasedOutput)
|
|
{
|
|
new_config->pllreset = cur_config->multisynth_divider != new_config->multisynth_divider;
|
|
}
|
|
break;
|
|
case 3:
|
|
new_config->pllreset = false;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static bool Si5351a_ApplyConfig(Si5351a_Config_t* config)
|
|
{
|
|
|
|
// Set up PLL A with the calculated multiplication ratio
|
|
bool result = Si5351a_SetupPLL(SI5351_SYNTH_PLL_A, config->pll_mult, config->pll_num, config->pll_denom);
|
|
|
|
// Set up MultiSynth divider 0, with the calculated divider.
|
|
// The final R division stage can divide by a power of two, from 1...128
|
|
// represented by constants SI5351_R_DIV1 to SI5351_R_DIV128 (see si5351a.h header file)
|
|
// If you want to output frequencies below 1MHz, you have to use the
|
|
// final R division stage
|
|
if (result == true)
|
|
{
|
|
if (config->phasedOutput)
|
|
{
|
|
result = Si5351a_SetupMultisynthInteger(SI5351_SYNTH_MS_0, config->multisynth_divider, config->multisynth_rdiv);
|
|
result &= Si5351a_SetupMultisynthInteger(SI5351_SYNTH_MS_1, config->multisynth_divider, config->multisynth_rdiv);
|
|
}
|
|
else
|
|
{
|
|
result = Si5351a_SetupMultisynthInteger(SI5351_SYNTH_MS_2, config->multisynth_divider, config->multisynth_rdiv);
|
|
}
|
|
|
|
}
|
|
|
|
// Reset the PLL. This causes a glitch in the output. For small changes to
|
|
// the parameters, you don't need to reset the PLL, and there is no glitch
|
|
// Finally switch on the CLK0 output (0x4F)
|
|
// and set the MultiSynth0 input to be PLL A
|
|
if (result == true)
|
|
{
|
|
// only if phased output is active, we need to care about phase offset of CLK1
|
|
if (config->phasedOutput)
|
|
{
|
|
Si5351a_WriteRegister(SI5351_CLK1_PHASE_OFFSET, config->multisynth_divider);
|
|
}
|
|
#ifdef IQ_CLOCK_DIV4_SIG
|
|
if (config->phasedOutput)
|
|
{
|
|
GPIO_ResetBits(IQ_CLOCK_DIV4_SIG_PIO,IQ_CLOCK_DIV4_SIG);
|
|
}
|
|
else
|
|
{
|
|
GPIO_SetBits(IQ_CLOCK_DIV4_SIG_PIO,IQ_CLOCK_DIV4_SIG);
|
|
}
|
|
#endif
|
|
|
|
// Phase of CLK0 and CLK2 are never changed after startup, so we don't set it.
|
|
|
|
Si5351a_WriteRegister( SI5351_CLK0_CONTROL, config->phasedOutput==true?SI5351_OUTPUT_ON:SI5351_OUTPUT_OFF);
|
|
Si5351a_WriteRegister( SI5351_CLK1_CONTROL, config->phasedOutput==true?SI5351_OUTPUT_ON:SI5351_OUTPUT_OFF);
|
|
Si5351a_WriteRegister( SI5351_CLK2_CONTROL, config->phasedOutput==false?SI5351_OUTPUT_ON:SI5351_OUTPUT_OFF);
|
|
|
|
if (config->pllreset)
|
|
{
|
|
Si5351a_WriteRegister( SI5351_PLL_RESET, SI5351_PLLA_RESET);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
bool Si5351a_IsPresent()
|
|
{
|
|
return si5351a_state.is_present;
|
|
}
|
|
static void Si5351a_SetPPM(float32_t ppm)
|
|
{
|
|
si5351a_state.xtal_freq = ((float64_t)SI5351_XTAL_FREQ) + (((float64_t)SI5351_XTAL_FREQ)*(float64_t)ppm/1000000.0);
|
|
}
|
|
|
|
static Oscillator_ResultCodes_t Si5351a_PrepareNextFrequency(uint32_t freq, int temp_factor)
|
|
#ifndef R928_PLUS // true M0NKA
|
|
{
|
|
UNUSED(temp_factor);
|
|
|
|
#ifdef TEST_QUADRATURE
|
|
// TODO: Replace this with a proper configurable switch point, the current limit is the minimum frequency we can do 90 degree phase
|
|
si5351a_state.next.phasedOutput = freq > SI5351_MIN_FREQ_PHASE90;
|
|
#else
|
|
si5351a_state.next.phasedOutput = false;
|
|
#endif
|
|
if (si5351a_state.next.phasedOutput == false)
|
|
{
|
|
freq *= 4;
|
|
// we are going to drive a johnson counter with 4x desired frequency
|
|
// to get two 1/4 clock aka 90 degrees phase shifted clocks with frequency freq
|
|
}
|
|
return Si5351a_CalculateConfig(freq, &si5351a_state.next, &si5351a_state.current) == true?OSC_OK:OSC_TUNE_IMPOSSIBLE;
|
|
}
|
|
#else // R928+ etc
|
|
{
|
|
si5351a_state.next.phasedOutput = freq > SI5351_MIN_FREQ_PHASE90;
|
|
freq *= 4;
|
|
return Si5351a_CalculateConfig(freq, &si5351a_state.next, &si5351a_state.current) == true?OSC_OK:OSC_TUNE_IMPOSSIBLE;
|
|
}
|
|
#endif
|
|
|
|
static Oscillator_ResultCodes_t Si5351a_ChangeToNextFrequency(void)
|
|
{
|
|
Oscillator_ResultCodes_t retval = OSC_COMM_ERROR;
|
|
if (Si5351a_ApplyConfig(&si5351a_state.next) == true)
|
|
{
|
|
memcpy(&si5351a_state.current, &si5351a_state.next, sizeof(si5351a_state.next));
|
|
retval = OSC_OK;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static bool Si5351a_IsNextStepLarge(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if all oscillator resources are available for switching frequency
|
|
* It basically checks if the I2C is currently in use
|
|
* This function must be called before changing the oscillator in interrupts
|
|
* otherwise deadlocks may happen
|
|
* @return true if it is safe to call oscillator functions in an interrupt
|
|
*/
|
|
bool Si5351a_ReadyForIrqCall()
|
|
{
|
|
return (SI5351A_I2C->Lock == HAL_UNLOCKED);
|
|
}
|
|
|
|
// FIXME: The limits assume a 4x johnson counter, not the
|
|
// also working internal QSD generation
|
|
|
|
static uint32_t Si5351a_getMinFrequency(void)
|
|
{
|
|
return SI5351_MIN_PLL / (SI5351_MAX_DIVIDER / 4);
|
|
}
|
|
|
|
static uint32_t Si5351a_getMaxFrequency(void)
|
|
{
|
|
// this is an experimental value
|
|
// outside the spec sheet but still
|
|
// most if not all Si5351a can do it.
|
|
return 290000000 / 4;
|
|
}
|
|
|
|
const OscillatorInterface_t osc_si5351a =
|
|
{
|
|
.init = Si5351a_Init,
|
|
.isPresent = Si5351a_IsPresent,
|
|
.setPPM = Si5351a_SetPPM,
|
|
.prepareNextFrequency = Si5351a_PrepareNextFrequency,
|
|
.changeToNextFrequency = Si5351a_ChangeToNextFrequency,
|
|
.isNextStepLarge = Si5351a_IsNextStepLarge,
|
|
.readyForIrqCall = Si5351a_ReadyForIrqCall,
|
|
.name = "Si5351a",
|
|
.type = OSC_SI5351A,
|
|
.getMinFrequency = Si5351a_getMinFrequency,
|
|
.getMaxFrequency = Si5351a_getMaxFrequency,
|
|
};
|
|
|
|
void Si5351a_Init()
|
|
{
|
|
si5351a_state.current.frequency = 0;
|
|
si5351a_state.next.frequency = 0;
|
|
si5351a_state.current.multisynth_divider = 0;
|
|
si5351a_state.next.multisynth_divider = 0;
|
|
|
|
si5351a_state.is_present = UhsdrHw_I2C_DeviceReady(SI5351A_I2C,SI5351_I2C_WRITE) == HAL_OK;
|
|
|
|
if (si5351a_state.is_present)
|
|
{
|
|
Si5351a_WriteRegister(SI5351_CLK0_PHASE_OFFSET, 0);
|
|
Si5351a_WriteRegister(SI5351_CLK1_PHASE_OFFSET, 0);
|
|
Si5351a_WriteRegister(SI5351_CLK2_PHASE_OFFSET, 0);
|
|
Si5351a_WriteRegister( SI5351_CLK0_CONTROL, SI5351_OUTPUT_OFF);
|
|
Si5351a_WriteRegister( SI5351_CLK1_CONTROL, SI5351_OUTPUT_OFF);
|
|
Si5351a_WriteRegister( SI5351_CLK2_CONTROL, SI5351_OUTPUT_OFF);
|
|
}
|
|
|
|
|
|
osc = Si5351a_IsPresent()?&osc_si5351a:NULL;
|
|
|
|
|
|
}
|
|
#endif
|