Files
uv-k1-k5v3-firmware-custom/App/radio.c
T

1348 lines
42 KiB
C

/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "driver/bk4819-regs.h"
#include <string.h>
#include "am_fix.h"
#include "app/dtmf.h"
#ifdef ENABLE_FMRADIO
#include "app/fm.h"
#endif
#include "audio.h"
#include "dcs.h"
#include "driver/bk4819.h"
#include "driver/py25q16.h"
#include "driver/gpio.h"
#include "driver/system.h"
#include "frequencies.h"
#include "functions.h"
#include "helper/battery.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/menu.h"
VFO_Info_t *gTxVfo;
VFO_Info_t *gRxVfo;
VFO_Info_t *gCurrentVfo;
DCS_CodeType_t gCurrentCodeType;
VfoState_t VfoState[2];
const char gModulationStr[MODULATION_UKNOWN][4] = {
[MODULATION_FM]="FM",
[MODULATION_AM]="AM",
[MODULATION_USB]="USB",
#ifdef ENABLE_BYP_RAW_DEMODULATORS
[MODULATION_BYP]="BYP",
[MODULATION_RAW]="RAW"
#endif
};
#ifdef ENABLE_FEAT_F4HWN_AUDIO
// About BK4819_WriteRegister(0x2b, val) experimentation...
//
// 0x000: 300 Hz high-pass filter enabled, 3 kHz low-pass filter enabled, de-emphasis enabled.
// Audio impression: the most "classic radio" tuning, more filtered and smoother.
//
// 0x300: 300 Hz high-pass filter enabled, 3 kHz low-pass filter disabled, de-emphasis disabled.
// Audio impression: clearer, brighter, and more open, while still cutting low frequencies.
//
// 0x400: 300 Hz high-pass filter disabled, 3 kHz low-pass filter enabled, de-emphasis enabled.
// Audio impression: fuller low end, but still softened by de-emphasis and upper-frequency limiting.
//
// 0x500: 300 Hz high-pass filter disabled, 3 kHz low-pass filter enabled, de-emphasis disabled.
// Audio impression: fuller bass, more direct sound, while still keeping the 3 kHz top-end limit.
static void AUDIO_ApplyFMProfile(uint8_t profile)
{ // | 0x54 || 0x55 |
static const uint16_t fm_profiles[][2] = {
{0x9009, 0x3200}, // 0: FLAT
{0x9009, 0x33A9}, // 1: CLEAN
{0x9009, 0x3600}, // 2: MID
{0x8546, 0x3AF0}, // 3: BOOST
{0x8566, 0x3D00} // 4: MAX
};
if (profile >= ARRAY_SIZE(fm_profiles))
profile = 0;
BK4819_WriteRegister(0x54, fm_profiles[profile][0]);
BK4819_WriteRegister(0x55, fm_profiles[profile][1]);
}
static void AUDIO_ApplyAMProfile(uint8_t profile)
{ // | 0x2b || 0x2f || 0x54 || 0x55 |
static const uint16_t am_profiles[][4] = {
// SHARP (ALPHA test profile) - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), low IF gain (REG55 bits[11:8]=1, ref=169)
// Selective and crisp, best adjacent channel rejection, may sound harsh on strong signals
{0x0300, 0x9990, 0x9009, 0x31A9},
// STOCK - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), moderate IF gain (REG55 bits[11:8]=4, ref=180)
// Selective filter with balanced gain, punchy and detailed, good compromise between rejection and sensitivity
{0x0500, 0x9990, 0x9009, 0x31A9},
// OPEN (BRAVO test profile) - Medium-wide IF filter (REG54 bits[14:8]=8, bits[7:0]=70), high IF gain (REG55 bits[11:8]=8, ref=192)
// Wide and pleasant, better sensitivity on weak signals, may struggle with adjacent channel interference
{0x0300, 0x9990, 0x8846, 0x38C0}
};
if (profile >= ARRAY_SIZE(am_profiles))
profile = 0;
BK4819_WriteRegister(0x2b, am_profiles[profile][0]);
BK4819_WriteRegister(0x2f, am_profiles[profile][1]);
BK4819_WriteRegister(0x54, am_profiles[profile][2]);
BK4819_WriteRegister(0x55, am_profiles[profile][3]);
}
static void AUDIO_ApplyUSBProfile(void)
{
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31A9);
}
#endif
bool RADIO_CheckValidList(uint8_t scanList)
{
if(scanList == MR_CHANNELS_LIST + 1)
return true;
for (uint16_t i = 0; IS_MR_CHANNEL(i); i++) {
const ChannelAttributes_t* att = MR_GetChannelAttributes(i);
if(att->scanlist == scanList && att->exclude == false)
{
return true;
}
}
return false;
}
void RADIO_NextValidList(int8_t direction)
{
uint8_t startList = gEeprom.SCAN_LIST_DEFAULT;
uint8_t attempts = 0;
const uint8_t MAX_VALUE = MR_CHANNELS_LIST + 1; // 25 (1-24 lists + ALL)
do {
if (direction > 0) {
// Forward: 1 → 2 → ... → 25 → 1
gEeprom.SCAN_LIST_DEFAULT = (gEeprom.SCAN_LIST_DEFAULT % MAX_VALUE) + 1;
} else {
// Backward: 25 → 24 → ... → 1 → 25
gEeprom.SCAN_LIST_DEFAULT = ((gEeprom.SCAN_LIST_DEFAULT - 2 + MAX_VALUE) % MAX_VALUE) + 1;
}
attempts++;
if (RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT))
return;
} while (gEeprom.SCAN_LIST_DEFAULT != startList && attempts < MAX_VALUE);
// Safety fallback: switch to ALL mode
if (!RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT)) {
gEeprom.SCAN_LIST_DEFAULT = MAX_VALUE; // ALL (25)
}
}
bool RADIO_CheckValidChannel(uint16_t channel, bool checkScanList, uint8_t scanList)
{
const ChannelAttributes_t* att = MR_GetChannelAttributes(channel);
// return true if the channel appears valid
if (!IS_MR_CHANNEL(channel))
return false;
if (checkScanList && att->exclude == true)
return false;
if (att->band > BAND7_470MHz)
return false;
if (!checkScanList || (scanList > MR_CHANNELS_LIST && att->scanlist != 0) || (scanList > 0 && att->scanlist == MR_CHANNELS_LIST + 1))
return true;
if ((scanList == 0) || (scanList != att->scanlist)) {
return false;
}
// Exclude priority channels ONLY if SCAN_LIST_ENABLED is active
// Otherwise, treat them as normal channels in the list
if (gEeprom.SCAN_LIST_ENABLED)
{
const uint16_t PriorityCh1 = gEeprom.SCANLIST_PRIORITY_CH[0];
const uint16_t PriorityCh2 = gEeprom.SCANLIST_PRIORITY_CH[1];
if (PriorityCh1 == channel || PriorityCh2 == channel)
return false; // Excluded because it's a priority channel and they are enabled
}
return true;
}
uint16_t RADIO_FindNextChannel(uint16_t Channel, int8_t Direction, bool bCheckScanList, uint8_t VFO)
{
for (uint16_t i = 0; IS_MR_CHANNEL(i); i++, Channel += Direction) {
if (Channel == 0xFFFF) {
Channel = MR_CHANNEL_LAST;
} else if (!IS_MR_CHANNEL(Channel)) {
Channel = MR_CHANNEL_FIRST;
}
if (RADIO_CheckValidChannel(Channel, bCheckScanList, VFO)) {
return Channel;
}
}
return 0xFFFF;
}
void RADIO_InitInfo(VFO_Info_t *pInfo, const uint16_t ChannelSave, const uint32_t Frequency)
{
memset(pInfo, 0, sizeof(*pInfo));
pInfo->Band = FREQUENCY_GetBand(Frequency);
pInfo->SCANLIST_PARTICIPATION = 0;
pInfo->STEP_SETTING = STEP_12_5kHz;
pInfo->StepFrequency = gStepFrequencyTable[pInfo->STEP_SETTING];
pInfo->CHANNEL_SAVE = ChannelSave;
pInfo->FrequencyReverse = false;
pInfo->TX_LOCK = true;
pInfo->OUTPUT_POWER = OUTPUT_POWER_LOW1;
pInfo->freq_config_RX.Frequency = Frequency;
pInfo->freq_config_TX.Frequency = Frequency;
pInfo->pRX = &pInfo->freq_config_RX;
pInfo->pTX = &pInfo->freq_config_TX;
pInfo->Compander = 0; // off
if (ChannelSave == (FREQ_CHANNEL_FIRST + BAND2_108MHz))
pInfo->Modulation = MODULATION_AM;
else
pInfo->Modulation = MODULATION_FM;
RADIO_ConfigureSquelchAndOutputPower(pInfo);
}
void RADIO_ValidateAndSetCode(FREQ_Config_t *pFreq_Config, uint8_t tmp) {
switch (pFreq_Config->CodeType) {
default:
case CODE_TYPE_OFF:
pFreq_Config->CodeType = CODE_TYPE_OFF;
tmp = 0;
break;
case CODE_TYPE_CONTINUOUS_TONE:
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
if (tmp > ((pFreq_Config->CodeType == CODE_TYPE_CONTINUOUS_TONE ? ARRAY_SIZE(CTCSS_Options) : ARRAY_SIZE(DCS_Options)) - 1))
tmp = 0;
break;
}
pFreq_Config->Code = tmp;
}
void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure)
{
VFO_Info_t *pVfo = &gEeprom.VfoInfo[VFO];
if (!gSetting_350EN) {
if (gEeprom.FreqChannel[VFO] == FREQ_CHANNEL_FIRST + BAND5_350MHz)
gEeprom.FreqChannel[VFO] = FREQ_CHANNEL_FIRST + BAND6_400MHz;
if (gEeprom.ScreenChannel[VFO] == FREQ_CHANNEL_FIRST + BAND5_350MHz)
gEeprom.ScreenChannel[VFO] = FREQ_CHANNEL_FIRST + BAND6_400MHz;
}
uint16_t channel = gEeprom.ScreenChannel[VFO];
if (IS_VALID_CHANNEL(channel)) {
#ifdef ENABLE_NOAA
if (IS_NOAA_CHANNEL(channel))
{
RADIO_InitInfo(pVfo, gEeprom.ScreenChannel[VFO], NoaaFrequencyTable[channel - NOAA_CHANNEL_FIRST]);
if (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF)
return;
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
gUpdateStatus = true;
return;
}
#endif
if (IS_MR_CHANNEL(channel)) {
channel = RADIO_FindNextChannel(channel, RADIO_CHANNEL_UP, false, VFO);
if (channel == 0xFFFF) {
channel = gEeprom.FreqChannel[VFO];
gEeprom.ScreenChannel[VFO] = gEeprom.FreqChannel[VFO];
}
else {
gEeprom.ScreenChannel[VFO] = channel;
gEeprom.MrChannel[VFO] = channel;
}
}
}
else
channel = FREQ_CHANNEL_LAST - 1;
ChannelAttributes_t* att = MR_GetChannelAttributes(channel);
if (att->__val == 0xFFFF) { // invalid/unused channel
if (IS_MR_CHANNEL(channel)) {
channel = gEeprom.FreqChannel[VFO];
gEeprom.ScreenChannel[VFO] = channel;
}
uint16_t bandIdx = channel - FREQ_CHANNEL_FIRST;
RADIO_InitInfo(pVfo, channel, frequencyBandTable[bandIdx].lower);
return;
}
uint8_t band = att->band;
if (band > BAND7_470MHz) {
band = BAND6_400MHz;
}
uint8_t bParticipation;
if (IS_MR_CHANNEL(channel)) {
bParticipation = att->scanlist;
}
else {
band = channel - FREQ_CHANNEL_FIRST;
bParticipation = MR_CHANNELS_LIST + 1;
}
pVfo->Band = band;
pVfo->SCANLIST_PARTICIPATION = bParticipation;
pVfo->CHANNEL_SAVE = channel;
uint32_t base;
if (IS_MR_CHANNEL(channel))
base = channel * 16;
else
base = 0x009000 + ((channel - FREQ_CHANNEL_FIRST) * 32) + (VFO * 16);
if (configure == VFO_CONFIGURE_RELOAD || IS_FREQ_CHANNEL(channel))
{
uint8_t tmp;
uint8_t data[8];
// ***************
PY25Q16_ReadBuffer(base + 8, data, sizeof(data));
tmp = data[3] & 0x0F;
if (tmp > TX_OFFSET_FREQUENCY_DIRECTION_SUB)
tmp = 0;
pVfo->TX_OFFSET_FREQUENCY_DIRECTION = tmp;
tmp = data[3] >> 4;
if (tmp >= MODULATION_UKNOWN)
tmp = MODULATION_FM;
pVfo->Modulation = tmp;
tmp = data[6];
if (tmp >= STEP_N_ELEM)
tmp = STEP_12_5kHz;
pVfo->STEP_SETTING = tmp;
pVfo->StepFrequency = gStepFrequencyTable[tmp];
tmp = data[7];
#ifndef ENABLE_FEAT_F4HWN
if (tmp > (ARRAY_SIZE(gSubMenu_SCRAMBLER) - 1))
tmp = 0;
pVfo->SCRAMBLING_TYPE = tmp;
#else
pVfo->SCRAMBLING_TYPE = 0;
#endif
pVfo->freq_config_RX.CodeType = (data[2] >> 0) & 0x0F;
pVfo->freq_config_TX.CodeType = (data[2] >> 4) & 0x0F;
RADIO_ValidateAndSetCode(&pVfo->freq_config_RX, data[0]);
RADIO_ValidateAndSetCode(&pVfo->freq_config_TX, data[1]);
if (data[4] == 0xFF)
{
pVfo->FrequencyReverse = false;
pVfo->CHANNEL_BANDWIDTH = BK4819_FILTER_BW_WIDE;
pVfo->OUTPUT_POWER = OUTPUT_POWER_LOW1;
pVfo->BUSY_CHANNEL_LOCK = false;
pVfo->TX_LOCK = true;
}
else
{
const uint8_t d4 = data[4];
pVfo->FrequencyReverse = !!((d4 >> 0) & 1u);
pVfo->CHANNEL_BANDWIDTH = !!((d4 >> 1) & 1u);
pVfo->OUTPUT_POWER = ((d4 >> 2) & 7u);
pVfo->BUSY_CHANNEL_LOCK = !!((d4 >> 5) & 1u);
pVfo->TX_LOCK = !!((d4 >> 6) & 1u);
}
if (data[5] == 0xFF)
{
#ifdef ENABLE_DTMF_CALLING
pVfo->DTMF_DECODING_ENABLE = false;
#endif
pVfo->DTMF_PTT_ID_TX_MODE = PTT_ID_OFF;
}
else
{
#ifdef ENABLE_DTMF_CALLING
pVfo->DTMF_DECODING_ENABLE = ((data[5] >> 0) & 1u) ? true : false;
#endif
uint8_t pttId = ((data[5] >> 1) & 7u);
pVfo->DTMF_PTT_ID_TX_MODE = pttId < ARRAY_SIZE(gSubMenu_PTT_ID) ? pttId : PTT_ID_OFF;
}
// ***************
struct {
uint32_t Frequency;
uint32_t Offset;
} __attribute__((packed)) info;
PY25Q16_ReadBuffer(base, &info, sizeof(info));
if(info.Frequency==0xFFFFFFFF)
pVfo->freq_config_RX.Frequency = frequencyBandTable[band].lower;
else
pVfo->freq_config_RX.Frequency = info.Frequency;
if (info.Offset >= _1GHz_in_KHz)
info.Offset = _1GHz_in_KHz / 100;
pVfo->TX_OFFSET_FREQUENCY = info.Offset;
// ***************
}
uint32_t frequency = pVfo->freq_config_RX.Frequency;
// fix previously set incorrect band
band = FREQUENCY_GetBand(frequency);
if (frequency < frequencyBandTable[band].lower)
frequency = frequencyBandTable[band].lower;
else if (frequency > frequencyBandTable[band].upper)
frequency = frequencyBandTable[band].upper;
else if (channel >= FREQ_CHANNEL_FIRST)
frequency = FREQUENCY_RoundToStep(frequency, pVfo->StepFrequency);
pVfo->freq_config_RX.Frequency = frequency;
if (frequency >= frequencyBandTable[BAND2_108MHz].upper && frequency < frequencyBandTable[BAND2_108MHz].upper)
pVfo->TX_OFFSET_FREQUENCY_DIRECTION = TX_OFFSET_FREQUENCY_DIRECTION_OFF;
else if (!IS_MR_CHANNEL(channel))
pVfo->TX_OFFSET_FREQUENCY = FREQUENCY_RoundToStep(pVfo->TX_OFFSET_FREQUENCY, pVfo->StepFrequency);
RADIO_ApplyOffset(pVfo);
if (IS_MR_CHANNEL(channel))
{ // 16 bytes allocated to the channel name but only 10 used, the rest are 0's
SETTINGS_FetchChannelName(pVfo->Name, channel);
}
if (!pVfo->FrequencyReverse)
{
pVfo->pRX = &pVfo->freq_config_RX;
pVfo->pTX = &pVfo->freq_config_TX;
}
else
{
pVfo->pRX = &pVfo->freq_config_TX;
pVfo->pTX = &pVfo->freq_config_RX;
}
if (!gSetting_350EN)
{
FREQ_Config_t *pConfig = pVfo->pRX;
if (pConfig->Frequency >= 35000000 && pConfig->Frequency < 40000000)
pConfig->Frequency = 43300000;
}
pVfo->Compander = att->compander;
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
if(gRemoveOffset)
{
pVfo->pTX = &pVfo->freq_config_RX;
}
if(gPowerHigh)
{
pVfo->OUTPUT_POWER = OUTPUT_POWER_HIGH;
}
#endif
RADIO_ConfigureSquelchAndOutputPower(pVfo);
}
void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
{
// *******************************
// squelch
FREQUENCY_Band_t Band = FREQUENCY_GetBand(pInfo->pRX->Frequency);
// 0x1E60 : 0x1E00
uint32_t Base = (Band < BAND4_174MHz) ? 0x010060 : 0x010000;
if (gEeprom.SQUELCH_LEVEL == 0)
{ // squelch == 0 (off)
pInfo->SquelchOpenRSSIThresh = 0; // 0 ~ 255
pInfo->SquelchOpenNoiseThresh = 127; // 127 ~ 0
pInfo->SquelchCloseGlitchThresh = 255; // 255 ~ 0
pInfo->SquelchCloseRSSIThresh = 0; // 0 ~ 255
pInfo->SquelchCloseNoiseThresh = 127; // 127 ~ 0
pInfo->SquelchOpenGlitchThresh = 255; // 255 ~ 0
}
else
{ // squelch >= 1
Base += gEeprom.SQUELCH_LEVEL; // my eeprom squelch-1
// VHF UHF
uint8_t *sq_ptrs[6] = {
&pInfo->SquelchOpenRSSIThresh, // 50 10
&pInfo->SquelchCloseRSSIThresh, // 40 5
&pInfo->SquelchOpenNoiseThresh, // 65 90
&pInfo->SquelchCloseNoiseThresh, // 70 100
&pInfo->SquelchCloseGlitchThresh, // 90 90
&pInfo->SquelchOpenGlitchThresh // 100 100
};
for(uint8_t i = 0; i < 6; i++) {
PY25Q16_ReadBuffer(Base + (i * 0x10), sq_ptrs[i], 1);
}
uint16_t noise_open = pInfo->SquelchOpenNoiseThresh;
uint16_t noise_close = pInfo->SquelchCloseNoiseThresh;
#if ENABLE_SQUELCH_MORE_SENSITIVE
uint16_t rssi_open = pInfo->SquelchOpenRSSIThresh;
uint16_t rssi_close = pInfo->SquelchCloseRSSIThresh;
uint16_t glitch_open = pInfo->SquelchOpenGlitchThresh;
uint16_t glitch_close = pInfo->SquelchCloseGlitchThresh;
// make squelch more sensitive
// note that 'noise' and 'glitch' values are inverted compared to 'rssi' values
rssi_open = (rssi_open * 1) / 2;
noise_open = (noise_open * 2) / 1;
glitch_open = (glitch_open * 2) / 1;
// ensure the 'close' threshold is lower than the 'open' threshold
if (rssi_close == rssi_open && rssi_close >= 2)
rssi_close -= 2;
if (noise_close == noise_open && noise_close <= 125)
noise_close += 2;
if (glitch_close == glitch_open && glitch_close <= 253)
glitch_close += 2;
pInfo->SquelchOpenRSSIThresh = MIN(rssi_open, 255);
pInfo->SquelchCloseRSSIThresh = MIN(rssi_close, 255);
pInfo->SquelchOpenGlitchThresh = MIN(glitch_open, 255);
pInfo->SquelchCloseGlitchThresh = MIN(glitch_close, 255);
#endif
pInfo->SquelchOpenNoiseThresh = MIN(noise_open, 127);
pInfo->SquelchCloseNoiseThresh = MIN(noise_close, 127);
}
// *******************************
// output power
Band = FREQUENCY_GetBand(pInfo->pTX->Frequency);
// my eeprom calibration data on UV-K5 (V1)
//
// 1ED0 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 50 MHz
// 1EE0 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 108 MHz
// 1EF0 5f 5f 5f 69 69 69 87 87 87 ff ff ff ff ff ff ff 137 MHz
// 1F00 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 174 MHz
// 1F10 5f 5f 5f 69 69 69 87 87 87 ff ff ff ff ff ff ff 350 MHz
// 1F20 5f 5f 5f 69 69 69 87 87 87 ff ff ff ff ff ff ff 400 MHz
// 1F30 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 470 MHz
// my eeprom calibration data on UV-K1
// 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 50 MHz
// 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 108 MHz
// 4b 4b 4b 78 78 78 96 96 96 ff ff ff ff ff ff ff 137 MHz
// 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 174 MHz
// 5a 5a 5a 64 64 64 a0 a0 a0 ff ff ff ff ff ff ff 350 MHz
// 4b 4b 4b 78 78 78 96 96 96 ff ff ff ff ff ff ff 400 MHz
// 32 32 32 64 64 64 94 8c 8c ff ff ff ff ff ff ff 470 MHz
uint8_t Txp[3];
uint8_t Op = 0; // Low eeprom calibration data
uint8_t currentPower = pInfo->OUTPUT_POWER;
if (currentPower == OUTPUT_POWER_USER)
currentPower = gSetting_set_pwr;
else
currentPower--;
if (currentPower == 5)
Op = 1; // Mid eeprom calibration data
else if (currentPower == 6)
Op = 2; // High eeprom calibration data
PY25Q16_ReadBuffer(0x100D0 + (Band * 16) + (Op * 3), Txp, 3);
#ifdef ENABLE_FEAT_F4HWN
// make low and mid even lower
// and use calibration values
// be aware with toxic fucking closed firmwares
/*
uint8_t shift[] = {0, 0, 0, 0, 0};
if(Band == 5) // UHF
{
shift[0] = 0;
shift[1] = 0;
shift[2] = 0;
shift[3] = 0;
shift[4] = 0;
}
*/
/*
for(uint8_t p = 0; p < 3; p++)
{
switch (currentPower)
{
case 0:
Txp[p] = (Txp[p] * 4) / 25; //+ shift[pInfo->OUTPUT_POWER];
break;
case 1:
Txp[p] = (Txp[p] * 4) / 19; // + shift[pInfo->OUTPUT_POWER];
break;
case 2:
Txp[p] = (Txp[p] * 4) / 13; // + shift[pInfo->OUTPUT_POWER];
break;
case 3:
Txp[p] = (Txp[p] * 4) / 10; // + shift[pInfo->OUTPUT_POWER];
break;
case 4:
Txp[p] = (Txp[p] * 4) / 7; // + shift[pInfo->OUTPUT_POWER];
break;
case 5:
Txp[p] = (Txp[p] * 3) / 4;
break;
case 6:
Txp[p] = Txp[p] + 30;
break;
}
}
*/
//static const uint8_t dividers[6] = { 25, 19, 13, 10, 7, 4}; // For UV-K5 V1
static const uint8_t dividers_band2[6] = { 20, 15, 10, 8, 6, 4 };
static const uint8_t dividers_band5[6] = { 25, 19, 13, 9, 6, 4 }; // Need to improve measure...
const uint8_t *dividers;
if (Band == 2) // VHF
dividers = dividers_band2;
else // UHF
dividers = dividers_band5;
for (uint8_t p = 0; p < 3; p++)
{
if (currentPower < 6)
{
Txp[p] = (Txp[p] * (currentPower == 5 ? 3 : 4)) / dividers[currentPower];
}
else // case 6
{
// Txp[p] += 30; // For UV-K5 V1
Txp[p] += 24;
}
}
#else
#ifdef ENABLE_REDUCE_LOW_MID_TX_POWER
// make low and mid even lower
if (pInfo->OUTPUT_POWER == OUTPUT_POWER_LOW) {
Txp[0] /= 5;
Txp[1] /= 5;
Txp[2] /= 5;
}
else if (pInfo->OUTPUT_POWER == OUTPUT_POWER_MID){
Txp[0] /= 3;
Txp[1] /= 3;
Txp[2] /= 3;
}
#endif
#endif
pInfo->TXP_CalculatedSetting = FREQUENCY_CalculateOutputPower(
Txp[0],
Txp[1],
Txp[2],
frequencyBandTable[Band].lower,
(frequencyBandTable[Band].lower + frequencyBandTable[Band].upper) / 2,
frequencyBandTable[Band].upper,
pInfo->pTX->Frequency);
// *******************************
}
void RADIO_ApplyOffset(VFO_Info_t *pInfo)
{
uint32_t Frequency = pInfo->freq_config_RX.Frequency;
if (pInfo->TX_OFFSET_FREQUENCY_DIRECTION == TX_OFFSET_FREQUENCY_DIRECTION_ADD)
Frequency += pInfo->TX_OFFSET_FREQUENCY;
else if (pInfo->TX_OFFSET_FREQUENCY_DIRECTION == TX_OFFSET_FREQUENCY_DIRECTION_SUB)
Frequency -= pInfo->TX_OFFSET_FREQUENCY;
pInfo->freq_config_TX.Frequency = Frequency;
}
static void RADIO_SelectCurrentVfo(void)
{
// if crossband is active and DW not the gCurrentVfo is gTxVfo (gTxVfo/TX_VFO is only ever changed by the user)
// otherwise it is set to gRxVfo which is set to gTxVfo in RADIO_SelectVfos
// so in the end gCurrentVfo is equal to gTxVfo unless dual watch changes it on incomming transmition (again, this can only happen when XB off)
// note: it is called only in certain situations so could be not up-to-date
gCurrentVfo = (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF || gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) ? gRxVfo : gTxVfo;
}
void RADIO_SelectVfos(void)
{
// if crossband without DW is used then RX_VFO is the opposite to the TX_VFO
gEeprom.RX_VFO = (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF || gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) ? gEeprom.TX_VFO : !gEeprom.TX_VFO;
gTxVfo = &gEeprom.VfoInfo[gEeprom.TX_VFO];
gRxVfo = &gEeprom.VfoInfo[gEeprom.RX_VFO];
RADIO_SelectCurrentVfo();
}
BK4819_FilterBandwidth_t RADIO_GetAMFilterBandwidth(const VFO_Info_t *pVfo)
{
// On BK4829, AM "wide" intentionally reuses the wider RF filter preset.
return (pVfo->CHANNEL_BANDWIDTH == BANDWIDTH_WIDE) ? BK4819_FILTER_BW_WIDE : BK4819_FILTER_BW_AM;
}
void RADIO_SetupRegisters(bool switchToForeground)
{
BK4819_FilterBandwidth_t Bandwidth = gRxVfo->CHANNEL_BANDWIDTH;
#ifdef ENABLE_FEAT_F4HWN_NARROWER
if(Bandwidth == BK4819_FILTER_BW_NARROW && gSetting_set_nfm == 1)
{
Bandwidth = BK4819_FILTER_BW_NARROWER;
}
#endif
AUDIO_AudioPathOff();
gEnableSpeaker = false;
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
if (gRxVfo->Modulation == MODULATION_AM)
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
else
{
switch (Bandwidth)
{
default:
Bandwidth = BK4819_FILTER_BW_WIDE;
[[fallthrough]];
case BK4819_FILTER_BW_WIDE:
case BK4819_FILTER_BW_NARROW:
case BK4819_FILTER_BW_NARROWER:
#ifdef ENABLE_AM_FIX
// BK4819_SetFilterBandwidth(Bandwidth, gRxVfo->Modulation == MODULATION_AM && gSetting_AM_fix);
BK4819_SetFilterBandwidth(Bandwidth, true);
#else
BK4819_SetFilterBandwidth(Bandwidth, false);
#endif
break;
}
}
BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false);
BK4819_SetupPowerAmplifier(0, 0);
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
while (1)
{
const uint16_t Status = BK4819_ReadRegister(BK4819_REG_0C);
if ((Status & 1u) == 0) // INTERRUPT REQUEST
break;
BK4819_WriteRegister(BK4819_REG_02, 0);
SYSTEM_DelayMs(1);
}
BK4819_WriteRegister(BK4819_REG_3F, 0);
// mic gain 0.5dB/step 0 to 63
BK4819_WriteRegister(BK4819_REG_7D, 0xE940 | (gEeprom.MIC_SENSITIVITY_TUNING & 0x3f));
uint32_t Frequency;
#ifdef ENABLE_NOAA
if (!IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE) || !gIsNoaaMode)
Frequency = gRxVfo->pRX->Frequency;
else
Frequency = NoaaFrequencyTable[gNoaaChannel];
#else
Frequency = gRxVfo->pRX->Frequency;
#endif
BK4819_SetFrequency(Frequency);
// Keep the demodulator in sync when retuning without entering RX audio.
RADIO_SetModulation(gRxVfo->Modulation);
BK4819_SetupSquelch(
gRxVfo->SquelchOpenRSSIThresh, gRxVfo->SquelchCloseRSSIThresh,
gRxVfo->SquelchOpenNoiseThresh, gRxVfo->SquelchCloseNoiseThresh,
gRxVfo->SquelchCloseGlitchThresh, gRxVfo->SquelchOpenGlitchThresh);
BK4819_PickRXFilterPathBasedOnFrequency(Frequency);
// what does this in do ?
BK4819_ToggleGpioOut(BK4819_GPIO0_PIN28_RX_ENABLE, true);
// AF RX Gain and DAC
//BK4819_WriteRegister(BK4819_REG_48, 0xB3A8); // 1011 00 111010 1000
BK4819_SetRxAudioGain();
uint16_t InterruptMask = BK4819_REG_3F_SQUELCH_FOUND | BK4819_REG_3F_SQUELCH_LOST;
#ifdef ENABLE_NOAA
if (!IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE))
#endif
{
if (gRxVfo->Modulation == MODULATION_FM)
{ // FM
uint8_t CodeType = gRxVfo->pRX->CodeType;
uint8_t Code = gRxVfo->pRX->Code;
switch (CodeType)
{
default:
case CODE_TYPE_OFF:
BK4819_SetCTCSSFrequency(SQL_TONE);
BK4819_SetTailDetection(SQL_TONE); // Default 550 = QS's 55Hz tone method
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL;
break;
case CODE_TYPE_CONTINUOUS_TONE:
BK4819_SetCTCSSFrequency(CTCSS_Options[Code]);
//#ifndef ENABLE_CTCSS_TAIL_PHASE_SHIFT
// BK4819_SetTailDetection(550); // QS's 55Hz tone method
//#else
// BK4819_SetTailDetection(CTCSS_Options[Code]);
//#endif
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL
| BK4819_REG_3F_CTCSS_FOUND
| BK4819_REG_3F_CTCSS_LOST;
break;
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_SetCDCSSCodeWord(DCS_GetGolayCodeWord(CodeType, Code));
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL
| BK4819_REG_3F_CDCSS_FOUND
| BK4819_REG_3F_CDCSS_LOST;
break;
}
#ifndef ENABLE_FEAT_F4HWN
if (gRxVfo->SCRAMBLING_TYPE > 0 && gSetting_ScrambleEnable)
BK4819_EnableScramble(gRxVfo->SCRAMBLING_TYPE - 1);
else
BK4819_DisableScramble();
#else
BK4819_DisableScramble();
#endif
}
}
#ifdef ENABLE_NOAA
else
{
BK4819_SetCTCSSFrequency(2625);
InterruptMask |= BK4819_REG_3F_CTCSS_FOUND
| BK4819_REG_3F_CTCSS_LOST;
}
#endif
#ifdef ENABLE_VOX
if (gEeprom.VOX_SWITCH && gCurrentVfo->Modulation == MODULATION_FM
#ifdef ENABLE_NOAA
&& !IS_NOAA_CHANNEL(gCurrentVfo->CHANNEL_SAVE)
#endif
#ifdef ENABLE_FMRADIO
&& !gFmRadioMode
#endif
){
BK4819_EnableVox(gEeprom.VOX1_THRESHOLD, gEeprom.VOX0_THRESHOLD);
InterruptMask |= BK4819_REG_3F_VOX_FOUND | BK4819_REG_3F_VOX_LOST;
}
else
#endif
{
BK4819_DisableVox();
}
// RX expander
BK4819_SetCompander((gRxVfo->Modulation == MODULATION_FM && gRxVfo->Compander >= 2) ? gRxVfo->Compander : 0);
BK4819_EnableDTMF();
InterruptMask |= BK4819_REG_3F_DTMF_5TONE_FOUND;
RADIO_SetupAGC(gRxVfo->Modulation == MODULATION_AM, false);
//RADIO_SetupAGC(false, false);
// enable/disable BK4819 selected interrupts
BK4819_WriteRegister(BK4819_REG_3F, InterruptMask);
FUNCTION_Init();
if (switchToForeground)
FUNCTION_Select(FUNCTION_FOREGROUND);
}
#ifdef ENABLE_NOAA
void RADIO_ConfigureNOAA(void)
{
uint8_t ChanAB;
gUpdateStatus = true;
if (gEeprom.NOAA_AUTO_SCAN)
{
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF)
{
if (!IS_NOAA_CHANNEL(gEeprom.ScreenChannel[0]))
{
if (!IS_NOAA_CHANNEL(gEeprom.ScreenChannel[1]))
{
gIsNoaaMode = false;
return;
}
ChanAB = 1;
}
else
ChanAB = 0;
if (!gIsNoaaMode)
gNoaaChannel = gEeprom.VfoInfo[ChanAB].CHANNEL_SAVE - NOAA_CHANNEL_FIRST;
gIsNoaaMode = true;
return;
}
if (IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE))
{
gIsNoaaMode = true;
gNoaaChannel = gRxVfo->CHANNEL_SAVE - NOAA_CHANNEL_FIRST;
gNOAA_Countdown_10ms = NOAA_countdown_2_10ms;
gScheduleNOAA = false;
}
else
gIsNoaaMode = false;
}
else
gIsNoaaMode = false;
}
#endif
void RADIO_SetTxParameters(void)
{
BK4819_FilterBandwidth_t Bandwidth = gCurrentVfo->CHANNEL_BANDWIDTH;
#ifdef ENABLE_FEAT_F4HWN_NARROWER
if(Bandwidth == BK4819_FILTER_BW_NARROW && gSetting_set_nfm == 1)
{
Bandwidth = BK4819_FILTER_BW_NARROWER;
}
#endif
AUDIO_AudioPathOff();
gEnableSpeaker = false;
BK4819_ToggleGpioOut(BK4819_GPIO0_PIN28_RX_ENABLE, false);
switch (Bandwidth)
{
default:
Bandwidth = BK4819_FILTER_BW_WIDE;
[[fallthrough]];
case BK4819_FILTER_BW_WIDE:
case BK4819_FILTER_BW_NARROW:
case BK4819_FILTER_BW_NARROWER:
#ifdef ENABLE_AM_FIX
// BK4819_SetFilterBandwidth(Bandwidth, gCurrentVfo->Modulation == MODULATION_AM && gSetting_AM_fix);
BK4819_SetFilterBandwidth(Bandwidth, true);
#else
BK4819_SetFilterBandwidth(Bandwidth, false);
#endif
break;
}
BK4819_SetFrequency(gCurrentVfo->pTX->Frequency);
// TX compressor
BK4819_SetCompander((gRxVfo->Modulation == MODULATION_FM && (gRxVfo->Compander == 1 || gRxVfo->Compander >= 3)) ? gRxVfo->Compander : 0);
BK4819_PrepareTransmit();
SYSTEM_DelayMs(10);
BK4819_PickRXFilterPathBasedOnFrequency(gCurrentVfo->pTX->Frequency);
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, true);
SYSTEM_DelayMs(5);
BK4819_SetupPowerAmplifier(gCurrentVfo->TXP_CalculatedSetting, gCurrentVfo->pTX->Frequency);
SYSTEM_DelayMs(10);
switch (gCurrentVfo->pTX->CodeType)
{
default:
case CODE_TYPE_OFF:
BK4819_ExitSubAu();
break;
case CODE_TYPE_CONTINUOUS_TONE:
BK4819_SetCTCSSFrequency(CTCSS_Options[gCurrentVfo->pTX->Code]);
break;
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_SetCDCSSCodeWord(DCS_GetGolayCodeWord(gCurrentVfo->pTX->CodeType, gCurrentVfo->pTX->Code));
break;
}
}
void RADIO_SetModulation(ModulationMode_t modulation)
{
#ifdef ENABLE_BYP_RAW_DEMODULATORS
if (modulation == MODULATION_BYP || modulation == MODULATION_RAW) {
uint16_t reg_3d_val = 0x0000;
if (modulation == MODULATION_BYP) {
// BYP on BK4829 uses full audio bypass profile.
BK4819_EnterBypass();
reg_3d_val = 0x2AAB;
} else {
// RAW on BK4829 uses RX-only filter bypass profile.
BK4819_EnterRaw();
// reg_3d_val = 0x0000;
}
BK4819_SetRegValue(afDacGainRegSpec, 0xF);
BK4819_WriteRegister(BK4819_REG_3D, reg_3d_val);
RADIO_SetupAGC(false, false);
return;
}
// Ensure we always leave bypass / raw mode before applying normal modulation settings.
BK4819_ExitBypass();
#endif
BK4819_AF_Type_t mod;
switch(modulation) {
default:
case MODULATION_FM:
mod = BK4819_AF_FM;
break;
case MODULATION_AM:
mod = BK4819_AF_FM; // AM no longer needs special AF setting
break;
case MODULATION_USB:
mod = BK4819_AF_BASEBAND2;
break;
}
BK4819_SetAF(mod);
//
// What follows is a direct copy of the AM enable/disable code from
// the original UV-K1 firmware. It is not clear why these specific register
// values are used for AM all of a sudden instead of the AF setting like on
// the BK4819, nor what exactly they do.
// So for now we just keep it as is to maintain compatibility.
//
uint16_t uVar1 = BK4819_ReadRegister(0x31);
switch (modulation)
{
case MODULATION_AM:
{
BK4819_WriteRegister(0x31, uVar1 | 1); // AM Demodulation Enable
BK4819_WriteRegister(0x42, 0x6f5c);
BK4819_WriteRegister(0x2a, 0x7434);
#ifdef ENABLE_FEAT_F4HWN_AUDIO
AUDIO_ApplyAMProfile(gSetting_set_audio_am);
#else
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31a9);
#endif
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
break;
}
case MODULATION_USB:
case MODULATION_FM:
default:
{
BK4819_WriteRegister(0x31, uVar1 & 0xfffe); // AM Demodulation Disable
BK4819_WriteRegister(0x42, 0x6b5a);
BK4819_WriteRegister(0x2a, 0x7400);
BK4819_WriteRegister(0x2b, 0x0000);
BK4819_WriteRegister(0x2f, 0x9890);
#ifdef ENABLE_FEAT_F4HWN_AUDIO
if (modulation == MODULATION_USB)
AUDIO_ApplyUSBProfile();
else
AUDIO_ApplyFMProfile(gSetting_set_audio_fm);
#else
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31a9);
#endif
break;
}
}
BK4819_SetRegValue(afDacGainRegSpec, 0xF);
BK4819_WriteRegister(BK4819_REG_3D, modulation == MODULATION_USB ? 0 : 0x2AAB);
BK4819_SetRegValue(afcDisableRegSpec, modulation != MODULATION_FM);
RADIO_SetupAGC(modulation == MODULATION_AM, false);
}
void RADIO_SetupAGC(bool listeningAM, bool disable)
{
static uint8_t lastSettings = 0xFF;
uint8_t newSettings = (listeningAM << 1) | disable;
if (lastSettings == newSettings)
return;
lastSettings = newSettings;
#ifdef ENABLE_AM_FIX
if (listeningAM && gSetting_AM_fix) {
BK4819_SetAGC(0);
AM_fix_enable(!disable);
return;
}
#endif
BK4819_SetAGC(!disable);
BK4819_InitAGC(listeningAM);
}
void RADIO_SetVfoState(VfoState_t State)
{
if (State == VFO_STATE_NORMAL) {
VfoState[0] = VFO_STATE_NORMAL;
VfoState[1] = VFO_STATE_NORMAL;
} else if (State == VFO_STATE_VOLTAGE_HIGH) {
VfoState[0] = VFO_STATE_VOLTAGE_HIGH;
VfoState[1] = VFO_STATE_TX_DISABLE;
} else {
// 1of11
const unsigned int vfo = (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF) ? gEeprom.RX_VFO : gEeprom.TX_VFO;
VfoState[vfo] = State;
}
gVFOStateResumeCountdown_500ms = (State == VFO_STATE_NORMAL) ? 0 : vfo_state_resume_countdown_500ms;
gUpdateDisplay = true;
}
void RADIO_PrepareTX(void)
{
VfoState_t State = VFO_STATE_NORMAL; // default to OK to TX
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF)
{ // dual-RX is enabled
gDualWatchCountdown_10ms = dual_watch_count_after_tx_10ms;
gScheduleDualWatch = false;
if (!gRxVfoIsActive)
{ // use the current RX vfo
gEeprom.RX_VFO = gEeprom.TX_VFO;
gRxVfo = gTxVfo;
gRxVfoIsActive = true;
}
// let the user see that DW is not active
gDualWatchActive = false;
gUpdateStatus = true;
}
RADIO_SelectCurrentVfo();
if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0
#ifdef ENABLE_FEAT_F4HWN
&& gCurrentVfo->TX_LOCK == true
#endif
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
&& gAlarmState != ALARM_STATE_SITE_ALARM
#endif
){
// TX frequency not allowed
State = VFO_STATE_TX_DISABLE;
gVfoConfigureMode = VFO_CONFIGURE;
} else if (SerialConfigInProgress()) {
// TX is disabled or config upload/download in progress
State = VFO_STATE_TX_DISABLE;
} else if (gCurrentVfo->BUSY_CHANNEL_LOCK && gCurrentFunction == FUNCTION_RECEIVE) {
// busy RX'ing a station
State = VFO_STATE_BUSY;
} else if (gBatteryDisplayLevel == 0) {
// charge your battery !git co
State = VFO_STATE_BAT_LOW;
} else if (gBatteryDisplayLevel > 6) {
// over voltage .. this is being a pain
State = VFO_STATE_VOLTAGE_HIGH;
}
#ifdef ENABLE_BYP_RAW_DEMODULATORS
else if (gCurrentVfo->Modulation == MODULATION_BYP || gCurrentVfo->Modulation == MODULATION_RAW) {
// BYP/RAW are receive-only modes.
State = VFO_STATE_TX_DISABLE;
}
#endif
#ifndef ENABLE_TX_WHEN_AM
else if (gCurrentVfo->Modulation != MODULATION_FM) {
// not allowed to TX if in AM mode
State = VFO_STATE_TX_DISABLE;
}
#endif
if (State != VFO_STATE_NORMAL) {
// TX not allowed
RADIO_SetVfoState(State);
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
gAlarmState = ALARM_STATE_OFF;
#endif
#ifdef ENABLE_DTMF_CALLING
gDTMF_ReplyState = DTMF_REPLY_NONE;
#endif
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL);
return;
}
// TX is allowed
#ifdef ENABLE_DTMF_CALLING
if (gDTMF_ReplyState == DTMF_REPLY_ANI)
{
gDTMF_IsTx = gDTMF_CallMode == DTMF_CALL_MODE_DTMF;
if (gDTMF_IsTx) {
gDTMF_CallState = DTMF_CALL_STATE_NONE;
gDTMF_TxStopCountdown_500ms = DTMF_txstop_countdown_500ms;
} else {
gDTMF_CallState = DTMF_CALL_STATE_CALL_OUT;
}
}
#endif
FUNCTION_Select(FUNCTION_TRANSMIT);
gTxTimerCountdown_500ms = 0; // no timeout
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
if (gAlarmState == ALARM_STATE_OFF)
#endif
{
gTxTimerCountdown_500ms = ((gEeprom.TX_TIMEOUT_TIMER + 1) * 5) * 2;
/*
if (gEeprom.TX_TIMEOUT_TIMER == 0)
gTxTimerCountdown_500ms = 60; // 30 sec
else if (gEeprom.TX_TIMEOUT_TIMER < (ARRAY_SIZE(gSubMenu_TOT) - 1))
gTxTimerCountdown_500ms = 120 * gEeprom.TX_TIMEOUT_TIMER; // minutes
else
gTxTimerCountdown_500ms = 120 * 15; // 15 minutes
*/
#ifdef ENABLE_FEAT_F4HWN
gTxTimerCountdownAlert_500ms = gTxTimerCountdown_500ms;
#endif
}
gTxTimeoutReached = false;
#ifdef ENABLE_FEAT_F4HWN
gTxTimeoutReachedAlert = false;
#endif
gFlagEndTransmission = false;
gRTTECountdown_10ms = 0;
#ifdef ENABLE_DTMF_CALLING
gDTMF_ReplyState = DTMF_REPLY_NONE;
#endif
}
void RADIO_SendCssTail(void)
{
if (gEeprom.TAIL_TONE_ELIMINATION) {
switch (gCurrentVfo->pTX->CodeType) {
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_PlayCDCSSTail();
break;
default:
BK4819_PlayCTCSSTail();
break;
}
SYSTEM_DelayMs(200);
}
}
void RADIO_SendEndOfTransmission(void)
{
BK4819_PlayRoger();
DTMF_SendEndOfTransmission();
// send the CTCSS/DCS tail tone - allows the receivers to mute the usual FM squelch tail/crash
RADIO_SendCssTail();
RADIO_SetupRegisters(false);
}
void RADIO_PrepareCssTX(void)
{
RADIO_PrepareTX();
SYSTEM_DelayMs(200);
RADIO_SendCssTail();
RADIO_SetupRegisters(true);
}