/* -*- 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 #include #include #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_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; }