UHSDR/UHSDR-active-devel/mchf-eclipse/drivers/ui/oscillator/osc_si5351a.c
2022-11-08 16:13:55 +01:00

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