first commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/* -*- 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 "osc_interface.h"
|
||||
#include "uhsdr_board.h"
|
||||
#include "osc_si570.h"
|
||||
#include "osc_si5351a.h"
|
||||
#include "soft_tcxo.h"
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Local Oscillator
|
||||
// ------------------
|
||||
static void OscDummy_Init(void);
|
||||
|
||||
const OscillatorInterface_t* osc;
|
||||
|
||||
static bool OscDummy_IsPresent(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static void OscDummy_SetPPM(float32_t ppm)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static Oscillator_ResultCodes_t OscDummy_PrepareNextFrequency(ulong freq, int temp_factor)
|
||||
{
|
||||
return OSC_OK;
|
||||
}
|
||||
static Oscillator_ResultCodes_t OscDummy_ChangeToNextFrequency(void)
|
||||
{
|
||||
return OSC_OK;
|
||||
}
|
||||
static bool OscDummy_IsNextStepLarge(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @brief Checks if all oscillator resources are available for switching frequency
|
||||
* 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
|
||||
*/
|
||||
static bool OscDummy_ReadyForIrqCall(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t OscDummy_getMinFrequency(void)
|
||||
{
|
||||
return 1; // 1 Hz
|
||||
}
|
||||
|
||||
static uint32_t OscDummy_getMaxFrequency(void)
|
||||
{
|
||||
return UINT32_MAX-1;
|
||||
}
|
||||
|
||||
|
||||
const OscillatorInterface_t osc_dummy =
|
||||
{
|
||||
.init = OscDummy_Init,
|
||||
.isPresent = OscDummy_IsPresent,
|
||||
.setPPM = OscDummy_SetPPM,
|
||||
.prepareNextFrequency = OscDummy_PrepareNextFrequency,
|
||||
.changeToNextFrequency = OscDummy_ChangeToNextFrequency,
|
||||
.isNextStepLarge = OscDummy_IsNextStepLarge,
|
||||
.readyForIrqCall = OscDummy_ReadyForIrqCall,
|
||||
.name = "Dummy",
|
||||
.type = OSC_DUMMY,
|
||||
.getMinFrequency = OscDummy_getMinFrequency,
|
||||
.getMaxFrequency = OscDummy_getMaxFrequency,
|
||||
};
|
||||
|
||||
static void OscDummy_Init()
|
||||
{
|
||||
osc = &osc_dummy;
|
||||
}
|
||||
|
||||
void Osc_Init()
|
||||
{
|
||||
#ifdef USE_OSC_SI5351A
|
||||
if (osc == NULL)
|
||||
{
|
||||
Si5351a_Init();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_OSC_SI570
|
||||
if (osc == NULL)
|
||||
{
|
||||
Si570_Init();
|
||||
}
|
||||
#endif
|
||||
if (osc == NULL)
|
||||
{
|
||||
OscDummy_Init();
|
||||
}
|
||||
|
||||
SoftTcxo_Init();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/* -*- 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 **
|
||||
************************************************************************************/
|
||||
|
||||
#ifndef __OSC_INTERFACE
|
||||
#define __OSC_INTERFACE
|
||||
|
||||
#include "uhsdr_types.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
OSC_OK = 0, // tuning ok
|
||||
OSC_TUNE_LIMITED, // tuning to freq close to desired freq, still ok
|
||||
OSC_TUNE_IMPOSSIBLE, // did not tune, tune freq unknown
|
||||
OSC_COMM_ERROR, // could not talk to Si570, tune freq unknown
|
||||
OSC_ERROR_VERIFY, // register do not match, tune freq unknown
|
||||
OSC_LARGE_STEP, // did not tune, just checking
|
||||
|
||||
} Oscillator_ResultCodes_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
OSC_UNKNOWN,
|
||||
OSC_DUMMY,
|
||||
OSC_SI570,
|
||||
OSC_SI5351A,
|
||||
} Oscillator_Type_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// startup handling
|
||||
void (*init)();
|
||||
|
||||
// presence information
|
||||
bool (*isPresent)();
|
||||
// startup/runtime reconfiguration
|
||||
void (*setPPM)(float32_t ppm);
|
||||
|
||||
// normal operations interface
|
||||
// freq is given in Hz, is QSD mixing frequency,
|
||||
// internally multiplied by 4 for Johnson Counter clock counters if needed by circuit
|
||||
Oscillator_ResultCodes_t (*prepareNextFrequency)(ulong freq, int temp_factor);
|
||||
Oscillator_ResultCodes_t (*changeToNextFrequency)();
|
||||
bool (*isNextStepLarge)();
|
||||
bool (*readyForIrqCall)();
|
||||
uint32_t (*getMinFrequency)();
|
||||
uint32_t (*getMaxFrequency)();
|
||||
const char* name;
|
||||
const Oscillator_Type_t type;
|
||||
|
||||
} OscillatorInterface_t;
|
||||
|
||||
extern const OscillatorInterface_t *osc;
|
||||
|
||||
void Osc_Init();
|
||||
#endif
|
||||
@@ -0,0 +1,481 @@
|
||||
/* -*- 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
|
||||
@@ -0,0 +1,22 @@
|
||||
/* -*- 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 **
|
||||
************************************************************************************/
|
||||
|
||||
#ifndef __HW_SI5351A
|
||||
#define __HW_SI5351A
|
||||
#include "osc_interface.h"
|
||||
|
||||
void Si5351a_Init(void);
|
||||
bool Si5351a_IsPresent(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,780 @@
|
||||
/* -*- 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 "codec.h"
|
||||
//
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "uhsdr_hw_i2c.h"
|
||||
#include "osc_si570.h"
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Local Oscillator
|
||||
// ------------------
|
||||
/* There are two supported alternative choices for the Si570:
|
||||
USE_SI570_CMOS frequencies up to 160/220 Mhz, used in most boards
|
||||
USE_SI570_CGRADE frequencies up to 280 Mhz, used in Lapwing
|
||||
*/
|
||||
#ifndef RF_BRD_LAPWING
|
||||
#define USE_SI570_CMOS
|
||||
#else
|
||||
#define USE_SI570_CGRADE
|
||||
#endif
|
||||
|
||||
// The SI570 Min/Max frequencies id spec sheet
|
||||
// The "hard limits" frequencies below/above which the synthesizer cannot be adjusted or else the system may crash
|
||||
// Lower limits apply to all cases
|
||||
|
||||
#define SI570_MIN_FREQ 10000000 //10.0=2.5 MHz
|
||||
#define SI570_HARD_MIN_FREQ 3500000 // 3.5=0.875 MHz
|
||||
|
||||
#ifdef USE_SI570_CGRADE
|
||||
#define SI570_MAX_FREQ 280000000 // 280=70 Mhz
|
||||
#define SI570_HARD_MAX_FREQ 280000000 // 280=70 MHz
|
||||
#endif
|
||||
|
||||
#ifdef USE_SI570_CMOS
|
||||
#define SI570_MAX_FREQ 160000000 // 160=40 Mhz
|
||||
#define SI570_HARD_MAX_FREQ 220000000 // 220=55 MHz
|
||||
#endif
|
||||
|
||||
#define SI570_RECALL (1<<0)
|
||||
#define SI570_FREEZE_DCO (1<<4)
|
||||
#define SI570_FREEZE_M (1<<5)
|
||||
#define SI570_NEW_FREQ (1<<6)
|
||||
|
||||
#define SI570_REG_135 135
|
||||
#define SI570_REG_137 137
|
||||
|
||||
#define FACTORY_FXTAL 114.285
|
||||
|
||||
// VCO range
|
||||
#define FDCO_MAX 5670
|
||||
#define FDCO_MIN 4850
|
||||
|
||||
#define POW_2_28 268435456.0
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t hsdiv;
|
||||
uint8_t n1;
|
||||
float64_t fdco;
|
||||
float64_t rfreq;
|
||||
float64_t freq;
|
||||
} Si570_FreqConfig;
|
||||
|
||||
typedef struct OscillatorState
|
||||
{
|
||||
Si570_FreqConfig cur_config;
|
||||
Si570_FreqConfig next_config;
|
||||
|
||||
float64_t fxtal; // base fxtal value
|
||||
float64_t fxtal_ppm; // Frequency Correction of fxtal_calc
|
||||
float64_t fxtal_calc; // ppm corrected fxtal value
|
||||
|
||||
uint8_t cur_regs[6];
|
||||
|
||||
bool next_is_small;
|
||||
|
||||
float fout; // contains startup frequency info of Si570
|
||||
|
||||
unsigned short si570_address;
|
||||
|
||||
uint8_t base_reg;
|
||||
|
||||
bool present; // is a working Si570 present?
|
||||
} OscillatorState;
|
||||
|
||||
|
||||
#define SMOOTH_DELTA (0.0035)
|
||||
// Datasheet says 0.0035 == 3500PPM but there have been issues if we get close to that value.
|
||||
// to play it safe, we make the delta range a little smaller.
|
||||
// if you want to play with it, tune to the end of the 10m band, set 100 khz step width and dial around
|
||||
// sooner or later jumps with delta close to 0.0035 (actually a calculated delta of 0.00334) cause a "crash" of Si570
|
||||
|
||||
static const uchar hs_div[6] = {11, 9, 7, 6, 5, 4};
|
||||
static const float fdco_max = FDCO_MAX;
|
||||
static const float fdco_min = FDCO_MIN;
|
||||
|
||||
OscillatorState os;
|
||||
|
||||
/*
|
||||
* @brief Returns startup frequency value of Si570, call only after init of Si570
|
||||
*
|
||||
* @returns Startup frequency in Mhz
|
||||
*/
|
||||
//*----------------------------------------------------------------------------
|
||||
//* Function Name : ui_si570_setbits
|
||||
//* Object :
|
||||
//* Input Parameters :
|
||||
//* Output Parameters :
|
||||
//* Functions called :
|
||||
//*----------------------------------------------------------------------------
|
||||
static uchar Si570_SetBits(unsigned char original, unsigned char reset_mask, unsigned char new_val)
|
||||
{
|
||||
return ((original & reset_mask) | new_val);
|
||||
}
|
||||
|
||||
static uint16_t Si570_ReadRegisters(uint8_t* regs)
|
||||
{
|
||||
return UhsdrHw_I2C_ReadBlock(SI570_I2C, os.si570_address, os.base_reg, 1, regs, 6);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief reads Si570 registers and verifies match with local copy of settings
|
||||
* @returns SI570_OK if matching, SI570_I2C_ERROR if I2C is not working, SI570_ERROR otherwise
|
||||
*/
|
||||
static Oscillator_ResultCodes_t Si570_VerifyFrequencyRegisters(void)
|
||||
{
|
||||
Oscillator_ResultCodes_t retval = OSC_OK;
|
||||
uchar regs[6];
|
||||
|
||||
// Read all regs
|
||||
if (Si570_ReadRegisters(®s[0]))
|
||||
{
|
||||
retval = OSC_COMM_ERROR;
|
||||
}
|
||||
|
||||
if (retval == OSC_OK)
|
||||
{
|
||||
// Not working - need fix
|
||||
// memset(regs, 0, 6);
|
||||
// trx4m_hw_i2c_ReadData(os.si570_address, 7, regs, 5);
|
||||
|
||||
if(memcmp(regs, (uchar*)os.cur_regs, 6) != 0)
|
||||
{
|
||||
retval = OSC_ERROR_VERIFY;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static uint16_t Si570_SetRegisterBits(uint8_t si570_address, uint8_t regaddr ,uint8_t* reg_ptr,uint8_t val){
|
||||
uint16_t retval = UhsdrHw_I2C_ReadRegister(SI570_I2C, si570_address, regaddr, 1, reg_ptr);
|
||||
if (retval == 0)
|
||||
{
|
||||
retval = UhsdrHw_I2C_WriteRegister(SI570_I2C, si570_address, regaddr, 1, (*reg_ptr|val));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
static uint16_t Si570_ClearRegisterBits(uint8_t si570_address, uint8_t regaddr ,uint8_t* reg_ptr,uint8_t val){
|
||||
uint16_t retval = UhsdrHw_I2C_ReadRegister(SI570_I2C, si570_address, regaddr, 1, reg_ptr);
|
||||
if (retval == 0)
|
||||
{
|
||||
retval = UhsdrHw_I2C_WriteRegister(SI570_I2C, si570_address, regaddr, 1, (*reg_ptr & ~val));
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//*----------------------------------------------------------------------------
|
||||
//* Function Name : ui_si570_small_frequency_change
|
||||
//* Object : small frequency changes handling
|
||||
//* Input Parameters :
|
||||
//* Output Parameters :
|
||||
//* Functions called :
|
||||
//*----------------------------------------------------------------------------
|
||||
static Oscillator_ResultCodes_t Si570_SmallFrequencyChange(void)
|
||||
{
|
||||
uint16_t ret;
|
||||
Oscillator_ResultCodes_t retval = OSC_OK;
|
||||
uchar reg_135;
|
||||
|
||||
// Read current
|
||||
ret = Si570_SetRegisterBits(os.si570_address, SI570_REG_135, ®_135, SI570_FREEZE_M);
|
||||
if (ret == 0)
|
||||
{
|
||||
// Write as block, registers 7-12
|
||||
ret = UhsdrHw_I2C_WriteBlock(SI570_I2C, os.si570_address, os.base_reg, 1, (uchar*)os.cur_regs, 6);
|
||||
if (ret == 0)
|
||||
{
|
||||
retval = Si570_VerifyFrequencyRegisters();
|
||||
}
|
||||
else
|
||||
{
|
||||
retval = OSC_COMM_ERROR;
|
||||
}
|
||||
}
|
||||
Si570_ClearRegisterBits(os.si570_address, SI570_REG_135, ®_135, SI570_FREEZE_M);
|
||||
return retval;
|
||||
}
|
||||
|
||||
//*----------------------------------------------------------------------------
|
||||
//* Function Name : ui_si570_large_frequency_change
|
||||
//* Object : large frequency changes handling
|
||||
//* Input Parameters :
|
||||
//* Output Parameters :
|
||||
//* Functions called :
|
||||
//*----------------------------------------------------------------------------
|
||||
static Oscillator_ResultCodes_t Si570_LargeFrequencyChange(void)
|
||||
{
|
||||
uint16_t ret;
|
||||
uint8_t reg_135, reg_137;
|
||||
Oscillator_ResultCodes_t retval = OSC_COMM_ERROR;
|
||||
|
||||
if (Si570_SetRegisterBits(os.si570_address, SI570_REG_137, ®_137, SI570_FREEZE_DCO) == 0)
|
||||
{
|
||||
// Write as block, registers 7-12
|
||||
if(UhsdrHw_I2C_WriteBlock(SI570_I2C, os.si570_address, os.base_reg, 1, (uchar*)os.cur_regs, 6) == 0)
|
||||
{
|
||||
retval = Si570_VerifyFrequencyRegisters();
|
||||
}
|
||||
}
|
||||
|
||||
// no matter what happened, try to unfreeze the Si570
|
||||
ret = Si570_ClearRegisterBits(os.si570_address, SI570_REG_137, ®_137, SI570_FREEZE_DCO);
|
||||
|
||||
if (ret == 0 && retval == OSC_OK)
|
||||
{
|
||||
if (Si570_SetRegisterBits(os.si570_address, SI570_REG_135, ®_135, SI570_NEW_FREQ) == 0)
|
||||
{
|
||||
// Wait for action completed
|
||||
do
|
||||
{
|
||||
ret = UhsdrHw_I2C_ReadRegister(SI570_I2C, os.si570_address, SI570_REG_135, 1, ®_135);
|
||||
} while(ret == 0 && (reg_135 & SI570_NEW_FREQ));
|
||||
}
|
||||
}
|
||||
return ret!=0?OSC_COMM_ERROR:retval;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void Si570_ClearConfig(Si570_FreqConfig *in) {
|
||||
memset(in,0,sizeof(Si570_FreqConfig));
|
||||
}
|
||||
|
||||
|
||||
static void Si570_CopyConfig(Si570_FreqConfig *in, Si570_FreqConfig* out) {
|
||||
memcpy(out,in,sizeof(Si570_FreqConfig));
|
||||
}
|
||||
|
||||
static float64_t Si570_FDCO_InRange(float64_t fdco){
|
||||
return (fdco >= fdco_min && fdco <= fdco_max);
|
||||
}
|
||||
|
||||
static float64_t Si570_GetFDCOForFreq(float64_t new_freq, uint8_t n1, uint8_t hsdiv){
|
||||
return (new_freq * (float64_t)(n1 * hsdiv));
|
||||
}
|
||||
|
||||
static bool Si570_FindSmoothRFreqForFreq(const Si570_FreqConfig* cur_config, Si570_FreqConfig* new_config) {
|
||||
float64_t fdco = Si570_GetFDCOForFreq(new_config->freq, cur_config->n1, cur_config->hsdiv);
|
||||
bool retval = false;
|
||||
float64_t fdiff = (fdco - cur_config->fdco)/cur_config->fdco;
|
||||
|
||||
if (fdiff < 0.0)
|
||||
{
|
||||
fdiff = -fdiff;
|
||||
}
|
||||
|
||||
if (fdiff <= SMOOTH_DELTA && Si570_FDCO_InRange(fdco))
|
||||
{
|
||||
new_config->rfreq = fdco / (float64_t)os.fxtal_calc;
|
||||
new_config->fdco = cur_config->fdco; // since we do only a small step, our fdco remains the same, so that we can keep an eye on the +/-3500ppm rule
|
||||
new_config->n1 = cur_config->n1;
|
||||
new_config->hsdiv = cur_config->hsdiv;
|
||||
|
||||
retval = true;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static bool Si570_FindConfigForFreq(Si570_FreqConfig* config) {
|
||||
uchar i;
|
||||
uint16_t divider_max, curr_div;
|
||||
bool retval = false;
|
||||
|
||||
bool n1_found = false;
|
||||
uint8_t n1 = 1; // this is to shut up gcc 4.9.x regarding uninitalized variable
|
||||
uint8_t hsdiv;
|
||||
float64_t fdco;
|
||||
|
||||
divider_max = (ushort)floorf(fdco_max / config->freq);
|
||||
curr_div = (ushort)ceilf (fdco_min / config->freq);
|
||||
|
||||
// for each available divisor hsdiv we calculate the n1 range
|
||||
// and see if an acceptable n1 (1,all even numbers between 2..128)
|
||||
// is available for the given divisor.
|
||||
// this requires at most 12 float division
|
||||
// for frequencies in the range from 3,45 to 120Mhz at most
|
||||
for(i = 0; i < 6 && n1_found == false; i++)
|
||||
{
|
||||
hsdiv = hs_div[i];
|
||||
uint8_t n1_cand_min = ceilf((float)curr_div / (float)hsdiv);
|
||||
uint8_t n1_cand_max = floorf((float)divider_max / (float)hsdiv);
|
||||
|
||||
if ((n1_cand_max >= 1) && (n1_cand_min <= 128) ) {
|
||||
if (n1_cand_min <= 1)
|
||||
{
|
||||
n1 = 1;
|
||||
n1_found = true;
|
||||
} else {
|
||||
n1 = ((n1_cand_min+1)& ~1);
|
||||
// get the closest even number (towards higher numbers)
|
||||
if (n1 <= n1_cand_max) {
|
||||
n1_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (n1_found) {
|
||||
fdco = Si570_GetFDCOForFreq(config->freq,n1,hsdiv);
|
||||
if (Si570_FDCO_InRange(fdco)) {
|
||||
config->n1 =n1;
|
||||
config->hsdiv = hsdiv;
|
||||
config->fdco = fdco;
|
||||
config->rfreq = fdco / os.fxtal_calc;
|
||||
|
||||
retval = true;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static Oscillator_ResultCodes_t Si570_ConfigToRegs(Si570_FreqConfig* config, uint8_t regs[6]) {
|
||||
|
||||
uint32_t frac_bits;
|
||||
uint16_t whole;
|
||||
|
||||
uint8_t n1_regVal = config->n1 - 1;
|
||||
uint8_t hsdiv_regVal = config->hsdiv - 4;
|
||||
uint8_t i;
|
||||
// the written value is n1 - 1, hsdiv -4 according to the datasheet
|
||||
Oscillator_ResultCodes_t retval = OSC_OK;
|
||||
|
||||
for(i = 0; i < 6; i++)
|
||||
{
|
||||
regs[i] = 0;
|
||||
}
|
||||
|
||||
regs[0] = (hsdiv_regVal << 5);
|
||||
|
||||
regs[0] = Si570_SetBits(regs[0], 0xE0, (n1_regVal >> 2));
|
||||
regs[1] = (n1_regVal & 3) << 6;
|
||||
|
||||
whole = floorf(config->rfreq);
|
||||
frac_bits = floorf((config->rfreq - whole) * POW_2_28);
|
||||
|
||||
for(i = 5; i >= 3; i--)
|
||||
{
|
||||
regs[i] = frac_bits & 0xFF;
|
||||
frac_bits = frac_bits >> 8;
|
||||
}
|
||||
|
||||
regs[2] = Si570_SetBits(regs[2], 0xF0, (frac_bits & 0xF));
|
||||
regs[2] = Si570_SetBits(regs[2], 0x0F, (whole & 0xF) << 4);
|
||||
regs[1] = Si570_SetBits(regs[1], 0xC0, (whole >> 4) & 0x3F);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static Oscillator_ResultCodes_t Si570_WriteRegs(bool is_small) {
|
||||
|
||||
Oscillator_ResultCodes_t retval = OSC_OK;
|
||||
|
||||
if(is_small)
|
||||
{
|
||||
retval = Si570_SmallFrequencyChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
retval = Si570_LargeFrequencyChange();
|
||||
}
|
||||
if(retval == OSC_OK)
|
||||
{
|
||||
|
||||
// Verify second time - we might be transmitting, so
|
||||
// it is absolutely unacceptable to be on startup
|
||||
// SI570 frequency if any I2C error or chip reset occurs!
|
||||
retval = Si570_VerifyFrequencyRegisters();
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static Oscillator_ResultCodes_t Si570_PrepareChangeFrequency(float64_t new_freq)
|
||||
{
|
||||
Oscillator_ResultCodes_t retval = OSC_OK;
|
||||
Si570_FreqConfig* next_config_ptr = &os.next_config;
|
||||
Si570_FreqConfig* cur_config_ptr = &os.cur_config;
|
||||
|
||||
next_config_ptr->freq = new_freq;
|
||||
|
||||
os.next_is_small = Si570_FindSmoothRFreqForFreq(cur_config_ptr,next_config_ptr);
|
||||
|
||||
if (os.next_is_small == false && Si570_FindConfigForFreq(next_config_ptr) == false)
|
||||
{
|
||||
retval = OSC_TUNE_IMPOSSIBLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
retval = Si570_ConfigToRegs(next_config_ptr,os.cur_regs);
|
||||
}
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if the next prepared step will be a large one, requiring sound muting etc. Requires a call to Si570_PrepareNextFrequency to have correct information
|
||||
*/
|
||||
static bool Si570_IsNextStepLarge(void)
|
||||
{
|
||||
return os.next_is_small == false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief execute the prepared frequency change. May be called multiple times in case of I2C issues
|
||||
* @returns SI570_OK or SI570_I2C_ERROR if I2C communication failed (which can happen at very high I2C speeds). SI570_ERROR_VERIFY should never happen anymore.
|
||||
*/
|
||||
static Oscillator_ResultCodes_t Si570_ChangeToNextFrequency(void)
|
||||
{
|
||||
Oscillator_ResultCodes_t retval = OSC_OK;
|
||||
Si570_FreqConfig* next_config_ptr = &os.next_config;
|
||||
Si570_FreqConfig* cur_config_ptr = &os.cur_config;
|
||||
|
||||
retval = Si570_WriteRegs(os.next_is_small);
|
||||
|
||||
// TODO: remove this handling, since it was almost certainly caused
|
||||
// by a wrong interpretation of the data sheet regarding small steps.
|
||||
if (retval == OSC_ERROR_VERIFY && os.next_is_small == true)
|
||||
{
|
||||
//
|
||||
// sometimes the small change simply does not work
|
||||
// for unknown reasons, so we execute a large step
|
||||
// instead to recover.
|
||||
retval = Si570_WriteRegs(false);
|
||||
}
|
||||
|
||||
// If everything is fine, get on with remembering our current configuration
|
||||
if (retval == OSC_OK) {
|
||||
Si570_CopyConfig(next_config_ptr,cur_config_ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Si570_ClearConfig(cur_config_ptr);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// by DF8OE
|
||||
//
|
||||
// startupfrequency-subroutine
|
||||
static void Si570_CalcSufHelper(void)
|
||||
{
|
||||
uchar si_regs[6];
|
||||
int hs_div;
|
||||
int n1;
|
||||
float rsfreq;
|
||||
Si570_ReadRegisters(si_regs);
|
||||
// calculate startup frequency
|
||||
rsfreq = (float)((si_regs[5] + (si_regs[4] * 0x100) + (si_regs[3] * 0x10000) + (double)((double)si_regs[2] * (double)0x1000000) + (double)((double)(si_regs[1] & 0x3F) * (double)0x100000000)) / (double)POW_2_28);
|
||||
hs_div = (si_regs[0] & 0xE0) / 32 + 4;
|
||||
n1 = (si_regs[1] & 0xC0) / 64 + (si_regs[0] & 0x1F) *4 + 1;
|
||||
if (n1 %2 != 0 && n1 != 1)
|
||||
{
|
||||
n1++;
|
||||
}
|
||||
os.fout = roundf((1142850 * rsfreq) / (hs_div * n1)) / 10000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
float Si570_GetStartupFrequency()
|
||||
{
|
||||
return os.fout;
|
||||
}
|
||||
|
||||
uint8_t Si570_GetI2CAddress()
|
||||
{
|
||||
return os.si570_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a new PPM value AND corrects the internally used xtal frequency accordingly
|
||||
* @param ppm ppm value
|
||||
*/
|
||||
static void Si570_SetPPM(float32_t ppm)
|
||||
{
|
||||
os.fxtal_ppm = ppm;
|
||||
os.fxtal_calc = os.fxtal + (os.fxtal / (float64_t)1000000.0) * os.fxtal_ppm;
|
||||
if (Si570_PrepareChangeFrequency(os.cur_config.freq) == OSC_OK)
|
||||
{
|
||||
Si570_ChangeToNextFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t Si570_ResetConfiguration(void)
|
||||
{
|
||||
uint8_t retval = 0;
|
||||
|
||||
short res;
|
||||
|
||||
ulong rfreq_frac;
|
||||
ulong rfreq_int;
|
||||
|
||||
uchar hsdiv_curr;
|
||||
uchar n1_curr;
|
||||
|
||||
// Reset publics
|
||||
os.fxtal = FACTORY_FXTAL;
|
||||
os.present = false;
|
||||
|
||||
res = UhsdrHw_I2C_WriteRegister(SI570_I2C, os.si570_address, SI570_REG_135, 1, SI570_RECALL);
|
||||
if(res != 0)
|
||||
{
|
||||
retval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t ret;
|
||||
int i = 0;
|
||||
do
|
||||
{
|
||||
res = UhsdrHw_I2C_ReadRegister(SI570_I2C, os.si570_address, SI570_REG_135, 1, &ret);
|
||||
if(res != 0)
|
||||
{
|
||||
retval = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
if(i == 30)
|
||||
{
|
||||
retval = 3;
|
||||
break;
|
||||
}
|
||||
} while(ret & SI570_RECALL);
|
||||
|
||||
if (retval == 0 && Si570_ReadRegisters(&(os.cur_regs[0])) != 0)
|
||||
{
|
||||
retval = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
hsdiv_curr = ((os.cur_regs[0] & 0xE0) >> 5) + 4;
|
||||
|
||||
n1_curr = 1 + ((os.cur_regs[0] & 0x1F) << 2) + ((os.cur_regs[1] & 0xC0) >> 6);
|
||||
|
||||
|
||||
rfreq_int = (os.cur_regs[1] & 0x3F);
|
||||
rfreq_int = (rfreq_int << 4) + ((os.cur_regs[2] & 0xF0) >> 4);
|
||||
|
||||
rfreq_frac = (os.cur_regs[2] & 0x0F);
|
||||
rfreq_frac = (rfreq_frac << 8) + os.cur_regs[3];
|
||||
rfreq_frac = (rfreq_frac << 8) + os.cur_regs[4];
|
||||
rfreq_frac = (rfreq_frac << 8) + os.cur_regs[5];
|
||||
|
||||
float64_t rfreq = rfreq_int + (float64_t)rfreq_frac / POW_2_28;
|
||||
os.fxtal = ((float64_t)os.fout * (float64_t)(n1_curr *hsdiv_curr)) / rfreq;
|
||||
|
||||
Si570_SetPPM(os.fxtal_ppm);
|
||||
|
||||
os.cur_config.rfreq = rfreq;
|
||||
os.cur_config.n1 = n1_curr;
|
||||
os.cur_config.hsdiv = hsdiv_curr;
|
||||
os.cur_config.fdco = Si570_GetFDCOForFreq(os.fout,n1_curr,hsdiv_curr);
|
||||
|
||||
os.present = true;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static Oscillator_ResultCodes_t Si570_PrepareNextFrequency(ulong freq, int temp_factor);
|
||||
|
||||
/**
|
||||
* @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 safe to call oscillator functions in an interrupt
|
||||
*/
|
||||
bool Si570_ReadyForIrqCall()
|
||||
{
|
||||
return (SI570_I2C->Lock == HAL_UNLOCKED);
|
||||
}
|
||||
|
||||
static bool Oscillator_IsPresent(void)
|
||||
{
|
||||
return os.present;
|
||||
}
|
||||
|
||||
static uint32_t Si570_getMinFrequency(void)
|
||||
{
|
||||
#ifdef RF_BRD_LAPWING
|
||||
return 1240000000L;
|
||||
#else
|
||||
return SI570_HARD_MIN_FREQ/4;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t Si570_getMaxFrequency(void)
|
||||
{
|
||||
#ifdef RF_BRD_LAPWING
|
||||
return 1300000000L;
|
||||
#else
|
||||
return SI570_HARD_MAX_FREQ/4;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t Si570_translateExt2Osc(uint32_t freq)
|
||||
{
|
||||
#ifdef RF_BRD_LAPWING
|
||||
return freq - 1124500000L;
|
||||
#else
|
||||
return freq * 4;
|
||||
// frequency multiplied with 4 since we drive a johnson counter for phased clock generation
|
||||
#endif
|
||||
}
|
||||
const OscillatorInterface_t osc_si570 =
|
||||
{
|
||||
.init = Si570_Init,
|
||||
.isPresent = Oscillator_IsPresent,
|
||||
.setPPM = Si570_SetPPM,
|
||||
.prepareNextFrequency = Si570_PrepareNextFrequency,
|
||||
.changeToNextFrequency = Si570_ChangeToNextFrequency,
|
||||
.isNextStepLarge = Si570_IsNextStepLarge,
|
||||
.readyForIrqCall = Si570_ReadyForIrqCall,
|
||||
.name = "Si570",
|
||||
.type = OSC_SI570,
|
||||
.getMinFrequency = Si570_getMinFrequency,
|
||||
.getMaxFrequency = Si570_getMaxFrequency,
|
||||
};
|
||||
|
||||
|
||||
void Si570_Init()
|
||||
{
|
||||
|
||||
os.base_reg = 13; // first test with regs 13+ for 7ppm SI570
|
||||
uchar dummy;
|
||||
|
||||
// test for hardware address of SI570
|
||||
os.si570_address = (0x55 << 1);
|
||||
if(UhsdrHw_I2C_ReadRegister(SI570_I2C,os.si570_address, (os.base_reg),1, &dummy) != 0)
|
||||
{
|
||||
os.si570_address = (0x50 << 1);
|
||||
if(UhsdrHw_I2C_ReadRegister(SI570_I2C,os.si570_address, (os.base_reg),1, &dummy) != 0)
|
||||
{
|
||||
os.si570_address = (0x01 << 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (UhsdrHw_I2C_DeviceReady(SI570_I2C,os.si570_address) == HAL_OK)
|
||||
{
|
||||
// make sure everything is cleared and in initial state
|
||||
Si570_ResetConfiguration();
|
||||
Si570_CalcSufHelper();
|
||||
|
||||
if(os.fout > 39.2 && os.fout < 39.3)
|
||||
{
|
||||
// its a 20 or 50 ppm device, use regs 7+
|
||||
os.base_reg = 7;
|
||||
Si570_CalcSufHelper();
|
||||
}
|
||||
|
||||
// all known startup frequencies
|
||||
static const float suf_table[] =
|
||||
{
|
||||
10,
|
||||
10.356,
|
||||
14.05,
|
||||
14.1,
|
||||
15,
|
||||
16.0915,
|
||||
22.5792,
|
||||
34.285,
|
||||
56.32,
|
||||
63,
|
||||
76.8,
|
||||
100,
|
||||
122,
|
||||
125,
|
||||
156.25,
|
||||
0
|
||||
};
|
||||
// test if startup frequency is known
|
||||
for (int i = 0; suf_table[i] != 0; i++)
|
||||
{
|
||||
float test = os.fout - suf_table[i];
|
||||
if (test < 0)
|
||||
{
|
||||
test = -test;
|
||||
}
|
||||
if (test < 0.2)
|
||||
{
|
||||
os.fout = suf_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we wait a little and then reset again
|
||||
HAL_Delay(40);
|
||||
Si570_ResetConfiguration();
|
||||
}
|
||||
osc = os.present?&osc_si570:NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief prepares all necessary information for the next frequency change
|
||||
* @param freq frequency in Hz to which the LO should be tuned.
|
||||
* @param calib the calibration correction value for the real vs. data sheet frequency of the Si570.
|
||||
* @param temp_factor the SoftTCXO code calculates a temperature correct value which is used to make a virtual tcxo out of the Si570.
|
||||
*
|
||||
* @returns SI570_TUNE_IMPOSSIBLE if tuning to the desired frequency is not possible at all (multiple reasons), SI570_OK if it is possible and within spec, SI570_TUNE_LIMITED if possible but out of spec
|
||||
*/
|
||||
static Oscillator_ResultCodes_t Si570_PrepareNextFrequency(ulong freq, int temp_factor)
|
||||
{
|
||||
Oscillator_ResultCodes_t retval = OSC_TUNE_IMPOSSIBLE;
|
||||
|
||||
if (osc->isPresent() == true) {
|
||||
float64_t freq_calc, temp_scale;
|
||||
|
||||
freq_calc = Si570_translateExt2Osc(freq);
|
||||
|
||||
temp_scale = ((float64_t)temp_factor)/14000000.0;
|
||||
// calculate scaling factor for the temperature correction (referenced to 14.000 MHz)
|
||||
|
||||
freq_calc *= (1 + temp_scale); // rescale by temperature correction factor
|
||||
|
||||
// when tuning frequency is outside SI570 hard limits, don't do it
|
||||
if (freq_calc <= SI570_HARD_MAX_FREQ && freq_calc >= SI570_HARD_MIN_FREQ)
|
||||
{
|
||||
// tuning inside known working spec
|
||||
retval = Si570_PrepareChangeFrequency(freq_calc/((float64_t)1000000.0));
|
||||
if ((freq_calc > SI570_MAX_FREQ || freq_calc < SI570_MIN_FREQ))
|
||||
{
|
||||
// outside official spec but known to work
|
||||
if (retval == OSC_OK)
|
||||
{
|
||||
retval = OSC_TUNE_LIMITED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* -*- 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 **
|
||||
************************************************************************************/
|
||||
|
||||
#ifndef __HW_SI570
|
||||
#define __HW_SI570
|
||||
|
||||
#include "osc_interface.h"
|
||||
|
||||
void Si570_Init(void);
|
||||
|
||||
// non-critical device information reading
|
||||
float32_t Si570_GetStartupFrequency(void);
|
||||
uint8_t Si570_GetI2CAddress(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,329 @@
|
||||
/* -*- 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 **
|
||||
************************************************************************************/
|
||||
|
||||
//
|
||||
// SI570 frequency shift table
|
||||
// 0-100 degrees C range
|
||||
// shots from 20m band (56Mhz)
|
||||
//
|
||||
// This frequency compensation table referenced to 14.000 MHz by KA7OEI, 10/14 using
|
||||
// a GPS reference in a temperature-controlled environment, extrapolated below 17C and above
|
||||
// 70C.
|
||||
//
|
||||
// Temperature normalized to 43C, a temperature achieved very soon after power-up
|
||||
// with both the Si570 and the temperature sensor thermally bonded.
|
||||
//
|
||||
// Note: The exact frequency/temperature dependencies will likely vary from unit to unit
|
||||
// for each Si570, but the values below appear to approximately follow typical AT-cut
|
||||
// temperature-frequency curves.
|
||||
//
|
||||
#include "uhsdr_board.h"
|
||||
#include "soft_tcxo.h"
|
||||
#include "radio_management.h"
|
||||
#include "uhsdr_hw_i2c.h"
|
||||
|
||||
LoTcxo lo;
|
||||
|
||||
// -------------------------------------------------------------------------------------
|
||||
// Temperature sensor
|
||||
// ------------------
|
||||
#define MCP_ADDR (0x90)
|
||||
|
||||
// MCP registers
|
||||
#define MCP_TEMP (0x00)
|
||||
#define MCP_CONFIG (0x01)
|
||||
#define MCP_HYSTR (0x02)
|
||||
#define MCP_LIMIT (0x03)
|
||||
|
||||
// MCP CONFIG register bits
|
||||
#define MCP_ONE_SHOT (7)
|
||||
#define MCP_ADC_RES (5)
|
||||
#define MCP_FAULT_QUEUE (3)
|
||||
#define MCP_ALERT_POL (2)
|
||||
#define MCP_INT_MODE (1)
|
||||
#define MCP_SHUTDOWN (0)
|
||||
#define R_BIT (1)
|
||||
#define W_BIT (0)
|
||||
|
||||
#define MCP_ADC_RES_9 0
|
||||
#define MCP_ADC_RES_10 1
|
||||
#define MCP_ADC_RES_11 2
|
||||
#define MCP_ADC_RES_12 3
|
||||
|
||||
#define MCP_POWER_UP 0
|
||||
#define MCP_POWER_DOWN 1
|
||||
|
||||
static int32_t MCP9801_ConvExternalTemp(uint8_t *temp)
|
||||
{
|
||||
int32_t ts = 0;
|
||||
|
||||
ts = temp[1];
|
||||
|
||||
ts += (int8_t)temp[0] << 8;
|
||||
ts *= 10000;
|
||||
ts /= 256;
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @brief configures the MCP9801 external temperature sensor for monitoring Si570 temperature
|
||||
* @returns 0 if detected and working, 1 if not detected on I2C, 2 for other errors
|
||||
*/
|
||||
static uint8_t MCP9801_InitExternalTempSensor(void)
|
||||
{
|
||||
uint8_t config, retval = 0;
|
||||
uint16_t res;
|
||||
|
||||
// Read config reg
|
||||
res = UhsdrHw_I2C_ReadRegister(MCP_I2C, MCP_ADDR, MCP_CONFIG, 1, &config);
|
||||
if(res != 0)
|
||||
{
|
||||
retval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Modify resolution
|
||||
config &= ~(3 << MCP_ADC_RES);
|
||||
config |= (MCP_ADC_RES_12 << MCP_ADC_RES);
|
||||
|
||||
// Modify power mode
|
||||
config &= ~(1 << MCP_SHUTDOWN);
|
||||
config |= (MCP_POWER_UP << MCP_SHUTDOWN);
|
||||
|
||||
// Write config reg
|
||||
res = UhsdrHw_I2C_WriteRegister(MCP_I2C, MCP_ADDR, MCP_CONFIG, 1, config);
|
||||
if(res != 0)
|
||||
{
|
||||
retval = 2;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
static uint8_t MCP9801_ReadExternalTempSensor(int32_t *temp)
|
||||
{
|
||||
uint8_t data[2];
|
||||
uint8_t retval = 0;
|
||||
|
||||
|
||||
// Read temperature
|
||||
if(temp != NULL && UhsdrHw_I2C_ReadBlock(MCP_I2C, MCP_ADDR, MCP_TEMP, 1, data, 2) == 0)
|
||||
{
|
||||
*temp = MCP9801_ConvExternalTemp(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
retval = 2;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
#define TCXO_TBL_SIZE 100
|
||||
const short tcxo_table_20m[TCXO_TBL_SIZE] =
|
||||
{
|
||||
-165 // 0 C
|
||||
-162, // 1 C
|
||||
-160, // 2 C
|
||||
-157, // 3 C
|
||||
-155, // 4 C
|
||||
-152, // 5 C
|
||||
-150, // 6 C
|
||||
-148, // 7 C
|
||||
-144, // 8 C
|
||||
-142, // 9 C
|
||||
-136, // 10 C
|
||||
-132, // 11 C
|
||||
-130, // 12 C
|
||||
-129, // 13 C
|
||||
-126, // 14 C
|
||||
-121, // 15 C
|
||||
-116, // 16 C
|
||||
-112, // 17 C
|
||||
-108, // 18 C
|
||||
-103, // 19 C
|
||||
-99, // 20 C
|
||||
-95, // 21 C
|
||||
-90, // 22 C
|
||||
-86, // 23 C
|
||||
-81, // 24 C
|
||||
-75, // 25 C
|
||||
-70, // 26 C
|
||||
-66, // 27 C
|
||||
-61, // 28 C
|
||||
-57, // 29 C
|
||||
-53, // 30 C
|
||||
-46, // 31 C
|
||||
-42, // 32 C
|
||||
-38, // 33 C
|
||||
-32, // 34 C
|
||||
-28, // 35 C
|
||||
-25, // 36 C
|
||||
-20, // 37 C
|
||||
-17, // 38 C
|
||||
-13, // 39 C
|
||||
-9, // 40 C
|
||||
-5, // 41 C
|
||||
-2, // 42 C
|
||||
1, // 43 C
|
||||
4, // 44 C
|
||||
7, // 45 C
|
||||
10, // 46 C
|
||||
12, // 47 C
|
||||
15, // 48 C
|
||||
16, // 49 C
|
||||
18, // 50 C
|
||||
20, // 51 C
|
||||
21, // 52 C
|
||||
22, // 53 C
|
||||
23, // 54 C
|
||||
23, // 55 C
|
||||
24, // 56 C
|
||||
23, // 57 C
|
||||
23, // 58 C
|
||||
22, // 59 C
|
||||
21, // 60 C
|
||||
20, // 61 C
|
||||
18, // 62 C
|
||||
16, // 63 C
|
||||
14, // 64 C
|
||||
11, // 65 C
|
||||
7, // 66 C
|
||||
3, // 67 C
|
||||
-1, // 68 C
|
||||
-5, // 69 C
|
||||
-11,// 70 C
|
||||
-17, // 71 C
|
||||
-24, // 72 C
|
||||
-31, // 73 C
|
||||
-38, // 74 C
|
||||
-45, // 75 C
|
||||
-52, // 76 C
|
||||
-58, // 77 C
|
||||
-67, // 78 C
|
||||
-71, // 79 C
|
||||
-77, // 80 C
|
||||
-83, // 81 C
|
||||
-89, // 82 C
|
||||
-95, // 83 C
|
||||
-102, // 84 C
|
||||
-108, // 85 C
|
||||
-115, // 86 C
|
||||
-121, // 87 C
|
||||
-127, // 88 C
|
||||
-134, // 89 C
|
||||
-141, // 90 C
|
||||
-147, // 91 C
|
||||
-153, // 92 C
|
||||
-159, // 93 C
|
||||
-166, // 94 C
|
||||
-177, // 95 C
|
||||
-184, // 96 C
|
||||
-190, // 97 C
|
||||
-197, // 98 C
|
||||
-203 // 99 C
|
||||
};
|
||||
|
||||
void SoftTcxo_Init()
|
||||
{
|
||||
|
||||
lo.comp = 0;
|
||||
|
||||
// Temp sensor setup
|
||||
lo.sensor_present = MCP9801_InitExternalTempSensor() == 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @brief measure local oscillator temperature and calculates compensation value
|
||||
* @return true if the temperature value has been measured
|
||||
*/
|
||||
bool SoftTcxo_HandleLoTemperatureDrift()
|
||||
{
|
||||
int32_t temp = 0;
|
||||
int comp, comp_p;
|
||||
float dtemp, remain, t_index;
|
||||
uchar tblp;
|
||||
|
||||
uint8_t temp_mode = RadioManagement_TcxoGetMode();
|
||||
|
||||
bool retval = false;
|
||||
|
||||
// No need to process if no chip avail or tcxo is disabled
|
||||
if((lo.sensor_present == true) && RadioManagement_TcxoIsEnabled())
|
||||
{
|
||||
{
|
||||
// Get current temperature
|
||||
if(MCP9801_ReadExternalTempSensor(&temp) == 0)
|
||||
{
|
||||
// Get temperature from sensor with its maximum precision
|
||||
dtemp = (float)temp; // get temperature
|
||||
dtemp /= 10000; // convert to decimal degrees
|
||||
remain = truncf(dtemp); // get integer portion of temperature
|
||||
remain = dtemp - remain; // get fractional portion
|
||||
|
||||
// Compensate only if enabled
|
||||
if((temp_mode == TCXO_ON))
|
||||
{
|
||||
// Temperature to unsigned table pointer
|
||||
t_index = (uchar)((temp%1000000)/100000);
|
||||
t_index *= 10;
|
||||
t_index += (uchar)((temp%100000)/10000);
|
||||
|
||||
// Check for overflow - keep within the lookup table
|
||||
if((t_index < 0) || (t_index > 150)) // the temperature sensor function "wraps around" below zero
|
||||
{
|
||||
t_index = 0; // point at the bottom of the temperature table
|
||||
dtemp = 0; // zero out fractional calculations
|
||||
remain = 0;
|
||||
}
|
||||
else if(t_index > TCXO_TBL_SIZE - 2) // High temperature - limit to maximum
|
||||
{
|
||||
t_index = TCXO_TBL_SIZE - 2; // Point to (near) top of table
|
||||
dtemp = 0; // zero out fractional calculations
|
||||
remain = 0;
|
||||
}
|
||||
|
||||
tblp = (uchar)t_index; // convert to index
|
||||
|
||||
comp = tcxo_table_20m[tblp]; // get the first entry in the table
|
||||
comp_p = tcxo_table_20m[tblp + 1]; // get the next entry in the table to determine fraction of frequency step
|
||||
|
||||
comp_p = comp_p - comp; // // get frequency difference between the two steps
|
||||
|
||||
dtemp = (float)comp_p; // change it to float for the calculation
|
||||
dtemp *= remain; // get proportion of temperature difference between the two steps using the fraction
|
||||
|
||||
comp += (int)dtemp; // add the compensation value to the lower of the two frequency steps
|
||||
|
||||
// Change needed ?
|
||||
if(lo.comp != comp) // is it there a difference?
|
||||
{
|
||||
// Update frequency, without reflecting it on the LCD
|
||||
df.temp_factor = comp;
|
||||
df.temp_factor_changed = true;
|
||||
lo.comp = comp;
|
||||
}
|
||||
}
|
||||
// Refresh UI
|
||||
retval = true;
|
||||
lo.temp = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/* -*- 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 **
|
||||
************************************************************************************/
|
||||
|
||||
//
|
||||
// SI570 frequency shift table
|
||||
// 0-100 degrees C range
|
||||
// shots from 20m band (56Mhz)
|
||||
//
|
||||
// This frequency compensation table referenced to 14.000 MHz by KA7OEI, 10/14 using
|
||||
// a GPS reference in a temperature-controlled environment, extrapolated below 17C and above
|
||||
// 70C.
|
||||
//
|
||||
// Temperature normalized to 43C, a temperature achieved very soon after power-up
|
||||
// with both the Si570 and the temperature sensor thermally bonded.
|
||||
//
|
||||
// Note: The exact frequency/temperature dependencies will likely vary from unit to unit
|
||||
// for each Si570, but the values below appear to approximately follow typical AT-cut
|
||||
// temperature-frequency curves.
|
||||
//
|
||||
#include "uhsdr_types.h"
|
||||
|
||||
// LO temperature compensation
|
||||
typedef struct LoTcxo
|
||||
{
|
||||
// Current compensation value
|
||||
// loaded to LO
|
||||
int32_t comp;
|
||||
int32_t temp;
|
||||
|
||||
bool sensor_present;
|
||||
} LoTcxo;
|
||||
|
||||
|
||||
// ------------------------------------------------
|
||||
// LO Tcxo
|
||||
extern LoTcxo lo;
|
||||
|
||||
bool SoftTcxo_HandleLoTemperatureDrift(void);
|
||||
void SoftTcxo_Init(void);
|
||||
Reference in New Issue
Block a user