Compare commits

...
74 Commits
Author SHA1 Message Date
Armel FAUVEAU 786318453f Merge pull request #314 from armel/feature_update_v5
Feature update v5
2026-04-28 03:51:29 +02:00
Armel FAUVEAU 61662f3965 Prepare new version v5.4.0 2026-04-28 03:48:04 +02:00
Armel FAUVEAU b3cfda76e5 Merge pull request #313 from mrkusypl/feature_update_v5_2
Code refactoring (68 B)
2026-04-27 22:13:05 +02:00
mrkusypl a20f1ce28e Code refactoring (68 B) 2026-04-27 16:26:49 +02:00
Armel FAUVEAU 8a292755b0 Prepare new version 2026-04-27 15:15:10 +02:00
Armel FAUVEAU 915fc6aa90 SysInf tweak 2026-04-27 15:12:48 +02:00
Armel FAUVEAU cac63f978e Revert "Add UI_PrintStringYOffset to shift big-font"
This reverts commit d5edba1fc2.
2026-04-27 15:03:44 +02:00
Armel FAUVEAU d5edba1fc2 Add UI_PrintStringYOffset to shift big-font 2026-04-27 06:10:44 +02:00
Armel FAUVEAU 2f0852cd03 Add SysInf build details page 2026-04-27 04:12:51 +02:00
Armel FAUVEAU a65f84305e Show firmware version in big font 2026-04-27 04:00:08 +02:00
Armel FAUVEAU 1c0d5ea628 Move SysInf battery data to dedicated page (and patch m lower...) 2026-04-27 03:54:13 +02:00
Armel FAUVEAU a0a5b93408 Merge pull request #312 from mrkusypl/feature_update_v5_2
Code refactoring (12 B)
2026-04-27 02:46:17 +02:00
mrkusypl e8cb32a4ca Code refactoring (12 B) 2026-04-27 02:34:49 +02:00
Armel FAUVEAU 706dea756c Refactor: QR codes only in SysInf menu, drop welcome splash 2026-04-26 23:20:44 +02:00
Armel FAUVEAU 1ea8aba88e Add QR code welcome screen with repo and wiki links 2026-04-26 15:12:24 +02:00
Armel FAUVEAU c2d6a4b751 Fix excluded scan gauge rendering for large channel lists 2026-04-26 04:39:14 +02:00
Armel FAUVEAU db61bce014 Merge pull request #310 from mrkusypl/feature_update_v5_2
Entering text into ChName in a multi-tap system
2026-04-26 03:09:28 +02:00
mrkusypl a3c03f18f0 Fix for string terminator overwriting 2026-04-26 02:24:27 +02:00
mrkusypl 8977a0a112 Entering text into ChName in a multi-tap system 2026-04-26 01:09:57 +02:00
Armel FAUVEAU 09a5e3c1e7 Fix limit bug with scan gauge 2026-04-25 05:09:32 +02:00
Armel FAUVEAU 4fa821e15f Oops ! Preserve scan gauge width calculation, with or without priority labels 2026-04-25 04:50:50 +02:00
Armel FAUVEAU c9a383919a Improve scan progress gauge when priority channels are in use 2026-04-25 04:39:18 +02:00
Armel FAUVEAU e8ba069000 Merge pull request #308 from mrkusypl/feature_update_v5_2
Code refactoring (200 B)
2026-04-25 02:21:18 +02:00
mrkusypl acd2c340ea Code refactoring (200 B) 2026-04-25 02:10:52 +02:00
Armel FAUVEAU 24fd6fd9b1 Improve spectrum trigger line rendering 2026-04-25 00:17:50 +02:00
Armel FAUVEAU 86bb710ca4 Merge pull request #307 from mrkusypl/feature_update_v5_2
Reversing the up/down button functions on the Spectrum
2026-04-24 20:04:32 +02:00
mrkusypl 7148c15f98 Reversing the up/down button functions on the Spectrum 2026-04-24 20:02:05 +02:00
Armel FAUVEAU 2d7093651d Show sweep direction arrow next to A:NORM/WEAK/STRG label 2026-04-24 19:45:20 +02:00
Armel FAUVEAU 874e5aac4b Extract SETTINGS_LoadEepromDtmf to file scope to drop the GCC nested-function extension 2026-04-24 19:35:48 +02:00
Armel FAUVEAU 8051605c16 Guard interlaced sweep code behind SPECTRUM_INTERLACE_LARGE_SWEEPS to silence unused warnings 2026-04-24 19:30:14 +02:00
Armel FAUVEAU 134624825f Merge pull request #306 from mrkusypl/feature_update_v5_2
Code refactoring (232 B)
2026-04-24 18:04:04 +02:00
mrkusypl 7d0fcc8415 Code refactoring (232 B) 2026-04-24 17:51:48 +02:00
Armel FAUVEAU c27b72d6bc Merge pull request #305 from mrkusypl/feature_update_v5_2
Code refactoring (180 B)
2026-04-24 04:49:11 +02:00
Armel FAUVEAU 066c0d0e14 Spectrum experimentation 2026-04-24 04:48:18 +02:00
mrkusypl e77b87f1f2 Another code refactoring (40 B) 2026-04-24 00:09:38 +02:00
mrkusypl 2dd5e1a509 Code refactoring (140 B) 2026-04-23 18:32:39 +02:00
Armel FAUVEAU ab9e97805e Merge pull request #300 from mrkusypl/feature_update_v5_2
Refactoring ScanProgress_DrawGaugeLine
2026-04-23 02:28:38 +02:00
Armel FAUVEAU 65c584ff7d Spectrum/BK4829: interlace large sweeps and cut SPI overhead 2026-04-23 02:27:52 +02:00
mrkusypl 4c76ca1fb4 Refactoring ScanProgress_DrawGaugeLine 2026-04-22 22:33:30 +02:00
Armel FAUVEAU 094896b5f1 UI format progress index with dynamic zero padding based on total digits 2026-04-22 04:00:29 +02:00
Armel FAUVEAU 669dad5b2e Merge pull request #298 from mrkusypl/feature_update_v5_2
Changing the length of the scan progress gauge
2026-04-22 03:31:48 +02:00
Armel FAUVEAU b9902e0c49 UI unify and center top-right menu info badges 2026-04-22 03:30:52 +02:00
mrkusypl 0a955c1f15 Changing the length of the scan progress gauge 2026-04-22 01:54:49 +02:00
Armel FAUVEAU 9f649b5f5b Merge pull request #297 from mrkusypl/feature_update_v5_2
Code refactoring (44 B)
2026-04-21 20:35:33 +02:00
Armel FAUVEAU bb992ca85d Scan progress gauge behind ENABLE_FEAT_F4HWN_SCAN_PROGRESS 2026-04-21 20:34:20 +02:00
mrkusypl 679cf4ea1f Code refactoring (44 B) 2026-04-21 17:26:18 +02:00
Armel FAUVEAU 0004074ffe Fix large-range spectrum stability and manual dB floor behavior 2026-04-21 05:25:28 +02:00
Armel FAUVEAU 35e5d8df90 Fix spectrum manual trigger persistence across scan relaunches 2026-04-21 03:07:23 +02:00
Armel FAUVEAU fa0de5f576 Spectrum: fix and polish manual mode (manualSetFlag) from PR #296 2026-04-21 02:36:32 +02:00
Armel FAUVEAU 4162618473 Merge pull request #296 from mrkusypl/feature_update_v5_2
Code refactoring, improvements in Spectrum
2026-04-21 01:58:39 +02:00
mrkusypl 284af7ca1d Code refactoring, improvements in Spectrum 2026-04-20 23:05:05 +02:00
Armel FAUVEAU c6f1f7b483 Spectrum: align channel name and frequency at x=43 to avoid dBm overlap 2026-04-19 03:21:43 +02:00
Armel FAUVEAU 6cea90d507 Merge pull request #292 from mrkusypl/feature_update_v5_2
Code optimization (80 B), behavior of the EXIT button when editing ChName
2026-04-19 00:29:34 +02:00
Armel FAUVEAU 46608af767 Spectrum refactoring 2026-04-18 18:36:39 +02:00
mrkusypl 24167a7902 Code optimization (80 B), behavior of the EXIT button when editing ChName 2026-04-18 18:30:22 +02:00
Armel FAUVEAU e300c4e7e3 Improve ChName editing with long-press EXIT backspace (issue #281) 2026-04-18 01:09:40 +02:00
Armel FAUVEAU 1a815b315f Update donors 2026-04-18 00:42:49 +02:00
Armel FAUVEAU a7fed2c2d8 Spectrum refactoring 2026-04-18 00:41:24 +02:00
Armel FAUVEAU 9e92037674 Fix build without ENABLE_FMRADIO in APP_TimeSlice500ms 2026-04-15 22:17:05 +02:00
Armel FAUVEAU e536725731 Show homologated CTCSS index in Rx/Tx CTCSS menus 2026-04-15 00:20:13 +02:00
Armel FAUVEAU f59370cee6 Merge pull request #268 from mrkusypl/feature_update_v5_2
Improvements to FM Radio and the UI
2026-04-14 23:11:03 +02:00
mrkusypl bdaf320c08 Fix the channel number display 2026-04-09 10:17:07 +02:00
Armel FAUVEAU fb60d6f7fd Add WIDE/NARROW filter selection for AM RX 2026-04-09 02:44:20 +02:00
Armel FAUVEAU 22295eedcf Update donors 2026-04-08 18:08:50 +02:00
mrkusypl 21c215e9fd Improvements to FM Radio and the UI 2026-04-05 21:01:13 +02:00
Armel FAUVEAU 0061613883 Merge pull request #265 from armel/feature_update_v5
Feature update v5
2026-04-05 06:29:27 +02:00
Armel FAUVEAU aecbcc6409 Update README 2026-04-05 06:27:34 +02:00
Armel FAUVEAU 642dcc9a43 Add support for decoding packed Quansheng stock firmware images 2026-04-03 05:00:38 +02:00
Armel FAUVEAU 2b05990a2c Refactor hidden menu entry to start from first hidden item 2026-04-03 04:28:39 +02:00
Armel FAUVEAU 975084baca Merge pull request #252 from mrkusypl/feature_update_v5_2
Code refactoring (108 B)
2026-04-02 00:06:08 +02:00
mrkusypl 775cd7751a Undo refactoring of AUDIO_Apply(FM/AM)Profile 2026-04-01 13:26:29 +02:00
mrkusypl c5da503cb3 Code refactoring (136 B) 2026-04-01 00:37:44 +02:00
Armel FAUVEAU 5f8a492f4f Merge pull request #251 from armel/feature_update_v5
Update donors
2026-03-31 19:01:33 +02:00
Armel FAUVEAU 8dd5da195e Update donors 2026-03-31 19:00:32 +02:00
44 changed files with 2686 additions and 928 deletions

No files matched your search

+2 -2
View File
@@ -12,6 +12,6 @@ compile_commands.json
k5_eeprom.raw
/build
/serialtool/__pycache__
__pycache__/
.DS_Store
.DS_Store
+3
View File
@@ -78,6 +78,7 @@ endif()
target_compile_definitions(App INTERFACE
"AUTHOR_STRING=\"${AUTHOR_STRING}\""
"VERSION_STRING=\"${VERSION_STRING}\""
"BUILD_COMMIT=\"${BUILD_COMMIT}\""
)
if(NOT SQL_TONE)
@@ -194,6 +195,7 @@ enable_feature(ENABLE_FEAT_F4HWN_RESUME_STATE)
enable_feature(ENABLE_FEAT_F4HWN_NARROWER)
enable_feature(ENABLE_FEAT_F4HWN_INV)
enable_feature(ENABLE_FEAT_F4HWN_CTR)
enable_feature(ENABLE_FEAT_F4HWN_SCAN_PROGRESS)
enable_feature(ENABLE_FEAT_F4HWN_RESCUE_OPS)
enable_feature(ENABLE_FEAT_F4HWN_VOL)
enable_feature(ENABLE_FEAT_F4HWN_AUDIO)
@@ -203,6 +205,7 @@ 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)
enable_feature(ENABLE_FEAT_F4HWN_QRCODE)
# ---- DEBUGGING ----
+9 -10
View File
@@ -597,7 +597,7 @@ void ACTION_Wn(void)
if (pVfo->Modulation == MODULATION_AM)
{
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_AM, true);
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(pVfo), true);
return;
}
@@ -660,26 +660,25 @@ void ACTION_Mute(void)
BK1080_WriteRegister(BK1080_REG_05_SYSTEM_CONFIGURATION2, gMute ? 0x0A10 : 0x0A1F);
#endif
gEeprom.VOLUME_GAIN = gMute ? 0 : gEeprom.VOLUME_GAIN_BACKUP;
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
(0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
gUpdateStatus = true;
}
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
void ACTION_ToggleVfoSetting(bool *setting) {
*setting = !(*setting);
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
}
void ACTION_Power_High(void)
{
gPowerHigh = !gPowerHigh;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
ACTION_ToggleVfoSetting(&gPowerHigh);
}
void ACTION_Remove_Offset(void)
{
gRemoveOffset = !gRemoveOffset;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
ACTION_ToggleVfoSetting(&gRemoveOffset);
}
#endif
#endif
+27 -28
View File
@@ -557,11 +557,7 @@ void APP_StartListening(FUNCTION_Type_t function)
gUpdateStatus = true;
}
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 to 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
#ifdef ENABLE_VOICE
if (gVoiceWriteIndex == 0) // AM/FM RX mode will be set when the voice has finished
@@ -816,6 +812,22 @@ void APP_EndTransmission(void)
}
}
void APP_HandleEndTransmission(void) {
if (gFlagEndTransmission) {
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else {
APP_EndTransmission();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0)
FUNCTION_Select(FUNCTION_FOREGROUND);
else
gRTTECountdown_10ms = gEeprom.REPEATER_TAIL_TONE_ELIMINATION * 10;
}
gFlagEndTransmission = false;
}
#ifdef ENABLE_VOX
static void HandleVox(void)
{
@@ -851,24 +863,10 @@ static void HandleVox(void)
gVOX_NoiseDetected = false;
if (gCurrentFunction == FUNCTION_TRANSMIT && !gPttIsPressed && !gVOX_NoiseDetected) {
if (gFlagEndTransmission) {
//if (gCurrentFunction != FUNCTION_FOREGROUND)
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else {
APP_EndTransmission();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0) {
//if (gCurrentFunction != FUNCTION_FOREGROUND)
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else
gRTTECountdown_10ms = gEeprom.REPEATER_TAIL_TONE_ELIMINATION * 10;
}
APP_HandleEndTransmission();
gUpdateStatus = true;
gUpdateDisplay = true;
gFlagEndTransmission = false;
}
return;
}
@@ -1437,8 +1435,7 @@ void APP_TimeSlice10ms(void)
if (gAlarmState == ALARM_STATE_TXALARM) {
gAlarmState = ALARM_STATE_SITE_ALARM;
if(gEeprom.TAIL_TONE_ELIMINATION)
RADIO_SendCssTail();
RADIO_SendCssTail();
BK4819_SetupPowerAmplifier(0, 0);
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
BK4819_Enable_AfDac_DiscMode_TxDsp();
@@ -1534,7 +1531,11 @@ void APP_TimeSlice500ms(void)
if (--gKeyInputCountdown == 0)
{
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE) && (gInputBoxIndex > 0 && gInputBoxIndex < 4) && (!gFmRadioMode))
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE) && (gInputBoxIndex > 0 && gInputBoxIndex < 4)
#ifdef ENABLE_FMRADIO
&& (!gFmRadioMode)
#endif
)
{
channelMoveSwitch();
@@ -1943,12 +1944,10 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
bool lowBatPopup = gLowBattery && !gLowBatteryConfirmed && gScreenToDisplay == DISPLAY_MAIN;
#ifdef ENABLE_FEAT_F4HWN // Disable PTT if KEY_LOCK
bool lck_condition = false;
bool lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT;
if(gSetting_set_lck)
lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT;
else
lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT && Key != KEY_PTT;
if(!gSetting_set_lck)
lck_condition = lck_condition && Key != KEY_PTT;
if (lck_condition)
#else
+1
View File
@@ -24,6 +24,7 @@
#include "radio.h"
void APP_EndTransmission(void);
void APP_HandleEndTransmission(void);
void APP_StartListening(FUNCTION_Type_t function);
uint32_t APP_SetFreqByStepAndLimits(VFO_Info_t *pInfo, int8_t direction, uint32_t lower, uint32_t upper);
uint32_t APP_SetFrequencyByStep(VFO_Info_t *pInfo, int8_t direction);
+17 -10
View File
@@ -40,7 +40,6 @@ volatile int8_t gFM_ScanState;
bool gFM_AutoScan;
uint8_t gFM_ChannelPosition;
bool gFM_FoundFrequency;
bool gFM_AutoScan;
uint16_t gFM_RestoreCountdown_10ms;
@@ -167,6 +166,12 @@ void FM_Tune(uint16_t Frequency, int8_t Step, bool bFlag)
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
}
void FM_AudioPathOn(void) {
BACKLIGHT_TurnOn();
AUDIO_AudioPathOn();
gEnableSpeaker = true;
}
void FM_PlayAndUpdate(void)
{
gFM_ScanState = FM_SCAN_OFF;
@@ -184,9 +189,7 @@ void FM_PlayAndUpdate(void)
gScheduleFM = false;
gAskToSave = false;
AUDIO_AudioPathOn();
gEnableSpeaker = true;
FM_AudioPathOn();
}
int FM_CheckFrequencyLock(uint16_t Frequency, uint16_t LowerLimit)
@@ -391,8 +394,14 @@ static void Key_FUNC(KEY_Code_t Key, uint8_t state)
static void Key_EXIT(uint8_t state)
{
if (state != BUTTON_EVENT_SHORT)
return;
if (gInputBoxIndex) {
if (state != BUTTON_EVENT_SHORT)
return;
}
else {
if (state != BUTTON_EVENT_PRESSED)
return;
}
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
@@ -603,8 +612,7 @@ void FM_Play(void)
if (!gEeprom.FM_IsMrMode)
gEeprom.FM_SelectedFrequency = gEeprom.FM_FrequencyPlaying;
AUDIO_AudioPathOn();
gEnableSpeaker = true;
FM_AudioPathOn();
GUI_SelectNextDisplay(DISPLAY_FM);
return;
@@ -639,9 +647,8 @@ void FM_Start(void)
// Disable UHF LNA, enable VHF LNA
BK4819_PickRXFilterPathBasedOnFrequency(10320000); // 103.2 MHz < 280 MHz
AUDIO_AudioPathOn();
FM_AudioPathOn();
gEnableSpeaker = true;
gUpdateStatus = true;
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
+2 -14
View File
@@ -91,7 +91,7 @@ void GENERIC_Key_F(bool bKeyPressed, bool bKeyHeld)
return;
}
#endif
gBeepToPlay = BEEP_440HZ_500MS;
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
gPttWasReleased = true;
}
}
@@ -103,20 +103,8 @@ void GENERIC_Key_PTT(bool bKeyPressed)
if (!bKeyPressed || SerialConfigInProgress())
{ // PTT released
if (gCurrentFunction == FUNCTION_TRANSMIT) {
// we are transmitting .. stop
if (gFlagEndTransmission) {
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else {
APP_EndTransmission();
APP_HandleEndTransmission();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0)
FUNCTION_Select(FUNCTION_FOREGROUND);
else
gRTTECountdown_10ms = gEeprom.REPEATER_TAIL_TONE_ELIMINATION * 10;
}
gFlagEndTransmission = false;
#ifdef ENABLE_VOX
gVOX_NoiseDetected = false;
#endif
+13 -14
View File
@@ -44,6 +44,7 @@
#include "radio.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/main.h"
#include "ui/ui.h"
#include <stdlib.h>
@@ -305,9 +306,9 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
gSquelchLevelOriginal = gEeprom.SQUELCH_LEVEL;
if (isKeyUp) {
gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL < 9) ? gEeprom.SQUELCH_LEVEL + 1 : 9;
if (gEeprom.SQUELCH_LEVEL < 9) gEeprom.SQUELCH_LEVEL++;
} else {
gEeprom.SQUELCH_LEVEL = (gEeprom.SQUELCH_LEVEL > 0) ? gEeprom.SQUELCH_LEVEL - 1 : 0;
if (gEeprom.SQUELCH_LEVEL > 0) gEeprom.SQUELCH_LEVEL--;
}
gVfoConfigureMode = VFO_CONFIGURE;
}
@@ -555,10 +556,10 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
return;
}
gKeyInputCountdown = (gInputBoxIndex >= totalDigits) ? (key_input_timeout_500ms / 16) : (key_input_timeout_500ms / 3);
gKeyInputCountdown = key_input_timeout_500ms / (gInputBoxIndex >= totalDigits ? 16 : 3);
if (gInputBoxIndex > totalDigits) {
gInputBoxIndex = totalDigits;
gInputBoxIndex = totalDigits;
return;
}
@@ -569,11 +570,8 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
// convert to int
uint32_t inputFreq = StrToUL(inputStr);
// how many zero to add
uint8_t zerosToAdd = totalDigits - inputLength;
// add missing zero
for (uint8_t i = 0; i < zerosToAdd; i++) {
for (uint8_t i = 0; i < totalDigits - inputLength; i++) {
inputFreq *= 10;
}
@@ -767,6 +765,7 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld)
att->exclude = true;
MR_SaveChannelAttributesToFlash(lastFoundFrqOrChan, att);
UI_MAIN_NotifyScanProgressDataChanged();
gVfoConfigureMode = VFO_CONFIGURE;
gFlagResetVfos = true;
@@ -941,12 +940,12 @@ static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
uint16_t Channel = gEeprom.ScreenChannel[gEeprom.TX_VFO];
if (bKeyHeld || !bKeyPressed) { // key held or released
if (gInputBoxIndex > 0) {
gInputBoxIndex = 0;
gHasVfoBackup = false;
}
if (gInputBoxIndex > 0) {
gInputBoxIndex = 0;
gHasVfoBackup = false;
}
if (bKeyHeld || !bKeyPressed) { // key held or released
if (!bKeyPressed) {
if (!bKeyHeld || IS_FREQ_CHANNEL(Channel))
return;
@@ -1079,4 +1078,4 @@ void MAIN_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
}
}
+193 -76
View File
@@ -457,6 +457,28 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
#endif
#endif
case MENU_VOL: {
// SysInf paginates:
// page 0 = identity,
// page 1 = build date/time,
// page 2 = battery,
// +1 if F4HWN_MEM (Flash/SRAM),
// +2 if F4HWN_QRCODE (Code QR + Wiki QR).
int32_t vol_max = 0;
#ifdef ENABLE_FEAT_F4HWN
vol_max += 2;
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
vol_max += 1;
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
vol_max += 2;
#endif
if (vol_max == 0) return -1;
*pMax = vol_max;
break;
}
default:
return -1;
}
@@ -594,7 +616,7 @@ void MENU_AcceptSetting(void)
case MENU_MEM_NAME:
for (int i = 9; i >= 0; i--) {
if (edit[i] != ' ' && edit[i] != '_' && edit[i] != 0x00 && edit[i] != 0xff)
if (edit[i] != ' ' && edit[i] != 0x00 && edit[i] != 0xff)
break;
edit[i] = ' ';
}
@@ -1030,6 +1052,11 @@ void MENU_ShowCurrentSetting(void)
gSubMenuSelection = gEeprom.SQUELCH_LEVEL;
break;
case MENU_VOL:
// SysInf is paginated; always start on page 0 (identity).
gSubMenuSelection = 0;
break;
case MENU_STEP:
gSubMenuSelection = FREQUENCY_GetSortedIdxFromStepIdx(gTxVfo->STEP_SETTING);
break;
@@ -1464,6 +1491,22 @@ void MENU_ShowCurrentSetting(void)
}
}
static KEY_Code_t edit_last_key = 255;
static uint8_t edit_char_index = 0;
static const char* const char_map[10] = {
" 0", // KEY_0
".,-()@/\\+=*#<>[]~1", // KEY_1
"abc2", // KEY_2
"def3", // KEY_3
"ghi4", // KEY_4
"jkl5", // KEY_5
"mno6", // KEY_6
"pqrs7", // KEY_7
"tuv8", // KEY_8
"wxyz9" // KEY_9
};
static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
uint8_t Offset;
@@ -1471,33 +1514,55 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
int32_t Max;
uint16_t Value = 0;
if (bKeyHeld || !bKeyPressed)
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0)
{ // currently editing the channel name
if (edit_index >= 10)
return;
if (edit_index < 10)
uint8_t key_idx = Key - KEY_0;
if (bKeyHeld)
{
if (Key <= KEY_9)
edit[edit_index] = '0' + key_idx;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (Key != edit_last_key)
{
edit_last_key = Key;
edit_char_index = 0;
}
else
{
edit_char_index++;
if (char_map[key_idx][edit_char_index] == '\0')
{
edit[edit_index] = '0' + Key - KEY_0;
if (++edit_index >= 10)
{ // exit edit
gFlagAcceptSetting = false;
gAskForConfirmation = 1;
}
gRequestDisplayScreen = DISPLAY_MENU;
edit_char_index = 0;
}
}
char c = char_map[key_idx][edit_char_index];
if (edit_is_uppercase && c >= 'a' && c <= 'z')
{
c -= 32;
}
edit[edit_index] = c;
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (bKeyHeld)
return;
INPUTBOX_Append(Key);
gRequestDisplayScreen = DISPLAY_MENU;
@@ -1586,7 +1651,7 @@ 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] * 1000) + (gInputBox[1] * 100) + (gInputBox[2] * 10) + gInputBox[3]) - 1;
Value = (((gInputBox[0] * 10 + gInputBox[1]) * 10 + gInputBox[2]) * 10 + gInputBox[3]) - 1;
if (IS_MR_CHANNEL(Value))
{
@@ -1643,6 +1708,58 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
static void MENU_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
const bool editing_name = !gCssBackgroundScan
&& UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME
&& gIsInSubMenu
&& edit_index >= 0;
if (editing_name)
{
if (!bKeyPressed)
{
if (bKeyHeld)
return; // release after a long press, keep editing
if (edit_index == 0)
goto Skip;
if (edit_index > 0)
{ // step back one character while editing the channel name
edit_index--;
edit_last_key = 255;
gAskForConfirmation = 0;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (!bKeyHeld)
{ // wait to see if the user wants a short exit or a long backspace
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
Skip:
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are editing from one of them. */
BACKLIGHT_TurnOn();
gAskForConfirmation = 0;
gIsInSubMenu = false;
gInputBoxIndex = 0;
gFlagRefreshSetting = true;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
if (bKeyHeld || !bKeyPressed)
return;
@@ -1650,29 +1767,22 @@ static void MENU_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
if (!gCssBackgroundScan)
{
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are exiting from one of them. */
BACKLIGHT_TurnOn();
if (gIsInSubMenu)
{
if (gInputBoxIndex == 0 || UI_MENU_GetCurrentMenuId() != MENU_OFFSET)
{
gAskForConfirmation = 0;
gIsInSubMenu = false;
gInputBoxIndex = 0;
gFlagRefreshSetting = true;
#ifdef ENABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
goto Skip;
}
else
{
/* Backlight related menus set full brightness. Set it back to the configured value,
just in case we are exiting from one of them. */
BACKLIGHT_TurnOn();
gInputBox[--gInputBoxIndex] = 10;
gRequestDisplayScreen = DISPLAY_MENU;
}
// ***********************
gRequestDisplayScreen = DISPLAY_MENU;
return;
}
@@ -1751,12 +1861,18 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
SETTINGS_FetchChannelName(edit, gSubMenuSelection);
// pad the channel name out with '_'
edit_index = strlen(edit);
while (edit_index < 10)
edit[edit_index++] = '_';
edit[edit_index] = 0;
// pad the channel name out with ' '
size_t len = strlen(edit);
if (len < 10)
{
memset(edit + len, ' ', 10 - len);
edit[10] = '\0';
}
edit_index = 0; // 'edit_index' is going to be used as the cursor position
edit_last_key = 255;
edit_char_index = 0;
edit_is_uppercase = false;
// make a copy so we can test for change when exiting the menu item
memcpy(edit_original, edit, sizeof(edit_original));
@@ -1766,6 +1882,7 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
else
if (edit_index >= 0 && edit_index < 10)
{ // editing the channel name characters
edit_last_key = 255;
if (++edit_index < 10)
return; // next char
@@ -1844,7 +1961,7 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld)
{
if (bKeyHeld || !bKeyPressed)
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
@@ -1854,13 +1971,8 @@ static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld)
if (edit_index < 10)
{
edit[edit_index] = '-';
if (++edit_index >= 10)
{ // exit edit
gFlagAcceptSetting = false;
gAskForConfirmation = 1;
}
edit[edit_index] = !bKeyHeld ? '-' : '*';
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
@@ -1868,6 +1980,9 @@ static void MENU_Key_STAR(const bool bKeyPressed, const bool bKeyHeld)
return;
}
if (bKeyHeld)
return;
RADIO_SelectVfos();
#ifdef ENABLE_NOAA
@@ -1902,28 +2017,6 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
Direction = -Direction;
}
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && gIsInSubMenu && edit_index >= 0)
{ // change the character
if (bKeyPressed && edit_index < 10 && Direction != 0)
{
const char unwanted[] = "$%&!\"':;?^`|{}";
char c = edit[edit_index] + Direction;
unsigned int i = 0;
while (i < sizeof(unwanted) && c >= 32 && c <= 126)
{
if (c == unwanted[i++])
{ // choose next character
c += Direction;
i = 0;
}
}
edit[edit_index] = (c < 32) ? 126 : (c > 126) ? 32 : c;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (!bKeyHeld)
{
if (!bKeyPressed)
@@ -1937,6 +2030,29 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
if (!bKeyPressed)
return;
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && gIsInSubMenu && edit_index >= 0)
{ // change the character
if (edit_index < 10 && Direction != 0)
{
const char unwanted[] = "$%&!\"':;?^`|{}_";
char c = edit[edit_index] + Direction;
unsigned int i = 0;
while (i < sizeof(unwanted) && c >= 32 && c <= 126)
{
if (c == unwanted[i++])
{ // choose next character
c += Direction;
i = 0;
}
}
edit[edit_index] = (c < 32) ? 126 : (c > 126) ? 32 : c;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
return;
}
if (SCANNER_IsScanning()) {
return;
}
@@ -2067,19 +2183,20 @@ void MENU_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
case KEY_F:
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && edit_index >= 0)
{ // currently editing the channel name
if (!bKeyHeld && bKeyPressed)
if (!bKeyPressed)
break;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (edit_index < 10)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (edit_index < 10)
{
edit[edit_index] = ' ';
if (++edit_index >= 10)
{ // exit edit
gFlagAcceptSetting = false;
gAskForConfirmation = 1;
}
gRequestDisplayScreen = DISPLAY_MENU;
}
if (bKeyHeld)
edit[edit_index] = '#';
edit_is_uppercase = !edit_is_uppercase;
edit_last_key = 255;
gRequestDisplayScreen = DISPLAY_MENU;
}
break;
}
+4 -4
View File
@@ -62,7 +62,7 @@ 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] * 1000) + (gInputBox[1] * 100) + (gInputBox[2] * 10) + gInputBox[3]) - 1;
uint16_t chan = (((gInputBox[0] * 10 + gInputBox[1]) * 10 + gInputBox[2]) * 10 + gInputBox[3]) - 1;
if (IS_MR_CHANNEL(chan)) {
#ifdef ENABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
@@ -345,7 +345,7 @@ void SCANNER_Start(bool singleFreq)
gScanFrequency = 0xFFFFFFFF;
BK4819_PickRXFilterPathBasedOnFrequency(gScanFrequency);
BK4819_EnableFrequencyScan();
BK4819_SetFrequencyScan(true);
gUpdateStatus = true;
}
@@ -418,10 +418,10 @@ void SCANNER_TimeSlice10ms(void)
else
scanHitCount = 0;
BK4819_DisableFrequencyScan();
BK4819_SetFrequencyScan(false);
if (scanHitCount < 3) {
BK4819_EnableFrequencyScan();
BK4819_SetFrequencyScan(true);
}
else {
BK4819_SetScanFrequency(gScanFrequency);
+1059 -283
View File
File diff suppressed because it is too large. Load diff
+20 -13
View File
@@ -42,7 +42,8 @@
#include <stdint.h>
#include <string.h>
static const uint8_t DrawingEndY = 40;
static const uint8_t DrawingEndY = 40;
static const uint8_t DrawingTopY = 8; // Reserve top 8px for frequency display (gFrameBuffer[0])
static const uint8_t U8RssiMap[] = {
121,
@@ -78,29 +79,35 @@ static const uint16_t scanStepValues[] = {
static const uint16_t scanStepBWRegValues[] = {
// RX RXw TX BW
// 0b0 000 000 001 01 1000
// 1
// 1 (S_STEP_0_01kHz, index 0)
0b0000000001011000, // 6.25
// 10
// 10 (S_STEP_0_1kHz, index 1)
0b0000000001011000, // 6.25
// 50
// 50 (S_STEP_0_5kHz, index 2)
0b0000000001011000, // 6.25
// 100
// 100 (S_STEP_1_0kHz, index 3)
0b0000000001011000, // 6.25
// 250
// 250 (S_STEP_2_5kHz, index 4)
0b0000000001011000, // 6.25
// 500
// 500 (S_STEP_5_0kHz, index 5)
0b0010010001011000, // 6.25
// 625
// 625 (S_STEP_6_25kHz, index 6)
0b0100100001011000, // 6.25
// 833
// 833 (S_STEP_8_33kHz, index 7)
0b0110110001001000, // 6.25
// 1000
// 1000 (S_STEP_10_0kHz, index 8)
0b0110110001001000, // 6.25
// 1250
// 1250 (S_STEP_12_5kHz, index 9)
0b0111111100001000, // 6.25
// 2500
// 1500 (S_STEP_15_0kHz, index 10)
0b0011011000101000, // 25
// 10000
// 2000 (S_STEP_20_0kHz, index 11)
0b0011011000101000, // 25
// 2500 (S_STEP_25_0kHz, index 12)
0b0011011000101000, // 25
// 5000 (S_STEP_50_0kHz, index 13)
0b0011011000101000, // 25
// 10000 (S_STEP_100_0kHz, index 14)
0b0011011000101000, // 25
};
+5 -5
View File
@@ -347,9 +347,9 @@ static void CMD_0514(uint32_t Port, const uint8_t *pBuffer)
#endif
gSerialConfigCountDown_500ms = 12; // 6 sec
// turn the LCD backlight off
BACKLIGHT_TurnOff();
if (gEeprom.BACKLIGHT_TIME < 61) // backlight is set to be always on
BACKLIGHT_TurnOff(); // turn the LCD backlight off
SendVersion(Port);
}
@@ -593,8 +593,8 @@ static void CMD_052F(uint32_t Port, const uint8_t *pBuffer)
}
#endif
// turn the LCD backlight off
BACKLIGHT_TurnOff();
if (gEeprom.BACKLIGHT_TIME < 61) // backlight is set to be always on
BACKLIGHT_TurnOff(); // turn the LCD backlight off
SendVersion(Port);
}
+2 -5
View File
@@ -48,7 +48,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
if (Beep != BEEP_880HZ_60MS_DOUBLE_BEEP &&
Beep != BEEP_500HZ_60MS_DOUBLE_BEEP &&
Beep != BEEP_440HZ_500MS &&
#ifdef ENABLE_DTMF_CALLING
Beep != BEEP_880HZ_200MS &&
Beep != BEEP_880HZ_500MS &&
@@ -95,9 +94,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
case BEEP_500HZ_60MS_DOUBLE_BEEP:
ToneFrequency = 500;
break;
case BEEP_440HZ_500MS:
ToneFrequency = 440;
break;
case BEEP_880HZ_60MS_DOUBLE_BEEP:
#ifndef ENABLE_FEAT_F4HWN
case BEEP_880HZ_200MS:
@@ -118,10 +114,12 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
#endif
}
#ifdef ENABLE_FEAT_F4HWN
if(Beep == BEEP_400HZ_30MS || Beep == BEEP_500HZ_30MS || Beep == BEEP_600HZ_30MS)
{
BK4819_WriteRegister(BK4819_REG_70, BK4819_REG_70_ENABLE_TONE1 | ((1 & 0x7f) << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
}
#endif
BK4819_PlayTone(ToneFrequency, true);
@@ -153,7 +151,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
Duration = 30;
break;
#endif
case BEEP_440HZ_500MS:
#ifndef ENABLE_FEAT_F4HWN
case BEEP_880HZ_200MS:
BK4819_ExitTxMute();
-1
View File
@@ -27,7 +27,6 @@ enum BEEP_Type_t
BEEP_NONE = 0,
BEEP_1KHZ_60MS_OPTIONAL,
BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL,
BEEP_440HZ_500MS,
#ifdef ENABLE_DTMF_CALLING
BEEP_880HZ_200MS,
BEEP_880HZ_500MS,
+33
View File
@@ -26,6 +26,12 @@ const uint16_t CTCSS_Options[50] = {
2035, 2065, 2107, 2181, 2257, 2291, 2336, 2418, 2503, 2541
};
// Indices in CTCSS_Options for the 12 non-homologated tones.
static const uint8_t CTCSS_ExtraIdx[12] = {
1, 26, 28, 30, 32, 34, 36, 38, 39, 41,
45, 49
};
const uint16_t DCS_Options[104] = {
0x0013, 0x0015, 0x0016, 0x0019, 0x001A, 0x001E, 0x0023, 0x0027,
0x0029, 0x002B, 0x002C, 0x0035, 0x0039, 0x003A, 0x003B, 0x003C,
@@ -108,3 +114,30 @@ uint8_t DCS_GetCtcssCode(int Code)
return Result;
}
uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option)
{
unsigned int i;
unsigned int extra_pos = 0;
uint8_t approved_index = 0;
if (Option >= ARRAY_SIZE(CTCSS_Options))
return 0xFF;
for (i = 0; i < ARRAY_SIZE(CTCSS_Options); i++) {
const bool is_extra = extra_pos < ARRAY_SIZE(CTCSS_ExtraIdx) &&
i == CTCSS_ExtraIdx[extra_pos];
if (is_extra) {
extra_pos++;
continue;
}
if (i == Option)
return approved_index;
approved_index++;
}
return 0xFF;
}
+1 -1
View File
@@ -40,6 +40,6 @@ extern const uint16_t DCS_Options[104];
uint32_t DCS_GetGolayCodeWord(DCS_CodeType_t CodeType, uint8_t Option);
uint8_t DCS_GetCdcssCode(uint32_t Code);
uint8_t DCS_GetCtcssCode(int Code);
uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option);
#endif
+2 -2
View File
@@ -147,11 +147,11 @@ uint8_t BK4819_GetGlitchIndicator(void);
uint8_t BK4819_GetExNoiceIndicator(void);
uint16_t BK4819_GetVoiceAmplitudeOut(void);
uint8_t BK4819_GetAfTxRx(void);
void BK4819_SetRxAudioGain(void);
bool BK4819_GetFrequencyScanResult(uint32_t *pFrequency);
BK4819_CssScanResult_t BK4819_GetCxCSSScanResult(uint32_t *pCdcssFreq, uint16_t *pCtcssFreq);
void BK4819_DisableFrequencyScan(void);
void BK4819_EnableFrequencyScan(void);
void BK4819_SetFrequencyScan(bool enable);
void BK4819_SetScanFrequency(uint32_t Frequency);
void BK4819_Disable(void);
+67 -65
View File
@@ -39,6 +39,16 @@ static const uint16_t FSK_RogerTable[7] = {0xF1A2, 0x7446, 0x61A4, 0x6544, 0x4E8
static uint16_t gBK4819_GpioOutState;
#define SHORT_DELAY() \
__asm volatile("nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n" \
"nop\n nop\n nop\n nop\n nop\n")
bool gRxIdleMode;
static inline void CS_Assert()
@@ -197,63 +207,78 @@ static uint16_t BK4819_ReadU16(void)
uint16_t Value;
SDA_SetDir(false);
SYSTICK_DelayUs(1);
SHORT_DELAY();
Value = 0;
for (i = 0; i < 16; i++)
{
Value <<= 1;
Value |= SDA_ReadInput();
SCL_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
}
SDA_SetDir(true);
return Value;
}
static uint16_t reg_30_cache = 0xFFFF;
static uint16_t reg_47_cache = 0xFFFF;
uint16_t BK4819_ReadRegister(BK4819_REGISTER_t Register)
{
uint16_t Value;
CS_Release();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
CS_Assert();
BK4819_WriteU8(Register | 0x80);
Value = BK4819_ReadU16();
CS_Release();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
SDA_Set();
if (Register == BK4819_REG_30)
reg_30_cache = Value;
else if (Register == BK4819_REG_47)
reg_47_cache = Value;
return Value;
}
void BK4819_WriteRegister(BK4819_REGISTER_t Register, uint16_t Data)
{
if (Register == BK4819_REG_30)
{
if (Data == reg_30_cache)
return;
reg_30_cache = Data;
}
else if (Register == BK4819_REG_47)
{
if (Data == reg_47_cache)
return;
reg_47_cache = Data;
}
CS_Release();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
CS_Assert();
BK4819_WriteU8(Register);
SYSTICK_DelayUs(1);
SHORT_DELAY();
BK4819_WriteU16(Data);
SYSTICK_DelayUs(1);
SHORT_DELAY();
CS_Release();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
SDA_Set();
@@ -271,14 +296,14 @@ void BK4819_WriteU8(uint8_t Data)
else
SDA_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
Data <<= 1;
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
}
}
@@ -294,14 +319,14 @@ void BK4819_WriteU16(uint16_t Data)
else
SDA_Set();
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Set();
Data <<= 1;
SYSTICK_DelayUs(1);
SHORT_DELAY();
SCL_Reset();
SYSTICK_DelayUs(1);
SHORT_DELAY();
}
}
@@ -1432,24 +1457,15 @@ void BK4819_GenTail(uint8_t Tail)
// freq(Hz) * 20.64888 for XTAL 13M/26M or
// freq(Hz)*20.97152 for XTAL 12.8M/19.2M/25.6M/38.4M
switch (Tail)
{
case 0: // 134.4Hz CTCSS Tail
BK4819_WriteRegister(BK4819_REG_52, 0x828F); // 1 00 0 001010 001111
break;
case 1: // 120° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0xA28F); // 1 01 0 001010 001111
break;
case 2: // 180° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0xC28F); // 1 10 0 001010 001111
break;
case 3: // 240° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0xE28F); // 1 11 0 001010 001111
break;
case 4: // 55Hz tone freq
BK4819_WriteRegister(BK4819_REG_07, 0x046f); // 0 00 0 010001 101111
break;
}
if (Tail <= 3)
// 0: 134.4Hz CTCSS Tail
// 1: 120° phase shift
// 2: 180° phase shift
// 3: 240° phase shift
BK4819_WriteRegister(BK4819_REG_52, 0x028F | (Tail << 13));
else if (Tail == 4)
// 4: 55Hz tone freq
BK4819_WriteRegister(BK4819_REG_07, 0x046F);
}
void BK4819_PlayCDCSSTail(void)
@@ -1531,6 +1547,14 @@ uint8_t BK4819_GetAfTxRx(void)
return BK4819_ReadRegister(BK4819_REG_6F) & 0x003F;
}
void BK4819_SetRxAudioGain(void) {
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
}
bool BK4819_GetFrequencyScanResult(uint32_t *pFrequency)
{
const uint16_t High = BK4819_ReadRegister(BK4819_REG_0D);
@@ -1566,7 +1590,7 @@ BK4819_CssScanResult_t BK4819_GetCxCSSScanResult(uint32_t *pCdcssFreq, uint16_t
return BK4819_CSS_RESULT_NOT_FOUND;
}
void BK4819_DisableFrequencyScan(void)
void BK4819_SetFrequencyScan(bool enable)
{
// REG_32
//
@@ -1582,32 +1606,10 @@ void BK4819_DisableFrequencyScan(void)
// 1 = enable
// 0 = disable
//
BK4819_WriteRegister(BK4819_REG_32, // 0x0244); // 00 0000100100010 0
BK4819_WriteRegister(BK4819_REG_32,
( 0u << 14) | // 0 frequency scan Time
(290u << 1) | // ???
( 0u << 0)); // 0 frequency scan enable
}
void BK4819_EnableFrequencyScan(void)
{
// REG_32
//
// <15:14> 0 frequency scan time
// 0 = 0.2 sec
// 1 = 0.4 sec
// 2 = 0.8 sec
// 3 = 1.6 sec
//
// <13:1> ???
//
// <0> 0 frequency scan enable
// 1 = enable
// 0 = disable
//
BK4819_WriteRegister(BK4819_REG_32, // 0x0245); // 00 0000100100010 1
( 0u << 14) | // 0 frequency scan time
(290u << 1) | // ???
( 1u << 0)); // 1 frequency scan enable
(enable ? 1u : 0u)); // frequency scan (1: enable | 0: disable)
}
void BK4819_SetScanFrequency(uint32_t Frequency)
@@ -1671,7 +1673,7 @@ void BK4819_Disable(void)
void BK4819_StopScan(void)
{
BK4819_DisableFrequencyScan();
BK4819_SetFrequencyScan(false);
BK4819_Disable();
}
+1 -1
View File
@@ -362,7 +362,7 @@ const uint8_t gFontSmall[95-1][6] =
{0x20, 0x40, 0x40, 0x3D, 0x00, 0x00}, // 'j'
{0x00, 0x7F, 0x10, 0x28, 0x44, 0x00}, // 'k'
{0x00, 0x00, 0x3F, 0x40, 0x00, 0x00}, // 'l'
{0x7C, 0x08, 0x10, 0x10, 0x08, 0x7C}, // 'm'
{0x7C, 0x04, 0x7C, 0x04, 0x78, 0x00}, // 'm'
{0x7C, 0x04, 0x04, 0x04, 0x78, 0x00}, // 'n'
{0x38, 0x44, 0x44, 0x44, 0x38, 0x00}, // 'o'
{0x7C, 0x14, 0x14, 0x14, 0x08, 0x00}, // 'p'
+1 -5
View File
@@ -157,11 +157,7 @@ void Main(void)
#ifdef ENABLE_FEAT_F4HWN
gEeprom.KEY_LOCK = 0;
SETTINGS_SaveSettings();
gMenuCursor = MENU_ITEMS;
#ifdef ENABLE_NOAA
gMenuCursor += 1; // move to hidden section, fix me if change... !!!
#endif
gMenuCursor = UI_MENU_GetMenuIdx(FIRST_HIDDEN_MENU_ITEM);
gSubMenuSelection = gSetting_F_LOCK;
#endif
}
+1 -3
View File
@@ -39,8 +39,6 @@
#define FM_CHANNELS_MAX 48
#define MR_CHANNELS_MAX 1024
#define MR_CHANNELS_LIST 24
#define MENU_ITEMS 69
// CACHE-BASED OPTIMIZATION: Only keep active channels in RAM
// Full array stays in EEPROM, cache holds ~10 most-used channels
#define MR_CHANNELS_CACHE_SIZE 10
@@ -472,4 +470,4 @@ void FUNCTION_NOP();
static inline bool SerialConfigInProgress() { return gSerialConfigCountDown_500ms != 0; }
#endif
#endif
+102 -173
View File
@@ -70,64 +70,45 @@ const char gModulationStr[MODULATION_UKNOWN][4] = {
// Audio impression: fuller bass, more direct sound, while still keeping the 3 kHz top-end limit.
static void AUDIO_ApplyFMProfile(uint8_t profile)
{
switch (profile)
{
default:
case 0: // FLAT
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x3200);
break;
{ // | 0x54 || 0x55 |
static const uint16_t fm_profiles[][2] = {
{0x9009, 0x3200}, // 0: FLAT
{0x9009, 0x33A9}, // 1: CLEAN
{0x9009, 0x3600}, // 2: MID
{0x8546, 0x3AF0}, // 3: BOOST
{0x8566, 0x3D00} // 4: MAX
};
case 1: // CLEAN
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x33A9);
break;
if (profile >= ARRAY_SIZE(fm_profiles))
profile = 0;
case 2: // MID
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x3600);
break;
case 3: // BOOST
BK4819_WriteRegister(0x54, 0x8546);
BK4819_WriteRegister(0x55, 0x3AF0);
break;
case 4: // MAX
BK4819_WriteRegister(0x54, 0x8566);
BK4819_WriteRegister(0x55, 0x3D00);
break;
}
BK4819_WriteRegister(0x54, fm_profiles[profile][0]);
BK4819_WriteRegister(0x55, fm_profiles[profile][1]);
}
static void AUDIO_ApplyAMProfile(uint8_t profile)
{
switch (profile)
{
default:
case 0: // SHARP (ALPHA test profile) - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), low IF gain (REG55 bits[11:8]=1, ref=169)
// Selective and crisp, best adjacent channel rejection, may sound harsh on strong signals
BK4819_WriteRegister(0x2b, 0x0300);
BK4819_WriteRegister(0x2f, 0x9990);
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31A9);
break;
case 1: // STOCK - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), moderate IF gain (REG55 bits[11:8]=4, ref=180)
// Selective filter with balanced gain, punchy and detailed, good compromise between rejection and sensitivity
BK4819_WriteRegister(0x2b, 0x0500);
BK4819_WriteRegister(0x2f, 0x9990);
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31A9);
break;
case 2: // OPEN (BRAVO test profile) - Medium-wide IF filter (REG54 bits[14:8]=8, bits[7:0]=70), high IF gain (REG55 bits[11:8]=8, ref=192)
// Wide and pleasant, better sensitivity on weak signals, may struggle with adjacent channel interference
BK4819_WriteRegister(0x2b, 0x0300);
BK4819_WriteRegister(0x2f, 0x9990);
BK4819_WriteRegister(0x54, 0x8846);
BK4819_WriteRegister(0x55, 0x38C0);
break;
}
{ // | 0x2b || 0x2f || 0x54 || 0x55 |
static const uint16_t am_profiles[][4] = {
// SHARP (ALPHA test profile) - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), low IF gain (REG55 bits[11:8]=1, ref=169)
// Selective and crisp, best adjacent channel rejection, may sound harsh on strong signals
{0x0300, 0x9990, 0x9009, 0x31A9},
// STOCK - Narrow IF filter (REG54 bits[14:8]=0, bits[7:0]=9), moderate IF gain (REG55 bits[11:8]=4, ref=180)
// Selective filter with balanced gain, punchy and detailed, good compromise between rejection and sensitivity
{0x0500, 0x9990, 0x9009, 0x31A9},
// OPEN (BRAVO test profile) - Medium-wide IF filter (REG54 bits[14:8]=8, bits[7:0]=70), high IF gain (REG55 bits[11:8]=8, ref=192)
// Wide and pleasant, better sensitivity on weak signals, may struggle with adjacent channel interference
{0x0300, 0x9990, 0x8846, 0x38C0}
};
if (profile >= ARRAY_SIZE(am_profiles))
profile = 0;
BK4819_WriteRegister(0x2b, am_profiles[profile][0]);
BK4819_WriteRegister(0x2f, am_profiles[profile][1]);
BK4819_WriteRegister(0x54, am_profiles[profile][2]);
BK4819_WriteRegister(0x55, am_profiles[profile][3]);
}
static void AUDIO_ApplyUSBProfile(void)
@@ -252,6 +233,24 @@ void RADIO_InitInfo(VFO_Info_t *pInfo, const uint16_t ChannelSave, const uint32_
RADIO_ConfigureSquelchAndOutputPower(pInfo);
}
void RADIO_ValidateAndSetCode(FREQ_Config_t *pFreq_Config, uint8_t tmp) {
switch (pFreq_Config->CodeType) {
default:
case CODE_TYPE_OFF:
pFreq_Config->CodeType = CODE_TYPE_OFF;
tmp = 0;
break;
case CODE_TYPE_CONTINUOUS_TONE:
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
if (tmp > ((pFreq_Config->CodeType == CODE_TYPE_CONTINUOUS_TONE ? ARRAY_SIZE(CTCSS_Options) : ARRAY_SIZE(DCS_Options)) - 1))
tmp = 0;
break;
}
pFreq_Config->Code = tmp;
}
void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure)
{
VFO_Info_t *pVfo = &gEeprom.VfoInfo[VFO];
@@ -325,7 +324,7 @@ void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure
}
pVfo->Band = band;
pVfo->SCANLIST_PARTICIPATION = bParticipation;
pVfo->SCANLIST_PARTICIPATION = bParticipation;
pVfo->CHANNEL_SAVE = channel;
uint32_t base;
@@ -370,49 +369,8 @@ void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure
pVfo->freq_config_RX.CodeType = (data[2] >> 0) & 0x0F;
pVfo->freq_config_TX.CodeType = (data[2] >> 4) & 0x0F;
tmp = data[0];
switch (pVfo->freq_config_RX.CodeType)
{
default:
case CODE_TYPE_OFF:
pVfo->freq_config_RX.CodeType = CODE_TYPE_OFF;
tmp = 0;
break;
case CODE_TYPE_CONTINUOUS_TONE:
if (tmp > (ARRAY_SIZE(CTCSS_Options) - 1))
tmp = 0;
break;
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
if (tmp > (ARRAY_SIZE(DCS_Options) - 1))
tmp = 0;
break;
}
pVfo->freq_config_RX.Code = tmp;
tmp = data[1];
switch (pVfo->freq_config_TX.CodeType)
{
default:
case CODE_TYPE_OFF:
pVfo->freq_config_TX.CodeType = CODE_TYPE_OFF;
tmp = 0;
break;
case CODE_TYPE_CONTINUOUS_TONE:
if (tmp > (ARRAY_SIZE(CTCSS_Options) - 1))
tmp = 0;
break;
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
if (tmp > (ARRAY_SIZE(DCS_Options) - 1))
tmp = 0;
break;
}
pVfo->freq_config_TX.Code = tmp;
RADIO_ValidateAndSetCode(&pVfo->freq_config_RX, data[0]);
RADIO_ValidateAndSetCode(&pVfo->freq_config_TX, data[1]);
if (data[4] == 0xFF)
{
@@ -624,30 +582,15 @@ void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
uint8_t Op = 0; // Low eeprom calibration data
uint8_t currentPower = pInfo->OUTPUT_POWER;
if(currentPower == OUTPUT_POWER_USER)
{
if(gSetting_set_pwr == 5)
{
Op = 1; // Mid eeprom calibration data
}
else if(gSetting_set_pwr == 6)
{
Op = 2; // High eeprom calibration data
}
if (currentPower == OUTPUT_POWER_USER)
currentPower = gSetting_set_pwr;
}
else
{
if (currentPower == OUTPUT_POWER_MID)
{
Op = 1; // Mid eeprom calibration data
}
else if(currentPower == OUTPUT_POWER_HIGH)
{
Op = 2; // High eeprom calibration data
}
currentPower--;
}
if (currentPower == 5)
Op = 1; // Mid eeprom calibration data
else if (currentPower == 6)
Op = 2; // High eeprom calibration data
PY25Q16_ReadBuffer(0x100D0 + (Band * 16) + (Op * 3), Txp, 3);
@@ -790,6 +733,12 @@ void RADIO_SelectVfos(void)
RADIO_SelectCurrentVfo();
}
BK4819_FilterBandwidth_t RADIO_GetAMFilterBandwidth(const VFO_Info_t *pVfo)
{
// On BK4829, AM "wide" intentionally reuses the wider RF filter preset.
return (pVfo->CHANNEL_BANDWIDTH == BANDWIDTH_WIDE) ? BK4819_FILTER_BW_WIDE : BK4819_FILTER_BW_AM;
}
void RADIO_SetupRegisters(bool switchToForeground)
{
BK4819_FilterBandwidth_t Bandwidth = gRxVfo->CHANNEL_BANDWIDTH;
@@ -808,7 +757,7 @@ void RADIO_SetupRegisters(bool switchToForeground)
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
if (gRxVfo->Modulation == MODULATION_AM)
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_AM, true);
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
else
{
switch (Bandwidth)
@@ -872,12 +821,7 @@ void RADIO_SetupRegisters(bool switchToForeground)
// AF RX Gain and DAC
//BK4819_WriteRegister(BK4819_REG_48, 0xB3A8); // 1011 00 111010 1000
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
uint16_t InterruptMask = BK4819_REG_3F_SQUELCH_FOUND | BK4819_REG_3F_SQUELCH_LOST;
@@ -1107,26 +1051,25 @@ void RADIO_SetTxParameters(void)
void RADIO_SetModulation(ModulationMode_t modulation)
{
#ifdef ENABLE_BYP_RAW_DEMODULATORS
// BYP on BK4829 uses full audio bypass profile.
if (modulation == MODULATION_BYP) {
BK4819_EnterBypass();
if (modulation == MODULATION_BYP || modulation == MODULATION_RAW) {
uint16_t reg_3d_val = 0x0000;
if (modulation == MODULATION_BYP) {
// BYP on BK4829 uses full audio bypass profile.
BK4819_EnterBypass();
reg_3d_val = 0x2AAB;
} else {
// RAW on BK4829 uses RX-only filter bypass profile.
BK4819_EnterRaw();
// reg_3d_val = 0x0000;
}
BK4819_SetRegValue(afDacGainRegSpec, 0xF);
BK4819_WriteRegister(BK4819_REG_3D, 0x2AAB);
BK4819_WriteRegister(BK4819_REG_3D, reg_3d_val);
RADIO_SetupAGC(false, false);
return;
}
// RAW on BK4829 uses RX-only filter bypass profile.
if (modulation == MODULATION_RAW) {
BK4819_EnterRaw();
BK4819_SetRegValue(afDacGainRegSpec, 0xF);
BK4819_WriteRegister(BK4819_REG_3D, 0x0000);
RADIO_SetupAGC(false, false);
return;
}
#endif
#ifdef ENABLE_BYP_RAW_DEMODULATORS
// Ensure we always leave bypass / raw mode before applying normal modulation settings.
BK4819_ExitBypass();
#endif
@@ -1172,28 +1115,11 @@ void RADIO_SetModulation(ModulationMode_t modulation)
BK4819_WriteRegister(0x55, 0x31a9);
#endif
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_AM, true);
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gCurrentVfo), true);
break;
}
case MODULATION_USB:
{
uint16_t uVar1 = BK4819_ReadRegister(0x31);
BK4819_WriteRegister(0x31, uVar1 & 0xfffe); // AM Demodulation Disable
BK4819_WriteRegister(0x42, 0x6b5a);
BK4819_WriteRegister(0x2a, 0x7400);
BK4819_WriteRegister(0x2b, 0x0000);
BK4819_WriteRegister(0x2f, 0x9890);
#ifdef ENABLE_FEAT_F4HWN_AUDIO
AUDIO_ApplyUSBProfile();
#else
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31a9);
#endif
break;
}
case MODULATION_FM:
default:
{
@@ -1205,7 +1131,10 @@ void RADIO_SetModulation(ModulationMode_t modulation)
BK4819_WriteRegister(0x2f, 0x9890);
#ifdef ENABLE_FEAT_F4HWN_AUDIO
AUDIO_ApplyFMProfile(gSetting_set_audio_fm);
if (modulation == MODULATION_USB)
AUDIO_ApplyUSBProfile();
else
AUDIO_ApplyFMProfile(gSetting_set_audio_fm);
#else
BK4819_WriteRegister(0x54, 0x9009);
BK4819_WriteRegister(0x55, 0x31a9);
@@ -1397,17 +1326,19 @@ void RADIO_PrepareTX(void)
void RADIO_SendCssTail(void)
{
switch (gCurrentVfo->pTX->CodeType) {
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_PlayCDCSSTail();
break;
default:
BK4819_PlayCTCSSTail();
break;
}
if (gEeprom.TAIL_TONE_ELIMINATION) {
switch (gCurrentVfo->pTX->CodeType) {
case CODE_TYPE_DIGITAL:
case CODE_TYPE_REVERSE_DIGITAL:
BK4819_PlayCDCSSTail();
break;
default:
BK4819_PlayCTCSSTail();
break;
}
SYSTEM_DelayMs(200);
SYSTEM_DelayMs(200);
}
}
void RADIO_SendEndOfTransmission(void)
@@ -1416,8 +1347,7 @@ void RADIO_SendEndOfTransmission(void)
DTMF_SendEndOfTransmission();
// send the CTCSS/DCS tail tone - allows the receivers to mute the usual FM squelch tail/crash
if(gEeprom.TAIL_TONE_ELIMINATION)
RADIO_SendCssTail();
RADIO_SendCssTail();
RADIO_SetupRegisters(false);
}
@@ -1427,7 +1357,6 @@ void RADIO_PrepareCssTX(void)
SYSTEM_DelayMs(200);
if(gEeprom.TAIL_TONE_ELIMINATION)
RADIO_SendCssTail();
RADIO_SendCssTail();
RADIO_SetupRegisters(true);
}
+2
View File
@@ -21,6 +21,7 @@
#include <stdint.h>
#include "dcs.h"
#include "driver/bk4819.h"
#include "frequencies.h"
enum {
@@ -161,6 +162,7 @@ void RADIO_SetupRegisters(bool switchToForeground);
#ifdef ENABLE_NOAA
void RADIO_ConfigureNOAA(void);
#endif
BK4819_FilterBandwidth_t RADIO_GetAMFilterBandwidth(const VFO_Info_t *pVfo);
void RADIO_SetTxParameters(void);
void RADIO_SetupAGC(bool listeningAM, bool disable);
void RADIO_SetModulation(ModulationMode_t modulation);
+28 -34
View File
@@ -30,6 +30,23 @@
EEPROM_Config_t gEeprom = { 0 };
// Load a DTMF code from EEPROM, falling back to default_val if invalid.
static void SETTINGS_LoadEepromDtmf(uint32_t addr, char *dest, size_t size, const char *default_val)
{
uint8_t buf[16];
if (size > sizeof(buf))
size = sizeof(buf);
PY25Q16_ReadBuffer(addr, buf, size);
if (DTMF_ValidateCodes((char *)buf, size)) {
memcpy(dest, buf, size);
} else {
strcpy(dest, default_val);
}
}
void SETTINGS_InitEEPROM(void)
{
uint8_t Data[16] = {0};
@@ -218,9 +235,12 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
gEeprom.FM_Band = fmCfg.band;
//gEeprom.FM_Space = fmCfg.space;
uint16_t freqLoLimit = BK1080_GetFreqLoLimit(gEeprom.FM_Band);
gEeprom.FM_SelectedFrequency =
(fmCfg.selFreq >= BK1080_GetFreqLoLimit(gEeprom.FM_Band) && fmCfg.selFreq <= BK1080_GetFreqHiLimit(gEeprom.FM_Band)) ?
fmCfg.selFreq : BK1080_GetFreqLoLimit(gEeprom.FM_Band);
(fmCfg.selFreq >= freqLoLimit && fmCfg.selFreq <= BK1080_GetFreqHiLimit(gEeprom.FM_Band)) ?
fmCfg.selFreq : freqLoLimit;
gEeprom.FM_SelectedChannel = fmCfg.selChn;
gEeprom.FM_IsMrMode = fmCfg.isMrMode;
@@ -294,51 +314,25 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
PY25Q16_ReadBuffer(0x00A0A8 + 0x48, Data, 8);
gEeprom.DTMF_CODE_PERSIST_TIME = (Data[0] < 101) ? Data[0] * 10 : 100;
gEeprom.DTMF_CODE_INTERVAL_TIME = (Data[1] < 101) ? Data[1] * 10 : 100;
#ifdef ENABLE_DTMF_CALLING
gEeprom.PERMIT_REMOTE_KILL = (Data[2] < 2) ? Data[2] : true;
// 0EE0..0EE7
PY25Q16_ReadBuffer(0x00A0F8, Data, sizeof(gEeprom.ANI_DTMF_ID));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.ANI_DTMF_ID))) {
memcpy(gEeprom.ANI_DTMF_ID, Data, sizeof(gEeprom.ANI_DTMF_ID));
} else {
strcpy(gEeprom.ANI_DTMF_ID, "123");
}
SETTINGS_LoadEepromDtmf(0x00A0F8, gEeprom.ANI_DTMF_ID, sizeof(gEeprom.ANI_DTMF_ID), "123");
// 0EE8..0EEF
PY25Q16_ReadBuffer(0x00A0F8 + 0x8, Data, sizeof(gEeprom.KILL_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.KILL_CODE))) {
memcpy(gEeprom.KILL_CODE, Data, sizeof(gEeprom.KILL_CODE));
} else {
strcpy(gEeprom.KILL_CODE, "ABCD9");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x08, gEeprom.KILL_CODE, sizeof(gEeprom.KILL_CODE), "ABCD9");
// 0EF0..0EF7
PY25Q16_ReadBuffer(0x00A0F8 + 0x10, Data, sizeof(gEeprom.REVIVE_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.REVIVE_CODE))) {
memcpy(gEeprom.REVIVE_CODE, Data, sizeof(gEeprom.REVIVE_CODE));
} else {
strcpy(gEeprom.REVIVE_CODE, "9DCBA");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x10, gEeprom.REVIVE_CODE, sizeof(gEeprom.REVIVE_CODE), "9DCBA");
#endif
// 0EF8..0F07
PY25Q16_ReadBuffer(0x00A0F8 + 0x18, Data, sizeof(gEeprom.DTMF_UP_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.DTMF_UP_CODE))) {
memcpy(gEeprom.DTMF_UP_CODE, Data, sizeof(gEeprom.DTMF_UP_CODE));
} else {
strcpy(gEeprom.DTMF_UP_CODE, "12345");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x18, gEeprom.DTMF_UP_CODE, sizeof(gEeprom.DTMF_UP_CODE), "12345");
// 0F08..0F17
PY25Q16_ReadBuffer(0x00A0F8 + 0x28, Data, sizeof(gEeprom.DTMF_DOWN_CODE));
if (DTMF_ValidateCodes((char *)Data, sizeof(gEeprom.DTMF_DOWN_CODE))) {
memcpy(gEeprom.DTMF_DOWN_CODE, Data, sizeof(gEeprom.DTMF_DOWN_CODE));
} else {
strcpy(gEeprom.DTMF_DOWN_CODE, "54321");
}
SETTINGS_LoadEepromDtmf(0x00A0F8 + 0x28, gEeprom.DTMF_DOWN_CODE, sizeof(gEeprom.DTMF_DOWN_CODE), "54321");
// 0F18..0F1F
PY25Q16_ReadBuffer(0x00A130, Data, 8);
+15
View File
@@ -41,4 +41,19 @@ const char* INPUTBOX_GetAscii()
inputBoxAscii[i] = (c==10)? '-' : '0' + c;
}
return inputBoxAscii;
}
const char* INPUTBOX_GetAsciiAlignRight() {
int targetPos = 7;
for (int i = 7; i >= 0; i--) {
if (gInputBox[i] != 10) {
inputBoxAscii[targetPos--] = '0' + gInputBox[i];
}
}
while(targetPos >= 0)
inputBoxAscii[targetPos--] = '-';
return inputBoxAscii;
}
+2
View File
@@ -25,7 +25,9 @@ extern char gInputBox[8];
extern uint8_t gInputBoxIndex;
void INPUTBOX_Append(const KEY_Code_t Digit);
const char* INPUTBOX_GetAscii();
const char* INPUTBOX_GetAsciiAlignRight();
#endif
+474 -41
View File
@@ -60,6 +60,30 @@ center_line_t center_line = CENTER_LINE_NONE;
}
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
#define SCAN_PROGRESS_MR_CHANNEL_BYTES ((MR_CHANNELS_MAX + 7u) / 8u)
static bool gScanProgressSessionActive;
static bool gScanProgressSessionIsMemory;
static uint8_t gScanProgressSessionScanList;
static uint32_t gScanProgressSessionRangeStart;
static uint32_t gScanProgressSessionRangeStop;
static uint32_t gScanProgressSessionStep;
static uint16_t gScanProgressMemoryTotal;
static uint8_t gScanProgressMemoryMap[SCAN_PROGRESS_MR_CHANNEL_BYTES];
static uint8_t gScanProgressMemoryExcludeOrdinalMap[SCAN_PROGRESS_MR_CHANNEL_BYTES];
static bool gScanProgressPrevResetVfosFlag;
static bool gScanProgressForceRebuild;
static uint16_t gScanProgressLastMemoryIndex;
static uint8_t gScanProgressPriorityState;
#define SCAN_PROGRESS_PRIORITY_LABEL_MASK 0x03u
#define SCAN_PROGRESS_PRIORITY_SEEN_SHIFT 2
#define SCAN_PROGRESS_PRIORITY_SEEN_MASK 0x1cu
#define SCAN_PROGRESS_PRIORITY_HOLD_SHIFT 5
#define SCAN_PROGRESS_PRIORITY_HOLD_MASK 0xe0u
#define SCAN_PROGRESS_PRIORITY_HOLD_FRAMES 6
#endif
const char *VfoStateStr[] = {
[VFO_STATE_NORMAL]="",
[VFO_STATE_BUSY]="BUSY",
@@ -70,6 +94,420 @@ const char *VfoStateStr[] = {
[VFO_STATE_VOLTAGE_HIGH]="VOLT HIGH"
};
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
static void ScanProgress_ResetSession(void)
{
gScanProgressSessionActive = false;
gScanProgressMemoryTotal = 0;
gScanProgressPrevResetVfosFlag = false;
gScanProgressForceRebuild = false;
gScanProgressLastMemoryIndex = 0;
gScanProgressPriorityState = 0;
}
void UI_MAIN_NotifyScanProgressDataChanged(void)
{
gScanProgressForceRebuild = true;
}
static inline void ScanProgress_SetBit(uint8_t *map, uint16_t ch)
{
map[ch >> 3] |= (uint8_t)(1u << (ch & 7));
}
static inline bool ScanProgress_GetBit(const uint8_t *map, uint16_t ch)
{
return ((map[ch >> 3] >> (ch & 7)) & 1u) != 0;
}
static uint8_t ScanProgress_GetActiveScanList(void)
{
const uint8_t max_scan_list = MR_CHANNELS_LIST + 1;
uint8_t scan_list = gEeprom.SCAN_LIST_DEFAULT;
if (scan_list == 0 || scan_list > max_scan_list)
scan_list = max_scan_list;
return scan_list;
}
static bool ScanProgress_ChannelBelongsToList(uint16_t channel, const ChannelAttributes_t *att, uint8_t scan_list)
{
if (!IS_MR_CHANNEL(channel))
return false;
if (att->band > BAND7_470MHz)
return false;
if (scan_list > MR_CHANNELS_LIST && att->scanlist != 0)
return true;
if (scan_list > 0 && att->scanlist == (MR_CHANNELS_LIST + 1))
return true;
if (scan_list == 0 || scan_list != att->scanlist)
return false;
if (gEeprom.SCAN_LIST_ENABLED) {
const uint16_t priority1 = gEeprom.SCANLIST_PRIORITY_CH[0];
const uint16_t priority2 = gEeprom.SCANLIST_PRIORITY_CH[1];
if (priority1 == channel || priority2 == channel)
return false;
}
return true;
}
static void ScanProgress_RebuildMemoryMap(uint8_t scan_list)
{
uint16_t ordinal = 0;
memset(gScanProgressMemoryMap, 0, sizeof(gScanProgressMemoryMap));
memset(gScanProgressMemoryExcludeOrdinalMap, 0, sizeof(gScanProgressMemoryExcludeOrdinalMap));
gScanProgressMemoryTotal = 0;
for (uint16_t ch = MR_CHANNEL_FIRST; IS_MR_CHANNEL(ch); ch++) {
const ChannelAttributes_t *att = MR_GetChannelAttributes(ch);
if (att == NULL || !ScanProgress_ChannelBelongsToList(ch, att, scan_list))
continue;
ScanProgress_SetBit(gScanProgressMemoryMap, ch);
ordinal++;
if (att->exclude) {
ScanProgress_SetBit(gScanProgressMemoryExcludeOrdinalMap, (uint16_t)(ordinal - 1));
}
gScanProgressMemoryTotal++;
}
}
static uint16_t ScanProgress_GetMemoryOrdinal(uint16_t channel)
{
uint16_t ordinal = 0;
for (uint16_t ch = MR_CHANNEL_FIRST; IS_MR_CHANNEL(ch); ch++) {
if (!ScanProgress_GetBit(gScanProgressMemoryMap, ch))
continue;
ordinal++;
if (ch == channel)
return ordinal;
}
return 0;
}
static bool ScanProgress_IsForward(void)
{
return gScanStateDir != SCAN_REV;
}
static bool ScanProgress_BucketHasExcludedOrdinal(uint32_t first_ordinal, uint32_t last_ordinal)
{
if (first_ordinal == 0)
first_ordinal = 1;
if (last_ordinal > gScanProgressMemoryTotal)
last_ordinal = gScanProgressMemoryTotal;
if (first_ordinal > last_ordinal)
return false;
for (uint32_t ordinal = first_ordinal; ordinal <= last_ordinal; ordinal++) {
if (ScanProgress_GetBit(gScanProgressMemoryExcludeOrdinalMap, (uint16_t)(ordinal - 1)))
return true;
}
return false;
}
static void ScanProgress_DrawGaugeLine(uint8_t line, uint32_t current_index, uint32_t total, uint8_t width, bool memory_mode, uint8_t extra_left_offset)
{
const bool forward = ScanProgress_IsForward();
const uint8_t gauge_left = (uint8_t)(width * 8 + 9 + extra_left_offset);
const uint8_t gauge_right = 126;
const uint8_t fill_start = gauge_left + 2;
const uint8_t fill_end = gauge_right - 2;
const uint8_t fill_cols = fill_end - fill_start + 1;
uint32_t head_col;
if (total == 0)
total = 1;
if (current_index == 0)
current_index = 1;
else if (current_index > total)
current_index = total;
head_col = (total <= 1) ? (fill_cols - 1) : ((current_index - 1) * (fill_cols - 1)) / (total - 1);
if (head_col >= fill_cols)
head_col = fill_cols - 1;
gFrameBuffer[line][gauge_left] = 0x0c;
gFrameBuffer[line][gauge_left + 1] = 0x12;
gFrameBuffer[line][gauge_right - 1] = 0x12;
gFrameBuffer[line][gauge_right] = 0x0c;
for (uint8_t col = 0; col < fill_cols; col++) {
const uint32_t first_ordinal = ((uint32_t)col * total) / fill_cols + 1;
uint32_t last_ordinal = ((uint32_t)(col + 1) * total) / fill_cols;
const bool processed = forward ? (col <= head_col) : (col >= head_col);
bool excluded = false;
uint8_t pixel = 0x21;
if (last_ordinal < first_ordinal)
last_ordinal = first_ordinal;
if (memory_mode)
excluded = ScanProgress_BucketHasExcludedOrdinal(first_ordinal, last_ordinal);
if (processed && !excluded) {
pixel = 0x2d;
} else if (excluded) {
pixel = 0x21;
}
gFrameBuffer[line][fill_start + col] = pixel;
}
}
static uint8_t ScanProgress_DecimalDigits(uint32_t value)
{
uint8_t digits = 1;
while (value >= 10u) {
value /= 10u;
digits++;
}
return digits;
}
static void ScanProgress_FormatIndex(char *out, size_t out_size, uint32_t current_index, uint32_t total, uint8_t width)
{
snprintf(out, out_size, "%0*u/%u",
width, (unsigned int)current_index,
(unsigned int)total);
}
static uint8_t ScanProgress_NextPriorityLabel(uint8_t current_label, uint8_t state_mask)
{
for (uint8_t i = 0; i < 3; i++) {
current_label++;
if (current_label > 2)
current_label = 0;
if ((state_mask & (1u << current_label)) != 0)
return current_label;
}
return 0;
}
static uint8_t ScanProgress_GetPriorityLabel(void)
{
return gScanProgressPriorityState & SCAN_PROGRESS_PRIORITY_LABEL_MASK;
}
static uint8_t ScanProgress_GetPrioritySeenMask(void)
{
return (gScanProgressPriorityState & SCAN_PROGRESS_PRIORITY_SEEN_MASK) >> SCAN_PROGRESS_PRIORITY_SEEN_SHIFT;
}
static uint8_t ScanProgress_GetPriorityHoldFrames(void)
{
return (gScanProgressPriorityState & SCAN_PROGRESS_PRIORITY_HOLD_MASK) >> SCAN_PROGRESS_PRIORITY_HOLD_SHIFT;
}
static void ScanProgress_SetPriorityFields(uint8_t label, uint8_t seen_mask, uint8_t hold_frames)
{
gScanProgressPriorityState =
(uint8_t)(label & SCAN_PROGRESS_PRIORITY_LABEL_MASK) |
(uint8_t)((seen_mask << SCAN_PROGRESS_PRIORITY_SEEN_SHIFT) & SCAN_PROGRESS_PRIORITY_SEEN_MASK) |
(uint8_t)((hold_frames << SCAN_PROGRESS_PRIORITY_HOLD_SHIFT) & SCAN_PROGRESS_PRIORITY_HOLD_MASK);
}
static bool ScanProgress_BuildRangeIndex(uint32_t *current_index_out, uint32_t *total_out)
{
#ifdef ENABLE_SCAN_RANGES
uint32_t step = gScanProgressSessionStep ? gScanProgressSessionStep : 1;
uint32_t total = ((gScanProgressSessionRangeStop - gScanProgressSessionRangeStart) / step) + 1;
uint32_t current_freq = gRxVfo->freq_config_RX.Frequency;
uint32_t current_abs;
if (gScanProgressSessionRangeStart == 0 || gScanProgressSessionRangeStop < gScanProgressSessionRangeStart || current_index_out == NULL || total_out == NULL)
return false;
if (total == 0)
total = 1;
if (current_freq < gScanProgressSessionRangeStart)
current_freq = gScanProgressSessionRangeStart;
else if (current_freq > gScanProgressSessionRangeStop)
current_freq = gScanProgressSessionRangeStop;
current_abs = ((current_freq - gScanProgressSessionRangeStart) / step) + 1;
if (current_abs > total)
current_abs = total;
*current_index_out = current_abs;
*total_out = total;
return true;
#else
(void)current_index_out;
(void)total_out;
return false;
#endif
}
static bool UI_DrawScanProgress(void)
{
bool show_memory = IS_MR_CHANNEL(gNextMrChannel);
bool show_range = false;
bool show_priority_label = false;
uint8_t priority_now = 0;
const char *priority_label = " ";
char text[24];
uint8_t line;
uint32_t current_index = 1;
uint32_t total = 1;
#ifdef ENABLE_SCAN_RANGES
show_range = !show_memory && gScanRangeStart != 0;
#endif
if (!show_memory && !show_range) {
ScanProgress_ResetSession();
return false;
}
if (show_memory) {
const uint8_t scan_list = ScanProgress_GetActiveScanList();
const bool reset_vfos_edge = gFlagResetVfos && !gScanProgressPrevResetVfosFlag;
const bool force_rebuild = !gScanProgressSessionActive ||
!gScanProgressSessionIsMemory ||
gScanProgressSessionScanList != scan_list ||
reset_vfos_edge ||
gScanProgressForceRebuild;
gScanProgressPrevResetVfosFlag = gFlagResetVfos;
if (force_rebuild) {
gScanProgressSessionActive = true;
gScanProgressSessionIsMemory = true;
gScanProgressSessionScanList = scan_list;
ScanProgress_RebuildMemoryMap(scan_list);
gScanProgressForceRebuild = false;
gScanProgressLastMemoryIndex = 0;
}
if (gScanProgressMemoryTotal == 0)
return false;
show_priority_label = gEeprom.SCAN_LIST_ENABLED &&
(gEeprom.SCANLIST_PRIORITY_CH[0] < MR_CHANNELS_MAX ||
gEeprom.SCANLIST_PRIORITY_CH[1] < MR_CHANNELS_MAX);
if (show_priority_label) {
if (gEeprom.SCANLIST_PRIORITY_CH[0] < MR_CHANNELS_MAX &&
gRxVfo->CHANNEL_SAVE == gEeprom.SCANLIST_PRIORITY_CH[0])
priority_now = 1;
else if (gEeprom.SCANLIST_PRIORITY_CH[1] < MR_CHANNELS_MAX &&
gRxVfo->CHANNEL_SAVE == gEeprom.SCANLIST_PRIORITY_CH[1])
priority_now = 2;
}
current_index = ScanProgress_GetMemoryOrdinal(gRxVfo->CHANNEL_SAVE);
if (priority_now != 0 || current_index == 0) {
// Keep the last known normal index during priority channel hops so
// the gauge does not flicker between the list and priority slots.
current_index = gScanProgressLastMemoryIndex ? gScanProgressLastMemoryIndex : 1;
} else {
gScanProgressLastMemoryIndex = current_index;
}
total = gScanProgressMemoryTotal;
} else {
#ifdef ENABLE_SCAN_RANGES
const uint32_t range_start = gScanRangeStart;
const uint32_t range_stop = gScanRangeStop;
const uint32_t step = gRxVfo->StepFrequency;
if (!gScanProgressSessionActive ||
gScanProgressSessionIsMemory ||
gScanProgressSessionRangeStart != range_start ||
gScanProgressSessionRangeStop != range_stop ||
gScanProgressSessionStep != step)
{
gScanProgressSessionActive = true;
gScanProgressSessionIsMemory = false;
gScanProgressSessionRangeStart = range_start;
gScanProgressSessionRangeStop = range_stop;
gScanProgressSessionStep = step;
}
if (!ScanProgress_BuildRangeIndex(&current_index, &total))
return false;
#else
return false;
#endif
}
if (show_priority_label) {
uint8_t priority_state_label = ScanProgress_GetPriorityLabel();
uint8_t priority_state_seen_mask = ScanProgress_GetPrioritySeenMask();
uint8_t priority_state_hold_frames = ScanProgress_GetPriorityHoldFrames();
priority_state_seen_mask |= (uint8_t)(1u << priority_now);
if (priority_state_hold_frames > 0)
priority_state_hold_frames--;
if (priority_state_hold_frames == 0) {
priority_state_label = ScanProgress_NextPriorityLabel(priority_state_label,
priority_state_seen_mask);
priority_state_seen_mask = 0;
priority_state_hold_frames = SCAN_PROGRESS_PRIORITY_HOLD_FRAMES;
}
ScanProgress_SetPriorityFields(priority_state_label, priority_state_seen_mask, priority_state_hold_frames);
if (priority_state_label != 0)
priority_label = (priority_state_label == 1) ? "P1" : "P2";
} else {
gScanProgressPriorityState = 0;
}
const uint8_t width = ScanProgress_DecimalDigits(total);
ScanProgress_FormatIndex(text, sizeof(text), current_index, total, width);
uint8_t extra_offset = 0;
#ifdef ENABLE_FEAT_F4HWN
line = isMainOnly() ? 5 : 3;
const uint8_t text_y = isMainOnly() ? 41 : 25;
GUI_DisplaySmallest(text, 2, text_y, false, true);
if (show_priority_label) {
const uint8_t priority_x = (uint8_t)(width * 8 + 11);
for (uint8_t x = 0; x < 7; x++)
for (uint8_t y = 0; y < 6; y++)
PutPixel(priority_x + x, text_y + y, false);
GUI_DisplaySmallest(priority_label, priority_x, text_y, false, true);
extra_offset = 11;
}
#else
line = 3;
UI_PrintStringSmallNormal(text, 2, 0, line);
#endif
ScanProgress_DrawGaugeLine(line, current_index, total, width, show_memory, extra_offset);
return true;
}
#endif
// ----------------------------------------
static void DrawSmallPowerBars(uint8_t *p, unsigned int level)
@@ -664,12 +1102,21 @@ void UI_MAIN_TimeSlice500ms(void)
// ----------------------------------------
static void UI_FormatFrequency(uint32_t freq, char *buffer) {
sprintf(buffer, "%3u.%05u", freq / 100000, freq % 100000);
}
void UI_DisplayMain(void)
{
char String[22];
center_line = CENTER_LINE_NONE;
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
if (gScanStateDir == SCAN_OFF)
ScanProgress_ResetSession();
#endif
// clear the screen
UI_DisplayClear();
@@ -752,9 +1199,9 @@ void UI_DisplayMain(void)
}
UI_PrintString("ScnRng", 5, 0, line + shift, 8);
sprintf(String, "%3u.%05u", gScanRangeStart / 100000, gScanRangeStart % 100000);
UI_FormatFrequency(gScanRangeStart, String);
UI_PrintStringSmallNormal(String, 56, 0, line + shift);
sprintf(String, "%3u.%05u", gScanRangeStop / 100000, gScanRangeStop % 100000);
UI_FormatFrequency(gScanRangeStop, String);
UI_PrintStringSmallNormal(String, 56, 0, line + shift + 1);
if (!isMainOnly())
@@ -766,9 +1213,9 @@ void UI_DisplayMain(void)
}
#else
UI_PrintString("ScnRng", 5, 0, line, 8);
sprintf(String, "%3u.%05u", gScanRangeStart / 100000, gScanRangeStart % 100000);
UI_FormatFrequency(gScanRangeStart, String);
UI_PrintStringSmallNormal(String, 56, 0, line);
sprintf(String, "%3u.%05u", gScanRangeStop / 100000, gScanRangeStop % 100000);
UI_FormatFrequency(gScanRangeStop, String);
UI_PrintStringSmallNormal(String, 56, 0, line + 1);
continue;
#endif
@@ -948,7 +1395,7 @@ void UI_DisplayMain(void)
if (!inputting || gScanStateDir != SCAN_OFF)
sprintf(String, "%04u", gEeprom.ScreenChannel[vfo_num] + 1);
else
sprintf(String, "%.4s", INPUTBOX_GetAscii()); // show the input text
sprintf(String, "%.4s", INPUTBOX_GetAsciiAlignRight() + 4); // show the input text
//if (gSetting_set_gui) {
UI_PrintStringSmallNormalInverse(String, x, 0, line + 1);
@@ -1067,29 +1514,22 @@ void UI_DisplayMain(void)
const ChannelAttributes_t* att = MR_GetChannelAttributes(gEeprom.ScreenChannel[vfo_num]);
const char *displayStr;
uint8_t xStart = 113; // 3-char name aligned left
if(att->exclude == false)
{
// show the scan list assigment symbols
const ChannelAttributes_t* att = MR_GetChannelAttributes(gEeprom.ScreenChannel[vfo_num]);
uint8_t countList = att->scanlist;
if(countList > MR_CHANNELS_LIST + 1) {
countList = 0;
}
const char *displayStr;
uint8_t xStart, xDisplay;
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
@@ -1099,41 +1539,27 @@ void UI_DisplayMain(void)
if (IsEmptyName(name, sizeof(gListName[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 ? 1 : 33, false, true);
gFrameBuffer[line][xStart] ^= 0x3E;
for (uint8_t x = xStart + 1; x < 127; x++) {
gFrameBuffer[line][x] ^= 0x7F;
}
gFrameBuffer[line][127] ^= 0x3E;
}
else
{
const char *displayStr = "EX";
uint8_t xStart = 117;
uint8_t xDisplay = 119;
GUI_DisplaySmallest(displayStr, xDisplay, line == 0 ? 1 : 33, false, true);
gFrameBuffer[line][xStart] ^= 0x3E;
for (uint8_t x = xStart + 1; x < 127; x++) {
gFrameBuffer[line][x] ^= 0x7F;
}
gFrameBuffer[line][127] ^= 0x3E;
displayStr = "EX";
xStart = 117;
}
GUI_DisplaySmallest(displayStr, xStart + 2, line == 0 ? 1 : 33, false, true);
gFrameBuffer[line][xStart] ^= 0x3E;
for (uint8_t x = xStart + 1; x < 127; x++) {
gFrameBuffer[line][x] ^= 0x7F;
}
gFrameBuffer[line][127] ^= 0x3E;
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
{
}
@@ -1151,7 +1577,7 @@ void UI_DisplayMain(void)
switch (gEeprom.CHANNEL_DISPLAY_MODE)
{
case MDF_FREQUENCY: // show the channel frequency
sprintf(String, "%3u.%05u", frequency / 100000, frequency % 100000);
UI_FormatFrequency(frequency, String);
#ifdef ENABLE_BIG_FREQ
if(frequency < _1GHz_in_KHz) {
// show the remaining 2 small frequency digits
@@ -1211,7 +1637,7 @@ void UI_DisplayMain(void)
#ifdef ENABLE_FEAT_F4HWN
if (isMainOnly())
{
sprintf(String, "%3u.%05u", frequency / 100000, frequency % 100000);
UI_FormatFrequency(frequency, String);
if(frequency < _1GHz_in_KHz) {
// show the remaining 2 small frequency digits
UI_PrintStringSmallNormal(String + 7, 113, 0, line + 4);
@@ -1241,7 +1667,7 @@ void UI_DisplayMain(void)
}
else
{ // frequency mode
sprintf(String, "%3u.%05u", frequency / 100000, frequency % 100000);
UI_FormatFrequency(frequency, String);
#ifdef ENABLE_BIG_FREQ
if(frequency < _1GHz_in_KHz) {
@@ -1610,6 +2036,13 @@ void UI_DisplayMain(void)
const bool rx = FUNCTION_IsRx();
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
if (gScanStateDir != SCAN_OFF && UI_DrawScanProgress())
{
center_line = CENTER_LINE_SCAN_PROGRESS;
}
else
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO_SCOPE
if (gSetting_mic_bar && gCurrentFunction == FUNCTION_TRANSMIT) {
// Reserve the line so no other element overwrites it.
+7
View File
@@ -22,6 +22,7 @@
enum center_line_t {
CENTER_LINE_NONE = 0,
CENTER_LINE_IN_USE,
CENTER_LINE_SCAN_PROGRESS,
CENTER_LINE_AUDIO_BAR,
CENTER_LINE_AUDIO_SCOPE,
CENTER_LINE_RSSI,
@@ -49,6 +50,12 @@ void UI_DisplayAudioScope(void);
void UI_MAIN_TimeSlice500ms(void);
void UI_DisplayMain(void);
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
void UI_MAIN_NotifyScanProgressDataChanged(void);
#else
static inline void UI_MAIN_NotifyScanProgressDataChanged(void) {}
#endif
#ifdef ENABLE_AGC_SHOW_DATA
void UI_MAIN_PrintAGC(bool force);
#endif
+196 -40
View File
@@ -27,6 +27,7 @@
#include "../driver/eeprom.h"
#include "../driver/st7565.h"
#include "../external/printf/printf.h"
#include "../font.h"
#include "../frequencies.h"
#include "../helper/battery.h"
#include "../misc.h"
@@ -40,6 +41,7 @@
#include "inputbox.h"
#include "menu.h"
#include "ui.h"
#include "welcome.h"
const t_menu_item MenuList[] =
@@ -523,6 +525,64 @@ int32_t gSubMenuSelection;
char edit_original[17]; // a copy of the text before editing so that we can easily test for changes/difference
char edit[17];
int edit_index;
bool edit_is_uppercase = false;
static void UI_MENU_DrawTopRightRoundedBadge(const char *text, const uint8_t line, const bool center_in_area, const uint8_t area_x1, const uint8_t area_x2)
{
const size_t length = strlen(text);
const size_t char_pitch = ARRAY_SIZE(gFontSmall[0]) + 1u;
const size_t text_width = length * char_pitch;
const size_t capsule_span = text_width + 1u; // matches UI_PrintStringSmallNormalInverse x_end computation
uint8_t text_x;
if (length == 0 || line == 0 || line >= FRAME_LINES) {
return;
}
if (center_in_area && area_x2 > area_x1 + 2u) {
const uint8_t min_x = area_x1 + 1u;
uint8_t max_x;
const uint8_t area_width = area_x2 - area_x1 + 1u;
if (capsule_span >= area_width) {
text_x = min_x;
} else {
text_x = (uint8_t)(area_x1 + ((area_width - capsule_span) / 2u));
}
if (area_x2 > capsule_span) {
max_x = (uint8_t)(area_x2 - capsule_span);
} else {
max_x = min_x;
}
if (max_x < min_x) {
max_x = min_x;
}
if (text_x < min_x) {
text_x = min_x;
} else if (text_x > max_x) {
text_x = max_x;
}
} else {
if (capsule_span >= (LCD_WIDTH - 3u)) {
text_x = 1u;
} else {
const uint8_t global_shift_right = 1u;
const uint8_t base_text_x = (uint8_t)(LCD_WIDTH - capsule_span - 3u);
const uint8_t max_text_x = (uint8_t)(LCD_WIDTH - capsule_span - 1u);
const uint16_t shifted_x = (uint16_t)base_text_x + global_shift_right;
if (shifted_x > max_text_x) {
text_x = max_text_x;
} else {
text_x = (uint8_t)shifted_x;
}
}
}
UI_PrintStringSmallNormalInverse(text, text_x, 0, line);
}
void UI_DisplayMenu(void)
{
@@ -531,6 +591,7 @@ void UI_DisplayMenu(void)
const unsigned int menu_item_x2 = LCD_WIDTH - 1;
unsigned int i;
char String[64]; // bigger cuz we can now do multi-line in one string (use '\n' char)
char top_right_badge[16];
#ifdef ENABLE_DTMF_CALLING
char Contact[16];
@@ -629,6 +690,7 @@ void UI_DisplayMenu(void)
// **************
memset(String, 0, sizeof(String));
memset(top_right_badge, 0, sizeof(top_right_badge));
bool already_printed = false;
@@ -865,7 +927,7 @@ void UI_DisplayMenu(void)
{ // show the frequency so that the user knows the channels frequency
const uint32_t frequency = SETTINGS_FetchChannelFrequency(gSubMenuSelection);
sprintf(String, "%u.%05u", frequency / 100000, frequency % 100000);
UI_PrintString(String, menu_item_x1, menu_item_x2, 4, 8);
UI_PrintString(String, menu_item_x1, menu_item_x2, 5, 8);
}
SETTINGS_FetchChannelName(String, gSubMenuSelection);
@@ -899,15 +961,34 @@ void UI_DisplayMenu(void)
{ // show the channel name being edited
//UI_PrintString(edit, menu_item_x1, 0, 2, 8);
UI_PrintString(edit, menu_item_x1, menu_item_x2, 2, 8);
if (edit_index < 10)
//UI_PrintString("^", menu_item_x1 + (8 * edit_index), 0, 4, 8); // show the cursor
UI_PrintString("^", menu_item_x1 - 1 + (8 * edit_index),0, 4, 8); // show the cursor
if (edit_index < 10) {
// UI_PrintString("^", menu_item_x1 - 1 + (8 * edit_index),0, 4, 8); // show the cursor
uint8_t x = menu_item_x1 - 1;
for (uint8_t i = 0; i < 10; i++)
{
if (i != edit_index)
{
if (edit[i] != 'g' && edit[i] != 'j')
{
UI_DrawLineBuffer(gFrameBuffer, x, 29, x + 6, 29, 1);
}
}
else
{
UI_DrawLineBuffer(gFrameBuffer, x + 2, 30, x + 4, 30, 1);
UI_DrawPixelBuffer(gFrameBuffer, x + 3, 29, 1);
}
x += 8;
}
UI_PrintStringSmallNormal(edit_is_uppercase ? "ABC" : "abc", 77, 0, 4);
}
}
if (!gAskForConfirmation)
{ // show the frequency so that the user knows the channels frequency
sprintf(String, "%u.%05u", frequency / 100000, frequency % 100000);
UI_PrintString(String, menu_item_x1, menu_item_x2, 4 + (gIsInSubMenu && edit_index >= 0), 8);
UI_PrintString(String, menu_item_x1, menu_item_x2, 5, 8);
}
}
@@ -1049,18 +1130,98 @@ void UI_DisplayMenu(void)
strcpy(String, gSubMenu_ROGER[gSubMenuSelection]);
break;
case MENU_VOL:
case MENU_VOL: {
// SysInf is paginated. Pages appear in this order, only when their
// feature flag is enabled:
// 0 -> identity
// next -> Build date/time (ENABLE_FEAT_F4HWN)
// next -> Battery (ENABLE_FEAT_F4HWN)
// next -> Flash / SRAM usage (ENABLE_FEAT_F4HWN_MEM)
// next, +1 -> CODE / WIKI QR codes (ENABLE_FEAT_F4HWN_QRCODE)
// In non-F4HWN builds, page 0 keeps the old battery-voltage display.
const uint8_t page = (uint8_t)gSubMenuSelection;
uint8_t p = 0;
if (page == p++) {
// Page 0: firmware identity.
#ifdef ENABLE_FEAT_F4HWN
sprintf(String, "%s\n%s",
AUTHOR_STRING_2,
VERSION_STRING_2
);
sprintf(String, "%s\n%s", AUTHOR_STRING_2, VERSION_STRING_2);
UI_PrintStringSmallNormal(Edition, menu_item_x1 - 1, menu_item_x2, 6);
#else
sprintf(String, "%u.%02uV\n%u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage));
sprintf(String, "%u.%02uV\n%u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage));
#endif
break;
}
#ifdef ENABLE_FEAT_F4HWN
if (page == p++) {
strcpy(top_right_badge, "BUILD");
UI_PrintStringSmallNormal(BuildDate, menu_item_x1 - 1, menu_item_x2, 3);
UI_PrintStringSmallNormal(BuildTime, menu_item_x1 - 1, menu_item_x2, 4);
UI_PrintStringSmallNormal(BuildCommit, menu_item_x1 - 1, menu_item_x2, 6);
already_printed = true;
break;
}
if (page == p++) {
char val[16];
strcpy(top_right_badge, "BATTERY");
sprintf(val, "%u.%02uV %u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage));
UI_PrintStringSmallNormal(val, menu_item_x1 - 1, menu_item_x2, 3);
UI_PrintStringSmallNormal(gSubMenu_BATTYP[gEeprom.BATTERY_TYPE], menu_item_x1 - 1, menu_item_x2, 5);
already_printed = true;
break;
}
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
if (page == p++) {
uint16_t flash_pct = 0;
uint16_t ram_pct = 0;
UI_GetMemPercents(&flash_pct, &ram_pct);
char val[16];
// MEMORY title capsule centered in right zone, fb line 1.
strcpy(top_right_badge, "MEMORY");
// Flash + SRAM values stacked below, normal small font, with a fb-line of breathing space.
sprintf(val, "FLASH %u.%u%%",
(unsigned)(flash_pct / 100), (unsigned)((flash_pct / 10) % 10));
UI_PrintStringSmallNormal(val, menu_item_x1 - 1, menu_item_x2, 3);
sprintf(val, "SRAM %u.%u%%",
(unsigned)(ram_pct / 100), (unsigned)((ram_pct / 10) % 10));
UI_PrintStringSmallNormal(val, menu_item_x1 - 1, menu_item_x2, 5);
already_printed = true;
break;
}
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// Right zone: x=49..127 (79 px). QR centered at x=72..104.
// Capsule label above QR (small-font Inverse style at fb line 1).
if (page == p || page == p + 1) {
const bool is_wiki = (page == (p + 1));
strcpy(top_right_badge, is_wiki ? "WIKI" : "CODE");
UI_DrawQRCode(is_wiki, 72, 28);
already_printed = true;
break;
}
p += 2;
#endif
break;
}
case MENU_RESET:
strcpy(String, gSubMenu_RESET[gSubMenuSelection]);
@@ -1191,15 +1352,15 @@ void UI_DisplayMenu(void)
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM) {
strcpy(String, gSubMenu_SET_AUD_AM[gSubMenuSelection]);
UI_PrintStringSmallNormal("AM", 114, 0, 0);
strcpy(top_right_badge, "AM");
}
else if (gTxVfo->Modulation == MODULATION_USB) {
strcpy(String, "USB");
UI_PrintStringSmallNormal("USB", 108, 0, 0);
strcpy(top_right_badge, "USB");
}
else {
strcpy(String, gSubMenu_SET_AUD_FM[gSubMenuSelection]);
UI_PrintStringSmallNormal("FM", 114, 0, 0);
strcpy(top_right_badge, "FM");
}
break;
#endif
@@ -1227,11 +1388,7 @@ void UI_DisplayMenu(void)
//#endif
}
// gEeprom.VOLUME_GAIN = gSubMenuSelection;
BK4819_WriteRegister(BK4819_REG_48,
(11u << 12) | // ??? .. 0 ~ 15, doesn't seem to make any difference
( 0u << 10) | // AF Rx Gain-1
(gEeprom.VOLUME_GAIN << 4) | // AF Rx Gain-2
(gEeprom.DAC_GAIN << 0)); // AF DAC Gain (after Gain-1 and Gain-2)
BK4819_SetRxAudioGain();
break;
#endif
@@ -1288,23 +1445,6 @@ void UI_DisplayMenu(void)
y = (small ? 3 : 2) - (lines / 2);
// only for SysInf
if(UI_MENU_GetCurrentMenuId() == MENU_VOL)
{
sprintf(edit, "%u.%02uV %u%%",
gBatteryVoltageAverage / 100, gBatteryVoltageAverage % 100,
BATTERY_VoltsToPercent(gBatteryVoltageAverage)
);
UI_PrintStringSmallNormal(edit, 54, 127, 1);
#ifdef ENABLE_FEAT_F4HWN
UI_PrintStringSmallNormal(Edition, 54, 127, 6);
#endif
y = 2;
}
// draw the text lines
for (i = 0; i < len && lines > 0; lines--)
{
@@ -1351,8 +1491,24 @@ void UI_DisplayMenu(void)
|| UI_MENU_GetCurrentMenuId() == MENU_D_LIST
#endif
) {
sprintf(String, "%03d", gSubMenuSelection);
UI_PrintStringSmallNormal(String, 107, 0, 0);
if (UI_MENU_GetCurrentMenuId() == MENU_R_CTCS ||
UI_MENU_GetCurrentMenuId() == MENU_T_CTCS) {
const uint8_t approved_index =
(gSubMenuSelection > 0) ? DCS_GetCtcssApprovedIndex(gSubMenuSelection - 1) : 0xFF;
if (gSubMenuSelection == 0)
sprintf(top_right_badge, "00/00");
else if (approved_index != 0xFF)
sprintf(top_right_badge, "%02u/%02u", (unsigned)gSubMenuSelection, approved_index + 1);
else
sprintf(top_right_badge, "%02u/--", (unsigned)gSubMenuSelection);
} else {
sprintf(top_right_badge, "%03d", gSubMenuSelection);
}
}
if (top_right_badge[0] != '\0') {
UI_MENU_DrawTopRightRoundedBadge(top_right_badge, 1, true, menu_item_x1, menu_item_x2);
}
if ((UI_MENU_GetCurrentMenuId() == MENU_RESET ||
+1
View File
@@ -233,6 +233,7 @@ extern int32_t gSubMenuSelection;
extern char edit_original[17];
extern char edit[17];
extern int edit_index;
extern bool edit_is_uppercase;
void UI_DisplayMenu(void);
int UI_MENU_GetCurrentMenuId();
+32 -27
View File
@@ -155,42 +155,47 @@ void UI_DisplayStatus()
if(!SCANNER_IsScanning()) {
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
if(gCurrentFunction == FUNCTION_TRANSMIT && gSetting_set_tmr == true)
{
convertTime(line, 0);
}
else if(FUNCTION_IsRx() && gSetting_set_tmr == true)
{
convertTime(line, 1);
bool isTransmit = gCurrentFunction == FUNCTION_TRANSMIT;
if (gSetting_set_tmr && (isTransmit || FUNCTION_IsRx())) {
convertTime(line, !isTransmit);
}
else
#endif
{
if(!gAirCopyBootMode) {
const void *src = NULL; // Pointer to the font/bitmap to copy
size_t sSize = 0; // Size of the font/bitmap
uint8_t sOff = 2; // Offset relative to the reference position
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
if(gEeprom.MENU_LOCK == true) {
memcpy(line + x + 2, gFontRO, sizeof(gFontRO));
}
else
if (gEeprom.MENU_LOCK) {
src = gFontRO;
sSize = sizeof(gFontRO);
} else
#endif
{
#endif
uint8_t dw = (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) + (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF) * 2;
if(dw == 1 || dw == 3) { // DWR - dual watch + respond
if(gDualWatchActive)
memcpy(line + x + (dw==1?0:2), gFontDWR, sizeof(gFontDWR) - (dw==1?0:5));
else
memcpy(line + x + 3, gFontHold, sizeof(gFontHold));
uint8_t xb = (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF);
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) {
if (gDualWatchActive) { // DWR - dual watch + respond
src = gFontDWR;
sOff = xb ? 2 : 0;
sSize = sizeof(gFontDWR) - (xb ? 5 : 0);
} else {
src = gFontHold;
sOff = 3;
sSize = sizeof(gFontHold);
}
} else {
src = xb ? gFontXB : gFontMO; // XB - crossband
sSize = xb ? sizeof(gFontXB) : sizeof(gFontMO); // MO - main only
}
else if(dw == 2) { // XB - crossband
memcpy(line + x + 2, gFontXB, sizeof(gFontXB));
}
else
{
memcpy(line + x + 2, gFontMO, sizeof(gFontMO));
}
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
}
#endif
// Perform the memcpy if a source was selected
if (src) {
memcpy(line + x + sOff, src, sSize);
}
}
}
}
+100 -1
View File
@@ -33,6 +33,52 @@
#include "screenshot.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// QR code (version 4, 33x33 modules, EC level L) encoding:
// https://github.com/armel/uv-k1-k5v3-firmware-custom
// Stored in framebuffer column-major format: 5 fb-lines x 33 columns.
// Each byte packs 8 vertical pixels (bit 0 = top). Last fb-line uses
// only bit 0 (row 32); bits 1..7 are always 0.
//
// static const uint8_t BITMAP_QR_GitHub[5][33] = {
// { 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0xB8, 0xCB, 0xA0, 0x6D, 0x07, 0xCB, 0x1F, 0xD2, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F },
// { 0x87, 0xA3, 0x69, 0xC3, 0x19, 0x0E, 0x55, 0x1F, 0x43, 0x11, 0x16, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22 },
// { 0xE0, 0x0B, 0x1D, 0x28, 0xF5, 0x87, 0x55, 0xEB, 0xA8, 0x11, 0xA3, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22 },
// { 0xFD, 0x04, 0x75, 0x75, 0x75, 0x04, 0xFD, 0x01, 0xF7, 0xE0, 0xD6, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x2C, 0x2B },
// { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00 }
// };
static const uint8_t BITMAP_QR_GitHub_Compressed[137] = {
0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0xB8, 0xCB, 0xA0, 0x6D, 0x07, 0xCB, 0x1F, 0xD2, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F,
0x87, 0xA3, 0x69, 0xC3, 0x19, 0x0E, 0x55, 0x1F, 0x43, 0x11, 0x16, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22,
0xE0, 0x0B, 0x1D, 0x28, 0xF5, 0x87, 0x55, 0xEB, 0xA8, 0x11, 0xA3, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22,
0xFD, 0x04, 0x75, 0x75, 0x75, 0x04, 0xFD, 0x01, 0xF7, 0xE0, 0xD6, 0xC1, 0x5A, 0x0E, 0x96, 0x3E, 0xA5, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x2C, 0x2B,
0x7F, 0x09, 0x8C, 0xA3, 0x00
};
// QR code (version 4, 33x33 modules, EC level L) encoding:
// https://github.com/armel/uv-k1-k5v3-firmware-custom/wiki
// Stored in framebuffer column-major format: 5 fb-lines x 33 columns.
// Last fb-line uses only bit 0 (row 32); bits 1..7 are always 0.
//
// static const uint8_t BITMAP_QR_GitHub_Wiki[5][33] = {
// { 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0x0F, 0x74, 0x0E, 0xD2, 0xB0, 0x74, 0xB1, 0x6D, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F },
// { 0xCD, 0x5D, 0x83, 0x65, 0xE7, 0xC6, 0x55, 0xBD, 0x6B, 0x3F, 0xA9, 0x1C, 0xA5, 0xE0, 0x69, 0xE3, 0x4A, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22 },
// { 0xFF, 0x25, 0xAE, 0xB6, 0x30, 0xF8, 0x55, 0xDD, 0x07, 0xB6, 0xC2, 0x1C, 0xA5, 0xE0, 0x69, 0xC4, 0x66, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22 },
// { 0xFD, 0x04, 0x74, 0x74, 0x74, 0x05, 0xFD, 0x01, 0xF7, 0xAE, 0x81, 0x1C, 0xA5, 0xE0, 0x69, 0x54, 0xFE, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x24, 0x33 },
// { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00 }
// };
static const uint8_t BITMAP_QR_GitHub_Wiki_Compressed[137] = {
0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F, 0x00, 0x6A, 0x0F, 0x74, 0x0E, 0xD2, 0xB0, 0x74, 0xB1, 0x6D, 0x18, 0x59, 0x15, 0x79, 0x86, 0xCE, 0x15, 0x43, 0x00, 0x7F, 0x41, 0x5D, 0x5D, 0x5D, 0x41, 0x7F,
0xCD, 0x5D, 0x83, 0x65, 0xE7, 0xC6, 0x55, 0xBD, 0x6B, 0x3F, 0xA9, 0x1C, 0xA5, 0xE0, 0x69, 0xE3, 0x4A, 0x15, 0x06, 0x2A, 0xFE, 0xCE, 0xCA, 0x3A, 0x70, 0xD9, 0xEA, 0xF5, 0x5C, 0x15, 0x8A, 0x67, 0x22,
0xFF, 0x25, 0xAE, 0xB6, 0x30, 0xF8, 0x55, 0xDD, 0x07, 0xB6, 0xC2, 0x1C, 0xA5, 0xE0, 0x69, 0xC4, 0x66, 0x15, 0x84, 0x2B, 0x72, 0xE8, 0xE9, 0x23, 0x11, 0xCD, 0xE6, 0xC1, 0x91, 0xE6, 0x88, 0x77, 0x22,
0xFD, 0x04, 0x74, 0x74, 0x74, 0x05, 0xFD, 0x01, 0xF7, 0xAE, 0x81, 0x1C, 0xA5, 0xE0, 0x69, 0x54, 0xFE, 0x37, 0x22, 0x2B, 0xEA, 0xAA, 0xA7, 0x8D, 0x5F, 0x31, 0x55, 0xB1, 0x3F, 0xCE, 0xCA, 0x24, 0x33,
0x7F, 0x53, 0x8E, 0xA3, 0x00
};
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
// Linker symbols (provided by the linker script)
extern uint8_t _sdata; // Start of .data in RAM
@@ -88,6 +134,57 @@ static inline uint16_t pct_x100(uint32_t used, uint32_t total)
{
return (uint16_t)((used * 10000u) / total); // 7559 => 75.59%
}
void UI_GetMemPercents(uint16_t *flash_pct_x100, uint16_t *ram_pct_x100)
{
uint32_t ram_used = 0;
uint32_t flash_used = 0;
build_usage(&ram_used, &flash_used);
if (flash_pct_x100) *flash_pct_x100 = pct_x100(flash_used, FLASH_SIZE_BYTES);
if (ram_pct_x100) *ram_pct_x100 = pct_x100(ram_used, RAM_SIZE_BYTES);
}
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// Set a single pixel at LCD-physical (x, y). y=0..7 maps to gStatusLine,
// y=8..63 maps to gFrameBuffer (line = (y-8)/8, bit = (y-8)%8).
static void QR_SetPixel(uint8_t x, uint8_t y)
{
if (x >= 128 || y >= 64) return;
if (y < 8) {
gStatusLine[x] |= (uint8_t)(1u << y);
} else {
const uint8_t fb_y = (uint8_t)(y - 8u);
gFrameBuffer[fb_y >> 3][x] |= (uint8_t)(1u << (fb_y & 7u));
}
}
// Render a square QR bitmap stored in framebuffer column-major format
// (size cols × ceil(size/8) fb-lines, row-major in memory).
static void QR_Draw(const uint8_t *bitmap, uint8_t size, uint8_t origin_x, uint8_t origin_y)
{
for (uint8_t qy = 0; qy < size; qy++) {
for (uint8_t qx = 0; qx < size; qx++) {
// const uint16_t idx = (uint16_t)(qy >> 3) * (uint16_t)size + (uint16_t)qx;
// if ((bitmap[idx] >> (qy & 7u)) & 1u) {
if (qy < 32 ?
((bitmap[(uint16_t)(qy >> 3) * (uint16_t)size + (uint16_t)qx] >> (qy & 7u)) & 1u) :
((bitmap[132 + (qx >> 3)] >> (qx & 7u)) & 1u)) {
QR_SetPixel((uint8_t)(origin_x + qx),
(uint8_t)(origin_y + qy));
}
}
}
}
void UI_DrawQRCode(bool wiki, uint8_t origin_x, uint8_t origin_y)
{
// QR_Draw(wiki ? (const uint8_t *)BITMAP_QR_GitHub_Wiki
// : (const uint8_t *)BITMAP_QR_GitHub,
QR_Draw(wiki ? (const uint8_t *)BITMAP_QR_GitHub_Wiki_Compressed
: (const uint8_t *)BITMAP_QR_GitHub_Compressed,
33, origin_x, origin_y);
}
#endif
void UI_DisplayReleaseKeys(void)
@@ -193,6 +290,7 @@ void UI_DisplayWelcome(void)
gFrameBuffer[4][108] ^= 0x7F;
UI_DrawLineBuffer(gFrameBuffer, 109, 35, 127, 35, 1);
/*
#ifdef ENABLE_FEAT_F4HWN_MEM
uint32_t ram_used = 0;
uint32_t flash_used = 0;
@@ -210,6 +308,7 @@ void UI_DisplayWelcome(void)
GUI_DisplaySmallest(WelcomeString3, 5, 1, true, true);
ST7565_BlitStatusLine();
#endif
*/
sprintf(WelcomeString3, "%s Edition", Edition);
UI_PrintStringSmallNormal(WelcomeString3, 0, 127, 6);
@@ -225,4 +324,4 @@ void UI_DisplayWelcome(void)
SCREENSHOT_Update(true);
#endif
}
}
}
+14
View File
@@ -17,8 +17,22 @@
#ifndef UI_WELCOME_H
#define UI_WELCOME_H
#include <stdbool.h>
#include <stdint.h>
void UI_DisplayReleaseKeys(void);
void UI_DisplayWelcome(void);
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// Draw the embedded GitHub QR code (V4, 33x33) at (origin_x, origin_y).
// `wiki` selects the wiki URL bitmap, otherwise the repo URL bitmap.
void UI_DrawQRCode(bool wiki, uint8_t origin_x, uint8_t origin_y);
#endif
#ifdef ENABLE_FEAT_F4HWN_MEM
// Compute current FLASH and SRAM usage as percentages × 100 (e.g. 7559 = 75.59%).
void UI_GetMemPercents(uint16_t *flash_pct_x100, uint16_t *ram_pct_x100);
#endif
#endif
+3
View File
@@ -8,6 +8,9 @@
#ifdef ENABLE_FEAT_F4HWN
const char Version[] = AUTHOR_STRING_2 " " VERSION_STRING_2;
const char Edition[] = EDITION_STRING;
const char BuildDate[] = __DATE__;
const char BuildTime[] = __TIME__;
const char BuildCommit[] = BUILD_COMMIT;
#else
const char Version[] = AUTHOR_STRING VER;
#endif
+3
View File
@@ -24,4 +24,7 @@ extern const char UART_Version[];
#ifdef ENABLE_FEAT_F4HWN
extern const char Edition[];
extern const char BuildDate[];
extern const char BuildTime[];
extern const char BuildCommit[];
#endif
+15
View File
@@ -19,6 +19,21 @@ message("Build type: " ${CMAKE_BUILD_TYPE})
enable_language(C ASM)
find_package(Git QUIET)
set(BUILD_COMMIT "unknown")
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
execute_process(
COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE BUILD_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT BUILD_COMMIT)
set(BUILD_COMMIT "unknown")
endif()
endif()
if(ENABLE_FEAT_F4HWN)
if(NOT AUTHOR_STRING_1)
set(AUTHOR_STRING_1 "EGZUMER")
+6 -1
View File
@@ -66,12 +66,14 @@
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": false,
"ENABLE_FEAT_F4HWN_CA": true,
"ENABLE_FEAT_F4HWN_DEBUG": false,
"ENABLE_FEAT_F4HWN_QRCODE": false,
"ENABLE_FEAT_F4HWN_MEM": false,
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": false,
"ENABLE_AGC_SHOW_DATA": false,
"ENABLE_UART_RW_BK_REGS": false,
"ENABLE_SWD": false,
"VERSION_STRING_1": "v0.22",
"VERSION_STRING_2": "v5.3.1"
"VERSION_STRING_2": "v5.4.0"
}
},
{
@@ -191,6 +193,9 @@
"ENABLE_FEAT_F4HWN_VOL": true,
"ENABLE_FEAT_F4HWN_AUDIO": true,
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": true,
"ENABLE_FEAT_F4HWN_QRCODE": true,
"ENABLE_FEAT_F4HWN_MEM": true,
"ENABLE_SWD": true,
"EDITION_STRING": "Fusion",
"TARGET": "f4hwn.fusion"
+54 -61
View File
@@ -31,9 +31,9 @@ Anyway, have fun.
> _FR - CE FIRMWARE N'A PAS DE VÉRITABLE CERVEAU. VEUILLEZ UTILISER LE VÔTRE. Utilisez ce firmware à vos risques et périls. Il n'y a absolument aucune garantie qu'il fonctionnera d'une manière ou d'une autre sur votre (vos) radio(s), il peut même bousiller votre (vos) radio(s), dans ce cas, vous devrez acheter une autre radio. Quoi qu'il en soit, amusez-vous bien._
> [!NOTE]
> EN - About Chirp, as many others firmwares, you need to use a dedicated driver available on [this repository](https://github.com/armel/uv-k5-chirp-driver).
> EN - About CHIRP, as with many other firmwares, you need to use a dedicated driver. The matching CHIRP driver is now bundled with each release of this repository, so you can download the firmware and its driver together from the [Releases page](https://github.com/armel/uv-k1-k5v3-firmware-custom/releases).
>
> _FR - A propos de Chirp, comme beaucoup d'autres firmwares, vous devez utiliser un pilote dédié disponible sur [ce dépôt](https://github.com/armel/uv-k5-chirp-driver)._
> _FR - A propos de CHIRP, comme pour beaucoup d'autres firmwares, vous devez utiliser un pilote dédié. Le driver CHIRP correspondant est désormais fourni avec chaque release de ce dépôt, ce qui permet de récupérer ensemble le firmware et son pilote depuis la page des [Releases](https://github.com/armel/uv-k1-k5v3-firmware-custom/releases)._
> [!CAUTION]
> EN - I recommend to backup your calibration data with [uvtools2](https://armel.github.io/uvtools2/) just after flashing this firmware. It's a good reflex to have.
@@ -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, Ken 2E0UMK, Victor TI2SYS, Tobi DG9LAY, Deaglan K4DFQ, Catherine PALMER and Brian WA6JFK 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, Victor TI2SYS, Tobi DG9LAY, Deaglan K4DFQ, Catherine PALMER, Brian WA6JFK, Stéphane Hintzy, Roger F1HCN and Marcin Kusaj for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻
## Table of Contents
@@ -57,12 +57,15 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
## Main features and improvements from F4HWN:
* several firmware versions:
* Bandscope (with spectrum analyzer made by Fagci),
* Broadcast (with commercial FM radio support),
* Basic (with spectrum analyzer and commercial FM radios support, but without certain functions such as Vox, Aircopy, etc.),
* RescueOps (specifically designed for first responders: firefighters, sea rescue, mountain rescue),
* Game (with a small breakout game),
* Fusion is now the reference edition of the project:
* all-in-one firmware for UV-K1 and UV-K5 V3,
* spectrum analyzer made by Fagci,
* commercial FM radio support,
* Vox and Aircopy support,
* screenshots and K5Viewer support,
* advanced RX audio profiles and Audio Scope,
* first-responder oriented options,
* small breakout game,
* improve default power settings level:
* Low1 to Low5 (<~20mW, ~125mW, ~250mW, ~500mW, ~1W),
* Mid ~2W,
@@ -92,6 +95,8 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* improve keyboard frequency input,
* add percent and gauge to Air Copy,
* improve audio bar,
* add backlight fading,
* add Audio Scope on TX,
* and more...
* new menu entries and changes:
* add SetPwr menu to set User power (<20mW, 125mW, 250mW, 500mW, 1W, 2W or 5W),
@@ -103,10 +108,12 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* add SetMet menu to set s-meter style (Classic or Tiny),
* add SetLck menu to set what is locked (Keys or Keys + PTT),
* add SetGUI menu to set font size on the VFO baseline (Classic or Tiny),
* add SetRxA menu to select RX audio profiles,
* add TXLock menu to open TX on channel,
* add SetTmr menu to set RX and TX timers (Off or On),
* add SetOff menu to set the delay before the transceiver goes into deep sleep (Off or 1 minute to 2 hours),
* add SetNFM menu to set Narrow width (12.5kHz or 6.25kHz),
* add SetVol menu to adjust RX audio volume,
* rename BatVol menu (52/63) to SysInf, which displays the firmware version in addition to the battery status,
* improve PonMsg menu,
* improve BackLt menu,
@@ -116,6 +123,7 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* add HAM CA F Lock band (for Canadian zone),
* add PMR 446 F Lock band,
* add FRS/GMRS/MURS F Lock band,
* add SetNav hidden menu to select the navigation layout according to the radio model,
* remove blink and SOS functionality,
* remove AM Fix menu (AM Fix is ENABLED by default),
* add support of 3500mAh battery,
@@ -124,18 +132,16 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* change font and bitmaps,
* move USB icon to left of battery information,
* add RX and TX timers,
* improve lists and scan lists options:
* add new list 3,
* add new list 0 (channel without list...),
* add new scan lists options,
* scan list 0 (all channels without list),
* scan list 1,
* scan list 2,
* scan list 3,
* scan lists [1, 2, 3],
* scan all (all channels with or without list),
* add scan list shortcuts,
* add resume mode on startup (scan, bandscope and broadcast FM),
* improve channel scanning:
* support up to 24 scan lists,
* each memory channel can be assigned to `OFF`, to one list (`01` to `24`), or to `ALL`,
* `ALL` scans every channel except those set to `OFF`,
* named scan lists are shown in the UI and status bar when available,
* if the selected scan list is empty or invalid, the firmware automatically jumps to the next valid one,
* add resume mode on startup (scan, spectrum analyzer and FM radio),
* improve VFO persistence and restore behavior:
* save the Squelch level adjusted with F + UP or F + DOWN,
* restore the full VFO state on long press of EXIT,
* new actions:
* RX MODE,
* MAIN ONLY,
@@ -143,23 +149,25 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* WIDE NARROW,
* 1750Hz,
* MUTE,
* POWER HIGH (RescueOps),
* REMOVE OFFSET (RescueOps),
* POWER HIGH,
* REMOVE OFFSET,
* new key combinations:
* add the F + UP or F + DOWN key combination to dynamically change the Squelch level,
* add the F + F1 or F + F2 key combination to dynamically change the Step,
* add F + 8 to quickly switch backlight between BLMin and BLMax on demand (this bypass BackLt strategy),
* add F + 9 to return to BackLt strategy,
* add long press on MENU, in * SCAN mode, to temporarily exclude a memory channel,
* add short press on [0, 1, 2, 3, 4 or 5], in * SCAN mode, to dynamically change scan list.
* add long press on MENU, in * SCAN mode, to exclude the current memory channel,
* add direct scan list selection while scanning with two digits (`00` = `ALL`, `01` to `24` = scan list).
* many fix:
* squelch,
* s-meter,
* DTMF overlaying,
* scan list 2 ignored,
* scan range limit,
* clean display on startup,
* no more PWM noise,
* K5Viewer/serial key handling,
* spectrum freeze on USB-C unplug,
* Audio Scope behavior in OnePush mode and after DTMF/1750,
* and more...
* enabled AIR COPY
* disabled ENABLE_DTMF_CALLING,
@@ -167,7 +175,7 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* remove 200Tx, 350Tx and 500Tx,
* unlock TX on all bands needs only to be repeat 3 times,
* code refactoring and many memory optimization,
* displays the live screen of the Quansheng K5 on your computer via a USB-to-Serial cable,
* stream the live screen of the Quansheng K5 to K5Viewer and capture screenshots over a USB-to-Serial cable,
* and more...
## Main features from Egzumer:
@@ -203,7 +211,7 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
## Manual
Up to date manual is available in the [Wiki section](https://github.com/armel/uv-k5-firmware-custom/wiki)
Up to date manual is available in the [Wiki section](https://github.com/armel/uv-k1-k5v3-firmware-custom/wiki)
## Radio performance
@@ -211,21 +219,21 @@ Please note that the Quansheng UV-Kx radios are not professional quality transce
performance is strictly limited. The RX front end has no track-tuned band pass filtering
at all, and so are wide band/wide open to any and all signals over a large frequency range.
Using the radio in high intensity RF environments will most likely make reception anything but
easy (AM mode will suffer far more than FM ever will), the receiver simply doesn't have a
great dynamic range, which results in distorted AM audio with stronger RX'ed signals.
There is nothing more anyone can do in firmware/software to improve that, once the RX gain
adjustment I do (AM fix) reaches the hardwares limit, your AM RX audio will be all but
non-existent (just like Quansheng's firmware).
On the other hand, FM RX audio will/should be fine.
Using the radio in high intensity RF environments will most likely make reception difficult,
especially in AM mode. The receiver simply does not have a great dynamic range, so stronger
signals can easily cause distortion, desensitization and poor AM audio.
This is fundamentally a hardware limitation: firmware can improve behavior at the margins, but
it cannot overcome the front-end design of the radio.
In practice, AM reception will degrade first and most severely, while FM reception is generally
more tolerant and should remain more usable.
But, they are nice toys for the price, fun to play with.
## Compiling and Building from Docker
This project provides a Docker-based build system to compile all firmware editions for the UV-K1 and UV-K5 V3. Everything is handled through the `compile-with-docker.sh` helper script.
This project provides a Docker-based build system to compile the Fusion firmware for the UV-K1 and UV-K5 V3. Everything is handled through the `compile-with-docker.sh` helper script.
All build outputs are generated inside the `build/<Preset>` directory, according to the CMake presets defined in `CMakePresets.json`.
The documented build output is generated inside `build/Fusion`, using the CMake presets defined in `CMakePresets.json`.
### Prerequisites
@@ -238,40 +246,25 @@ The script `compile-with-docker.sh` performs the following actions:
1. Builds the Docker image (`uvk1-uvk5v3`) if it does not already exist.
2. Removes any previous `build` directory to ensure a clean configuration.
3. Runs CMake using the selected preset inside the Docker container.
4. Builds the firmware and outputs `.elf`, `.bin` and `.hex` files for the chosen edition.
3. Runs CMake using the `Fusion` preset inside the Docker container.
4. Builds the firmware and outputs `.elf`, `.bin` and `.hex` files.
### Usage
```bash
./compile-with-docker.sh <Preset> [extra CMake options]
./compile-with-docker.sh Fusion [extra CMake options]
```
### Available Presets
### Documented Preset
- **Custom**
- **Bandscope**
- **Broadcast**
- **Basic**
- **RescueOps**
- **Game**
- **Fusion**
- **All** (builds all editions sequentially)
### Examples
Build a single edition:
Build Fusion:
```bash
./compile-with-docker.sh Fusion
./compile-with-docker.sh Bandscope
./compile-with-docker.sh Broadcast
```
Build everything:
```bash
./compile-with-docker.sh All
```
### Passing Additional CMake Options
@@ -282,15 +275,14 @@ These are forwarded directly to `cmake --preset` inside the container.
Examples:
```bash
./compile-with-docker.sh Bandscope -DENABLE_SPECTRUM=ON
./compile-with-docker.sh Broadcast -DENABLE_FEAT_F4HWN_GAME=ON -DENABLE_NOAA=ON
./compile-with-docker.sh Bandscope -DSQL_TONE=600
./compile-with-docker.sh Fusion -DENABLE_SPECTRUM=ON
./compile-with-docker.sh Fusion -DENABLE_FEAT_F4HWN_GAME=ON -DENABLE_NOAA=ON
./compile-with-docker.sh Fusion -DSQL_TONE=600
```
### Notes
- The first run may take a few minutes while Docker builds the base image.
- Running with `All` will build every firmware variant in sequence.
- Each build runs inside Docker, so your host environment remains clean.
## Flashing the Firmware with UVTools2
@@ -342,6 +334,7 @@ Click `Restore Calibration Data` and wait until the process fully completes.
Many thanks to various people:
* [Muzkr](https://github.com/muzkr)
* [Mrkusypl](https://github.com/mrkusypl)
* [Andrej](https://github.com/Tunas1337)
* [Egzumer](https://github.com/egzumer)
* [OneOfEleven](https://github.com/OneOfEleven)
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
# Copyright (c) 2026
#
# Licensed under the MIT License (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at the root of this repository.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from binascii import crc_hqx
from itertools import cycle
# Quansheng stock updater pack format:
# - raw firmware split at 0x2000
# - 16-byte ASCII version field inserted there
# - XOR obfuscation with the table below
# - CRC16/XMODEM appended at the end
OBFUSCATION = bytes(
[
0x47, 0x22, 0xC0, 0x52, 0x5D, 0x57, 0x48, 0x94, 0xB1, 0x60, 0x60, 0xDB, 0x6F, 0xE3, 0x4C, 0x7C,
0xD8, 0x4A, 0xD6, 0x8B, 0x30, 0xEC, 0x25, 0xE0, 0x4C, 0xD9, 0x00, 0x7F, 0xBF, 0xE3, 0x54, 0x05,
0xE9, 0x3A, 0x97, 0x6B, 0xB0, 0x6E, 0x0C, 0xFB, 0xB1, 0x1A, 0xE2, 0xC9, 0xC1, 0x56, 0x47, 0xE9,
0xBA, 0xF1, 0x42, 0xB6, 0x67, 0x5F, 0x0F, 0x96, 0xF7, 0xC9, 0x3C, 0x84, 0x1B, 0x26, 0xE1, 0x4E,
0x3B, 0x6F, 0x66, 0xE6, 0xA0, 0x6A, 0xB0, 0xBF, 0xC6, 0xA5, 0x70, 0x3A, 0xBA, 0x18, 0x9E, 0x27,
0x1A, 0x53, 0x5B, 0x71, 0xB1, 0x94, 0x1E, 0x18, 0xF2, 0xD6, 0x81, 0x02, 0x22, 0xFD, 0x5A, 0x28,
0x91, 0xDB, 0xBA, 0x5D, 0x64, 0xC6, 0xFE, 0x86, 0x83, 0x9C, 0x50, 0x1C, 0x73, 0x03, 0x11, 0xD6,
0xAF, 0x30, 0xF4, 0x2C, 0x77, 0xB2, 0x7D, 0xBB, 0x3F, 0x29, 0x28, 0x57, 0x22, 0xD6, 0x92, 0x8B,
]
)
VERSION_OFFSET = 0x2000
VERSION_SIZE = 16
CRC_SIZE = 2
def _vector_table_looks_valid(image: bytes) -> bool:
if len(image) < 8:
return False
stack_pointer = int.from_bytes(image[0:4], "little")
reset_vector = int.from_bytes(image[4:8], "little")
return (stack_pointer & 0xFFF00000) == 0x20000000 and (reset_vector & 0xFFF00000) == 0x08000000
def is_raw_image(image: bytes) -> bool:
return _vector_table_looks_valid(image)
def has_valid_vendor_crc(image: bytes) -> bool:
if len(image) < CRC_SIZE:
return False
expected_crc = crc_hqx(image[:-CRC_SIZE], 0)
actual_crc = int.from_bytes(image[-CRC_SIZE:], "little")
return expected_crc == actual_crc
def is_packed_image(image: bytes) -> bool:
if len(image) <= VERSION_OFFSET + VERSION_SIZE + CRC_SIZE:
return False
if is_raw_image(image):
return False
return has_valid_vendor_crc(image)
def decode_packed_image(image: bytes) -> tuple[bytes, str]:
if not is_packed_image(image):
raise ValueError("not a packed Quansheng firmware image")
decoded = bytes(a ^ b for a, b in zip(image[:-CRC_SIZE], cycle(OBFUSCATION)))
version_bytes = decoded[VERSION_OFFSET:VERSION_OFFSET + VERSION_SIZE]
version = version_bytes.split(b"\x00", 1)[0].decode("ascii", errors="replace")
raw = decoded[:VERSION_OFFSET] + decoded[VERSION_OFFSET + VERSION_SIZE:]
if not is_raw_image(raw):
raise ValueError("decoded firmware does not look like a raw MCU image")
return raw, version
+80 -8
View File
@@ -17,14 +17,11 @@
import argparse
import serial
import signal
from time import sleep
import os
import _prog as pp
import _dump as dd
import _restore as rr
import _fwcodec as fc
def load_image(file: str) -> bytes:
@@ -42,7 +39,9 @@ def load_image(file: str) -> bytes:
return a
def main_dump(args, ser: serial.Serial):
def main_dump(args, ser):
import _dump as dd
dump_file: str = args.file
@@ -73,7 +72,10 @@ def main_dump(args, ser: serial.Serial):
sleep(0)
def main_restore(args, ser: serial.Serial):
def main_restore(args, ser):
import _dump as dd
import _restore as rr
dump_file: str = args.file
@@ -105,7 +107,9 @@ def main_restore(args, ser: serial.Serial):
sleep(0)
def main_flash(args, ser: serial.Serial):
def main_flash(args, ser):
import _prog as pp
bl_ver: str = args.bl_ver
fw_file: str = args.file
@@ -139,6 +143,56 @@ def main_flash(args, ser: serial.Serial):
sleep(0)
def get_raw_output_path(file: str) -> str:
root, ext = os.path.splitext(file)
if ext:
file_name = "{}.raw{}".format(os.path.basename(root), ext)
candidate = "{}.raw{}".format(root, ext)
else:
file_name = os.path.basename(file) + ".raw.bin"
candidate = file + ".raw.bin"
out_dir = os.path.dirname(candidate) or "."
if os.access(out_dir, os.W_OK):
return candidate
return os.path.join(os.getcwd(), file_name)
def main_decode(args):
fw_file: str = args.file
out_file: str = args.output or get_raw_output_path(fw_file)
try:
fw_image = load_image(fw_file)
if 0 == len(fw_image):
print("Invalid firmware image: {}: empty file".format(fw_file))
return
except Exception as e:
print("Cannot load firmware image '{}': {}".format(fw_file, e))
return
if fc.is_raw_image(fw_image):
print("Firmware image already looks raw")
raw_image = fw_image
version = None
else:
try:
raw_image, version = fc.decode_packed_image(fw_image)
except Exception as e:
print("Cannot decode firmware image '{}': {}".format(fw_file, e))
return
with open(out_file, "wb") as fd:
fd.write(raw_image)
print("Raw firmware image written: {}, size = {}".format(out_file, len(raw_image)))
if version:
print("Packed version field: {}".format(version))
def main():
# Usage:
@@ -146,6 +200,7 @@ def main():
# serialtool.py .. flash [--bl-ver <ver>] <file>
# serialtool.py .. dump {--config | --calib [| --all]} file
# serialtool.py .. restore {--config | --calib [| --all]} file
# serialtool.py decode <packed.bin> [raw.bin]
ap = argparse.ArgumentParser(description="UV-K5 V2 serial tool")
# TODO: have to add option to each of subcommands ??
@@ -198,14 +253,31 @@ def main():
)
ap_restore.add_argument("file", help="input dump file")
ap_decode = sp.add_parser(
"decode", help="decode a packed Quansheng stock firmware into a raw image"
)
ap_decode.add_argument("file", help="input firmware image file")
ap_decode.add_argument(
"output",
nargs="?",
help="output raw firmware image file (default: <input>.raw.bin)",
)
args = ap.parse_args()
port: str = args.port
sub_name: str = args.subcommand
print(ap.description)
# print("Press Ctrl-C to quit")
if "decode" == sub_name:
main_decode(args)
return
port: str = args.port
try:
import serial
ser = serial.Serial(port, baudrate=38400, timeout=0.0001, write_timeout=None)
except Exception as e:
print("Cannot open port '{}': {}".format(port, e))