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

1427 lines
46 KiB
C

/* Copyright 2025 muzkr https://github.com/muzkr
* 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 <string.h>
#include "app/dtmf.h"
#ifdef ENABLE_FMRADIO
#include "app/fm.h"
#endif
#include "driver/bk1080.h"
#include "driver/bk4819.h"
#include "driver/py25q16.h"
#include "misc.h"
#include "settings.h"
#include "ui/menu.h"
#define SETTINGS_SCAN_MIX_ADDR 0x00A170u
EEPROM_Config_t gEeprom = { 0 };
// Load a DTMF code from EEPROM, falling back to default_val if invalid.
static void SETTINGS_LoadEepromDtmf(uint32_t addr, char *dest, size_t size, const char *default_val)
{
uint8_t buf[16];
if (size > sizeof(buf))
size = sizeof(buf);
PY25Q16_ReadBuffer(addr, buf, size);
if (DTMF_ValidateCodes((char *)buf, size)) {
memcpy(dest, buf, size);
} else {
strcpy(dest, default_val);
}
}
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG
void SETTINGS_InitEEPROM(bool preserve_display_mode)
#else
void SETTINGS_InitEEPROM(void)
#endif
{
uint8_t Data[16] = {0};
//
// Version check
// Read stored version from EEPROM and compare with VERSION_STRING_2
//
{
char storedVersion[16] = {0};
PY25Q16_ReadBuffer(0x00A160, storedVersion, sizeof(storedVersion));
// Compare with current version
if (strncmp(storedVersion, VERSION_STRING_2, sizeof(storedVersion)) != 0)
{
// Different version: new install or firmware update
// 1. Write new version to EEPROM
char newVersion[16] = {0};
strncpy(newVersion, VERSION_STRING_2, sizeof(newVersion));
PY25Q16_WriteBuffer(0x00A160, newVersion, sizeof(newVersion), false);
// 2. Reset sensitive parameters (MENU_LOCK, etc.)
uint8_t configByte[8] = {0};
PY25Q16_ReadBuffer(0x00A000, configByte, sizeof(configByte));
configByte[4] &= (uint8_t)~0x01; // KEY_LOCK = 0
configByte[4] &= (uint8_t)~0x02; // MENU_LOCK = 0
configByte[4] &= (uint8_t)~0x3C; // SET_KEY = 0
//configByte[4] &= (uint8_t)~0x40; // SET_NAV = 0
PY25Q16_WriteBuffer(0x00A000, configByte, sizeof(configByte), false);
// 3. Reset display inversion (SET_INV = 0)
uint8_t displayByte[8] = {0};
PY25Q16_ReadBuffer(0x00A158, displayByte, sizeof(displayByte));
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG
if (!preserve_display_mode || displayByte[5] == 0xFFu)
{
#endif
displayByte[5] &= (uint8_t)~0x10; // Clear bit 4 (SET_INV)
PY25Q16_WriteBuffer(0x00A158, displayByte, sizeof(displayByte), false);
#ifdef ENABLE_FEAT_F4HWN_MULTIBOOT_HOT_CFG
}
#endif
// 4. Reset logo lines (clear to null for strlen() == 0)
/* The two boot-message lines are contiguous in external flash. */
char bootMessageLines[32];
PY25Q16_ReadBuffer(SETTINGS_BOOT_MESSAGE_LINE1_ADDR,
bootMessageLines, sizeof(bootMessageLines));
bool needsWrite = false;
for (int line = 0; line < 2; line++) {
int offset = line * 16;
for (int i = 0; i < 16; i++) {
char c = bootMessageLines[offset + i];
if (c == 0) {
break;
}
if (c < 0x20 || c > 0x7E) {
memset(bootMessageLines + offset, 0, 16);
needsWrite = true;
break;
}
}
}
if (needsWrite) {
PY25Q16_WriteBuffer(SETTINGS_BOOT_MESSAGE_LINE1_ADDR,
bootMessageLines, sizeof(bootMessageLines), false);
}
// 5. Reset dBmCorrTable
int8_t buf[7];
PY25Q16_ReadBuffer(0x00A0B9, (uint8_t *)buf, 7);
needsWrite = true;
for (uint8_t i = 0; i < 7; i++) {
if ((uint8_t)buf[i] != 0xFF) {
needsWrite = false;
break;
}
}
if (needsWrite) {
for (uint8_t i = 0; i < 7; i++)
buf[i] = dBmCorrTable[i];
PY25Q16_WriteBuffer(0x00A0B9, buf, 7, false);
}
}
}
// 0E70..0E77
PY25Q16_ReadBuffer(0x00A000, Data, 8);
#ifdef ENABLE_FEAT_F4HWN_AUDIO
gSetting_set_audio_fm = ((Data[0] & 0x0F) < 5) ? (Data[0] & 0x0F) : 0;
gSetting_set_audio_am = (((Data[0] >> 4) & 0x0F) < 3) ? ((Data[0] >> 4) & 0x0F) : 0;
#endif
gEeprom.SQUELCH_LEVEL = (Data[1] < 10) ? Data[1] : 1;
gEeprom.TX_TIMEOUT_TIMER = (Data[2] > 4 && Data[2] < 180) ? Data[2] : 11;
#ifdef ENABLE_NOAA
gEeprom.NOAA_AUTO_SCAN = (Data[3] < 2) ? Data[3] : false;
#endif
gEeprom.KEY_LOCK = (Data[4] & 0x01) != 0;
gEeprom.SET_NAV = (Data[4] & 0x40) != 0;
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
gEeprom.MENU_LOCK = (Data[4] & 0x02) != 0;
gEeprom.SET_KEY = ((Data[4] >> 2) & 0x0F) > 4 ? 0 : (Data[4] >> 2) & 0x0F;
#endif
#ifdef ENABLE_VOX
gEeprom.VOX_SWITCH = (Data[5] < 2) ? Data[5] : false;
gEeprom.VOX_LEVEL = (Data[6] < 10) ? Data[6] : 1;
#endif
gEeprom.MIC_SENSITIVITY = (Data[7] < 9) ? Data[7] : 4;
// 0E78..0E7F
PY25Q16_ReadBuffer(0x00A008, Data, 8);
gEeprom.BACKLIGHT_MAX = (Data[0] & 0xF) <= 10 ? (Data[0] & 0xF) : 10;
gEeprom.BACKLIGHT_MIN = (Data[0] >> 4) < gEeprom.BACKLIGHT_MAX ? (Data[0] >> 4) : 0;
gEeprom.CHANNEL_DISPLAY_MODE = (Data[1] < 4) ? Data[1] : MDF_FREQUENCY; // 4 instead of 3 - extra display mode
gEeprom.CROSS_BAND_RX_TX = (Data[2] < 3) ? Data[2] : CROSS_BAND_OFF;
gEeprom.BATTERY_SAVE = (Data[3] < 6) ? Data[3] : 4;
#ifdef ENABLE_FEAT_F4HWN_FULL_WATCH
gEeprom.DUAL_WATCH = (Data[4] <= DUAL_WATCH_FULL) ? Data[4] : DUAL_WATCH_CHAN_A;
#else
gEeprom.DUAL_WATCH = (Data[4] < 3) ? Data[4] : DUAL_WATCH_CHAN_A;
#endif
gEeprom.BACKLIGHT_TIME = (Data[5] < 62) ? Data[5] : 12;
#ifdef ENABLE_FEAT_F4HWN_NARROWER
gEeprom.TAIL_TONE_ELIMINATION = Data[6] & 0x01;
gSetting_set_nfm = (Data[6] >> 1) & 0x01;
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
gEeprom.VFO_OPEN = ((Data[6] >> 2) & 0x01) != 0 ? true : true;
#endif
#else
gEeprom.TAIL_TONE_ELIMINATION = (Data[6] < 2) ? Data[6] : false;
#endif
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
gEeprom.CURRENT_STATE = Data[7] & 0x07; // bits 0..2
gEeprom.CURRENT_LIST = (Data[7] >> 3) & 0x1F; // bits 3..7
#else
gEeprom.VFO_OPEN = (Data[7] < 2) ? Data[7] : true;
#endif
// 0E80..0E87
/*
PY25Q16_ReadBuffer(0x00A010, Data, 8);
gEeprom.ScreenChannel[0] = IS_VALID_CHANNEL(Data[0]) ? Data[0] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
gEeprom.ScreenChannel[1] = IS_VALID_CHANNEL(Data[3]) ? Data[3] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
gEeprom.MrChannel[0] = IS_MR_CHANNEL(Data[1]) ? Data[1] : MR_CHANNEL_FIRST;
gEeprom.MrChannel[1] = IS_MR_CHANNEL(Data[4]) ? Data[4] : MR_CHANNEL_FIRST;
gEeprom.FreqChannel[0] = IS_FREQ_CHANNEL(Data[2]) ? Data[2] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data[5]) ? Data[5] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
#ifdef ENABLE_NOAA
gEeprom.NoaaChannel[0] = IS_NOAA_CHANNEL(Data[6]) ? Data[6] : NOAA_CHANNEL_FIRST;
gEeprom.NoaaChannel[1] = IS_NOAA_CHANNEL(Data[7]) ? Data[7] : NOAA_CHANNEL_FIRST;
#endif
*/
// 0x00A010 .. 0x00A01F
uint16_t Data16[8];
PY25Q16_ReadBuffer(0x00A010, Data16, sizeof(Data16));
gEeprom.ScreenChannel[0] = IS_VALID_CHANNEL(Data16[0]) ? Data16[0] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
gEeprom.MrChannel[0] = IS_MR_CHANNEL(Data16[1]) ? Data16[1] : MR_CHANNEL_FIRST;
gEeprom.FreqChannel[0] = IS_FREQ_CHANNEL(Data16[2]) ? Data16[2] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
gEeprom.ScreenChannel[1] = IS_VALID_CHANNEL(Data16[3]) ? Data16[3] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
gEeprom.MrChannel[1] = IS_MR_CHANNEL(Data16[4]) ? Data16[4] : MR_CHANNEL_FIRST;
gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNEL_FIRST + BAND6_400MHz);
#ifdef ENABLE_NOAA
gEeprom.NoaaChannel[0] = IS_NOAA_CHANNEL(Data16[6]) ? Data16[6] : NOAA_CHANNEL_FIRST;
gEeprom.NoaaChannel[1] = IS_NOAA_CHANNEL(Data16[7]) ? Data16[7] : NOAA_CHANNEL_FIRST;
#endif
#ifdef ENABLE_FMRADIO
{ // 0E88..0E8F
struct
{
uint16_t selFreq;
uint8_t selChn;
uint8_t isMrMode:1;
uint8_t band:2;
//uint8_t space:2;
} __attribute__((packed)) fmCfg;
PY25Q16_ReadBuffer(0x00A020, &fmCfg, 4);
gEeprom.FM_Band = fmCfg.band;
//gEeprom.FM_Space = fmCfg.space;
uint16_t freqLoLimit = BK1080_GetFreqLoLimit(gEeprom.FM_Band);
gEeprom.FM_SelectedFrequency =
(fmCfg.selFreq >= freqLoLimit && fmCfg.selFreq <= BK1080_GetFreqHiLimit(gEeprom.FM_Band)) ?
fmCfg.selFreq : freqLoLimit;
gEeprom.FM_SelectedChannel = fmCfg.selChn;
gEeprom.FM_IsMrMode = fmCfg.isMrMode;
}
// 0E40..0E67
PY25Q16_ReadBuffer(0x00A028, gFM_Channels, sizeof(gFM_Channels));
FM_ConfigureChannelState();
#endif
// 0E90..0E97
PY25Q16_ReadBuffer(0x00A0A8, Data, 8);
gEeprom.BEEP_CONTROL = Data[0] & 1;
gEeprom.KEY_M_LONG_PRESS_ACTION = ((Data[0] >> 1) < ACTION_OPT_LEN) ? (Data[0] >> 1) : ACTION_OPT_NONE;
gEeprom.KEY_1_SHORT_PRESS_ACTION = (Data[1] < ACTION_OPT_LEN) ? Data[1] : ACTION_OPT_MONITOR;
gEeprom.KEY_1_LONG_PRESS_ACTION = (Data[2] < ACTION_OPT_LEN) ? Data[2] : ACTION_OPT_NONE;
gEeprom.KEY_2_SHORT_PRESS_ACTION = (Data[3] < ACTION_OPT_LEN) ? Data[3] : ACTION_OPT_SCAN;
gEeprom.KEY_2_LONG_PRESS_ACTION = (Data[4] < ACTION_OPT_LEN) ? Data[4] : ACTION_OPT_NONE;
gEeprom.SCAN_RESUME_MODE = (Data[5] < 105) ? Data[5] : 14;
gEeprom.AUTO_KEYPAD_LOCK = (Data[6] < 41) ? Data[6] : 0;
#ifdef ENABLE_FEAT_F4HWN
gEeprom.POWER_ON_DISPLAY_MODE = (Data[7] < 6) ? Data[7] : POWER_ON_DISPLAY_MODE_VOLTAGE;
#else
gEeprom.POWER_ON_DISPLAY_MODE = (Data[7] < 4) ? Data[7] : POWER_ON_DISPLAY_MODE_VOLTAGE;
#endif
// 0E98..0E9F
#ifdef ENABLE_PWRON_PASSWORD
PY25Q16_ReadBuffer(0x00A0A8 + 0x8, Data, 8);
memcpy(&gEeprom.POWER_ON_PASSWORD, Data, 4);
#endif
// 0EA0..0EA7
PY25Q16_ReadBuffer(0x00A0A8 + 0x10, Data, 8);
#ifdef ENABLE_VOICE
gEeprom.VOICE_PROMPT = (Data[0] < 3) ? Data[0] : VOICE_PROMPT_ENGLISH;
#endif
#ifdef ENABLE_RSSI_BAR
for (uint8_t i = 0; i < 7; i++) {
int8_t val = (int8_t)Data[i + 1];
if (val >= -64 && val <= 64)
dBmCorrTable[i] = val;
}
#endif
// 0EA8..0EAF
PY25Q16_ReadBuffer(0x00A0A8 + 0x18, Data, 8);
gEeprom.ROGER = (Data[1] < 3) ? Data[1] : ROGER_MODE_OFF;
gEeprom.REPEATER_TAIL_TONE_ELIMINATION = (Data[2] < 11) ? Data[2] : 0;
gEeprom.TX_VFO = (Data[3] < 2) ? Data[3] : 0;
gEeprom.BATTERY_TYPE = (Data[4] < BATTERY_TYPE_UNKNOWN) ? Data[4] : BATTERY_TYPE_1600_MAH;
// 0ED0..0ED7
PY25Q16_ReadBuffer(0x00A0A8 + 0x40, Data, 8);
gEeprom.DTMF_SIDE_TONE = (Data[0] < 2) ? Data[0] : true;
#ifdef ENABLE_DTMF_CALLING
gEeprom.DTMF_SEPARATE_CODE = DTMF_ValidateCodes((char *)(Data + 1), 1) ? Data[1] : '*';
gEeprom.DTMF_GROUP_CALL_CODE = DTMF_ValidateCodes((char *)(Data + 2), 1) ? Data[2] : '#';
gEeprom.DTMF_DECODE_RESPONSE = (Data[3] < 4) ? Data[3] : 0;
gEeprom.DTMF_auto_reset_time = (Data[4] < 61) ? Data[4] : (Data[4] >= 5) ? Data[4] : 10;
#endif
gEeprom.DTMF_PRELOAD_TIME = (Data[5] < 101) ? Data[5] * 10 : 300;
gEeprom.DTMF_FIRST_CODE_PERSIST_TIME = (Data[6] < 101) ? Data[6] * 10 : 100;
gEeprom.DTMF_HASH_CODE_PERSIST_TIME = (Data[7] < 101) ? Data[7] * 10 : 100;
// 0ED8..0EDF
PY25Q16_ReadBuffer(0x00A0A8 + 0x48, Data, 8);
gEeprom.DTMF_CODE_PERSIST_TIME = (Data[0] < 101) ? Data[0] * 10 : 100;
gEeprom.DTMF_CODE_INTERVAL_TIME = (Data[1] < 101) ? Data[1] * 10 : 100;
#ifdef ENABLE_DTMF_CALLING
gEeprom.PERMIT_REMOTE_KILL = (Data[2] < 2) ? Data[2] : true;
// 0EE0..0EE7
SETTINGS_LoadEepromDtmf(0x00A0F8, gEeprom.ANI_DTMF_ID, sizeof(gEeprom.ANI_DTMF_ID), "123");
// 0EE8..0EEF
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x08, gEeprom.KILL_CODE, sizeof(gEeprom.KILL_CODE), "ABCD9");
// 0EF0..0EF7
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x10, gEeprom.REVIVE_CODE, sizeof(gEeprom.REVIVE_CODE), "9DCBA");
#endif
// 0EF8..0F07
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x18, gEeprom.DTMF_UP_CODE, sizeof(gEeprom.DTMF_UP_CODE), "12345");
// 0F08..0F17
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x28, gEeprom.DTMF_DOWN_CODE, sizeof(gEeprom.DTMF_DOWN_CODE), "54321");
// 0F18..0F1F
PY25Q16_ReadBuffer(0x00A130, Data, 8);
gEeprom.SCAN_LIST_DEFAULT =
(((Data[0] & 0x7F) >= 1) && ((Data[0] & 0x7F) <= SCAN_LIST_MODE_MIX))
? (Data[0] & 0x7F)
: 1;
gEeprom.SCAN_LIST_ENABLED = (Data[0] >> 7) & 0x01;
gEeprom.SCANLIST_PRIORITY_CH[0] =
(uint16_t)Data[1] |
((uint16_t)Data[2] << 8);
gEeprom.SCANLIST_PRIORITY_CH[1] =
(uint16_t)Data[3] |
((uint16_t)Data[4] << 8);
gEeprom.CHAN_1_CALL =
(uint16_t)Data[5] |
((uint16_t)Data[6] << 8);
// 0F58..0F5F
PY25Q16_ReadBuffer(SETTINGS_SCAN_MIX_ADDR, Data, 8);
if (Data[3] == 'M' && Data[4] == 'I' && Data[5] == 'X' && Data[6] == 1) {
gEeprom.SCAN_LIST_MIX_MASK =
(uint32_t)Data[0] |
((uint32_t)Data[1] << 8) |
((uint32_t)Data[2] << 16);
if (gEeprom.SCAN_LIST_MIX_MASK == 0)
gEeprom.SCAN_LIST_MIX_MASK = SCAN_LIST_MIX_MASK_ALL;
} else {
gEeprom.SCAN_LIST_MIX_MASK = SCAN_LIST_MIX_MASK_ALL;
}
// 0F40..0F47
PY25Q16_ReadBuffer(0x00A150, Data, 8);
gSetting_F_LOCK = (Data[0] < F_LOCK_LEN) ? Data[0] : F_LOCK_DEF;
#ifndef ENABLE_FEAT_F4HWN
gSetting_350TX = (Data[1] < 2) ? Data[1] : false; // was true
#endif
#ifdef ENABLE_DTMF_CALLING
gSetting_KILLED = (Data[2] < 2) ? Data[2] : false;
#endif
#ifndef ENABLE_FEAT_F4HWN
gSetting_200TX = (Data[3] < 2) ? Data[3] : false;
gSetting_500TX = (Data[4] < 2) ? Data[4] : false;
#endif
gSetting_350EN = (Data[5] < 2) ? Data[5] : true;
#ifdef ENABLE_FEAT_F4HWN
gSetting_ScrambleEnable = false;
#else
gSetting_ScrambleEnable = (Data[6] < 2) ? Data[6] : true;
#endif
//gSetting_TX_EN = (Data[7] & (1u << 0)) ? true : false;
gSetting_live_DTMF_decoder = !!(Data[7] & (1u << 1));
gSetting_battery_text = (((Data[7] >> 2) & 3u) <= 2) ? (Data[7] >> 2) & 3 : 2;
#ifdef ENABLE_AUDIO_BAR
gSetting_mic_bar = !!(Data[7] & (1u << 4));
#endif
// Data[7] bit 5 is reserved (legacy ENABLE_AM_FIX).
gSetting_backlight_on_tx_rx = (Data[7] >> 6) & 3u;
if (!gEeprom.VFO_OPEN)
{
gEeprom.ScreenChannel[0] = gEeprom.MrChannel[0];
gEeprom.ScreenChannel[1] = gEeprom.MrChannel[1];
}
// 0D60..0E27
/*
PY25Q16_ReadBuffer(0x008000, gMR_ChannelAttributes, sizeof(gMR_ChannelAttributes));
uint16_t count = ARRAY_SIZE(gMR_ChannelAttributes);
for (uint16_t i = 0; i < count; i++) {
ChannelAttributes_t *att = MR_GetChannelAttributes(i);
if (att->__val == 0xFFFF) {
att->__val = 0;
att->band = 0x7;
}
else
{
att->exclude = 0;
}
}
*/
// Init list name
PY25Q16_ReadBuffer(0x00880E, gListName, sizeof(gListName));
// Init attr cache
MR_InitChannelAttributesCache();
// Load and check channel
for (uint16_t i = 0; i < MR_CHANNELS_MAX + 7; i++) {
ChannelAttributes_t *att = MR_GetChannelAttributes(i);
if (att != NULL) {
if (att->__val == 0xFFFF) {
att->__val = 0;
att->band = 0x7;
MR_SetChannelAttributes(i, att); // ⭐ IMPORTANT: Sauvegarder!
}
else {
att->exclude = 0;
MR_SetChannelAttributes(i, att); // ⭐ IMPORTANT: Sauvegarder!
}
}
}
// 0F30..0F3F
PY25Q16_ReadBuffer(0x00A138, gCustomAesKey, sizeof(gCustomAesKey));
bHasCustomAesKey = false;
#ifndef ENABLE_FEAT_F4HWN
for (unsigned int i = 0; i < ARRAY_SIZE(gCustomAesKey); i++)
{
if (gCustomAesKey[i] != 0xFFFFFFFFu)
{
bHasCustomAesKey = true;
return;
}
}
#endif
#ifdef ENABLE_FEAT_F4HWN
// 1FF0..0x1FF7
// TODO: address TBD
PY25Q16_ReadBuffer(0x00A158, Data, 8);
const uint8_t set_ptt_scn_sav = Data[7] & 0x0F;
const bool set_ptt_scn_sav_erased = Data[7] == 0xFF;
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
const bool set_ptt_scn_sav_valid = !set_ptt_scn_sav_erased && set_ptt_scn_sav < (SET_SAV_LEN << 2);
#else
const bool set_ptt_scn_sav_valid = !set_ptt_scn_sav_erased && set_ptt_scn_sav < 4;
#endif
gSetting_set_pwr = (((Data[7] & 0xF0) >> 4) < 7) ? ((Data[7] & 0xF0) >> 4) : 0;
gSetting_set_ptt = set_ptt_scn_sav_valid ? (set_ptt_scn_sav & 0x01) : 0;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
gSetting_set_scn = set_ptt_scn_sav_valid ? ((set_ptt_scn_sav & 0x02) == 0) : 0;
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
gSetting_set_sav = set_ptt_scn_sav_valid ? ((set_ptt_scn_sav >> 2) & 0x03) : SET_SAV_OFF;
#endif
gSetting_set_tot = (((Data[6] & 0xF0) >> 4) < 4) ? ((Data[6] & 0xF0) >> 4) : 0;
gSetting_set_eot = (((Data[6] & 0x0F)) < 4) ? ((Data[6] & 0x0F)) : 0;
/*
int tmp = ((Data[5] & 0xF0) >> 4);
gSetting_set_inv = (((tmp >> 0) & 0x01) < 2) ? ((tmp >> 0) & 0x01): 0;
gSetting_set_met = (((tmp >> 2) & 0x01) < 2) ? ((tmp >> 2) & 0x01): 0;
gSetting_set_gui = (((tmp >> 3) & 0x01) < 2) ? ((tmp >> 3) & 0x01): 0;
gSetting_set_ctr = (((Data[5] & 0x0F)) > 00 && ((Data[5] & 0x0F)) < 16) ? ((Data[5] & 0x0F)) : 10;
gSetting_set_tmr = ((Data[4] & 1) < 2) ? (Data[4] & 1): 0;
*/
int tmp = (Data[5] & 0xF0) >> 4;
#ifdef ENABLE_FEAT_F4HWN_INV
gSetting_set_inv = (tmp >> 0) & 0x01;
#else
gSetting_set_inv = 0;
#endif
gSetting_set_lck = (Data[2] < SET_LCK_LEN) ? Data[2] : SET_LCK_KEYS;
gSetting_set_met = (tmp >> 2) & 0x01;
gSetting_set_gui = (tmp >> 3) & 0x01;
#ifdef ENABLE_FEAT_F4HWN_CTR
int ctr_value = Data[5] & 0x0F;
gSetting_set_ctr = (ctr_value > 0 && ctr_value < 16) ? ctr_value : 10;
#else
gSetting_set_ctr = 10;
#endif
gSetting_set_tmr = Data[4] & 0x01;
#ifdef ENABLE_FEAT_F4HWN_SLEEP
gSetting_set_off = (Data[4] >> 1) > 120 ? 60 : (Data[4] >> 1);
#endif
// And set special session settings for actions
gSetting_set_ptt_session = gSetting_set_ptt;
#endif
}
void SETTINGS_LoadCalibration(void)
{
// uint8_t Mic;
// 0x1EC0
PY25Q16_ReadBuffer(0x010000 + 0xc0, gEEPROM_RSSI_CALIB[3], 8);
memcpy(gEEPROM_RSSI_CALIB[4], gEEPROM_RSSI_CALIB[3], 8);
memcpy(gEEPROM_RSSI_CALIB[5], gEEPROM_RSSI_CALIB[3], 8);
memcpy(gEEPROM_RSSI_CALIB[6], gEEPROM_RSSI_CALIB[3], 8);
// 0x1EC8
PY25Q16_ReadBuffer(0x010000 + 0xc8, gEEPROM_RSSI_CALIB[0], 8);
memcpy(gEEPROM_RSSI_CALIB[1], gEEPROM_RSSI_CALIB[0], 8);
memcpy(gEEPROM_RSSI_CALIB[2], gEEPROM_RSSI_CALIB[0], 8);
// 0x1F40
PY25Q16_ReadBuffer(0x010000 + 0x140, gBatteryCalibration, 12);
if (gBatteryCalibration[0] >= 5000)
{
gBatteryCalibration[0] = 1900;
gBatteryCalibration[1] = 2000;
}
// A wiped calibration zone (0x0000 / 0xFFFF) leaves gBatteryCalibration[3]
// invalid. As it is the divisor of the battery-voltage computation, that
// collapses the reading to "critical" and can trap the radio in reduced
// service -> reset (reboot loop). Fall back to a nominal value (RAM only).
// Bounds match the MENU_BATCAL accepted range [1500, 3500] so a legitimate
// calibration is never overwritten.
if (gBatteryCalibration[3] < 1500 || gBatteryCalibration[3] > 3500)
gBatteryCalibration[3] = 2000;
gBatteryCalibration[5] = 2300;
#ifdef ENABLE_VOX
// 0x1F50
PY25Q16_ReadBuffer(0x010000 + 0x150 + (gEeprom.VOX_LEVEL * 2), &gEeprom.VOX1_THRESHOLD, 2);
// 0x1F68
PY25Q16_ReadBuffer(0x010000 + 0x168 + (gEeprom.VOX_LEVEL * 2), &gEeprom.VOX0_THRESHOLD, 2);
#endif
//PY25Q16_ReadBuffer(0x1F80 + gEeprom.MIC_SENSITIVITY, &Mic, 1);
//gEeprom.MIC_SENSITIVITY_TUNING = (Mic < 32) ? Mic : 15;
gEeprom.MIC_SENSITIVITY_TUNING = gMicGain_dB2[gEeprom.MIC_SENSITIVITY];
{
struct
{
int16_t BK4819_XtalFreqLow;
uint16_t EEPROM_1F8A;
uint16_t EEPROM_1F8C;
uint8_t VOLUME_GAIN;
uint8_t DAC_GAIN;
} __attribute__((packed)) Misc;
// radio 1 .. 04 00 46 00 50 00 2C 0E
// radio 2 .. 05 00 46 00 50 00 2C 0E
// 0x1F88
PY25Q16_ReadBuffer(0x010000 + 0x188, &Misc, 8);
gEeprom.BK4819_XTAL_FREQ_LOW = (Misc.BK4819_XtalFreqLow >= -1000 && Misc.BK4819_XtalFreqLow <= 1000) ? Misc.BK4819_XtalFreqLow : 0;
gEEPROM_1F8A = Misc.EEPROM_1F8A & 0x01FF;
gEEPROM_1F8C = Misc.EEPROM_1F8C & 0x01FF;
gEeprom.VOLUME_GAIN = (Misc.VOLUME_GAIN < 64) ? Misc.VOLUME_GAIN : 58;
gEeprom.DAC_GAIN = (Misc.DAC_GAIN < 16) ? Misc.DAC_GAIN : 8;
#ifdef ENABLE_FEAT_F4HWN
gEeprom.VOLUME_GAIN_BACKUP = gEeprom.VOLUME_GAIN;
#endif
BK4819_WriteRegister(BK4819_REG_3B, 22656 + gEeprom.BK4819_XTAL_FREQ_LOW);
// BK4819_WriteRegister(BK4819_REG_3C, gEeprom.BK4819_XTAL_FREQ_HIGH);
}
}
uint32_t SETTINGS_FetchChannelFrequency(const uint16_t channel)
{
struct
{
uint32_t frequency;
uint32_t offset;
} __attribute__((packed)) info;
PY25Q16_ReadBuffer(channel * 16, &info, sizeof(info));
return info.frequency;
}
bool SETTINGS_FetchChannelScanInfo(const uint16_t channel, uint32_t *frequency, ModulationMode_t *modulation)
{
struct
{
uint32_t frequency;
uint32_t offset;
uint8_t settings[4];
} __attribute__((packed)) info;
PY25Q16_ReadBuffer(channel * 16, &info, sizeof(info));
if (frequency)
*frequency = info.frequency;
if (modulation)
{
uint8_t mode = info.settings[3] >> 4;
if (mode >= MODULATION_UKNOWN)
mode = MODULATION_FM;
*modulation = (ModulationMode_t)mode;
}
return info.frequency != 0 && info.frequency != 0xFFFFFFFF;
}
bool SETTINGS_FetchChannelScanDisplayInfo(const uint16_t channel, ChannelScanDisplayInfo_t *info)
{
if (info == NULL)
return false;
struct
{
uint32_t frequency;
uint32_t offset;
uint8_t data[8];
} __attribute__((packed)) raw;
PY25Q16_ReadBuffer(channel * 16, &raw, sizeof(raw));
if (raw.frequency == 0 || raw.frequency == 0xFFFFFFFF)
return false;
memset(info, 0, sizeof(*info));
info->rx.Frequency = raw.frequency;
info->tx.Frequency = raw.frequency;
info->offset = (raw.offset >= _1GHz_in_KHz) ? (_1GHz_in_KHz / 100) : raw.offset;
info->rx.CodeType = (raw.data[2] >> 0) & 0x0F;
info->tx.CodeType = (raw.data[2] >> 4) & 0x0F;
RADIO_ValidateAndSetCode(&info->rx, raw.data[0]);
RADIO_ValidateAndSetCode(&info->tx, raw.data[1]);
uint8_t tmp = raw.data[3] & 0x0F;
if (tmp > TX_OFFSET_FREQUENCY_DIRECTION_SUB)
tmp = TX_OFFSET_FREQUENCY_DIRECTION_OFF;
info->txOffsetFrequencyDirection = tmp;
tmp = raw.data[3] >> 4;
if (tmp >= MODULATION_UKNOWN)
tmp = MODULATION_FM;
info->modulation = (ModulationMode_t)tmp;
tmp = raw.data[6];
if (tmp >= STEP_N_ELEM)
tmp = STEP_12_5kHz;
info->stepSetting = (STEP_Setting_t)tmp;
info->stepFrequency = gStepFrequencyTable[tmp];
if (raw.data[4] == 0xFF)
{
info->frequencyReverse = false;
info->channelBandwidth = BANDWIDTH_WIDE;
info->outputPower = OUTPUT_POWER_LOW1;
info->busyChannelLock = false;
info->txLock = true;
}
else
{
const uint8_t d4 = raw.data[4];
info->frequencyReverse = !!((d4 >> 0) & 1u);
info->channelBandwidth = !!((d4 >> 1) & 1u);
info->outputPower = ((d4 >> 2) & 7u);
info->busyChannelLock = !!((d4 >> 5) & 1u);
info->txLock = !!((d4 >> 6) & 1u);
}
switch (info->txOffsetFrequencyDirection)
{
case TX_OFFSET_FREQUENCY_DIRECTION_ADD:
info->tx.Frequency = raw.frequency + info->offset;
break;
case TX_OFFSET_FREQUENCY_DIRECTION_SUB:
info->tx.Frequency = raw.frequency - info->offset;
break;
default:
break;
}
if (raw.data[5] == 0xFF)
{
#ifdef ENABLE_DTMF_CALLING
info->dtmfDecodingEnable = false;
#endif
info->dtmfPttIdTxMode = PTT_ID_OFF;
}
else
{
#ifdef ENABLE_DTMF_CALLING
info->dtmfDecodingEnable = (raw.data[5] >> 0) & 1u;
#endif
const uint8_t pttId = (raw.data[5] >> 1) & 7u;
info->dtmfPttIdTxMode = pttId < ARRAY_SIZE(gSubMenu_PTT_ID) ? pttId : PTT_ID_OFF;
}
return true;
}
#if defined(ENABLE_FEAT_F4HWN_FULL_WATCH) || defined(ENABLE_FEAT_F4HWN_SCAN_FASTER)
void SETTINGS_ApplyChannelScanDisplayInfo(VFO_Info_t *vfo, uint16_t channel, const ChannelScanDisplayInfo_t *info)
{
vfo->CHANNEL_SAVE = channel;
vfo->freq_config_RX = info->rx;
vfo->freq_config_TX = info->tx;
vfo->TX_OFFSET_FREQUENCY = info->offset;
vfo->StepFrequency = info->stepFrequency;
vfo->STEP_SETTING = info->stepSetting;
vfo->Modulation = info->modulation;
vfo->TX_OFFSET_FREQUENCY_DIRECTION = info->txOffsetFrequencyDirection;
vfo->OUTPUT_POWER = info->outputPower;
vfo->FrequencyReverse = info->frequencyReverse;
vfo->CHANNEL_BANDWIDTH = info->channelBandwidth;
vfo->BUSY_CHANNEL_LOCK = info->busyChannelLock;
vfo->TX_LOCK = info->txLock;
#ifdef ENABLE_DTMF_CALLING
vfo->DTMF_DECODING_ENABLE = info->dtmfDecodingEnable;
#endif
vfo->DTMF_PTT_ID_TX_MODE = info->dtmfPttIdTxMode;
if (!vfo->FrequencyReverse)
{
vfo->pRX = &vfo->freq_config_RX;
vfo->pTX = &vfo->freq_config_TX;
}
else
{
vfo->pRX = &vfo->freq_config_TX;
vfo->pTX = &vfo->freq_config_RX;
}
}
#endif
void SETTINGS_FetchChannelName(char *s, const uint16_t channel)
{
if (s == NULL)
return;
s[0] = 0;
if (channel < 0)
return;
if (!RADIO_CheckValidChannel(channel, false, 0))
return;
// 0x0F50
PY25Q16_ReadBuffer(0x004000 + (channel * 16), s, 10);
int i;
for (i = 0; i < 10; i++)
if (s[i] < 32 || s[i] > 127)
break; // invalid char
s[i--] = 0; // null term
while (i >= 0 && s[i] == 32) // trim trailing spaces
s[i--] = 0; // null term
}
void SETTINGS_FactoryReset(bool bIsAll)
{
// PY25Q16_SectorErase(0x000000);
// PY25Q16_SectorErase(0x001000);
// PY25Q16_SectorErase(0x002000);
// PY25Q16_SectorErase(0x003000);
// PY25Q16_SectorErase(0x004000);
// PY25Q16_SectorErase(0x005000);
// PY25Q16_SectorErase(0x006000);
// PY25Q16_SectorErase(0x007000);
// PY25Q16_SectorErase(0x008000);
// PY25Q16_SectorErase(0x009000);
for (uint32_t addr = 0x000000; addr <= 0x009000; addr += 0x1000) {
PY25Q16_SectorErase(addr);
}
// 0d60 - 0e30
if (bIsAll)
{
PY25Q16_SectorErase(0x00A000);
}
// Reset navigation in every edition and lock controls in RescueOps editions.
uint8_t Data8[0x10];
PY25Q16_ReadBuffer(0x00A000, Data8, sizeof(Data8));
// SET_NAV to false
Data8[4] &= (uint8_t)~0x40; // Clear bit 6 (SET_NAV) for UV-K1 by default
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
// MENU_LOCK & KEY_LOCK to 0
Data8[4] &= (uint8_t)~0x01;
Data8[4] &= (uint8_t)~0x02;
// SET_KEY to 0
Data8[4] &= (uint8_t)~0x3C; // Clear bits 2-5 (SET_KEY)
#ifdef ENABLE_FEAT_F4HWN_RESET_VFO
Data8[7] = (1 & 0x01);
#endif
#endif
PY25Q16_WriteBuffer(0x00A000, Data8, sizeof(Data8), false);
// Keep RAM consistent with the persisted defaults.
gEeprom.SET_NAV = false;
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
gEeprom.MENU_LOCK = 0;
#endif
// Reset VFO for the first time...
#ifdef ENABLE_FEAT_F4HWN_RESET_VFO
RADIO_InitInfo(&gEeprom.VfoInfo[0], FREQ_CHANNEL_FIRST + BAND3_137MHz, 14550000);
RADIO_InitInfo(&gEeprom.VfoInfo[1], FREQ_CHANNEL_FIRST + BAND6_400MHz, 43350000);
gEeprom.ScreenChannel[0] = FREQ_CHANNEL_FIRST + BAND3_137MHz;
gEeprom.ScreenChannel[1] = FREQ_CHANNEL_FIRST + BAND6_400MHz;
gEeprom.MrChannel[0] = MR_CHANNEL_FIRST;
gEeprom.MrChannel[1] = MR_CHANNEL_FIRST;
gEeprom.FreqChannel[0] = FREQ_CHANNEL_FIRST + BAND3_137MHz;
gEeprom.FreqChannel[1] = FREQ_CHANNEL_FIRST + BAND6_400MHz;
SETTINGS_SaveChannel(FREQ_CHANNEL_FIRST + BAND3_137MHz, 0, &gEeprom.VfoInfo[0], 2);
SETTINGS_SaveChannel(FREQ_CHANNEL_FIRST + BAND6_400MHz, 1, &gEeprom.VfoInfo[1], 2);
gVfoStateChanged = true;
gScheduleVfoSave = true;
SETTINGS_SaveVfoIndicesFlush();
#endif
}
#ifdef ENABLE_FMRADIO
void SETTINGS_SaveFM(void)
{
union {
struct {
uint16_t selFreq;
uint8_t selChn;
uint8_t isMrMode:1;
uint8_t band:2;
//uint8_t space:2;
};
uint8_t __raw[8];
} __attribute__((packed)) fmCfg;
memset(fmCfg.__raw, 0xFF, sizeof(fmCfg.__raw));
fmCfg.selChn = gEeprom.FM_SelectedChannel;
fmCfg.selFreq = gEeprom.FM_SelectedFrequency;
fmCfg.isMrMode = gEeprom.FM_IsMrMode;
fmCfg.band = gEeprom.FM_Band;
// fmCfg.space = gEeprom.FM_Space;
// 0E88
PY25Q16_WriteBuffer(0x00A020, fmCfg.__raw, 8, false);
// 0E40
PY25Q16_WriteBuffer(0x00A028, gFM_Channels, sizeof(gFM_Channels), false);
}
#endif
void SETTINGS_SaveVfoIndices(void)
{
gVfoStateChanged = true;
gVfoSaveCountdown_10ms = 2;
}
void SETTINGS_SaveVfoIndicesFlush(void)
{
if (gScheduleVfoSave) {
gScheduleVfoSave = false;
if (gVfoStateChanged) {
gVfoStateChanged = false;
uint16_t Data16[8];
#ifndef ENABLE_NOAA
PY25Q16_ReadBuffer(0x00A010, Data16, sizeof(Data16));
#endif
Data16[0] = gEeprom.ScreenChannel[0];
Data16[1] = gEeprom.MrChannel[0];
Data16[2] = gEeprom.FreqChannel[0];
Data16[3] = gEeprom.ScreenChannel[1];
Data16[4] = gEeprom.MrChannel[1];
Data16[5] = gEeprom.FreqChannel[1];
#ifdef ENABLE_NOAA
Data16[6] = gEeprom.NoaaChannel[0];
Data16[7] = gEeprom.NoaaChannel[1];
#endif
PY25Q16_WriteBuffer(0x00A010, Data16, sizeof(Data16), false);
}
}
}
void SETTINGS_SaveSettings(void)
{
uint8_t *State;
uint8_t tmp = 0;
uint8_t SecBuf[0x50];
// ----------------------
// 0e70 - 0e80
memset(SecBuf, 0xff, 0x10);
// 0x0E70
State = SecBuf;
#ifdef ENABLE_FEAT_F4HWN_AUDIO
State[0] = (gSetting_set_audio_fm & 0x0F) | ((gSetting_set_audio_am & 0x0F) << 4);
#endif
#ifdef ENABLE_FEAT_F4HWN
if (gSquelchLevelOriginal < 10)
State[1] = gSquelchLevelOriginal;
else
#endif
State[1] = gEeprom.SQUELCH_LEVEL;
State[2] = gEeprom.TX_TIMEOUT_TIMER;
#ifdef ENABLE_NOAA
State[3] = gEeprom.NOAA_AUTO_SCAN;
#else
State[3] = false;
#endif
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
State[4] =
(gEeprom.KEY_LOCK ? 0x01 : 0) |
(gEeprom.MENU_LOCK ? 0x02 : 0) |
((gEeprom.SET_KEY & 0x0F) << 2) |
(gEeprom.SET_NAV ? 0x40 : 0);
#else
// A non-RescueOps preset owns KEY_LOCK and SET_NAV. Preserve the
// RescueOps-only MENU_LOCK and SET_KEY fields plus the reserved bit.
PY25Q16_ReadBuffer(0x00A004, &State[4], 1);
State[4] = (State[4] & 0xBEu) |
(gEeprom.KEY_LOCK ? 0x01u : 0u) |
(gEeprom.SET_NAV ? 0x40u : 0u);
#endif
#ifdef ENABLE_VOX
State[5] = gEeprom.VOX_SWITCH;
State[6] = gEeprom.VOX_LEVEL;
#else
State[5] = false;
State[6] = 0;
#endif
State[7] = gEeprom.MIC_SENSITIVITY;
// 0x0E78
State = SecBuf + 0x8;
State[0] = (gEeprom.BACKLIGHT_MIN << 4) + gEeprom.BACKLIGHT_MAX;
State[1] = gEeprom.CHANNEL_DISPLAY_MODE;
State[2] = gEeprom.CROSS_BAND_RX_TX;
State[3] = gEeprom.BATTERY_SAVE;
State[4] = gEeprom.DUAL_WATCH;
#ifdef ENABLE_FEAT_F4HWN
if(!gSaveRxMode)
{
State[2] = gCB;
State[4] = gDW;
}
if(gBackLight)
{
State[5] = gBacklightTimeOriginal;
}
else
{
State[5] = gEeprom.BACKLIGHT_TIME;
}
#else
State[5] = gEeprom.BACKLIGHT_TIME;
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
State[6] =
(gEeprom.TAIL_TONE_ELIMINATION & 0x01) |
((gSetting_set_nfm & 0x01) << 1)
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
| ((gEeprom.VFO_OPEN & 0x01) << 2)
#endif
;
#else
State[6] = gEeprom.TAIL_TONE_ELIMINATION;
#endif
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
State[7] = (gEeprom.CURRENT_STATE & 0x07) | ((gEeprom.SCAN_LIST_DEFAULT & 0x1F) << 3);
#else
State[7] = gEeprom.VFO_OPEN;
#endif
PY25Q16_WriteBuffer(0x00A000, SecBuf, 0x10, false);
// -------------------------
// 0e90 - 0ee0
// memset(SecBuf, 0xff, 0x50);
PY25Q16_ReadBuffer(0x00A0A8, SecBuf, 0x50);
// 0x0E90
State = SecBuf;
State[0] = gEeprom.BEEP_CONTROL;
State[0] |= gEeprom.KEY_M_LONG_PRESS_ACTION << 1;
State[1] = gEeprom.KEY_1_SHORT_PRESS_ACTION;
State[2] = gEeprom.KEY_1_LONG_PRESS_ACTION;
State[3] = gEeprom.KEY_2_SHORT_PRESS_ACTION;
State[4] = gEeprom.KEY_2_LONG_PRESS_ACTION;
State[5] = gEeprom.SCAN_RESUME_MODE;
State[6] = gEeprom.AUTO_KEYPAD_LOCK;
State[7] = gEeprom.POWER_ON_DISPLAY_MODE;
// 0x0E98
#ifdef ENABLE_PWRON_PASSWORD
State = SecBuf + 0x8;
State[0] = gEeprom.POWER_ON_PASSWORD;
#endif
// 0x0EA0
State = SecBuf + 0x10;
#ifdef ENABLE_VOICE
State[0] = gEeprom.VOICE_PROMPT;
#endif
#ifdef ENABLE_RSSI_BAR
State[1] = gEeprom.S0_LEVEL;
State[2] = gEeprom.S9_LEVEL;
#endif
// 0x0EA8
State = SecBuf + 0x18;
State[0] = false;
State[1] = gEeprom.ROGER;
State[2] = gEeprom.REPEATER_TAIL_TONE_ELIMINATION;
State[3] = gEeprom.TX_VFO;
State[4] = gEeprom.BATTERY_TYPE;
// 0x0ED0
State = SecBuf + 0x40;
State[0] = gEeprom.DTMF_SIDE_TONE;
#ifdef ENABLE_DTMF_CALLING
State[1] = gEeprom.DTMF_SEPARATE_CODE;
State[2] = gEeprom.DTMF_GROUP_CALL_CODE;
State[3] = gEeprom.DTMF_DECODE_RESPONSE;
State[4] = gEeprom.DTMF_auto_reset_time;
#endif
State[5] = gEeprom.DTMF_PRELOAD_TIME / 10U;
State[6] = gEeprom.DTMF_FIRST_CODE_PERSIST_TIME / 10U;
State[7] = gEeprom.DTMF_HASH_CODE_PERSIST_TIME / 10U;
// 0x0ED8
State = SecBuf + 0x48;
State[0] = gEeprom.DTMF_CODE_PERSIST_TIME / 10U;
State[1] = gEeprom.DTMF_CODE_INTERVAL_TIME / 10U;
#ifdef ENABLE_DTMF_CALLING
State[2] = gEeprom.PERMIT_REMOTE_KILL;
#endif
PY25Q16_WriteBuffer(0x00A0A8, SecBuf, 0x50, false);
// -------------------------
// 0f18 - 0f20
memset(SecBuf, 0xff, 0x08);
// 0x0F18
State = SecBuf;
State[0] = (gEeprom.SCAN_LIST_DEFAULT & 0x7F)
| ((gEeprom.SCAN_LIST_ENABLED & 0x01) << 7);
State[1] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[0] & 0xFF);
State[2] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[0] >> 8);
State[3] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[1] & 0xFF);
State[4] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[1] >> 8);
State[5] = (uint8_t)(gEeprom.CHAN_1_CALL & 0xFF);
State[6] = (uint8_t)(gEeprom.CHAN_1_CALL >> 8);
PY25Q16_WriteBuffer(0x00A130, SecBuf, 0x08, false);
// ---------------------
// 0f40 - 0f48
memset(SecBuf, 0xff, 8);
// 0x0F40
State = SecBuf;
State[0] = gSetting_F_LOCK;
#ifndef ENABLE_FEAT_F4HWN
State[1] = gSetting_350TX;
#endif
#ifdef ENABLE_DTMF_CALLING
State[2] = gSetting_KILLED;
#endif
#ifndef ENABLE_FEAT_F4HWN
State[3] = gSetting_200TX;
State[4] = gSetting_500TX;
#endif
State[5] = gSetting_350EN;
#ifdef ENABLE_FEAT_F4HWN
State[6] = false;
#else
State[6] = gSetting_ScrambleEnable;
#endif
//if (!gSetting_TX_EN) State[7] &= ~(1u << 0);
if (!gSetting_live_DTMF_decoder) State[7] &= ~(1u << 1);
State[7] = (State[7] & ~(3u << 2)) | ((gSetting_battery_text & 3u) << 2);
#ifdef ENABLE_AUDIO_BAR
if (!gSetting_mic_bar) State[7] &= ~(1u << 4);
#endif
// State[7] bit 5 is preserved (legacy ENABLE_AM_FIX).
State[7] = (State[7] & ~(3u << 6)) | ((gSetting_backlight_on_tx_rx & 3u) << 6);
PY25Q16_WriteBuffer(0x00A150, SecBuf, 8, false);
// ------------------
#ifdef ENABLE_FEAT_F4HWN
// 0x1FF0
State = SecBuf;
// TODO: TBD
PY25Q16_ReadBuffer(0x00A158, State, 8);
//memset(State, 0xFF, sizeof(State));
/*
tmp = 0;
if(gSetting_set_tmr == 1)
tmp = tmp | (1 << 0);
State[4] = tmp;
tmp = 0;
if(gSetting_set_inv == 1)
tmp = tmp | (1 << 0);
if (gSetting_set_met == 1)
tmp = tmp | (1 << 2);
if (gSetting_set_gui == 1)
tmp = tmp | (1 << 3);
*/
#ifdef ENABLE_FEAT_F4HWN_SLEEP
State[4] = (gSetting_set_off << 1) | (gSetting_set_tmr & 0x01);
#else
State[4] = gSetting_set_tmr ? (1 << 0) : 0;
#endif
tmp = (gSetting_set_inv << 0) |
(gSetting_set_met << 2) |
(gSetting_set_gui << 3);
State[2] = gSetting_set_lck;
State[5] = ((tmp << 4) | (gSetting_set_ctr & 0x0F));
State[6] = ((gSetting_set_tot << 4) | (gSetting_set_eot & 0x0F));
uint8_t set_ptt_scn_sav = gSetting_set_ptt & 0x01;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
if (!gSetting_set_scn)
set_ptt_scn_sav |= 0x02;
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
set_ptt_scn_sav |= (gSetting_set_sav & 0x03) << 2;
#endif
State[7] = ((gSetting_set_pwr << 4) | set_ptt_scn_sav);
PY25Q16_WriteBuffer(0x00A158, SecBuf, 8, false);
#endif
#ifdef ENABLE_FEAT_F4HWN_VOL
SETTINGS_WriteCurrentVol();
#endif
// 0F58..0F5F
PY25Q16_ReadBuffer(SETTINGS_SCAN_MIX_ADDR, SecBuf, 8);
SecBuf[0] = (uint8_t)(gEeprom.SCAN_LIST_MIX_MASK & 0xFFu);
SecBuf[1] = (uint8_t)((gEeprom.SCAN_LIST_MIX_MASK >> 8) & 0xFFu);
SecBuf[2] = (uint8_t)((gEeprom.SCAN_LIST_MIX_MASK >> 16) & 0xFFu);
SecBuf[3] = 'M';
SecBuf[4] = 'I';
SecBuf[5] = 'X';
SecBuf[6] = 1;
PY25Q16_WriteBuffer(SETTINGS_SCAN_MIX_ADDR, SecBuf, 8, false);
}
void SETTINGS_SaveChannel(uint16_t Channel, uint8_t VFO, const VFO_Info_t *pVFO, uint8_t Mode)
{
#ifdef ENABLE_NOAA
if (IS_NOAA_CHANNEL(Channel))
return;
#endif
// 0
uint16_t OffsetVFO = 0 + Channel * 16;
if (IS_FREQ_CHANNEL(Channel)) { // it's a VFO, not a channel
// 0x0C80
OffsetVFO = (VFO == 0) ? 0x009000 : 0x009010;
OffsetVFO += (Channel - FREQ_CHANNEL_FIRST) * 32;
}
if (Mode >= 2 || IS_FREQ_CHANNEL(Channel)) { // copy VFO to a channel
typedef union {
uint8_t _8[8];
uint32_t _32[2];
} State_t;
State_t *State;
uint8_t Buf[0x10];
State = (State_t *)Buf;
State -> _32[0] = pVFO->freq_config_RX.Frequency;
State -> _32[1] = pVFO->TX_OFFSET_FREQUENCY;
State = (State_t *)(Buf + 0x8);
State -> _8[0] = pVFO->freq_config_RX.Code;
State -> _8[1] = pVFO->freq_config_TX.Code;
State -> _8[2] = (pVFO->freq_config_TX.CodeType << 4) | pVFO->freq_config_RX.CodeType;
State -> _8[3] = (pVFO->Modulation << 4) | pVFO->TX_OFFSET_FREQUENCY_DIRECTION;
State -> _8[4] = 0
| (pVFO->TX_LOCK << 6)
| (pVFO->BUSY_CHANNEL_LOCK << 5)
| (pVFO->OUTPUT_POWER << 2)
| (pVFO->CHANNEL_BANDWIDTH << 1)
| (pVFO->FrequencyReverse << 0);
State -> _8[5] = ((pVFO->DTMF_PTT_ID_TX_MODE & 7u) << 1)
#ifdef ENABLE_DTMF_CALLING
| ((pVFO->DTMF_DECODING_ENABLE & 1u) << 0)
#endif
;
State -> _8[6] = pVFO->STEP_SETTING;
#ifdef ENABLE_FEAT_F4HWN
State -> _8[7] = 0;
#else
State -> _8[7] = pVFO->SCRAMBLING_TYPE;
#endif
PY25Q16_WriteBuffer(OffsetVFO, Buf, 0x10, false);
SETTINGS_UpdateChannel(Channel, pVFO, true);
if (IS_MR_CHANNEL(Channel)) {
#ifndef ENABLE_KEEP_MEM_NAME
// clear/reset the channel name
SETTINGS_SaveChannelName(Channel, "");
#else
if (Mode >= 3) {
SETTINGS_SaveChannelName(Channel, pVFO->Name);
}
#endif
}
}
}
void SETTINGS_SaveBatteryCalibration(const uint16_t * batteryCalibration)
{
// 0x1F40
PY25Q16_WriteBuffer(0x010000 + 0x140, batteryCalibration, 12, false);
}
void SETTINGS_SaveChannelName(uint16_t channel, const char * name)
{
uint16_t offset = channel * 16;
uint8_t buf[16] = {0};
memcpy(buf, name, MIN(strlen(name), 10u));
// 0x0F50
PY25Q16_WriteBuffer(0x004000 + offset, buf, 0x10, false);
}
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep)
{
#ifdef ENABLE_NOAA
if (IS_NOAA_CHANNEL(channel))
return;
#endif
ChannelAttributes_t att = {
.band = 0x7,
.compander = 0,
.unused_1 = 0,
.unused_2 = 0,
.exclude = 0,
.scanlist = 0,
};
if (keep) {
att.band = pVFO->Band;
att.compander = pVFO->Compander;
att.scanlist = pVFO->SCANLIST_PARTICIPATION;
}
MR_SetChannelAttributes(channel, &att);
if (IS_MR_CHANNEL(channel) && !keep)
SETTINGS_SaveChannelName(channel, "");
}
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
void SETTINGS_WriteCurrentState(void)
{
uint8_t State[0x08];
PY25Q16_ReadBuffer(0x00A008, State, sizeof(State));
State[7] =
(gEeprom.CURRENT_STATE & 0x07) |
((gEeprom.SCAN_LIST_DEFAULT & 0x1F) << 3);
PY25Q16_WriteBuffer(0x00A008, State, sizeof(State), false);
//
PY25Q16_ReadBuffer(0x00A130, State, sizeof(State));
State[0] = (gEeprom.SCAN_LIST_DEFAULT & 0x7F)
| ((gEeprom.SCAN_LIST_ENABLED & 0x01) << 7);
State[1] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[0] & 0xFF);
State[2] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[0] >> 8);
State[3] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[1] & 0xFF);
State[4] = (uint8_t)(gEeprom.SCANLIST_PRIORITY_CH[1] >> 8);
State[5] = (uint8_t)(gEeprom.CHAN_1_CALL & 0xFF);
State[6] = (uint8_t)(gEeprom.CHAN_1_CALL >> 8);
PY25Q16_WriteBuffer(0x00A130, State, sizeof(State), false);
}
#endif
#ifdef ENABLE_FEAT_F4HWN_VOL
void SETTINGS_WriteCurrentVol(void)
{
uint8_t State[8];
// 0x1F88
PY25Q16_ReadBuffer(0x010000 + 0x188, State, sizeof(State));
State[6] = gEeprom.VOLUME_GAIN;
PY25Q16_WriteBuffer(0x010000 + 0x188, State, sizeof(State), false);
}
#endif
#ifdef ENABLE_FEAT_F4HWN
void SETTINGS_ResetTxLock(void)
{
// This is an expensive operation: full scan of all MR channels
#define CHANNEL_SIZE 16
#define TXLOCK_BYTE_OFFSET 12
#define TXLOCK_BIT 6
#define SETTINGS_ResetTxLock_BATCH 32
const uint32_t TotalBytes = MR_CHANNELS_MAX * CHANNEL_SIZE; // 1024 * 16 = 16 384
const uint32_t BatchSize = TotalBytes / SETTINGS_ResetTxLock_BATCH; // 16 384 / 32 = 512
const uint32_t BatchChCnt = BatchSize / CHANNEL_SIZE; // 32 channels per batch
uint8_t Buf[BatchSize];
for (uint32_t batch = 0; batch < SETTINGS_ResetTxLock_BATCH; batch++)
{
uint32_t Offset = batch * BatchSize;
PY25Q16_ReadBuffer(Offset, Buf, BatchSize);
for (uint32_t ch = 0; ch < BatchChCnt; ch++)
{
uint32_t off = ch * CHANNEL_SIZE;
Buf[off + TXLOCK_BYTE_OFFSET] |= (1 << TXLOCK_BIT);
}
PY25Q16_WriteBuffer(Offset, Buf, BatchSize, false);
}
RADIO_ConfigureChannel(0, VFO_CONFIGURE_RELOAD);
RADIO_ConfigureChannel(1, VFO_CONFIGURE_RELOAD);
#undef SETTINGS_ResetTxLock_BATCH
#undef CHANNEL_SIZE
#undef TXLOCK_BYTE_OFFSET
#undef TXLOCK_BIT
}
#endif