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