first commit

This commit is contained in:
2022-08-24 08:39:13 +02:00
commit e663300109
2491 changed files with 2231094 additions and 0 deletions
@@ -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,776 @@
/* -*- 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(&regs[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, &reg_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, &reg_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, &reg_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, &reg_137, SI570_FREEZE_DCO);
if (ret == 0 && retval == OSC_OK)
{
if (Si570_SetRegisterBits(os.si570_address, SI570_REG_135, &reg_135, SI570_NEW_FREQ) == 0)
{
// Wait for action completed
do
{
ret = UhsdrHw_I2C_ReadRegister(SI570_I2C, os.si570_address, SI570_REG_135, 1, &reg_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_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);