2242 lines
77 KiB
C
2242 lines
77 KiB
C
/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */
|
|
/************************************************************************************
|
|
** **
|
|
** UHSDR **
|
|
** a powerful firmware for STM32 based SDR transceivers **
|
|
** **
|
|
**---------------------------------------------------------------------------------**
|
|
** **
|
|
** File name: **
|
|
** Description: **
|
|
** Last Modified: **
|
|
** Licence: GNU GPLv3 **
|
|
************************************************************************************/
|
|
|
|
// Common
|
|
#include <assert.h>
|
|
#include "radio_management.h"
|
|
#include "profiling.h"
|
|
#include "adc.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <soft_tcxo.h>
|
|
|
|
#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;
|
|
}
|