From 85f1bfdb7f2bf384c7ef321f21b88f3b091114aa Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sun, 8 Feb 2026 03:24:25 +0100 Subject: [PATCH 01/47] Fix SCAN_SAVE_CHANNEL and UI_GenerateChannelStringEx --- App/app/app.c | 2 +- App/app/menu.c | 7 ++++--- App/app/scanner.c | 21 ++++++++++++++------- App/app/scanner.h | 2 +- App/ui/helper.c | 4 ++-- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/App/app/app.c b/App/app/app.c index 6820ca57..271a9696 100644 --- a/App/app/app.c +++ b/App/app/app.c @@ -2192,7 +2192,7 @@ Skip: } if (gRequestSaveChannel > 0) { // TODO: remove the gRequestSaveChannel, why use global variable for that?? - if ((!bKeyHeld && !bKeyPressed) || (UI_MENU_GetCurrentMenuId() != 0 && gScreenToDisplay == DISPLAY_MENU)) + if ((!bKeyHeld && !bKeyPressed) || (UI_MENU_GetCurrentMenuId() != 0 && gScreenToDisplay == DISPLAY_MENU) || gScreenToDisplay == DISPLAY_SCANNER) { SETTINGS_SaveChannel(gTxVfo->CHANNEL_SAVE, gEeprom.TX_VFO, gTxVfo, gRequestSaveChannel); diff --git a/App/app/menu.c b/App/app/menu.c index c5bad9e4..5f421c4c 100644 --- a/App/app/menu.c +++ b/App/app/menu.c @@ -1553,9 +1553,9 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_1 || UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_2 || UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME) - { // enter 3-digit channel number + { // enter 4-digit channel number - if (gInputBoxIndex < 3) + if (gInputBoxIndex < 4) { #ifdef ENABLE_VOICE gAnotherVoiceID = (VOICE_ID_t)Key; @@ -1566,7 +1566,8 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) gInputBoxIndex = 0; - Value = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1; + //Value = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1; + Value = ((gInputBox[0] * 1000) + (gInputBox[1] * 100) + (gInputBox[2] * 10) + gInputBox[3]) - 1; if (IS_MR_CHANNEL(Value)) { diff --git a/App/app/scanner.c b/App/app/scanner.c index f7a0b7be..85fdb434 100644 --- a/App/app/scanner.c +++ b/App/app/scanner.c @@ -32,7 +32,7 @@ DCS_CodeType_t gScanCssResultType; uint8_t gScanCssResultCode; bool gScanSingleFrequency; // scan CTCSS/DCS codes for current frequency SCAN_SaveState_t gScannerSaveState; -uint8_t gScanChannel; +uint16_t gScanChannel; uint32_t gScanFrequency; SCAN_CssState_t gScanCssState; uint8_t gScanProgressIndicator; @@ -52,7 +52,7 @@ static void SCANNER_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) gRequestDisplayScreen = DISPLAY_SCANNER; - if (gInputBoxIndex < 3) { + if (gInputBoxIndex < 4) { #ifdef ENABLE_VOICE gAnotherVoiceID = (VOICE_ID_t)Key; #endif @@ -61,13 +61,14 @@ static void SCANNER_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) gInputBoxIndex = 0; - uint16_t chan = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1; + // uint16_t chan = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1; + uint16_t chan = ((gInputBox[0] * 1000) + (gInputBox[1] * 100) + (gInputBox[2] * 10) + gInputBox[3]) - 1; if (IS_MR_CHANNEL(chan)) { #ifdef ENABLE_VOICE gAnotherVoiceID = (VOICE_ID_t)Key; #endif gShowChPrefix = RADIO_CheckValidChannel(chan, false, 0); - gScanChannel = (uint8_t)chan; + gScanChannel = chan; return; } } @@ -207,7 +208,7 @@ static void SCANNER_Key_MENU(bool bKeyPressed, bool bKeyHeld) gTxVfo->freq_config_TX.Code = gScanCssResultCode; } - uint8_t chan; + uint16_t chan; if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE)) { chan = gScanChannel; gEeprom.MrChannel[gEeprom.TX_VFO] = chan; @@ -275,10 +276,16 @@ void SCANNER_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) SCANNER_Key_MENU(bKeyPressed, bKeyHeld); break; case KEY_UP: - SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); + if(gEeprom.SET_NAV == 0) + SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); + else + SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); break; case KEY_DOWN: - SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); + if(gEeprom.SET_NAV == 0) + SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); + else + SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); break; case KEY_EXIT: SCANNER_Key_EXIT(bKeyPressed, bKeyHeld); diff --git a/App/app/scanner.h b/App/app/scanner.h index 6217ef59..be409a78 100644 --- a/App/app/scanner.h +++ b/App/app/scanner.h @@ -40,7 +40,7 @@ extern DCS_CodeType_t gScanCssResultType; extern uint8_t gScanCssResultCode; extern bool gScanSingleFrequency; extern SCAN_SaveState_t gScannerSaveState; -extern uint8_t gScanChannel; +extern uint16_t gScanChannel; extern uint32_t gScanFrequency; extern SCAN_CssState_t gScanCssState; extern uint8_t gScanProgressIndicator; diff --git a/App/ui/helper.c b/App/ui/helper.c index 408673cd..f49492c0 100644 --- a/App/ui/helper.c +++ b/App/ui/helper.c @@ -44,11 +44,11 @@ void UI_GenerateChannelString(char *pString, const uint16_t Channel) void UI_GenerateChannelStringEx(char *pString, const bool bShowPrefix, const uint16_t ChannelNumber) { if (gInputBoxIndex > 0) { - for (unsigned int i = 0; i < 3; i++) { + for (unsigned int i = 0; i < 4; i++) { pString[i] = (gInputBox[i] == 10) ? '-' : gInputBox[i] + '0'; } - pString[3] = 0; + pString[4] = 0; return; } From 1236c3863fd6623881d676487ff4c2b55177cd62 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sun, 8 Feb 2026 03:56:13 +0100 Subject: [PATCH 02/47] Restore 10-character channel name limit with MAIN ONLY --- App/ui/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App/ui/main.c b/App/ui/main.c index 80f47384..a7f42073 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -1004,7 +1004,7 @@ void UI_DisplayMain(void) #ifdef ENABLE_FEAT_F4HWN if (isMainOnly()) { - String[9] = 0; + String[10] = 0; UI_PrintString(String, 36, 0, line, 8); } else From 712a984b96f06a31334bbec1075d470cc8e957b1 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sun, 8 Feb 2026 04:29:09 +0100 Subject: [PATCH 03/47] Update donors --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 657bb429..b1b689d2 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Anyway, have fun. # Donations -Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar, Zhuo BG7SGA and Jamie M0JLB for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 +Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB and Antoine LIBERT for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 ## Table of Contents From 7957377388ed0052b64560c2bc3dcf527ab460b9 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Tue, 10 Feb 2026 01:35:08 +0100 Subject: [PATCH 04/47] Add named lists --- App/driver/eeprom_compat.c | 3 ++- App/misc.c | 1 + App/misc.h | 2 ++ App/settings.c | 3 +++ App/ui/main.c | 34 ++++++++++++++++++++++++++++------ App/ui/menu.c | 21 +++++++++++++++++---- App/ui/status.c | 18 ++++++++++++------ 7 files changed, 65 insertions(+), 17 deletions(-) diff --git a/App/driver/eeprom_compat.c b/App/driver/eeprom_compat.c index 60bfa8db..59b5e874 100644 --- a/App/driver/eeprom_compat.c +++ b/App/driver/eeprom_compat.c @@ -48,7 +48,8 @@ static const AddrMapping_t ADDR_MAPPINGS[] = { _MK_MAPPING(0x006000, 0x006000, 0x007000), // 256 MR Name * 16 Bytes _MK_MAPPING(0x007000, 0x007000, 0x008000), // 256 MR Name * 16 Bytes - _MK_MAPPING(0x008000, 0x008000, 0x00880E), // 1024 MR + 7 VFO Attributes * 2 Bytes (ex 0x002000) + _MK_MAPPING(0x008000, 0x008000, 0x00886E), // 1024 MR + 7 VFO Attributes * 2 Bytes (ex 0x002000) 0x008000 -> 0x00880E + // List name * 4 Bytes 0x00880E -> 0x00886E _MK_MAPPING(0x009000, 0x009000, 0x0090D6), // 14 VFO * 16 Bytes (ex 0x001000) diff --git a/App/misc.c b/App/misc.c index 91cb977f..00ca1bb9 100644 --- a/App/misc.c +++ b/App/misc.c @@ -334,6 +334,7 @@ uint8_t gIsLocked = 0xFF; uint16_t gVfoSaveCountdown_10ms = 0; bool gScheduleVfoSave = false; bool gVfoStateChanged = false; + char gListName[MR_CHANNELS_LIST][4]; #endif inline void FUNCTION_NOP() { ; } diff --git a/App/misc.h b/App/misc.h index cab2e4d7..e42bcde9 100644 --- a/App/misc.h +++ b/App/misc.h @@ -455,6 +455,8 @@ extern volatile uint8_t boot_counter_10ms; extern uint16_t gVfoSaveCountdown_10ms; extern bool gScheduleVfoSave; extern bool gVfoStateChanged; + + extern char gListName[MR_CHANNELS_LIST][4]; #endif int32_t NUMBER_AddWithWraparound(int32_t Base, int32_t Add, int32_t LowerLimit, int32_t UpperLimit); diff --git a/App/settings.c b/App/settings.c index 904353de..0216c906 100644 --- a/App/settings.c +++ b/App/settings.c @@ -403,6 +403,9 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE } */ + // Init list name + PY25Q16_ReadBuffer(0x00880E, gListName, sizeof(gListName)); + // Init attr cache MR_InitChannelAttributesCache(); diff --git a/App/ui/main.c b/App/ui/main.c index a7f42073..6156efe5 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -917,13 +917,35 @@ void UI_DisplayMain(void) countList = 0; } - const char *displayStr = (countList == MR_CHANNELS_LIST + 1) ? "ALL" : - (countList == 0) ? "OFF" : String; - uint8_t xStart = (countList > 0 && countList != MR_CHANNELS_LIST + 1) ? 117 : 113; - uint8_t xDisplay = (countList > 0 && countList != MR_CHANNELS_LIST + 1) ? 119 : 115; + const char *displayStr; + uint8_t xStart, xDisplay; - if(countList > 0 && countList != MR_CHANNELS_LIST + 1) { - sprintf(String, "%02d", countList); + if (countList == MR_CHANNELS_LIST + 1) { + displayStr = "ALL"; + xStart = 113; + xDisplay = 115; + } + else if (countList == 0) { + displayStr = "OFF"; + xStart = 113; + xDisplay = 115; + } + else { + // List 1 to MR_CHANNELS_LIST + const char *name = gListName[countList - 1]; + + // If name is empty/invalid, display number + if (name[0] == '\0' || name[0] == '\xff' || name[0] == ' ') { + sprintf(String, "%02d", countList); + xStart = 117; // 2-digit number aligned right + xDisplay = 119; + } + else { + sprintf(String, "%.3s", name); + xStart = 113; // 3-char name aligned left + xDisplay = 115; + } + displayStr = String; } GUI_DisplaySmallest(displayStr, xDisplay, line == 0 ? 2 : 34, false, true); diff --git a/App/ui/menu.c b/App/ui/menu.c index a3682be4..adb86386 100644 --- a/App/ui/menu.c +++ b/App/ui/menu.c @@ -969,15 +969,28 @@ void UI_DisplayMenu(void) strcpy(String, "ALL"); else if (gSubMenuSelection == 0) strcpy(String, "OFF"); - else - sprintf(String, "%u", gSubMenuSelection); + else { + const char *name = gListName[gSubMenuSelection - 1]; + + // If first character is empty/invalid, display "N/A" + if (name[0] == '\0' || name[0] == '\xff' || name[0] == ' ') + sprintf(String, "%02u - N/A", gSubMenuSelection); + else + sprintf(String, "%02u - %.3s", gSubMenuSelection, name); + } break; case MENU_S_LIST: if (gSubMenuSelection == MR_CHANNELS_LIST + 1) strcpy(String, "ALL"); - else - sprintf(String, "%u", gSubMenuSelection); + else { + const char *name = gListName[gSubMenuSelection - 1]; + // If first character is empty/invalid, display "N/A" + if (name[0] == '\0' || name[0] == '\xff' || name[0] == ' ') + sprintf(String, "%02u - N/A", gSubMenuSelection); + else + sprintf(String, "%02u - %.3s", gSubMenuSelection, name); + } break; #ifdef ENABLE_ALARM diff --git a/App/ui/status.c b/App/ui/status.c index 05f3ea32..4db85745 100644 --- a/App/ui/status.c +++ b/App/ui/status.c @@ -111,13 +111,19 @@ void UI_DisplayStatus() } else { - if(gEeprom.SCAN_LIST_ENABLED==1) { - sprintf(str, "%02d+", gEeprom.SCAN_LIST_DEFAULT); - end = 15; - } + const char *name = gListName[gEeprom.SCAN_LIST_DEFAULT - 1]; + + // Check if name is valid + bool nameValid = (name[0] != '\0' && name[0] != '\xff' && name[0] != ' '); + + // Format the string + if (nameValid) { + sprintf(str, "%.3s%s", name, gEeprom.SCAN_LIST_ENABLED ? "+" : ""); + end = gEeprom.SCAN_LIST_ENABLED ? 19 : 15; + } else { - sprintf(str, "%02d", gEeprom.SCAN_LIST_DEFAULT); - end = 11; + sprintf(str, "%02d%s", gEeprom.SCAN_LIST_DEFAULT, gEeprom.SCAN_LIST_ENABLED ? "+" : ""); + end = gEeprom.SCAN_LIST_ENABLED ? 15 : 11; } } From 8825eb9ac6dff75dbbdfda76a3477be8d59dd122 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Tue, 10 Feb 2026 01:46:31 +0100 Subject: [PATCH 05/47] Fix Aircopy because of named list --- App/app/aircopy.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/App/app/aircopy.c b/App/app/aircopy.c index f555484d..f5b42871 100644 --- a/App/app/aircopy.c +++ b/App/app/aircopy.c @@ -89,12 +89,13 @@ DECLARE_AIRCOPY_BANK(1) static const AIRCOPY_Segment_t AIRCOPY_Segments_Settings[] = { { 0xA000, 0xA170, AIRCOPY_WRITE_BYTES }, + { 0x880E, 0x886E, AIRCOPY_WRITE_BYTES }, }; static const AIRCOPY_TransferMap_t AIRCOPY_Map_Settings = { .segments = AIRCOPY_Segments_Settings, - .num_segments = 1, - .total_blocks = 6 + .num_segments = 2, + .total_blocks = 8 }; // Finally From c1e5f23d72d6bb673496e93ad4f115589df373f0 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Tue, 10 Feb 2026 04:12:59 +0100 Subject: [PATCH 06/47] Remove N/A --- App/ui/menu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/App/ui/menu.c b/App/ui/menu.c index adb86386..9da1bf11 100644 --- a/App/ui/menu.c +++ b/App/ui/menu.c @@ -974,9 +974,9 @@ void UI_DisplayMenu(void) // If first character is empty/invalid, display "N/A" if (name[0] == '\0' || name[0] == '\xff' || name[0] == ' ') - sprintf(String, "%02u - N/A", gSubMenuSelection); + sprintf(String, "%02u", gSubMenuSelection); else - sprintf(String, "%02u - %.3s", gSubMenuSelection, name); + sprintf(String, "%02u (%.3s)", gSubMenuSelection, name); } break; @@ -987,9 +987,9 @@ void UI_DisplayMenu(void) const char *name = gListName[gSubMenuSelection - 1]; // If first character is empty/invalid, display "N/A" if (name[0] == '\0' || name[0] == '\xff' || name[0] == ' ') - sprintf(String, "%02u - N/A", gSubMenuSelection); + sprintf(String, "%02u", gSubMenuSelection); else - sprintf(String, "%02u - %.3s", gSubMenuSelection, name); + sprintf(String, "%02u (%.3s)", gSubMenuSelection, name); } break; From 0ddbb0ad82c618147e30f978c35c046d1c33f48a Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Tue, 10 Feb 2026 05:24:17 +0100 Subject: [PATCH 07/47] Fix issue #40 --- App/app/main.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/App/app/main.c b/App/app/main.c index ecb5fe5d..0f5323d8 100644 --- a/App/app/main.c +++ b/App/app/main.c @@ -47,6 +47,12 @@ #include "ui/ui.h" #include +// Full VFO backup for restore on EXIT +static VFO_Info_t gVfoBackup; +static uint16_t gScreenChannelBackup = 0; +static uint16_t gFreqChannelBackup = 0; +static bool gHasVfoBackup = false; + static void toggle_chan_scanlist(void) { // toggle the selected channels scanlist setting @@ -505,6 +511,15 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) } const uint8_t Vfo = gEeprom.TX_VFO; + + // Save full VFO state BEFORE first digit + if (gInputBoxIndex == 0 && IS_FREQ_CHANNEL(gTxVfo->CHANNEL_SAVE)) { + memcpy(&gVfoBackup, gTxVfo, sizeof(VFO_Info_t)); + gScreenChannelBackup = gEeprom.ScreenChannel[Vfo]; + gFreqChannelBackup = gEeprom.FreqChannel[Vfo]; + gHasVfoBackup = true; + } + INPUTBOX_Append(Key); gKeyInputCountdown = key_input_timeout_500ms; @@ -684,6 +699,25 @@ static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld) return; gInputBox[--gInputBoxIndex] = 10; + // Restore full VFO state when back to 0 + if (gInputBoxIndex == 0 && gHasVfoBackup) { + const uint8_t Vfo = gEeprom.TX_VFO; + + // Restore indices + gEeprom.ScreenChannel[Vfo] = gScreenChannelBackup; + gEeprom.FreqChannel[Vfo] = gFreqChannelBackup; + + // Restore full VFO + memcpy(gTxVfo, &gVfoBackup, sizeof(VFO_Info_t)); + + // Save and apply + SETTINGS_SaveVfoIndices(); + RADIO_ConfigureSquelchAndOutputPower(gTxVfo); + RADIO_SetupRegisters(true); + + gHasVfoBackup = false; + } + gKeyInputCountdown = key_input_timeout_500ms; #ifdef ENABLE_VOICE @@ -713,6 +747,26 @@ static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld) if (bKeyHeld && bKeyPressed) { // exit key held down if (gInputBoxIndex > 0 || gDTMF_InputBox_Index > 0 || gDTMF_InputMode) { // cancel key input mode (channel/frequency entry) + + // Restore full VFO state on long press EXIT + if (gHasVfoBackup) { + const uint8_t Vfo = gEeprom.TX_VFO; + + // Restore indices + gEeprom.ScreenChannel[Vfo] = gScreenChannelBackup; + gEeprom.FreqChannel[Vfo] = gFreqChannelBackup; + + // Restore full VFO + memcpy(gTxVfo, &gVfoBackup, sizeof(VFO_Info_t)); + + // Save and apply + SETTINGS_SaveVfoIndices(); + RADIO_ConfigureSquelchAndOutputPower(gTxVfo); + RADIO_SetupRegisters(true); + + gHasVfoBackup = false; + } + gDTMF_InputMode = false; gDTMF_InputBox_Index = 0; memset(gDTMF_String, 0, sizeof(gDTMF_String)); @@ -1067,4 +1121,4 @@ void MAIN_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; break; } -} +} \ No newline at end of file From e515d53aaa448d9fdf788c4c502120fa7fe886be Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 11 Feb 2026 01:20:16 +0100 Subject: [PATCH 08/47] Improve GUI --- App/ui/helper.c | 12 ++++++------ App/ui/main.c | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/App/ui/helper.c b/App/ui/helper.c index f49492c0..5f2d46c7 100644 --- a/App/ui/helper.c +++ b/App/ui/helper.c @@ -130,17 +130,17 @@ void UI_PrintStringSmallNormalInverse(const char *pString, uint8_t Start, uint8_ if (End != 0 && x_end > End) x_end = End; - gFrameBuffer[Line][x_start - 3] ^= 0x3E; - gFrameBuffer[Line][x_start - 2] ^= 0x7F; - gFrameBuffer[Line][x_start - 1] ^= 0xFF; + gFrameBuffer[Line][x_start - 2] ^= 0x3E; + gFrameBuffer[Line][x_start - 1] ^= 0x7F; + //gFrameBuffer[Line][x_start - 1] ^= 0xFF; for (uint8_t x = x_start; x < x_end; x++) { gFrameBuffer[Line][x] ^= 0xFF; gFrameBuffer[Line - 1][x] ^= 0x80; } - gFrameBuffer[Line][x_end + 0] ^= 0xFF; - gFrameBuffer[Line][x_end + 1] ^= 0x7F; - gFrameBuffer[Line][x_end + 2] ^= 0x3E; + //gFrameBuffer[Line][x_end + 0] ^= 0xFF; + gFrameBuffer[Line][x_end + 0] ^= 0x7F; + gFrameBuffer[Line][x_end + 1] ^= 0x3E; } diff --git a/App/ui/main.c b/App/ui/main.c index 6156efe5..50d9eb10 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -821,7 +821,7 @@ void UI_DisplayMain(void) if (IS_MR_CHANNEL(gEeprom.ScreenChannel[vfo_num])) { // channel mode - const unsigned int x = 3; + const unsigned int x = 2; const bool inputting = gInputBoxIndex != 0 && gEeprom.TX_VFO == vfo_num; if (!inputting || gScanStateDir != SCAN_OFF) sprintf(String, "%04u", gEeprom.ScreenChannel[vfo_num] + 1); @@ -948,10 +948,10 @@ void UI_DisplayMain(void) displayStr = String; } - GUI_DisplaySmallest(displayStr, xDisplay, line == 0 ? 2 : 34, false, true); + GUI_DisplaySmallest(displayStr, xDisplay, line == 0 ? 1 : 33, false, true); for (uint8_t x = xStart; x < 128; x++) { - gFrameBuffer[line][x] ^= 0xFE; + gFrameBuffer[line][x] ^= 0x7F; } } @@ -962,10 +962,10 @@ void UI_DisplayMain(void) uint8_t xStart = 117; uint8_t xDisplay = 119; - GUI_DisplaySmallest(displayStr, xDisplay, line == 0 ? 2 : 34, false, true); + GUI_DisplaySmallest(displayStr, xDisplay, line == 0 ? 1 : 33, false, true); for (uint8_t x = xStart; x < 128; x++) { - gFrameBuffer[line][x] ^= 0xFE; + gFrameBuffer[line][x] ^= 0x7F; } } @@ -1552,11 +1552,18 @@ void UI_DisplayMain(void) if (isMainOnly() && !gDTMF_InputMode) { sprintf(String, "VFO %s", activeTxVFO ? "B" : "A"); + GUI_DisplaySmallest(String, 108, 50, false, true); + + for (uint8_t x = 107; x < 128; x++) { + gFrameBuffer[6][x] ^= 0xFE; + } + /* UI_PrintStringSmallBold(String, 92, 0, 6); for (uint8_t i = 92; i < 128; i++) { gFrameBuffer[6][i] ^= 0x7F; } + */ } //#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS //} From c1a6633302ca025edd495a8fe8c7031e4cb1b40e Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 11 Feb 2026 02:14:41 +0100 Subject: [PATCH 09/47] Shift VFO --- App/ui/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/App/ui/main.c b/App/ui/main.c index 50d9eb10..4c2f5aab 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -1552,9 +1552,9 @@ void UI_DisplayMain(void) if (isMainOnly() && !gDTMF_InputMode) { sprintf(String, "VFO %s", activeTxVFO ? "B" : "A"); - GUI_DisplaySmallest(String, 108, 50, false, true); + GUI_DisplaySmallest(String, 107, 50, false, true); - for (uint8_t x = 107; x < 128; x++) { + for (uint8_t x = 105; x < 128; x++) { gFrameBuffer[6][x] ^= 0xFE; } /* From fa7537d53934d32024be44b10ddd7a80c852c882 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 11 Feb 2026 02:52:51 +0100 Subject: [PATCH 10/47] Frequency Band refactoring --- App/ui/main.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/App/ui/main.c b/App/ui/main.c index 4c2f5aab..9d0e56d9 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -833,9 +833,11 @@ void UI_DisplayMain(void) { // frequency mode // show the frequency band number const unsigned int x = 2; - char * buf = gEeprom.VfoInfo[vfo_num].pRX->Frequency < _1GHz_in_KHz ? "" : "+"; - sprintf(String, "F%u%s", 1 + gEeprom.ScreenChannel[vfo_num] - FREQ_CHANNEL_FIRST, buf); - UI_PrintStringSmallNormal(String, x, 0, line + 1); + const uint8_t f = 1 + gEeprom.ScreenChannel[vfo_num] - FREQ_CHANNEL_FIRST; + const bool over1GHz = gEeprom.VfoInfo[vfo_num].pRX->Frequency >= _1GHz_in_KHz; + + sprintf(String, over1GHz ? "F%u+" : "F%u", f); + UI_PrintStringSmallNormalInverse(String, x, 0, line + 1); } #ifdef ENABLE_NOAA else From 5ccb6cd7ffa37e8618c424520baf0d5c1c4bd13f Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 11 Feb 2026 19:54:58 +0100 Subject: [PATCH 11/47] Fix 4 digits --- App/ui/helper.c | 4 ++-- App/ui/main.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/App/ui/helper.c b/App/ui/helper.c index 5f2d46c7..cbb0ee27 100644 --- a/App/ui/helper.c +++ b/App/ui/helper.c @@ -54,13 +54,13 @@ void UI_GenerateChannelStringEx(char *pString, const bool bShowPrefix, const uin if (bShowPrefix) { // BUG here? Prefixed NULLs are allowed - sprintf(pString, "CH-%03u", ChannelNumber + 1); + sprintf(pString, "CH-%04u", ChannelNumber + 1); } else if (ChannelNumber == MR_CHANNEL_LAST + 1) { strcpy(pString, "None"); } else if (ChannelNumber == 0xFFFF) { strcpy(pString, "NULL"); } else { - sprintf(pString, "%03u", ChannelNumber + 1); + sprintf(pString, "%04u", ChannelNumber + 1); } } diff --git a/App/ui/main.c b/App/ui/main.c index 9d0e56d9..93218ce5 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -1007,7 +1007,7 @@ void UI_DisplayMain(void) break; case MDF_CHANNEL: // show the channel number - sprintf(String, "CH-%03u", gEeprom.ScreenChannel[vfo_num] + 1); + sprintf(String, "CH-%04u", gEeprom.ScreenChannel[vfo_num] + 1); UI_PrintString(String, 36, 0, line, 8); break; @@ -1017,7 +1017,7 @@ void UI_DisplayMain(void) SETTINGS_FetchChannelName(String, gEeprom.ScreenChannel[vfo_num]); if (String[0] == 0) { // no channel name, show the channel number instead - sprintf(String, "CH-%03u", gEeprom.ScreenChannel[vfo_num] + 1); + sprintf(String, "CH-%04u", gEeprom.ScreenChannel[vfo_num] + 1); } if (gEeprom.CHANNEL_DISPLAY_MODE == MDF_NAME) { From 261aeb4fb793903690556206c73d73081e3df9ae Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 12 Feb 2026 00:28:03 +0100 Subject: [PATCH 12/47] Fix issue #153 --- App/settings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App/settings.c b/App/settings.c index 0216c906..5eea835f 100644 --- a/App/settings.c +++ b/App/settings.c @@ -1108,7 +1108,7 @@ void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep, }; // default attributes // 0x0D60 - PY25Q16_ReadBuffer(0x008000 + channel, &state, 1); + PY25Q16_ReadBuffer(0x008000 + (channel * 2), &state, 2); if (keep) { att.band = pVFO->Band; From ee1d68e7efaf6c0ebe3230f6f31ce0e943cafd52 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 12 Feb 2026 04:32:12 +0100 Subject: [PATCH 13/47] Extend Tiny GUI ? --- App/ui/main.c | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/App/ui/main.c b/App/ui/main.c index 93218ce5..4c222927 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -827,7 +827,24 @@ void UI_DisplayMain(void) sprintf(String, "%04u", gEeprom.ScreenChannel[vfo_num] + 1); else sprintf(String, "%.4s", INPUTBOX_GetAscii()); // show the input text - UI_PrintStringSmallNormalInverse(String, x, 0, line + 1); + + //if (gSetting_set_gui) { + UI_PrintStringSmallNormalInverse(String, x, 0, line + 1); + /* + } + else + { + GUI_DisplaySmallest(String, x + 1, line == 0 ? 9 : 41, false, true); + gFrameBuffer[line + 1][0] ^= 0x1C; + gFrameBuffer[line + 1][1] ^= 0x3E; + for (uint8_t i = 2; i < 21; i++) { + gFrameBuffer[line + 1][i] ^= 0x7F; + } + gFrameBuffer[line + 1][21] ^= 0x3E; + gFrameBuffer[line + 1][22] ^= 0x1C; + + } + */ } else if (IS_FREQ_CHANNEL(gEeprom.ScreenChannel[vfo_num])) { // frequency mode @@ -837,7 +854,27 @@ void UI_DisplayMain(void) const bool over1GHz = gEeprom.VfoInfo[vfo_num].pRX->Frequency >= _1GHz_in_KHz; sprintf(String, over1GHz ? "F%u+" : "F%u", f); - UI_PrintStringSmallNormalInverse(String, x, 0, line + 1); + //if (gSetting_set_gui) { + UI_PrintStringSmallNormalInverse(String, x, 0, line + 1); + /* + } + else + { + GUI_DisplaySmallest(String, x + 2, line == 0 ? 9 : 41, false, true); + uint8_t g = 13; + if(over1GHz) + g = 17; + + gFrameBuffer[line + 1][0] ^= 0x1C; + gFrameBuffer[line + 1][1] ^= 0x3E; + for (uint8_t i = 2; i < g; i++) { + gFrameBuffer[line + 1][i] ^= 0x7F; + } + gFrameBuffer[line + 1][g] ^= 0x3E; + gFrameBuffer[line + 1][g + 1] ^= 0x1C; + + } + */ } #ifdef ENABLE_NOAA else From 5b4b8cf2f6610d254682112dcb807765a5f60685 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Fri, 13 Feb 2026 02:04:55 +0100 Subject: [PATCH 14/47] Blink arrow on RX (issue #146) --- App/bitmaps.c | 11 +++++++++++ App/bitmaps.h | 1 + App/ui/main.c | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/App/bitmaps.c b/App/bitmaps.c index fe860021..517e1b75 100644 --- a/App/bitmaps.c +++ b/App/bitmaps.c @@ -199,6 +199,17 @@ const uint8_t BITMAP_VFO_NotDefault[7] = 0b00001000 }; +const uint8_t BITMAP_VFO_Empty[7] = +{ + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000 +}; + const uint8_t BITMAP_compand[6] = { 0b00000000, diff --git a/App/bitmaps.h b/App/bitmaps.h index be10a970..9ddc6b18 100644 --- a/App/bitmaps.h +++ b/App/bitmaps.h @@ -38,6 +38,7 @@ extern const uint8_t BITMAP_NotReady[7]; extern const uint8_t BITMAP_Antenna[5]; extern const uint8_t BITMAP_VFO_Default[7]; extern const uint8_t BITMAP_VFO_NotDefault[7]; +extern const uint8_t BITMAP_VFO_Empty[7]; extern const uint8_t BITMAP_VFO_Lock[7]; extern const uint8_t BITMAP_PowerUser[3]; extern const uint8_t BITMAP_compand[6]; diff --git a/App/ui/main.c b/App/ui/main.c index 4c222927..631ff34a 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -273,24 +273,23 @@ void DisplayRSSIBar(const bool now) //sprintf(String, "%d", RxBlink); //UI_PrintStringSmallBold(String, 80, 0, RxLine); - /* if(RxLine >= 0 && center_line != CENTER_LINE_IN_USE) { - if (RxBlink == 0 || RxBlink == 1) { - UI_PrintStringSmallBold("RX", 8, 0, RxLine); - //GUI_DisplaySmallest("RX", 10, (RxLine * 8) + 1, false, true); + static bool clean = false; + uint8_t *p_line0 = gFrameBuffer[RxLine + 0]; - if (RxBlink == 1) RxBlink = 2; + clean = !clean; + + if(clean) { + for(uint8_t i = 0; i < sizeof(BITMAP_VFO_Default); i++) + p_line0[i] = (p_line0[i] & 0x80) | BITMAP_VFO_Default[i]; } else { - for (uint8_t i = 8; i < 24; i++) - { - gFrameBuffer[RxLine][i] = 0x00; - } - RxBlink = 1; + for(uint8_t i = 0; i < sizeof(BITMAP_VFO_Empty); i++) + p_line0[i] = (p_line0[i] & 0x80) | BITMAP_VFO_Empty[i]; } + ST7565_BlitLine(RxLine); } - */ #else const unsigned int line = 3; #endif @@ -809,8 +808,9 @@ void UI_DisplayMain(void) { if(RxOnVfofrequency == frequency && !isMainOnly()) { - UI_PrintStringSmallNormal(">>", 8, 0, line); + //UI_PrintStringSmallNormal(">>", 8, 0, line); //memcpy(p_line0 + 14, BITMAP_VFO_Default, sizeof(BITMAP_VFO_Default)); + GUI_DisplaySmallest(">>", 8, RxLine == 0 ? 1 : 33, false, true); } if(RxBlinkLed == 1) From 3f8ddb05025a46463ca0cec038385d4c2d096071 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Fri, 13 Feb 2026 18:21:34 +0100 Subject: [PATCH 15/47] Fix version --- tools/unbrick_k5_v1/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/unbrick_k5_v1/README.md b/tools/unbrick_k5_v1/README.md index 37f49770..338bf876 100644 --- a/tools/unbrick_k5_v1/README.md +++ b/tools/unbrick_k5_v1/README.md @@ -2,11 +2,11 @@ pcbite -Restoring a UV-K5 V1 accidentally flashed with the F4HWN 4.3 Fusion 🔥 Edition firmware, using OpenOCD and a ST-LINK programmer. +Restoring a UV-K5 V1 accidentally flashed with the F4HWN Fusion 🔥 Edition firmware, using OpenOCD and a ST-LINK programmer. ## Introduction -Some users have accidentally flashed a UV-K5 V1 with the F4HWN 4.3 Fusion 🔥 Edition firmware. This edition **is only** compatible with: +Some users have accidentally flashed a UV-K5 V1 with the F4HWN Fusion 🔥 Edition firmware. This edition **is only** compatible with: - UV-K5 V3 - UV-K1 From aef113eacfb7f30d0de9fe59563d9891964d35bb Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 14 Feb 2026 18:50:14 +0100 Subject: [PATCH 16/47] Remove BITMAP_Antenna --- App/bitmaps.c | 9 --------- App/bitmaps.h | 1 - App/ui/main.c | 18 +++++++++++++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/App/bitmaps.c b/App/bitmaps.c index 517e1b75..3eca0c51 100644 --- a/App/bitmaps.c +++ b/App/bitmaps.c @@ -157,15 +157,6 @@ const uint8_t BITMAP_USB_C[9] = }; #endif -const uint8_t BITMAP_Antenna[5] = -{ - 0b00000011, - 0b00000101, - 0b01111111, - 0b00000101, - 0b00000011 -}; - const uint8_t BITMAP_VFO_Lock[7] = { 0b00111100, diff --git a/App/bitmaps.h b/App/bitmaps.h index 9ddc6b18..697d4843 100644 --- a/App/bitmaps.h +++ b/App/bitmaps.h @@ -35,7 +35,6 @@ extern const uint8_t BITMAP_NotReady[7]; extern const uint8_t gFontVox[2][6]; #endif -extern const uint8_t BITMAP_Antenna[5]; extern const uint8_t BITMAP_VFO_Default[7]; extern const uint8_t BITMAP_VFO_NotDefault[7]; extern const uint8_t BITMAP_VFO_Empty[7]; diff --git a/App/ui/main.c b/App/ui/main.c index 631ff34a..a23ff2bf 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -87,11 +87,19 @@ static void DrawSmallAntennaAndBars(uint8_t *p, unsigned int level) if(level>6) level = 6; - memcpy(p, BITMAP_Antenna, ARRAY_SIZE(BITMAP_Antenna)); - - for(uint8_t i = 1; i <= level; i++) { - char bar = (0xff << (6-i)) & 0x7F; - memset(p + 2 + i*3, bar, 2); + if(gSetting_set_gui) + { + for(uint8_t i = 0; i <= level; i++) { + char bar = (0xff << (6-i)) & 0x7F; + memset(p + 2 + i*3, bar, 2); + } + } + else + { + for(uint8_t i = 0; i <= level; i++) { + char bar = 0b00111110; + memset(p + 2 + i*3, bar, 2); + } } } #if defined ENABLE_AUDIO_BAR || defined ENABLE_RSSI_BAR From f0cca71673dedaa881548aec81c947fe1bef9107 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 14 Feb 2026 18:54:49 +0100 Subject: [PATCH 17/47] Rename function --- App/ui/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/App/ui/main.c b/App/ui/main.c index a23ff2bf..b69d5ba8 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -82,7 +82,7 @@ const char *VfoStateStr[] = { // *************************************************************************** -static void DrawSmallAntennaAndBars(uint8_t *p, unsigned int level) +static void DrawSmallPowerBars(uint8_t *p, unsigned int level) { if(level>6) level = 6; @@ -426,7 +426,7 @@ void DisplayRSSIBar(const bool now) uint8_t *pLine = (gEeprom.RX_VFO == 0)? gFrameBuffer[2] : gFrameBuffer[6]; if (now) memset(pLine, 0, 23); - DrawSmallAntennaAndBars(pLine, Level); + DrawSmallPowerBars(pLine, Level); if (now) ST7565_BlitFullScreen(); #endif @@ -1190,7 +1190,7 @@ void UI_DisplayMain(void) #endif } if(Level >= 0) - DrawSmallAntennaAndBars(p_line1 + LCD_WIDTH, Level); + DrawSmallPowerBars(p_line1 + LCD_WIDTH, Level); } // ************ From 6b4e575d1f8d1beaeefab4e68b2d37c0d63b88f2 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 14 Feb 2026 18:59:20 +0100 Subject: [PATCH 18/47] Remove stars... --- App/ui/main.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/App/ui/main.c b/App/ui/main.c index b69d5ba8..026cd26f 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -80,7 +80,7 @@ const char *VfoStateStr[] = { [VFO_STATE_VOLTAGE_HIGH]="VOLT HIGH" }; -// *************************************************************************** +// ---------------------------------------- static void DrawSmallPowerBars(uint8_t *p, unsigned int level) { @@ -535,7 +535,7 @@ void UI_MAIN_TimeSlice500ms(void) } } -// *************************************************************************** +// ---------------------------------------- void UI_DisplayMain(void) { @@ -899,7 +899,7 @@ void UI_DisplayMain(void) } #endif - // ************ + // ---------------------------------------- enum VfoState_t state = VfoState[vfo_num]; @@ -1151,7 +1151,7 @@ void UI_DisplayMain(void) } } - // ************ + // ---------------------------------------- { // show the TX/RX level int8_t Level = -1; @@ -1193,7 +1193,7 @@ void UI_DisplayMain(void) DrawSmallPowerBars(p_line1 + LCD_WIDTH, Level); } - // ************ + // ---------------------------------------- String[0] = '\0'; const VFO_Info_t *vfoInfo = &gEeprom.VfoInfo[vfo_num]; @@ -1619,5 +1619,3 @@ void UI_DisplayMain(void) ST7565_BlitFullScreen(); } - -// *************************************************************************** From e51813e1bb9e29ede026806922f2e164e574da2e Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 14 Feb 2026 23:59:44 +0100 Subject: [PATCH 19/47] Fix Reset All (issue #156) --- App/settings.c | 1 + 1 file changed, 1 insertion(+) diff --git a/App/settings.c b/App/settings.c index 5eea835f..b57d47ce 100644 --- a/App/settings.c +++ b/App/settings.c @@ -604,6 +604,7 @@ void SETTINGS_FactoryReset(bool bIsAll) { PY25Q16_SectorErase(0x000000); PY25Q16_SectorErase(0x001000); + PY25Q16_SectorErase(0x002000); PY25Q16_SectorErase(0x003000); PY25Q16_SectorErase(0x004000); PY25Q16_SectorErase(0x005000); From 706283c16b9520d2c4f5fbf4e6f9925e772ffd88 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sun, 15 Feb 2026 03:38:01 +0100 Subject: [PATCH 20/47] Fix blink arrow and screenshot --- App/driver/st7565.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/App/driver/st7565.c b/App/driver/st7565.c index 71ea771d..53ae3142 100644 --- a/App/driver/st7565.c +++ b/App/driver/st7565.c @@ -24,6 +24,7 @@ #include "driver/st7565.h" #include "driver/system.h" #include "misc.h" +#include "screenshot.h" #define SPIx SPI1 @@ -163,6 +164,7 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u void ST7565_BlitLine(unsigned line) { ST7565_BlitScreen(line + 1); + getScreenShot(true); // Force immediate capture } void ST7565_BlitStatusLine(void) From 9081b017739c09c057a5d402563cc35e503a4500 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sun, 15 Feb 2026 04:19:57 +0100 Subject: [PATCH 21/47] Add bidirectional list navigation with UP/DOWN keys (issue #145) --- App/app/action.c | 2 +- App/app/chFrScanner.c | 2 +- App/app/main.c | 27 ++++++++++++++++++--------- App/radio.c | 22 ++++++++++++---------- App/radio.h | 2 +- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/App/app/action.c b/App/app/action.c index 1792d474..410ee518 100644 --- a/App/app/action.c +++ b/App/app/action.c @@ -236,7 +236,7 @@ void ACTION_Scan(bool bRestart) } // channel mode. Keep scanning but toggle between scan lists - RADIO_NextValidList(); + RADIO_NextValidList(1); #ifdef ENABLE_FEAT_F4HWN_RESUME_STATE SETTINGS_WriteCurrentState(); diff --git a/App/app/chFrScanner.c b/App/app/chFrScanner.c index 339c389f..f6c6d479 100644 --- a/App/app/chFrScanner.c +++ b/App/app/chFrScanner.c @@ -55,7 +55,7 @@ void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_directi { if(!RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT)) { - RADIO_NextValidList(); + RADIO_NextValidList(1); } // channel mode diff --git a/App/app/main.c b/App/app/main.c index 0f5323d8..716436e6 100644 --- a/App/app/main.c +++ b/App/app/main.c @@ -291,16 +291,25 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) #ifdef ENABLE_FEAT_F4HWN // Set Squelch F + UP or Down and Step F + SIDE1 or F + SIDE2 case KEY_UP: - gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL < 9) ? gEeprom.SQUELCH_LEVEL + 1: 9; - gVfoConfigureMode = VFO_CONFIGURE; - gWasFKeyPressed = false; - break; case KEY_DOWN: - gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL > 0) ? gEeprom.SQUELCH_LEVEL - 1: 0; - gVfoConfigureMode = VFO_CONFIGURE; - gWasFKeyPressed = false; - break; + { + int8_t direction = (Key == KEY_UP) ? 1 : -1; + if (gScanStateDir != SCAN_OFF) { + RADIO_NextValidList(direction); + } else { + // Adjust squelch: UP increments, DOWN decrements + if (direction > 0) { + gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL < 9) ? gEeprom.SQUELCH_LEVEL + 1 : 9; + } else { + gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL > 0) ? gEeprom.SQUELCH_LEVEL - 1 : 0; + } + gVfoConfigureMode = VFO_CONFIGURE; + } + + gWasFKeyPressed = false; + } + break; case KEY_SIDE1: uint8_t a = FREQUENCY_GetSortedIdxFromStepIdx(gTxVfo->STEP_SETTING); if (a < STEP_N_ELEM - 1) @@ -496,7 +505,7 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) /* Requested scan list is empty or invalid: jump to the next valid scan list */ gEeprom.SCAN_LIST_DEFAULT = value; - RADIO_NextValidList(); + RADIO_NextValidList(1); } #ifdef ENABLE_FEAT_F4HWN_RESUME_STATE diff --git a/App/radio.c b/App/radio.c index 5ef924ad..008fab87 100644 --- a/App/radio.c +++ b/App/radio.c @@ -102,28 +102,30 @@ bool RADIO_CheckValidList(uint8_t scanList) return false; } -void RADIO_NextValidList(void) +void RADIO_NextValidList(int8_t direction) { uint8_t startList = gEeprom.SCAN_LIST_DEFAULT; uint8_t attempts = 0; - const uint8_t MAX_LISTS = MR_CHANNELS_LIST + 2; // 1-25, includes ALL + const uint8_t MAX_VALUE = MR_CHANNELS_LIST + 1; // 25 (1-24 lists + ALL) do { - // Move to next scan list, wrapping around from 25 to 1 - gEeprom.SCAN_LIST_DEFAULT = ((gEeprom.SCAN_LIST_DEFAULT + 1) % MAX_LISTS) ?: 1; + 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++; - // Check if current list has valid channels if (RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT)) return; - // Stop if we've cycled through all lists or made too many attempts - } while (gEeprom.SCAN_LIST_DEFAULT != startList && attempts < MAX_LISTS); + } while (gEeprom.SCAN_LIST_DEFAULT != startList && attempts < MAX_VALUE); - // Safety fallback: if no valid list found, switch to ALL mode - // This prevents infinite loops and ensures scanning can continue + // Safety fallback: switch to ALL mode if (!RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT)) { - gEeprom.SCAN_LIST_DEFAULT = MR_CHANNELS_LIST + 1; // ALL + gEeprom.SCAN_LIST_DEFAULT = MAX_VALUE; // ALL (25) } } diff --git a/App/radio.h b/App/radio.h index c93044b0..ee2c0efd 100644 --- a/App/radio.h +++ b/App/radio.h @@ -149,7 +149,7 @@ extern DCS_CodeType_t gCurrentCodeType; extern VfoState_t VfoState[2]; bool RADIO_CheckValidList(uint8_t scanList); -void RADIO_NextValidList(void); +void RADIO_NextValidList(int8_t direction); bool RADIO_CheckValidChannel(uint16_t channel, bool checkScanList, uint8_t scanList); uint16_t RADIO_FindNextChannel(uint16_t ChNum, int8_t Direction, bool bCheckScanList, uint8_t RadioNum); void RADIO_InitInfo(VFO_Info_t *pInfo, const uint16_t ChannelSave, const uint32_t Frequency); From 9ef3379ccf539c389ed30bd7fc7cbfb012c9ab5b Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Mon, 16 Feb 2026 23:08:39 +0100 Subject: [PATCH 22/47] Fix -93dBm on VHF --- App/ui/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/App/ui/main.c b/App/ui/main.c index 026cd26f..47698997 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -62,8 +62,8 @@ center_line_t center_line = CENTER_LINE_NONE; const int8_t dBmCorrTable[7] = { -15, // band 1 - -25, // band 2 - -20, // band 3 + -12, // band 2 + -10, // band 3 -4, // band 4 -7, // band 5 -6, // band 6 From f680b3af361b35151de521e581591f484a5095bb Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Mon, 16 Feb 2026 23:16:35 +0100 Subject: [PATCH 23/47] Fix -93dBm on AIR --- App/ui/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/App/ui/main.c b/App/ui/main.c index 47698997..4c5fd9e1 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -62,7 +62,7 @@ center_line_t center_line = CENTER_LINE_NONE; const int8_t dBmCorrTable[7] = { -15, // band 1 - -12, // band 2 + -16, // band 2 -10, // band 3 -4, // band 4 -7, // band 5 From ed6b98ce46e2d52ba304459b59986317229cbf32 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Tue, 17 Feb 2026 03:18:26 +0100 Subject: [PATCH 24/47] Add strong signal correction --- App/ui/main.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/App/ui/main.c b/App/ui/main.c index 4c5fd9e1..9a8925b2 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -340,6 +340,23 @@ void DisplayRSSIBar(const bool now) rssi_dBm = -rssi_dBm; + // VHF band 2 & 3 strong signal correction + if((gRxVfo->Band == 1 || gRxVfo->Band == 2) && rssi_dBm < 93) { // Band 2 & 3, signal > S9 + if(rssi_dBm <= 63) { + rssi_dBm -= 4; + } + else if(rssi_dBm <= 73) { + rssi_dBm -= 3; + } + else if(rssi_dBm <= 85) { + rssi_dBm -= 1; + } + } + // UHF band 6 strong signal correction + else if(gRxVfo->Band == 5 && rssi_dBm < 93) { // Band 6, signal > S9) + rssi_dBm -= 2; + } + if(rssi_dBm > 141) rssi_dBm = 141; if(rssi_dBm < 53) rssi_dBm = 53; From 1b170b9ff9d30c32d8639858a35c727316c7f468 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 19 Feb 2026 02:06:20 +0100 Subject: [PATCH 25/47] Improve UI --- App/bitmaps.c | 26 ++++++++++++++++++++++++++ App/ui/helper.c | 4 ++-- App/ui/main.c | 17 +++++++++++------ App/ui/status.c | 13 ++++++++----- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/App/bitmaps.c b/App/bitmaps.c index 3eca0c51..817f5017 100644 --- a/App/bitmaps.c +++ b/App/bitmaps.c @@ -168,6 +168,7 @@ const uint8_t BITMAP_VFO_Lock[7] = 0b00111100, }; +/* const uint8_t BITMAP_VFO_Default[7] = { 0b01111111, @@ -189,6 +190,31 @@ const uint8_t BITMAP_VFO_NotDefault[7] = 0b00010100, 0b00001000 }; +*/ + +// Compact arrow +const uint8_t BITMAP_VFO_Default[7] = +{ + 0b00111110, + 0b00111110, + 0b00011100, + 0b00011100, + 0b00001000, + 0b00001000, + 0b00000000 +}; + +// Compact empty arrow +const uint8_t BITMAP_VFO_NotDefault[7] = +{ + 0b00100010, + 0b00100010, + 0b00010100, + 0b00010100, + 0b00001000, + 0b00001000, + 0b00000000 +}; const uint8_t BITMAP_VFO_Empty[7] = { diff --git a/App/ui/helper.c b/App/ui/helper.c index cbb0ee27..ca1363a7 100644 --- a/App/ui/helper.c +++ b/App/ui/helper.c @@ -130,7 +130,7 @@ void UI_PrintStringSmallNormalInverse(const char *pString, uint8_t Start, uint8_ if (End != 0 && x_end > End) x_end = End; - gFrameBuffer[Line][x_start - 2] ^= 0x3E; + //gFrameBuffer[Line][x_start - 2] ^= 0x3E; gFrameBuffer[Line][x_start - 1] ^= 0x7F; //gFrameBuffer[Line][x_start - 1] ^= 0xFF; for (uint8_t x = x_start; x < x_end; x++) @@ -140,7 +140,7 @@ void UI_PrintStringSmallNormalInverse(const char *pString, uint8_t Start, uint8_ } //gFrameBuffer[Line][x_end + 0] ^= 0xFF; gFrameBuffer[Line][x_end + 0] ^= 0x7F; - gFrameBuffer[Line][x_end + 1] ^= 0x3E; + //gFrameBuffer[Line][x_end + 1] ^= 0x3E; } diff --git a/App/ui/main.c b/App/ui/main.c index 9a8925b2..ede9ed5b 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -846,7 +846,7 @@ void UI_DisplayMain(void) if (IS_MR_CHANNEL(gEeprom.ScreenChannel[vfo_num])) { // channel mode - const unsigned int x = 2; + const unsigned int x = 1; const bool inputting = gInputBoxIndex != 0 && gEeprom.TX_VFO == vfo_num; if (!inputting || gScanStateDir != SCAN_OFF) sprintf(String, "%04u", gEeprom.ScreenChannel[vfo_num] + 1); @@ -1014,9 +1014,11 @@ void UI_DisplayMain(void) GUI_DisplaySmallest(displayStr, xDisplay, line == 0 ? 1 : 33, false, true); - for (uint8_t x = xStart; x < 128; x++) { + gFrameBuffer[line][xStart] ^= 0x3E; + for (uint8_t x = xStart + 1; x < 127; x++) { gFrameBuffer[line][x] ^= 0x7F; } + gFrameBuffer[line][127] ^= 0x3E; } else @@ -1083,15 +1085,15 @@ void UI_DisplayMain(void) } if (gEeprom.CHANNEL_DISPLAY_MODE == MDF_NAME) { - String[9] = 0; - UI_PrintString(String, 36, 0, line, 8); + String[10] = 0; + UI_PrintString(String, 33, 0, line, 8); } else { #ifdef ENABLE_FEAT_F4HWN if (isMainOnly()) { String[10] = 0; - UI_PrintString(String, 36, 0, line, 8); + UI_PrintString(String, 33, 0, line, 8); } else { @@ -1618,9 +1620,12 @@ void UI_DisplayMain(void) sprintf(String, "VFO %s", activeTxVFO ? "B" : "A"); GUI_DisplaySmallest(String, 107, 50, false, true); - for (uint8_t x = 105; x < 128; x++) { + gFrameBuffer[6][105] ^= 0x7C; + for (uint8_t x = 106; x < 127; x++) { gFrameBuffer[6][x] ^= 0xFE; } + gFrameBuffer[6][127] ^= 0x7C; + /* UI_PrintStringSmallBold(String, 92, 0, 6); for (uint8_t i = 92; i < 128; i++) diff --git a/App/ui/status.c b/App/ui/status.c index 4db85745..1d9b9829 100644 --- a/App/ui/status.c +++ b/App/ui/status.c @@ -101,12 +101,12 @@ void UI_DisplayStatus() { if(gEeprom.SCAN_LIST_ENABLED==1) { sprintf(str, "%s+", "ALL"); - end = 19; + end = 18; } else { sprintf(str, "%s", "ALL"); - end = 15; + end = 14; } } else @@ -119,19 +119,22 @@ void UI_DisplayStatus() // Format the string if (nameValid) { sprintf(str, "%.3s%s", name, gEeprom.SCAN_LIST_ENABLED ? "+" : ""); - end = gEeprom.SCAN_LIST_ENABLED ? 19 : 15; + end = gEeprom.SCAN_LIST_ENABLED ? 18 : 14; } else { sprintf(str, "%02d%s", gEeprom.SCAN_LIST_DEFAULT, gEeprom.SCAN_LIST_ENABLED ? "+" : ""); - end = gEeprom.SCAN_LIST_ENABLED ? 15 : 11; + end = gEeprom.SCAN_LIST_ENABLED ? 14 : 10; } } GUI_DisplaySmallest(str, 2, 1, true, true); - for (uint8_t x = 0; x < end; x++) + + gStatusLine[0] ^= 0x3E; + for (uint8_t x = 1; x < end; x++) { gStatusLine[x] ^= 0x7F; } + gStatusLine[end] ^= 0x3E; } else { // frequency mode memcpy(line + x + 1, gFontS, sizeof(gFontS)); From 30aa3f13a0ea4687da5a1955609be69f8e78626a Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 19 Feb 2026 02:46:53 +0100 Subject: [PATCH 26/47] Clean code --- App/ui/helper.c | 24 ------------------------ App/ui/helper.h | 1 - 2 files changed, 25 deletions(-) diff --git a/App/ui/helper.c b/App/ui/helper.c index ca1363a7..252a7c15 100644 --- a/App/ui/helper.c +++ b/App/ui/helper.c @@ -157,30 +157,6 @@ void UI_PrintStringSmallBold(const char *pString, uint8_t Start, uint8_t End, ui UI_PrintStringSmall(pString, Start, End, Line, char_width, font); } -void UI_PrintStringSmallBoldInverse(const char *pString, uint8_t Start, uint8_t End, uint8_t Line) -{ - // First draw the string normally - UI_PrintStringSmallBold(pString, Start, End, Line); - - // Now invert the framebuffer bits for the rendered area - uint8_t len = strlen(pString); - uint8_t char_width = 7; // small font is typically 6px wide - - uint8_t x_start = Start; - uint8_t x_end = Start + (len * char_width); - - if (End != 0 && x_end > End) - x_end = End; - - gFrameBuffer[Line][x_start] ^= 0x7F; - for (uint8_t x = x_start + 1; x < x_end; x++) - { - gFrameBuffer[Line][x] ^= 0x41; - } - gFrameBuffer[Line][x_end + 1] ^= 0x7F; -} - - void UI_PrintStringSmallBufferNormal(const char *pString, uint8_t * buffer) { UI_PrintStringBuffer(pString, buffer, ARRAY_SIZE(gFontSmall[0]), (uint8_t *)gFontSmall); diff --git a/App/ui/helper.h b/App/ui/helper.h index 47114a2f..302d375c 100644 --- a/App/ui/helper.h +++ b/App/ui/helper.h @@ -26,7 +26,6 @@ void UI_PrintString(const char *pString, uint8_t Start, uint8_t End, uint8_t Lin void UI_PrintStringSmallNormal(const char *pString, uint8_t Start, uint8_t End, uint8_t Line); void UI_PrintStringSmallNormalInverse(const char *pString, uint8_t Start, uint8_t End, uint8_t Line); void UI_PrintStringSmallBold(const char *pString, uint8_t Start, uint8_t End, uint8_t Line); -void UI_PrintStringSmallBoldInverse(const char *pString, uint8_t Start, uint8_t End, uint8_t Line); void UI_PrintStringSmallBufferNormal(const char *pString, uint8_t *buffer); void UI_PrintStringSmallBufferBold(const char *pString, uint8_t * buffer); void UI_DisplayFrequency(const char *string, uint8_t X, uint8_t Y, bool center); From 0234900bafe2529e5b820c33661af7c5c2037af6 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 19 Feb 2026 05:15:20 +0100 Subject: [PATCH 27/47] Add ENABLE_FEAT_F4HWN_MEM --- App/CMakeLists.txt | 1 + App/font.c | 2 +- App/ui/welcome.c | 143 ++++++++++++++++++++++++--------------------- CMakePresets.json | 2 + Core/py32f071xb.ld | 9 +++ 5 files changed, 91 insertions(+), 66 deletions(-) diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index 3985dcdf..697fbeda 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -201,6 +201,7 @@ enable_feature(ENABLE_FEAT_F4HWN_PMR) enable_feature(ENABLE_FEAT_F4HWN_GMRS_FRS_MURS) enable_feature(ENABLE_FEAT_F4HWN_CA) enable_feature(ENABLE_FEAT_F4HWN_DEBUG) +enable_feature(ENABLE_FEAT_F4HWN_MEM) # ---- DEBUGGING ---- diff --git a/App/font.c b/App/font.c index 4b2732f7..cbc66b59 100644 --- a/App/font.c +++ b/App/font.c @@ -491,7 +491,7 @@ const uint8_t gFontSmall[95-1][6] = {0x03, 0x00, 0x03}, // 34 - quotedbl {0x1f, 0x0a, 0x1f}, // 35 - numbersign {0x0a, 0x1f, 0x05}, // 36 - dollar - {0x09, 0x04, 0x12}, // 37 - percent + {0x19, 0x04, 0x13}, // 37 - percent {0x0f, 0x17, 0x1c}, // 38 - ampersand {0x00, 0x03, 0x00}, // 39 - quotesingle {0x00, 0x0e, 0x11}, // 40 - parenleft diff --git a/App/ui/welcome.c b/App/ui/welcome.c index 757d8f31..be34e823 100644 --- a/App/ui/welcome.c +++ b/App/ui/welcome.c @@ -15,6 +15,7 @@ */ #include +#include #include "driver/py25q16.h" #include "driver/st7565.h" @@ -32,6 +33,63 @@ #include "screenshot.h" #endif +#ifdef ENABLE_FEAT_F4HWN_MEM +// Linker symbols (provided by the linker script) +extern uint8_t _sdata; // Start of .data in RAM +extern uint8_t _edata; // End of .data in RAM +extern uint8_t _sbss; // Start of .bss in RAM +extern uint8_t _ebss; // End of .bss in RAM + +// _eflash_used must be defined in the linker script immediately after the last +// section with a FLASH load address (after .noncacheable). Example: +// +// .noncacheable : { +// ... +// } > RAM AT> FLASH +// _eflash_used = LOADADDR(.noncacheable) + SIZEOF(.noncacheable); +// +// This gives the exact byte count that the linker reports as FLASH used. +extern uint8_t _eflash_used; + +// Absolute symbols: their *address* IS the numeric size value (ARM/CMSIS convention). +// RAM = .data + gap + .bss + heap_reserve + stack_reserve +extern uint8_t _Min_Heap_Size; +extern uint8_t _Min_Stack_Size; + +// Region sizes (must match your linker MEMORY regions) +#define RAM_SIZE_BYTES (16u * 1024u) +#define FLASH_SIZE_BYTES (118u * 1024u) + +// Base address of FLASH — must match ORIGIN(FLASH) in your linker script +#define FLASH_BASE (0x08002800u) + +static inline uint32_t span(const void* a, const void* b) +{ + return (uint32_t)((uintptr_t)b - (uintptr_t)a); +} + +static void build_usage(uint32_t* ram_used, uint32_t* flash_used) +{ + // RAM: span from start of .data to end of .bss covers .data + alignment gap + .bss. + // Then add heap and stack reservations (absolute linker symbols: address = size). + // Proof: (0x20002A60 - 0x20000000) + 0x200 + 0x400 = 10848 + 512 + 1024 = 12384 B ✓ + const uint32_t heap_size = (uint32_t)(uintptr_t)&_Min_Heap_Size; + const uint32_t stack_size = (uint32_t)(uintptr_t)&_Min_Stack_Size; + *ram_used = span(&_sdata, &_ebss) + heap_size + stack_size; + + // FLASH: _eflash_used is placed by the linker script right after the last + // section copied to FLASH (.data LMA + .noncacheable LMA). + // Note: _etext is NOT usable here because this linker script places .rodata + // sections AFTER _etext, making it an unreliable end-of-flash marker. + *flash_used = span((void*)FLASH_BASE, &_eflash_used); +} + +static inline uint16_t pct_x100(uint32_t used, uint32_t total) +{ + return (uint16_t)((used * 10000u) / total); // 7559 => 75.59% +} +#endif + void UI_DisplayReleaseKeys(void) { memset(gStatusLine, 0, sizeof(gStatusLine)); @@ -52,7 +110,7 @@ void UI_DisplayWelcome(void) char WelcomeString0[16]; char WelcomeString1[16]; char WelcomeString2[16]; - char WelcomeString3[20]; + char WelcomeString3[32]; memset(gStatusLine, 0, sizeof(gStatusLine)); @@ -132,72 +190,27 @@ void UI_DisplayWelcome(void) gFrameBuffer[4][i] ^= 0xFF; } + #ifdef ENABLE_FEAT_F4HWN_MEM + uint32_t ram_used = 0; + uint32_t flash_used = 0; + build_usage(&ram_used, &flash_used); + + const uint16_t ram_pct = pct_x100(ram_used, RAM_SIZE_BYTES); + const uint16_t flash_pct = pct_x100(flash_used, FLASH_SIZE_BYTES); + + // No floats: 7559 => 75.59% + sprintf(WelcomeString3, + "FLASH %u.%02u %% - SRAM %u.%02u %%", + (unsigned)(flash_pct / 100), (unsigned)(flash_pct % 100), + (unsigned)(ram_pct / 100), (unsigned)(ram_pct % 100)); + + GUI_DisplaySmallest(WelcomeString3, 5, 1, true, true); + ST7565_BlitStatusLine(); + #endif + sprintf(WelcomeString3, "%s Edition", Edition); UI_PrintStringSmallNormal(WelcomeString3, 0, 127, 6); - /* - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - #if ENABLE_FEAT_F4HWN_RESCUE_OPS > 1 - UI_PrintStringSmallNormal(Edition, 18, 0, 6); - if(gEeprom.MENU_LOCK == true) { - memcpy(gFrameBuffer[6] + 103, BITMAP_Ready, sizeof(BITMAP_Ready)); - } - else - { - memcpy(gFrameBuffer[6] + 103, BITMAP_NotReady, sizeof(BITMAP_NotReady)); - } - #else - UI_PrintStringSmallNormal(Edition, 18, 0, 5); - memcpy(gFrameBuffer[5] + 103, BITMAP_Ready, sizeof(BITMAP_Ready)); - - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - UI_PrintStringSmallNormal("RescueOps", 18, 0, 6); - if(gEeprom.MENU_LOCK == true) { - memcpy(gFrameBuffer[6] + 103, BITMAP_Ready, sizeof(BITMAP_Ready)); - } - else - { - memcpy(gFrameBuffer[6] + 103, BITMAP_NotReady, sizeof(BITMAP_NotReady)); - } - #endif - #endif - #else - UI_PrintStringSmallNormal(Edition, 18, 0, 6); - memcpy(gFrameBuffer[6] + 103, BITMAP_Ready, sizeof(BITMAP_Ready)); - #endif - */ - - /* - #ifdef ENABLE_SPECTRUM - #ifdef ENABLE_FMRADIO - UI_PrintStringSmallNormal(Based, 0, 127, 5); - UI_PrintStringSmallNormal(Credits, 0, 127, 6); - #else - UI_PrintStringSmallNormal("Bandscope ", 0, 127, 5); - memcpy(gFrameBuffer[5] + 95, BITMAP_Ready, sizeof(BITMAP_Ready)); - - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - UI_PrintStringSmallNormal("RescueOps ", 0, 127, 6); - if(gEeprom.MENU_LOCK == true) { - memcpy(gFrameBuffer[6] + 95, BITMAP_Ready, sizeof(BITMAP_Ready)); - } - #else - UI_PrintStringSmallNormal("Broadcast ", 0, 127, 6); - #endif - #endif - #else - #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS - UI_PrintStringSmallNormal("RescueOps ", 0, 127, 5); - if(gEeprom.MENU_LOCK == true) { - memcpy(gFrameBuffer[5] + 95, BITMAP_Ready, sizeof(BITMAP_Ready)); - } - #else - UI_PrintStringSmallNormal("Bandscope ", 0, 127, 5); - #endif - UI_PrintStringSmallNormal("Broadcast ", 0, 127, 6); - memcpy(gFrameBuffer[6] + 95, BITMAP_Ready, sizeof(BITMAP_Ready)); - #endif - */ #else UI_PrintStringSmallNormal(Version, 0, 127, 6); #endif @@ -209,4 +222,4 @@ void UI_DisplayWelcome(void) getScreenShot(true); #endif } -} +} \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json index 74f1e6df..01130203 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -65,6 +65,7 @@ "ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": false, "ENABLE_FEAT_F4HWN_CA": true, "ENABLE_FEAT_F4HWN_DEBUG": false, + "ENABLE_FEAT_F4HWN_MEM": false, "ENABLE_AGC_SHOW_DATA": false, "ENABLE_UART_RW_BK_REGS": false, "ENABLE_SWD": false, @@ -188,6 +189,7 @@ "ENABLE_FEAT_F4HWN_RESCUE_OPS": true, "ENABLE_FEAT_F4HWN_VOL": true, "ENABLE_FEAT_F4HWN_AUDIO": true, + "ENABLE_FEAT_F4HWN_MEM": true, "ENABLE_SWD": true, "EDITION_STRING": "Fusion", "TARGET": "f4hwn.fusion" diff --git a/Core/py32f071xb.ld b/Core/py32f071xb.ld index 4c7e3c9b..bedcc0af 100644 --- a/Core/py32f071xb.ld +++ b/Core/py32f071xb.ld @@ -145,6 +145,15 @@ SECTIONS _edata = .; /* define a global symbol at data end */ } >RAM AT> FLASH + /* Explicit placement of .noncacheable (CherryUSB) + end-of-FLASH symbol */ + .noncacheable : + { + . = ALIGN(4); + *(.noncacheable) + *(.noncacheable*) + . = ALIGN(4); + } >RAM AT> FLASH + _eflash_used = LOADADDR(.noncacheable) + SIZEOF(.noncacheable); /* Uninitialized data section */ . = ALIGN(4); From dc0863127496d2f4b9e731527c29bd68dd34e590 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 19 Feb 2026 15:32:33 +0100 Subject: [PATCH 28/47] Update donors --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1b689d2..cf578ea6 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Anyway, have fun. # Donations -Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB and Antoine LIBERT for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 +Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT and Vince K0DKR for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 ## Table of Contents From d4e29c56a3432b6fee42fb31060488bac9742b81 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 21 Feb 2026 00:36:28 +0100 Subject: [PATCH 29/47] Improve channel input --- App/app/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/App/app/main.c b/App/app/main.c index 716436e6..784c1f80 100644 --- a/App/app/main.c +++ b/App/app/main.c @@ -728,6 +728,7 @@ static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld) } gKeyInputCountdown = key_input_timeout_500ms; + channelMoveSwitch(); #ifdef ENABLE_VOICE if (gInputBoxIndex == 0) From 335e1b550135985c974d137bfd35ce6ae3b9a8ba Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 21 Feb 2026 00:37:46 +0100 Subject: [PATCH 30/47] Update donors --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf578ea6..4dcc3e81 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Anyway, have fun. # Donations -Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT and Vince K0DKR for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 +Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT, Vince K0DKR and Julia DF7JA for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 ## Table of Contents From ed640588c57f4721cd7cdb97a4319e42829af7d0 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 21 Feb 2026 04:31:43 +0100 Subject: [PATCH 31/47] Welcome refactoring --- App/ui/welcome.c | 11 +++++++---- CMakePresets.json | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/App/ui/welcome.c b/App/ui/welcome.c index be34e823..cb6f3d9d 100644 --- a/App/ui/welcome.c +++ b/App/ui/welcome.c @@ -183,12 +183,15 @@ void UI_DisplayWelcome(void) #ifdef ENABLE_FEAT_F4HWN UI_PrintStringSmallNormal(Version, 0, 128, 4); - UI_DrawLineBuffer(gFrameBuffer, 0, 31, 127, 31, 1); // Be ware, status zone = 8 lines, the rest = 56 ->total 64 - - for (uint8_t i = 18; i < 110; i++) + UI_DrawLineBuffer(gFrameBuffer, 0, 35, 18, 35, 1); + gFrameBuffer[4][19] ^= 0x7F; + for (uint8_t x = 20; x < 108; x++) { - gFrameBuffer[4][i] ^= 0xFF; + gFrameBuffer[4][x] ^= 0xFF; + gFrameBuffer[3][x] ^= 0x80; } + gFrameBuffer[4][108] ^= 0x7F; + UI_DrawLineBuffer(gFrameBuffer, 109, 35, 127, 35, 1); #ifdef ENABLE_FEAT_F4HWN_MEM uint32_t ram_used = 0; diff --git a/CMakePresets.json b/CMakePresets.json index 01130203..0701d442 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -189,7 +189,6 @@ "ENABLE_FEAT_F4HWN_RESCUE_OPS": true, "ENABLE_FEAT_F4HWN_VOL": true, "ENABLE_FEAT_F4HWN_AUDIO": true, - "ENABLE_FEAT_F4HWN_MEM": true, "ENABLE_SWD": true, "EDITION_STRING": "Fusion", "TARGET": "f4hwn.fusion" From 29c0338c61dcc67566baeab70e133310ffdc73d6 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 21 Feb 2026 18:27:13 +0100 Subject: [PATCH 32/47] Fix screenshot and keyboard race --- App/screenshot.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/App/screenshot.c b/App/screenshot.c index f7c4f353..73c18c29 100644 --- a/App/screenshot.c +++ b/App/screenshot.c @@ -19,6 +19,7 @@ #include "screenshot.h" #include "misc.h" #include "driver/vcp.h" +#include "driver/keyboard.h" static void Screenshot_Send(const uint8_t *buf, uint16_t len) { @@ -125,6 +126,11 @@ void getScreenShot(bool force) if (deltaLen == 0) return; + // Skip transmission if a key is currently pressed + // UART_Send is blocking - would freeze the main loop and lose keypresses + if (gKeyReading0 != KEY_INVALID) + return; + // ==== Send version marker (for backward compatibility detection) ==== // New format: sends 0xFF before header // Old format: doesn't exist, so viewers can differentiate From 4937d0baba5ed548a952f3ab4602cb27603488b7 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Mon, 23 Feb 2026 02:43:30 +0100 Subject: [PATCH 33/47] Improve SMeter Calibration --- App/misc.c | 1 + App/misc.h | 1 + App/settings.c | 19 +++++++++------- App/ui/main.c | 55 ++++++++++++----------------------------------- App/ui/main.h | 1 - CMakePresets.json | 2 +- 6 files changed, 28 insertions(+), 51 deletions(-) diff --git a/App/misc.c b/App/misc.c index 00ca1bb9..c4724bcd 100644 --- a/App/misc.c +++ b/App/misc.c @@ -149,6 +149,7 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx; bool gPowerHigh = false; bool gRemoveOffset = false; #endif + int8_t dBmCorrTable[7] = {-15, -16, -10, -4, -7, -6, -1}; #endif #ifdef ENABLE_AUDIO_BAR diff --git a/App/misc.h b/App/misc.h index e42bcde9..02631d2d 100644 --- a/App/misc.h +++ b/App/misc.h @@ -211,6 +211,7 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx; extern bool gPowerHigh; extern bool gRemoveOffset; #endif + extern int8_t dBmCorrTable[7]; #endif #ifdef ENABLE_AUDIO_BAR diff --git a/App/settings.c b/App/settings.c index b57d47ce..e000c122 100644 --- a/App/settings.c +++ b/App/settings.c @@ -59,7 +59,7 @@ void SETTINGS_InitEEPROM(void) 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 + //configByte[4] &= (uint8_t)~0x40; // SET_NAV = 0 PY25Q16_WriteBuffer(0x00A000, configByte, sizeof(configByte), false); @@ -97,6 +97,12 @@ void SETTINGS_InitEEPROM(void) if (needsWrite) { PY25Q16_WriteBuffer(0x00A0C8, logoLines, sizeof(logoLines), false); } + + // 5. Reset dBmCorrTable + int8_t buf[7]; + for (uint8_t i = 0; i < 7; i++) + buf[i] = dBmCorrTable[i]; // values from misc.c + PY25Q16_WriteBuffer(0x00A0B9, buf, 7, false); } } @@ -240,13 +246,10 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE gEeprom.VOICE_PROMPT = (Data[0] < 3) ? Data[0] : VOICE_PROMPT_ENGLISH; #endif #ifdef ENABLE_RSSI_BAR - if((Data[1] < 200 && Data[1] > 90) && (Data[2] < Data[1]-9 && Data[1] < 160 && Data[2] > 50)) { - gEeprom.S0_LEVEL = Data[1]; - gEeprom.S9_LEVEL = Data[2]; - } - else { - gEeprom.S0_LEVEL = 130; - gEeprom.S9_LEVEL = 76; + 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 diff --git a/App/ui/main.c b/App/ui/main.c index ede9ed5b..44a2a537 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -60,16 +60,6 @@ center_line_t center_line = CENTER_LINE_NONE; } #endif -const int8_t dBmCorrTable[7] = { - -15, // band 1 - -16, // band 2 - -10, // band 3 - -4, // band 4 - -7, // band 5 - -6, // band 6 - -1 // band 7 - }; - const char *VfoStateStr[] = { [VFO_STATE_NORMAL]="", [VFO_STATE_BUSY]="BUSY", @@ -338,38 +328,21 @@ void DisplayRSSIBar(const bool now) #endif + dBmCorrTable[gRxVfo->Band]; - rssi_dBm = -rssi_dBm; + // S9 = -93 dBm, S0 = -141 dBm (IARU standard) + const int16_t s9_dBm = -93; + const int16_t s0_dBm = -141; - // VHF band 2 & 3 strong signal correction - if((gRxVfo->Band == 1 || gRxVfo->Band == 2) && rssi_dBm < 93) { // Band 2 & 3, signal > S9 - if(rssi_dBm <= 63) { - rssi_dBm -= 4; - } - else if(rssi_dBm <= 73) { - rssi_dBm -= 3; - } - else if(rssi_dBm <= 85) { - rssi_dBm -= 1; - } - } - // UHF band 6 strong signal correction - else if(gRxVfo->Band == 5 && rssi_dBm < 93) { // Band 6, signal > S9) - rssi_dBm -= 2; - } - - if(rssi_dBm > 141) rssi_dBm = 141; - if(rssi_dBm < 53) rssi_dBm = 53; - - uint8_t s_level = 0; - uint8_t overS9dBm = 0; + uint8_t s_level = 0; + uint8_t overS9dBm = 0; uint8_t overS9Bars = 0; - if(rssi_dBm >= 93) { - s_level = map(rssi_dBm, 141, 93, 1, 9); - } - else { - s_level = 9; - overS9dBm = map(rssi_dBm, 93, 53, 0, 40); + if (rssi_dBm <= s9_dBm) { + // Signal <= S9 : map between S0 and S9 + s_level = map(rssi_dBm, s0_dBm, s9_dBm, 0, 9); + } else { + // Signal > S9 : compute over-S9 + s_level = 9; + overS9dBm = map(rssi_dBm, s9_dBm, s9_dBm + 40, 0, 40); overS9Bars = map(overS9dBm, 0, 40, 0, 4); } #else @@ -390,12 +363,12 @@ void DisplayRSSIBar(const bool now) #ifdef ENABLE_FEAT_F4HWN if (gSetting_set_gui) { - sprintf(str, "%3d", -rssi_dBm); + sprintf(str, "%3d", rssi_dBm); UI_PrintStringSmallNormal(str, LCD_WIDTH + 8, 0, line - 1); } else { - sprintf(str, "% 4d %s", -rssi_dBm, "dBm"); + sprintf(str, "% 4d %s", rssi_dBm, "dBm"); if(isMainOnly()) GUI_DisplaySmallest(str, 2, 41, false, true); else diff --git a/App/ui/main.h b/App/ui/main.h index 92a1cb36..90fbb9fe 100644 --- a/App/ui/main.h +++ b/App/ui/main.h @@ -36,7 +36,6 @@ enum Vfo_txtr_mode{ typedef enum center_line_t center_line_t; extern center_line_t center_line; -extern const int8_t dBmCorrTable[7]; void UI_DisplayAudioBar(void); void UI_MAIN_TimeSlice500ms(void); diff --git a/CMakePresets.json b/CMakePresets.json index 0701d442..e82784d7 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -70,7 +70,7 @@ "ENABLE_UART_RW_BK_REGS": false, "ENABLE_SWD": false, "VERSION_STRING_1": "v0.22", - "VERSION_STRING_2": "v5.1.0" + "VERSION_STRING_2": "v5.2.0" } }, { From 793a44855d7dd3366683b78ae3ba2588e3a01552 Mon Sep 17 00:00:00 2001 From: kusy <110433144+mrkusypl@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:50:05 +0000 Subject: [PATCH 34/47] modified: App/app/app.c modified: App/app/fm.c modified: App/app/generic.c modified: App/app/main.c --- App/app/app.c | 6 +-- App/app/fm.c | 8 +++ App/app/generic.c | 2 +- App/app/main.c | 135 +++++++++++++++++++++++----------------------- 4 files changed, 78 insertions(+), 73 deletions(-) diff --git a/App/app/app.c b/App/app/app.c index 271a9696..dcdb016b 100644 --- a/App/app/app.c +++ b/App/app/app.c @@ -1535,7 +1535,6 @@ void cancelUserInputModes(void) gWasFKeyPressed = false; gInputBoxIndex = 0; gKeyInputCountdown = 0; - gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; gUpdateStatus = true; gUpdateDisplay = true; } @@ -1558,15 +1557,13 @@ void APP_TimeSlice500ms(void) if (--gKeyInputCountdown == 0) { - if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE) && (gInputBoxIndex == 1 || gInputBoxIndex == 2)) + if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE) && (gInputBoxIndex > 0 && gInputBoxIndex < 4)) { channelMoveSwitch(); if (gBeepToPlay == BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL) { AUDIO_PlayBeep(gBeepToPlay); } - - SETTINGS_SaveVfoIndices(); } cancelUserInputModes(); @@ -1936,6 +1933,7 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) // cancel user input cancelUserInputModes(); + gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; if (gMonitor) ACTION_Monitor(); //turn off the monitor diff --git a/App/app/fm.c b/App/app/fm.c index e22c2978..ed4882b2 100644 --- a/App/app/fm.c +++ b/App/app/fm.c @@ -377,6 +377,14 @@ static void Key_FUNC(KEY_Code_t Key, uint8_t state) gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; break; + case KEY_8: + ACTION_BackLightOnDemand(); + break; + + case KEY_9: + ACTION_BackLight(); + break; + case KEY_STAR: ACTION_Scan(autoScan); break; diff --git a/App/app/generic.c b/App/app/generic.c index 542e660e..4c5a42c5 100644 --- a/App/app/generic.c +++ b/App/app/generic.c @@ -39,7 +39,7 @@ void GENERIC_Key_F(bool bKeyPressed, bool bKeyHeld) { - if (gInputBoxIndex > 0) { + if (gInputBoxIndex > 0 || gScreenToDisplay == DISPLAY_MENU) { if (!bKeyHeld && bKeyPressed) // short pressed gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; return; diff --git a/App/app/main.c b/App/app/main.c index 784c1f80..a1cfa934 100644 --- a/App/app/main.c +++ b/App/app/main.c @@ -114,8 +114,6 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; return; } - - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; switch (Key) { case KEY_0: @@ -212,7 +210,7 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) #endif COMMON_SwitchVFOMode(); if (beep) - gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; + gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; break; @@ -230,7 +228,7 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) break; case KEY_5: - if(beep) { + if(!beep) { #ifdef ENABLE_NOAA if (!IS_NOAA_CHANNEL(gTxVfo->CHANNEL_SAVE)) { gEeprom.ScreenChannel[Vfo] = gEeprom.NoaaChannel[gEeprom.TX_VFO]; @@ -377,6 +375,8 @@ void channelMove(uint16_t Channel) #endif RADIO_ConfigureChannel(gEeprom.TX_VFO, gVfoConfigureMode); + + SETTINGS_SaveVfoIndices(); return; } @@ -411,11 +411,6 @@ void channelMoveSwitch(void) { if (gInputBoxIndex == 4) { gInputBoxIndex = 0; gKeyInputCountdown = 1; - - channelMove(Channel - 1); - SETTINGS_SaveVfoIndices(); - - return; } channelMove(Channel - 1); @@ -436,7 +431,7 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) gWasFKeyPressed = false; gUpdateStatus = true; - processFKeyFunction(Key, false); + processFKeyFunction(Key, true); } } return; @@ -682,34 +677,23 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) } #endif - processFKeyFunction(Key, true); + processFKeyFunction(Key, false); } static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld) { if (!bKeyHeld && bKeyPressed) { // exit key pressed - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; + gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; // beep when key is pressed + return; // don't use the key till it's released + } -#ifdef ENABLE_DTMF_CALLING - if (gDTMF_CallState != DTMF_CALL_STATE_NONE && gCurrentFunction != FUNCTION_TRANSMIT) - { // clear CALL mode being displayed - gDTMF_CallState = DTMF_CALL_STATE_NONE; - gUpdateDisplay = true; - return; - } -#endif + if (bKeyHeld) { // exit key held down + if (bKeyPressed) { + if (gInputBoxIndex > 0 || gDTMF_InputBox_Index > 0 || gDTMF_InputMode) + { // cancel key input mode (channel/frequency entry) -#ifdef ENABLE_FMRADIO - if (!gFmRadioMode) -#endif - { - if (gScanStateDir == SCAN_OFF) { - if (gInputBoxIndex == 0) - return; - gInputBox[--gInputBoxIndex] = 10; - - // Restore full VFO state when back to 0 - if (gInputBoxIndex == 0 && gHasVfoBackup) { + // Restore full VFO state on long press EXIT + if (gHasVfoBackup) { const uint8_t Vfo = gEeprom.TX_VFO; // Restore indices @@ -727,39 +711,38 @@ static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld) gHasVfoBackup = false; } - gKeyInputCountdown = key_input_timeout_500ms; - channelMoveSwitch(); - -#ifdef ENABLE_VOICE - if (gInputBoxIndex == 0) - gAnotherVoiceID = VOICE_ID_CANCEL; -#endif + gDTMF_InputMode = false; + gDTMF_InputBox_Index = 0; + memset(gDTMF_String, 0, sizeof(gDTMF_String)); + gInputBoxIndex = 0; + gRequestDisplayScreen = DISPLAY_MAIN; + gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; } - else { - gScanKeepResult = false; - CHFRSCANNER_Stop(); - -#ifdef ENABLE_VOICE - gAnotherVoiceID = VOICE_ID_SCANNING_STOP; -#endif - } - - gRequestDisplayScreen = DISPLAY_MAIN; - return; } -#ifdef ENABLE_FMRADIO - ACTION_FM(); -#endif return; } - if (bKeyHeld && bKeyPressed) { // exit key held down - if (gInputBoxIndex > 0 || gDTMF_InputBox_Index > 0 || gDTMF_InputMode) - { // cancel key input mode (channel/frequency entry) +#ifdef ENABLE_DTMF_CALLING + if (gDTMF_CallState != DTMF_CALL_STATE_NONE && gCurrentFunction != FUNCTION_TRANSMIT) + { // clear CALL mode being displayed + gDTMF_CallState = DTMF_CALL_STATE_NONE; + gUpdateDisplay = true; + return; + } +#endif - // Restore full VFO state on long press EXIT - if (gHasVfoBackup) { +#ifdef ENABLE_FMRADIO + if (!gFmRadioMode) +#endif + { + if (gScanStateDir == SCAN_OFF) { + if (gInputBoxIndex == 0) + return; + gInputBox[--gInputBoxIndex] = 10; + + // Restore full VFO state when back to 0 + if (gInputBoxIndex == 0 && gHasVfoBackup) { const uint8_t Vfo = gEeprom.TX_VFO; // Restore indices @@ -777,14 +760,31 @@ static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld) gHasVfoBackup = false; } - gDTMF_InputMode = false; - gDTMF_InputBox_Index = 0; - memset(gDTMF_String, 0, sizeof(gDTMF_String)); - gInputBoxIndex = 0; - gRequestDisplayScreen = DISPLAY_MAIN; - gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; + gKeyInputCountdown = key_input_timeout_500ms; + channelMoveSwitch(); + +#ifdef ENABLE_VOICE + if (gInputBoxIndex == 0) + gAnotherVoiceID = VOICE_ID_CANCEL; +#endif } + else { + gScanKeepResult = false; + CHFRSCANNER_Stop(); + +#ifdef ENABLE_VOICE + gAnotherVoiceID = VOICE_ID_SCANNING_STOP; +#endif + } + + gRequestDisplayScreen = DISPLAY_MAIN; + return; } + +#ifdef ENABLE_FMRADIO + ACTION_FM(); +#endif + return; } static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld) @@ -837,6 +837,9 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld) } if (!bKeyPressed && !gDTMF_InputMode) { // menu key released + gKeyInputCountdown = 1; + channelMoveSwitch(); + const bool bFlag = !gInputBoxIndex; gInputBoxIndex = 0; @@ -985,7 +988,7 @@ static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) if (bKeyHeld || !bKeyPressed) { // key held or released if (gInputBoxIndex > 0) - return; // leave if input box active + gInputBoxIndex = 0; if (!bKeyPressed) { if (!bKeyHeld || IS_FREQ_CHANNEL(Channel)) @@ -999,10 +1002,6 @@ static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) } } else { // short pressed - if (gInputBoxIndex > 0) { - gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; - return; - } gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; } From f072b3eb0232e1b893af95111bac0da24e1ccb9d Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 25 Feb 2026 02:24:39 +0100 Subject: [PATCH 35/47] Add virtual keyboard --- App/driver/keyboard.c | 36 +++++++++++++++++ App/driver/keyboard.h | 12 +++++- App/driver/uart.c | 43 ++++++++++++++++++--- App/driver/vcp.c | 90 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 170 insertions(+), 11 deletions(-) diff --git a/App/driver/keyboard.c b/App/driver/keyboard.c index ed4e2e6a..4d0a2532 100644 --- a/App/driver/keyboard.c +++ b/App/driver/keyboard.c @@ -27,6 +27,27 @@ KEY_Code_t gKeyReading1 = KEY_INVALID; uint16_t gDebounceCounter = 0; bool gWasFKeyPressed = false; +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT +// Number of consecutive KEYBOARD_Poll() calls that return the injected key. +// Must exceed key_debounce_10ms (typically 2) to pass the debounce gate in app.c. +// After HOLD polls the key is cleared, which triggers a natural release cycle. +#define SERIAL_KEY_HOLD_POLLS 5 + +volatile KEY_Code_t gKeyFromSerial = KEY_INVALID; +static uint8_t gSerialKeyHoldCount = 0; + +// Inject a key received from serial (UART or VCP). +// keyCode is the raw packet byte; validated against KEY_Code_e range before use. +// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial. +void KEYBOARD_InjectKey(uint8_t keyCode) +{ + if (keyCode < KEY_INVALID && keyCode != KEY_PTT) { + gKeyFromSerial = (KEY_Code_t)keyCode; + gSerialKeyHoldCount = 0; + } +} +#endif + #define GPIOx GPIOB #define PIN_MASK_COLS (LL_GPIO_PIN_6 | LL_GPIO_PIN_5 | LL_GPIO_PIN_4 | LL_GPIO_PIN_3) #define PIN_COLS GPIO_MAKE_PIN(GPIOx, PIN_MASK_COLS) @@ -78,6 +99,21 @@ static const KEY_Code_t keyboard[5][4] = { KEY_Code_t KEYBOARD_Poll(void) { +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT + // Serial-injected key: hold it for SERIAL_KEY_HOLD_POLLS consecutive calls + // so the debounce counter in app.c has time to reach key_debounce_10ms. + // Once the hold count is exhausted we clear it — the next call returns + // KEY_INVALID, which triggers the release path in app.c naturally. + if (gKeyFromSerial != KEY_INVALID) { + KEY_Code_t injected = gKeyFromSerial; + if (++gSerialKeyHoldCount >= SERIAL_KEY_HOLD_POLLS) { + gKeyFromSerial = KEY_INVALID; + gSerialKeyHoldCount = 0; + } + return injected; + } +#endif + KEY_Code_t Key = KEY_INVALID; // Scan all 5 columns - never break early to avoid GPIO state issues diff --git a/App/driver/keyboard.h b/App/driver/keyboard.h index 5a018445..3eef61af 100644 --- a/App/driver/keyboard.h +++ b/App/driver/keyboard.h @@ -50,7 +50,17 @@ extern KEY_Code_t gKeyReading1; extern uint16_t gDebounceCounter; extern bool gWasFKeyPressed; -KEY_Code_t KEYBOARD_Poll(void); +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT +// Serial-injected key (written by UART/VCP parser, consumed by KEYBOARD_Poll). +// Held for SERIAL_KEY_HOLD_POLLS calls to satisfy the debounce logic in app.c. +extern volatile KEY_Code_t gKeyFromSerial; +// Inject a key received from serial (UART or VCP), bypasses physical matrix. +// keyCode is the raw byte from the serial packet — validated against KEY_Code_e range. +// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial. +void KEYBOARD_InjectKey(uint8_t keyCode); #endif +KEY_Code_t KEYBOARD_Poll(void); + +#endif \ No newline at end of file diff --git a/App/driver/uart.c b/App/driver/uart.c index 33f4481f..d3e70079 100644 --- a/App/driver/uart.c +++ b/App/driver/uart.c @@ -22,7 +22,12 @@ #include "py32f071_ll_dma.h" #include "py32f071_ll_gpio.h" #include "py32f071_ll_usart.h" -#include "driver/uart.h" + +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT +#include "driver/keyboard.h" +// Packet type for serial key injection (K5Viewer → radio) +#define UART_TYPE_KEY 0x03 +#endif #define USARTx USART1 #define DMA_CHANNEL LL_DMA_CHANNEL_2 @@ -149,12 +154,40 @@ void UART_LogSend(const void *pBuffer, uint32_t Size) #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT bool UART_IsCableConnected(void) { + bool connected = false; + for (size_t i = 0; i < sizeof(UART_DMA_Buffer); i++) { - if (UART_DMA_Buffer[i] == 0x55) { - UART_DMA_Buffer[i] = 0x00; // Clear only the matched byte - return true; + uint8_t b = UART_DMA_Buffer[i]; + + if (b == 0x55) { + // Keepalive byte — viewer is alive + UART_DMA_Buffer[i] = 0x00; + connected = true; + } + else if (b == 0xAA) { + // Possible start of a key packet: 0xAA 0x55 0x03 + // Check if the next 3 bytes are available and match + size_t i1 = (i + 1) % sizeof(UART_DMA_Buffer); + size_t i2 = (i + 2) % sizeof(UART_DMA_Buffer); + size_t i3 = (i + 3) % sizeof(UART_DMA_Buffer); + + if (UART_DMA_Buffer[i1] == 0x55 && + UART_DMA_Buffer[i2] == UART_TYPE_KEY) + { + uint8_t keyCode = UART_DMA_Buffer[i3]; + + // Consume all 4 bytes + UART_DMA_Buffer[i] = 0x00; + UART_DMA_Buffer[i1] = 0x00; + UART_DMA_Buffer[i2] = 0x00; + UART_DMA_Buffer[i3] = 0x00; + + KEYBOARD_InjectKey(keyCode); + connected = true; + } } } - return false; + + return connected; } #endif \ No newline at end of file diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 87cfc1d5..27d9613a 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -18,6 +18,12 @@ #include "usb_config.h" #include "py32f071_ll_bus.h" +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT +#include "driver/keyboard.h" +// Packet type for serial key injection (K5Viewer → radio) +#define VCP_TYPE_KEY 0x03 +#endif + uint8_t VCP_RxBuf[VCP_RX_BUF_SIZE]; volatile uint32_t VCP_RxBufPointer = 0; @@ -40,22 +46,96 @@ void VCP_Init() bool VCP_ScreenshotPing(void) { - static uint32_t read_ptr = 0; +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT + // State machine for parsing incoming packets: + // Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive + // Key packet: 0xAA 0x55 0x03 → inject key + // + // State transitions: + // IDLE → 0x55 → KA_1 + // KA_1 → 0xAA → KA_2 (else IDLE) + // KA_2 → 0x00 → KA_3 (else IDLE) + // KA_3 → 0x00 → keepalive OK, IDLE + // + // IDLE → 0xAA → KEY_1 + // KEY_1 → 0x55 → KEY_2 (else IDLE) + // KEY_2 → 0x03 → KEY_3 (else IDLE) + // KEY_3 → → inject key, IDLE + typedef enum { + STATE_IDLE = 0, + STATE_KA_1, + STATE_KA_2, + STATE_KA_3, + STATE_KEY_1, + STATE_KEY_2, + STATE_KEY_3, + } ParseState_t; + + static uint32_t read_ptr = 0; + static ParseState_t state = STATE_IDLE; + + bool connected = false; uint32_t write_ptr = VCP_RxBufPointer; while (read_ptr != write_ptr) { uint8_t b = VCP_RxBuf[read_ptr]; - read_ptr++; if (read_ptr >= VCP_RX_BUF_SIZE) read_ptr = 0; - if (b == 0x55) + switch (state) { - return true; + case STATE_IDLE: + if (b == 0x55) state = STATE_KA_1; + else if (b == 0xAA) state = STATE_KEY_1; + break; + + case STATE_KA_1: + state = (b == 0xAA) ? STATE_KA_2 : STATE_IDLE; + break; + + case STATE_KA_2: + state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE; + break; + + case STATE_KA_3: + if (b == 0x00) connected = true; + state = STATE_IDLE; + break; + + case STATE_KEY_1: + state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE; + break; + + case STATE_KEY_2: + state = (b == VCP_TYPE_KEY) ? STATE_KEY_3 : STATE_IDLE; + break; + + case STATE_KEY_3: + KEYBOARD_InjectKey(b); + connected = true; + state = STATE_IDLE; + break; + + default: + state = STATE_IDLE; + break; } } + + return connected; + +#else + // Simple ping: just detect any incoming byte from the viewer + static uint32_t read_ptr = 0; + uint32_t write_ptr = VCP_RxBufPointer; + + if (read_ptr != write_ptr) { + read_ptr = write_ptr; + return true; + } return false; -} +#endif +} \ No newline at end of file From a244a2551362f61ffde5bdd3b946e76fb2ca6f47 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 25 Feb 2026 03:08:16 +0100 Subject: [PATCH 36/47] Add long press --- App/driver/keyboard.c | 43 +++++++++++++++++++++++++++----------- App/driver/keyboard.h | 8 ++++---- App/driver/uart.c | 19 +++++++++++------ App/driver/vcp.c | 48 +++++++++++++++++++++---------------------- 4 files changed, 72 insertions(+), 46 deletions(-) diff --git a/App/driver/keyboard.c b/App/driver/keyboard.c index 4d0a2532..c7f3875c 100644 --- a/App/driver/keyboard.c +++ b/App/driver/keyboard.c @@ -28,22 +28,37 @@ uint16_t gDebounceCounter = 0; bool gWasFKeyPressed = false; #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT -// Number of consecutive KEYBOARD_Poll() calls that return the injected key. -// Must exceed key_debounce_10ms (typically 2) to pass the debounce gate in app.c. -// After HOLD polls the key is cleared, which triggers a natural release cycle. -#define SERIAL_KEY_HOLD_POLLS 5 +// Short press: hold key for SERIAL_KEY_SHORT_POLLS calls. +// Must exceed key_debounce_10ms (2) to trigger ProcessKey(key, true, false). +#define SERIAL_KEY_SHORT_POLLS 5 + +// Long press: hold key for SERIAL_KEY_LONG_POLLS calls. +// Must exceed key_repeat_delay_10ms (40) to trigger ProcessKey(key, true, true). +#define SERIAL_KEY_LONG_POLLS 45 volatile KEY_Code_t gKeyFromSerial = KEY_INVALID; static uint8_t gSerialKeyHoldCount = 0; +static uint8_t gSerialKeyLong = 0; // 0 = short press, 1 = long press -// Inject a key received from serial (UART or VCP). -// keyCode is the raw packet byte; validated against KEY_Code_e range before use. +// Inject a short press from serial (UART or VCP). // KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial. void KEYBOARD_InjectKey(uint8_t keyCode) { if (keyCode < KEY_INVALID && keyCode != KEY_PTT) { gKeyFromSerial = (KEY_Code_t)keyCode; gSerialKeyHoldCount = 0; + gSerialKeyLong = 0; + } +} + +// Inject a long press from serial (UART or VCP). +// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial. +void KEYBOARD_InjectKeyLong(uint8_t keyCode) +{ + if (keyCode < KEY_INVALID && keyCode != KEY_PTT) { + gKeyFromSerial = (KEY_Code_t)keyCode; + gSerialKeyHoldCount = 0; + gSerialKeyLong = 1; } } #endif @@ -100,15 +115,19 @@ static const KEY_Code_t keyboard[5][4] = { KEY_Code_t KEYBOARD_Poll(void) { #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT - // Serial-injected key: hold it for SERIAL_KEY_HOLD_POLLS consecutive calls - // so the debounce counter in app.c has time to reach key_debounce_10ms. - // Once the hold count is exhausted we clear it — the next call returns - // KEY_INVALID, which triggers the release path in app.c naturally. + // Serial-injected key: hold it for SHORT or LONG polls depending on press type, + // so the debounce counter in app.c reaches the right threshold: + // - Short: key_debounce_10ms (2) → ProcessKey(key, true, false) + // - Long: key_repeat_delay_10ms (40) → ProcessKey(key, true, true) + // Once the hold count is exhausted we clear it — next call returns KEY_INVALID, + // which triggers the release path in app.c naturally. if (gKeyFromSerial != KEY_INVALID) { - KEY_Code_t injected = gKeyFromSerial; - if (++gSerialKeyHoldCount >= SERIAL_KEY_HOLD_POLLS) { + KEY_Code_t injected = gKeyFromSerial; + uint8_t threshold = gSerialKeyLong ? SERIAL_KEY_LONG_POLLS : SERIAL_KEY_SHORT_POLLS; + if (++gSerialKeyHoldCount >= threshold) { gKeyFromSerial = KEY_INVALID; gSerialKeyHoldCount = 0; + gSerialKeyLong = 0; } return injected; } diff --git a/App/driver/keyboard.h b/App/driver/keyboard.h index 3eef61af..1decc46b 100644 --- a/App/driver/keyboard.h +++ b/App/driver/keyboard.h @@ -52,13 +52,13 @@ extern bool gWasFKeyPressed; #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT // Serial-injected key (written by UART/VCP parser, consumed by KEYBOARD_Poll). -// Held for SERIAL_KEY_HOLD_POLLS calls to satisfy the debounce logic in app.c. extern volatile KEY_Code_t gKeyFromSerial; -// Inject a key received from serial (UART or VCP), bypasses physical matrix. -// keyCode is the raw byte from the serial packet — validated against KEY_Code_e range. -// KEY_PTT is explicitly blocked — PTT release cannot be guaranteed over serial. +// Inject a short press received from serial (UART or VCP). void KEYBOARD_InjectKey(uint8_t keyCode); + +// Inject a long press received from serial (UART or VCP). +void KEYBOARD_InjectKeyLong(uint8_t keyCode); #endif KEY_Code_t KEYBOARD_Poll(void); diff --git a/App/driver/uart.c b/App/driver/uart.c index d3e70079..de99a5e7 100644 --- a/App/driver/uart.c +++ b/App/driver/uart.c @@ -25,8 +25,9 @@ #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT #include "driver/keyboard.h" -// Packet type for serial key injection (K5Viewer → radio) -#define UART_TYPE_KEY 0x03 +// Packet types for serial key injection (K5Viewer → radio) +#define UART_TYPE_KEY 0x03 +#define UART_TYPE_KEY_LONG 0x04 #endif #define USARTx USART1 @@ -165,15 +166,17 @@ void UART_LogSend(const void *pBuffer, uint32_t Size) connected = true; } else if (b == 0xAA) { - // Possible start of a key packet: 0xAA 0x55 0x03 - // Check if the next 3 bytes are available and match + // Possible start of a key packet: 0xAA 0x55 + // type 0x03 = short press, 0x04 = long press size_t i1 = (i + 1) % sizeof(UART_DMA_Buffer); size_t i2 = (i + 2) % sizeof(UART_DMA_Buffer); size_t i3 = (i + 3) % sizeof(UART_DMA_Buffer); if (UART_DMA_Buffer[i1] == 0x55 && - UART_DMA_Buffer[i2] == UART_TYPE_KEY) + (UART_DMA_Buffer[i2] == UART_TYPE_KEY || + UART_DMA_Buffer[i2] == UART_TYPE_KEY_LONG)) { + uint8_t type = UART_DMA_Buffer[i2]; uint8_t keyCode = UART_DMA_Buffer[i3]; // Consume all 4 bytes @@ -182,7 +185,11 @@ void UART_LogSend(const void *pBuffer, uint32_t Size) UART_DMA_Buffer[i2] = 0x00; UART_DMA_Buffer[i3] = 0x00; - KEYBOARD_InjectKey(keyCode); + if (type == UART_TYPE_KEY_LONG) + KEYBOARD_InjectKeyLong(keyCode); + else + KEYBOARD_InjectKey(keyCode); + connected = true; } } diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 27d9613a..2ef8ffe0 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -20,8 +20,9 @@ #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT #include "driver/keyboard.h" -// Packet type for serial key injection (K5Viewer → radio) -#define VCP_TYPE_KEY 0x03 +// Packet types for serial key injection (K5Viewer → radio) +#define VCP_TYPE_KEY 0x03 +#define VCP_TYPE_KEY_LONG 0x04 #endif uint8_t VCP_RxBuf[VCP_RX_BUF_SIZE]; @@ -46,21 +47,23 @@ void VCP_Init() bool VCP_ScreenshotPing(void) { -#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT // State machine for parsing incoming packets: - // Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive - // Key packet: 0xAA 0x55 0x03 → inject key + // Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive + // Short key press: 0xAA 0x55 0x03 → inject short press + // Long key press: 0xAA 0x55 0x04 → inject long press // // State transitions: // IDLE → 0x55 → KA_1 - // KA_1 → 0xAA → KA_2 (else IDLE) - // KA_2 → 0x00 → KA_3 (else IDLE) + // KA_1 → 0xAA → KA_2 (else IDLE) + // KA_2 → 0x00 → KA_3 (else IDLE) // KA_3 → 0x00 → keepalive OK, IDLE // - // IDLE → 0xAA → KEY_1 - // KEY_1 → 0x55 → KEY_2 (else IDLE) - // KEY_2 → 0x03 → KEY_3 (else IDLE) - // KEY_3 → → inject key, IDLE + // IDLE → 0xAA → KEY_1 + // KEY_1 → 0x55 → KEY_2 (else IDLE) + // KEY_2 → 0x03 → KEY_3 (short press, else check long) + // KEY_2 → 0x04 → KEY_3L (long press, else IDLE) + // KEY_3 → → InjectKey(b), IDLE + // KEY_3L → → InjectKeyLong(b), IDLE typedef enum { STATE_IDLE = 0, @@ -70,6 +73,7 @@ bool VCP_ScreenshotPing(void) STATE_KEY_1, STATE_KEY_2, STATE_KEY_3, + STATE_KEY_3L, } ParseState_t; static uint32_t read_ptr = 0; @@ -110,7 +114,9 @@ bool VCP_ScreenshotPing(void) break; case STATE_KEY_2: - state = (b == VCP_TYPE_KEY) ? STATE_KEY_3 : STATE_IDLE; + if (b == VCP_TYPE_KEY) state = STATE_KEY_3; + else if (b == VCP_TYPE_KEY_LONG) state = STATE_KEY_3L; + else state = STATE_IDLE; break; case STATE_KEY_3: @@ -119,6 +125,12 @@ bool VCP_ScreenshotPing(void) state = STATE_IDLE; break; + case STATE_KEY_3L: + KEYBOARD_InjectKeyLong(b); + connected = true; + state = STATE_IDLE; + break; + default: state = STATE_IDLE; break; @@ -126,16 +138,4 @@ bool VCP_ScreenshotPing(void) } return connected; - -#else - // Simple ping: just detect any incoming byte from the viewer - static uint32_t read_ptr = 0; - uint32_t write_ptr = VCP_RxBufPointer; - - if (read_ptr != write_ptr) { - read_ptr = write_ptr; - return true; - } - return false; -#endif } \ No newline at end of file From 2248b0cbbe16ba18d61172baf1b16c2d62df6c23 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 25 Feb 2026 03:16:51 +0100 Subject: [PATCH 37/47] Update donors --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4dcc3e81..2246a8d0 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Anyway, have fun. # Donations -Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT, Vince K0DKR and Julia DF7JA for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 +Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT, Vince K0DKR, Julia DF7JA and Ken 2E0UMK for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 ## Table of Contents From 0c90b7d95c0f80fb675ae3799ecc19e56e6dd520 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 25 Feb 2026 04:49:38 +0100 Subject: [PATCH 38/47] Fix freeze when using K5Viewer and USB-C --- App/driver/vcp.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 2ef8ffe0..563c923c 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -80,14 +80,20 @@ bool VCP_ScreenshotPing(void) static ParseState_t state = STATE_IDLE; bool connected = false; - uint32_t write_ptr = VCP_RxBufPointer; + uint32_t write_ptr = VCP_RxBufPointer; // snapshot once — ISR may update concurrently - while (read_ptr != write_ptr) + // Cap bytes processed per call to VCP_RX_BUF_SIZE. + // Prevents unbounded loop if the ISR write pointer laps read_ptr + // (buffer overflow / corrupted state), which would freeze the firmware. + uint32_t processed = 0; + + while (read_ptr != write_ptr && processed < VCP_RX_BUF_SIZE) { uint8_t b = VCP_RxBuf[read_ptr]; read_ptr++; if (read_ptr >= VCP_RX_BUF_SIZE) read_ptr = 0; + processed++; switch (state) { From ed0f15959b8d7c027a467b69412fe0ac45b6d750 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 26 Feb 2026 05:41:49 +0100 Subject: [PATCH 39/47] Fix bug: can't build with Custom #175 --- App/driver/st7565.c | 4 +++- App/driver/vcp.c | 4 +++- App/driver/vcp.h | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/App/driver/st7565.c b/App/driver/st7565.c index 53ae3142..1be740a0 100644 --- a/App/driver/st7565.c +++ b/App/driver/st7565.c @@ -164,7 +164,9 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u void ST7565_BlitLine(unsigned line) { ST7565_BlitScreen(line + 1); - getScreenShot(true); // Force immediate capture + #ifdef ENABLE_FEAT_F4HWN_SCREENSHOT + getScreenShot(true); // Force immediate capture + #endif } void ST7565_BlitStatusLine(void) diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 563c923c..6082fc72 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -45,6 +45,7 @@ void VCP_Init() NVIC_EnableIRQ(USBD_IRQn); } +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT bool VCP_ScreenshotPing(void) { // State machine for parsing incoming packets: @@ -144,4 +145,5 @@ bool VCP_ScreenshotPing(void) } return connected; -} \ No newline at end of file +} +#endif // ENABLE_FEAT_F4HWN_SCREENSHOT diff --git a/App/driver/vcp.h b/App/driver/vcp.h index d7cc2c3a..0346dca1 100644 --- a/App/driver/vcp.h +++ b/App/driver/vcp.h @@ -28,7 +28,10 @@ extern uint8_t VCP_RxBuf[VCP_RX_BUF_SIZE]; extern volatile uint32_t VCP_RxBufPointer; void VCP_Init(); + +#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT bool VCP_ScreenshotPing(void); +#endif static inline void VCP_Send(const uint8_t *Buf, uint32_t Size) { From ec9bcba7819397088a1ee16b1bfe2e139c2280cc Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sat, 28 Feb 2026 00:13:39 +0100 Subject: [PATCH 40/47] Increase MIC gain values --- App/app/menu.c | 2 +- App/misc.c | 2 +- App/misc.h | 2 +- App/radio.c | 4 ++-- App/settings.c | 2 +- App/ui/menu.c | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/App/app/menu.c b/App/app/menu.c index 5f421c4c..13eba652 100644 --- a/App/app/menu.c +++ b/App/app/menu.c @@ -308,7 +308,7 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax) case MENU_MIC: //*pMin = 0; - *pMax = 4; + *pMax = 8; break; case MENU_LIST_CH: diff --git a/App/misc.c b/App/misc.c index c4724bcd..1b3dea84 100644 --- a/App/misc.c +++ b/App/misc.c @@ -87,7 +87,7 @@ const uint16_t NOAA_countdown_3_10ms = 200 / 10; // 200ms const uint32_t gDefaultAesKey[4] = {0x4AA5CC60, 0x0312CC5F, 0xFFD2DABB, 0x6BBA7F92}; -const uint8_t gMicGain_dB2[5] = {3, 8, 16, 24, 31}; +const uint8_t gMicGain_dB2[9] = {3, 8, 16, 24, 32, 40, 48, 56, 63}; // BK4819 {3, 8, 16, 24, 31}; #ifndef ENABLE_FEAT_F4HWN bool gSetting_350TX; diff --git a/App/misc.h b/App/misc.h index 02631d2d..d978894e 100644 --- a/App/misc.h +++ b/App/misc.h @@ -148,7 +148,7 @@ extern const uint16_t scan_pause_delay_in_7_10ms; //extern const uint16_t gMax_bat_v; //extern const uint16_t gMin_bat_v; -extern const uint8_t gMicGain_dB2[5]; +extern const uint8_t gMicGain_dB2[9]; #ifndef ENABLE_FEAT_F4HWN extern bool gSetting_350TX; diff --git a/App/radio.c b/App/radio.c index 008fab87..92153af8 100644 --- a/App/radio.c +++ b/App/radio.c @@ -796,8 +796,8 @@ void RADIO_SetupRegisters(bool switchToForeground) } BK4819_WriteRegister(BK4819_REG_3F, 0); - // mic gain 0.5dB/step 0 to 31 - BK4819_WriteRegister(BK4819_REG_7D, 0xE940 | (gEeprom.MIC_SENSITIVITY_TUNING & 0x1f)); + // 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 diff --git a/App/settings.c b/App/settings.c index e000c122..aa5888e3 100644 --- a/App/settings.c +++ b/App/settings.c @@ -128,7 +128,7 @@ void SETTINGS_InitEEPROM(void) gEeprom.VOX_SWITCH = (Data[5] < 2) ? Data[5] : false; gEeprom.VOX_LEVEL = (Data[6] < 10) ? Data[6] : 1; #endif - gEeprom.MIC_SENSITIVITY = (Data[7] < 5) ? Data[7] : 4; + gEeprom.MIC_SENSITIVITY = (Data[7] < 9) ? Data[7] : 4; // 0E78..0E7F PY25Q16_ReadBuffer(0x00A008, Data, 8); diff --git a/App/ui/menu.c b/App/ui/menu.c index 9da1bf11..5edaa315 100644 --- a/App/ui/menu.c +++ b/App/ui/menu.c @@ -646,7 +646,7 @@ void UI_DisplayMenu(void) case MENU_MIC: { // display the mic gain in actual dB rather than just an index number const uint8_t mic = gMicGain_dB2[gSubMenuSelection]; - sprintf(String, "+%u.%01udB", mic / 2, mic % 2); + sprintf(String, "+%u.%udB", mic / 2, (mic % 2) * 5); } break; From 37fb4abd2e16440fd749dffff7e681d2461a3661 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Tue, 3 Mar 2026 17:45:54 +0100 Subject: [PATCH 41/47] Update donors --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2246a8d0..78a1972b 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Anyway, have fun. # Donations -Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT, Vince K0DKR, Julia DF7JA and Ken 2E0UMK for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 +Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Olivier 14RC206, Frédéric F4ESO, Stéphane F5LGW (2 times), Jorge Ornelas (4 times), Laurent F4AXK, Christophe Morel, Clayton W0LED, Pierre Antoine F6FWB, Jean-Claude 14FRS3306, Thierry F4GVO, Eric F1NOU, PricelessToolkit, Ady M6NYJ, Tom McGovern (4 times), Joseph Roth, Pierre-Yves Colin, Frank DJ7FG, Marcel Testaz, Brian Frobisher, Yannick F4JFO, Paolo Bussola, Dirk DL8DF, Levente Szőke (2 times), Bernard-Michel Herrera, Jérôme Saintespes, Paul Davies, RS (3 times), Johan F4WAT, Robert Wörle, Rafael Sundorf, Paul Harker, Peter Fintl, Pascal F4ICR (2 times), Mike DL2MF (3 times), Eric KI1C (2 times), Phil G0ELM, Jérôme Lambert, Eliot Vedel, Alfonso EA7KDF, Jean-François F1EVM, Robert DC1RDB (2 times), Ian KE2CHJ, Daryl VK3AWA, Roberto Brunelli, Robert Boardman, Stephen Oliver, Nicolas F4INE, William Bruno, Daniel OK2VLK, Tayler Chew, Peter DL7RFP, Philippe Kopp, Rune LA6YMA, Jeremy Luna, Steef Wagenaar (2 times), Zhuo BG7SGA, Jamie M0JLB, Antoine LIBERT, Vince K0DKR, Julia DF7JA, Ken 2E0UMK and Victor TI2SYS for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻 ## Table of Contents From 4b8f8cf22af0d4d21662abc1d63dad0032637a61 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Wed, 4 Mar 2026 01:42:38 +0100 Subject: [PATCH 42/47] Change gDebug type --- App/driver/py25q16.c | 6 +++--- App/misc.c | 2 +- App/misc.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/App/driver/py25q16.c b/App/driver/py25q16.c index bc23f043..11e5f737 100644 --- a/App/driver/py25q16.c +++ b/App/driver/py25q16.c @@ -256,9 +256,9 @@ void PY25Q16_WriteBuffer(uint32_t Address, const void *pBuffer, uint32_t Size, b printf("spi flash write: %06x %ld %d\n", Address, Size, Append); #endif - #ifdef ENABLE_FEAT_F4HWN_DEBUG - gDebug++; - #endif + //#ifdef ENABLE_FEAT_F4HWN_DEBUG + // gDebug++; + //#endif uint32_t SecIndex = Address / SECTOR_SIZE; uint32_t SecAddr = SecIndex * SECTOR_SIZE; diff --git a/App/misc.c b/App/misc.c index 1b3dea84..b2490baf 100644 --- a/App/misc.c +++ b/App/misc.c @@ -135,7 +135,7 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx; bool gSetting_set_tmr = 0; bool gSetting_set_ptt_session; #ifdef ENABLE_FEAT_F4HWN_DEBUG - uint16_t gDebug; + int16_t gDebug; #endif uint8_t gDW = 0; uint8_t gCB = 0; diff --git a/App/misc.h b/App/misc.h index d978894e..d9179a93 100644 --- a/App/misc.h +++ b/App/misc.h @@ -197,7 +197,7 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx; extern bool gSetting_set_tmr; extern bool gSetting_set_ptt_session; #ifdef ENABLE_FEAT_F4HWN_DEBUG - extern uint16_t gDebug; + extern int16_t gDebug; #endif extern uint8_t gDW; extern uint8_t gCB; From 28500e1667c061801d935b8c86873c67d98e7293 Mon Sep 17 00:00:00 2001 From: kusy <110433144+mrkusypl@users.noreply.github.com> Date: Wed, 4 Mar 2026 14:10:02 +0000 Subject: [PATCH 43/47] Major code optimization (536 bytes saved) --- App/app/action.c | 36 +++++++----------- App/app/aircopy.c | 25 +++---------- App/app/app.c | 36 ++++-------------- App/app/breakout.c | 14 +++---- App/app/fm.c | 69 ++++++++++------------------------- App/app/main.c | 91 ++++++++++++++++++---------------------------- App/app/menu.c | 35 +++--------------- App/app/scanner.c | 14 +++---- App/app/spectrum.c | 84 +++++++++++++++--------------------------- App/functions.c | 9 +---- App/ui/main.c | 56 ++++++++++++++-------------- App/ui/menu.c | 3 +- 12 files changed, 157 insertions(+), 315 deletions(-) diff --git a/App/app/action.c b/App/app/action.c index 410ee518..fd53936f 100644 --- a/App/app/action.c +++ b/App/app/action.c @@ -503,17 +503,13 @@ void ACTION_RxMode(void) { static bool cycle = 0; - switch(cycle) { - case 0: - gEeprom.DUAL_WATCH = !gEeprom.DUAL_WATCH; - cycle = 1; - break; - case 1: - gEeprom.CROSS_BAND_RX_TX = !gEeprom.CROSS_BAND_RX_TX; - cycle = 0; - break; + if (cycle) { + gEeprom.CROSS_BAND_RX_TX = !gEeprom.CROSS_BAND_RX_TX; + } else { + gEeprom.DUAL_WATCH = !gEeprom.DUAL_WATCH; } + cycle = !cycle; ACTION_Update(); } @@ -523,22 +519,18 @@ void ACTION_MainOnly(void) static uint8_t dw = 0; static uint8_t cb = 0; - switch(cycle) { - case 0: - dw = gEeprom.DUAL_WATCH; - cb = gEeprom.CROSS_BAND_RX_TX; + if (cycle) { + gEeprom.DUAL_WATCH = dw; + gEeprom.CROSS_BAND_RX_TX = cb; + } else { + dw = gEeprom.DUAL_WATCH; + cb = gEeprom.CROSS_BAND_RX_TX; - gEeprom.DUAL_WATCH = 0; - gEeprom.CROSS_BAND_RX_TX = 0; - cycle = 1; - break; - case 1: - gEeprom.DUAL_WATCH = dw; - gEeprom.CROSS_BAND_RX_TX = cb; - cycle = 0; - break; + gEeprom.DUAL_WATCH = 0; + gEeprom.CROSS_BAND_RX_TX = 0; } + cycle = !cycle; ACTION_Update(); } diff --git a/App/app/aircopy.c b/App/app/aircopy.c index f5b42871..9d716d17 100644 --- a/App/app/aircopy.c +++ b/App/app/aircopy.c @@ -431,6 +431,10 @@ static void AIRCOPY_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Directio return; } + if (!gEeprom.SET_NAV) { + Direction = -Direction; + } + switch(Direction) { case 1: @@ -447,16 +451,7 @@ static void AIRCOPY_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Directio void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { switch (Key) { - case KEY_0: - case KEY_1: - case KEY_2: - case KEY_3: - case KEY_4: - case KEY_5: - case KEY_6: - case KEY_7: - case KEY_8: - case KEY_9: + case KEY_0...KEY_9: AIRCOPY_Key_DIGITS(Key, bKeyPressed, bKeyHeld); break; case KEY_MENU: @@ -466,16 +461,8 @@ void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) AIRCOPY_Key_EXIT(bKeyPressed, bKeyHeld); break; case KEY_UP: - if(gEeprom.SET_NAV == 0) - AIRCOPY_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); - else - AIRCOPY_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - break; case KEY_DOWN: - if(gEeprom.SET_NAV == 0) - AIRCOPY_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - else - AIRCOPY_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); + AIRCOPY_Key_UP_DOWN(bKeyPressed, bKeyHeld, Key == KEY_UP ? 1 : -1); break; default: break; diff --git a/App/app/app.c b/App/app/app.c index dcdb016b..ba58eb13 100644 --- a/App/app/app.c +++ b/App/app/app.c @@ -273,18 +273,9 @@ static void HandleReceive(void) break; case CODE_TYPE_CONTINUOUS_TONE: - if (gFoundCTCSS && gFoundCTCSSCountdown_10ms == 0) - { - gFoundCTCSS = false; - gFoundCDCSS = false; - Mode = END_OF_RX_MODE_END; - goto Skip; - } - break; - case CODE_TYPE_DIGITAL: case CODE_TYPE_REVERSE_DIGITAL: - if (gFoundCDCSS && gFoundCDCSSCountdown_10ms == 0) + if ((gFoundCTCSS && gFoundCTCSSCountdown_10ms == 0) || (gFoundCDCSS && gFoundCDCSSCountdown_10ms == 0)) { gFoundCTCSS = false; gFoundCDCSS = false; @@ -1133,14 +1124,7 @@ void APP_Update(void) // go back to sleep #ifdef ENABLE_FEAT_F4HWN_SLEEP - if(gWakeUp) - { - gPowerSave_10ms = gEeprom.BATTERY_SAVE * 200; // deep sleep now indexed on BatSav - } - else - { - gPowerSave_10ms = gEeprom.BATTERY_SAVE * 10; - } + gPowerSave_10ms = gEeprom.BATTERY_SAVE * (gWakeUp ? 200 : 10); // deep sleep now indexed on BatSav #else gPowerSave_10ms = gEeprom.BATTERY_SAVE * 10; #endif @@ -2124,18 +2108,14 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) } #endif } + else if (gScreenToDisplay != DISPLAY_INVALID && ( + (Key != KEY_SIDE1 && Key != KEY_SIDE2) #ifdef ENABLE_FEAT_F4HWN // For F + SIDE1 or F + SIDE2 - else if (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2)) { - ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld); - } - else if (Key != KEY_SIDE1 && Key != KEY_SIDE2 && gScreenToDisplay != DISPLAY_INVALID) { - ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld); - } -#else - else if (Key != KEY_SIDE1 && Key != KEY_SIDE2 && gScreenToDisplay != DISPLAY_INVALID) { - ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld); - } + || (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2)) #endif + )) { + ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld); + } else if (!SCANNER_IsScanning() #ifdef ENABLE_AIRCOPY && gScreenToDisplay != DISPLAY_AIRCOPY diff --git a/App/app/breakout.c b/App/app/breakout.c index 3e1ff4e9..72cae521 100644 --- a/App/app/breakout.c +++ b/App/app/breakout.c @@ -426,15 +426,7 @@ static bool HandleUserInput() if (kbd.current != KEY_INVALID && kbd.current == kbd.prev) { kbd.counter = 1; - } - else - { - kbd.counter = 0; - } - - // Process the key if counter indicates it should be handled - if (kbd.counter == 1) - { + OnKeyDown(kbd.current); // Special handling for MENU key @@ -444,6 +436,10 @@ static bool HandleUserInput() SYSTEM_DelayMs(250); } } + else + { + kbd.counter = 0; + } return true; } diff --git a/App/app/fm.c b/App/app/fm.c index ed4882b2..567c24b0 100644 --- a/App/app/fm.c +++ b/App/app/fm.c @@ -182,65 +182,38 @@ void FM_PlayAndUpdate(void) int FM_CheckFrequencyLock(uint16_t Frequency, uint16_t LowerLimit) { - int ret = -1; - const uint16_t Test2 = BK1080_ReadRegister(BK1080_REG_07); // This is supposed to be a signed value, but above function is unsigned const uint16_t Deviation = BK1080_REG_07_GET_FREQD(Test2); - - if (BK1080_REG_07_GET_SNR(Test2) <= 2) { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; - } - const uint16_t Status = BK1080_ReadRegister(BK1080_REG_10); - if ((Status & BK1080_REG_10_MASK_AFCRL) != BK1080_REG_10_AFCRL_NOT_RAILED || BK1080_REG_10_GET_RSSI(Status) < 10) { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; + BK1080_FrequencyDeviation = Deviation; + BK1080_BaseFrequency = Frequency; - return ret; - } + if (BK1080_REG_07_GET_SNR(Test2) <= 2) + return -1; + + if ((Status & BK1080_REG_10_MASK_AFCRL) != BK1080_REG_10_AFCRL_NOT_RAILED || BK1080_REG_10_GET_RSSI(Status) < 10) + return -1; //if (Deviation > -281 && Deviation < 280) - if (Deviation >= 280 && Deviation <= 3815) { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; - } - + if (Deviation >= 280 && Deviation <= 3815) + return -1; + // not BLE(less than or equal) if (Frequency > LowerLimit && (Frequency - BK1080_BaseFrequency) == 1) { - if (BK1080_FrequencyDeviation & 0x800 || (BK1080_FrequencyDeviation < 20)) - { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; - } + if ((BK1080_FrequencyDeviation & 0x800) || (BK1080_FrequencyDeviation < 20)) + return -1; } // not BLT(less than) - if (Frequency >= LowerLimit && (BK1080_BaseFrequency - Frequency) == 1) { if ((BK1080_FrequencyDeviation & 0x800) == 0 || (BK1080_FrequencyDeviation > 4075)) - { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; - } + return -1; } - ret = 0; - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; + return 0; } static void Key_DIGITS(KEY_Code_t Key, uint8_t state) @@ -501,6 +474,10 @@ static void Key_UP_DOWN(uint8_t state, int8_t Step) return; } + if (!gEeprom.SET_NAV) { + Step = -Step; + } + if (gAskToSave) { gRequestDisplayScreen = DISPLAY_FM; gFM_ChannelPosition = NUMBER_AddWithWraparound(gFM_ChannelPosition, Step, 0, FM_CHANNELS_MAX - 1); @@ -561,16 +538,8 @@ void FM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) Key_MENU(state); break; case KEY_UP: - if(gEeprom.SET_NAV == 0) - Key_UP_DOWN(state, -1); - else - Key_UP_DOWN(state, 1); - break; case KEY_DOWN: - if(gEeprom.SET_NAV == 0) - Key_UP_DOWN(state, 1); - else - Key_UP_DOWN(state, -1); + Key_UP_DOWN(state, Key == KEY_UP ? 1 : -1); break; case KEY_EXIT: Key_EXIT(state); diff --git a/App/app/main.c b/App/app/main.c index a1cfa934..ddacafbb 100644 --- a/App/app/main.c +++ b/App/app/main.c @@ -76,18 +76,18 @@ static void toggle_chan_scanlist(void) { att->exclude = false; return; - } + } - uint8_t scanlist = gTxVfo->SCANLIST_PARTICIPATION; + uint8_t scanlist = gTxVfo->SCANLIST_PARTICIPATION; - scanlist++; + scanlist++; - if (scanlist > MR_CHANNELS_LIST + 1) - scanlist = 0; + if (scanlist > MR_CHANNELS_LIST + 1) + scanlist = 0; - gTxVfo->SCANLIST_PARTICIPATION = scanlist; + gTxVfo->SCANLIST_PARTICIPATION = scanlist; - SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, true, true); + SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, true, true); gVfoConfigureMode = VFO_CONFIGURE; gFlagResetVfos = true; @@ -291,13 +291,13 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) case KEY_UP: case KEY_DOWN: { - int8_t direction = (Key == KEY_UP) ? 1 : -1; + bool isKeyUp = (Key == KEY_UP); if (gScanStateDir != SCAN_OFF) { - RADIO_NextValidList(direction); + RADIO_NextValidList(isKeyUp ? 1 : -1); } else { // Adjust squelch: UP increments, DOWN decrements - if (direction > 0) { + if (isKeyUp) { gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL < 9) ? gEeprom.SQUELCH_LEVEL + 1 : 9; } else { gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL > 0) ? gEeprom.SQUELCH_LEVEL - 1 : 0; @@ -309,30 +309,23 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep) } break; case KEY_SIDE1: - uint8_t a = FREQUENCY_GetSortedIdxFromStepIdx(gTxVfo->STEP_SETTING); - if (a < STEP_N_ELEM - 1) - { - gTxVfo->STEP_SETTING = FREQUENCY_GetStepIdxFromSortedIdx(a + 1); - } - if (IS_FREQ_CHANNEL(gTxVfo->CHANNEL_SAVE)) - { - gRequestSaveChannel = 1; - } - gVfoConfigureMode = VFO_CONFIGURE; - gWasFKeyPressed = false; - break; case KEY_SIDE2: - uint8_t b = FREQUENCY_GetSortedIdxFromStepIdx(gTxVfo->STEP_SETTING); - if (b > 0) { - gTxVfo->STEP_SETTING = FREQUENCY_GetStepIdxFromSortedIdx(b - 1); + bool isKeySide1 = (Key == KEY_SIDE1); + uint8_t idx = FREQUENCY_GetSortedIdxFromStepIdx(gTxVfo->STEP_SETTING); + + if ((isKeySide1 && idx < STEP_N_ELEM - 1) || (!isKeySide1 && idx > 0)) + { + gTxVfo->STEP_SETTING = FREQUENCY_GetStepIdxFromSortedIdx(idx + (isKeySide1 ? 1 : -1)); + + if (IS_FREQ_CHANNEL(gTxVfo->CHANNEL_SAVE)) { + gRequestSaveChannel = 1; + } + gVfoConfigureMode = VFO_CONFIGURE; + } + + gWasFKeyPressed = false; } - if (IS_FREQ_CHANNEL(gTxVfo->CHANNEL_SAVE)) - { - gRequestSaveChannel = 1; - } - gVfoConfigureMode = VFO_CONFIGURE; - gWasFKeyPressed = false; break; #endif @@ -493,15 +486,15 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) if (RADIO_CheckValidList(value)) { /* Requested scan list is valid */ - gEeprom.SCAN_LIST_DEFAULT = value; + gEeprom.SCAN_LIST_DEFAULT = value; } else - { - /* Requested scan list is empty or invalid: - jump to the next valid scan list */ + { + /* Requested scan list is empty or invalid: + jump to the next valid scan list */ gEeprom.SCAN_LIST_DEFAULT = value; - RADIO_NextValidList(1); - } + RADIO_NextValidList(1); + } #ifdef ENABLE_FEAT_F4HWN_RESUME_STATE SETTINGS_WriteCurrentState(); @@ -826,7 +819,6 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld) gRequestDisplayScreen = DISPLAY_MAIN; } - gWasFKeyPressed = false; gUpdateStatus = true; ACTION_Handle(KEY_MENU, bKeyPressed, bKeyHeld); @@ -963,18 +955,13 @@ static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld) static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) { + if (!gEeprom.SET_NAV) { + Direction = -Direction; + } #ifdef ENABLE_FEAT_F4HWN // Set Squelch F + UP or Down if(gWasFKeyPressed) { - switch(Direction) - { - case 1: - processFKeyFunction(KEY_UP, false); - break; - case -1: - processFKeyFunction(KEY_DOWN, false); - break; - } + processFKeyFunction(Direction == 1 ? KEY_UP : KEY_DOWN, false); return; } #endif @@ -1102,16 +1089,8 @@ void MAIN_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) MAIN_Key_MENU(bKeyPressed, bKeyHeld); break; case KEY_UP: - if(gEeprom.SET_NAV == 0) - MAIN_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); - else - MAIN_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - break; case KEY_DOWN: - if(gEeprom.SET_NAV == 0) - MAIN_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - else - MAIN_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); + MAIN_Key_UP_DOWN(bKeyPressed, bKeyHeld, Key == KEY_UP ? 1 : -1); break; case KEY_EXIT: MAIN_Key_EXIT(bKeyPressed, bKeyHeld); diff --git a/App/app/menu.c b/App/app/menu.c index 13eba652..9c307ef0 100644 --- a/App/app/menu.c +++ b/App/app/menu.c @@ -1874,6 +1874,10 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction) uint16_t Channel; bool bCheckScanList; + if (!gEeprom.SET_NAV && gIsInSubMenu) { + Direction = -Direction; + } + if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && gIsInSubMenu && edit_index >= 0) { // change the character if (bKeyPressed && edit_index < 10 && Direction != 0) @@ -2014,42 +2018,15 @@ void MENU_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { switch (Key) { - case KEY_0: - case KEY_1: - case KEY_2: - case KEY_3: - case KEY_4: - case KEY_5: - case KEY_6: - case KEY_7: - case KEY_8: - case KEY_9: + case KEY_0...KEY_9: MENU_Key_0_to_9(Key, bKeyPressed, bKeyHeld); break; case KEY_MENU: MENU_Key_MENU(bKeyPressed, bKeyHeld); break; case KEY_UP: - if(gEeprom.SET_NAV == 0) { - if(gIsInSubMenu) - MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); - else - MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - } - else { - MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - } - break; case KEY_DOWN: - if(gEeprom.SET_NAV == 0) { - if(gIsInSubMenu) - MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - else - MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); - } - else { - MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); - } + MENU_Key_UP_DOWN(bKeyPressed, bKeyHeld, Key == KEY_UP ? 1 : -1); break; case KEY_EXIT: MENU_Key_EXIT(bKeyPressed, bKeyHeld); diff --git a/App/app/scanner.c b/App/app/scanner.c index 85fdb434..b023ec44 100644 --- a/App/app/scanner.c +++ b/App/app/scanner.c @@ -257,6 +257,10 @@ static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool pKeyHeld, int8_t Directio gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL; } + if (!gEeprom.SET_NAV) { + Direction = -Direction; + } + if (gScannerSaveState == SCAN_SAVE_CHAN_SEL) { gScanChannel = NUMBER_AddWithWraparound(gScanChannel, Direction, 0, MR_CHANNEL_LAST); gShowChPrefix = RADIO_CheckValidChannel(gScanChannel, false, 0); @@ -276,16 +280,8 @@ void SCANNER_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) SCANNER_Key_MENU(bKeyPressed, bKeyHeld); break; case KEY_UP: - if(gEeprom.SET_NAV == 0) - SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); - else - SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - break; case KEY_DOWN: - if(gEeprom.SET_NAV == 0) - SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1); - else - SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1); + SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, Key == KEY_UP ? 1 : -1); break; case KEY_EXIT: SCANNER_Key_EXIT(bKeyPressed, bKeyHeld); diff --git a/App/app/spectrum.c b/App/app/spectrum.c index a5d00510..b8e3b996 100644 --- a/App/app/spectrum.c +++ b/App/app/spectrum.c @@ -1185,37 +1185,32 @@ static void DrawArrow(uint8_t x) static void OnKeyDown(uint8_t key) { - bool nav = (gEeprom.SET_NAV != 0); + bool nav = gEeprom.SET_NAV; + bool isTrue = false; switch (key) { case KEY_3: - UpdateDBMax(true); - break; + isTrue = true; + [[fallthrough]]; case KEY_9: - UpdateDBMax(false); + UpdateDBMax(isTrue); break; case KEY_1: - UpdateScanStep(true); - break; + isTrue = true; + [[fallthrough]]; case KEY_7: - UpdateScanStep(false); + UpdateScanStep(isTrue); break; case KEY_2: - UpdateFreqChangeStep(true); - break; + isTrue = true; + [[fallthrough]]; case KEY_8: - UpdateFreqChangeStep(false); + UpdateFreqChangeStep(isTrue); break; case KEY_UP: -#ifdef ENABLE_SCAN_RANGES - if (!gScanRangeStart) { -#endif - UpdateCurrentFreq(nav); -#ifdef ENABLE_SCAN_RANGES - } -#endif - break; + nav = !nav; + [[fallthrough]]; case KEY_DOWN: #ifdef ENABLE_SCAN_RANGES if (!gScanRangeStart) { @@ -1229,10 +1224,10 @@ static void OnKeyDown(uint8_t key) Blacklist(); break; case KEY_STAR: - UpdateRssiTriggerLevel(true); - break; + isTrue = true; + [[fallthrough]]; case KEY_F: - UpdateRssiTriggerLevel(false); + UpdateRssiTriggerLevel(isTrue); break; case KEY_5: #ifdef ENABLE_SCAN_RANGES @@ -1285,21 +1280,10 @@ static void OnKeyDownFreqInput(uint8_t key) { switch (key) { - case KEY_0: - case KEY_1: - case KEY_2: - case KEY_3: - case KEY_4: - case KEY_5: - case KEY_6: - case KEY_7: - case KEY_8: - case KEY_9: + case KEY_0...KEY_9: case KEY_STAR: - UpdateFreqInput(key); - break; case KEY_EXIT: - if (freqInputIndex == 0) + if (freqInputIndex == 0 && key == KEY_EXIT) { SetState(previousState); break; @@ -1330,23 +1314,20 @@ static void OnKeyDownFreqInput(uint8_t key) void OnKeyDownStill(KEY_Code_t key) { - bool nav = (gEeprom.SET_NAV != 0); + bool nav = gEeprom.SET_NAV; + bool isTrue = false; switch (key) { case KEY_3: - UpdateDBMax(true); - break; + isTrue = true; + [[fallthrough]]; case KEY_9: - UpdateDBMax(false); + UpdateDBMax(isTrue); break; case KEY_UP: - if (menuState) { - SetRegMenuValue(menuState, nav); - break; - } - UpdateCurrentFreqStill(nav); - break; + nav = !nav; + [[fallthrough]]; case KEY_DOWN: if (menuState) { SetRegMenuValue(menuState, !nav); @@ -1355,10 +1336,10 @@ void OnKeyDownStill(KEY_Code_t key) UpdateCurrentFreqStill(!nav); break; case KEY_STAR: - UpdateRssiTriggerLevel(true); - break; + isTrue = true; + [[fallthrough]]; case KEY_F: - UpdateRssiTriggerLevel(false); + UpdateRssiTriggerLevel(isTrue); break; case KEY_5: FreqInput(); @@ -1381,14 +1362,7 @@ void OnKeyDownStill(KEY_Code_t key) BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, true); */ break; case KEY_MENU: - if (menuState == ARRAY_SIZE(registerSpecs) - 1) - { - menuState = 1; - } - else - { - menuState++; - } + menuState = (menuState == ARRAY_SIZE(registerSpecs) - 1) ? 1 : menuState + 1; redrawScreen = true; break; case KEY_EXIT: diff --git a/App/functions.c b/App/functions.c index ef580fc7..e68e6723 100644 --- a/App/functions.c +++ b/App/functions.c @@ -118,14 +118,7 @@ void FUNCTION_Foreground(const FUNCTION_Type_t PreviousFunction) void FUNCTION_PowerSave() { #ifdef ENABLE_FEAT_F4HWN_SLEEP - if(gWakeUp) - { - gPowerSave_10ms = gEeprom.BATTERY_SAVE * 200; // deep sleep now indexed on BatSav - } - else - { - gPowerSave_10ms = gEeprom.BATTERY_SAVE * 10; - } + gPowerSave_10ms = gEeprom.BATTERY_SAVE * (gWakeUp ? 200 : 10); // deep sleep now indexed on BatSav #else gPowerSave_10ms = gEeprom.BATTERY_SAVE * 10; #endif diff --git a/App/ui/main.c b/App/ui/main.c index 44a2a537..931387af 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -46,7 +46,7 @@ center_line_t center_line = CENTER_LINE_NONE; #ifdef ENABLE_FEAT_F4HWN - static int8_t RxBlink; + // static int8_t RxBlink; static int8_t RxBlinkLed = 0; static int8_t RxBlinkLedCounter; static int8_t RxLine; @@ -770,37 +770,37 @@ void UI_DisplayMain(void) //if (FUNCTION_IsRx() && gEeprom.RX_VFO == vfo_num) { if (FUNCTION_IsRx() && gEeprom.RX_VFO == vfo_num && VfoState[vfo_num] == VFO_STATE_NORMAL) { #ifdef ENABLE_FEAT_F4HWN - RxBlinkLed = 1; - RxBlinkLedCounter = 0; - RxLine = line; - RxOnVfofrequency = frequency; - if(!isMainVFO) - { - RxBlink = 1; - } - else - { - RxBlink = 0; - } + RxBlinkLed = 1; + RxBlinkLedCounter = 0; + RxLine = line; + RxOnVfofrequency = frequency; + // if(!isMainVFO) + // { + // RxBlink = 1; + // } + // else + // { + // RxBlink = 0; + // } - if (RxBlink == 0 || RxBlink == 1) { - if(gRxVfo->Modulation == MODULATION_AM) - GUI_DisplaySmallest("AIR", 10, RxLine == 0 ? 1 : 33, false, true); - else { - #ifdef ENABLE_FEAT_F4HWN_AUDIO - strcpy(String, gSubMenu_SET_AUD[gSetting_set_audio]); - #else - strcpy(String, "RX"); - #endif - GUI_DisplaySmallest(String, 10, RxLine == 0 ? 1 : 33, false, true); - } + // if (RxBlink == 0 || RxBlink == 1) { + if(gRxVfo->Modulation == MODULATION_AM) + GUI_DisplaySmallest("AIR", 10, RxLine == 0 ? 1 : 33, false, true); + else { + #ifdef ENABLE_FEAT_F4HWN_AUDIO + strcpy(String, gSubMenu_SET_AUD[gSetting_set_audio]); + #else + strcpy(String, "RX"); + #endif + GUI_DisplaySmallest(String, 10, RxLine == 0 ? 1 : 33, false, true); + } - //UI_PrintStringSmallBold("RX", 8, 0, RxLine); - } + //UI_PrintStringSmallBold("RX", 8, 0, RxLine); + // } #else - UI_PrintStringSmallBold("RX", 8, 0, line); + UI_PrintStringSmallBold("RX", 8, 0, line); #endif - } + } #ifdef ENABLE_FEAT_F4HWN else { diff --git a/App/ui/menu.c b/App/ui/menu.c index 5edaa315..827a30cc 100644 --- a/App/ui/menu.c +++ b/App/ui/menu.c @@ -703,15 +703,14 @@ void UI_DisplayMenu(void) if (!gIsInSubMenu || gInputBoxIndex == 0) { sprintf(String, "%3d.%05u", gSubMenuSelection / 100000, abs(gSubMenuSelection) % 100000); - UI_PrintString(String, menu_item_x1, menu_item_x2, 1, 8); } else { const char * ascii = INPUTBOX_GetAscii(); sprintf(String, "%.3s.%.3s ",ascii, ascii + 3); - UI_PrintString(String, menu_item_x1, menu_item_x2, 1, 8); } + UI_PrintString(String, menu_item_x1, menu_item_x2, 1, 8); UI_PrintString("MHz", menu_item_x1, menu_item_x2, 3, 8); already_printed = true; From 2f90b401cbbb3d4a51e77dd2632cca6c0a228373 Mon Sep 17 00:00:00 2001 From: kusy <110433144+mrkusypl@users.noreply.github.com> Date: Wed, 4 Mar 2026 18:06:41 +0000 Subject: [PATCH 44/47] Restore FM_CheckFrequencyLock() --- App/app/fm.c | 55 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/App/app/fm.c b/App/app/fm.c index 567c24b0..93831a8b 100644 --- a/App/app/fm.c +++ b/App/app/fm.c @@ -182,38 +182,65 @@ void FM_PlayAndUpdate(void) int FM_CheckFrequencyLock(uint16_t Frequency, uint16_t LowerLimit) { + int ret = -1; + const uint16_t Test2 = BK1080_ReadRegister(BK1080_REG_07); // This is supposed to be a signed value, but above function is unsigned const uint16_t Deviation = BK1080_REG_07_GET_FREQD(Test2); + + if (BK1080_REG_07_GET_SNR(Test2) <= 2) { + BK1080_FrequencyDeviation = Deviation; + BK1080_BaseFrequency = Frequency; + + return ret; + } + const uint16_t Status = BK1080_ReadRegister(BK1080_REG_10); - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; + if ((Status & BK1080_REG_10_MASK_AFCRL) != BK1080_REG_10_AFCRL_NOT_RAILED || BK1080_REG_10_GET_RSSI(Status) < 10) { + BK1080_FrequencyDeviation = Deviation; + BK1080_BaseFrequency = Frequency; - if (BK1080_REG_07_GET_SNR(Test2) <= 2) - return -1; - - if ((Status & BK1080_REG_10_MASK_AFCRL) != BK1080_REG_10_AFCRL_NOT_RAILED || BK1080_REG_10_GET_RSSI(Status) < 10) - return -1; + return ret; + } //if (Deviation > -281 && Deviation < 280) - if (Deviation >= 280 && Deviation <= 3815) - return -1; - + if (Deviation >= 280 && Deviation <= 3815) { + BK1080_FrequencyDeviation = Deviation; + BK1080_BaseFrequency = Frequency; + + return ret; + } + // not BLE(less than or equal) if (Frequency > LowerLimit && (Frequency - BK1080_BaseFrequency) == 1) { - if ((BK1080_FrequencyDeviation & 0x800) || (BK1080_FrequencyDeviation < 20)) - return -1; + if (BK1080_FrequencyDeviation & 0x800 || (BK1080_FrequencyDeviation < 20)) + { + BK1080_FrequencyDeviation = Deviation; + BK1080_BaseFrequency = Frequency; + + return ret; + } } // not BLT(less than) + if (Frequency >= LowerLimit && (BK1080_BaseFrequency - Frequency) == 1) { if ((BK1080_FrequencyDeviation & 0x800) == 0 || (BK1080_FrequencyDeviation > 4075)) - return -1; + { + BK1080_FrequencyDeviation = Deviation; + BK1080_BaseFrequency = Frequency; + + return ret; + } } - return 0; + ret = 0; + BK1080_FrequencyDeviation = Deviation; + BK1080_BaseFrequency = Frequency; + + return ret; } static void Key_DIGITS(KEY_Code_t Key, uint8_t state) From d9eb55fccf35746840e82e01f561667e1ab44142 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 5 Mar 2026 00:19:11 +0100 Subject: [PATCH 45/47] FM_CheckFrequencyLock() refactoring --- App/app/fm.c | 68 +++++++++++++++++++--------------------------------- 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/App/app/fm.c b/App/app/fm.c index 93831a8b..d3f53e3a 100644 --- a/App/app/fm.c +++ b/App/app/fm.c @@ -182,65 +182,45 @@ void FM_PlayAndUpdate(void) int FM_CheckFrequencyLock(uint16_t Frequency, uint16_t LowerLimit) { - int ret = -1; - - const uint16_t Test2 = BK1080_ReadRegister(BK1080_REG_07); - - // This is supposed to be a signed value, but above function is unsigned + const uint16_t Test2 = BK1080_ReadRegister(BK1080_REG_07); const uint16_t Deviation = BK1080_REG_07_GET_FREQD(Test2); - if (BK1080_REG_07_GET_SNR(Test2) <= 2) { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; + // Helper macro to update globals and return + #define RETURN(val) \ + do { \ + BK1080_FrequencyDeviation = Deviation; \ + BK1080_BaseFrequency = Frequency; \ + return (val); \ + } while (0) - return ret; - } + if (BK1080_REG_07_GET_SNR(Test2) <= 2) + RETURN(-1); const uint16_t Status = BK1080_ReadRegister(BK1080_REG_10); + if ((Status & BK1080_REG_10_MASK_AFCRL) != BK1080_REG_10_AFCRL_NOT_RAILED || + BK1080_REG_10_GET_RSSI(Status) < 10) + RETURN(-1); - if ((Status & BK1080_REG_10_MASK_AFCRL) != BK1080_REG_10_AFCRL_NOT_RAILED || BK1080_REG_10_GET_RSSI(Status) < 10) { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; + if (Deviation >= 280 && Deviation <= 3815) + RETURN(-1); - return ret; - } - - //if (Deviation > -281 && Deviation < 280) - if (Deviation >= 280 && Deviation <= 3815) { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; - } - - // not BLE(less than or equal) + // Scanning upward: previous deviation was negative (bit 11 set) or near zero if (Frequency > LowerLimit && (Frequency - BK1080_BaseFrequency) == 1) { - if (BK1080_FrequencyDeviation & 0x800 || (BK1080_FrequencyDeviation < 20)) - { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; - } + if (BK1080_FrequencyDeviation & 0x800 || BK1080_FrequencyDeviation < 20) + RETURN(-1); } - // not BLT(less than) - + // Scanning downward: previous deviation was positive or saturated high if (Frequency >= LowerLimit && (BK1080_BaseFrequency - Frequency) == 1) { - if ((BK1080_FrequencyDeviation & 0x800) == 0 || (BK1080_FrequencyDeviation > 4075)) - { - BK1080_FrequencyDeviation = Deviation; - BK1080_BaseFrequency = Frequency; - - return ret; - } + if ((BK1080_FrequencyDeviation & 0x800) == 0 || BK1080_FrequencyDeviation > 4075) + RETURN(-1); } - ret = 0; + #undef RETURN + BK1080_FrequencyDeviation = Deviation; BK1080_BaseFrequency = Frequency; - - return ret; + return 0; } static void Key_DIGITS(KEY_Code_t Key, uint8_t state) From 5b638e298efa3264b218c3f15890d63929689663 Mon Sep 17 00:00:00 2001 From: mrkusypl Date: Thu, 5 Mar 2026 15:19:20 +0100 Subject: [PATCH 46/47] Bug fixes: Receiving the same channel in Dual Watch mode, disabling channel exclusion, changing the scan list, compilation error --- App/app/main.c | 27 ++++++++++++--------------- App/misc.c | 2 +- App/ui/main.c | 34 +++++++++++++++++++--------------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/App/app/main.c b/App/app/main.c index ddacafbb..69f0e4c9 100644 --- a/App/app/main.c +++ b/App/app/main.c @@ -75,9 +75,10 @@ static void toggle_chan_scanlist(void) if(att->exclude == true) { att->exclude = false; - return; + MR_SaveChannelAttributesToFlash(gTxVfo->CHANNEL_SAVE, att); } - + else + { uint8_t scanlist = gTxVfo->SCANLIST_PARTICIPATION; scanlist++; @@ -88,6 +89,7 @@ static void toggle_chan_scanlist(void) gTxVfo->SCANLIST_PARTICIPATION = scanlist; SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, true, true); + } gVfoConfigureMode = VFO_CONFIGURE; gFlagResetVfos = true; @@ -483,27 +485,21 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) /* 01 .. MR_CHANNELS_LIST */ if (value <= MR_CHANNELS_LIST) { - if (RADIO_CheckValidList(value)) + gEeprom.SCAN_LIST_DEFAULT = value; + + if (!RADIO_CheckValidList(value)) { - /* Requested scan list is valid */ - gEeprom.SCAN_LIST_DEFAULT = value; + /* Requested scan list is empty or invalid: + jump to the next valid scan list */ + gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; + RADIO_NextValidList(1); } - else - { - /* Requested scan list is empty or invalid: - jump to the next valid scan list */ - gEeprom.SCAN_LIST_DEFAULT = value; - RADIO_NextValidList(1); - } #ifdef ENABLE_FEAT_F4HWN_RESUME_STATE SETTINGS_WriteCurrentState(); #endif } - gInputBoxIndex = 0; - - gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL; return; } @@ -763,6 +759,7 @@ static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld) } else { gScanKeepResult = false; + gInputBoxIndex = 0; CHFRSCANNER_Stop(); #ifdef ENABLE_VOICE diff --git a/App/misc.c b/App/misc.c index b2490baf..b923617e 100644 --- a/App/misc.c +++ b/App/misc.c @@ -293,7 +293,7 @@ uint8_t gFSKWriteIndex; #ifdef ENABLE_NOAA bool gIsNoaaMode; - uint16_t gNoaaChannel; + uint8_t gNoaaChannel; #endif bool gUpdateDisplay; diff --git a/App/ui/main.c b/App/ui/main.c index 931387af..008ee431 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -768,22 +768,23 @@ void UI_DisplayMain(void) { // receiving .. show the RX symbol mode = VFO_MODE_RX; //if (FUNCTION_IsRx() && gEeprom.RX_VFO == vfo_num) { - if (FUNCTION_IsRx() && gEeprom.RX_VFO == vfo_num && VfoState[vfo_num] == VFO_STATE_NORMAL) { + if (FUNCTION_IsRx()) { + if (gEeprom.RX_VFO == vfo_num && VfoState[vfo_num] == VFO_STATE_NORMAL) { #ifdef ENABLE_FEAT_F4HWN RxBlinkLed = 1; RxBlinkLedCounter = 0; RxLine = line; RxOnVfofrequency = frequency; - // if(!isMainVFO) - // { - // RxBlink = 1; - // } - // else - // { - // RxBlink = 0; - // } + // if(!isMainVFO) + // { + // RxBlink = 1; + // } + // else + // { + // RxBlink = 0; + // } - // if (RxBlink == 0 || RxBlink == 1) { + // if (RxBlink == 0 || RxBlink == 1) { if(gRxVfo->Modulation == MODULATION_AM) GUI_DisplaySmallest("AIR", 10, RxLine == 0 ? 1 : 33, false, true); else { @@ -796,16 +797,19 @@ void UI_DisplayMain(void) } //UI_PrintStringSmallBold("RX", 8, 0, RxLine); - // } + // } #else UI_PrintStringSmallBold("RX", 8, 0, line); #endif } #ifdef ENABLE_FEAT_F4HWN - else - { - if(RxOnVfofrequency == frequency && !isMainOnly()) - { + else { + if(RxBlinkLed == 1) + RxBlinkLed = 2; + } + } + else { + if(RxOnVfofrequency == frequency && !isMainOnly()) { //UI_PrintStringSmallNormal(">>", 8, 0, line); //memcpy(p_line0 + 14, BITMAP_VFO_Default, sizeof(BITMAP_VFO_Default)); GUI_DisplaySmallest(">>", 8, RxLine == 0 ? 1 : 33, false, true); From 9a46bdf6bfb1d234ebfc5f230c665a2f7fa89a8d Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 5 Mar 2026 16:36:01 +0100 Subject: [PATCH 47/47] Add mic gauge --- App/ui/menu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/App/ui/menu.c b/App/ui/menu.c index 827a30cc..2d230457 100644 --- a/App/ui/menu.c +++ b/App/ui/menu.c @@ -647,6 +647,10 @@ void UI_DisplayMenu(void) { // display the mic gain in actual dB rather than just an index number const uint8_t mic = gMicGain_dB2[gSubMenuSelection]; sprintf(String, "+%u.%udB", mic / 2, (mic % 2) * 5); + + gaugeLine = 4; + gaugeMin = 0; + gaugeMax = 8; } break;