/* -*- 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 #include "radio_management.h" #include "profiling.h" #include "adc.h" #include #include #include #include #include "uhsdr_hw_i2c.h" #include "freedv_uhsdr.h" // SI570 control #include "osc_interface.h" #include "codec.h" #include "audio_driver.h" #include "audio_management.h" #include "cat_driver.h" #include "ui_spectrum.h" #include "ui_configuration.h" #include "ui_menu.h" // for CONFIG_160M_FULL_POWER_ADJUST and Co. #include "ui_driver.h" // for SWR_MIN_CALC_POWER #include "config_storage.h" #include "cw_gen.h" #include "cw_decoder.h" #include "psk.h" #include "rtty.h" #include "uhsdr_digi_buffer.h" #include "audio_nr.h" #define SWR_SAMPLES_SKP 1 //5000 #define SWR_SAMPLES_CNT 5//10 #define SWR_ADC_FULL_SCALE 4095 // full scale of A/D converter (4095 = 10 bits) #define SWR_ADC_VOLT_REFERENCE 3.3 // NOMINAL A/D reference voltage. The PRECISE value is calibrated by a menu item! (Probably "FWD/REV ADC Cal.") // // coefficients for very low power (<75 milliwatt) power levels. Do NOT use this above approx. 0.07 volts input! // #define LOW_RF_PWR_COEFF_A -0.0338205168744131 // constant (offset) #define LOW_RF_PWR_COEFF_B 5.02584652062682 // "b" coefficient (for x) #define LOW_RF_PWR_COEFF_C -106.610490958242 // "c" coefficient (for x^2) #define LOW_RF_PWR_COEFF_D 853.156505329744 // "d" coefficient (for x^3) // // coefficients for higher power levels (>50 milliwatts). This is actually good down to 25 milliwatts or so. // #define HIGH_RF_PWR_COEFF_A 0.01209 //0.0120972709513557 // constant (offset) #define HIGH_RF_PWR_COEFF_B 0.8334 //0.833438917330908 // "b" coefficient (for x) #define HIGH_RF_PWR_COEFF_C 1.569 //1.56930042559198 // "c" coefficient (for x^2) #define LOW_POWER_CALC_THRESHOLD 0.05 // voltage from sensor below which we use the "low power" calculations, above // SWR/Power meter SWRMeter swrm; // ------------------------------------------------ // Frequency public DialFrequency df; // // Bands definition // // // We first define all individual band definitions static const BandInfo bi_2200m_all = { .tune = 135700, .size = 2100, .name = "2200m", BAND_MODE_2200}; static const BandInfo bi_630m_all = { .tune = 472000, .size = 7000, .name = "630m", BAND_MODE_630}; static const BandInfo bi_160m_all = { .tune = 1810000, .size = 190000, .name = "160m", BAND_MODE_160}; static const BandInfo bi_80m_r1 = { .tune = 3500000, .size = 300000, .name = "80m", BAND_MODE_80}; static const BandInfo bi_80m_r2 = { .tune = 3500000, .size = 500000, .name = "80m", BAND_MODE_80}; static const BandInfo bi_80m_r3 = { .tune = 3500000, .size = 400000, .name = "80m", BAND_MODE_80}; static const BandInfo bi_60m_gen = { .tune = 5250000, .size = 200000, .name = "60m", BAND_MODE_60}; static const BandInfo bi_40m_r1 = { .tune = 7000000, .size = 200000, .name = "40m", BAND_MODE_40}; static const BandInfo bi_40m_r2_3 = { .tune = 7000000, .size = 300000, .name = "40m", BAND_MODE_40}; static const BandInfo bi_30m_all = { .tune = 10100000, .size = 50000, .name = "30m", BAND_MODE_30}; static const BandInfo bi_20m_all = { .tune = 14000000, .size = 350000, .name = "20m", BAND_MODE_20}; static const BandInfo bi_17m_all = { .tune = 18068000, .size = 100000, .name = "17m", BAND_MODE_17}; static const BandInfo bi_15m_all = { .tune = 21000000, .size = 450000, .name = "15m", BAND_MODE_15}; static const BandInfo bi_12m_all = { .tune = 24890000, .size = 100000, .name = "12m", BAND_MODE_12}; static const BandInfo bi_10m_all = { .tune = 28000000, .size = 1700000, .name = "10m", BAND_MODE_10}; static const BandInfo bi_6m_r2_3 = { .tune = 50000000, .size = 4000000, .name = "6m", BAND_MODE_6}; static const BandInfo bi_4m_gen = { .tune = 70000000, .size = 500000, .name = "4m", BAND_MODE_4}; static const BandInfo bi_2m_r1 = { .tune = 144000000, .size = 2000000, .name = "2m", BAND_MODE_2}; static const BandInfo bi_2m_r2_3 = { .tune = 144000000, .size = 4000000, .name = "2m", BAND_MODE_2}; static const BandInfo bi_70cm_r1 = { .tune = 430000000, .size = 10000000, .name = "70cm", BAND_MODE_70}; static const BandInfo bi_70cm_r2_3 = { .tune = 430000000, .size = 20000000, .name = "70cm", BAND_MODE_70}; static const BandInfo bi_23cm_all = { .tune = 1240000000, .size = 60000000, .name = "23cm", BAND_MODE_23}; static const BandInfo bi_160m_r3_thai ={ .tune = 1800000, .size = 200000, .name = "160m", BAND_MODE_160}; static const BandInfo bi_80m_r3_thai = { .tune = 3500000, .size = 100000, .name = "80m", BAND_MODE_80}; static const BandInfo bi_2m_r3_thai = { .tune = 144000000, .size = 3000000, .name = "2m", BAND_MODE_2}; static const BandInfo bi_60m_rx = { .tune = 5250000, .size = 200000, .name = "60m", .band_mode = BAND_MODE_60, .rx_only = true }; static const BandInfo bi_gen_all = { .tune = 0, .size = 0, .name = "Gen", BAND_MODE_GEN}; // now we combine from the above defined bands the set of bands for each region // to be backwards compatible we provide the original set of bands which are // the maximum band size from the 3 different IARU regions // IMPORTANT: Right now the order of bands in the list is fixed to the order of BAND_MODE_xxx (low to high) // TODO: Make this list ordered by frequency static const BandInfo *bandInfo_combined[MAX_BAND_NUM] = { &bi_80m_r2, &bi_60m_gen, &bi_40m_r2_3, &bi_30m_all, &bi_20m_all, &bi_17m_all, &bi_15m_all, &bi_12m_all, &bi_10m_all, &bi_6m_r2_3, &bi_4m_gen, &bi_2m_r2_3, &bi_70cm_r2_3, &bi_23cm_all, &bi_2200m_all, &bi_630m_all, &bi_160m_all, &bi_gen_all, }; static const BandInfo* bandInfo_region1[MAX_BAND_NUM] = { &bi_80m_r1, &bi_60m_gen, // should cover all regions &bi_40m_r1, &bi_30m_all, &bi_20m_all, &bi_17m_all, &bi_15m_all, &bi_12m_all, &bi_10m_all, &bi_6m_r2_3, &bi_4m_gen, &bi_2m_r1, &bi_70cm_r1, &bi_23cm_all, &bi_2200m_all, &bi_630m_all, &bi_160m_all, &bi_gen_all, }; static const BandInfo* bandInfo_region2[MAX_BAND_NUM] = { &bi_80m_r2, &bi_60m_gen, // should cover all regions &bi_40m_r2_3, &bi_30m_all, &bi_20m_all, &bi_17m_all, &bi_15m_all, &bi_12m_all, &bi_10m_all, &bi_6m_r2_3, &bi_4m_gen, &bi_2m_r2_3, &bi_70cm_r2_3, &bi_23cm_all, &bi_2200m_all, &bi_630m_all, &bi_160m_all, &bi_gen_all, }; static const BandInfo* bandInfo_region3[MAX_BAND_NUM] = { &bi_80m_r3, &bi_60m_gen, // should cover all regions &bi_40m_r2_3, &bi_30m_all, &bi_20m_all, &bi_17m_all, &bi_15m_all, &bi_12m_all, &bi_10m_all, &bi_6m_r2_3, &bi_4m_gen, &bi_2m_r2_3, &bi_70cm_r2_3, &bi_23cm_all, &bi_2200m_all, &bi_630m_all, &bi_160m_all, &bi_gen_all, }; static const BandInfo* bandInfo_region3_thai[MAX_BAND_NUM] = { &bi_80m_r3_thai, &bi_60m_rx, // should cover all regions &bi_40m_r1, &bi_30m_all, &bi_20m_all, &bi_17m_all, &bi_15m_all, &bi_12m_all, &bi_10m_all, &bi_6m_r2_3, &bi_4m_gen, &bi_2m_r3_thai, &bi_70cm_r2_3, &bi_23cm_all, &bi_2200m_all, &bi_630m_all, &bi_160m_r3_thai, &bi_gen_all, }; // finally we list all of them in a table and give them names for the menu const BandInfoSet bandInfos[] = { { bandInfo_combined, "R1+2+3" }, { bandInfo_region1, "Region 1" }, { bandInfo_region2, "Region 2" }, { bandInfo_region3, "Region 3" }, { bandInfo_region3_thai, "Thailand" }, }; const int BAND_INFO_SET_NUM = sizeof(bandInfos)/sizeof(BandInfoSet); BandInfo_c **bandInfo = bandInfo_combined; uint8_t bandinfo_idx; // default init with 0 is fine /** * Searches the band info for a given band memory index * This relieves us from ordering the vfo band memories exactly like the * band infos. * * @param new_band_index * @return the band with the provided index or if this is not found, the current bandInfo */ const BandInfo* RadioManagement_GetBandInfo(uint8_t new_band_index) { const BandInfo* bi = ts.band; for (int idx = 0; idx < MAX_BANDS; idx ++) { if (bandInfo[idx]->band_mode == new_band_index) { bi = bandInfo[idx]; break; } } return bi; } // this structure MUST match the order of entries in power_level_t ! static const power_level_desc_t mchf_rf_power_levels[] = { { .id = PA_LEVEL_FULL, .mW = 0, }, // we use 0 to indicate max power { .id = PA_LEVEL_HIGH, .mW = 5000, }, { .id = PA_LEVEL_MEDIUM, .mW = 2000, }, { .id = PA_LEVEL_LOW, .mW = 1000, }, { .id = PA_LEVEL_MINIMAL,.mW = 500, }, }; const pa_power_levels_info_t mchf_power_levelsInfo = { .levels = mchf_rf_power_levels, .count = sizeof(mchf_rf_power_levels)/sizeof(*mchf_rf_power_levels), }; #ifdef RF_BRD_MCHF const pa_info_t mchf_pa = { .name = "mcHF PA", .reference_power = 5000.0, .max_freq = 55000000,//32000000, .min_freq = 1800000, .max_am_power = 2000, .max_power = 10000, }; #endif // RF_BRD_MCHF #ifdef RF_BRD_LAPWING const pa_info_t mchf_pa = { .name = "Lapwing PA", .reference_power = 5000.0, .max_freq = 1300 * 1000000, .min_freq = 1240 * 1000000, .max_am_power = 2000, .max_power = 20000, }; #endif // LAPWING // The following descriptor table has to be in the order of the enum digital_modes_t in radio_management.h // This table is stored in flash (due to const) and cannot be written to // for operational data per mode [r/w], use a different table with order of modes const digital_mode_desc_t digimodes[DigitalMode_Num_Modes] = { { "DIGITAL" , true }, #ifdef USE_FREEDV { "FreeDV" , true }, #endif { "RTTY" , true }, { "BPSK" , true }, }; static void RadioManagement_SetCouplingForFrequency(uint32_t freq); static void RadioManagement_SetHWFiltersForFrequency(uint32_t freq); /** * @brief returns the "real" frequency translation mode for a given transceiver state. This may differ from the configured one due to modulation demands * */ uint32_t RadioManagement_GetRealFreqTranslationMode(uint32_t txrx_mode, uint32_t dmod_mode, uint32_t iq_freq_mode) { uint32_t retval = iq_freq_mode; // if (dmod_mode == DEMOD_CW && txrx_mode == TRX_MODE_TX) // UB8JDC if (!(ts.expflags1 & EXPFLAGS1_CLEAR_TX_CW_RTTY_BPSK) && dmod_mode == DEMOD_CW && txrx_mode == TRX_MODE_TX) { retval = FREQ_IQ_CONV_MODE_OFF; } return retval; } /** * @brief permits to dis/enable a digital codec (or get back to analog) */ void RadioManagement_ChangeCodec(uint32_t codec, bool enableCodec) { // codec == 0 -> Analog Sound // all other codecs -> digital codec if (codec == 0) { ts.dvmode = false; } else { ts.dvmode = enableCodec; } ts.digital_mode = codec; } /** * Returns the scaling which needs to be applied to the standard signal level (which delivers the PA_REFERENCE_POWER) * in order to output the request power. * @param powerMw requested power in mW. mW =< 0.0 returns scale 1 * @return scaling (gain) */ float32_t RadioManagement_CalculatePowerFactorScale(float32_t powerMw) { float32_t retval = 1.0; #ifndef SDR_AMBER if (powerMw > 0) { retval = sqrtf(powerMw / mchf_pa.reference_power); } #else if(!ts.amber_dac_pwr_tx_present || !(ts.expflags2 & EXPFLAGS2_ALT_PWR_CTRL)) { if (powerMw > 0) { retval = sqrtf(powerMw / mchf_pa.reference_power); } } else { uint16_t vol = DAC_5W; if(powerMw == 0) { vol = DAC_5W; } // FULL else if(powerMw < 500) { vol = DAC_5MW; } else if(powerMw < 1000) { vol = DAC_05W; } else if(powerMw < 2000) { vol = DAC_1W; } else if(powerMw < 4000) { vol = DAC_2W; } else if(powerMw < 5000) { vol = DAC_4W; } else { vol = DAC_5W; } ts.amber_dac_pwr_tx_state = vol; ALT_RWP_CTRL_DAC_WriteReg(vol); } #endif return retval; } /** * * Depends on globals: ts.pwr_adj, ts.power_level, df.tune_old, ts.flags2 & FLAGS2_LOW_BAND_BIAS_REDUCE * Impacts globals: ts.tx_power_factor * @param band * @return true if the power factor value differs from previous */ static bool RadioManagement_SetBandPowerFactor(const BandInfo* band, int32_t power) { float32_t pf_bandvalue; // used as a holder for percentage of power output scaling // FIXME: This code needs fixing, the hack for TX Outside should at least reduce power factor for lower bands if (RadioManagement_IsGenericBand(band)) // we are outside a TX band { // TX outside bands **very dirty hack** // FIXME: calculate based on 2 frequency points close the selected frequency, should be inter-/extrapolated // uint32_t freq_min = RadioManagement_GetBandInfo(BAND_MODE_80)->tune; uint32_t freq_min = 3600000; // float32_t adj_min = ts.pwr_adj[ADJ_REF_PWR][BAND_MODE_80] / (RadioManagement_IsPowerFactorReduce(freq_min)? 400: 100); float32_t adj_min = ts.pwr_adj[ADJ_REF_PWR][BAND_MODE_80]; // adj_min /= RadioManagement_IsPowerFactorReduce(df.tune_old)? 400.0: 100.0; adj_min /= RadioManagement_IsPowerFactorReduce(df.tune_old)? (((ts.expflags3 & EXPFLAGS3_LOW_BAND_BIAS_REDUCE_MORE) && (df.tune_old < 8000000))? 2000.0 : 400.0): 100.0; // uint32_t freq_max = RadioManagement_GetBandInfo(BAND_MODE_10)->tune; uint32_t freq_max = 28600000; // float32_t adj_max = ts.pwr_adj[ADJ_REF_PWR][BAND_MODE_10] / (RadioManagement_IsPowerFactorReduce(freq_max)? 400: 100); float32_t adj_max = ts.pwr_adj[ADJ_REF_PWR][BAND_MODE_10]; // adj_max /= RadioManagement_IsPowerFactorReduce(df.tune_old)? 400.0: 100.0; adj_max /= RadioManagement_IsPowerFactorReduce(df.tune_old)? (((ts.expflags3 & EXPFLAGS3_LOW_BAND_BIAS_REDUCE_MORE) && (df.tune_old < 8000000))? 2000.0 : 400.0): 100.0; uint32_t t_freq = df.tune_old; t_freq = t_freq <= 3600000 ? 3600000 : t_freq; t_freq = t_freq >= 28600000 ? 28600000 : t_freq; // float32_t delta_f = (float32_t)df.tune_old - (float32_t)freq_min; // we must convert to a signed type float32_t delta_f = (float32_t)t_freq - (float32_t)freq_min; float32_t delta_points = freq_max - freq_min; float32_t freq_mult = delta_f / delta_points; pf_bandvalue = freq_mult * (adj_max - adj_min) + adj_min; pf_bandvalue = pf_bandvalue * ((ts.power_level == PA_LEVEL_FULL && RadioManagement_CBFullPwrEnabled())? ts.power_scale_gen_full : ts.power_scale_gen) / 100.0; } else { pf_bandvalue = ts.pwr_adj[power == 0?ADJ_FULL_POWER:ADJ_REF_PWR][band->band_mode]; // pf_bandvalue /= RadioManagement_IsPowerFactorReduce(df.tune_old)? 400: 100; pf_bandvalue /= RadioManagement_IsPowerFactorReduce(df.tune_old)? (((ts.expflags3 & EXPFLAGS3_LOW_BAND_BIAS_REDUCE_MORE) && (df.tune_old < 8000000))? 2000.0 : 400.0): 100.0; } float32_t power_factor_scale = 1.0 ; // now rescale to power levels below reference power (i.e for mcHF <5 watts) if necessary. // if (power != 0) // { power_factor_scale = RadioManagement_CalculatePowerFactorScale(power); // } float32_t power_factor = pf_bandvalue * power_factor_scale; // limit hard limit for power factor since it otherwise may overdrive the PA section const float32_t old_pf = ts.tx_power_factor; ts.tx_power_factor = (power_factor > TX_POWER_FACTOR_MAX_INTERNAL) ? TX_POWER_FACTOR_MAX_INTERNAL : power_factor; ts.power_modified |= (power_factor == 0 || ts.tx_power_factor != power_factor); return ts.tx_power_factor != old_pf; } /** * Is the currently active modulation / transceiver mode able to generate a side tone during transmit * Can be called during RX and TX * * @return true if the selected modulation mode permits the use of a side tone */ bool RadioManagement_UsesTxSidetone() { return ts.dmod_mode == DEMOD_CW || is_demod_rtty() || is_demod_psk() || (ts.tune && !ts.iq_freq_mode); } /** * @brief API Function, implements application logic for changing the power level including filter changes * * * @param power_level The requested power level (as PA_LEVEL constants). Invalid values are not accepted * @returns true if power level has been changed, false otherwise */ bool RadioManagement_SetPowerLevel(const BandInfo* band, power_level_t power_level) { bool retval = false; bool power_modified = false; uint32_t t_freq = df.tune_new;//ts.tune_freq; bool HereIsEnableCBband = false; int32_t power = power_level < mchf_power_levelsInfo.count ? mchf_power_levelsInfo.levels[power_level].mW : -1; ts.band_index = band->band_mode; // Cross beep if(ts.expflags2 & EXPFLAGS2_BEEP_GEN) { if (RadioManagement_IsGenericBand(band)) { if(ts.it_is_band) { AudioManagement_KeyBeep(); ts.it_is_band = false; } } else { if(!ts.it_is_band) { AudioManagement_KeyBeep(); ts.it_is_band = true; } } } if (power != -1 && band != NULL) { if (RadioManagement_IsGenericBand(band)) // { // if(ts.flags1 & FLAGS1_TX_OUTSIDE_BANDS) // { // power = 50; // ~50 mW limit; // power_modified = true; // // I never will use this function (DF8OE) // } // else // { // power = 5; // 5mW, use very low value in case of wrong call to this function // power_modified = true; // } // } // // if(ts.dmod_mode == DEMOD_AM) // in AM mode? // { // if(power > mchf_pa.max_am_power || power == 0) // yes, power over am limits? // { // power = mchf_pa.max_am_power; // force to keep am limits // power_modified = true; // } // } // else if(power > mchf_pa.reference_power) // { // power = 0; // 0 == full power // power_level = PA_LEVEL_FULL; // } { // HereIsEnableCBband = ((ts.expflags1 & EXPFLAGS1_CB_27MC_TX_ENABLE) && (t_freq >= 26960000) && (t_freq <= 27860000)) || ((ts.expflags1 & EXPFLAGS1_CB_26MC_TX_ENABLE) && (t_freq >= 25670000) && (t_freq <= 26100000)); // ts.HereIsEnableCB26Mc = ((ts.expflags1 & EXPFLAGS1_CB_26MC_TX_ENABLE) && (t_freq >= 25670000) && (t_freq <= 26100000)); ts.HereIsEnableCB26Mc = ((ts.expflags1 & EXPFLAGS1_CB_26MC_TX_ENABLE) && (t_freq >= 25670000) && (t_freq < 26960000)); // ts.HereIsEnableCB27Mc = ((ts.expflags1 & EXPFLAGS1_CB_27MC_TX_ENABLE) && (t_freq >= 26960000) && (t_freq <= 27860000)); ts.HereIsEnableCB27Mc = ((ts.expflags1 & EXPFLAGS1_CB_27MC_TX_ENABLE) && (t_freq >= 26960000) && (t_freq <= 27991250)); HereIsEnableCBband = ts.HereIsEnableCB26Mc || ts.HereIsEnableCB27Mc; // if (HereIsEnableCBband && ((ts.dmod_mode == DEMOD_AM) || (ts.dmod_mode == DEMOD_FM) || (ts.dmod_mode == DEMOD_USB))) // We are working in enable CB band & mode if (!(ts.flags1 & FLAGS1_TX_OUTSIDE_BANDS) && (HereIsEnableCBband && ((ts.dmod_mode == DEMOD_AM) || (ts.dmod_mode == DEMOD_FM) || (ts.dmod_mode == DEMOD_USB) || (ts.dmod_mode == DEMOD_CW)))) // We are working in enable CB band & mode { if (ts.expflags1 & EXPFLAGS1_CB_10W_TX_ENABLE) // enabled any mode 10W { // no restrictions } // if((ts.dmod_mode == DEMOD_AM) || (ts.dmod_mode == DEMOD_FM)) // if((ts.dmod_mode == DEMOD_AM) || (ts.dmod_mode == DEMOD_FM) || (ts.dmod_mode == DEMOD_CW)) else if((ts.dmod_mode == DEMOD_AM) || (ts.dmod_mode == DEMOD_FM) || (ts.dmod_mode == DEMOD_CW)) { if (power > 4000.0 || power == 0) { power = 4000; // 4W limit; power_modified = true; } } else if ((ts.dmod_mode == DEMOD_USB) && !(ts.expflags1 & EXPFLAGS1_CB_12W_SSB_TX_ENABLE)) { if (power > 4000.0 || power == 0) { power = 4000; // 4W limit; power_modified = true; } } } else { if(ts.flags1 & FLAGS1_TX_OUTSIDE_BANDS) { // nothing to do //#ifndef OPEN_TX_OUTBAND // ================================================= // power = 50; // ~50 mW limit; // power_modified = true; //#else // =================================================================== // if (power > 5000.0 || power == 0) // { // power = 5000; // 5W limit; // } //#endif //=================================================================== // I never will use this function (DF8OE) } else { power = 5; // 5mW, use very low value in case of wrong call to this function power_modified = true; } } } if (!HereIsEnableCBband) { if (ts.dmod_mode == DEMOD_AM) // in AM mode? { if(power > mchf_pa.max_am_power || power == 0) // yes, power over am limits? { power = mchf_pa.max_am_power; // force to keep am limits power_modified = true; } } else if(power > mchf_pa.reference_power) { power = 0; // 0 == full power power_level = PA_LEVEL_FULL; } } // Calculate TX power factor - see if power changed bool pf_change = RadioManagement_SetBandPowerFactor(band, power); if (pf_change == true || power != ts.power || ts.power_level != power_level || ts.power_modified != power_modified) { retval = true; ts.power_level = power_level; ts.power = power; ts.power_modified = power_modified; if (RadioManagement_UsesTxSidetone()) { Codec_TxSidetoneSetgain(ts.txrx_mode); } } } return retval; } bool RadioManagement_CBFullPwrEnabled() { bool retval = false; uint32_t t_freq = df.tune_new; /* bool HereIsEnableCBband = ((ts.expflags1 & EXPFLAGS1_CB_26MC_TX_ENABLE) && (t_freq >= 25670000) && (t_freq <= 26100000)) || \ ((ts.expflags1 & EXPFLAGS1_CB_27MC_TX_ENABLE) && (t_freq >= 26960000) && (t_freq <= 27860000)); */ bool HereIsEnableCBband = ((ts.expflags1 & EXPFLAGS1_CB_26MC_TX_ENABLE) && (t_freq >= 25670000) && (t_freq < 26960000)) || \ ((ts.expflags1 & EXPFLAGS1_CB_27MC_TX_ENABLE) && (t_freq >= 26960000) && (t_freq <= 27991250)); if(HereIsEnableCBband) { if ((ts.expflags1 & EXPFLAGS1_CB_10W_TX_ENABLE) || ((ts.dmod_mode == DEMOD_USB) && (ts.expflags1 & EXPFLAGS1_CB_12W_SSB_TX_ENABLE))) { retval = true; } } return retval; } bool RadioManagement_Tune(bool tune) { bool retval = tune; if(RadioManagement_IsTxDisabled() == false && (ts.dmod_mode != DEMOD_SAM)) { if(tune) { if(ts.tune_power_level != PA_LEVEL_TUNE_KEEP_CURRENT) { ts.power_temp = ts.power_level; //store tx level and set tune level ts.power_level = ts.tune_power_level; } RadioManagement_SwitchTxRx(TRX_MODE_TX,true); // tune ON, this will also update the power to use our // to our temporarily changed level retval = (ts.txrx_mode == TRX_MODE_TX); } else { RadioManagement_SwitchTxRx(TRX_MODE_RX,true); // tune OFF if(ts.tune_power_level != PA_LEVEL_TUNE_KEEP_CURRENT) { RadioManagement_SetPowerLevel(RadioManagement_GetBand(df.tune_new), ts.power_temp); } retval = (ts.txrx_mode == TRX_MODE_TX); // no longer tuning } } else { retval = false; // no TUNE mode in AM or FM or with disabled TX! } return retval; } /** * @returns offset of tuned frequency to dial frequency in CW mode, in Hz */ int32_t RadioManagement_GetCWDialOffset() { int32_t retval = 0; switch(ts.cw_offset_mode) { case CW_OFFSET_USB_SHIFT: // Yes - USB? case CW_OFFSET_LSB_SHIFT: // LSB? case CW_OFFSET_AUTO_SHIFT: // Auto mode? Check flag if(ts.cw_lsb) { retval += ts.cw_sidetone_freq; // it was LSB - raise by sidetone amount } else { retval -= ts.cw_sidetone_freq; // it was USB - lower by sidetone amount } } return retval; } /** * @brief returns true if the mode parameters tell us we will TX at zero if as opposed to offset frequency */ bool RadioManagement_IsTxAtZeroIF(uint8_t dmod_mode, uint8_t digital_mode) { return ( // dmod_mode == DEMOD_CW || // UB8JDC (dmod_mode == DEMOD_CW && !(ts.expflags1 & EXPFLAGS1_CLEAR_TX_CW_RTTY_BPSK)) || (dmod_mode == DEMOD_DIGI && ( #ifdef USE_FREEDV digital_mode == DigitalMode_FreeDV #else false #endif // || is_demod_psk() // UB8JDC || (is_demod_psk() && !(ts.expflags1 & EXPFLAGS1_CLEAR_TX_CW_RTTY_BPSK)) #ifdef USE_RTTY_PROCESSOR // || is_demod_rtty() // UB8JDC || (is_demod_rtty() && !(ts.expflags1 & EXPFLAGS1_CLEAR_TX_CW_RTTY_BPSK)) #endif ) ) ); } uint32_t RadioManagement_Dial2TuneFrequency(const uint32_t dial_freq, uint8_t txrx_mode) { uint32_t tune_freq = dial_freq; // // Do "Icom" style frequency offset of the LO if in "CW OFFSET" mode. (Display freq. is also offset!) if(ts.dmod_mode == DEMOD_CW) // In CW mode? { tune_freq += RadioManagement_GetCWDialOffset(); } // Offset dial frequency if the RX/TX frequency translation is active and we are not transmitting in mode which // does not use frequency translation, this permits to use the generated I or Q channel as audio sidetone // that is right now RTTY, FreeDV, CW, BPSK if(txrx_mode != TRX_MODE_TX || RadioManagement_IsTxAtZeroIF(ts.dmod_mode, ts.digital_mode) == false) { tune_freq += AudioDriver_GetTranslateFreq(); } // Extra tuning actions if(txrx_mode == TRX_MODE_RX) { tune_freq += (ts.rit_value*20); // Add RIT on receive } return tune_freq; } /** * @brief switch off the PA Bias to mute HF output ( even if PTT is on ) * Using this method the PA will be effectively muted no matter what setting * the main bias switch has (which directly connected to the PTT HW Signal) * Used to suppress signal path reconfiguration noise during rx/tx and tx/rx switching */ void RadioManagement_DisablePaBias() { Board_SetPaBiasValue(0); } /** * @brief recalculate and set the PA Bias according to requested value * * Please note that at the mcHF BIAS is only applied if the PTT HW Signal * is active (which is controlled using MchfBoard_EnableTXSignalPath()) */ void RadioManagement_SetPaBias() { uint32_t calc_var; if((ts.pa_cw_bias) && (ts.dmod_mode == DEMOD_CW)) // is CW PA bias non-zero AND are we in CW mode? { calc_var = ts.pa_cw_bias; // use special CW-mode bias setting } else { calc_var = ts.pa_bias; // use "default" bias setting } calc_var = calc_var *2 + BIAS_OFFSET; if(calc_var > 255) { calc_var = 255; } #ifdef SDR_AMBER if ((ts.expflags2 & EXPFLAGS2_XVTR_OFF_PA) && (ts.xverter_mode & 0xf)) // OFF PA on XVTR TX & XVTR mode ON { calc_var = 0; // disable PA bias } #endif Board_SetPaBiasValue(calc_var); } bool RadioManagement_ChangeFrequency(bool force_update, uint32_t dial_freq,uint8_t txrx_mode) { // everything else uses main VFO frequency // uint32_t tune_freq; bool lo_change_pending = false; // Calculate actual tune frequency ts.tune_freq_req = RadioManagement_Dial2TuneFrequency(dial_freq, txrx_mode); if((ts.tune_freq != ts.tune_freq_req) || df.temp_factor_changed || force_update ) // did the frequency NOT change and display refresh NOT requested?? { if(ts.sysclock-ts.last_tuning > 5 || ts.last_tuning == 0) // prevention for SI570 crash due too fast frequency changes { Oscillator_ResultCodes_t lo_prep_result = osc->prepareNextFrequency(ts.tune_freq_req, df.temp_factor); // first check and mute output if a large step is to be done if(osc->isNextStepLarge() == true) // did the tuning require that a large tuning step occur? { // 18 is a little more than 10ms (15 ==10ms) which is max for the Si570 to change the frequency if (ts.audio_dac_muting_buffer_count < 18) { ts.audio_dac_muting_buffer_count = 18; } if (ts.audio_processor_input_mute_counter < 18) { ts.audio_processor_input_mute_counter = 18; } } ts.last_tuning = ts.sysclock; ts.last_lo_result = lo_prep_result; Oscillator_ResultCodes_t lo_exec_result = OSC_TUNE_IMPOSSIBLE; if (lo_prep_result != OSC_TUNE_IMPOSSIBLE) { // Set frequency lo_exec_result = osc->changeToNextFrequency(); } // if i2c error or verify error, there is a chance that we can fix that, so we mark this // as NOT executed, in all other cases we assume the change has happened (but may prevent TX) if (lo_exec_result != OSC_COMM_ERROR && lo_exec_result != OSC_ERROR_VERIFY) { df.temp_factor_changed = false; ts.tune_freq = ts.tune_freq_req; // frequency change required - update change detector // Save current freq df.tune_old = dial_freq; } else { ts.last_lo_result = lo_exec_result; } if (ts.last_lo_result == OSC_OK || ts.last_lo_result == OSC_TUNE_LIMITED) { ts.tx_disable &= ~TX_DISABLE_OUTOFRANGE; } else { ts.tx_disable |= TX_DISABLE_OUTOFRANGE; } uint32_t tune_freq_real = ts.tune_freq; RadioManagement_SetCouplingForFrequency(tune_freq_real); // adjust wattmeter coupling factor RadioManagement_SetHWFiltersForFrequency(tune_freq_real); // check the filter status with the new frequency update AudioManagement_CalcIqPhaseGainAdjust(tune_freq_real); // Inform Spectrum Display code that a frequency change has happened ts.dial_moved = 1; } else { lo_change_pending = true; } } // successfully executed the change return lo_change_pending == false; } /** * @brief temporary muting of the receiver when making changes which may cause audible pops etc., unmuting happens some 10s of milliseconds automatically later * */ void RadioManagement_MuteTemporarilyRxAudio() { // save us from the loud "POP" that will occur when we change bands ts.audio_processor_input_mute_counter = 5 * 15; ts.audio_dac_muting_buffer_count = 13 * 15; } Oscillator_ResultCodes_t RadioManagement_ValidateFrequencyForTX(uint32_t dial_freq) { // // we check with the si570 code if the frequency is tunable, we do not tune to it. // Oscillator_ResultCodes_t osc_res = osc->prepareNextFrequency(RadioManagement_Dial2TuneFrequency(dial_freq, TRX_MODE_TX), df.temp_factor); // bool osc_ok = osc_res == OSC_OK || osc_res == OSC_TUNE_LIMITED; // // get bandinfo to check if this is rx_only const BandInfo* bi = RadioManagement_GetBand(dial_freq); // we also check if our PA is able to support this frequency bool pa_ok = dial_freq >= mchf_pa.min_freq && dial_freq <= mchf_pa.max_freq; // return pa_ok && osc_ok ? osc_res: OSC_TUNE_IMPOSSIBLE; Oscillator_ResultCodes_t retval = (pa_ok && bi != NULL && bi->rx_only == false)? OSC_OK : OSC_TUNE_IMPOSSIBLE; if (retval == OSC_OK) { // we check with the si570 code if the frequency is tunable, we do not tune to it. retval = osc->prepareNextFrequency(RadioManagement_Dial2TuneFrequency(dial_freq, TRX_MODE_TX), df.temp_factor); } return retval; } /** * @brief returns the current LO Tune Frequency for TX * @returns LO Frequency in Hz */ uint32_t RadioManagement_GetTXDialFrequency() { uint32_t retval; if (ts.txrx_mode != TRX_MODE_TX) { if(is_splitmode()) // is SPLIT mode active and? { uint8_t vfo_tx; if (is_vfo_b()) { vfo_tx = VFO_A; } else { vfo_tx = VFO_B; } retval = vfo[vfo_tx].band[ts.band->band_mode].dial_value; // load with VFO-A frequency } else { retval = df.tune_new; } } else { retval = df.tune_new; } return retval; } /** * @brief returns the current LO Dial Frequency for RX * @returns LO Frequency in Hz */ uint32_t RadioManagement_GetRXDialFrequency() { uint32_t baseval; if (ts.txrx_mode != TRX_MODE_RX) { if(is_splitmode()) // is SPLIT mode active? { uint8_t vfo_rx; if (is_vfo_b()) { vfo_rx = VFO_B; } else { vfo_rx = VFO_A; } baseval = vfo[vfo_rx].band[ts.band->band_mode].dial_value; // load with VFO-A frequency } else { baseval = df.tune_new; } } else { baseval = df.tune_new; } return baseval + (ts.rit_value*20); } // globals used: // ts.trx_mode -> R / W // ts.band -> R // vfo[] -> r / w // df.tune_new -> r // ts.dmod_mode -> r // ts.cw_text_entry -> r // functions called: // RadioManagement_ValidateFrequencyForTX // RadioManagement_IsTxDisabled() // ts.audio_dac_muting_buffer_count -> r/w // ts.audio_dac_muting_flag = true; // let the audio being muted initially as long as we need it // ads.agc_holder -> w // ads.agc_val -> r // RadioManagement_DisablePaBias(); // kill bias to mute the HF output quickly // Board_RedLed(LED_STATE_ON); // TX // Board_GreenLed(LED_STATE_OFF); // Board_EnableTXSignalPath(true); // switch antenna to output and codec output to QSE mixer // RadioManagement_ChangeFrequency(false,df.tune_new, txrx_mode_final); // uint8_t tx_band = RadioManagement_GetBand(tune_new); // RadioManagement_PowerLevelChange(tx_band,ts.power_level); // RadioManagement_SetBandPowerFactor(tx_band); // AudioManagement_SetSidetoneForDemodMode(ts.dmod_mode,txrx_mode_final == TRX_MODE_RX?false:tune_mode); // Codec_SwitchTxRxMode(txrx_mode_final); // RadioManagement_SetPaBias(); // ts.txrx_switch_audio_muting_timing -> r; // ts.audio_processor_input_mute_counter -> w; // ts.audio_dac_muting_buffer_count -> w // ts.audio_dac_muting_flag -> w; // ts.tx_audio_source -> r // ts.power_level -> r /** * @brief check if all resources are available to switch tx/rx mode in an interrupt * @return true if all resources are available to switch tx/rx mode in an interrupt */ static __IO bool radioManagement_SwitchTxRx_running; /** * This function should only return true if it is absolutely safe to switch between Tx and Rx in an interrupt. Better safe than sorry. * @return true if we can switch because no code is running in a critical section */ bool RadioManagement_SwitchTxRx_Possible() { // we check all resources which may be locked by a user mode (non-interrupt activity) return RadioManagement_TxRxSwitching_IsEnabled() && osc->readyForIrqCall() && Codec_ReadyForIrqCall() && radioManagement_SwitchTxRx_running == false; } void RadioManagement_SwitchTxRx(uint8_t txrx_mode, bool tune_mode) { radioManagement_SwitchTxRx_running = true; uint32_t tune_new; bool tx_ok = false; bool tx_pa_disabled = false; // ts.last_tuning = 0; // prevents transmitting on wrong frequency during "RX bk phases" if(is_splitmode()) // is SPLIT mode active? { uint8_t vfo_tx,vfo_rx; if (is_vfo_b()) { vfo_rx = VFO_B; vfo_tx = VFO_A; } else { vfo_rx = VFO_A; vfo_tx = VFO_B; } if(txrx_mode == TRX_MODE_TX) // are we in TX mode? { if(ts.txrx_mode == TRX_MODE_RX) // did we want to enter TX mode? { vfo[vfo_rx].band[ts.band->band_mode].dial_value = df.tune_new; // yes - save current RX frequency in RX VFO location } tune_new = vfo[vfo_tx].band[ts.band->band_mode].dial_value; // load with TX VFO frequency } else // we are in RX mode { tune_new = vfo[vfo_rx].band[ts.band->band_mode].dial_value; // load with RX VFO frequency } } else { // we just take the current one if not in split mode tune_new = df.tune_new; } if(txrx_mode == TRX_MODE_TX) { // FIXME: Not very robust code, make sure Validate always returns TUNE_IMPOSSIBLE in case of issues tx_ok = RadioManagement_ValidateFrequencyForTX(tune_new) != OSC_TUNE_IMPOSSIBLE; // this code handles the ts.tx_disable // even if ts.tx_disble is set in CW and only in CW we still switch to TX // but leave the PA disabled. This is for support of CW training right with the mcHF. if (RadioManagement_IsTxDisabled() || ts.cw_text_entry) { if ((tx_ok == true && ts.dmod_mode == DEMOD_CW) || ts.cw_text_entry) { tx_pa_disabled = true; } else { // in any other case, it is not okay to transmit with ts.tx_disable == true tx_ok = false; } } if (is_demod_psk()) { Psk_Modulator_PrepareTx(); } } uint8_t txrx_mode_final = tx_ok?txrx_mode:TRX_MODE_RX; // only switch mode if tx was permitted or rx was requested if (txrx_mode_final != ts.txrx_mode || txrx_mode_final == TRX_MODE_RX) { // there is in fact a switch happening // which may cause audio issues if (txrx_mode_final != ts.txrx_mode) { ts.audio_dac_muting_buffer_count = 2; // wait at least 2 buffer cycles ts.audio_dac_muting_flag = true; // let the audio being muted initially as long as we need it RadioManagement_DisablePaBias(); // kill bias to mute the HF output quickly } if(txrx_mode_final == TRX_MODE_TX) { // We mute the audio BEFORE we activate the PTT. // This is necessary since U3 is switched the instant that we do so, // rerouting audio paths and causing all sorts of disruption including CLICKs and squeaks. Codec_PrepareTx(ts.txrx_mode); // 5ms while (ts.audio_dac_muting_buffer_count >0) { // TODO: Find a better solution here asm("nop"); // just wait a little for the silence to come out of the audio path // this can take up to 1.2ms (time for processing two audio buffer dma requests } // this is here to allow CW training // with ts.tx_disabled on nothing will be transmitted but you can hear the sidetone if (tx_pa_disabled == false) { Board_RedLed(LED_STATE_ON); // TX Board_GreenLed(LED_STATE_OFF); Board_EnableTXSignalPath(true); // switch antenna to output and codec output to QSE mixer } } df.tune_new = tune_new; RadioManagement_ChangeFrequency(false,df.tune_new, txrx_mode_final); // ts.audio_dac_muting_flag = true; // let the audio being muted initially as long as we need it // there might have been a band change between the modes, make sure to have the power settings fitting the mode if (txrx_mode_final == TRX_MODE_TX) { RadioManagement_SetPowerLevel(RadioManagement_GetBand(tune_new),ts.power_level); } AudioManagement_SetSidetoneForDemodMode(ts.dmod_mode,txrx_mode_final == TRX_MODE_RX?false:tune_mode); // make sure the audio is set properly according to txrx and tune modes if (txrx_mode_final == TRX_MODE_RX) { while (ts.audio_dac_muting_buffer_count >0) { // TODO: Find a better solution here asm("nop"); // just wait a little for the silence to come out of the audio path // this can take up to 1.2ms (time for processing two audio buffer dma requests } Board_EnableTXSignalPath(false); // switch antenna to input and codec output to lineout Board_RedLed(LED_STATE_OFF); // TX led off Board_GreenLed(LED_STATE_ON); // TX led off ts.audio_dac_muting_flag = false; // unmute audio output //CwGen_PrepareTx(); // make sure the keyer is set correctly for next round // commented out as resetting now part of cw_gen state machine } if (ts.txrx_mode != txrx_mode_final) { Codec_SwitchTxRxMode(txrx_mode_final); if (txrx_mode_final == TRX_MODE_TX) { RadioManagement_SetPaBias(); uint32_t input_mute_time = 0, dac_mute_time = 0, dac_mute_time_mode = 0, input_mute_time_mode = 0; // aka 1.3ms // calculate expire time for audio muting in interrupts, it is 15 interrupts per 10ms dac_mute_time = ts.txrx_switch_audio_muting_timing * 15; if (ts.dmod_mode != DEMOD_CW) { switch(ts.tx_audio_source) { case TX_AUDIO_DIG: dac_mute_time_mode = 0* 15; // Minimum time is 0ms break; case TX_AUDIO_LINEIN_L: case TX_AUDIO_LINEIN_R: dac_mute_time_mode = 4 * 15; // Minimum time is 40ms input_mute_time_mode = dac_mute_time_mode; break; case TX_AUDIO_MIC: dac_mute_time_mode = 4* 15; // Minimum time is 40ms input_mute_time_mode = dac_mute_time_mode; break; } } dac_mute_time = (dac_mute_time > dac_mute_time_mode)? dac_mute_time : dac_mute_time_mode; input_mute_time = (input_mute_time > input_mute_time_mode)? input_mute_time : input_mute_time_mode; ts.audio_processor_input_mute_counter = input_mute_time; ts.audio_dac_muting_buffer_count = dac_mute_time; // 15 == 10ms ts.audio_dac_muting_flag = false; // unmute audio output unless timed muting is active } ts.txrx_mode = txrx_mode_final; } } radioManagement_SwitchTxRx_running = false; } /* * @returns: false -> use USB, true -> use LSB */ bool RadioManagement_CalculateCWSidebandMode() { bool retval = false; switch(RadioManagement_CWConfigValueToModeEntry(ts.cw_offset_mode)->sideband_mode) { case CW_SB_AUTO: // For "auto" modes determine if we are above or below threshold frequency // if (RadioManagement_SSB_AutoSideBand(df.tune_new) == DEMOD_USB) // is the current frequency above the USB threshold? retval = (df.tune_new <= USB_FREQ_THRESHOLD && RadioManagement_GetBand(df.tune_new)->band_mode != BAND_MODE_60); // is the current frequency below the USB threshold AND is it not 60m? -> LSB break; case CW_SB_LSB: retval = true; break; case CW_SB_USB: default: retval = false; break; } return retval; } typedef struct BandFilterDescriptor { uint32_t upper; uint16_t band_mode; } BandFilterDescriptor; // TODO: This code below approach assumes that all filter hardware uses a set of separate filter banks // other approaches such as configurable filters need a different approach, should be factored out // into some hardware abstraction at some point // The descriptor array below has to be ordered from the lowest BPF frequency filter // to the highest. static const BandFilterDescriptor mchf_rf_bandFilters[] = { #ifndef SDR_AMBER { 4000000, 0 }, { 8000000, 1 }, { 16000000, 2 }, { 32000000, 3 }, #else // Amber { 1800000, 4 }, { 2000000, 5 }, { 4000000, 0 }, { 8000000, 1 }, { 16000000, 2 }, { 32000000, 3 }, { 60000000, 6 }, #endif }; const int BAND_FILTER_NUM = sizeof(mchf_rf_bandFilters)/sizeof(BandFilterDescriptor); #ifdef SDR_AMBER void RadioManagement_Set_PA_Bandcode(uint32_t freq) { uint8_t state = ts.amber_io4_state; if(!ts.amber_pa_bandcode_mode) // OFF { state = 0; } else if (ts.amber_pa_bandcode_mode == 1) // 4 LPF 3.5-30Mc as in mcHF, direct control { if(freq <= 4000000) {state = 0x01;} else if(freq <= 8000000) {state = 0x02;} else if(freq <= 16000000) {state = 0x04;} else {state = 0x08;} } else if (ts.amber_pa_bandcode_mode == 2) // 4 LPF 3.5-30Mc as in mcHF, bin code A0, A1 { if(freq <= 4000000) {state = 0x00;} else if(freq <= 8000000) {state = 0x01;} else if(freq <= 16000000) {state = 0x02;} else {state = 0x03;} } else if (ts.amber_pa_bandcode_mode == 3) // Yaesu ACC FT-891. A0,A1,A2,A3 -> A,B,C,D { if(freq < 2500000) {state = 0x01;} else if(freq < 4100000) {state = 0x02;} else if(freq < 7500000) {state = 0x03;} else if(freq < 11500000) {state = 0x04;} else if(freq < 14500000) {state = 0x05;} else if(freq < 20900000) {state = 0x06;} else if(freq < 21500000) {state = 0x07;} else if(freq < 25500000) {state = 0x08;} else if(freq < 41500000) {state = 0x09;} else {state = 0x0A;} } if(state != ts.amber_io4_state) { ts.amber_io4_state = state; Board_AmberIOx4_Write(1, state); } } #endif /** * @brief Select and activate the correct BPF for the frequency given in @p freq * * * @param freq The frequency to activate the BPF for in Hz * * @warning If the frequency given in @p freq is too high for any of the filters, no filter change is executed. */ static void RadioManagement_SetHWFiltersForFrequency(uint32_t freq) { uint8_t bandmode_t; #ifdef SDR_AMBER if (ts.amber_io4_present && ts.amber_pa_bandcode_mode) // chip are here and PA bandcode output is not OFF { RadioManagement_Set_PA_Bandcode(freq); } #endif for (int idx = 0; idx < BAND_FILTER_NUM; idx++) { if(freq < mchf_rf_bandFilters[idx].upper) // are we low enough if frequency for this band filter? { bandmode_t = mchf_rf_bandFilters[idx].band_mode; #ifdef SDR_AMBER if(!(ts.expflags2 & EXPFLAGS2_3_ADD_BPF)) { if(bandmode_t == 5) { bandmode_t = 4; } } #endif // if(ts.filter_band != mchf_rf_bandFilters[idx].band_mode) if(ts.filter_band != bandmode_t) { // Board_SelectLpfBpf(mchf_rf_bandFilters[idx].band_mode); // ts.filter_band = mchf_rf_bandFilters[idx].band_mode; Board_SelectLpfBpf(bandmode_t); ts.filter_band = bandmode_t; nr_params.first_time = 1; // in case of any Bandfilter change restart the NR routine } break; } } } typedef struct { uint32_t upper; uint16_t coupling_band; } CouplingDescriptor; static const CouplingDescriptor mchf_rf_coupling[] = { { 200000, COUPLING_2200M}, { 600000, COUPLING_630M}, { 2500000, COUPLING_160M}, { 4250000, COUPLING_80M}, { 8000000, COUPLING_40M}, { 16000000, COUPLING_20M}, { 25000000, COUPLING_15M}, { 32000000, COUPLING_10M}, { 60000000, COUPLING_6M}, }; const int COUPLING_NUM = sizeof(mchf_rf_coupling)/sizeof(CouplingDescriptor); /** * @brief Select and activate the correct coupling factor for the wattmeter for the frequency given in @p freq * * * @param freq The frequency in Hz * * @warning If the frequency given in @p freq is too high , no change is executed. */ static void RadioManagement_SetCouplingForFrequency(uint32_t freq) { for (int idx = 0; idx < COUPLING_NUM; idx++) { if(freq < mchf_rf_coupling[idx].upper) // are we low enough if frequency for this coupling factor? { ts.coupling_band = mchf_rf_coupling[idx].coupling_band; break; } } } /** * @brief shall the stored power factor be interpreted as coarse or fine (4*resolution of coarse) * @param freq in Hertz */ bool RadioManagement_IsPowerFactorReduce(uint32_t freq) { return (((freq < 8000000) && (ts.flags2 & FLAGS2_LOW_BAND_BIAS_REDUCE)) || ((freq >= 8000000) && (ts.flags2 & FLAGS2_HIGH_BAND_BIAS_REDUCE))) != 0; } /* * @brief returns the band according to the given frequency * @param freq frequency to get band for. Unit is Hertz. This value is used without any further adjustments and should be the intended RX/TX frequency and NOT the IQ center frequency * */ void RadioManagement_SetDemodMode(uint8_t new_mode) { ts.dsp.inhibit++; // ads.af_disabled++; if (new_mode == DEMOD_DIGI) { if (ts.digital_mode == DigitalMode_None) { ts.digital_mode = 1; // this maps to the first available digital mode (if any) } RadioManagement_ChangeCodec(ts.digital_mode,1); } else if (ts.dmod_mode == DEMOD_DIGI) { RadioManagement_ChangeCodec(ts.digital_mode,0); // FIXME: Maybe we should better handle the // mode switching centrally for all modes AudioNr_Prepare(); // prepare AudioNr for use after FreeDV using the same buffer } if (new_mode == DEMOD_FM && ts.dmod_mode != DEMOD_FM) { // ads.fm_squelched = true; // ads.fm_sql_avg = 1; } if (ts.cw_offset_shift_keep_signal == true && (ts.cw_offset_mode == CW_OFFSET_AUTO_SHIFT || ts.cw_offset_mode == CW_OFFSET_LSB_SHIFT || ts.cw_offset_mode == CW_OFFSET_USB_SHIFT)) { static int16_t sidetone_mult = 0; if (new_mode == DEMOD_CW && ts.dmod_mode != DEMOD_CW) { // we come from a non-CW mode if (RadioManagement_UsesBothSidebands(ts.dmod_mode) == false) { sidetone_mult = RadioManagement_LSBActive(ts.dmod_mode) ? -1 : 1; // if we have a sideband mode // adjust dial frequency by side tone offset // if the sidetone frequency is not change, we return exactly to the frequency we have been before // this is important if we just cycle through the modes. df.tune_new += sidetone_mult * ts.cw_sidetone_freq; } else { sidetone_mult = 0; } } if (new_mode != DEMOD_CW && ts.dmod_mode == DEMOD_CW) { // we revert now our frequency change // we go to a non-CW mode // adjust dial frequency by former side tone offset df.tune_new -= sidetone_mult * ts.cw_sidetone_freq; } } AudioDriver_SetProcessingChain(new_mode, false); AudioManagement_SetSidetoneForDemodMode(new_mode,false); if (new_mode == DEMOD_SAM) { ts.tx_disable |= TX_DISABLE_RXMODE; // set bit } else { ts.tx_disable &= ~TX_DISABLE_RXMODE; // clear bit } ts.dmod_mode = new_mode; if (ads.af_disabled) { ads.af_disabled--; } if (ts.dsp.inhibit) { ts.dsp.inhibit--; } nr_params.first_time = 1; // re-initialize spectral noise reduction, when dmod_mode was changed } /** * * Is the given frequency in the limits of a band? * @param bandInfo* ptr to band info for this band * @param freq the frequency to check */ bool RadioManagement_FreqIsInBand(const BandInfo* bandinfo, const uint32_t freq) { assert(bandinfo != NULL); return (freq >= bandinfo->tune) && (freq <= (bandinfo->tune + bandinfo->size)); } bool band_enabled[MAX_BAND_NUM]; // we store which band is to be used (or ignored) /** * Is the given frequency in an enabled band? * @param freq the frequency to check * @returns true if in any of the currently enabled bands */ bool RadioManagement_FreqIsInEnabledBand ( uint32_t freq ) { bool retval = false; for ( int idx = 0; idx < MAX_BAND_NUM; idx++ ) { if ( band_enabled[idx] ) { retval = true; RadioManagement_FreqIsInBand( bandInfo[idx], freq); break; // we found the first enabled band following the current one } } return retval; } /** * Identify ham band for a given frequency * @param freq in Hz * @return the ham band info or generic band info if not a known ham band */ const BandInfo* RadioManagement_GetBand(uint32_t freq) { static const BandInfo* band_scan_old = &bi_gen_all; const BandInfo* band_scan = &bi_gen_all; // generic band, which we return if we can't find a match // first try the previously selected band, and see if it is an match if (RadioManagement_FreqIsInBand(band_scan_old, freq) == true) { band_scan = band_scan_old; } else { // search trough all bands, except the generic band (which is last) for(int band_idx = 0; band_idx < MAX_BANDS; band_idx++) { if(RadioManagement_FreqIsInBand(bandInfo[band_idx],freq)) // Is this frequency within this band? { band_scan = bandInfo[band_idx]; break; // yes - stop the scan } } } band_scan_old = band_scan; // remember last result return band_scan; // return with the band } uint32_t RadioManagement_SSB_AutoSideBand(uint32_t freq) { uint32_t retval; if((ts.lsb_usb_auto_select == AUTO_LSB_USB_60M) && ((freq < USB_FREQ_THRESHOLD) && (RadioManagement_GetBand(freq)->band_mode != BAND_MODE_60))) // are we <10 MHz and NOT on 60 meters? { retval = DEMOD_LSB; } else if((ts.lsb_usb_auto_select == AUTO_LSB_USB_ON) && (freq < USB_FREQ_THRESHOLD)) // are we <10 MHz (not in 60 meter mode) { retval = DEMOD_LSB; } else // we must be > 10 MHz OR on 60 meters { retval = DEMOD_USB; } return retval; } /** * Call this to switch off transmit as soon as possible * Please note, this is an asynchronous request, there may be a delay until TX is switched off! */ void RadioManagement_Request_TxOn() { ts.ptt_req = true; } /** * Call this to switch off transmit as soon as possible * Please note, this is an asynchronous request, there may be a delay until TX is switched off! */ void RadioManagement_Request_TxOff() { // we have to do both here to handle a potential race condition if // the CAT code deasserts RTS before we actually switched to TX. ts.ptt_req = false; ts.tx_stop_req = true; if(ts.cw_keyer_speed != ts.cw_keyer_speed_bak) { ts.cw_keyer_speed = ts.cw_keyer_speed_bak; CwGen_SetSpeed(); } } const int32_t ptt_debounce_time = 3; // n*10ms, delay for debouncing manually started TX (using the PTT button / input line) #define PTT_BREAK_IDLE (-1) void RadioManagement_HandlePttOnOff() { static int64_t ptt_break_timer = PTT_BREAK_IDLE; // not when tuning, in this case we are TXing already anyway until tune is being stopped if(ts.tune == false) { // we are asked to start TX if(ts.ptt_req) { if(ts.txrx_mode == TRX_MODE_RX && (RadioManagement_IsTxDisabled() == false || ts.dmod_mode == DEMOD_CW || ts.cw_text_entry)) // FIXME cw_text_entry situation is not correctly processed { RadioManagement_SwitchTxRx(TRX_MODE_TX,false); } // if we have a ptt request, all stop requests are cancelled ts.tx_stop_req = false; // the ptt request has been processed ts.ptt_req = false; } #ifdef SDR_AMBER_PTT_ALT else if (Board_PttAltLinePressed()) { if(ts.txrx_mode == TRX_MODE_RX && (RadioManagement_IsTxDisabled() == false)) { RadioManagement_SwitchTxRx(TRX_MODE_TX,false); } ts.tx_stop_req = true; } #endif else if (CatDriver_CatPttActive() == false) { // When CAT driver "pressed" PTT skip auto return to RX if (ts.tx_stop_req == true && is_demod_psk() && Psk_Modulator_GetState() == PSK_MOD_ACTIVE) { Psk_Modulator_SetState(PSK_MOD_POSTAMBLE); ts.tx_stop_req = false; } else if(!(ts.dmod_mode == DEMOD_CW || is_demod_rtty() || is_demod_psk() || ts.cw_text_entry) || ts.tx_stop_req == true) { // we get here either if there is an explicit request to stop transmission no matter which mode we are in // or if the mode relies on us to switch off after PTT has been released (we defines this by exclusion // of modes which control transmission state via paddles or keyboard) // If we are in TX if(ts.txrx_mode == TRX_MODE_TX) { // ... the PTT line is released ... if(Board_PttDahLinePressed() == false) { // ... we start the break timer if not started already! if (ptt_break_timer == PTT_BREAK_IDLE) { ptt_break_timer = ts.sysclock + ptt_debounce_time; } } else { // ... but if not released we stop the break timer ptt_break_timer = PTT_BREAK_IDLE; } // ... if break time is over or we are forced to leave TX immediately ... if((ptt_break_timer < ts.sysclock && ptt_break_timer != PTT_BREAK_IDLE) || ts.tx_stop_req == true) { // ... go back to RX and ... RadioManagement_SwitchTxRx(TRX_MODE_RX,false); // ... stop the timer ptt_break_timer = PTT_BREAK_IDLE; } } // if we are here a stop request has been processed completely ts.tx_stop_req = false; } } } } bool RadioManagement_IsApplicableDemodMode(uint32_t demod_mode) { bool retval = false; switch(demod_mode) { case DEMOD_LSB: case DEMOD_USB: if((ts.lsb_usb_auto_select)) // is auto-select LSB/USB mode enabled AND mode-skip NOT enabled? { retval = RadioManagement_SSB_AutoSideBand(df.tune_new) == demod_mode; // is this a voice mode, subject to "auto" LSB/USB select? } else { retval = true; } break; case DEMOD_AM: retval = (ts.demod_mode_disable & DEMOD_AM_DISABLE) == 0; // is AM enabled? break; case DEMOD_DIGI: retval = (ts.demod_mode_disable & DEMOD_DIGI_DISABLE) == 0; // is DIGI enabled? if((ts.lsb_usb_auto_select) && retval == true) // is auto-select LSB/USB mode enabled AND mode-skip NOT enabled? { // TODO: this is only true for FreeDV, but since we have only FreeDV... ts.digi_lsb = RadioManagement_SSB_AutoSideBand(df.tune_new) == DEMOD_LSB; // is this a voice mode, subject to "auto" LSB/USB select? } break; case DEMOD_CW: retval = (ts.demod_mode_disable & DEMOD_CW_DISABLE) == 0; // is CW enabled? break; case DEMOD_FM: // FIXME: ts.lsb_usb_auto_select acts as fm select here. Rename! retval = (ts.iq_freq_mode != FREQ_IQ_CONV_MODE_OFF) && (((ts.flags2 & FLAGS2_FM_MODE_ENABLE) != 0) || (ts.band->band_mode == BAND_MODE_10 && ts.lsb_usb_auto_select)); // is FM enabled? break; case DEMOD_SAM: retval =( ts.flags1 & FLAGS1_SAM_ENABLE) != 0; // is SAM enabled? break; #ifdef USE_TWO_CHANNEL_AUDIO case DEMOD_SSBSTEREO: case DEMOD_IQ: if(!ts.stereo_enable) { retval = false; } else { retval = true; } break; #endif default: retval = true; } return retval; } /** * @brief finds next related alternative modes for a given mode (e.g. for USB it returns LSB) * @returns next mode OR same mode if none found. */ uint32_t RadioManagement_NextAlternativeDemodMode(uint32_t loc_mode) { uint32_t retval = loc_mode; // default is to simply return the original mode switch(loc_mode) { case DEMOD_USB: retval = DEMOD_LSB; break; case DEMOD_LSB: retval = DEMOD_USB; break; case DEMOD_CW: // FIXME: get rid of ts.cw_lsb // better use it generally to indicate selected side band (also in SSB) ts.cw_lsb = !ts.cw_lsb; break; case DEMOD_AM: retval = DEMOD_SAM; break; case DEMOD_SAM: ads.sam_sideband ++; // stereo SAM is only switchable if you have stereo modes enabled #ifdef USE_TWO_CHANNEL_AUDIO if (ts.stereo_enable == false && ads.sam_sideband == SAM_SIDEBAND_STEREO) { // skip if we have stereo disabled ads.sam_sideband ++; } #endif if (ads.sam_sideband >= SAM_SIDEBAND_MAX) { ads.sam_sideband = SAM_SIDEBAND_BOTH; retval = DEMOD_AM; } break; case DEMOD_DIGI: ts.digi_lsb = !ts.digi_lsb; break; case DEMOD_FM: // toggle between narrow and wide fm RadioManagement_FmDevSet5khz( !RadioManagement_FmDevIs5khz() ); } // if there is no explicit alternative mode // we return the original mode. return retval; } /** * @brief find the next "different" demodulation mode which is enabled. * @returns new mode, or current mode if no other mode is available */ uint32_t RadioManagement_NextNormalDemodMode(uint32_t loc_mode) { uint32_t retval = loc_mode; // default is to simply return the original mode do { retval++; if (retval > DEMOD_MAX_MODE) { retval = 0; // wrap around; } } while (RadioManagement_IsApplicableDemodMode(retval) == false && retval != loc_mode); // if we loop around to the initial mode, there is no other option than the original mode // so we return it, otherwise we provide the new mode. return retval; } bool RadioManagement_UsesBothSidebands(uint16_t dmod_mode) { bool retval = ( (dmod_mode == DEMOD_AM) ||(dmod_mode == DEMOD_SAM && (ads.sam_sideband == SAM_SIDEBAND_BOTH)) || (dmod_mode == DEMOD_FM) ); #ifdef USE_TWO_CHANNEL_AUDIO // if we support two channel audio, then both bands are used by some additional modes retval = retval || ( (dmod_mode == DEMOD_SSBSTEREO) || (dmod_mode == DEMOD_IQ) || (dmod_mode == DEMOD_SAM && ads.sam_sideband == SAM_SIDEBAND_STEREO ) ); #endif return retval; } bool RadioManagement_LSBActive(uint16_t dmod_mode) { bool is_lsb; switch(dmod_mode) // determine if the receiver is set to LSB or USB or FM { case DEMOD_SAM: is_lsb = ads.sam_sideband == SAM_SIDEBAND_LSB; break; case DEMOD_LSB: is_lsb = true; // it is LSB break; case DEMOD_CW: is_lsb = ts.cw_lsb; // is this USB RX mode? (LSB of mode byte was zero) break; case DEMOD_DIGI: is_lsb = ts.digi_lsb; break; case DEMOD_USB: default: is_lsb = false; // it is USB break; } return is_lsb; } bool RadioManagement_USBActive(uint16_t dmod_mode) { return RadioManagement_UsesBothSidebands(dmod_mode) == false && RadioManagement_LSBActive(dmod_mode) == false; } static void RadioManagement_PowerFromADCValue(float inval, float sensor_null, float coupling_calc,volatile float* pwr_ptr, volatile float* dbm_ptr) { float32_t pwr; const float32_t val = sensor_null + ((inval * SWR_ADC_VOLT_REFERENCE) / SWR_ADC_FULL_SCALE); // get nominal A/D reference voltage // divide by full-scale A/D count to yield actual input voltage from detector // offset result if(val <= LOW_POWER_CALC_THRESHOLD) // is this low power as evidenced by low voltage from the sensor? { pwr = LOW_RF_PWR_COEFF_A + (LOW_RF_PWR_COEFF_B * val) + (LOW_RF_PWR_COEFF_C * (val * val) ) + (LOW_RF_PWR_COEFF_D * powf(val, 3)); } else // it is high power { pwr = HIGH_RF_PWR_COEFF_A + (HIGH_RF_PWR_COEFF_B * val) + (HIGH_RF_PWR_COEFF_C * (val * val)); } // calculate forward and reverse RF power in watts (p = a + bx + cx^2) for high power (above 50-60 if(pwr < 0) // prevent negative power readings from emerging from the equations - particularly at zero output power { pwr = 0; } const float32_t dbm = (10 * (log10f(pwr))) + 30 + coupling_calc; *dbm_ptr = dbm; *pwr_ptr = pow10f(dbm/10)/1000; } /* * @brief Measures and calculates TX Power Output and SWR, has to be called regularly * @returns true if new values have been calculated */ bool RadioManagement_UpdatePowerAndVSWR() { uint16_t val_p,val_s = 0; float sensor_null, coupling_calc; bool retval = false; // Collect samples if(swrm.p_curr < SWR_SAMPLES_CNT) { // Get next sample if(!(ts.flags1 & FLAGS1_SWAP_FWDREV_SENSE)) // is bit NOT set? If this is so, do NOT swap FWD/REV inputs from power detectors { val_p = HAL_ADC_GetValue(&hadc2); // forward val_s = HAL_ADC_GetValue(&hadc3); // return } else // FWD/REV bits should be swapped { val_p = HAL_ADC_GetValue(&hadc3); // forward val_s = HAL_ADC_GetValue(&hadc2); // return } // Add to accumulator to average A/D values swrm.fwd_calc += val_p; swrm.rev_calc += val_s; swrm.p_curr++; } else { // obtain and calculate power meter coupling coefficients coupling_calc = (swrm.coupling_calc[ts.coupling_band] - 100.0)/10.0; // offset to zero and rescale to 0.1 dB/unit sensor_null = (swrm.sensor_null - 100.0) / 1000 ; // get calibration factor // offset it so that 100 = 0 // divide so that each step = 1 millivolt // Compute average values RadioManagement_PowerFromADCValue(swrm.fwd_calc / SWR_SAMPLES_CNT, sensor_null, coupling_calc,&swrm.fwd_pwr, &swrm.fwd_dbm); RadioManagement_PowerFromADCValue(swrm.rev_calc / SWR_SAMPLES_CNT, sensor_null, coupling_calc,&swrm.rev_pwr, &swrm.rev_dbm); // Reset accumulators and variables for power measurements swrm.p_curr = 0; swrm.fwd_calc = 0; swrm.rev_calc = 0; // Calculate VSWR from power readings swrm.vswr = (1+sqrtf(swrm.rev_pwr/swrm.fwd_pwr))/(1-sqrtf(swrm.rev_pwr/swrm.fwd_pwr)); if ( ts.debug_vswr_protection_threshold > 1 ) { if ((swrm.fwd_pwr >= SWR_MIN_CALC_POWER) && ( swrm.vswr > ts.debug_vswr_protection_threshold )) { RadioManagement_DisablePaBias ( ); swrm.high_vswr_detected = true; // change output power to "PA_LEVEL_0_5W" when VSWR protection is active RadioManagement_SetPowerLevel ( RadioManagement_GetBand ( df.tune_new), PA_LEVEL_MINIMAL ); } } retval = true; } return retval; } /** * @brief Calculation and setting of RX Audio Codec gain for ADC of IQ signal and detects signal clipping, needs to be called regularly every 40ms. */ void RadioManagement_HandleRxIQSignalCodecGain() { static uint16_t rfg_timer= 0; // counter used for timing RFG control decay static float auto_rfg = 8; float rfg_calc; float gcalc; // ************************ // Update S-Meter and control the input gain of the codec to maximize A/D and receiver dynamic range // ************************ // // Calculate attenuation of "RF Codec Gain" setting so that S-meter reading can be compensated. // for input RF attenuation setting // if(ts.rf_codec_gain == RF_CODEC_GAIN_AUTO) // Is RF gain in "AUTO" mode? { rfg_calc = auto_rfg; } else // not in "AUTO" mode { rfg_calc = ts.rf_codec_gain; // get copy of RF gain setting auto_rfg = rfg_calc; // keep "auto" variable updated with manual setting when in manual mode rfg_timer = 0; } // rfg_calc = (auto_rfg + 1) * 2 + 13; --> 9 * 2 + 13 = 31 ! rfg_calc += 1; // offset to prevent zero rfg_calc *= 2; // double the range of adjustment rfg_calc += 13; // offset, as bottom of range of A/D gain control is not useful (e.g. ADC saturates before RX hardware) if(rfg_calc >31) // limit calc to hardware range { rfg_calc = 31; } Codec_IQInGainAdj(rfg_calc); // set the RX gain on the codec // Now calculate the RF gain setting gcalc = pow10(((rfg_calc * 1.5) - 34.5) / 10) ; // codec has 1.5 dB/step // offset codec setting by 34.5db (full gain = 12dB) // convert to power ratio ads.codec_gain_calc = sqrtf(gcalc); // convert to voltage ratio - we now have current A/D (codec) gain setting // Now handle automatic A/D input gain control timing rfg_timer++; // bump RFG timer if(rfg_timer > 10000) // limit count of RFG timer { rfg_timer = 10000; } if(ads.adc_half_clip) // did clipping almost occur? { if(rfg_timer >= AUTO_RFG_DECREASE_LOCKOUT) // has enough time passed since the last gain decrease? { if(auto_rfg) // yes - is this NOT zero? { auto_rfg -= 0.5; // decrease gain one step, 1.5dB (it is multiplied by 2, above) rfg_timer = 0; // reset the adjustment timer } } } else if(!ads.adc_quarter_clip) // no clipping occurred { if(rfg_timer >= AUTO_RFG_INCREASE_TIMER) // has it been long enough since the last increase? { auto_rfg += 0.5; // increase gain by one step, 1.5dB (it is multiplied by 2, above) rfg_timer = 0; // reset the timer to prevent this from executing too often if(auto_rfg > 8) // limit it to 8 { auto_rfg = 8; } } } ads.adc_half_clip = false; // clear "half clip" indicator that tells us that we should decrease gain ads.adc_quarter_clip = false; // clear indicator that, if not triggered, indicates that we can increase gain } const cw_mode_map_entry_t cw_mode_map[] = { {CW_OFFSET_TX, CW_SB_USB}, // 0 {CW_OFFSET_TX, CW_SB_LSB}, // 1 {CW_OFFSET_TX, CW_SB_AUTO}, // 2 {CW_OFFSET_RX, CW_SB_USB}, // 3 {CW_OFFSET_RX, CW_SB_LSB}, // 4 {CW_OFFSET_RX, CW_SB_AUTO}, // 5 {CW_OFFSET_SHIFT, CW_SB_USB}, // 6 {CW_OFFSET_SHIFT, CW_SB_LSB}, // 7 {CW_OFFSET_SHIFT, CW_SB_AUTO}, // 8 }; const cw_mode_map_entry_t* RadioManagement_CWConfigValueToModeEntry(uint8_t cw_offset_mode) { const cw_mode_map_entry_t* retval = &cw_mode_map[0]; if (cw_offset_mode < CW_OFFSET_NUM) { retval = &cw_mode_map[cw_offset_mode]; } return retval; } uint8_t RadioManagement_CWModeEntryToConfigValue(const cw_mode_map_entry_t* mode_entry) { uint8_t retval = CW_OFFSET_MODE_DEFAULT; for (uint8_t cw_offset_mode = 0; cw_offset_mode < CW_OFFSET_NUM; cw_offset_mode++) { if (cw_mode_map[cw_offset_mode].dial_mode == mode_entry->dial_mode && cw_mode_map[cw_offset_mode].sideband_mode == mode_entry->sideband_mode ) { retval = cw_offset_mode; break; } } return retval; } void RadioManagement_ToggleVfoAB() { uint8_t vfo_new,vfo_active; if(is_vfo_b()) // LSB on VFO mode byte set? { vfo_new = VFO_A; vfo_active = VFO_B; ts.vfo_mem_mode &= ~VFO_MEM_MODE_VFO_B; // yes, it's now VFO-B mode, so clear it, setting it to VFO A mode } else // LSB on VFO mode byte NOT set? { ts.vfo_mem_mode |= VFO_MEM_MODE_VFO_B; // yes, it's now in VFO-A mode, so set it, setting it to VFO B mode vfo_new = VFO_B; vfo_active = VFO_A; } // vfo[vfo_active].band[ts.band->band_mode].dial_value = df.tune_old; //band_dial_value[ts.band]; // save "VFO B" settings vfo[vfo_active].band[ts.band->band_mode].dial_value = df.tune_new; vfo[vfo_active].band[ts.band->band_mode].decod_mode = ts.dmod_mode; //band_decod_mode[ts.band]; vfo[vfo_active].band[ts.band->band_mode].digital_mode = ts.digital_mode; vfo[vfo_active].band[ts.band->band_mode].dial_delta = ts.iq_freq_delta; df.tune_new = vfo[vfo_new].band[ts.band->band_mode].dial_value; ts.iq_freq_delta = vfo[vfo_new].band[ts.band->band_mode].dial_delta; bool digitalModeDiffers = ts.digital_mode != vfo[vfo_new].band[ts.band->band_mode].digital_mode; bool newIsDigitalMode = vfo[vfo_new].band[ts.band->band_mode].decod_mode == DEMOD_DIGI; ts.digital_mode = vfo[vfo_new].band[ts.band->band_mode].digital_mode; if(ts.dmod_mode != vfo[vfo_new].band[ts.band->band_mode].decod_mode || (newIsDigitalMode && digitalModeDiffers) ) { RadioManagement_SetDemodMode(vfo[vfo_new].band[ts.band->band_mode].decod_mode); } nr_params.first_time = 1; // restart in case of VFO-Toggle } /** * @returns true == fm deviation 5khz, false == fm deviation = 2.5khz */ bool RadioManagement_FmDevIs5khz() { return (ts.flags2 & FLAGS2_FM_MODE_DEVIATION_5KHZ) != 0; } /** * @param is5khz true == fm deviation 5khz, false == fm deviation = 2.5khz */ void RadioManagement_FmDevSet5khz(bool is5khz) { if (is5khz) { ts.flags2 |= FLAGS2_FM_MODE_DEVIATION_5KHZ; } else { ts.flags2 &= ~FLAGS2_FM_MODE_DEVIATION_5KHZ; } } bool RadioManagement_TxPermitted() { return ts.dmod_mode != DEMOD_SAM && RadioManagement_IsTxDisabled(); } bool RadioManagement_Transverter_IsEnabled() { return (ts.xverter_mode & 0xf) > 0; } uint64_t RadioManagement_Transverter_GetFreq(const uint32_t dial_freq, const uint8_t trx_mode) { uint32_t xverter_offset = (ts.xverter_offset_tx != 0 && trx_mode == TRX_MODE_TX) ? ts.xverter_offset_tx : ts.xverter_offset; uint64_t offset_multiplier = xverter_offset>XVERTER_OFFSET_MAX_HZ? 1000 : 1; uint64_t offset_offset = xverter_offset - (xverter_offset>XVERTER_OFFSET_MAX_HZ ? ((XVERTER_OFFSET_MAX_HZ)-XVERTER_OFFSET_MAX_HZ/1000) : 0); return dial_freq * ts.xverter_mode + offset_offset * offset_multiplier; }