/* * Copyright (C)2019 Kai Ludwig, DG4KLU * and Roger Clark, VK3KYY / G4KYF * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "hardware/EEPROM.h" #include "functions/settings.h" #include "functions/sound.h" #include "functions/trx.h" #include "user_interface/menuSystem.h" #include "user_interface/uiLocalisation.h" #include "functions/ticks.h" #include "functions/rxPowerSaving.h" static const int STORAGE_BASE_ADDRESS = 0x6000; static const int STORAGE_MAGIC_NUMBER = 0x475C; // NOTE: never use 0xDEADBEEF, it's reserved value // Bit patterns for DMR Beep const uint8_t BEEP_TX_NONE = 0x00; const uint8_t BEEP_TX_START = 0x01; const uint8_t BEEP_TX_STOP = 0x02; #if defined(PLATFORM_RD5R) static uint32_t dirtyTime = 0; #endif static bool settingsDirty = false; static bool settingsVFODirty = false; settingsStruct_t nonVolatileSettings; struct_codeplugChannel_t *currentChannelData; struct_codeplugChannel_t channelScreenChannelData = { .rxFreq = 0 }; struct_codeplugContact_t contactListContactData; struct_codeplugDTMFContact_t contactListDTMFContactData; struct_codeplugChannel_t settingsVFOChannel[2];// VFO A and VFO B from the codeplug. int settingsUsbMode = USB_MODE_CPS; int *nextKeyBeepMelody = (int *)MELODY_KEY_BEEP; struct_codeplugGeneralSettings_t settingsCodeplugGeneralSettings; monitorModeSettingsStruct_t monitorModeData = {.isEnabled = false}; const int ECO_LEVEL_MAX = 4; bool settingsSaveSettings(bool includeVFOs) { if (includeVFOs) { codeplugSetVFO_ChannelData(&settingsVFOChannel[CHANNEL_VFO_A], CHANNEL_VFO_A); codeplugSetVFO_ChannelData(&settingsVFOChannel[CHANNEL_VFO_B], CHANNEL_VFO_B); settingsVFODirty = false; } // Never reset this setting (as voicePromptsCacheInit() can change it if voice data are missing) #if defined(PLATFORM_GD77S) nonVolatileSettings.audioPromptMode = AUDIO_PROMPT_MODE_VOICE_LEVEL_3; #endif bool ret = EEPROM_Write(STORAGE_BASE_ADDRESS, (uint8_t *)&nonVolatileSettings, sizeof(settingsStruct_t)); if (ret) { settingsDirty = false; } return ret; } bool settingsLoadSettings(void) { bool hasRestoredDefaultsettings = false; if (!EEPROM_Read(STORAGE_BASE_ADDRESS, (uint8_t *)&nonVolatileSettings, sizeof(settingsStruct_t))) { nonVolatileSettings.magicNumber = 0;// flag settings could not be loaded } if (nonVolatileSettings.magicNumber != STORAGE_MAGIC_NUMBER) { settingsRestoreDefaultSettings(); hasRestoredDefaultsettings = true; } // Force Hotspot mode to off for existing RD-5R users. #if defined(PLATFORM_RD5R) nonVolatileSettings.hotspotType = HOTSPOT_TYPE_OFF; #endif codeplugGetVFO_ChannelData(&settingsVFOChannel[CHANNEL_VFO_A], CHANNEL_VFO_A); codeplugGetVFO_ChannelData(&settingsVFOChannel[CHANNEL_VFO_B], CHANNEL_VFO_B); /* 2020.10.27 vk3kyy. This should not be necessary as the rest of the fimware e.g. on the VFO screen and in the contact lookup handles when Rx Group and / or Contact is set to none settingsInitVFOChannel(0);// clean up any problems with VFO data settingsInitVFOChannel(1); */ trxDMRID = codeplugGetUserDMRID(); struct_codeplugDeviceInfo_t tmpDeviceInfoBuffer;// Temporary buffer to load the data including the CPS user band limits if (codeplugGetDeviceInfo(&tmpDeviceInfoBuffer)) { // Validate CPS band limit data if ( (tmpDeviceInfoBuffer.minVHFFreq < tmpDeviceInfoBuffer.maxVHFFreq) && (tmpDeviceInfoBuffer.minUHFFreq > tmpDeviceInfoBuffer.maxVHFFreq) && (tmpDeviceInfoBuffer.minUHFFreq < tmpDeviceInfoBuffer.maxUHFFreq) && ((tmpDeviceInfoBuffer.minVHFFreq * 100000) >= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_VHF].minFreq) && ((tmpDeviceInfoBuffer.minVHFFreq * 100000) <= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_VHF].maxFreq) && ((tmpDeviceInfoBuffer.maxVHFFreq * 100000) >= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_VHF].minFreq) && ((tmpDeviceInfoBuffer.maxVHFFreq * 100000) <= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_VHF].maxFreq) && ((tmpDeviceInfoBuffer.minUHFFreq * 100000) >= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_UHF].minFreq) && ((tmpDeviceInfoBuffer.minUHFFreq * 100000) <= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_UHF].maxFreq) && ((tmpDeviceInfoBuffer.maxUHFFreq * 100000) >= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_UHF].minFreq) && ((tmpDeviceInfoBuffer.maxUHFFreq * 100000) <= RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BAND_UHF].maxFreq) ) { // Only use it, if EVERYTHING is OK. USER_FREQUENCY_BANDS[RADIO_BAND_VHF].minFreq = tmpDeviceInfoBuffer.minVHFFreq * 100000;// value needs to be in 10s of Hz; USER_FREQUENCY_BANDS[RADIO_BAND_VHF].maxFreq = tmpDeviceInfoBuffer.maxVHFFreq * 100000;// value needs to be in 10s of Hz; USER_FREQUENCY_BANDS[RADIO_BAND_UHF].minFreq = tmpDeviceInfoBuffer.minUHFFreq * 100000;// value needs to be in 10s of Hz; USER_FREQUENCY_BANDS[RADIO_BAND_UHF].maxFreq = tmpDeviceInfoBuffer.maxUHFFreq * 100000;// value needs to be in 10s of Hz; } } //codeplugGetGeneralSettings(&settingsCodeplugGeneralSettings); if (nonVolatileSettings.languageIndex >= NUM_LANGUAGES) { nonVolatileSettings.languageIndex = 0U; // Reset to English settingsSetDirty(); } else { settingsDirty = false; } currentLanguage = &languages[nonVolatileSettings.languageIndex]; soundBeepVolumeDivider = nonVolatileSettings.beepVolumeDivider; trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel); codeplugInitChannelsPerZone();// Initialise the codeplug channels per zone settingsVFODirty = false; rxPowerSavingSetLevel(nonVolatileSettings.ecoLevel); return hasRestoredDefaultsettings; } void settingsInitVFOChannel(int vfoNumber) { // temporary hack in case the code plug has no RxGroup selected // The TG needs to come from the RxGroupList if (settingsVFOChannel[vfoNumber].rxGroupList == 0) { settingsVFOChannel[vfoNumber].rxGroupList = 1; } if (settingsVFOChannel[vfoNumber].contact == 0) { settingsVFOChannel[vfoNumber].contact = 1; } } void settingsRestoreDefaultSettings(void) { nonVolatileSettings.magicNumber = STORAGE_MAGIC_NUMBER; nonVolatileSettings.currentChannelIndexInZone = 0; nonVolatileSettings.currentChannelIndexInAllZone = 1; nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] = 0; nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE] = 0; nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_B_MODE] = 0; nonVolatileSettings.currentZone = 0; nonVolatileSettings.backlightMode = #if defined(PLATFORM_GD77S) BACKLIGHT_MODE_NONE; #else BACKLIGHT_MODE_AUTO; #endif nonVolatileSettings.backLightTimeout = 0U;//0 = never timeout. 1 - 255 time in seconds nonVolatileSettings.displayContrast = #if defined(PLATFORM_DM1801) 0x0e; // 14 #elif defined (PLATFORM_RD5R) 0x06; #else 0x12; // 18 #endif nonVolatileSettings.initialMenuNumber = #if defined(PLATFORM_GD77S) UI_CHANNEL_MODE; #else UI_VFO_MODE; #endif nonVolatileSettings.displayBacklightPercentage = 100;// 100% brightness nonVolatileSettings.displayBacklightPercentageOff = 0;// 0% brightness nonVolatileSettings.extendedInfosOnScreen = INFO_ON_SCREEN_OFF; nonVolatileSettings.txFreqLimited = #if defined(PLATFORM_GD77S) BAND_LIMITS_NONE;//GD-77S is channelised, and there is no way to disable band limits from the UI, so disable limits by default. #else BAND_LIMITS_ON_LEGACY_DEFAULT;// Limit Tx frequency to US Amateur bands #endif nonVolatileSettings.txPowerLevel = #if defined(PLATFORM_GD77S) 3U; // 750mW #else 4U; // 1 W 3:750 2:500 1:250 #endif nonVolatileSettings.userPower = 4100U;// Max DAC value is 4095. 4100 is a hack to make the numbers more palatable. nonVolatileSettings.bitfieldOptions = #if defined(PLATFORM_GD77S) 0U; #else BIT_SETTINGS_UPDATED; // we need to keep track if the user has been notified about settings update. #endif nonVolatileSettings.overrideTG = 0U;// 0 = No override nonVolatileSettings.txTimeoutBeepX5Secs = 2U; nonVolatileSettings.beepVolumeDivider = 4U; //-6dB: Beeps are way too loud using the same setting as the official firmware nonVolatileSettings.micGainDMR = 11U; // Normal value used by the official firmware nonVolatileSettings.micGainFM = 17U; // Default (from all of my cals, datasheet default: 16) nonVolatileSettings.tsManualOverride = 0U; // No manual TS override using the Star key nonVolatileSettings.keypadTimerLong = 5U; nonVolatileSettings.keypadTimerRepeat = 3U; nonVolatileSettings.currentVFONumber = CHANNEL_VFO_A; nonVolatileSettings.dmrDestinationFilter = #if defined(PLATFORM_GD77S) DMR_DESTINATION_FILTER_TG; #else DMR_DESTINATION_FILTER_NONE; #endif nonVolatileSettings.dmrCcTsFilter = DMR_CCTS_FILTER_CC_TS; nonVolatileSettings.dmrCaptureTimeout = 10U;// Default to holding 10 seconds after a call ends nonVolatileSettings.analogFilterLevel = ANALOG_FILTER_CSS; trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel); nonVolatileSettings.languageIndex = 0U; nonVolatileSettings.scanDelay = 5U;// 5 seconds nonVolatileSettings.scanStepTime = 0;// 30ms nonVolatileSettings.scanModePause = SCAN_MODE_HOLD; nonVolatileSettings.squelchDefaults[RADIO_BAND_VHF] = 10U;// 1 - 21 = 0 - 100% , same as from the CPS variable squelch nonVolatileSettings.squelchDefaults[RADIO_BAND_220MHz] = 10U;// 1 - 21 = 0 - 100% , same as from the CPS variable squelch nonVolatileSettings.squelchDefaults[RADIO_BAND_UHF] = 10U;// 1 - 21 = 0 - 100% , same as from the CPS variable squelch nonVolatileSettings.hotspotType = #if defined(PLATFORM_GD77S) HOTSPOT_TYPE_MMDVM; #else HOTSPOT_TYPE_OFF; #endif nonVolatileSettings.privateCalls = #if defined(PLATFORM_GD77S) ALLOW_PRIVATE_CALLS_OFF; #else ALLOW_PRIVATE_CALLS_ON; #endif // Set all these value to zero to force the operator to set their own limits. nonVolatileSettings.vfoScanLow[CHANNEL_VFO_A] = 0U; nonVolatileSettings.vfoScanLow[CHANNEL_VFO_B] = 0U; nonVolatileSettings.vfoScanHigh[CHANNEL_VFO_A] = 0U; nonVolatileSettings.vfoScanHigh[CHANNEL_VFO_B] = 0U; nonVolatileSettings.contactDisplayPriority = CONTACT_DISPLAY_PRIO_CC_DB_TA; nonVolatileSettings.splitContact = SPLIT_CONTACT_SINGLE_LINE_ONLY; nonVolatileSettings.beepOptions = BEEP_TX_STOP | BEEP_TX_START; // VOX related nonVolatileSettings.voxThreshold = 20U; nonVolatileSettings.voxTailUnits = 4U; // 2 seconds tail #if defined(PLATFORM_GD77S) nonVolatileSettings.audioPromptMode = AUDIO_PROMPT_MODE_VOICE_LEVEL_3; #else if (voicePromptDataIsLoaded) { nonVolatileSettings.audioPromptMode = AUDIO_PROMPT_MODE_VOICE_LEVEL_1; } else { nonVolatileSettings.audioPromptMode = AUDIO_PROMPT_MODE_BEEP; } #endif nonVolatileSettings.temperatureCalibration = 0; nonVolatileSettings.ecoLevel = 1; currentChannelData = &settingsVFOChannel[nonVolatileSettings.currentVFONumber];// Set the current channel data to point to the VFO data since the default screen will be the VFO settingsDirty = true; settingsSaveSettings(false); } void enableVoicePromptsIfLoaded(void) { if (voicePromptDataIsLoaded) { #if defined(PLATFORM_GD77S) nonVolatileSettings.audioPromptMode = AUDIO_PROMPT_MODE_VOICE_LEVEL_3; #else nonVolatileSettings.audioPromptMode = AUDIO_PROMPT_MODE_VOICE_LEVEL_1; #endif settingsDirty = true; settingsSaveSettings(false); } } void settingsEraseCustomContent(void) { //Erase OpenGD77 custom content SPI_Flash_eraseSector(0);// The first sector (4k) contains the OpenGD77 custom codeplug content e.g. Boot melody and boot image. } // --- Helpers --- void settingsSetBOOL(bool *s, bool v) { *s = v; settingsSetDirty(); } void settingsSetINT8(int8_t *s, int8_t v) { *s = v; settingsSetDirty(); } void settingsSetUINT8(uint8_t *s, uint8_t v) { *s = v; settingsSetDirty(); } void settingsSetINT16(int16_t *s, int16_t v) { *s = v; settingsSetDirty(); } void settingsSetUINT16(uint16_t *s, uint16_t v) { *s = v; settingsSetDirty(); } void settingsSetINT32(int32_t *s, int32_t v) { *s = v; settingsSetDirty(); } void settingsSetUINT32(uint32_t *s, uint32_t v) { *s = v; settingsSetDirty(); } void settingsIncINT8(int8_t *s, int8_t v) { *s = *s + v; settingsSetDirty(); } void settingsIncUINT8(uint8_t *s, uint8_t v) { *s = *s + v; settingsSetDirty(); } void settingsIncINT16(int16_t *s, int16_t v) { *s = *s + v; settingsSetDirty(); } void settingsIncUINT16(uint16_t *s, uint16_t v) { *s = *s + v; settingsSetDirty(); } void settingsIncINT32(int32_t *s, int32_t v) { *s = *s + v; settingsSetDirty(); } void settingsIncUINT32(uint32_t *s, uint32_t v) { *s = *s + v; settingsSetDirty(); } void settingsDecINT8(int8_t *s, int8_t v) { *s = *s - v; settingsSetDirty(); } void settingsDecUINT8(uint8_t *s, uint8_t v) { *s = *s - v; settingsSetDirty(); } void settingsDecINT16(int16_t *s, int16_t v) { *s = *s - v; settingsSetDirty(); } void settingsDecUINT16(uint16_t *s, uint16_t v) { *s = *s - v; settingsSetDirty(); } void settingsDecINT32(int32_t *s, int32_t v) { *s = *s - v; settingsSetDirty(); } void settingsDecUINT32(uint32_t *s, uint32_t v) { *s = *s - v; settingsSetDirty(); } // --- End of Helpers --- void settingsSetOptionBit(bitfieldOptions_t bit, bool set) { if (set) { nonVolatileSettings.bitfieldOptions |= bit; } else { nonVolatileSettings.bitfieldOptions &= ~bit; } settingsSetDirty(); } bool settingsIsOptionBitSet(bitfieldOptions_t bit) { return ((nonVolatileSettings.bitfieldOptions & bit) == (bit)); } void settingsSetDirty(void) { settingsDirty = true; #if defined(PLATFORM_RD5R) dirtyTime = fw_millis(); #endif } void settingsSetVFODirty(void) { settingsVFODirty = true; #if defined(PLATFORM_RD5R) dirtyTime = fw_millis(); #endif } void settingsSaveIfNeeded(bool immediately) { #if defined(PLATFORM_RD5R) const int DIRTY_DURTION_MILLISECS = 500; if ((settingsDirty || settingsVFODirty) && (immediately || (((fw_millis() - dirtyTime) > DIRTY_DURTION_MILLISECS) && // DIRTY_DURTION_ has passed since last change ((uiDataGlobal.Scan.active == false) || // not scanning, or scanning anything but channels (menuSystemGetCurrentMenuNumber() != UI_CHANNEL_MODE))))) { settingsSaveSettings(settingsVFODirty); } #endif } int settingsGetScanStepTimeMilliseconds(void) { return TIMESLOT_DURATION + (nonVolatileSettings.scanStepTime * TIMESLOT_DURATION); }