mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-05 04:27:40 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
435192e3bf | ||
|
|
9c86a7c349 | ||
|
|
0a61a6de12 | ||
|
|
1021ee5e18 | ||
|
|
6692b5191b | ||
|
|
c65db46bad | ||
|
|
a38f611bd5 | ||
|
|
528678a31b | ||
|
|
abc146a1b6 | ||
|
|
c7ff3eeb66 | ||
|
|
a3c72bdd93 | ||
|
|
e4408fcb55 | ||
|
|
5715cd27ef | ||
|
|
253104bb87 | ||
|
|
debf67a027 | ||
|
|
535ee80d2b | ||
|
|
559460739e | ||
|
|
0d69388e6d | ||
|
|
9bf075ea22 | ||
|
|
fe9c4e9432 | ||
|
|
1e4698409b | ||
|
|
39f46a8f93 | ||
|
|
3518ab8294 | ||
|
|
c45b7d2deb | ||
|
|
f904b87172 | ||
|
|
926787ddf3 | ||
|
|
fb9491caa5 | ||
|
|
04e8d47130 | ||
|
|
3bdddce67b | ||
|
|
d5cde97e5b | ||
|
|
66ed14dead | ||
|
|
602dfad6f3 | ||
|
|
0203586cdc | ||
|
|
3e5795255d | ||
|
|
a8912d3208 | ||
|
|
fe602b075d | ||
|
|
e445cfca85 | ||
|
|
18e9de38e0 | ||
|
|
f371795ef8 | ||
|
|
e9181c2287 | ||
|
|
5437bf2d8e | ||
|
|
b972e026ca | ||
|
|
0cad22f369 | ||
|
|
2a13c1819d | ||
|
|
4799eb46ac | ||
|
|
2569e4649d | ||
|
|
a59f3edcff | ||
|
|
027240de54 | ||
|
|
73f44911ed | ||
|
|
c0e3909853 | ||
|
|
31ff3b4df0 | ||
|
|
bbee375623 | ||
|
|
12dfa8a87f | ||
|
|
c9da65117f | ||
|
|
222448e1ad | ||
|
|
e1610bcc6f | ||
|
|
7f906300e3 | ||
|
|
bf7eda8a48 | ||
|
|
2ee1d96e5c | ||
|
|
65ec12bb0d | ||
|
|
dd3fc786b5 | ||
|
|
10868858c5 | ||
|
|
3bd3ebba2c | ||
|
|
39cbbd7e90 | ||
|
|
0567f01786 | ||
|
|
fc12a35418 | ||
|
|
2580acfb07 | ||
|
|
56a6083689 | ||
|
|
29725ed704 | ||
|
|
b44df132d0 | ||
|
|
676f26be34 | ||
|
|
46034ec39f | ||
|
|
e2d21c61c2 | ||
|
|
f8e1b7a436 | ||
|
|
9447eaada9 | ||
|
|
43533c838b | ||
|
|
7306db5f8f | ||
|
|
fbd7b37c21 | ||
|
|
8d3754b42f | ||
|
|
6a556e0601 | ||
|
|
9c1b1cb063 | ||
|
|
ae7bf65383 | ||
|
|
fb1360cce2 | ||
|
|
374e52c4d1 | ||
|
|
42aeac3469 | ||
|
|
98ac57b032 | ||
|
|
3e6e7c87b5 | ||
|
|
e908eda1da | ||
|
|
4fccdb78b9 | ||
|
|
a14127867e | ||
|
|
c02aac265a | ||
|
|
c24592cbe8 | ||
|
|
ff096e12e5 | ||
|
|
5fcc1598dd | ||
|
|
9b8493b450 | ||
|
|
f1df96f1f6 | ||
|
|
f36c524fa7 | ||
|
|
9ab5542326 | ||
|
|
0187c05e65 | ||
|
|
914c354da0 | ||
|
|
a771387edd | ||
|
|
09bc14e64b | ||
|
|
58c0170fb1 | ||
|
|
5b4b3e2a71 | ||
|
|
65a5c9f4ef | ||
|
|
a2936402c2 | ||
|
|
f069ed935f | ||
|
|
dcbbd744ac | ||
|
|
bca06e3382 | ||
|
|
8723863fb0 | ||
|
|
c775fa8b2f | ||
|
|
80e7d4b3d1 | ||
|
|
e6a727eeda | ||
|
|
887e18efe6 | ||
|
|
20b87feb1d | ||
|
|
e2e9c9ba60 | ||
|
|
337eb0cb12 | ||
|
|
504a2b0fb0 | ||
|
|
16b07c6374 | ||
|
|
f5b20d4f80 | ||
|
|
ae14bf5df3 | ||
|
|
9dc85852ba | ||
|
|
f7d29f6a14 | ||
|
|
1184618731 | ||
|
|
3d15d59ce0 | ||
|
|
15fccc31d6 | ||
|
|
48f753caaf | ||
|
|
edc4673983 | ||
|
|
9e03f02296 | ||
|
|
25ea1f827b | ||
|
|
8f11a32c9d | ||
|
|
576b8c552f | ||
|
|
85db5a2394 | ||
|
|
98989d4f0a | ||
|
|
8c976cfffd | ||
|
|
0ce5422db3 | ||
|
|
d939009949 | ||
|
|
50efade5c4 | ||
|
|
682a59fc0e | ||
|
|
3d3f370b86 | ||
|
|
eebeba09db | ||
|
|
6cceb26814 | ||
|
|
a77b132fa5 | ||
|
|
86aa05f1ec | ||
|
|
953f579adb | ||
|
|
d429b3afe8 | ||
|
|
a65a4f601a | ||
|
|
546cd35681 | ||
|
|
8ecf976067 | ||
|
|
3e54dc1c00 | ||
|
|
9eb9a9da5c | ||
|
|
a74529b9ec | ||
|
|
7ccb53572a | ||
|
|
c515188a06 | ||
|
|
cdcb950ccd | ||
|
|
13ca3b8a39 | ||
|
|
34acff0fbf | ||
|
|
fa75ab4243 | ||
|
|
1589d8bdd7 | ||
|
|
a5597a1ab9 | ||
|
|
955b245e66 | ||
|
|
2c4bca4202 | ||
|
|
6c4c240e9a |
No files matched your search
@@ -8,6 +8,7 @@ firmware
|
||||
compile_commands.json
|
||||
.vscode
|
||||
.claude
|
||||
.agents
|
||||
/docs
|
||||
k5_eeprom.raw
|
||||
|
||||
@@ -15,3 +16,4 @@ k5_eeprom.raw
|
||||
__pycache__/
|
||||
|
||||
.DS_Store
|
||||
AGENTS.md
|
||||
+33
-6
@@ -70,6 +70,7 @@ if(ENABLE_FEAT_F4HWN)
|
||||
"AUTHOR_STRING_2=\"${AUTHOR_STRING_2}\""
|
||||
"VERSION_STRING_1=\"${VERSION_STRING_1}\""
|
||||
"VERSION_STRING_2=\"${VERSION_STRING_2}\""
|
||||
"DISPLAY_VERSION_STRING_2=\"${DISPLAY_VERSION_STRING_2}\""
|
||||
"EDITION_STRING=\"${EDITION_STRING}\""
|
||||
ALERT_TOT=10
|
||||
)
|
||||
@@ -171,10 +172,6 @@ enable_feature(ENABLE_SCAN_RANGES)
|
||||
|
||||
# ---- CONTRIB MODS ----
|
||||
|
||||
# Thank you @markusb
|
||||
enable_feature(ENABLE_REGA
|
||||
app/rega.c
|
||||
)
|
||||
# Thank you @reppad
|
||||
enable_feature(ENABLE_EXTRA_UART_CMD)
|
||||
|
||||
@@ -184,8 +181,8 @@ enable_feature(ENABLE_FEAT_F4HWN)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_GAME
|
||||
app/breakout.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
screenshot.c
|
||||
enable_feature(ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
k5viewer.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_SPECTRUM)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RX_TX_TIMER)
|
||||
@@ -197,6 +194,8 @@ enable_feature(ENABLE_FEAT_F4HWN_INV)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_CTR)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_SCAN_PROGRESS)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_SCAN_FASTER)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_SCAN_RSSI)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RESCUE_OPS)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_VOL)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_AUDIO)
|
||||
@@ -209,12 +208,40 @@ enable_feature(ENABLE_FEAT_F4HWN_MEM)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_BEAM
|
||||
app/beam.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
app/rxtx_log.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
app/foxhunt.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_ACTION_PICKER)
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_ACTION_PICKER AND NOT ENABLE_FEAT_F4HWN)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_ACTION_PICKER requires ENABLE_FEAT_F4HWN.")
|
||||
endif()
|
||||
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER)
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER AND NOT (ENABLE_FEAT_F4HWN_RXTX_LOG AND ENABLE_FEAT_F4HWN_K5VIEWER))
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER requires ENABLE_FEAT_F4HWN_RXTX_LOG (log data) and ENABLE_FEAT_F4HWN_K5VIEWER (serial transport).")
|
||||
endif()
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_BEAM AND NOT ENABLE_AIRCOPY)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_BEAM requires ENABLE_AIRCOPY (it reuses g_FSK_Buffer, AIRCOPY_Obfuscate and the FSK packet plumbing).")
|
||||
endif()
|
||||
enable_feature(ENABLE_FEAT_F4HWN_QRCODE)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_LOGO)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_LOGO_SAV)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_MENU_CAT)
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_LOGO_SAV AND NOT ENABLE_FEAT_F4HWN_LOGO)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_LOGO_SAV requires ENABLE_FEAT_F4HWN_LOGO.")
|
||||
endif()
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_MENU_CAT AND NOT ENABLE_FEAT_F4HWN)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_MENU_CAT requires ENABLE_FEAT_F4HWN.")
|
||||
endif()
|
||||
|
||||
# ---- DEBUGGING ----
|
||||
|
||||
|
||||
+145
-42
@@ -42,12 +42,18 @@
|
||||
#include "ui/inputbox.h"
|
||||
#include "ui/main.h"
|
||||
#include "ui/ui.h"
|
||||
#ifdef ENABLE_REGA
|
||||
#include "app/rega.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
#include "app/beam.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
#include "app/foxhunt.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
#include "ui/menu.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_FMRADIO)
|
||||
static void ACTION_Scan_FM(bool bRestart);
|
||||
@@ -61,7 +67,7 @@ inline static void ACTION_1750() { ACTION_AlarmOr1750(true); };
|
||||
|
||||
inline static void ACTION_ScanRestart() { ACTION_Scan(true); };
|
||||
|
||||
void (*action_opt_table[])(void) = {
|
||||
void (*const action_opt_table[])(void) = {
|
||||
[ACTION_OPT_NONE] = &FUNCTION_NOP,
|
||||
[ACTION_OPT_POWER] = &ACTION_Power,
|
||||
[ACTION_OPT_MONITOR] = &ACTION_Monitor,
|
||||
@@ -131,13 +137,15 @@ void (*action_opt_table[])(void) = {
|
||||
#else
|
||||
[ACTION_OPT_RXMODE] = &FUNCTION_NOP,
|
||||
#endif
|
||||
#ifdef ENABLE_REGA
|
||||
[ACTION_OPT_REGA_ALARM] = &ACTION_RegaAlarm,
|
||||
[ACTION_OPT_REGA_TEST] = &ACTION_RegaTest,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
[ACTION_OPT_BEAM] = &ACTION_Beam,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
[ACTION_OPT_RXTX_LOG] = &ACTION_RxTxLog,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
[ACTION_OPT_FOXHUNT] = &ACTION_FoxHunt,
|
||||
#endif
|
||||
};
|
||||
|
||||
static_assert(ARRAY_SIZE(action_opt_table) == ACTION_OPT_LEN);
|
||||
@@ -244,7 +252,7 @@ void ACTION_Scan(bool bRestart)
|
||||
|
||||
// channel mode. Keep scanning but toggle between scan lists
|
||||
RADIO_NextValidList(1);
|
||||
UI_MAIN_NotifyScanProgressDataChanged();
|
||||
UI_MAIN_NotifyScanListChanged();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
|
||||
SETTINGS_WriteCurrentState();
|
||||
@@ -294,6 +302,120 @@ void ACTION_SwitchDemodul(void)
|
||||
}
|
||||
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
inline static bool ACTION_IsBlockedInFM(uint8_t action)
|
||||
{
|
||||
switch (action) {
|
||||
case ACTION_OPT_POWER:
|
||||
case ACTION_OPT_MONITOR:
|
||||
case ACTION_OPT_A_B:
|
||||
case ACTION_OPT_VFO_MR:
|
||||
case ACTION_OPT_SWITCH_DEMODUL:
|
||||
#ifdef ENABLE_VOX
|
||||
case ACTION_OPT_VOX:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
case ACTION_OPT_RXMODE:
|
||||
case ACTION_OPT_MAINONLY:
|
||||
case ACTION_OPT_WN:
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
case ACTION_OPT_RXA:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
case ACTION_OPT_POWER_HIGH:
|
||||
case ACTION_OPT_REMOVE_OFFSET:
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
case ACTION_OPT_BEAM:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
case ACTION_OPT_FOXHUNT:
|
||||
#endif
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
static void ACTION_Execute(uint8_t action)
|
||||
{
|
||||
if (action >= ACTION_OPT_LEN || action_opt_table[action] == NULL) {
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
if (gFmRadioMode && ACTION_IsBlockedInFM(action)) {
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
action_opt_table[action]();
|
||||
}
|
||||
|
||||
uint8_t gActionPickerKey;
|
||||
uint8_t gActionPickerSelection[2] = {1, 1};
|
||||
uint8_t gActionPickerTimeout_500ms;
|
||||
|
||||
bool ACTION_PickerProcessKey(KEY_Code_t key, bool isPressed, bool isHeld)
|
||||
{
|
||||
if (gActionPickerKey == 0)
|
||||
return false;
|
||||
if (isPressed)
|
||||
gActionPickerTimeout_500ms = ACTION_PICKER_TIMEOUT_500MS;
|
||||
uint8_t *selection = &gActionPickerSelection[gActionPickerKey - 1];
|
||||
|
||||
switch (key) {
|
||||
case KEY_UP:
|
||||
case KEY_DOWN:
|
||||
if (isPressed && !isHeld) {
|
||||
if (key == KEY_UP) {
|
||||
if (--*selection == 0)
|
||||
*selection = gSubMenu_SIDEFUNCTIONS_size - 1;
|
||||
}
|
||||
else if (++*selection >= gSubMenu_SIDEFUNCTIONS_size) {
|
||||
*selection = 1;
|
||||
}
|
||||
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
return true;
|
||||
|
||||
case KEY_MENU:
|
||||
if (!isPressed && !isHeld) {
|
||||
const uint8_t action = gSubMenu_SIDEFUNCTIONS[*selection].id;
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
ACTION_Execute(action);
|
||||
}
|
||||
return true;
|
||||
|
||||
case KEY_EXIT:
|
||||
case KEY_F:
|
||||
if (!isPressed) {
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
return true;
|
||||
|
||||
case KEY_PTT:
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
{
|
||||
HideFKeyIcon();
|
||||
@@ -355,45 +477,20 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
}
|
||||
|
||||
// held or released after short press
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
ACTION_Execute(func);
|
||||
#else
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
if (gFmRadioMode) { // do not run these actions in FM radio mode
|
||||
switch (func) {
|
||||
case ACTION_OPT_POWER:
|
||||
case ACTION_OPT_MONITOR:
|
||||
case ACTION_OPT_A_B:
|
||||
case ACTION_OPT_VFO_MR:
|
||||
case ACTION_OPT_SWITCH_DEMODUL:
|
||||
#ifdef ENABLE_VOX
|
||||
case ACTION_OPT_VOX:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
case ACTION_OPT_RXMODE:
|
||||
case ACTION_OPT_MAINONLY:
|
||||
case ACTION_OPT_WN:
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
case ACTION_OPT_RXA:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
case ACTION_OPT_POWER_HIGH:
|
||||
case ACTION_OPT_REMOVE_OFFSET:
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
case ACTION_OPT_BEAM:
|
||||
#endif
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (gFmRadioMode && ACTION_IsBlockedInFM(func)) {
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
action_opt_table[func]();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -417,6 +514,12 @@ void ACTION_FM(void)
|
||||
|
||||
gMonitor = false;
|
||||
|
||||
if (gScanStateDir != SCAN_OFF) {
|
||||
// Stop the channel/frequency scan before switching to the FM radio.
|
||||
gScanKeepResult = false;
|
||||
CHFRSCANNER_Stop();
|
||||
}
|
||||
|
||||
RADIO_SelectVfos();
|
||||
RADIO_SetupRegisters(true);
|
||||
|
||||
@@ -473,7 +576,7 @@ static void ACTION_Scan_FM(bool bRestart)
|
||||
static void ACTION_AlarmOr1750(const bool b1750)
|
||||
{
|
||||
|
||||
if(gEeprom.KEY_LOCK && gEeprom.KEY_LOCK_PTT)
|
||||
if(gEeprom.KEY_LOCK && (gSetting_set_lck & SET_LCK_PTT))
|
||||
return;
|
||||
|
||||
#if defined(ENABLE_ALARM)
|
||||
|
||||
@@ -50,8 +50,18 @@ void ACTION_SwitchDemodul(void);
|
||||
void ACTION_Power_High(void);
|
||||
void ACTION_Remove_Offset(void);
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
void ACTION_RxTxLog(void);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
#define ACTION_PICKER_TIMEOUT_500MS 10u
|
||||
extern uint8_t gActionPickerKey;
|
||||
extern uint8_t gActionPickerSelection[2];
|
||||
extern uint8_t gActionPickerTimeout_500ms;
|
||||
bool ACTION_PickerProcessKey(KEY_Code_t key, bool isPressed, bool isHeld);
|
||||
#endif
|
||||
void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
|
||||
|
||||
#endif
|
||||
+11
-10
@@ -30,8 +30,8 @@
|
||||
#include "settings.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#include "screenshot.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "k5viewer.h"
|
||||
#endif
|
||||
|
||||
static const uint16_t Obfuscation[8] = { 0x6C16, 0xE614, 0x912E, 0x400D, 0x3521, 0x40D5, 0x0313, 0x80E9 };
|
||||
@@ -90,10 +90,11 @@ DECLARE_AIRCOPY_BANK(1)
|
||||
static const AIRCOPY_Segment_t AIRCOPY_Segments_Settings[] = {
|
||||
{ 0xA000, 0xA170, AIRCOPY_WRITE_BYTES },
|
||||
{ 0x880E, 0x886E, AIRCOPY_WRITE_BYTES },
|
||||
{ 0x9000, 0x90D6, AIRCOPY_WRITE_BYTES }, // VFO area (so scan-range source frequencies are replicated)
|
||||
{ 0x9000, 0x90E5, AIRCOPY_WRITE_BYTES }, // VFO area (full 14 VFOs 0x9000..0x90E0) +
|
||||
// Fox Hunt settings tail 0x90E0..0x90E5
|
||||
};
|
||||
|
||||
// total_blocks = ceil(0x170/64) + ceil(0x60/64) + ceil(0xD6/64) = 6 + 2 + 4 = 12
|
||||
// total_blocks = ceil(0x170/64) + ceil(0x60/64) + ceil(0xE5/64) = 6 + 2 + 4 = 12
|
||||
static const AIRCOPY_TransferMap_t AIRCOPY_Map_Settings = {
|
||||
.segments = AIRCOPY_Segments_Settings,
|
||||
.num_segments = 3,
|
||||
@@ -140,8 +141,8 @@ static void AIRCOPY_clear()
|
||||
{
|
||||
crc[i] = 0;
|
||||
}
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
SCREENSHOT_Update(true);
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -170,8 +171,8 @@ static inline void AIRCOPY_CheckComplete(uint16_t *num)
|
||||
if (done >= map->total_blocks)
|
||||
{
|
||||
gAircopyState = AIRCOPY_COMPLETE;
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
SCREENSHOT_Update(false);
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -222,8 +223,8 @@ bool AIRCOPY_SendMessage(void)
|
||||
// Check if transfer is complete
|
||||
if (CurrentSegmentIndex >= map->num_segments) {
|
||||
gAircopyState = AIRCOPY_COMPLETE;
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
SCREENSHOT_Update(false);
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
+398
-24
@@ -39,6 +39,9 @@
|
||||
#include "app/generic.h"
|
||||
#include "app/main.h"
|
||||
#include "app/menu.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#include "app/scanner.h"
|
||||
#if defined(ENABLE_UART) || defined(ENABLE_USB)
|
||||
#include "app/uart.h"
|
||||
@@ -72,24 +75,39 @@
|
||||
#include "sram-overlay.h"
|
||||
#endif
|
||||
#include "ui/battery.h"
|
||||
#include "ui/helper.h"
|
||||
#include "ui/inputbox.h"
|
||||
#include "ui/main.h"
|
||||
#include "ui/menu.h"
|
||||
#include "ui/status.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/welcome.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#include "screenshot.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "k5viewer.h"
|
||||
#endif
|
||||
|
||||
static bool flagSaveVfo;
|
||||
static bool flagSaveSettings;
|
||||
static bool flagSaveChannel;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
static KEY_Code_t gSleepWakeKey = KEY_INVALID;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
static bool gScreenSaverDisplayed;
|
||||
static uint8_t gScreenSaverTick;
|
||||
static uint16_t gMatrixRandom = 0xACE1;
|
||||
static uint8_t gMatrixHeads[32];
|
||||
static uint8_t gMatrixSpeeds[32];
|
||||
static KEY_Code_t gScreenSaverWakeKey = KEY_INVALID;
|
||||
#endif
|
||||
|
||||
static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
|
||||
|
||||
|
||||
void (*ProcessKeysFunctions[])(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) = {
|
||||
void (*const ProcessKeysFunctions[])(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) = {
|
||||
[DISPLAY_MAIN] = &MAIN_ProcessKeys,
|
||||
[DISPLAY_MENU] = &MENU_ProcessKeys,
|
||||
[DISPLAY_SCANNER] = &SCANNER_ProcessKeys,
|
||||
@@ -101,20 +119,210 @@ void (*ProcessKeysFunctions[])(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
#ifdef ENABLE_AIRCOPY
|
||||
[DISPLAY_AIRCOPY] = &AIRCOPY_ProcessKeys,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
[DISPLAY_RXTX_LOG] = &RXTX_LOG_ProcessKeys,
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef ENABLE_REGA
|
||||
// This is a hack for REGA as I need a special display element only for it with no key
|
||||
static_assert(ARRAY_SIZE(ProcessKeysFunctions) == DISPLAY_N_ELEM-1);
|
||||
#else
|
||||
static_assert(ARRAY_SIZE(ProcessKeysFunctions) == DISPLAY_N_ELEM);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
static uint8_t ScreenSaverRandom(void)
|
||||
{
|
||||
gMatrixRandom = (uint16_t)(gMatrixRandom * 2053u + 13849u);
|
||||
return (uint8_t)(gMatrixRandom >> 8);
|
||||
}
|
||||
|
||||
static void ScreenSaverSetPixel(uint8_t x, uint8_t y, bool fill)
|
||||
{
|
||||
const uint8_t pattern = 1u << (y & 7u);
|
||||
|
||||
uint8_t *target = (y < 8) ? &gStatusLine[x] : &gFrameBuffer[(y >> 3) - 1][x];
|
||||
|
||||
if (fill)
|
||||
*target |= pattern;
|
||||
else
|
||||
*target &= (uint8_t)~pattern;
|
||||
}
|
||||
|
||||
static void ScreenSaverDrawMatrixGlyph(uint8_t x, int16_t y, uint8_t glyph)
|
||||
{
|
||||
static const uint8_t glyphs[][3] = {
|
||||
{0x1F, 0x11, 0x1F},
|
||||
{0x15, 0x1F, 0x15},
|
||||
{0x1D, 0x15, 0x17},
|
||||
{0x17, 0x15, 0x1D},
|
||||
{0x1F, 0x04, 0x1F},
|
||||
{0x1B, 0x15, 0x1B},
|
||||
{0x0E, 0x11, 0x0E},
|
||||
{0x11, 0x0A, 0x04}
|
||||
};
|
||||
|
||||
for (uint8_t dx = 0; dx < 3; dx++) {
|
||||
uint8_t pixels = glyphs[glyph & 7u][dx];
|
||||
for (uint8_t dy = 0; dy < 5; dy++) {
|
||||
const int16_t py = y + dy;
|
||||
if ((pixels & (1u << dy)) && py >= 0 && py < LCD_HEIGHT)
|
||||
ScreenSaverSetPixel(x + dx, (uint8_t)py, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ScreenSaverRenderMatrix(bool reset)
|
||||
{
|
||||
if (reset) {
|
||||
for (uint8_t i = 0; i < ARRAY_SIZE(gMatrixHeads); i++) {
|
||||
gMatrixHeads[i] = ScreenSaverRandom() % 112u;
|
||||
gMatrixSpeeds[i] = 1u + (ScreenSaverRandom() % 3u);
|
||||
}
|
||||
} else {
|
||||
for (uint8_t i = 0; i < ARRAY_SIZE(gMatrixHeads); i++) {
|
||||
gMatrixHeads[i] += gMatrixSpeeds[i];
|
||||
if (gMatrixHeads[i] > 112u)
|
||||
gMatrixHeads[i] = ScreenSaverRandom() & 0x0Fu;
|
||||
}
|
||||
}
|
||||
|
||||
UI_StatusClear();
|
||||
UI_DisplayClear();
|
||||
|
||||
for (uint8_t col = 0; col < ARRAY_SIZE(gMatrixHeads); col++) {
|
||||
const uint8_t x = col * 4u;
|
||||
for (uint8_t trail = 0; trail < 8; trail++) {
|
||||
const int16_t y = (int16_t)gMatrixHeads[col] - ((int16_t)trail * 7);
|
||||
const uint8_t glyph = (uint8_t)(col + trail + gMatrixHeads[col]);
|
||||
|
||||
if (trail == 0) {
|
||||
ScreenSaverDrawMatrixGlyph(x, y, glyph);
|
||||
} else if ((trail < 4) || ((glyph & 1u) != 0)) {
|
||||
ScreenSaverDrawMatrixGlyph(x, y, glyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
|
||||
static void ScreenSaverScrollLogoLine(uint8_t *line)
|
||||
{
|
||||
const uint8_t first = line[0];
|
||||
|
||||
memmove(line, line + 1, LCD_WIDTH - 1);
|
||||
line[LCD_WIDTH - 1] = first;
|
||||
}
|
||||
|
||||
static void ScreenSaverRenderLogoPlus(bool reset)
|
||||
{
|
||||
if (reset) {
|
||||
UI_DisplayLogo();
|
||||
return;
|
||||
}
|
||||
|
||||
ScreenSaverScrollLogoLine(gStatusLine);
|
||||
for (uint8_t line = 0; line < FRAME_LINES; line++)
|
||||
ScreenSaverScrollLogoLine(gFrameBuffer[line]);
|
||||
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
|
||||
static void ScreenSaverUpdateViewer(void)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool ScreenSaverCanDisplay(void)
|
||||
{
|
||||
if (gSetting_set_sav == SET_SAV_OFF ||
|
||||
gEeprom.BACKLIGHT_TIME == 0 ||
|
||||
gEeprom.BACKLIGHT_TIME >= 61 ||
|
||||
gScreenSaverDisplayed ||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
gWakeUp ||
|
||||
#endif
|
||||
gCurrentFunction == FUNCTION_TRANSMIT ||
|
||||
FUNCTION_IsRx() ||
|
||||
gPttIsPressed
|
||||
#ifdef ENABLE_FMRADIO
|
||||
|| (gFM_ScanState != FM_SCAN_OFF && !gFM_FoundFrequency)
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
|| gBeamActive
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gScreenToDisplay == DISPLAY_MAIN)
|
||||
return true;
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
if (gScreenToDisplay == DISPLAY_FM)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ScreenSaverTryDisplay(void)
|
||||
{
|
||||
if (!ScreenSaverCanDisplay())
|
||||
return;
|
||||
|
||||
if (gSetting_set_sav == SET_SAV_LOGO)
|
||||
UI_DisplayLogo();
|
||||
else if (gSetting_set_sav == SET_SAV_LOGO_PLUS)
|
||||
ScreenSaverRenderLogoPlus(true);
|
||||
else if (gSetting_set_sav == SET_SAV_MATRIX)
|
||||
ScreenSaverRenderMatrix(true);
|
||||
|
||||
gScreenSaverDisplayed = true;
|
||||
gScreenSaverTick = 0;
|
||||
gUpdateDisplay = false;
|
||||
gUpdateStatus = false;
|
||||
ScreenSaverUpdateViewer();
|
||||
}
|
||||
|
||||
static void ScreenSaverExit(void)
|
||||
{
|
||||
if (gScreenSaverDisplayed) {
|
||||
gScreenSaverDisplayed = false;
|
||||
gUpdateDisplay = true;
|
||||
gUpdateStatus = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APP_IsScreenSaverDisplayed(void)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
return gScreenSaverDisplayed;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void CheckForIncoming(void)
|
||||
{
|
||||
if (!g_SquelchLost)
|
||||
return; // squelch is closed
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
// FM scan in progress: ignore main-channel RX so scanning is not interrupted.
|
||||
// Normal FM listening (FM_SCAN_OFF) still yields to channel signals as before.
|
||||
if (gFmRadioMode && gFM_ScanState != FM_SCAN_OFF)
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
ScreenSaverExit();
|
||||
#endif
|
||||
|
||||
// squelch is open
|
||||
|
||||
if (gScanStateDir == SCAN_OFF)
|
||||
@@ -243,6 +451,12 @@ static void HandleIncoming(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
// Defensive: do not leave FM scan for a main-channel signal.
|
||||
if (gFmRadioMode && gFM_ScanState != FM_SCAN_OFF)
|
||||
return;
|
||||
#endif
|
||||
|
||||
APP_StartListening(gMonitor ? FUNCTION_MONITOR : FUNCTION_RECEIVE);
|
||||
}
|
||||
|
||||
@@ -495,6 +709,10 @@ void APP_StartListening(FUNCTION_Type_t function)
|
||||
{
|
||||
const unsigned int vfo = gEeprom.RX_VFO;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
ScreenSaverExit();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
|
||||
gRxTimerCountdown_500ms = 7200;
|
||||
#endif
|
||||
@@ -569,6 +787,10 @@ void APP_StartListening(FUNCTION_Type_t function)
|
||||
|
||||
FUNCTION_Select(function);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
RXTX_LOG_BeginRx(gRxVfo, function);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
if (function == FUNCTION_MONITOR || gFmRadioMode)
|
||||
#else
|
||||
@@ -912,10 +1134,10 @@ static void HandleVox(void)
|
||||
|
||||
void APP_Update(void)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Parse incoming packets on every tick so serial keys are never missed,
|
||||
// regardless of whether the screen needs redrawing.
|
||||
SCREENSHOT_ParseInput();
|
||||
K5VIEWER_ParseInput();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
@@ -1036,9 +1258,11 @@ void APP_Update(void)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed && gVoiceWriteIndex == 0)
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed && gVoiceWriteIndex == 0
|
||||
&& !UI_MAIN_ShouldHoldScanResume())
|
||||
#else
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed)
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed
|
||||
&& !UI_MAIN_ShouldHoldScanResume())
|
||||
#endif
|
||||
{ // scanning
|
||||
CHFRSCANNER_ContinueScanning();
|
||||
@@ -1373,6 +1597,10 @@ void APP_TimeSlice10ms(void)
|
||||
|
||||
SETTINGS_SaveVfoIndicesFlush();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
RXTX_LOG_Task10ms();
|
||||
#endif
|
||||
|
||||
BACKLIGHT_Update();
|
||||
|
||||
gFlashLightBlinkCounter++;
|
||||
@@ -1396,6 +1624,13 @@ void APP_TimeSlice10ms(void)
|
||||
|
||||
if (gCurrentFunction != FUNCTION_POWER_SAVE || !gRxIdleMode)
|
||||
CheckRadioInterrupts();
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0 && FUNCTION_IsRx()) {
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (gCurrentFunction == FUNCTION_TRANSMIT)
|
||||
{ // transmitting
|
||||
@@ -1416,6 +1651,36 @@ void APP_TimeSlice10ms(void)
|
||||
|
||||
if (gUpdateDisplayCurrent) {
|
||||
gUpdateDisplay = false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
bool screenSaverRendered = false;
|
||||
|
||||
if (gScreenSaverDisplayed) {
|
||||
if (gUpdateDisplayCurrent) {
|
||||
gUpdateDisplayCurrent = false;
|
||||
} else if (gUpdateStatusCurrent) {
|
||||
gUpdateStatusCurrent = false;
|
||||
gUpdateStatus = false;
|
||||
}
|
||||
|
||||
if (gSetting_set_sav == SET_SAV_MATRIX) {
|
||||
if (++gScreenSaverTick >= 8u) {
|
||||
gScreenSaverTick = 0;
|
||||
ScreenSaverRenderMatrix(false);
|
||||
screenSaverRendered = true;
|
||||
}
|
||||
} else if (gSetting_set_sav == SET_SAV_LOGO_PLUS) {
|
||||
if (++gScreenSaverTick >= 16u) {
|
||||
gScreenSaverTick = 0;
|
||||
ScreenSaverRenderLogoPlus(false);
|
||||
screenSaverRendered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gUpdateDisplayCurrent) {
|
||||
GUI_DisplayScreen();
|
||||
}
|
||||
|
||||
@@ -1423,9 +1688,15 @@ void APP_TimeSlice10ms(void)
|
||||
UI_DisplayStatus();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
if (gUpdateDisplayCurrent || gUpdateStatusCurrent) {
|
||||
SCREENSHOT_Update(false);
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
if (gUpdateDisplayCurrent || gUpdateStatusCurrent
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
|| screenSaverRendered
|
||||
#endif
|
||||
) {
|
||||
K5VIEWER_Update(false);
|
||||
} else if (K5VIEWER_HasPendingStateChange()) {
|
||||
K5VIEWER_Update(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1521,6 +1792,10 @@ void APP_TimeSlice10ms(void)
|
||||
|
||||
SCANNER_TimeSlice10ms();
|
||||
|
||||
#if defined(ENABLE_SCAN_RANGES) && defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
CHFRSCANNER_UpdateCssDetection();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_AIRCOPY
|
||||
if (gScreenToDisplay == DISPLAY_AIRCOPY && gAircopyState == AIRCOPY_TRANSFER && gAirCopyIsSendMode == 1) {
|
||||
if (!AIRCOPY_SendMessage()) {
|
||||
@@ -1555,6 +1830,15 @@ void cancelUserInputModes(void)
|
||||
void APP_TimeSlice500ms(void)
|
||||
{
|
||||
gNextTimeslice_500ms = false;
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0 && gActionPickerTimeout_500ms > 0 &&
|
||||
--gActionPickerTimeout_500ms == 0)
|
||||
{
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool exit_menu = false;
|
||||
|
||||
// Skipped authentic device check
|
||||
@@ -1563,6 +1847,21 @@ void APP_TimeSlice500ms(void)
|
||||
if (--gKeypadLocked == 0)
|
||||
gUpdateDisplay = true;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
RXTX_LOG_Tick500ms();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
|
||||
if (gSetting_set_tmr && (gCurrentFunction == FUNCTION_TRANSMIT || FUNCTION_IsRx())) {
|
||||
const uint16_t timerCountdown = (gCurrentFunction == FUNCTION_TRANSMIT)
|
||||
? gTxTimerCountdown_500ms
|
||||
: gRxTimerCountdown_500ms;
|
||||
|
||||
if ((timerCountdown & 1u) != 0u)
|
||||
gUpdateStatus = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gKeyInputCountdown > 0)
|
||||
{
|
||||
if (--gKeyInputCountdown == 0)
|
||||
@@ -1634,15 +1933,23 @@ void APP_TimeSlice500ms(void)
|
||||
&& gEeprom.BACKLIGHT_TIME < 61
|
||||
) {
|
||||
BACKLIGHT_TurnOff();
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
ScreenSaverTryDisplay();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
if (gSleepModeCountdown_500ms == gSetting_set_off * 120 && gWakeUp) {
|
||||
//ST7565_Init();
|
||||
ST7565_FixInterfGlitch();
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
ScreenSaverExit();
|
||||
#endif
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false);
|
||||
gPowerSave_10ms = gEeprom.BATTERY_SAVE * 10;
|
||||
gWakeUp = false;
|
||||
gUpdateDisplay = true;
|
||||
gUpdateStatus = true;
|
||||
}
|
||||
|
||||
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx()
|
||||
@@ -1658,6 +1965,9 @@ void APP_TimeSlice500ms(void)
|
||||
gBacklightCountdown_500ms = 0;
|
||||
gPowerSave_10ms = 1;
|
||||
gWakeUp = true;
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
ScreenSaverExit();
|
||||
#endif
|
||||
// TODO:
|
||||
// PWM_PLUS0_CH0_COMP = 0;
|
||||
BACKLIGHT_SetBrightness(0);
|
||||
@@ -1748,6 +2058,10 @@ void APP_TimeSlice500ms(void)
|
||||
{
|
||||
gEeprom.KEY_LOCK = true; // lock the keyboard
|
||||
gUpdateStatus = true; // lock symbol needs showing
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (exit_menu) {
|
||||
@@ -1815,6 +2129,17 @@ void APP_TimeSlice500ms(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
if (gBacklightCountdown_500ms == 0 &&
|
||||
gEeprom.BACKLIGHT_TIME > 0 &&
|
||||
gEeprom.BACKLIGHT_TIME < 61 &&
|
||||
!gAskToSave &&
|
||||
!gCssBackgroundScan)
|
||||
{
|
||||
ScreenSaverTryDisplay();
|
||||
}
|
||||
#endif
|
||||
|
||||
BATTERY_TimeSlice500ms();
|
||||
SCANNER_TimeSlice500ms();
|
||||
UI_MAIN_TimeSlice500ms();
|
||||
@@ -1876,24 +2201,53 @@ static void ALARM_Off(void)
|
||||
|
||||
static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
if (gSleepWakeKey != KEY_INVALID) {
|
||||
if (Key == gSleepWakeKey && !bKeyPressed)
|
||||
gSleepWakeKey = KEY_INVALID;
|
||||
return;
|
||||
}
|
||||
|
||||
if(gWakeUp)
|
||||
{
|
||||
if(!bKeyPressed || Key == KEY_PTT)
|
||||
if(bKeyPressed || Key == KEY_PTT)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
ScreenSaverExit();
|
||||
#endif
|
||||
BACKLIGHT_TurnOn();
|
||||
|
||||
if(Key != KEY_PTT)
|
||||
{
|
||||
Key = KEY_INVALID;
|
||||
gSleepWakeKey = Key;
|
||||
gBeepToPlay = BEEP_NONE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if(Key != KEY_PTT)
|
||||
{
|
||||
Key = KEY_INVALID;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
if (gScreenSaverWakeKey != KEY_INVALID) {
|
||||
if (Key == gScreenSaverWakeKey && !bKeyPressed)
|
||||
gScreenSaverWakeKey = KEY_INVALID;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bKeyPressed && gScreenSaverDisplayed) {
|
||||
ScreenSaverExit();
|
||||
BACKLIGHT_TurnOn();
|
||||
if (Key != KEY_PTT) {
|
||||
gScreenSaverWakeKey = Key;
|
||||
gBeepToPlay = BEEP_NONE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (Key == KEY_EXIT && !BACKLIGHT_IsOn() && gEeprom.BACKLIGHT_TIME > 0)
|
||||
{ // just turn the light on for now so the user can see what's what
|
||||
@@ -1948,7 +2302,11 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
BACKLIGHT_TurnOn();
|
||||
}
|
||||
|
||||
if (Key == KEY_EXIT && bKeyHeld) { // exit key held pressed
|
||||
if (Key == KEY_EXIT && bKeyHeld
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
&& gActionPickerKey == 0
|
||||
#endif
|
||||
) { // exit key held pressed
|
||||
// clear the live DTMF decoder
|
||||
if (gDTMF_RX_live[0] != 0) {
|
||||
DTMF_clear_input_box_memory();
|
||||
@@ -1985,11 +2343,19 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
}
|
||||
|
||||
bool lowBatPopup = gLowBattery && !gLowBatteryConfirmed && gScreenToDisplay == DISPLAY_MAIN;
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0 &&
|
||||
(gEeprom.KEY_LOCK || lowBatPopup || gScreenToDisplay != DISPLAY_MAIN))
|
||||
gActionPickerKey = 0;
|
||||
if (ACTION_PickerProcessKey(Key, bKeyPressed, bKeyHeld))
|
||||
goto Skip;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN // Disable PTT if KEY_LOCK
|
||||
bool lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT;
|
||||
|
||||
if(!gSetting_set_lck)
|
||||
if((gSetting_set_lck & SET_LCK_PTT) == 0)
|
||||
lck_condition = lck_condition && Key != KEY_PTT;
|
||||
|
||||
if (lck_condition)
|
||||
@@ -2006,6 +2372,12 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
return;
|
||||
}
|
||||
|
||||
// The ACTIONS restriction only applies when the keypad is actually
|
||||
// locked: this block is also entered for the low battery popup, where
|
||||
// action keys must keep working as before
|
||||
const bool passActionKey = ((gSetting_set_lck & SET_LCK_ACTIONS) == 0 || !gEeprom.KEY_LOCK) &&
|
||||
(Key == KEY_SIDE1 || Key == KEY_SIDE2 || (Key == KEY_MENU && bKeyHeld));
|
||||
|
||||
if (Key == KEY_F) { // function/key-lock key
|
||||
if (!bKeyPressed)
|
||||
return;
|
||||
@@ -2020,8 +2392,7 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
// KEY_MENU has a special treatment here, because we want to pass hold event to ACTION_Handle
|
||||
// but we don't want it to complain when initial press happens
|
||||
// we want to react on realese instead
|
||||
else if (Key != KEY_SIDE1 && Key != KEY_SIDE2 && // pass side buttons
|
||||
!(Key == KEY_MENU && bKeyHeld)) // pass KEY_MENU held
|
||||
else if (!passActionKey)
|
||||
{
|
||||
if ((!bKeyPressed || bKeyHeld || (Key == KEY_MENU && bKeyPressed)) && // prevent released or held, prevent KEY_MENU pressed
|
||||
!(Key == KEY_MENU && !bKeyPressed)) // pass KEY_MENU released
|
||||
@@ -2281,7 +2652,10 @@ Skip:
|
||||
gFlagRefreshSetting = false;
|
||||
gMenuCountdown = menu_timeout_500ms;
|
||||
|
||||
MENU_ShowCurrentSetting();
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel != MENU_LEVEL_CAT)
|
||||
#endif
|
||||
MENU_ShowCurrentSetting();
|
||||
}
|
||||
|
||||
if (gFlagPrepareTX) {
|
||||
|
||||
+1
-1
@@ -31,6 +31,6 @@ uint32_t APP_SetFrequencyByStep(VFO_Info_t *pInfo, int8_t direction);
|
||||
void APP_Update(void);
|
||||
void APP_TimeSlice10ms(void);
|
||||
void APP_TimeSlice500ms(void);
|
||||
bool APP_IsScreenSaverDisplayed(void);
|
||||
|
||||
#endif
|
||||
|
||||
+13
-6
@@ -16,8 +16,12 @@
|
||||
|
||||
#include "app/breakout.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#include "screenshot.h"
|
||||
#if defined(ENABLE_UART) || defined(ENABLE_USB)
|
||||
#include "app/uart.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "k5viewer.h"
|
||||
#endif
|
||||
|
||||
static uint32_t randSeed = 1;
|
||||
@@ -304,10 +308,10 @@ static void OnKeyDown(uint8_t key)
|
||||
// HandleUserInput
|
||||
static bool HandleUserInput()
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Parse incoming packets on every tick so serial keys are never missed,
|
||||
// regardless of whether the screen needs redrawing.
|
||||
SCREENSHOT_ParseInput();
|
||||
K5VIEWER_ParseInput();
|
||||
#endif
|
||||
|
||||
kbd.prev = kbd.current;
|
||||
@@ -359,6 +363,9 @@ void APP_RunBreakout(void) {
|
||||
|
||||
while(isInitialized)
|
||||
{
|
||||
#if defined(ENABLE_UART) || defined(ENABLE_USB)
|
||||
UART_ServiceCommands();
|
||||
#endif
|
||||
Tick();
|
||||
if(!isPaused)
|
||||
{
|
||||
@@ -388,10 +395,10 @@ void APP_RunBreakout(void) {
|
||||
ST7565_BlitStatusLine(); // Blank status line
|
||||
ST7565_BlitFullScreen();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
if(isPaused || swap == 0)
|
||||
{
|
||||
SCREENSHOT_Update(false);
|
||||
K5VIEWER_Update(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+143
-4
@@ -1,5 +1,6 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/chFrScanner.h"
|
||||
@@ -21,7 +22,14 @@ bool gScanPauseMode;
|
||||
uint32_t gScanRangeStart;
|
||||
uint32_t gScanRangeStop;
|
||||
|
||||
#define SCAN_RANGE_SKIP_MAX 32
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
DCS_CodeType_t gScanRangeCssType = CODE_TYPE_OFF;
|
||||
uint8_t gScanRangeCssCode = 0xFF;
|
||||
static uint8_t scanRangeCssCandidate = 0xFF;
|
||||
static uint8_t scanRangeCssHitCount = 0;
|
||||
#endif
|
||||
|
||||
#define SCAN_RANGE_SKIP_MAX 64
|
||||
#if (SCAN_RANGE_SKIP_MAX & (SCAN_RANGE_SKIP_MAX - 1)) != 0
|
||||
#error SCAN_RANGE_SKIP_MAX must be a power of two
|
||||
#endif
|
||||
@@ -182,6 +190,58 @@ bool CHFRSCANNER_HasScanRangeExcludedOrdinal(uint32_t first_ordinal, uint32_t la
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
void CHFRSCANNER_UpdateCssDetection(void)
|
||||
{
|
||||
if (!gScanRangeStart || !FUNCTION_IsRx())
|
||||
{
|
||||
gScanRangeCssType = CODE_TYPE_OFF;
|
||||
gScanRangeCssCode = 0xFF;
|
||||
scanRangeCssCandidate = 0xFF;
|
||||
scanRangeCssHitCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t cdcssFreq;
|
||||
uint16_t ctcssFreq;
|
||||
const BK4819_CssScanResult_t result = BK4819_GetCxCSSScanResult(&cdcssFreq, &ctcssFreq);
|
||||
|
||||
if (result == BK4819_CSS_RESULT_CDCSS)
|
||||
{
|
||||
const uint8_t Code = DCS_GetCdcssCode(cdcssFreq);
|
||||
if (Code != 0xFF && Code != gScanRangeCssCode)
|
||||
{
|
||||
gScanRangeCssType = CODE_TYPE_DIGITAL;
|
||||
gScanRangeCssCode = Code;
|
||||
scanRangeCssCandidate = 0xFF;
|
||||
scanRangeCssHitCount = 0;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
}
|
||||
else if (result == BK4819_CSS_RESULT_CTCSS)
|
||||
{
|
||||
const uint8_t Code = DCS_GetCtcssCode(ctcssFreq);
|
||||
if (Code != 0xFF)
|
||||
{
|
||||
if (Code == scanRangeCssCandidate)
|
||||
{
|
||||
if (++scanRangeCssHitCount >= 2 && Code != gScanRangeCssCode)
|
||||
{
|
||||
gScanRangeCssType = CODE_TYPE_CONTINUOUS_TONE;
|
||||
gScanRangeCssCode = Code;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scanRangeCssCandidate = Code;
|
||||
scanRangeCssHitCount = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static void CHFRSCANNER_AbortActiveReception(void)
|
||||
@@ -232,6 +292,65 @@ static uint32_t scanFastPrevFrequency;
|
||||
static bool scanFastLastFullTuneCandidate;
|
||||
static VFO_Info_t scanFastDisplayVfo;
|
||||
static bool scanFastDisplayVfoValid;
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
|
||||
static uint16_t scanRssiSparkline[CHFRSCANNER_RSSI_SPARKLINE_WIDTH];
|
||||
static uint8_t scanRssiSparklineWrite;
|
||||
static uint8_t scanRssiSparklineCount;
|
||||
|
||||
static void ScanRssiSparklineReset(void)
|
||||
{
|
||||
memset(scanRssiSparkline, 0, sizeof(scanRssiSparkline));
|
||||
scanRssiSparklineWrite = 0;
|
||||
scanRssiSparklineCount = 0;
|
||||
}
|
||||
|
||||
static void ScanRssiSparklinePush(uint16_t rssi)
|
||||
{
|
||||
scanRssiSparkline[scanRssiSparklineWrite] = rssi;
|
||||
scanRssiSparklineWrite++;
|
||||
if (scanRssiSparklineWrite >= CHFRSCANNER_RSSI_SPARKLINE_WIDTH)
|
||||
scanRssiSparklineWrite = 0;
|
||||
|
||||
if (scanRssiSparklineCount < CHFRSCANNER_RSSI_SPARKLINE_WIDTH)
|
||||
scanRssiSparklineCount++;
|
||||
}
|
||||
|
||||
bool CHFRSCANNER_HasScanRssiSparkline(void)
|
||||
{
|
||||
return gScanStateDir != SCAN_OFF && scanRssiSparklineCount > 1;
|
||||
}
|
||||
|
||||
uint8_t CHFRSCANNER_GetScanRssiSparklineLevel(uint8_t index)
|
||||
{
|
||||
uint16_t minRssi = SCAN_FAST_RSSI_MAX;
|
||||
uint16_t rssi = 0;
|
||||
uint8_t oldest;
|
||||
|
||||
if (index >= CHFRSCANNER_RSSI_SPARKLINE_WIDTH || scanRssiSparklineCount == 0)
|
||||
return 0;
|
||||
|
||||
if (index < CHFRSCANNER_RSSI_SPARKLINE_WIDTH - scanRssiSparklineCount)
|
||||
return 0;
|
||||
|
||||
oldest = (uint8_t)((scanRssiSparklineWrite + CHFRSCANNER_RSSI_SPARKLINE_WIDTH - scanRssiSparklineCount)
|
||||
% CHFRSCANNER_RSSI_SPARKLINE_WIDTH);
|
||||
index -= (uint8_t)(CHFRSCANNER_RSSI_SPARKLINE_WIDTH - scanRssiSparklineCount);
|
||||
|
||||
for (uint8_t i = 0; i < scanRssiSparklineCount; i++)
|
||||
{
|
||||
const uint16_t sample = scanRssiSparkline[(oldest + i) % CHFRSCANNER_RSSI_SPARKLINE_WIDTH];
|
||||
if (sample != 0 && sample < minRssi)
|
||||
minRssi = sample;
|
||||
}
|
||||
|
||||
rssi = scanRssiSparkline[(oldest + index) % CHFRSCANNER_RSSI_SPARKLINE_WIDTH];
|
||||
if (rssi == 0 || minRssi == SCAN_FAST_RSSI_MAX || rssi <= minRssi + 2)
|
||||
return 0;
|
||||
|
||||
// 48 RSSI units ~= 24 dB. This keeps quiet jitter low while strong hits pop.
|
||||
return (uint8_t)MIN(((uint32_t)(rssi - minRssi) * 5u + 24u) / 48u, 5u);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ScanFastResetState(void)
|
||||
{
|
||||
@@ -538,6 +657,9 @@ static scan_fast_result_t ScanRangeFastPrecheck(void)
|
||||
ScanFastTune(freq);
|
||||
|
||||
const uint16_t rssi = ScanFastReadCandidateRssi();
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
|
||||
ScanRssiSparklinePush(rssi);
|
||||
#endif
|
||||
if (ScanFastIsCandidate(rssi))
|
||||
{
|
||||
ScanRangeFastRefineCandidate(rssi);
|
||||
@@ -573,7 +695,12 @@ static bool MemChannelFastPrecheck(uint16_t channel)
|
||||
scanFastReg30 = BK4819_ReadRegister(BK4819_REG_30) & ~BK4819_REG_30_MASK_ENABLE_AF_DAC;
|
||||
ScanFastTune(frequency);
|
||||
|
||||
if (ScanFastIsCandidate(ScanFastReadCandidateRssi()))
|
||||
const uint16_t rssi = ScanFastReadCandidateRssi();
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
|
||||
ScanRssiSparklinePush(rssi);
|
||||
#endif
|
||||
|
||||
if (ScanFastIsCandidate(rssi))
|
||||
{
|
||||
scanFastLastFullTuneCandidate = true;
|
||||
return true; // signal detected: let the full tune path follow
|
||||
@@ -637,17 +764,24 @@ void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_directi
|
||||
currentScanList = SCAN_NEXT_CHAN_SCANLIST1;
|
||||
gScanStateDir = scan_direction;
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
|
||||
ScanRssiSparklineReset();
|
||||
#endif
|
||||
ScanFastResetState();
|
||||
#endif
|
||||
|
||||
if (IS_MR_CHANNEL(gNextMrChannel))
|
||||
{
|
||||
{
|
||||
bool scanListChanged = false;
|
||||
|
||||
if(!RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT)) {
|
||||
RADIO_NextValidList(1);
|
||||
UI_MAIN_NotifyScanProgressDataChanged();
|
||||
scanListChanged = true;
|
||||
}
|
||||
|
||||
if (storeBackupSettings || scanListChanged)
|
||||
UI_MAIN_NotifyScanListChanged();
|
||||
|
||||
// channel mode
|
||||
if (storeBackupSettings) {
|
||||
initialFrqOrChan = gRxVfo->CHANNEL_SAVE;
|
||||
@@ -820,6 +954,9 @@ void CHFRSCANNER_Stop(void)
|
||||
}
|
||||
|
||||
gScanStateDir = SCAN_OFF;
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_FASTER) && defined(ENABLE_FEAT_F4HWN_SCAN_RSSI)
|
||||
ScanRssiSparklineReset();
|
||||
#endif
|
||||
|
||||
const uint32_t chFr = gScanKeepResult ? lastFoundFrqOrChan : initialFrqOrChan;
|
||||
const bool channelChanged = chFr != initialFrqOrChan;
|
||||
@@ -996,7 +1133,9 @@ static void NextMemChannel(void)
|
||||
|
||||
if (!enabled || chan == 0xFFFF)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
const uint16_t searchStart = gNextMrChannel;
|
||||
#endif
|
||||
chan = RADIO_FindNextChannel(gNextMrChannel + gScanStateDir, gScanStateDir, true, gEeprom.SCAN_LIST_DEFAULT);
|
||||
if (chan == 0xFFFF)
|
||||
{ // no valid channel found -> wrapping back to the first channel
|
||||
|
||||
@@ -15,8 +15,16 @@ extern bool gScanPauseMode;
|
||||
#ifdef ENABLE_SCAN_RANGES
|
||||
extern uint32_t gScanRangeStart;
|
||||
extern uint32_t gScanRangeStop;
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
extern DCS_CodeType_t gScanRangeCssType;
|
||||
extern uint8_t gScanRangeCssCode;
|
||||
#endif
|
||||
|
||||
bool CHFRSCANNER_ExcludeCurrentScanRange(void);
|
||||
bool CHFRSCANNER_HasScanRangeExcludedOrdinal(uint32_t first_ordinal, uint32_t last_ordinal);
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
void CHFRSCANNER_UpdateCssDetection(void);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void CHFRSCANNER_Found(void);
|
||||
@@ -26,6 +34,11 @@ void CHFRSCANNER_ManualResume(const int8_t scan_direction);
|
||||
void CHFRSCANNER_ContinueScanning(void);
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
const VFO_Info_t *CHFRSCANNER_GetScanDisplayVfo(void);
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_RSSI
|
||||
bool CHFRSCANNER_HasScanRssiSparkline(void);
|
||||
uint8_t CHFRSCANNER_GetScanRssiSparklineLevel(uint8_t index);
|
||||
#define CHFRSCANNER_RSSI_SPARKLINE_WIDTH 24u
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_RESUME_STATE) || defined(ENABLE_SCAN_RANGES)
|
||||
|
||||
+1
-1
@@ -474,7 +474,7 @@ void DTMF_Reply(void)
|
||||
if (pString == NULL)
|
||||
return;
|
||||
|
||||
Delay = (gEeprom.DTMF_PRELOAD_TIME < 200) ? 200 : gEeprom.DTMF_PRELOAD_TIME;
|
||||
Delay = MAX(gEeprom.DTMF_PRELOAD_TIME, 200);
|
||||
|
||||
if (gEeprom.DTMF_SIDE_TONE)
|
||||
{ // the user will also hear the transmitted tones
|
||||
|
||||
+19
-13
@@ -94,6 +94,11 @@ int FM_ConfigureChannelState(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FM_SetFrequency(void)
|
||||
{
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
}
|
||||
|
||||
void FM_TurnOff(void)
|
||||
{
|
||||
gFmRadioMode = false;
|
||||
@@ -163,11 +168,10 @@ void FM_Tune(uint16_t Frequency, int8_t Step, bool bFlag)
|
||||
|
||||
gFM_ScanState = Step;
|
||||
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
FM_SetFrequency();
|
||||
}
|
||||
|
||||
void FM_AudioPathOn(void) {
|
||||
BACKLIGHT_TurnOn();
|
||||
AUDIO_AudioPathOn();
|
||||
gEnableSpeaker = true;
|
||||
}
|
||||
@@ -182,13 +186,14 @@ void FM_PlayAndUpdate(void)
|
||||
}
|
||||
|
||||
FM_ConfigureChannelState();
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
FM_SetFrequency();
|
||||
SETTINGS_SaveFM();
|
||||
|
||||
gFmPlayCountdown_10ms = 0;
|
||||
gScheduleFM = false;
|
||||
gAskToSave = false;
|
||||
|
||||
BACKLIGHT_TurnOn();
|
||||
FM_AudioPathOn();
|
||||
}
|
||||
|
||||
@@ -291,7 +296,7 @@ static void Key_DIGITS(KEY_Code_t Key, uint8_t state)
|
||||
gAnotherVoiceID = (VOICE_ID_t)Key;
|
||||
#endif
|
||||
gEeprom.FM_FrequencyPlaying = gEeprom.FM_SelectedFrequency;
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
FM_SetFrequency();
|
||||
gRequestSaveFM = true;
|
||||
return;
|
||||
}
|
||||
@@ -311,7 +316,7 @@ static void Key_DIGITS(KEY_Code_t Key, uint8_t state)
|
||||
#endif
|
||||
gEeprom.FM_SelectedChannel = Channel;
|
||||
gEeprom.FM_FrequencyPlaying = gFM_Channels[Channel];
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
FM_SetFrequency();
|
||||
gRequestSaveFM = true;
|
||||
return;
|
||||
}
|
||||
@@ -366,7 +371,7 @@ static void Key_FUNC(KEY_Code_t Key, uint8_t state)
|
||||
gEeprom.FM_IsMrMode = !gEeprom.FM_IsMrMode;
|
||||
|
||||
if (!FM_ConfigureChannelState()) {
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
FM_SetFrequency();
|
||||
gRequestSaveFM = true;
|
||||
}
|
||||
else
|
||||
@@ -480,7 +485,7 @@ static void Key_MENU(uint8_t state)
|
||||
gFM_Channels[gEeprom.FM_SelectedChannel] = 0xFFFF;
|
||||
|
||||
FM_ConfigureChannelState();
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
FM_SetFrequency();
|
||||
|
||||
gRequestSaveFM = true;
|
||||
}
|
||||
@@ -558,7 +563,7 @@ static void Key_UP_DOWN(uint8_t state, int8_t Step)
|
||||
gRequestSaveFM = true;
|
||||
|
||||
Bail:
|
||||
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying, gEeprom.FM_Band/*, gEeprom.FM_Space*/);
|
||||
FM_SetFrequency();
|
||||
|
||||
gRequestDisplayScreen = DISPLAY_FM;
|
||||
}
|
||||
@@ -612,19 +617,19 @@ void FM_Play(void)
|
||||
if (!gEeprom.FM_IsMrMode)
|
||||
gEeprom.FM_SelectedFrequency = gEeprom.FM_FrequencyPlaying;
|
||||
|
||||
BACKLIGHT_TurnOn();
|
||||
FM_AudioPathOn();
|
||||
|
||||
GUI_SelectNextDisplay(DISPLAY_FM);
|
||||
return;
|
||||
goto Display;
|
||||
}
|
||||
|
||||
if (gFM_ChannelPosition < FM_CHANNELS_MAX)
|
||||
gFM_Channels[gFM_ChannelPosition++] = gEeprom.FM_FrequencyPlaying;
|
||||
|
||||
|
||||
if (gFM_ChannelPosition >= FM_CHANNELS_MAX) {
|
||||
FM_PlayAndUpdate();
|
||||
GUI_SelectNextDisplay(DISPLAY_FM);
|
||||
return;
|
||||
|
||||
goto Display;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +638,7 @@ void FM_Play(void)
|
||||
else
|
||||
FM_Tune(gEeprom.FM_FrequencyPlaying, gFM_ScanState, false);
|
||||
|
||||
Display:
|
||||
GUI_SelectNextDisplay(DISPLAY_FM);
|
||||
}
|
||||
|
||||
|
||||
+1505
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,57 @@
|
||||
/* Copyright 2026 Armel F4HWN
|
||||
* https://github.com/armel
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef APP_FOXHUNT_H
|
||||
#define APP_FOXHUNT_H
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
|
||||
#include "keyboard_state.h"
|
||||
|
||||
#include "../bitmaps.h"
|
||||
#include "../board.h"
|
||||
#include "py32f0xx.h"
|
||||
#include "../driver/backlight.h"
|
||||
#include "../driver/bk4819-regs.h"
|
||||
#include "../driver/bk4819.h"
|
||||
#include "../driver/gpio.h"
|
||||
#include "../driver/keyboard.h"
|
||||
#include "../driver/py25q16.h"
|
||||
#include "../driver/st7565.h"
|
||||
#include "../driver/system.h"
|
||||
#include "../driver/systick.h"
|
||||
#include "../external/printf/printf.h"
|
||||
#include "../font.h"
|
||||
#include "../helper/battery.h"
|
||||
#include "../misc.h"
|
||||
#include "../radio.h"
|
||||
#include "../settings.h"
|
||||
#include "../ui/battery.h"
|
||||
#include "../ui/helper.h"
|
||||
#include "../ui/ui.h"
|
||||
#include "../audio.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
// Entry point wired as an assignable side-key action (see action.c).
|
||||
void ACTION_FoxHunt(void);
|
||||
|
||||
// Self-contained modal loop that owns the screen and keypad until EXIT.
|
||||
void APP_RunFoxHunt(void);
|
||||
|
||||
#endif // ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
#endif // APP_FOXHUNT_H
|
||||
+32
-4
@@ -45,6 +45,7 @@
|
||||
#include "settings.h"
|
||||
#include "ui/inputbox.h"
|
||||
#include "ui/main.h"
|
||||
#include "ui/menu.h"
|
||||
#include "ui/ui.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -105,7 +106,7 @@ static void toggle_chan_scanlist(void)
|
||||
|
||||
gTxVfo->SCANLIST_PARTICIPATION = scanlist;
|
||||
|
||||
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, true, true);
|
||||
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
|
||||
}
|
||||
|
||||
gVfoConfigureMode = VFO_CONFIGURE;
|
||||
@@ -225,6 +226,12 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
|
||||
case KEY_4:
|
||||
HideFKeyIcon();
|
||||
|
||||
if (gScanStateDir != SCAN_OFF) {
|
||||
// Stop the channel/frequency scan before saving the RX mode for Close Call.
|
||||
gScanKeepResult = false;
|
||||
CHFRSCANNER_Stop();
|
||||
}
|
||||
|
||||
gBackup_CROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX;
|
||||
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
|
||||
|
||||
@@ -317,7 +324,7 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
|
||||
|
||||
if (gScanStateDir != SCAN_OFF) {
|
||||
RADIO_NextValidList(isKeyUp ? 1 : -1);
|
||||
UI_MAIN_NotifyScanProgressDataChanged();
|
||||
UI_MAIN_NotifyScanListChanged();
|
||||
} else {
|
||||
// Adjust squelch: UP increments, DOWN decrements
|
||||
if (gSquelchLevelOriginal == 10)
|
||||
@@ -448,6 +455,16 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
gRequestDisplayScreen = DISPLAY_MAIN;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2)) {
|
||||
gActionPickerKey = (Key == KEY_SIDE1) ? 1 : 2;
|
||||
gActionPickerTimeout_500ms = ACTION_PICKER_TIMEOUT_500MS;
|
||||
gUpdateDisplay = true;
|
||||
HideFKeyIcon();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
HideFKeyIcon();
|
||||
|
||||
processFKeyFunction(Key, true);
|
||||
@@ -500,7 +517,7 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
if (value == 0)
|
||||
{
|
||||
gEeprom.SCAN_LIST_DEFAULT = MR_CHANNELS_LIST + 1;
|
||||
UI_MAIN_NotifyScanProgressDataChanged();
|
||||
UI_MAIN_NotifyScanListChanged();
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
|
||||
SETTINGS_WriteCurrentState();
|
||||
#endif
|
||||
@@ -519,7 +536,7 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
RADIO_NextValidList(1);
|
||||
}
|
||||
UI_MAIN_NotifyScanProgressDataChanged();
|
||||
UI_MAIN_NotifyScanListChanged();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
|
||||
SETTINGS_WriteCurrentState();
|
||||
@@ -829,6 +846,11 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld)
|
||||
|
||||
gFlagRefreshSetting = true;
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
gMenuLevel = MENU_LEVEL_CAT;
|
||||
UI_MENU_BuildCategoryScreen();
|
||||
gMenuCursor = (gMenuCatCursor < gMenuListCount) ? gMenuCatCursor : 0;
|
||||
#endif
|
||||
#ifdef ENABLE_VOICE
|
||||
gAnotherVoiceID = VOICE_ID_MENU;
|
||||
#endif
|
||||
@@ -918,6 +940,12 @@ static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (gScanStateDir != SCAN_OFF) {
|
||||
// Stop the channel/frequency scan before saving the RX mode for the CTCSS/DCS scan.
|
||||
gScanKeepResult = false;
|
||||
CHFRSCANNER_Stop();
|
||||
}
|
||||
|
||||
// scan the CTCSS/DCS code
|
||||
gBackup_CROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX;
|
||||
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
|
||||
|
||||
+95
-7
@@ -85,10 +85,10 @@ void MENU_StartCssScan(void)
|
||||
void MENU_CssScanFound(void)
|
||||
{
|
||||
if(gScanCssResultType == CODE_TYPE_DIGITAL || gScanCssResultType == CODE_TYPE_REVERSE_DIGITAL) {
|
||||
gMenuCursor = UI_MENU_GetMenuIdx(MENU_R_DCS);
|
||||
gMenuCursor = UI_MENU_GetViewPos(MENU_R_DCS);
|
||||
}
|
||||
else if(gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {
|
||||
gMenuCursor = UI_MENU_GetMenuIdx(MENU_R_CTCS);
|
||||
gMenuCursor = UI_MENU_GetViewPos(MENU_R_CTCS);
|
||||
}
|
||||
|
||||
MENU_ShowCurrentSetting();
|
||||
@@ -222,6 +222,13 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
|
||||
*pMax = ARRAY_SIZE(gSubMenu_RX_TX) - 1;
|
||||
break;
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
|
||||
case MENU_SET_SAV:
|
||||
//*pMin = 0;
|
||||
*pMax = SET_SAV_LEN - 1;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
#ifdef ENABLE_AM_FIX
|
||||
case MENU_AM_FIX:
|
||||
@@ -419,7 +426,7 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
|
||||
#endif
|
||||
case MENU_SET_LCK:
|
||||
//*pMin = 0;
|
||||
*pMax = ARRAY_SIZE(gSubMenu_SET_LCK) - 1;
|
||||
*pMax = SET_LCK_LEN - 1;
|
||||
break;
|
||||
case MENU_SET_MET:
|
||||
case MENU_SET_GUI:
|
||||
@@ -718,7 +725,7 @@ void MENU_AcceptSetting(void)
|
||||
|
||||
case MENU_LIST_CH:
|
||||
gTxVfo->SCANLIST_PARTICIPATION = gSubMenuSelection;
|
||||
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, false, true);
|
||||
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
|
||||
gVfoConfigureMode = VFO_CONFIGURE;
|
||||
gFlagResetVfos = true;
|
||||
return;
|
||||
@@ -745,7 +752,7 @@ void MENU_AcceptSetting(void)
|
||||
|
||||
case MENU_COMPAND:
|
||||
gTxVfo->Compander = gSubMenuSelection;
|
||||
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true, false, true);
|
||||
SETTINGS_UpdateChannel(gTxVfo->CHANNEL_SAVE, gTxVfo, true);
|
||||
gVfoConfigureMode = VFO_CONFIGURE;
|
||||
gFlagResetVfos = true;
|
||||
// gRequestSaveChannel = 1;
|
||||
@@ -857,7 +864,7 @@ void MENU_AcceptSetting(void)
|
||||
#endif
|
||||
|
||||
case MENU_DEL_CH:
|
||||
SETTINGS_UpdateChannel(gSubMenuSelection, NULL, false, false, true);
|
||||
SETTINGS_UpdateChannel(gSubMenuSelection, NULL, false);
|
||||
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
|
||||
gFlagResetVfos = true;
|
||||
return;
|
||||
@@ -1030,6 +1037,11 @@ void MENU_AcceptSetting(void)
|
||||
case MENU_SET_TMR:
|
||||
gSetting_set_tmr = gSubMenuSelection;
|
||||
break;
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
case MENU_SET_SAV:
|
||||
gSetting_set_sav = gSubMenuSelection;
|
||||
break;
|
||||
#endif
|
||||
case MENU_TX_LOCK:
|
||||
gTxVfo->TX_LOCK = gSubMenuSelection;
|
||||
gRequestSaveChannel = 1;
|
||||
@@ -1497,6 +1509,11 @@ void MENU_ShowCurrentSetting(void)
|
||||
case MENU_SET_TMR:
|
||||
gSubMenuSelection = gSetting_set_tmr;
|
||||
break;
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
case MENU_SET_SAV:
|
||||
gSubMenuSelection = gSetting_set_sav;
|
||||
break;
|
||||
#endif
|
||||
case MENU_TX_LOCK:
|
||||
gSubMenuSelection = gTxVfo->TX_LOCK;
|
||||
break;
|
||||
@@ -1590,6 +1607,36 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{ // saut-par-numero global : depuis l'ecran categories, entre dans All a l'item N
|
||||
const uint8_t allCount = UI_MENU_CategoryItemCount(CAT_ALL);
|
||||
uint16_t value;
|
||||
|
||||
if (gInputBoxIndex >= 2) {
|
||||
gInputBoxIndex = 0;
|
||||
value = (gInputBox[0] * 10) + gInputBox[1];
|
||||
} else {
|
||||
value = gInputBox[0];
|
||||
}
|
||||
|
||||
if (value > 0 && value <= allCount)
|
||||
{
|
||||
gMenuCategory = CAT_ALL;
|
||||
gMenuCatCursor = gMenuListCount - 1; // All = derniere entree de gCatOrder
|
||||
UI_MENU_BuildView();
|
||||
gMenuLevel = MENU_LEVEL_ITEMS;
|
||||
gMenuCursor = value - 1;
|
||||
gFlagRefreshSetting = true;
|
||||
}
|
||||
else if (gInputBoxIndex == 0)
|
||||
{
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!gIsInSubMenu)
|
||||
{
|
||||
switch (gInputBoxIndex)
|
||||
@@ -1804,6 +1851,22 @@ Skip:
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_ITEMS)
|
||||
{ // remonter aux categories au lieu de quitter le menu
|
||||
gCatLastPos[gMenuCategory] = gMenuCursor; // memorise la position dans la categorie
|
||||
gMenuLevel = MENU_LEVEL_CAT;
|
||||
UI_MENU_BuildCategoryScreen();
|
||||
gMenuCursor = gMenuCatCursor;
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
#ifdef ENABLE_VOICE
|
||||
gAnotherVoiceID = VOICE_ID_CANCEL;
|
||||
#endif
|
||||
gPttWasReleased = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
gAnotherVoiceID = VOICE_ID_CANCEL;
|
||||
#endif
|
||||
@@ -1837,13 +1900,28 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{ // niveau categories : MENU descend dans la categorie choisie
|
||||
gMenuCatCursor = gMenuCursor;
|
||||
gMenuCategory = gCatOrder[gMenuCursor];
|
||||
|
||||
UI_MENU_BuildView();
|
||||
gMenuLevel = MENU_LEVEL_ITEMS;
|
||||
gMenuCursor = (gCatLastPos[gMenuCategory] < gMenuListCount) ? gCatLastPos[gMenuCategory] : 0;
|
||||
gIsInSubMenu = false;
|
||||
gFlagRefreshSetting = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!gIsInSubMenu)
|
||||
{
|
||||
const int m = UI_MENU_GetCurrentMenuId();
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
if (m != MENU_SCR)
|
||||
gAnotherVoiceID = MenuList[gMenuCursor].voice_id;
|
||||
gAnotherVoiceID = MenuList[gMenuIndices[gMenuCursor]].voice_id;
|
||||
#endif
|
||||
if (m == MENU_UPCODE
|
||||
|| m == MENU_DWCODE
|
||||
@@ -2043,6 +2121,16 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{ // niveau categories : deplacement simple du curseur
|
||||
gMenuCursor = NUMBER_AddWithWraparound(gMenuCursor, -Direction, 0, gMenuListCount - 1);
|
||||
gFlagRefreshSetting = true; // rearme le timeout menu (ShowCurrentSetting saute au niveau CAT)
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!gEeprom.SET_NAV && gIsInSubMenu) {
|
||||
Direction = -Direction;
|
||||
}
|
||||
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
* REGA Alam and Test
|
||||
* ==================
|
||||
*
|
||||
* This bit of code is to implement the REGA test and alarm functions.
|
||||
* This function sends out a fixed ZVEI tone sequence for testing or alarm purposes.
|
||||
* Further information: https://www.rega.ch/en/our-missions/sites-and-infrastructure/emergency-radio
|
||||
*
|
||||
* There are two REGA ACTIONS: Test and Alarm.
|
||||
* The Test action sends out a fixed ZVEI tone sequence for testing purposes.
|
||||
* The Alarm action sends out a fixed ZVEI tone sequence for alarm purposes.
|
||||
*
|
||||
* These actions can be assigned to a key in the settings menu.
|
||||
*
|
||||
* The Test/Alarm function will perform the following actions:
|
||||
* - Set the radio to transmit mode
|
||||
* - Wait 100ms to allow the radio to switch to transmit mode and stabilize the tramsmitter
|
||||
* - Send out the ZVEI tone sequence
|
||||
* - Wait 100ms to allow the radio to finish transmitting
|
||||
* - Set the radio back to receive mode
|
||||
*
|
||||
* The ZVEI tone squence for alarm is: 21414
|
||||
* The ZVEI tone squence for test is: 21301
|
||||
*
|
||||
* To save space the two tone sequences are stored as precalculated register values.
|
||||
* This avoids the need to calculate the register values at runtime.
|
||||
* They are hardcoded as array of 32bit integers in the rega.h file.
|
||||
*
|
||||
* Copyright 2025 Markus Bärtschi
|
||||
* https://github.com/markusb
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "rega.h"
|
||||
#include "radio.h"
|
||||
#include "action.h"
|
||||
#include "misc.h"
|
||||
#include "settings.h"
|
||||
#include "functions.h"
|
||||
#include "driver/bk4819.h"
|
||||
#include "driver/st7565.h"
|
||||
#include "driver/system.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "bsp/dp32g030/gpio.h"
|
||||
#include "ui/helper.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
|
||||
const uint16_t rega_alarm_tones[5] = {
|
||||
1160, // 2 1160Hz
|
||||
1060, // 1 1060Hz
|
||||
1400, // 4 1400Hz
|
||||
1060, // 1 1060Hz
|
||||
1400, // 4 1400Hz
|
||||
};
|
||||
const uint16_t rega_test_tones[5] = {
|
||||
1160, // 2 1160Hz
|
||||
1060, // 1 1060Hz
|
||||
1260, // 3 1260Hz
|
||||
2400, // 0 2400Hz
|
||||
1060, // 1 1060Hz
|
||||
};
|
||||
|
||||
// Global variable to pass the message to the display
|
||||
char rega_message[16];
|
||||
|
||||
// Transmit the ZVEI tone sequence for alarm
|
||||
void ACTION_RegaAlarm()
|
||||
{
|
||||
const char message[16] = "REGA Alarm";
|
||||
REGA_TransmitZvei(rega_alarm_tones,message);
|
||||
}
|
||||
|
||||
// Tranmit the ZVEI tone sequence for test
|
||||
void ACTION_RegaTest()
|
||||
{
|
||||
const char message[16] = "REGA Test";
|
||||
REGA_TransmitZvei(rega_test_tones,message);
|
||||
}
|
||||
|
||||
// Display the REGA message on the screen
|
||||
void UI_DisplayREGA()
|
||||
{
|
||||
UI_DisplayClear();
|
||||
UI_DisplayPopup(rega_message);
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
|
||||
// Transmit a ZVEI tone sequence on the REGA frequency
|
||||
// Configures the radio on VFO A with the required parameters
|
||||
// tones: array of 5 ZVEI tones
|
||||
// message: message to display on the screen
|
||||
void REGA_TransmitZvei(const uint16_t tones[],const char message[])
|
||||
{
|
||||
// Copy the message text
|
||||
strncpy(rega_message,message,16);
|
||||
// Trigger the display
|
||||
gScreenToDisplay = DISPLAY_REGA;
|
||||
|
||||
// Flash the green LED twice
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
|
||||
SYSTEM_DelayMs(70);
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
|
||||
SYSTEM_DelayMs(50);
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
|
||||
SYSTEM_DelayMs(70);
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
|
||||
|
||||
// Select VFO A
|
||||
gEeprom.TX_VFO = 0;
|
||||
gEeprom.ScreenChannel[gEeprom.TX_VFO] = gEeprom.FreqChannel[gEeprom.TX_VFO];
|
||||
gRxVfo = gTxVfo;
|
||||
gRequestSaveVFO = true;
|
||||
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
|
||||
|
||||
// Set the REGA frequency, no offset
|
||||
gTxVfo->freq_config_RX.Frequency = REGA_FREQUENCY;
|
||||
gTxVfo->freq_config_TX = gTxVfo->freq_config_RX;
|
||||
gTxVfo->TX_OFFSET_FREQUENCY = 0;
|
||||
|
||||
// Set the modulation to FM narrow
|
||||
gTxVfo->Modulation = MODULATION_FM;
|
||||
gTxVfo->CHANNEL_BANDWIDTH = BK4819_FILTER_BW_NARROW;
|
||||
|
||||
// Set Tx Squelch Tone
|
||||
gTxVfo->freq_config_TX.CodeType = CODE_TYPE_CONTINUOUS_TONE;
|
||||
gTxVfo->freq_config_TX.Code = REGA_CTCSS_FREQ_INDEX;
|
||||
BK4819_SetCTCSSFrequency(CTCSS_Options[gTxVfo->freq_config_TX.Code]);
|
||||
// Turn Rx squelch tone off
|
||||
gTxVfo->freq_config_RX.CodeType = CODE_TYPE_OFF;
|
||||
|
||||
// Set the transmit power to high
|
||||
gTxVfo->OUTPUT_POWER = OUTPUT_POWER_HIGH;
|
||||
|
||||
// Lock the keyboard
|
||||
gEeprom.KEY_LOCK = true;
|
||||
|
||||
gRequestSaveChannel = 1;
|
||||
gRequestSaveSettings = true;
|
||||
|
||||
// Configure the receiver
|
||||
BK4819_RX_TurnOn();
|
||||
|
||||
// Set the radio to transmit mode
|
||||
RADIO_PrepareCssTX();
|
||||
FUNCTION_Select(FUNCTION_TRANSMIT);
|
||||
|
||||
// Wait to allow the radio to switch to transmit mode and stabilize the transmitter
|
||||
SYSTEM_DelayMs(ZVEI_PRE_LENGTH_MS);
|
||||
|
||||
// Send out the ZVEI2 tone sequence
|
||||
for (int i = 0; i < ZVEI_NUM_TONES; i++)
|
||||
{
|
||||
BK4819_PlaySingleTone(tones[i], ZVEI_TONE_LENGTH_MS, 100, true);
|
||||
SYSTEM_DelayMs(ZVEI_PAUSE_LENGTH_MS);
|
||||
}
|
||||
|
||||
// Wait to allow the radio to finish transmitting
|
||||
SYSTEM_DelayMs(ZVEI_POST_LENGTH_MS);
|
||||
|
||||
// Flash the green LED twice
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
|
||||
SYSTEM_DelayMs(70);
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
|
||||
SYSTEM_DelayMs(70);
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
|
||||
SYSTEM_DelayMs(70);
|
||||
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
|
||||
|
||||
// Display the normal screen
|
||||
gRequestDisplayScreen = DISPLAY_MAIN;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* REGA Alam and Test
|
||||
* ==================
|
||||
*
|
||||
* This function sends out a fixed ZVEI tone sequence for testing or alarm purposes for the Alpine REGA alarm channel.
|
||||
* Further information: https://www.rega.ch/en/our-missions/sites-and-infrastructure/emergency-radio
|
||||
*
|
||||
* There are two REGA ACTIONS: Test and Alarm.
|
||||
* The Test action sends out a fixed ZVEI tone sequence for testing purposes.
|
||||
* The Alarm action sends out a fixed ZVEI tone sequence for alarm purposes.
|
||||
*
|
||||
* These actions can be assigned to a key in the settings menu.
|
||||
*
|
||||
* The Test/Alarm function will perform the following actions:
|
||||
* - Set the radio configuration to the REGA frequency, CTCSS, FM, full power etc.
|
||||
* - Start transmitting
|
||||
* - Wait 100ms to allow the radio to switch to transmit mode and stabilize the transmitter
|
||||
* - Send out the ZVEI tone sequence
|
||||
* - Wait 100ms to allow the radio to finish transmitting
|
||||
* - Set the radio back to receive mode
|
||||
*
|
||||
* The REGA frequency is 161.300 Mhz
|
||||
*
|
||||
* The ZVEI tone squence for alarm is: 21414
|
||||
* The ZVEI tone squence for test is: 21301
|
||||
*
|
||||
* The two tone sequences are hardcoded in two arrays
|
||||
*
|
||||
* Copyright 2025 Markus Bärtschi
|
||||
*
|
||||
* https://github.com/markusb
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef REGA_H
|
||||
#define REGA_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ZVEI_NUM_TONES 5
|
||||
#define ZVEI_TONE_LENGTH_MS 70 // 70
|
||||
#define ZVEI_PAUSE_LENGTH_MS 10 // 10
|
||||
#define ZVEI_PRE_LENGTH_MS 300
|
||||
#define ZVEI_POST_LENGTH_MS 100
|
||||
#define REGA_CTCSS_FREQ_INDEX 18 // dcs.c: CTCSS_Options[18] = 1230
|
||||
#define REGA_FREQUENCY 16130000 // 43370000 16130000
|
||||
|
||||
void ACTION_RegaAlarm(void);
|
||||
void ACTION_RegaTest(void);
|
||||
void UI_DisplayREGA(void);
|
||||
void REGA_TransmitZvei(const uint16_t[], const char[]);
|
||||
|
||||
#endif
|
||||
+1376
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,89 @@
|
||||
/* Copyright 2026
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
*/
|
||||
|
||||
#ifndef APP_RXTX_LOG_H
|
||||
#define APP_RXTX_LOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "driver/keyboard.h"
|
||||
#include "functions.h"
|
||||
#include "radio.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
|
||||
#define RXTX_LOG_VISIBLE_COUNT 512
|
||||
|
||||
// Field order mirrors the leading fields of RXTX_LogFlashEntry_t
|
||||
// (rxtx_log.c) so both layouts match byte-for-byte up to and including
|
||||
// battVolt, copied in one pass. Scan-only fields (sequence) sit past the
|
||||
// copied prefix in the flash layout and are not cached in RAM.
|
||||
// The channel name is not stored in flash: it is resolved from `channel` when
|
||||
// exporting the K5Viewer packet.
|
||||
typedef struct {
|
||||
uint32_t frequency;
|
||||
uint32_t trafficSeq;
|
||||
uint16_t durationSeconds;
|
||||
uint16_t channel;
|
||||
uint8_t flags;
|
||||
uint8_t sMeter;
|
||||
uint8_t battVolt;
|
||||
} RXTX_LogEntry_t;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
#define RXTX_LOG_K5VIEWER_VERSION 2u
|
||||
#define RXTX_LOG_K5VIEWER_ROW_COUNT 64u
|
||||
#define RXTX_LOG_K5VIEWER_HISTORY_ROW_COUNT 64u
|
||||
#define RXTX_LOG_K5VIEWER_HISTORY_START 0xFFFFFFFFu
|
||||
#define RXTX_LOG_K5VIEWER_NAME_LENGTH 10u
|
||||
#define RXTX_LOG_K5VIEWER_STATUS_ACTIVE (1u << 0)
|
||||
#define RXTX_LOG_K5VIEWER_STATUS_HAS_TRAFFIC (1u << 1)
|
||||
#define RXTX_LOG_K5VIEWER_STATUS_CLEARING (1u << 2)
|
||||
#define RXTX_LOG_K5VIEWER_STATUS_DISABLED (1u << 3)
|
||||
#define RXTX_LOG_K5VIEWER_STATUS_PACKET_SIZE 4u
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t frequency;
|
||||
uint32_t trafficSeq;
|
||||
uint16_t durationSeconds;
|
||||
uint16_t channel;
|
||||
uint8_t flags;
|
||||
uint8_t meter;
|
||||
uint8_t battVolt;
|
||||
char channelName[RXTX_LOG_K5VIEWER_NAME_LENGTH];
|
||||
} RXTX_LogK5ViewerRow_t;
|
||||
|
||||
// On-wire packet layout: a disabled log sends only the 4-byte status header.
|
||||
// An enabled log continues with one live row and
|
||||
// RXTX_LOG_K5VIEWER_ROW_COUNT row slots, zero-padded past the last valid row.
|
||||
// The packet is streamed row by row and never built whole in RAM.
|
||||
#define RXTX_LOG_K5VIEWER_PACKET_SIZE (RXTX_LOG_K5VIEWER_STATUS_PACKET_SIZE + ((RXTX_LOG_K5VIEWER_ROW_COUNT + 1u) * sizeof(RXTX_LogK5ViewerRow_t)))
|
||||
#define RXTX_LOG_K5VIEWER_HISTORY_PACKET_SIZE (RXTX_LOG_K5VIEWER_HISTORY_ROW_COUNT * sizeof(RXTX_LogK5ViewerRow_t))
|
||||
|
||||
uint32_t RXTX_LOG_K5ViewerSignature(void);
|
||||
void RXTX_LOG_SendK5ViewerPacket(void (*send)(const uint8_t *data, uint16_t size));
|
||||
// Sends the page of history rows below `beforeSeq` (HISTORY_START begins
|
||||
// a dump below the live packet's coverage) and returns the bound for the
|
||||
// next page, 0 once the visible history is exhausted.
|
||||
uint32_t RXTX_LOG_SendK5ViewerHistoryPage(uint32_t beforeSeq, void (*send)(const uint8_t *data, uint16_t size));
|
||||
#endif
|
||||
|
||||
void RXTX_LOG_Init(void);
|
||||
bool RXTX_LOG_IsEnabled(void);
|
||||
void RXTX_LOG_BeginRx(const VFO_Info_t *vfo, FUNCTION_Type_t function);
|
||||
void RXTX_LOG_BeginTx(const VFO_Info_t *vfo);
|
||||
void RXTX_LOG_EndActive(void);
|
||||
void RXTX_LOG_Task10ms(void);
|
||||
void RXTX_LOG_Tick500ms(void);
|
||||
const char *RXTX_LOG_GetFilterName(void);
|
||||
|
||||
void ACTION_RxTxLog(void);
|
||||
void RXTX_LOG_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
|
||||
void UI_DisplayRxTxLog(void);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+113
-17
@@ -41,6 +41,101 @@ bool gScanUseCssResult;
|
||||
STEP_Setting_t stepSetting;
|
||||
uint8_t scanHitCount;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SCAN_FREQ_VERIFY_OFF,
|
||||
SCAN_FREQ_VERIFY_FUNDAMENTAL,
|
||||
SCAN_FREQ_VERIFY_HARMONIC
|
||||
} SCAN_FrequencyVerifyState_t;
|
||||
|
||||
static SCAN_FrequencyVerifyState_t scanFreqVerifyState;
|
||||
static uint32_t scanVerifyFundamental;
|
||||
static uint32_t scanVerifyHarmonic;
|
||||
static uint16_t scanVerifyFundamentalRssi;
|
||||
|
||||
static bool SCANNER_ShouldVerifyVhfSecondHarmonic(const uint32_t frequency)
|
||||
{
|
||||
const uint32_t fundamental = frequency / 2;
|
||||
|
||||
return frequency >= (frequencyBandTable[BAND3_137MHz].lower * 2) &&
|
||||
frequency < (frequencyBandTable[BAND4_174MHz].lower * 2) &&
|
||||
fundamental >= frequencyBandTable[BAND3_137MHz].lower &&
|
||||
fundamental < frequencyBandTable[BAND4_174MHz].lower;
|
||||
}
|
||||
|
||||
static void SCANNER_StartCssScanAtFrequency(const uint32_t frequency)
|
||||
{
|
||||
gScanFrequency = frequency;
|
||||
gScanCssResultCode = 0xFF;
|
||||
gScanCssResultType = 0xFF;
|
||||
scanHitCount = 0;
|
||||
gScanUseCssResult = false;
|
||||
gScanProgressIndicator = 0;
|
||||
gScanCssState = SCAN_CSS_STATE_SCANNING;
|
||||
gScanDelay_10ms = scan_delay_10ms;
|
||||
|
||||
BK4819_SetScanFrequency(gScanFrequency);
|
||||
|
||||
if (!gCssBackgroundScan)
|
||||
GUI_SelectNextDisplay(DISPLAY_SCANNER);
|
||||
|
||||
gUpdateStatus = true;
|
||||
}
|
||||
|
||||
static void SCANNER_TuneForFrequencyVerification(const uint32_t frequency)
|
||||
{
|
||||
BK4819_SetFrequency(frequency);
|
||||
BK4819_PickRXFilterPathBasedOnFrequency(frequency);
|
||||
BK4819_RX_TurnOn();
|
||||
}
|
||||
|
||||
static uint16_t SCANNER_ReadVerificationRssi(void)
|
||||
{
|
||||
(void)BK4819_GetRSSI();
|
||||
return BK4819_GetRSSI();
|
||||
}
|
||||
|
||||
static void SCANNER_StartFrequencyVerification(const uint32_t harmonic)
|
||||
{
|
||||
scanVerifyFundamental = harmonic / 2;
|
||||
scanVerifyHarmonic = harmonic;
|
||||
scanFreqVerifyState = SCAN_FREQ_VERIFY_FUNDAMENTAL;
|
||||
|
||||
SCANNER_TuneForFrequencyVerification(scanVerifyFundamental);
|
||||
gScanDelay_10ms = 2;
|
||||
}
|
||||
|
||||
static bool SCANNER_HandleFrequencyVerification(void)
|
||||
{
|
||||
switch (scanFreqVerifyState) {
|
||||
case SCAN_FREQ_VERIFY_FUNDAMENTAL:
|
||||
scanVerifyFundamentalRssi = SCANNER_ReadVerificationRssi();
|
||||
scanFreqVerifyState = SCAN_FREQ_VERIFY_HARMONIC;
|
||||
SCANNER_TuneForFrequencyVerification(scanVerifyHarmonic);
|
||||
gScanDelay_10ms = 2;
|
||||
return true;
|
||||
|
||||
case SCAN_FREQ_VERIFY_HARMONIC: {
|
||||
const uint16_t harmonicRssi = SCANNER_ReadVerificationRssi();
|
||||
const uint16_t margin = 4;
|
||||
uint32_t verifiedFrequency = scanVerifyHarmonic;
|
||||
|
||||
if (scanVerifyFundamentalRssi > harmonicRssi &&
|
||||
scanVerifyFundamentalRssi - harmonicRssi >= margin)
|
||||
{
|
||||
verifiedFrequency = scanVerifyFundamental;
|
||||
}
|
||||
|
||||
scanFreqVerifyState = SCAN_FREQ_VERIFY_OFF;
|
||||
SCANNER_StartCssScanAtFrequency(verifiedFrequency);
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void SCANNER_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
{
|
||||
if (!bKeyHeld && bKeyPressed)
|
||||
@@ -341,7 +436,8 @@ void SCANNER_Start(bool singleFreq)
|
||||
gScanCssState = SCAN_CSS_STATE_OFF;
|
||||
gScanFrequency = 0xFFFFFFFF;
|
||||
|
||||
BK4819_PickRXFilterPathBasedOnFrequency(gScanFrequency);
|
||||
// Keep the RX input path selected by RADIO_SetupRegisters().
|
||||
// 0xFFFFFFFF means "unknown frequency" here and would turn both LNAs off.
|
||||
BK4819_SetFrequencyScan(true);
|
||||
|
||||
gUpdateStatus = true;
|
||||
@@ -366,6 +462,7 @@ void SCANNER_Start(bool singleFreq)
|
||||
g_SquelchLost = false;
|
||||
gScannerSaveState = SCAN_SAVE_NO_PROMPT;
|
||||
gScanProgressIndicator = 0;
|
||||
scanFreqVerifyState = SCAN_FREQ_VERIFY_OFF;
|
||||
}
|
||||
|
||||
void SCANNER_Stop(void)
|
||||
@@ -377,6 +474,7 @@ void SCANNER_Stop(void)
|
||||
gUpdateStatus = true;
|
||||
gCssBackgroundScan = false;
|
||||
gScanUseCssResult = false;
|
||||
scanFreqVerifyState = SCAN_FREQ_VERIFY_OFF;
|
||||
#ifdef ENABLE_VOICE
|
||||
gAnotherVoiceID = VOICE_ID_CANCEL;
|
||||
#endif
|
||||
@@ -398,6 +496,10 @@ void SCANNER_TimeSlice10ms(void)
|
||||
return;
|
||||
}
|
||||
|
||||
if (SCANNER_HandleFrequencyVerification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (gScanCssState) {
|
||||
case SCAN_CSS_STATE_OFF: {
|
||||
// must be RF frequency scanning if we're here ?
|
||||
@@ -420,22 +522,16 @@ void SCANNER_TimeSlice10ms(void)
|
||||
if (scanHitCount < 3) {
|
||||
BK4819_SetFrequencyScan(true);
|
||||
}
|
||||
else if (SCANNER_ShouldVerifyVhfSecondHarmonic(gScanFrequency)) {
|
||||
SCANNER_StartFrequencyVerification(gScanFrequency);
|
||||
}
|
||||
else {
|
||||
BK4819_SetScanFrequency(gScanFrequency);
|
||||
gScanCssResultCode = 0xFF;
|
||||
gScanCssResultType = 0xFF;
|
||||
scanHitCount = 0;
|
||||
gScanUseCssResult = false;
|
||||
gScanProgressIndicator = 0;
|
||||
gScanCssState = SCAN_CSS_STATE_SCANNING;
|
||||
|
||||
if(!gCssBackgroundScan)
|
||||
GUI_SelectNextDisplay(DISPLAY_SCANNER);
|
||||
|
||||
gUpdateStatus = true;
|
||||
SCANNER_StartCssScanAtFrequency(gScanFrequency);
|
||||
}
|
||||
|
||||
gScanDelay_10ms = scan_delay_10ms;
|
||||
if (scanFreqVerifyState == SCAN_FREQ_VERIFY_OFF) {
|
||||
gScanDelay_10ms = scan_delay_10ms;
|
||||
}
|
||||
//gScanDelay_10ms = 1; // 10ms
|
||||
break;
|
||||
}
|
||||
@@ -506,8 +602,8 @@ void SCANNER_TimeSlice500ms(void)
|
||||
if (SCANNER_IsScanning() && gScannerSaveState == SCAN_SAVE_NO_PROMPT && gScanCssState < SCAN_CSS_STATE_FOUND) {
|
||||
gScanProgressIndicator++;
|
||||
#ifndef ENABLE_NO_CODE_SCAN_TIMEOUT
|
||||
if (gScanProgressIndicator > 32) {
|
||||
if (gScanCssState == SCAN_CSS_STATE_SCANNING && !gScanSingleFrequency)
|
||||
if (gScanCssState == SCAN_CSS_STATE_SCANNING && gScanProgressIndicator > 32) {
|
||||
if (!gScanSingleFrequency)
|
||||
gScanCssState = SCAN_CSS_STATE_FOUND;
|
||||
else
|
||||
gScanCssState = SCAN_CSS_STATE_FAILED;
|
||||
@@ -525,4 +621,4 @@ void SCANNER_TimeSlice500ms(void)
|
||||
bool SCANNER_IsScanning(void)
|
||||
{
|
||||
return gCssBackgroundScan || (gScreenToDisplay == DISPLAY_SCANNER);
|
||||
}
|
||||
}
|
||||
+30
-21
@@ -18,6 +18,10 @@
|
||||
#include "audio.h"
|
||||
#include "misc.h"
|
||||
|
||||
#if defined(ENABLE_UART) || defined(ENABLE_USB)
|
||||
#include "app/uart.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SCAN_RANGES
|
||||
#include "chFrScanner.h"
|
||||
#endif
|
||||
@@ -27,8 +31,8 @@
|
||||
#include "ui/helper.h"
|
||||
#include "ui/main.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#include "screenshot.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "k5viewer.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SPECTRUM
|
||||
@@ -73,7 +77,7 @@ static uint16_t blacklistFreqs[15];
|
||||
static uint8_t blacklistFreqsIdx;
|
||||
#endif
|
||||
|
||||
const char *bwOptions[] = {"25", "12.5", "6.25"};
|
||||
const char *const bwOptions[] = {"25", "12.5", "6.25"};
|
||||
const uint8_t modulationTypeTuneSteps[] = {100, 50, 10};
|
||||
const uint8_t modTypeReg47Values[] = {1, 7, 5};
|
||||
|
||||
@@ -176,7 +180,7 @@ char freqInputString[11];
|
||||
uint8_t menuState = 0;
|
||||
uint16_t listenT = 0;
|
||||
|
||||
RegisterSpec registerSpecs[] = {
|
||||
const RegisterSpec registerSpecs[] = {
|
||||
{},
|
||||
{"LNAs", BK4819_REG_13, 8, 0b11, 1},
|
||||
{"LNA", BK4819_REG_13, 5, 0b111, 1},
|
||||
@@ -433,8 +437,15 @@ static void ToggleAFDAC(bool on)
|
||||
BK4819_WriteRegister(BK4819_REG_30, Reg);
|
||||
}
|
||||
|
||||
static uint32_t NormalizeScanFrequency(uint32_t f)
|
||||
{
|
||||
const uint16_t step = scanStepValues[settings.scanStepIndex];
|
||||
return (step == 833) ? FREQUENCY_RoundToStep(f, step) : f;
|
||||
}
|
||||
|
||||
static void SetF(uint32_t f)
|
||||
{
|
||||
f = NormalizeScanFrequency(f);
|
||||
fMeasure = f;
|
||||
|
||||
BK4819_SetFrequency(fMeasure);
|
||||
@@ -451,6 +462,8 @@ static void SetF(uint32_t f)
|
||||
// cutting the SPI bus activity that causes SPI-induced audio interference.
|
||||
static void SetFScan(uint32_t f)
|
||||
{
|
||||
f = NormalizeScanFrequency(f);
|
||||
|
||||
// Refresh RF path only when crossing the VHF/UHF boundary (280 MHz)
|
||||
if ((f < 28000000) != (fMeasure < 28000000))
|
||||
BK4819_PickRXFilterPathBasedOnFrequency(f);
|
||||
@@ -756,7 +769,7 @@ static void UpdateScanInfo()
|
||||
if (scanInfo.rssi > scanInfo.rssiMax)
|
||||
{
|
||||
scanInfo.rssiMax = scanInfo.rssi;
|
||||
scanInfo.fPeak = scanInfo.f;
|
||||
scanInfo.fPeak = NormalizeScanFrequency(scanInfo.f);
|
||||
scanInfo.iPeak = scanInfo.i;
|
||||
}
|
||||
|
||||
@@ -1593,6 +1606,7 @@ static void ShowChannelName(uint32_t f)
|
||||
{
|
||||
static uint32_t channelF = 0;
|
||||
static char channelName[12];
|
||||
f = NormalizeScanFrequency(f);
|
||||
|
||||
// Channel name starts at x=43 (fixed), leaving room for the dBm
|
||||
// string on the left (max ~40 px) and battery indicator at x=116.
|
||||
@@ -1604,7 +1618,7 @@ static void ShowChannelName(uint32_t f)
|
||||
if (f != channelF) {
|
||||
channelF = f;
|
||||
unsigned int i;
|
||||
memset(channelName, 0, sizeof(channelName));
|
||||
channelName[0] = 0;
|
||||
for (i = 0; IS_MR_CHANNEL(i); i++)
|
||||
{
|
||||
if (RADIO_CheckValidChannel(i, false, 0))
|
||||
@@ -1632,6 +1646,7 @@ static void FormatFrequency(uint32_t freq, char *buffer) {
|
||||
|
||||
static void DrawF(uint32_t f)
|
||||
{
|
||||
f = NormalizeScanFrequency(f);
|
||||
FormatFrequency(f, String);
|
||||
// Align frequency with channel name in status bar (both at x=43).
|
||||
// Left-aligned (End == Start = 43) so it does not collide with BW at x=108.
|
||||
@@ -1715,16 +1730,7 @@ static bool SpectrumColumnAtOrAboveY(const uint8_t *topY, uint8_t x, uint8_t y)
|
||||
|
||||
static uint8_t GetScanStepTextWidth()
|
||||
{
|
||||
uint16_t whole = GetScanStep() / 100;
|
||||
uint8_t digits = 1;
|
||||
|
||||
while (whole >= 10)
|
||||
{
|
||||
whole /= 10;
|
||||
digits++;
|
||||
}
|
||||
|
||||
return (digits + 4) * 4; // "%u.%02uk", 4 px advance per char
|
||||
return (sprintf(NULL, "%u", GetScanStep() / 100) + 4) * 4; // "%u.%02uk", 4 px advance per char
|
||||
}
|
||||
|
||||
static void DrawRssiTriggerLevel(const uint8_t *topY)
|
||||
@@ -2459,10 +2465,10 @@ static void UpdateListening()
|
||||
|
||||
static void Tick()
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Parse incoming packets on every tick so serial keys are never missed,
|
||||
// regardless of whether the screen needs redrawing.
|
||||
SCREENSHOT_ParseInput();
|
||||
K5VIEWER_ParseInput();
|
||||
#endif
|
||||
|
||||
if (gNextTimeslice)
|
||||
@@ -2530,9 +2536,9 @@ static void Tick()
|
||||
if (redrawScreen || ++renderTimer >= RENDER_PERIOD_TICKS)
|
||||
{
|
||||
Render();
|
||||
// For screenshot
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
SCREENSHOT_Update(false);
|
||||
// For K5Viewer
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(false);
|
||||
#endif
|
||||
redrawScreen = false;
|
||||
renderTimer = 0;
|
||||
@@ -2606,6 +2612,9 @@ void APP_RunSpectrum()
|
||||
|
||||
while (isInitialized)
|
||||
{
|
||||
#if defined(ENABLE_UART) || defined(ENABLE_USB)
|
||||
UART_ServiceCommands();
|
||||
#endif
|
||||
Tick();
|
||||
}
|
||||
|
||||
|
||||
+16
-3
@@ -863,7 +863,20 @@ void UART_HandleCommand(uint32_t Port)
|
||||
#endif
|
||||
} // switch
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
gUART_LockScreenshot = 20; // lock screenshot
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
gUART_LockK5Viewer = 20; // lock the K5Viewer stream
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void UART_ServiceCommands(void)
|
||||
{
|
||||
#ifdef ENABLE_USB
|
||||
if (UART_IsCommandAvailable(UART_PORT_VCP))
|
||||
UART_HandleCommand(UART_PORT_VCP);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_UART
|
||||
if (UART_IsCommandAvailable(UART_PORT_UART))
|
||||
UART_HandleCommand(UART_PORT_UART);
|
||||
#endif
|
||||
}
|
||||
+1
-1
@@ -32,6 +32,6 @@ enum
|
||||
|
||||
bool UART_IsCommandAvailable(uint32_t Port);
|
||||
void UART_HandleCommand(uint32_t Port);
|
||||
void UART_ServiceCommands(void);
|
||||
|
||||
#endif
|
||||
|
||||
+144
@@ -57,6 +57,21 @@ const uint8_t gFontLight[9] =
|
||||
0b00001100,
|
||||
};
|
||||
|
||||
// Same bulb outline as gFontLight with the inner filament removed,
|
||||
// used when the manual backlight is currently off
|
||||
const uint8_t gFontLightOff[9] =
|
||||
{
|
||||
0b00001100,
|
||||
0b00010010,
|
||||
0b00100001,
|
||||
0b01100001,
|
||||
0b01100001,
|
||||
0b01100001,
|
||||
0b00100001,
|
||||
0b00010010,
|
||||
0b00001100,
|
||||
};
|
||||
|
||||
const uint8_t gFontMute[12] =
|
||||
{
|
||||
0b00011100,
|
||||
@@ -286,6 +301,135 @@ const uint8_t BITMAP_NOAA[12] =
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
const uint8_t BITMAP_FoxHuntSignal[10] =
|
||||
{ // point source + the speaker bitmap's two sound waves, oriented horizontally
|
||||
0b00001000,
|
||||
0b00011100,
|
||||
0b00011100,
|
||||
0b00001000,
|
||||
0b00000000,
|
||||
0b00100010,
|
||||
0b00011100,
|
||||
0b01000001,
|
||||
0b00100010,
|
||||
0b00011100
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntSpeaker[10] =
|
||||
{ // speaker cone + two sound waves (bit0 = top row)
|
||||
0b00011100,
|
||||
0b00011100,
|
||||
0b00111110,
|
||||
0b01111111,
|
||||
0b00000000,
|
||||
0b00100010,
|
||||
0b00011100,
|
||||
0b01000001,
|
||||
0b00100010,
|
||||
0b00011100
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntUp[11] =
|
||||
{ // filled up triangle (getting nearer)
|
||||
0b00100000,
|
||||
0b00110000,
|
||||
0b00111000,
|
||||
0b00111100,
|
||||
0b00111110,
|
||||
0b00111111,
|
||||
0b00111110,
|
||||
0b00111100,
|
||||
0b00111000,
|
||||
0b00110000,
|
||||
0b00100000
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntDown[11] =
|
||||
{ // filled down triangle (getting farther)
|
||||
0b00000001,
|
||||
0b00000011,
|
||||
0b00000111,
|
||||
0b00001111,
|
||||
0b00011111,
|
||||
0b00111111,
|
||||
0b00011111,
|
||||
0b00001111,
|
||||
0b00000111,
|
||||
0b00000011,
|
||||
0b00000001
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntFlat[11] =
|
||||
{ // equals sign (stable)
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010,
|
||||
0b00010010
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntBars[11] =
|
||||
{ // ascending bars = S-meter (staircase) gauge mode (bit0 = top row)
|
||||
0b01000000,
|
||||
0b01000000,
|
||||
0b00000000,
|
||||
0b01110000,
|
||||
0b01110000,
|
||||
0b00000000,
|
||||
0b01111100,
|
||||
0b01111100,
|
||||
0b00000000,
|
||||
0b01111111,
|
||||
0b01111111
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntGraph[15] =
|
||||
{ // sine wave = signal-history gauge mode (bit0 = top row)
|
||||
0b00001000,
|
||||
0b00000100,
|
||||
0b00000010,
|
||||
0b00000100,
|
||||
0b00001000,
|
||||
0b00010000,
|
||||
0b00100000,
|
||||
0b00010000,
|
||||
0b00001000,
|
||||
0b00000100,
|
||||
0b00000010,
|
||||
0b00000100,
|
||||
0b00001000,
|
||||
0b00010000,
|
||||
0b00100000
|
||||
};
|
||||
|
||||
const uint8_t BITMAP_FoxHuntTx[16] =
|
||||
{
|
||||
0b00011100,
|
||||
0b00100010,
|
||||
0b01000001,
|
||||
0b00011100,
|
||||
0b00100010,
|
||||
0b00000000,
|
||||
0b00001000,
|
||||
0b00011100,
|
||||
0b00011100,
|
||||
0b00001000,
|
||||
0b00000000,
|
||||
0b00100010,
|
||||
0b00011100,
|
||||
0b01000001,
|
||||
0b00100010,
|
||||
0b00011100
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_CUSTOM_MENU_LAYOUT
|
||||
const uint8_t BITMAP_CurrentIndicator[8] = {
|
||||
0xFF,
|
||||
|
||||
@@ -12,6 +12,7 @@ extern const uint8_t gFontS[6];
|
||||
|
||||
extern const uint8_t gFontKeyLock[9];
|
||||
extern const uint8_t gFontLight[9];
|
||||
extern const uint8_t gFontLightOff[9];
|
||||
extern const uint8_t gFontMute[12];
|
||||
|
||||
extern const uint8_t gFontXB[2][6];
|
||||
@@ -44,6 +45,17 @@ extern const uint8_t BITMAP_compand[6];
|
||||
|
||||
extern const uint8_t BITMAP_NOAA[12];
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
extern const uint8_t BITMAP_FoxHuntSignal[10];
|
||||
extern const uint8_t BITMAP_FoxHuntSpeaker[10];
|
||||
extern const uint8_t BITMAP_FoxHuntUp[11];
|
||||
extern const uint8_t BITMAP_FoxHuntDown[11];
|
||||
extern const uint8_t BITMAP_FoxHuntFlat[11];
|
||||
extern const uint8_t BITMAP_FoxHuntBars[11];
|
||||
extern const uint8_t BITMAP_FoxHuntGraph[15];
|
||||
extern const uint8_t BITMAP_FoxHuntTx[16];
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_CUSTOM_MENU_LAYOUT
|
||||
extern const uint8_t BITMAP_CurrentIndicator[8];
|
||||
#endif
|
||||
|
||||
+55
-5
@@ -32,7 +32,9 @@
|
||||
#endif
|
||||
|
||||
#define PWM_FREQ 4000
|
||||
#define DUTY_CYCLE_LEVELS 64
|
||||
// 32 levels keep every step of the value[] table distinct after
|
||||
// quantization while halving the DMA duty cycle buffer (128 bytes saved)
|
||||
#define DUTY_CYCLE_LEVELS 32
|
||||
|
||||
#define DUTY_CYCLE_ON_VALUE GPIO_PIN_MASK(GPIO_PIN_BACKLIGHT)
|
||||
#define DUTY_CYCLE_OFF_VALUE (DUTY_CYCLE_ON_VALUE << 16)
|
||||
@@ -52,12 +54,23 @@ static int16_t currentBrightness = 0;
|
||||
static int16_t targetBrightness = 0;
|
||||
static int16_t fadeStep = 0;
|
||||
|
||||
static void BACKLIGHT_SetHardwareBrightness(uint8_t brightness);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
// Power-on fade-in tuning.
|
||||
// STEPS = number of intermediate brightness stops (smoothness).
|
||||
// STEP_MS = pacing between stops.
|
||||
// STEPS * STEP_MS ~= total fade duration in ms.
|
||||
#define BL_STARTUP_FADE_STEPS 40
|
||||
#define BL_STARTUP_FADE_STEP_MS 12
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
const uint8_t value[] = {
|
||||
0, // 0 off
|
||||
8, // 1 visible in the dark
|
||||
14, // 2
|
||||
22, // 3
|
||||
16, // 2
|
||||
24, // 3
|
||||
32, // 4
|
||||
48, // 5
|
||||
72, // 6
|
||||
@@ -107,7 +120,12 @@ void BACKLIGHT_InitHardware()
|
||||
|
||||
static void BACKLIGHT_Sound(void)
|
||||
{
|
||||
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_SOUND || gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_ALL)
|
||||
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_SOUND ||
|
||||
gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_ALL
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO
|
||||
|| gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_LOGO
|
||||
#endif
|
||||
)
|
||||
{
|
||||
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
|
||||
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
|
||||
@@ -123,6 +141,37 @@ void BACKLIGHT_UpdateTickless(void) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
// Soft, progressive power-on fade-in.
|
||||
// Ramps from the current brightness (0 at power-on) to targetBrightness using
|
||||
// a smoothstep (3x^2 - 2x^3) easing curve. Easing gently at both ends reads as
|
||||
// a smooth, progressive fade instead of the abrupt linear ramp, and stepping
|
||||
// through many fine stops keeps it fluid even with the 32-level PWM.
|
||||
static void BACKLIGHT_FadeInStartup(void)
|
||||
{
|
||||
const int16_t from = currentBrightness; // 0 at power-on
|
||||
const int16_t span = targetBrightness - from; // ramp amplitude
|
||||
|
||||
if (span <= 0) {
|
||||
gUpdateBacklight = false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t s = 1; s < BL_STARTUP_FADE_STEPS; s++) {
|
||||
// x in Q8 (0..256), e = smoothstep(x) in Q16 (0..65536)
|
||||
uint32_t x = (uint32_t)s * 256 / BL_STARTUP_FADE_STEPS;
|
||||
uint32_t e = (x * x * (768 - 2 * x)) >> 8;
|
||||
currentBrightness = from + (int16_t)(((uint32_t)span * e) >> 16);
|
||||
BACKLIGHT_SetHardwareBrightness((uint8_t)currentBrightness);
|
||||
SYSTEM_DelayMs(BL_STARTUP_FADE_STEP_MS);
|
||||
}
|
||||
|
||||
currentBrightness = targetBrightness;
|
||||
BACKLIGHT_SetHardwareBrightness((uint8_t)currentBrightness);
|
||||
gUpdateBacklight = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void BACKLIGHT_TurnOn(void)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
@@ -156,10 +205,11 @@ void BACKLIGHT_TurnOn(void)
|
||||
if(startup)
|
||||
#endif
|
||||
{
|
||||
BACKLIGHT_UpdateTickless();
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
BACKLIGHT_FadeInStartup();
|
||||
BACKLIGHT_Sound();
|
||||
#else
|
||||
BACKLIGHT_UpdateTickless();
|
||||
startup = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
+21
-5
@@ -411,6 +411,11 @@ void BK4819_ToggleGpioOut(BK4819_GPIO_PIN_t Pin, bool bSet)
|
||||
BK4819_WriteRegister(BK4819_REG_33, gBK4819_GpioOutState);
|
||||
}
|
||||
|
||||
bool BK4819_IsGpioOutSet(BK4819_GPIO_PIN_t Pin)
|
||||
{
|
||||
return (gBK4819_GpioOutState & (0x40u >> Pin)) != 0;
|
||||
}
|
||||
|
||||
void BK4819_SetCDCSSCodeWord(uint32_t CodeWord)
|
||||
{
|
||||
// REG_51
|
||||
@@ -1719,7 +1724,7 @@ void BK4819_PrepareFSKReceive(void)
|
||||
BK4819_WriteRegister(BK4819_REG_59, 0x3068);
|
||||
}
|
||||
|
||||
static void BK4819_PlayRogerNormal(void)
|
||||
static void BK4819_PlayRogerNormal(BK4819_FilterBandwidth_t Bandwidth)
|
||||
{
|
||||
#if 0
|
||||
const uint32_t tone1_Hz = 500;
|
||||
@@ -1734,12 +1739,19 @@ static void BK4819_PlayRogerNormal(void)
|
||||
BK4819_EnterTxMute();
|
||||
BK4819_SetAF(BK4819_AF_MUTE);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_70, BK4819_REG_70_ENABLE_TONE1 | (66u << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
|
||||
const uint8_t rogerToneGain = (Bandwidth == BK4819_FILTER_BW_WIDE) ? 32u : 66u;
|
||||
|
||||
if (Bandwidth == BK4819_FILTER_BW_WIDE)
|
||||
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_NARROW, true);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
|
||||
|
||||
BK4819_EnableTXLink();
|
||||
SYSTEM_DelayMs(50);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
|
||||
BK4819_WriteRegister(BK4819_REG_70,
|
||||
BK4819_REG_70_ENABLE_TONE1 |
|
||||
(rogerToneGain << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
|
||||
|
||||
BK4819_ExitTxMute();
|
||||
SYSTEM_DelayMs(80);
|
||||
@@ -1752,6 +1764,10 @@ static void BK4819_PlayRogerNormal(void)
|
||||
BK4819_EnterTxMute();
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_70, 0x0000);
|
||||
|
||||
if (Bandwidth == BK4819_FILTER_BW_WIDE)
|
||||
BK4819_SetFilterBandwidth(Bandwidth, true);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_30, 0xC1FE); // 1 1 0000 0 1 1111 1 1 1 0
|
||||
}
|
||||
|
||||
@@ -1804,10 +1820,10 @@ void BK4819_PlayRogerMDC(void)
|
||||
BK4819_WriteRegister(BK4819_REG_58, 0x0000);
|
||||
}
|
||||
|
||||
void BK4819_PlayRoger(void)
|
||||
void BK4819_PlayRoger(BK4819_FilterBandwidth_t Bandwidth)
|
||||
{
|
||||
if (gEeprom.ROGER == ROGER_MODE_ROGER) {
|
||||
BK4819_PlayRogerNormal();
|
||||
BK4819_PlayRogerNormal(Bandwidth);
|
||||
} else if (gEeprom.ROGER == ROGER_MODE_MDC) {
|
||||
BK4819_PlayRogerMDC();
|
||||
}
|
||||
|
||||
+2
-1
@@ -77,6 +77,7 @@ void BK4819_SetAGC(bool enable);
|
||||
void BK4819_InitAGC(bool amModulation);
|
||||
|
||||
void BK4819_ToggleGpioOut(BK4819_GPIO_PIN_t Pin, bool bSet);
|
||||
bool BK4819_IsGpioOutSet(BK4819_GPIO_PIN_t Pin);
|
||||
|
||||
void BK4819_SetCDCSSCodeWord(uint32_t CodeWord);
|
||||
void BK4819_SetCTCSSFrequency(uint32_t BaudRate);
|
||||
@@ -169,7 +170,7 @@ uint8_t BK4819_GetCTCType(void);
|
||||
void BK4819_SendFSKData(uint16_t *pData);
|
||||
void BK4819_PrepareFSKReceive(void);
|
||||
|
||||
void BK4819_PlayRoger(void);
|
||||
void BK4819_PlayRoger(BK4819_FilterBandwidth_t Bandwidth);
|
||||
|
||||
void BK4819_Enable_AfDac_DiscMode_TxDsp(void);
|
||||
|
||||
|
||||
+30
-11
@@ -441,6 +441,11 @@ void BK4819_ToggleGpioOut(BK4819_GPIO_PIN_t Pin, bool bSet)
|
||||
BK4819_WriteRegister(BK4819_REG_33, gBK4819_GpioOutState);
|
||||
}
|
||||
|
||||
bool BK4819_IsGpioOutSet(BK4819_GPIO_PIN_t Pin)
|
||||
{
|
||||
return (gBK4819_GpioOutState & (0x40u >> Pin)) != 0;
|
||||
}
|
||||
|
||||
void BK4819_SetCDCSSCodeWord(uint32_t CodeWord)
|
||||
{
|
||||
// REG_51
|
||||
@@ -696,11 +701,11 @@ void BK4819_SetFilterBandwidth(const BK4819_FilterBandwidth_t Bandwidth, const b
|
||||
break;
|
||||
|
||||
case BK4819_FILTER_BW_NARROWER: // 6.25kHz
|
||||
// 0x205C = (0b010 << 12) | (0b000 << 9) | (0b001 << 6) |
|
||||
// (0b01 << 4) | (1 << 3) | (1 << 2)
|
||||
// 0x2058 = (0b010 << 12) | (0b000 << 9) | (0b001 << 6) |
|
||||
// (0b01 << 4) | (1 << 3)
|
||||
// = RF 3.0 kHz, weak-RF 2.0 kHz, AF Tx LPF2 2.5 kHz,
|
||||
// 6.25k mode, FM gain +6 dB.
|
||||
val = 0x205C;
|
||||
// 6.25k mode, FM gain 0 dB.
|
||||
val = 0x2058;
|
||||
break;
|
||||
|
||||
case BK4819_FILTER_BW_AM: // Stock AM preset
|
||||
@@ -1754,7 +1759,7 @@ void BK4819_PrepareFSKReceive(void)
|
||||
BK4819_WriteRegister(BK4819_REG_59, 0x3068);
|
||||
}
|
||||
|
||||
static void BK4819_PlayRogerNormal(void)
|
||||
static void BK4819_PlayRogerNormal(BK4819_FilterBandwidth_t Bandwidth)
|
||||
{
|
||||
#if 0
|
||||
const uint32_t tone1_Hz = 500;
|
||||
@@ -1769,17 +1774,31 @@ static void BK4819_PlayRogerNormal(void)
|
||||
BK4819_EnterTxMute();
|
||||
BK4819_SetAF(BK4819_AF_MUTE);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_70, // BK4819_REG_70_ENABLE_TONE1 | (66u << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
|
||||
0xC300
|
||||
);
|
||||
const uint8_t rogerToneGain = (Bandwidth == BK4819_FILTER_BW_WIDE) ? 32u : 67u;
|
||||
|
||||
if (Bandwidth == BK4819_FILTER_BW_WIDE)
|
||||
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_NARROW, true);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
|
||||
|
||||
BK4819_EnableTXLink();
|
||||
SYSTEM_DelayMs(50);
|
||||
|
||||
BK4819_PlayToneRaw(tone1_Hz, 80);
|
||||
BK4819_WriteRegister(BK4819_REG_70,
|
||||
BK4819_REG_70_ENABLE_TONE1 |
|
||||
(rogerToneGain << BK4819_REG_70_SHIFT_TONE1_TUNING_GAIN));
|
||||
|
||||
BK4819_ExitTxMute();
|
||||
SYSTEM_DelayMs(80);
|
||||
BK4819_EnterTxMute();
|
||||
|
||||
BK4819_PlayToneRaw(tone2_Hz, 80);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_70, 0x0000);
|
||||
|
||||
if (Bandwidth == BK4819_FILTER_BW_WIDE)
|
||||
BK4819_SetFilterBandwidth(Bandwidth, true);
|
||||
|
||||
BK4819_WriteRegister(BK4819_REG_30, 0xC1FE); // 1 1 0000 0 1 1111 1 1 1 0
|
||||
}
|
||||
|
||||
@@ -1832,10 +1851,10 @@ void BK4819_PlayRogerMDC(void)
|
||||
BK4819_WriteRegister(BK4819_REG_58, 0x0000);
|
||||
}
|
||||
|
||||
void BK4819_PlayRoger(void)
|
||||
void BK4819_PlayRoger(BK4819_FilterBandwidth_t Bandwidth)
|
||||
{
|
||||
if (gEeprom.ROGER == ROGER_MODE_ROGER) {
|
||||
BK4819_PlayRogerNormal();
|
||||
BK4819_PlayRogerNormal(Bandwidth);
|
||||
} else if (gEeprom.ROGER == ROGER_MODE_MDC) {
|
||||
BK4819_PlayRogerMDC();
|
||||
}
|
||||
|
||||
@@ -51,7 +51,13 @@ static const AddrMapping_t ADDR_MAPPINGS[] = {
|
||||
_MK_MAPPING(0x008000, 0x008000, 0x00886E), // 1024 MR + 7 VFO Attributes * 2 Bytes (ex 0x002000) 0x008000 -> 0x00880E
|
||||
// List name * 4 Bytes 0x00880E -> 0x00886E
|
||||
|
||||
_MK_MAPPING(0x009000, 0x009000, 0x0090D6), // 14 VFO * 16 Bytes (ex 0x001000)
|
||||
_MK_MAPPING(0x009000, 0x009000, 0x0090E7), // 14 VFO * 16 Bytes = 0x9000 -> 0x90E0 (the old
|
||||
// 0x90D6 bound was 10 B short: it truncated the
|
||||
// 470 MHz VFO1 record at 0x90D0 -> 0x90DF)
|
||||
// Fox Hunt settings * 7 Bytes 0x90E0 -> 0x90E7
|
||||
// (ENABLE_FEAT_F4HWN_FOXHUNT, written directly by
|
||||
// app/foxhunt.c; concatenated here so aircopy clones
|
||||
// them together with the VFOs)
|
||||
|
||||
_MK_MAPPING(0x00A000, 0x00A000, 0x00A170), // Settings * 16 Bytes (ex 0x004000) 0x00A000 -> 0x00A010
|
||||
// Settings * 16 Bytes (ex 0x005000) 0x00A010 -> 0x00A020
|
||||
@@ -72,6 +78,13 @@ static const AddrMapping_t ADDR_MAPPINGS[] = {
|
||||
// [0x00..0x07] 8-byte header (reserved)
|
||||
// [0x08..0x407] 128x64 monochrome bitmap, 1024 Bytes
|
||||
// ST7565-native: 8 pages * 128 columns, column-major LSB-top
|
||||
|
||||
// Not mapped, for documentation only (the EEPROM API uses 16-bit
|
||||
// addresses and could not reach a 32 KB window anyway):
|
||||
//
|
||||
// 0x1E0000 -> 0x1E8000: RX/TX append-only log * 32 KB / 8 sectors
|
||||
// (ENABLE_FEAT_F4HWN_RXTX_LOG, accessed directly
|
||||
// by app/rxtx_log.c, not through this layer)
|
||||
};
|
||||
|
||||
static void AddrTranslate(uint16_t EEPROM_Addr, uint16_t Size, uint32_t *PY25Q16_Addr_out, uint16_t *Size_out, bool *End_out);
|
||||
|
||||
+33
-5
@@ -27,7 +27,7 @@ KEY_Code_t gKeyReading1 = KEY_INVALID;
|
||||
uint16_t gDebounceCounter = 0;
|
||||
bool gWasFKeyPressed = false;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Short press: hold key for SERIAL_KEY_SHORT_POLLS calls.
|
||||
// Must exceed key_debounce_10ms (2) to trigger ProcessKey(key, true, false).
|
||||
#define SERIAL_KEY_SHORT_POLLS 5
|
||||
@@ -39,8 +39,14 @@ bool gWasFKeyPressed = false;
|
||||
// Packet types for serial key injection (K5Viewer → radio)
|
||||
#define SERIAL_KEY_TYPE 0x03
|
||||
#define SERIAL_KEY_TYPE_LONG 0x04
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
#define SERIAL_FEATURE_TYPE 0x05
|
||||
#endif
|
||||
|
||||
volatile KEY_Code_t gKeyFromSerial = KEY_INVALID;
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
volatile uint8_t gSerialViewerFeatures = 0;
|
||||
#endif
|
||||
static uint8_t gSerialKeyHoldCount = 0;
|
||||
static uint8_t gSerialKeyLong = 0; // 0 = short press, 1 = long press
|
||||
|
||||
@@ -71,13 +77,35 @@ bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b)
|
||||
break;
|
||||
|
||||
case STATE_KA_2:
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
if (b == 0x00)
|
||||
*state = STATE_KA_3;
|
||||
else if (b == SERIAL_FEATURE_TYPE)
|
||||
*state = STATE_KA_FEATURE;
|
||||
else
|
||||
*state = STATE_IDLE;
|
||||
#else
|
||||
*state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE;
|
||||
#endif
|
||||
break;
|
||||
|
||||
|
||||
case STATE_KA_3:
|
||||
if (b == 0x00) connected = true;
|
||||
if (b == 0x00) {
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
gSerialViewerFeatures = 0;
|
||||
#endif
|
||||
connected = true;
|
||||
}
|
||||
*state = STATE_IDLE;
|
||||
break;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
case STATE_KA_FEATURE:
|
||||
gSerialViewerFeatures = b;
|
||||
connected = true;
|
||||
*state = STATE_IDLE;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case STATE_KEY_1:
|
||||
*state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE;
|
||||
@@ -156,7 +184,7 @@ static const KEY_Code_t keyboard[5][4] = {
|
||||
|
||||
KEY_Code_t KEYBOARD_Poll(void)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Serial-injected key: hold it for SHORT or LONG polls depending on press type,
|
||||
// so the debounce counter in app.c reaches the right threshold:
|
||||
// - Short: key_debounce_10ms (2) → ProcessKey(key, true, false)
|
||||
@@ -265,4 +293,4 @@ KEY_Code_t KEYBOARD_GetKey(void)
|
||||
void HideFKeyIcon(void) {
|
||||
gWasFKeyPressed = false;
|
||||
gUpdateStatus = true;
|
||||
}
|
||||
}
|
||||
+14
-2
@@ -50,6 +50,9 @@ typedef enum {
|
||||
STATE_KA_1,
|
||||
STATE_KA_2,
|
||||
STATE_KA_3,
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
STATE_KA_FEATURE,
|
||||
#endif
|
||||
STATE_KEY_1,
|
||||
STATE_KEY_2,
|
||||
STATE_KEY_3,
|
||||
@@ -61,10 +64,19 @@ extern KEY_Code_t gKeyReading1;
|
||||
extern uint16_t gDebounceCounter;
|
||||
extern bool gWasFKeyPressed;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
// Serial-injected key (written by UART/VCP parser, consumed by KEYBOARD_Poll).
|
||||
extern volatile KEY_Code_t gKeyFromSerial;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
#define SERIAL_VIEWER_FEATURE_RF_LOG 0x01u
|
||||
#define SERIAL_VIEWER_FEATURE_RF_LOG_HISTORY 0x02u
|
||||
#define SERIAL_VIEWER_FEATURE_RF_LOG_RESTART 0x80u
|
||||
|
||||
// Extension flags announced by the viewer's feature keepalive.
|
||||
extern volatile uint8_t gSerialViewerFeatures;
|
||||
#endif
|
||||
|
||||
bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b);
|
||||
#endif
|
||||
|
||||
@@ -73,4 +85,4 @@ KEY_Code_t KEYBOARD_GetKey(void);
|
||||
|
||||
void HideFKeyIcon(void);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -284,9 +284,13 @@ void PY25Q16_WriteBuffer(uint32_t Address, const void *pBuffer, uint32_t Size, b
|
||||
if (0 != memcmp(pBuffer, (char *)SectorCache + SecOffset, SecSize))
|
||||
{
|
||||
bool Erase = false;
|
||||
const uint8_t *oldData = SectorCache + SecOffset;
|
||||
const uint8_t *newData = (const uint8_t *)pBuffer;
|
||||
|
||||
for (uint32_t i = 0; i < SecSize; i++)
|
||||
{
|
||||
if (0xff != SectorCache[SecOffset + i])
|
||||
// NOR flash programming can only change bits from 1 to 0.
|
||||
if ((oldData[i] & newData[i]) != newData[i])
|
||||
{
|
||||
Erase = true;
|
||||
break;
|
||||
@@ -491,4 +495,4 @@ void DMA1_Channel4_5_6_7_IRQHandler()
|
||||
|
||||
TC_Flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -24,7 +24,7 @@
|
||||
#include "driver/st7565.h"
|
||||
#include "driver/system.h"
|
||||
#include "misc.h"
|
||||
#include "screenshot.h"
|
||||
#include "k5viewer.h"
|
||||
|
||||
#define SPIx SPI1
|
||||
|
||||
@@ -164,8 +164,8 @@ void ST7565_DrawLine(const unsigned int Column, const unsigned int Line, const u
|
||||
void ST7565_BlitLine(unsigned line)
|
||||
{
|
||||
ST7565_BlitScreen(line + 1);
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
SCREENSHOT_Update(true); // Force immediate capture
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(true); // Force immediate capture
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ void ST7565_FillScreen(uint8_t value)
|
||||
// D=0, display OFF
|
||||
#define ST7565_CMD_DISPLAY_ON_OFF 0xAE
|
||||
|
||||
uint8_t cmds[] = {
|
||||
const uint8_t cmds[] = {
|
||||
ST7565_CMD_BIAS_SELECT | 0, // Select bias setting: 1/9
|
||||
ST7565_CMD_COM_DIRECTION | (0 << 3), // Set output direction of COM: normal
|
||||
ST7565_CMD_SEG_DIRECTION | 1, // Set scan direction of SEG: reverse
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@
|
||||
#include "py32f071_ll_gpio.h"
|
||||
#include "py32f071_ll_usart.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "driver/keyboard.h"
|
||||
#endif
|
||||
|
||||
@@ -150,7 +150,7 @@ void UART_LogSend(const void *pBuffer, uint32_t Size)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
bool UART_IsCableConnected(void) {
|
||||
static uint8_t read_ptr = 0;
|
||||
static ParseState_t state = STATE_IDLE;
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ void UART_Init(void);
|
||||
void UART_Send(const void *pBuffer, uint32_t Size);
|
||||
void UART_LogSend(const void *pBuffer, uint32_t Size);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
bool UART_IsCableConnected(void);
|
||||
#endif
|
||||
|
||||
|
||||
+15
-9
@@ -18,7 +18,7 @@
|
||||
#include "usb_config.h"
|
||||
#include "py32f071_ll_bus.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "driver/keyboard.h"
|
||||
#endif
|
||||
|
||||
@@ -42,19 +42,25 @@ void VCP_Init()
|
||||
NVIC_EnableIRQ(USBD_IRQn);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
bool VCP_ScreenshotPing(void)
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
bool VCP_K5ViewerPing(void)
|
||||
{
|
||||
// State machine for parsing incoming packets:
|
||||
// Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive
|
||||
// Short key press: 0xAA 0x55 0x03 <key> → inject short press
|
||||
// Long key press: 0xAA 0x55 0x04 <key> → inject long press
|
||||
// Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive, no extensions
|
||||
// Feature keepalive (ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER builds only):
|
||||
// 0x55 0xAA 0x05 <flags> → viewer alive, extensions enabled
|
||||
// flags bit 0 = RF log stream
|
||||
// flags bit 1 = paged RF log history
|
||||
// flags bit 7 = restart RF log synchronization
|
||||
// Short key press: 0xAA 0x55 0x03 <key> → inject short press
|
||||
// Long key press: 0xAA 0x55 0x04 <key> → inject long press
|
||||
//
|
||||
// State transitions:
|
||||
// IDLE → 0x55 → KA_1
|
||||
// KA_1 → 0xAA → KA_2 (else IDLE)
|
||||
// KA_2 → 0x00 → KA_3 (else IDLE)
|
||||
// KA_3 → 0x00 → keepalive OK, IDLE
|
||||
// KA_2 → 0x00 → KA_3 (else 0x05 → KA_FEATURE, else IDLE)
|
||||
// KA_3 → 0x00 → keepalive OK, clear extensions, IDLE
|
||||
// KA_FEATURE → <flags> → keepalive OK, set extensions, IDLE
|
||||
//
|
||||
// IDLE → 0xAA → KEY_1
|
||||
// KEY_1 → 0x55 → KEY_2 (else IDLE)
|
||||
@@ -89,4 +95,4 @@ bool VCP_ScreenshotPing(void)
|
||||
|
||||
return connected;
|
||||
}
|
||||
#endif // ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#endif // ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
+2
-2
@@ -29,8 +29,8 @@ extern volatile uint32_t VCP_RxBufPointer;
|
||||
|
||||
void VCP_Init();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
bool VCP_ScreenshotPing(void);
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
bool VCP_K5ViewerPing(void);
|
||||
#endif
|
||||
|
||||
static inline void VCP_Send(const uint8_t *Buf, uint32_t Size)
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
#include "frequencies.h"
|
||||
#include "functions.h"
|
||||
#include "helper/battery.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#include "misc.h"
|
||||
#include "radio.h"
|
||||
#include "settings.h"
|
||||
@@ -239,8 +242,31 @@ void FUNCTION_Select(FUNCTION_Type_t Function)
|
||||
const FUNCTION_Type_t PreviousFunction = gCurrentFunction;
|
||||
const bool bWasPowerSave = PreviousFunction == FUNCTION_POWER_SAVE;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
const bool previousWasActive =
|
||||
PreviousFunction == FUNCTION_TRANSMIT ||
|
||||
PreviousFunction == FUNCTION_MONITOR ||
|
||||
PreviousFunction == FUNCTION_RECEIVE;
|
||||
const bool previousWasTx = PreviousFunction == FUNCTION_TRANSMIT;
|
||||
const bool nextIsActive =
|
||||
Function == FUNCTION_TRANSMIT ||
|
||||
Function == FUNCTION_MONITOR ||
|
||||
Function == FUNCTION_RECEIVE;
|
||||
const bool nextIsTx = Function == FUNCTION_TRANSMIT;
|
||||
|
||||
if (previousWasActive &&
|
||||
(!nextIsActive ||
|
||||
(previousWasTx != nextIsTx)))
|
||||
RXTX_LOG_EndActive();
|
||||
#endif
|
||||
|
||||
gCurrentFunction = Function;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
if (nextIsActive && !nextIsTx && (!previousWasActive || previousWasTx))
|
||||
RXTX_LOG_BeginRx(gRxVfo, Function);
|
||||
#endif
|
||||
|
||||
if (bWasPowerSave && Function != FUNCTION_POWER_SAVE) {
|
||||
BK4819_Conditional_RX_TurnOn_and_GPIO6_Enable();
|
||||
gRxIdleMode = false;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef FUNCTIONS_H
|
||||
#define FUNCTIONS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum FUNCTION_Type_t
|
||||
|
||||
+6
-6
@@ -73,12 +73,14 @@ BOOT_Mode_t BOOT_GetMode(void)
|
||||
|
||||
void BOOT_ProcessMode(BOOT_Mode_t Mode)
|
||||
{
|
||||
GUI_DisplayType_t display = DISPLAY_MAIN;
|
||||
|
||||
if (Mode == BOOT_MODE_F_LOCK)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
|
||||
gEeprom.CURRENT_STATE = 0; // Don't resume is active...
|
||||
#endif
|
||||
GUI_SelectNextDisplay(DISPLAY_MENU);
|
||||
display = DISPLAY_MENU;
|
||||
}
|
||||
#ifdef ENABLE_AIRCOPY
|
||||
else
|
||||
@@ -119,11 +121,9 @@ void BOOT_ProcessMode(BOOT_Mode_t Mode)
|
||||
gEeprom.CURRENT_STATE = 0; // Don't resume is active...
|
||||
#endif
|
||||
|
||||
GUI_SelectNextDisplay(DISPLAY_AIRCOPY);
|
||||
display = DISPLAY_AIRCOPY;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
GUI_SelectNextDisplay(DISPLAY_MAIN);
|
||||
}
|
||||
|
||||
GUI_SelectNextDisplay(display);
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
/* Copyright 2024 Armel F4HWN
|
||||
* https://github.com/armel
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "debugging.h"
|
||||
#include "driver/st7565.h"
|
||||
#include "k5viewer.h"
|
||||
#include "misc.h"
|
||||
#include "driver/vcp.h"
|
||||
#include "driver/keyboard.h"
|
||||
#include "driver/bk4819.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
|
||||
// SRAM optimization: minimize static allocations
|
||||
// - previousHash: one fingerprint per 8-byte chunk instead of a full
|
||||
// 1024-byte copy of the previous frame (chunks are only compared,
|
||||
// never retransmitted from history). A 16-bit fingerprint keeps the
|
||||
// collision odds at ~1/65536 per chunk, so a stale chunk slipping
|
||||
// through is rare; the forcedBlock rotation repairs that residual
|
||||
// within at most 128 frames.
|
||||
// Stack optimization: chunks are computed on demand straight from
|
||||
// gStatusLine/gFrameBuffer instead of building the whole 1024-byte
|
||||
// frame on the stack. Changed chunks are tracked in a 16-byte bitmap.
|
||||
// Peak stack drops from ~1.3 KB to ~40 bytes, at the cost of
|
||||
// transforming each transmitted chunk twice (negligible next to the
|
||||
// blocking UART transfer).
|
||||
static uint16_t previousHash[128];
|
||||
static uint8_t previousStateFlags = 0xFF;
|
||||
static uint8_t forcedBlock = 0;
|
||||
static uint8_t keepAlive = 3;
|
||||
static bool hasConnectionPing = false;
|
||||
static bool wasConnected = false;
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
static uint32_t previousRfLogSignature = 0;
|
||||
static bool rfLogSent = false;
|
||||
// Pagination bound of the history dump: rows with trafficSeq below this
|
||||
// value remain to be sent; HISTORY_START arms a new dump, 0 means done.
|
||||
static uint32_t rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
|
||||
#endif
|
||||
|
||||
// FNV-1a over one 8-byte chunk, folded to 16 bits
|
||||
static uint16_t K5VIEWER_Hash(const uint8_t *data)
|
||||
{
|
||||
uint32_t h = 2166136261u;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
h = (h ^ data[i]) * 16777619u;
|
||||
}
|
||||
return (uint16_t)(h ^ (h >> 16));
|
||||
}
|
||||
|
||||
void K5VIEWER_ParseInput(void)
|
||||
{
|
||||
if (K5VIEWER_IsLocked())
|
||||
return;
|
||||
|
||||
if (UART_IsCableConnected()) {
|
||||
keepAlive = 15;
|
||||
hasConnectionPing = true;
|
||||
gUSB_K5ViewerEnabled = false;
|
||||
}
|
||||
else if (VCP_K5ViewerPing()) {
|
||||
keepAlive = 15;
|
||||
hasConnectionPing = true;
|
||||
gUSB_K5ViewerEnabled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void K5VIEWER_Send(const uint8_t *buf, uint16_t len)
|
||||
{
|
||||
if (gUSB_K5ViewerEnabled) {
|
||||
cdc_acm_data_send_with_dtr(buf, len);
|
||||
} else {
|
||||
UART_Send(buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
enum {
|
||||
K5VIEWER_CHUNK_SIZE = 8,
|
||||
K5VIEWER_CHUNKS_PER_LINE = 16,
|
||||
K5VIEWER_HALF_LINE_COLUMNS = LCD_WIDTH / 2,
|
||||
K5VIEWER_MARKER_BASE = 0xF0,
|
||||
K5VIEWER_TYPE_DIFF = 0x02,
|
||||
K5VIEWER_TYPE_RXTX_LOG = 0x05,
|
||||
K5VIEWER_TYPE_RXTX_LOG_HISTORY = 0x06,
|
||||
K5VIEWER_FLAG_DEEP_SLEEP = 1 << 0,
|
||||
K5VIEWER_FLAG_LED_RED = 1 << 1,
|
||||
K5VIEWER_FLAG_LED_GREEN = 1 << 2,
|
||||
};
|
||||
|
||||
static uint8_t K5VIEWER_StateFlags(void)
|
||||
{
|
||||
uint8_t flags = 0;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
if (gWakeUp)
|
||||
flags |= K5VIEWER_FLAG_DEEP_SLEEP;
|
||||
#endif
|
||||
|
||||
if (BK4819_IsGpioOutSet(BK4819_GPIO5_PIN1_RED))
|
||||
flags |= K5VIEWER_FLAG_LED_RED;
|
||||
|
||||
if (BK4819_IsGpioOutSet(BK4819_GPIO6_PIN2_GREEN))
|
||||
flags |= K5VIEWER_FLAG_LED_GREEN;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
static bool K5VIEWER_HasPendingRfLogUpdate(void)
|
||||
{
|
||||
if (!RXTX_LOG_IsEnabled())
|
||||
rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
|
||||
|
||||
if ((gSerialViewerFeatures & SERIAL_VIEWER_FEATURE_RF_LOG_RESTART) != 0) {
|
||||
gSerialViewerFeatures &= ~SERIAL_VIEWER_FEATURE_RF_LOG_RESTART;
|
||||
rfLogSent = false;
|
||||
rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
|
||||
}
|
||||
|
||||
if ((gSerialViewerFeatures & SERIAL_VIEWER_FEATURE_RF_LOG) == 0)
|
||||
return false;
|
||||
|
||||
return !rfLogSent || RXTX_LOG_K5ViewerSignature() != previousRfLogSignature;
|
||||
}
|
||||
|
||||
static void K5VIEWER_SendRfLogFrameHeader(uint8_t type, uint16_t len)
|
||||
{
|
||||
uint8_t header[5] = {0xAA, 0x55, type, (uint8_t)(len >> 8), (uint8_t)len};
|
||||
|
||||
K5VIEWER_Send(header, sizeof(header));
|
||||
}
|
||||
|
||||
static void K5VIEWER_SendRfLogFrameEnd(void)
|
||||
{
|
||||
const uint8_t end = 0x0A;
|
||||
|
||||
K5VIEWER_Send(&end, 1);
|
||||
}
|
||||
|
||||
static void K5VIEWER_SendRfLog(void)
|
||||
{
|
||||
// Capture the signature before streaming: a state change landing
|
||||
// during the blocking send still differs afterwards and triggers a
|
||||
// resend on the next cycle.
|
||||
previousRfLogSignature = RXTX_LOG_K5ViewerSignature();
|
||||
rfLogSent = true;
|
||||
|
||||
const uint16_t size = RXTX_LOG_IsEnabled()
|
||||
? RXTX_LOG_K5VIEWER_PACKET_SIZE
|
||||
: RXTX_LOG_K5VIEWER_STATUS_PACKET_SIZE;
|
||||
K5VIEWER_SendRfLogFrameHeader(K5VIEWER_TYPE_RXTX_LOG, size);
|
||||
RXTX_LOG_SendK5ViewerPacket(K5VIEWER_Send);
|
||||
K5VIEWER_SendRfLogFrameEnd();
|
||||
}
|
||||
|
||||
static bool K5VIEWER_HasPendingRfLogHistory(void)
|
||||
{
|
||||
return RXTX_LOG_IsEnabled() &&
|
||||
(gSerialViewerFeatures & SERIAL_VIEWER_FEATURE_RF_LOG_HISTORY) != 0 &&
|
||||
rfLogHistoryBefore != 0;
|
||||
}
|
||||
|
||||
static void K5VIEWER_SendRfLogHistory(void)
|
||||
{
|
||||
K5VIEWER_SendRfLogFrameHeader(K5VIEWER_TYPE_RXTX_LOG_HISTORY,
|
||||
RXTX_LOG_K5VIEWER_HISTORY_PACKET_SIZE);
|
||||
rfLogHistoryBefore = RXTX_LOG_SendK5ViewerHistoryPage(rfLogHistoryBefore, K5VIEWER_Send);
|
||||
K5VIEWER_SendRfLogFrameEnd();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool K5VIEWER_HasPendingStateChange(void)
|
||||
{
|
||||
if (gUART_LockK5Viewer > 0 || keepAlive == 0 || !hasConnectionPing)
|
||||
return false;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
if (K5VIEWER_HasPendingRfLogUpdate())
|
||||
return true;
|
||||
if (K5VIEWER_HasPendingRfLogHistory())
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return !wasConnected || K5VIEWER_StateFlags() != previousStateFlags;
|
||||
}
|
||||
|
||||
// Compute one 8-byte output chunk directly from the display buffers.
|
||||
// Frame layout: per line (status + 7 frame lines), 8 bit layers of
|
||||
// 16 bytes each. Each bit layer is split into two 64-column chunks.
|
||||
static void K5VIEWER_Chunk(uint8_t chunkIdx, uint8_t *dest)
|
||||
{
|
||||
const uint8_t chunkInLine = chunkIdx % K5VIEWER_CHUNKS_PER_LINE;
|
||||
const uint8_t line = chunkIdx / K5VIEWER_CHUNKS_PER_LINE;
|
||||
const uint8_t bit = chunkInLine / 2;
|
||||
const uint8_t columnBase = (chunkInLine % 2) * K5VIEWER_HALF_LINE_COLUMNS;
|
||||
const uint8_t *src = (line == 0 ? gStatusLine : gFrameBuffer[line - 1])
|
||||
+ columnBase;
|
||||
|
||||
for (uint8_t j = 0; j < K5VIEWER_CHUNK_SIZE; j++) {
|
||||
uint8_t acc = 0;
|
||||
for (uint8_t k = 0; k < K5VIEWER_CHUNK_SIZE; k++) {
|
||||
if (src[j * K5VIEWER_CHUNK_SIZE + k] & (1 << bit)) acc |= (1 << k);
|
||||
}
|
||||
dest[j] = gSetting_set_inv ? ~acc : acc;
|
||||
}
|
||||
}
|
||||
|
||||
void K5VIEWER_Update(bool force)
|
||||
{
|
||||
if (K5VIEWER_IsLocked())
|
||||
return;
|
||||
|
||||
if (keepAlive > 0) {
|
||||
if (--keepAlive == 0) {
|
||||
// Connection just lost → reset state for next reconnection
|
||||
wasConnected = false;
|
||||
hasConnectionPing = false;
|
||||
previousStateFlags = 0xFF;
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
gSerialViewerFeatures = 0;
|
||||
rfLogSent = false;
|
||||
rfLogHistoryBefore = RXTX_LOG_K5VIEWER_HISTORY_START;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection is alive — detect reconnection and force full frame
|
||||
if (!wasConnected) {
|
||||
force = true;
|
||||
}
|
||||
|
||||
const uint8_t stateFlags = K5VIEWER_StateFlags();
|
||||
const bool stateChanged = (stateFlags != previousStateFlags);
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
const bool rfLogPending = K5VIEWER_HasPendingRfLogUpdate();
|
||||
const bool rfLogHistoryPending = K5VIEWER_HasPendingRfLogHistory();
|
||||
#else
|
||||
const bool rfLogPending = false;
|
||||
const bool rfLogHistoryPending = false;
|
||||
#endif
|
||||
|
||||
// ==== FIRST PASS: Count changed chunks ====
|
||||
uint16_t deltaLen = 0;
|
||||
uint8_t changedBitmap[16] = {0}; // 1 bit per chunk
|
||||
uint8_t chunk[9]; // [0] = index, [1..8] = payload
|
||||
|
||||
for (uint8_t chunkIdx = 0; chunkIdx < 128; chunkIdx++) {
|
||||
K5VIEWER_Chunk(chunkIdx, &chunk[1]);
|
||||
|
||||
bool changed = K5VIEWER_Hash(&chunk[1]) != previousHash[chunkIdx];
|
||||
bool isForced = (chunkIdx == forcedBlock);
|
||||
|
||||
if (changed || isForced || force) {
|
||||
changedBitmap[chunkIdx >> 3] |= 1 << (chunkIdx & 7);
|
||||
deltaLen += 9;
|
||||
}
|
||||
}
|
||||
|
||||
forcedBlock = (forcedBlock + 1) % 128;
|
||||
|
||||
if (deltaLen == 0 && !stateChanged && !rfLogPending && !rfLogHistoryPending)
|
||||
return;
|
||||
|
||||
// Skip transmission if a key is currently pressed
|
||||
// UART_Send is blocking - would freeze the main loop and lose keypresses
|
||||
if (gKeyReading0 != KEY_INVALID)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
// A pending RF log packet may be the only thing to send: skip the
|
||||
// frame section when it carries nothing. Without the RF log stream
|
||||
// the early return above already guarantees this condition.
|
||||
if (deltaLen != 0 || stateChanged)
|
||||
#endif
|
||||
{
|
||||
// ==== Send version marker and state flags ====
|
||||
// 0xF0 keeps a resync-safe marker before the standard AA 55 header.
|
||||
uint8_t versionMarker = K5VIEWER_MARKER_BASE | stateFlags;
|
||||
K5VIEWER_Send(&versionMarker, 1);
|
||||
|
||||
// ==== Send header ====
|
||||
uint8_t header[5] = {
|
||||
0xAA, 0x55, K5VIEWER_TYPE_DIFF,
|
||||
(uint8_t)(deltaLen >> 8),
|
||||
(uint8_t)(deltaLen & 0xFF)
|
||||
};
|
||||
|
||||
K5VIEWER_Send(header, 5);
|
||||
|
||||
// ==== SECOND PASS: Send only changed chunks ====
|
||||
for (uint8_t chunkIdx = 0; chunkIdx < 128; chunkIdx++) {
|
||||
if (!(changedBitmap[chunkIdx >> 3] & (1 << (chunkIdx & 7))))
|
||||
continue;
|
||||
|
||||
chunk[0] = chunkIdx;
|
||||
K5VIEWER_Chunk(chunkIdx, &chunk[1]);
|
||||
|
||||
K5VIEWER_Send(chunk, 9);
|
||||
|
||||
// Update the fingerprint only once the chunk is actually sent,
|
||||
// so chunks skipped by an early return stay marked as changed.
|
||||
// Hashing the recomputed payload also keeps the fingerprint in
|
||||
// sync if the display buffer changed between the two passes.
|
||||
previousHash[chunkIdx] = K5VIEWER_Hash(&chunk[1]);
|
||||
}
|
||||
|
||||
uint8_t end = 0x0A;
|
||||
K5VIEWER_Send(&end, 1);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER
|
||||
if (rfLogPending)
|
||||
K5VIEWER_SendRfLog();
|
||||
if (rfLogHistoryPending)
|
||||
K5VIEWER_SendRfLogHistory();
|
||||
#endif
|
||||
|
||||
previousStateFlags = stateFlags;
|
||||
wasConnected = true;
|
||||
}
|
||||
@@ -14,10 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef SCREENSHOT_H
|
||||
#define SCREENSHOT_H
|
||||
#ifndef K5VIEWER_H
|
||||
#define K5VIEWER_H
|
||||
|
||||
void SCREENSHOT_Update(bool force);
|
||||
void SCREENSHOT_ParseInput(void);
|
||||
void K5VIEWER_Update(bool force);
|
||||
void K5VIEWER_ParseInput(void);
|
||||
bool K5VIEWER_HasPendingStateChange(void);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
+16
-8
@@ -24,6 +24,9 @@
|
||||
|
||||
#include "audio.h"
|
||||
#include "board.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#include "misc.h"
|
||||
#include "radio.h"
|
||||
#include "settings.h"
|
||||
@@ -99,6 +102,10 @@ void Main(void)
|
||||
|
||||
SETTINGS_InitEEPROM();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
RXTX_LOG_Init();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
gDW = gEeprom.DUAL_WATCH;
|
||||
gCB = gEeprom.CROSS_BAND_RX_TX;
|
||||
@@ -157,19 +164,17 @@ void Main(void)
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
gEeprom.KEY_LOCK = 0;
|
||||
SETTINGS_SaveSettings();
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
gMenuLevel = MENU_LEVEL_ITEMS;
|
||||
gMenuCategory = CAT_ALL;
|
||||
#endif
|
||||
gMenuCursor = UI_MENU_GetMenuIdx(FIRST_HIDDEN_MENU_ITEM);
|
||||
gSubMenuSelection = gSetting_F_LOCK;
|
||||
#endif
|
||||
}
|
||||
|
||||
// count the number of menu items
|
||||
gMenuListCount = 0;
|
||||
while (MenuList[gMenuListCount].name[0] != '\0') {
|
||||
if(!gF_LOCK && MenuList[gMenuListCount].menu_id == FIRST_HIDDEN_MENU_ITEM)
|
||||
break;
|
||||
|
||||
gMenuListCount++;
|
||||
}
|
||||
// build the current menu view (Etape 1: vue = All, identite)
|
||||
UI_MENU_BuildView();
|
||||
|
||||
// wait for user to release all butts before moving on
|
||||
if (GPIO_IsPttPressed() ||
|
||||
@@ -245,6 +250,9 @@ void Main(void)
|
||||
|
||||
BOOT_ProcessMode(BootMode);
|
||||
|
||||
if (gEeprom.AUTO_KEYPAD_LOCK && !gEeprom.KEY_LOCK)
|
||||
gKeyLockCountdown = gEeprom.AUTO_KEYPAD_LOCK * 30; // 15 seconds step
|
||||
|
||||
// GPIO_ClearBit(&GPIOA->DATA, GPIOA_PIN_VOICE_0);
|
||||
|
||||
gUpdateStatus = true;
|
||||
|
||||
+11
-8
@@ -127,7 +127,7 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
|
||||
uint8_t gSetting_set_ctr = 10;
|
||||
bool gSetting_set_inv = false;
|
||||
uint8_t gSetting_set_eot = 0;
|
||||
bool gSetting_set_lck = false;
|
||||
uint8_t gSetting_set_lck = SET_LCK_KEYS;
|
||||
bool gSetting_set_met = 0;
|
||||
bool gSetting_set_gui = 0;
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
@@ -137,6 +137,9 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
|
||||
#ifdef ENABLE_FEAT_F4HWN_NARROWER
|
||||
bool gSetting_set_nfm = 0;
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
uint8_t gSetting_set_sav = SET_SAV_OFF;
|
||||
#endif
|
||||
bool gSetting_set_tmr = 0;
|
||||
bool gSetting_set_ptt_session;
|
||||
#ifdef ENABLE_FEAT_F4HWN_DEBUG
|
||||
@@ -207,9 +210,9 @@ volatile bool gTxTimeoutReached;
|
||||
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
|
||||
volatile uint16_t gRxTimerCountdown_500ms;
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
volatile uint8_t gUART_LockScreenshot = 0; // lock screenshot if Chirp is used
|
||||
bool gUSB_ScreenshotEnabled = false;
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
volatile uint8_t gUART_LockK5Viewer = 0; // lock the K5Viewer stream if Chirp is used
|
||||
bool gUSB_K5ViewerEnabled = false;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -643,11 +646,11 @@ void MR_PrintCacheStats(void)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
bool SCREENSHOT_IsLocked(void)
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
bool K5VIEWER_IsLocked(void)
|
||||
{
|
||||
if (gUART_LockScreenshot > 0) {
|
||||
gUART_LockScreenshot--;
|
||||
if (gUART_LockK5Viewer > 0) {
|
||||
gUART_LockK5Viewer--;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+31
-5
@@ -181,13 +181,36 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
// Keypad lock scope. Values are BOTH a bitmask (ACTIONS/PTT bits tested
|
||||
// individually) AND a contiguous 0..3 menu index: the order must stay
|
||||
// aligned with gSubMenu_SET_LCK[] and any new entry must keep the range
|
||||
// contiguous so that SET_LCK_LEN remains valid as menu bound and EEPROM
|
||||
// range check.
|
||||
enum SET_LCK_t {
|
||||
SET_LCK_KEYS = 0u,
|
||||
SET_LCK_ACTIONS = 1u,
|
||||
SET_LCK_PTT = 2u,
|
||||
SET_LCK_ACTIONS_PTT = SET_LCK_ACTIONS | SET_LCK_PTT,
|
||||
SET_LCK_LEN
|
||||
};
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
enum SET_SAV_t {
|
||||
SET_SAV_OFF,
|
||||
SET_SAV_LOGO,
|
||||
SET_SAV_LOGO_PLUS,
|
||||
SET_SAV_MATRIX,
|
||||
SET_SAV_LEN
|
||||
};
|
||||
#endif
|
||||
|
||||
extern uint8_t gSetting_set_pwr;
|
||||
extern bool gSetting_set_ptt;
|
||||
extern uint8_t gSetting_set_tot;
|
||||
extern uint8_t gSetting_set_ctr;
|
||||
extern bool gSetting_set_inv;
|
||||
extern uint8_t gSetting_set_eot;
|
||||
extern bool gSetting_set_lck;
|
||||
extern uint8_t gSetting_set_lck;
|
||||
extern bool gSetting_set_met;
|
||||
extern bool gSetting_set_gui;
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
@@ -197,6 +220,9 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
|
||||
#ifdef ENABLE_FEAT_F4HWN_NARROWER
|
||||
extern bool gSetting_set_nfm;
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
extern uint8_t gSetting_set_sav;
|
||||
#endif
|
||||
extern bool gSetting_set_tmr;
|
||||
extern bool gSetting_set_ptt_session;
|
||||
#ifdef ENABLE_FEAT_F4HWN_DEBUG
|
||||
@@ -323,11 +349,11 @@ extern volatile bool gTxTimeoutReached;
|
||||
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
|
||||
extern volatile uint16_t gRxTimerCountdown_500ms;
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
extern volatile uint8_t gUART_LockScreenshot; // lock screenshot if Chirp is used
|
||||
extern bool gUSB_ScreenshotEnabled;
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
extern volatile uint8_t gUART_LockK5Viewer; // lock the K5Viewer stream if Chirp is used
|
||||
extern bool gUSB_K5ViewerEnabled;
|
||||
|
||||
bool SCREENSHOT_IsLocked(void);
|
||||
bool K5VIEWER_IsLocked(void);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
+61
-60
@@ -19,6 +19,9 @@
|
||||
|
||||
#include "am_fix.h"
|
||||
#include "app/dtmf.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FMRADIO
|
||||
#include "app/fm.h"
|
||||
#endif
|
||||
@@ -510,17 +513,20 @@ void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
|
||||
}
|
||||
else
|
||||
{ // squelch >= 1
|
||||
Base += gEeprom.SQUELCH_LEVEL; // my eeprom squelch-1
|
||||
// VHF UHF
|
||||
PY25Q16_ReadBuffer(Base + 0x00, &pInfo->SquelchOpenRSSIThresh, 1); // 50 10
|
||||
PY25Q16_ReadBuffer(Base + 0x10, &pInfo->SquelchCloseRSSIThresh, 1); // 40 5
|
||||
|
||||
PY25Q16_ReadBuffer(Base + 0x20, &pInfo->SquelchOpenNoiseThresh, 1); // 65 90
|
||||
PY25Q16_ReadBuffer(Base + 0x30, &pInfo->SquelchCloseNoiseThresh, 1); // 70 100
|
||||
|
||||
PY25Q16_ReadBuffer(Base + 0x40, &pInfo->SquelchCloseGlitchThresh, 1); // 90 90
|
||||
PY25Q16_ReadBuffer(Base + 0x50, &pInfo->SquelchOpenGlitchThresh, 1); // 100 100
|
||||
Base += gEeprom.SQUELCH_LEVEL; // my eeprom squelch-1
|
||||
// VHF UHF
|
||||
uint8_t *sq_ptrs[6] = {
|
||||
&pInfo->SquelchOpenRSSIThresh, // 50 10
|
||||
&pInfo->SquelchCloseRSSIThresh, // 40 5
|
||||
&pInfo->SquelchOpenNoiseThresh, // 65 90
|
||||
&pInfo->SquelchCloseNoiseThresh, // 70 100
|
||||
&pInfo->SquelchCloseGlitchThresh, // 90 90
|
||||
&pInfo->SquelchOpenGlitchThresh // 100 100
|
||||
};
|
||||
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
PY25Q16_ReadBuffer(Base + (i * 0x10), sq_ptrs[i], 1);
|
||||
}
|
||||
|
||||
uint16_t noise_open = pInfo->SquelchOpenNoiseThresh;
|
||||
uint16_t noise_close = pInfo->SquelchCloseNoiseThresh;
|
||||
@@ -544,14 +550,14 @@ void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
|
||||
if (glitch_close == glitch_open && glitch_close <= 253)
|
||||
glitch_close += 2;
|
||||
|
||||
pInfo->SquelchOpenRSSIThresh = (rssi_open > 255) ? 255 : rssi_open;
|
||||
pInfo->SquelchCloseRSSIThresh = (rssi_close > 255) ? 255 : rssi_close;
|
||||
pInfo->SquelchOpenGlitchThresh = (glitch_open > 255) ? 255 : glitch_open;
|
||||
pInfo->SquelchCloseGlitchThresh = (glitch_close > 255) ? 255 : glitch_close;
|
||||
pInfo->SquelchOpenRSSIThresh = MIN(rssi_open, 255);
|
||||
pInfo->SquelchCloseRSSIThresh = MIN(rssi_close, 255);
|
||||
pInfo->SquelchOpenGlitchThresh = MIN(glitch_open, 255);
|
||||
pInfo->SquelchCloseGlitchThresh = MIN(glitch_close, 255);
|
||||
#endif
|
||||
|
||||
pInfo->SquelchOpenNoiseThresh = (noise_open > 127) ? 127 : noise_open;
|
||||
pInfo->SquelchCloseNoiseThresh = (noise_close > 127) ? 127 : noise_close;
|
||||
pInfo->SquelchOpenNoiseThresh = MIN(noise_open, 127);
|
||||
pInfo->SquelchCloseNoiseThresh = MIN(noise_close, 127);
|
||||
}
|
||||
|
||||
// *******************************
|
||||
@@ -698,18 +704,11 @@ void RADIO_ApplyOffset(VFO_Info_t *pInfo)
|
||||
{
|
||||
uint32_t Frequency = pInfo->freq_config_RX.Frequency;
|
||||
|
||||
switch (pInfo->TX_OFFSET_FREQUENCY_DIRECTION)
|
||||
{
|
||||
case TX_OFFSET_FREQUENCY_DIRECTION_OFF:
|
||||
break;
|
||||
case TX_OFFSET_FREQUENCY_DIRECTION_ADD:
|
||||
Frequency += pInfo->TX_OFFSET_FREQUENCY;
|
||||
break;
|
||||
case TX_OFFSET_FREQUENCY_DIRECTION_SUB:
|
||||
Frequency -= pInfo->TX_OFFSET_FREQUENCY;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pInfo->TX_OFFSET_FREQUENCY_DIRECTION == TX_OFFSET_FREQUENCY_DIRECTION_ADD)
|
||||
Frequency += pInfo->TX_OFFSET_FREQUENCY;
|
||||
else if (pInfo->TX_OFFSET_FREQUENCY_DIRECTION == TX_OFFSET_FREQUENCY_DIRECTION_SUB)
|
||||
Frequency -= pInfo->TX_OFFSET_FREQUENCY;
|
||||
|
||||
pInfo->freq_config_TX.Frequency = Frequency;
|
||||
}
|
||||
|
||||
@@ -809,6 +808,9 @@ void RADIO_SetupRegisters(bool switchToForeground)
|
||||
#endif
|
||||
BK4819_SetFrequency(Frequency);
|
||||
|
||||
// Keep the demodulator in sync when retuning without entering RX audio.
|
||||
RADIO_SetModulation(gRxVfo->Modulation);
|
||||
|
||||
BK4819_SetupSquelch(
|
||||
gRxVfo->SquelchOpenRSSIThresh, gRxVfo->SquelchCloseRSSIThresh,
|
||||
gRxVfo->SquelchOpenNoiseThresh, gRxVfo->SquelchCloseNoiseThresh,
|
||||
@@ -841,7 +843,7 @@ void RADIO_SetupRegisters(bool switchToForeground)
|
||||
BK4819_SetCTCSSFrequency(SQL_TONE);
|
||||
BK4819_SetTailDetection(SQL_TONE); // Default 550 = QS's 55Hz tone method
|
||||
|
||||
InterruptMask = BK4819_REG_3F_CxCSS_TAIL | BK4819_REG_3F_SQUELCH_FOUND | BK4819_REG_3F_SQUELCH_LOST;
|
||||
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL;
|
||||
break;
|
||||
|
||||
case CODE_TYPE_CONTINUOUS_TONE:
|
||||
@@ -853,24 +855,17 @@ void RADIO_SetupRegisters(bool switchToForeground)
|
||||
// BK4819_SetTailDetection(CTCSS_Options[Code]);
|
||||
//#endif
|
||||
|
||||
InterruptMask = 0
|
||||
| BK4819_REG_3F_CxCSS_TAIL
|
||||
| BK4819_REG_3F_CTCSS_FOUND
|
||||
| BK4819_REG_3F_CTCSS_LOST
|
||||
| BK4819_REG_3F_SQUELCH_FOUND
|
||||
| BK4819_REG_3F_SQUELCH_LOST;
|
||||
|
||||
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL
|
||||
| BK4819_REG_3F_CTCSS_FOUND
|
||||
| BK4819_REG_3F_CTCSS_LOST;
|
||||
break;
|
||||
|
||||
case CODE_TYPE_DIGITAL:
|
||||
case CODE_TYPE_REVERSE_DIGITAL:
|
||||
BK4819_SetCDCSSCodeWord(DCS_GetGolayCodeWord(CodeType, Code));
|
||||
InterruptMask = 0
|
||||
| BK4819_REG_3F_CxCSS_TAIL
|
||||
| BK4819_REG_3F_CDCSS_FOUND
|
||||
| BK4819_REG_3F_CDCSS_LOST
|
||||
| BK4819_REG_3F_SQUELCH_FOUND
|
||||
| BK4819_REG_3F_SQUELCH_LOST;
|
||||
InterruptMask |= BK4819_REG_3F_CxCSS_TAIL
|
||||
| BK4819_REG_3F_CDCSS_FOUND
|
||||
| BK4819_REG_3F_CDCSS_LOST;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -888,11 +883,8 @@ void RADIO_SetupRegisters(bool switchToForeground)
|
||||
else
|
||||
{
|
||||
BK4819_SetCTCSSFrequency(2625);
|
||||
InterruptMask = 0
|
||||
| BK4819_REG_3F_CTCSS_FOUND
|
||||
| BK4819_REG_3F_CTCSS_LOST
|
||||
| BK4819_REG_3F_SQUELCH_FOUND
|
||||
| BK4819_REG_3F_SQUELCH_LOST;
|
||||
InterruptMask |= BK4819_REG_3F_CTCSS_FOUND
|
||||
| BK4819_REG_3F_CTCSS_LOST;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1099,11 +1091,12 @@ void RADIO_SetModulation(ModulationMode_t modulation)
|
||||
// So for now we just keep it as is to maintain compatibility.
|
||||
//
|
||||
|
||||
uint16_t uVar1 = BK4819_ReadRegister(0x31);
|
||||
|
||||
switch (modulation)
|
||||
{
|
||||
case MODULATION_AM:
|
||||
{
|
||||
uint16_t uVar1 = BK4819_ReadRegister(0x31);
|
||||
BK4819_WriteRegister(0x31, uVar1 | 1); // AM Demodulation Enable
|
||||
BK4819_WriteRegister(0x42, 0x6f5c);
|
||||
BK4819_WriteRegister(0x2a, 0x7434);
|
||||
@@ -1115,7 +1108,7 @@ void RADIO_SetModulation(ModulationMode_t modulation)
|
||||
BK4819_WriteRegister(0x55, 0x31a9);
|
||||
#endif
|
||||
|
||||
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gCurrentVfo), true);
|
||||
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1123,7 +1116,6 @@ void RADIO_SetModulation(ModulationMode_t modulation)
|
||||
case MODULATION_FM:
|
||||
default:
|
||||
{
|
||||
uint16_t uVar1 = BK4819_ReadRegister(0x31);
|
||||
BK4819_WriteRegister(0x31, uVar1 & 0xfffe); // AM Demodulation Disable
|
||||
BK4819_WriteRegister(0x42, 0x6b5a);
|
||||
BK4819_WriteRegister(0x2a, 0x7400);
|
||||
@@ -1213,16 +1205,12 @@ void RADIO_PrepareTX(void)
|
||||
|
||||
RADIO_SelectCurrentVfo();
|
||||
|
||||
if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0 && gCurrentVfo->TX_LOCK == true
|
||||
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
|
||||
&& gAlarmState != ALARM_STATE_SITE_ALARM
|
||||
#endif
|
||||
#else
|
||||
if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0
|
||||
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
|
||||
&& gAlarmState != ALARM_STATE_SITE_ALARM
|
||||
#endif
|
||||
&& gCurrentVfo->TX_LOCK == true
|
||||
#endif
|
||||
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
|
||||
&& gAlarmState != ALARM_STATE_SITE_ALARM
|
||||
#endif
|
||||
){
|
||||
// TX frequency not allowed
|
||||
@@ -1287,6 +1275,10 @@ void RADIO_PrepareTX(void)
|
||||
|
||||
FUNCTION_Select(FUNCTION_TRANSMIT);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
RXTX_LOG_BeginTx(gCurrentVfo);
|
||||
#endif
|
||||
|
||||
gTxTimerCountdown_500ms = 0; // no timeout
|
||||
|
||||
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
|
||||
@@ -1343,7 +1335,16 @@ void RADIO_SendCssTail(void)
|
||||
|
||||
void RADIO_SendEndOfTransmission(void)
|
||||
{
|
||||
BK4819_PlayRoger();
|
||||
BK4819_FilterBandwidth_t Bandwidth = gCurrentVfo->CHANNEL_BANDWIDTH;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_NARROWER
|
||||
if(Bandwidth == BK4819_FILTER_BW_NARROW && gSetting_set_nfm == 1)
|
||||
{
|
||||
Bandwidth = BK4819_FILTER_BW_NARROWER;
|
||||
}
|
||||
#endif
|
||||
|
||||
BK4819_PlayRoger(Bandwidth);
|
||||
DTMF_SendEndOfTransmission();
|
||||
|
||||
// send the CTCSS/DCS tail tone - allows the receivers to mute the usual FM squelch tail/crash
|
||||
|
||||
@@ -149,6 +149,9 @@ extern DCS_CodeType_t gCurrentCodeType;
|
||||
|
||||
extern VfoState_t VfoState[2];
|
||||
|
||||
// Human-readable label per VfoState_t (defined in ui/main.c), e.g. "TX DISABLE".
|
||||
extern const char *const VfoStateStr[];
|
||||
|
||||
bool RADIO_CheckValidList(uint8_t scanList);
|
||||
void RADIO_NextValidList(int8_t direction);
|
||||
bool RADIO_CheckValidChannel(uint16_t channel, bool checkScanList, uint8_t scanList);
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/* Copyright 2024 Armel F4HWN
|
||||
* https://github.com/armel
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "debugging.h"
|
||||
#include "driver/st7565.h"
|
||||
#include "screenshot.h"
|
||||
#include "misc.h"
|
||||
#include "driver/vcp.h"
|
||||
#include "driver/keyboard.h"
|
||||
|
||||
// SRAM optimization: minimize static allocations
|
||||
// - previousFrame: 1024 bytes (REQUIRED - need to compare for delta)
|
||||
// - No currentFrame or deltaFrame static buffers
|
||||
static uint8_t previousFrame[1024] = {0};
|
||||
static uint8_t forcedBlock = 0;
|
||||
static uint8_t keepAlive = 3;
|
||||
|
||||
void SCREENSHOT_ParseInput(void)
|
||||
{
|
||||
if (SCREENSHOT_IsLocked())
|
||||
return;
|
||||
|
||||
if (UART_IsCableConnected()) {
|
||||
keepAlive = 15;
|
||||
gUSB_ScreenshotEnabled = false;
|
||||
}
|
||||
else if (VCP_ScreenshotPing()) {
|
||||
keepAlive = 15;
|
||||
gUSB_ScreenshotEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void SCREENSHOT_Send(const uint8_t *buf, uint16_t len)
|
||||
{
|
||||
if (gUSB_ScreenshotEnabled) {
|
||||
cdc_acm_data_send_with_dtr(buf, len);
|
||||
} else {
|
||||
UART_Send(buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
void SCREENSHOT_Line(uint8_t *src, uint8_t *dest, uint16_t *idx) {
|
||||
for (uint8_t b = 0; b < 8; b++) {
|
||||
for (uint8_t i = 0; i < 128; i += 8) {
|
||||
uint8_t acc = 0;
|
||||
for (uint8_t k = 0; k < 8; k++) {
|
||||
if (src[i + k] & (1 << b)) acc |= (1 << k);
|
||||
}
|
||||
dest[(*idx)++] = gSetting_set_inv ? ~acc : acc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SCREENSHOT_Update(bool force)
|
||||
{
|
||||
// Build frame in a temporary stack buffer
|
||||
// This is 1024 bytes but it's temporary and gets freed after the function
|
||||
uint8_t frameBuffer[1024];
|
||||
uint16_t index = 0;
|
||||
uint8_t acc = 0;
|
||||
uint8_t bitCount = 0;
|
||||
static bool wasConnected = false;
|
||||
|
||||
if (SCREENSHOT_IsLocked())
|
||||
return;
|
||||
|
||||
if (keepAlive > 0) {
|
||||
if (--keepAlive == 0) {
|
||||
// Connection just lost → reset state for next reconnection
|
||||
wasConnected = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection is alive — detect reconnection and force full frame
|
||||
if (!wasConnected) {
|
||||
force = true;
|
||||
wasConnected = true;
|
||||
}
|
||||
|
||||
// ==== BUILD FRAME ONCE ====
|
||||
// Status line: 8 bit layers × 128 columns
|
||||
SCREENSHOT_Line(gStatusLine, frameBuffer, &index);
|
||||
|
||||
// Frame buffer: 7 lines × 8 bit layers × 128 columns
|
||||
for (uint8_t l = 0; l < 7; l++) {
|
||||
SCREENSHOT_Line(gFrameBuffer[l], frameBuffer, &index);
|
||||
}
|
||||
|
||||
if (bitCount > 0)
|
||||
frameBuffer[index++] = acc;
|
||||
|
||||
if (index != 1024)
|
||||
return;
|
||||
|
||||
// ==== FIRST PASS: Count changed chunks ====
|
||||
uint16_t deltaLen = 0;
|
||||
uint8_t changedChunks[128]; // List of changed chunk indices
|
||||
uint8_t changedCount = 0;
|
||||
|
||||
for (uint8_t chunk = 0; chunk < 128; chunk++) {
|
||||
uint8_t *cur = &frameBuffer[chunk * 8];
|
||||
uint8_t *prev = &previousFrame[chunk * 8];
|
||||
|
||||
bool changed = memcmp(cur, prev, 8) != 0;
|
||||
bool isForced = (chunk == forcedBlock);
|
||||
bool fullUpdate = force;
|
||||
|
||||
if (changed || isForced || fullUpdate) {
|
||||
changedChunks[changedCount++] = chunk;
|
||||
deltaLen += 9;
|
||||
}
|
||||
}
|
||||
|
||||
forcedBlock = (forcedBlock + 1) % 128;
|
||||
|
||||
if (deltaLen == 0)
|
||||
return;
|
||||
|
||||
// Skip transmission if a key is currently pressed
|
||||
// UART_Send is blocking - would freeze the main loop and lose keypresses
|
||||
if (gKeyReading0 != KEY_INVALID)
|
||||
return;
|
||||
|
||||
// ==== Send version marker (for backward compatibility detection) ====
|
||||
// New format: sends 0xFF before header
|
||||
// Old format: doesn't exist, so viewers can differentiate
|
||||
uint8_t versionMarker = 0xFF;
|
||||
SCREENSHOT_Send(&versionMarker, 1);
|
||||
|
||||
// ==== Send header ====
|
||||
uint8_t header[5] = {
|
||||
0xAA, 0x55, 0x02,
|
||||
(uint8_t)(deltaLen >> 8),
|
||||
(uint8_t)(deltaLen & 0xFF)
|
||||
};
|
||||
|
||||
SCREENSHOT_Send(header, 5);
|
||||
|
||||
// ==== SECOND PASS: Send only changed chunks ====
|
||||
uint8_t chunk[9];
|
||||
|
||||
for (uint8_t i = 0; i < changedCount; i++) {
|
||||
uint8_t chunkIdx = changedChunks[i];
|
||||
uint8_t *cur = &frameBuffer[chunkIdx * 8];
|
||||
uint8_t *prev = &previousFrame[chunkIdx * 8];
|
||||
|
||||
chunk[0] = chunkIdx;
|
||||
memcpy(&chunk[1], cur, 8);
|
||||
|
||||
SCREENSHOT_Send(chunk, 9);
|
||||
|
||||
// Update previousFrame for next comparison
|
||||
memcpy(prev, cur, 8);
|
||||
}
|
||||
|
||||
uint8_t end = 0x0A;
|
||||
SCREENSHOT_Send(&end, 1);
|
||||
}
|
||||
+41
-61
@@ -454,11 +454,20 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
|
||||
// 1FF0..0x1FF7
|
||||
// TODO: address TBD
|
||||
PY25Q16_ReadBuffer(0x00A158, Data, 8);
|
||||
const uint8_t set_ptt_scn = Data[7] & 0x0F;
|
||||
const uint8_t set_ptt_scn_sav = Data[7] & 0x0F;
|
||||
const bool set_ptt_scn_sav_erased = Data[7] == 0xFF;
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
const bool set_ptt_scn_sav_valid = !set_ptt_scn_sav_erased && set_ptt_scn_sav < (SET_SAV_LEN << 2);
|
||||
#else
|
||||
const bool set_ptt_scn_sav_valid = !set_ptt_scn_sav_erased && set_ptt_scn_sav < 4;
|
||||
#endif
|
||||
gSetting_set_pwr = (((Data[7] & 0xF0) >> 4) < 7) ? ((Data[7] & 0xF0) >> 4) : 0;
|
||||
gSetting_set_ptt = (set_ptt_scn < 4) ? (set_ptt_scn & 0x01) : 0;
|
||||
gSetting_set_ptt = set_ptt_scn_sav_valid ? (set_ptt_scn_sav & 0x01) : 0;
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
gSetting_set_scn = (set_ptt_scn < 4) ? ((set_ptt_scn & 0x02) == 0) : 1;
|
||||
gSetting_set_scn = set_ptt_scn_sav_valid ? ((set_ptt_scn_sav & 0x02) == 0) : 0;
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
gSetting_set_sav = set_ptt_scn_sav_valid ? ((set_ptt_scn_sav >> 2) & 0x03) : SET_SAV_OFF;
|
||||
#endif
|
||||
|
||||
gSetting_set_tot = (((Data[6] & 0xF0) >> 4) < 4) ? ((Data[6] & 0xF0) >> 4) : 0;
|
||||
@@ -468,7 +477,6 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
|
||||
int tmp = ((Data[5] & 0xF0) >> 4);
|
||||
|
||||
gSetting_set_inv = (((tmp >> 0) & 0x01) < 2) ? ((tmp >> 0) & 0x01): 0;
|
||||
gSetting_set_lck = (((tmp >> 1) & 0x01) < 2) ? ((tmp >> 1) & 0x01): 0;
|
||||
gSetting_set_met = (((tmp >> 2) & 0x01) < 2) ? ((tmp >> 2) & 0x01): 0;
|
||||
gSetting_set_gui = (((tmp >> 3) & 0x01) < 2) ? ((tmp >> 3) & 0x01): 0;
|
||||
gSetting_set_ctr = (((Data[5] & 0x0F)) > 00 && ((Data[5] & 0x0F)) < 16) ? ((Data[5] & 0x0F)) : 10;
|
||||
@@ -483,7 +491,7 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
|
||||
#else
|
||||
gSetting_set_inv = 0;
|
||||
#endif
|
||||
gSetting_set_lck = (tmp >> 1) & 0x01;
|
||||
gSetting_set_lck = (Data[2] < SET_LCK_LEN) ? Data[2] : SET_LCK_KEYS;
|
||||
gSetting_set_met = (tmp >> 2) & 0x01;
|
||||
gSetting_set_gui = (tmp >> 3) & 0x01;
|
||||
|
||||
@@ -501,7 +509,6 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
|
||||
|
||||
// And set special session settings for actions
|
||||
gSetting_set_ptt_session = gSetting_set_ptt;
|
||||
gEeprom.KEY_LOCK_PTT = gSetting_set_lck;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1110,8 +1117,6 @@ void SETTINGS_SaveSettings(void)
|
||||
|
||||
if(gSetting_set_inv == 1)
|
||||
tmp = tmp | (1 << 0);
|
||||
if (gSetting_set_lck == 1)
|
||||
tmp = tmp | (1 << 1);
|
||||
if (gSetting_set_met == 1)
|
||||
tmp = tmp | (1 << 2);
|
||||
if (gSetting_set_gui == 1)
|
||||
@@ -1125,21 +1130,22 @@ void SETTINGS_SaveSettings(void)
|
||||
#endif
|
||||
|
||||
tmp = (gSetting_set_inv << 0) |
|
||||
(gSetting_set_lck << 1) |
|
||||
(gSetting_set_met << 2) |
|
||||
(gSetting_set_gui << 3);
|
||||
|
||||
State[2] = gSetting_set_lck;
|
||||
State[5] = ((tmp << 4) | (gSetting_set_ctr & 0x0F));
|
||||
State[6] = ((gSetting_set_tot << 4) | (gSetting_set_eot & 0x0F));
|
||||
uint8_t set_ptt_scn = gSetting_set_ptt & 0x01;
|
||||
uint8_t set_ptt_scn_sav = gSetting_set_ptt & 0x01;
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
if (!gSetting_set_scn)
|
||||
set_ptt_scn |= 0x02;
|
||||
set_ptt_scn_sav |= 0x02;
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
set_ptt_scn_sav |= (gSetting_set_sav & 0x03) << 2;
|
||||
#endif
|
||||
|
||||
State[7] = ((gSetting_set_pwr << 4) | set_ptt_scn);
|
||||
|
||||
gEeprom.KEY_LOCK_PTT = gSetting_set_lck;
|
||||
State[7] = ((gSetting_set_pwr << 4) | set_ptt_scn_sav);
|
||||
|
||||
PY25Q16_WriteBuffer(0x00A158, SecBuf, 8, false);
|
||||
#endif
|
||||
@@ -1204,7 +1210,7 @@ void SETTINGS_SaveChannel(uint16_t Channel, uint8_t VFO, const VFO_Info_t *pVFO,
|
||||
|
||||
PY25Q16_WriteBuffer(OffsetVFO, Buf, 0x10, false);
|
||||
|
||||
SETTINGS_UpdateChannel(Channel, pVFO, true, true, true);
|
||||
SETTINGS_UpdateChannel(Channel, pVFO, true);
|
||||
|
||||
if (IS_MR_CHANNEL(Channel)) {
|
||||
#ifndef ENABLE_KEEP_MEM_NAME
|
||||
@@ -1235,58 +1241,32 @@ void SETTINGS_SaveChannelName(uint16_t channel, const char * name)
|
||||
PY25Q16_WriteBuffer(0x004000 + offset, buf, 0x10, false);
|
||||
}
|
||||
|
||||
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep, bool check, bool save)
|
||||
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep)
|
||||
{
|
||||
#ifdef ENABLE_NOAA
|
||||
if (!IS_NOAA_CHANNEL(channel))
|
||||
if (IS_NOAA_CHANNEL(channel))
|
||||
return;
|
||||
#endif
|
||||
{
|
||||
ChannelAttributes_t state;
|
||||
ChannelAttributes_t att = {
|
||||
.band = 0x7,
|
||||
.compander = 0,
|
||||
.unused_1 = 0,
|
||||
.unused_2 = 0,
|
||||
.exclude = 0,
|
||||
.scanlist = 0,
|
||||
}; // default attributes
|
||||
|
||||
// 0x0D60
|
||||
PY25Q16_ReadBuffer(0x008000 + (channel * 2), &state, 2);
|
||||
ChannelAttributes_t att = {
|
||||
.band = 0x7,
|
||||
.compander = 0,
|
||||
.unused_1 = 0,
|
||||
.unused_2 = 0,
|
||||
.exclude = 0,
|
||||
.scanlist = 0,
|
||||
};
|
||||
|
||||
if (keep) {
|
||||
att.band = pVFO->Band;
|
||||
att.compander = pVFO->Compander;
|
||||
att.unused_1 = 0;
|
||||
att.unused_2 = 0;
|
||||
att.exclude = 0;
|
||||
att.scanlist = pVFO->SCANLIST_PARTICIPATION;
|
||||
if (check && state.__val == att.__val)
|
||||
return; // no change in the attributes
|
||||
}
|
||||
|
||||
state.__val = att.__val;
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
save = true;
|
||||
#endif
|
||||
if(save)
|
||||
{
|
||||
uint16_t buf[MR_CHANNELS_MAX + 24];
|
||||
PY25Q16_ReadBuffer(0x008000, buf, sizeof(buf));
|
||||
buf[channel] = state.__val;
|
||||
PY25Q16_WriteBuffer(0x008000, buf, sizeof(buf), false);
|
||||
}
|
||||
|
||||
MR_SetChannelAttributes(channel, &att);
|
||||
|
||||
if (IS_MR_CHANNEL(channel)) { // it's a memory channel
|
||||
if (!keep) {
|
||||
// clear/reset the channel name
|
||||
SETTINGS_SaveChannelName(channel, "");
|
||||
}
|
||||
}
|
||||
if (keep) {
|
||||
att.band = pVFO->Band;
|
||||
att.compander = pVFO->Compander;
|
||||
att.scanlist = pVFO->SCANLIST_PARTICIPATION;
|
||||
}
|
||||
|
||||
MR_SetChannelAttributes(channel, &att);
|
||||
|
||||
if (IS_MR_CHANNEL(channel) && !keep)
|
||||
SETTINGS_SaveChannelName(channel, "");
|
||||
}
|
||||
|
||||
void SETTINGS_WriteBuildOptions(void)
|
||||
|
||||
+7
-6
@@ -127,12 +127,14 @@ enum ACTION_OPT_t {
|
||||
ACTION_OPT_REMOVE_OFFSET,
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ENABLE_REGA
|
||||
ACTION_OPT_REGA_ALARM,
|
||||
ACTION_OPT_REGA_TEST,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
ACTION_OPT_BEAM,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
ACTION_OPT_RXTX_LOG,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
ACTION_OPT_FOXHUNT,
|
||||
#endif
|
||||
ACTION_OPT_LEN
|
||||
};
|
||||
@@ -200,7 +202,6 @@ typedef struct {
|
||||
uint8_t TX_TIMEOUT_TIMER;
|
||||
bool KEY_LOCK;
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
bool KEY_LOCK_PTT;
|
||||
bool SET_NAV;
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
@@ -348,7 +349,7 @@ void SETTINGS_SaveSettings(void);
|
||||
void SETTINGS_SaveChannelName(uint16_t channel, const char * name);
|
||||
void SETTINGS_SaveChannel(uint16_t Channel, uint8_t VFO, const VFO_Info_t *pVFO, uint8_t Mode);
|
||||
void SETTINGS_SaveBatteryCalibration(const uint16_t * batteryCalibration);
|
||||
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep, bool check, bool save);
|
||||
void SETTINGS_UpdateChannel(uint16_t channel, const VFO_Info_t *pVFO, bool keep);
|
||||
void SETTINGS_WriteBuildOptions(void);
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
|
||||
void SETTINGS_WriteCurrentState(void);
|
||||
|
||||
+45
-52
@@ -37,9 +37,8 @@ static int get_bit(uint8_t* array, int bit_index) {
|
||||
|
||||
void UI_DisplayAircopy(void)
|
||||
{
|
||||
char String[16] = { 0 };
|
||||
char *pPrintStr = { 0 };
|
||||
uint16_t percent;
|
||||
char String[16];
|
||||
char *pPrintStr;
|
||||
|
||||
UI_DisplayClear();
|
||||
|
||||
@@ -60,15 +59,13 @@ void UI_DisplayAircopy(void)
|
||||
// show the remaining 2 small frequency digits
|
||||
UI_PrintStringSmallNormal(String + 7, 97, 0, 3);
|
||||
String[7] = 0;
|
||||
// show the main large frequency digits
|
||||
UI_DisplayFrequency(String, 16, 2, false);
|
||||
} else {
|
||||
const char *ascii = INPUTBOX_GetAscii();
|
||||
sprintf(String, "%.3s.%.3s", ascii, ascii + 3);
|
||||
UI_DisplayFrequency(String, 16, 2, false);
|
||||
}
|
||||
|
||||
memset(String, 0, sizeof(String));
|
||||
// show the main large frequency digits
|
||||
UI_DisplayFrequency(String, 16, 2, false);
|
||||
|
||||
// Get the current map and calculate percentage based on its total blocks
|
||||
const AIRCOPY_TransferMap_t *currentMap = AIRCOPY_GetCurrentMap();
|
||||
@@ -78,47 +75,44 @@ void UI_DisplayAircopy(void)
|
||||
if (doneBlocks > currentMap->total_blocks)
|
||||
doneBlocks = currentMap->total_blocks;
|
||||
|
||||
percent = (doneBlocks * 10000) / currentMap->total_blocks;
|
||||
|
||||
if (gAirCopyIsSendMode == 0) {
|
||||
sprintf(String, "RCV:%02u.%02u%% E:%d", percent / 100, percent % 100, gErrorsDuringAirCopy);
|
||||
} else if (gAirCopyIsSendMode == 1) {
|
||||
sprintf(String, "SND:%02u.%02u%%", percent / 100, percent % 100);
|
||||
}
|
||||
|
||||
// Draw gauge
|
||||
if(gAircopyStep != 0)
|
||||
{
|
||||
UI_PrintString(String, 2, 127, 5, 8);
|
||||
|
||||
gFrameBuffer[4][1] = 0x3c;
|
||||
gFrameBuffer[4][2] = 0x42;
|
||||
|
||||
for(uint8_t i = 1; i <= AIRCOPY_BAR_WIDTH + 2; i++)
|
||||
{
|
||||
gFrameBuffer[4][2 + i] = 0x81;
|
||||
}
|
||||
|
||||
gFrameBuffer[4][125] = 0x42;
|
||||
gFrameBuffer[4][126] = 0x3c;
|
||||
}
|
||||
|
||||
// Draw memory selection
|
||||
if(gAircopyState == AIRCOPY_READY)
|
||||
if (gAircopyState == AIRCOPY_READY)
|
||||
{
|
||||
doneBlocks = 0;
|
||||
|
||||
memset(gFrameBuffer[5], 0, 128);
|
||||
memset(gFrameBuffer[6], 0, 128);
|
||||
|
||||
if(gAircopyCurrentMapIndex < AIRCOPY_NUM_BANKS) {
|
||||
sprintf(String, "MEM %03u - %03u%", (gAircopyCurrentMapIndex * 128) + 1, (gAircopyCurrentMapIndex + 1) * 128);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(String, "Settings");
|
||||
sprintf(String, "MEM %03u - %03u", (gAircopyCurrentMapIndex * 128) + 1, (gAircopyCurrentMapIndex + 1) * 128);
|
||||
} else {
|
||||
strcpy(String, "Settings");
|
||||
}
|
||||
UI_PrintString(String, 2, 127, 5, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint16_t percent = (doneBlocks * 10000) / currentMap->total_blocks;
|
||||
|
||||
if (gAirCopyIsSendMode == 0) {
|
||||
sprintf(String, "RCV:%02u.%02u%% E:%d", percent / 100, percent % 100, gErrorsDuringAirCopy);
|
||||
} else {
|
||||
sprintf(String, "SND:%02u.%02u%%", percent / 100, percent % 100);
|
||||
}
|
||||
|
||||
// Draw gauge
|
||||
if(gAircopyStep != 0)
|
||||
{
|
||||
UI_PrintString(String, 2, 127, 5, 8);
|
||||
|
||||
gFrameBuffer[4][1] = 0x3c;
|
||||
gFrameBuffer[4][2] = 0x42;
|
||||
|
||||
for(uint8_t i = 1; i <= AIRCOPY_BAR_WIDTH + 2; i++)
|
||||
{
|
||||
gFrameBuffer[4][2 + i] = 0x81;
|
||||
}
|
||||
|
||||
gFrameBuffer[4][125] = 0x42;
|
||||
gFrameBuffer[4][126] = 0x3c;
|
||||
}
|
||||
}
|
||||
|
||||
if (doneBlocks > 0)
|
||||
@@ -131,19 +125,12 @@ void UI_DisplayAircopy(void)
|
||||
lErrorsDuringAirCopy = gErrorsDuringAirCopy;
|
||||
}
|
||||
|
||||
const AIRCOPY_TransferMap_t *currentMap = AIRCOPY_GetCurrentMap();
|
||||
|
||||
uint16_t total = currentMap->total_blocks;
|
||||
uint16_t done = gAirCopyBlockNumber + gErrorsDuringAirCopy;
|
||||
|
||||
if (done > total) done = total;
|
||||
uint16_t b = 0;
|
||||
uint16_t fraction_accumulator = 0;
|
||||
|
||||
for (uint8_t col = 0; col < AIRCOPY_BAR_WIDTH; col++)
|
||||
{
|
||||
/* Map column [0..BAR_WIDTH-1] to block [0..total-1] */
|
||||
uint16_t b = (uint16_t)((col * (uint32_t)total) / AIRCOPY_BAR_WIDTH);
|
||||
|
||||
bool processed = (b < done);
|
||||
bool processed = (b < doneBlocks);
|
||||
bool error = processed && get_bit(crc, b);
|
||||
|
||||
if (!processed)
|
||||
@@ -152,8 +139,14 @@ void UI_DisplayAircopy(void)
|
||||
gFrameBuffer[4][col + 4] = 0x81; // error gap (intentional hole)
|
||||
else
|
||||
gFrameBuffer[4][col + 4] = 0xBD; // ok filled
|
||||
}
|
||||
|
||||
// DDA/Bresenham algorythm
|
||||
fraction_accumulator += currentMap->total_blocks;
|
||||
while (fraction_accumulator >= AIRCOPY_BAR_WIDTH) {
|
||||
fraction_accumulator -= AIRCOPY_BAR_WIDTH;
|
||||
b++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ST7565_BlitFullScreen();
|
||||
|
||||
+1
-2
@@ -31,7 +31,7 @@
|
||||
|
||||
void UI_DisplayFM(void)
|
||||
{
|
||||
char String[16] = {0};
|
||||
char String[16];
|
||||
char *pPrintStr = String;
|
||||
UI_DisplayClear();
|
||||
|
||||
@@ -80,7 +80,6 @@ void UI_DisplayFM(void)
|
||||
|
||||
UI_PrintString(pPrintStr, 0, 127, 3, 10); // memory, vfo, scan
|
||||
|
||||
memset(String, 0, sizeof(String));
|
||||
if (gAskToSave || (gEeprom.FM_IsMrMode && gInputBoxIndex > 0)) {
|
||||
UI_GenerateChannelString(String, gFM_ChannelPosition);
|
||||
} else if (gAskToDelete) {
|
||||
|
||||
@@ -40,6 +40,8 @@ void UI_GenerateChannelString(char *pString, const uint16_t Channel)
|
||||
pString[2] = '-';
|
||||
for (i = 0; i < 2; i++)
|
||||
pString[i + 3] = (gInputBox[i] == 10) ? '-' : gInputBox[i] + '0';
|
||||
|
||||
pString[5] = 0;
|
||||
}
|
||||
|
||||
void UI_GenerateChannelStringEx(char *pString, const bool bShowPrefix, const uint16_t ChannelNumber)
|
||||
@@ -323,6 +325,23 @@ static void sort(int16_t *a, int16_t *b)
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_DisplaySmallestInverse(const char *pString, uint8_t x, uint8_t Line,
|
||||
bool statusbar, bool fill, uint8_t end)
|
||||
{
|
||||
// First draw the string normally
|
||||
GUI_DisplaySmallest(pString, x, (Line * 8) + 1, statusbar, fill);
|
||||
|
||||
// Now invert the framebuffer/statusline bits for the rendered area
|
||||
uint8_t start = (x - 2);
|
||||
uint8_t *buffer = statusbar ? gStatusLine : gFrameBuffer[Line];
|
||||
|
||||
buffer[start] ^= 0x3E;
|
||||
for (uint8_t i = start + 1; i < end; i++) {
|
||||
buffer[i] ^= 0x7F;
|
||||
}
|
||||
buffer[end] ^= 0x3E;
|
||||
}
|
||||
|
||||
void UI_DisplayUnlockKeyboard(uint8_t shift) {
|
||||
if (gEeprom.KEY_LOCK && gKeypadLocked > 0)
|
||||
{ // tell user how to unlock the keyboard
|
||||
|
||||
@@ -38,6 +38,7 @@ void UI_DrawPixelBuffer(uint8_t (*buffer)[128], uint8_t x, uint8_t y, bool black
|
||||
void PutPixel(uint8_t x, uint8_t y, bool fill);
|
||||
void PutPixelStatus(uint8_t x, uint8_t y, bool fill);
|
||||
void GUI_DisplaySmallest(const char *pString, uint8_t x, uint8_t y, bool statusbar, bool fill);
|
||||
void GUI_DisplaySmallestInverse(const char *pString, uint8_t x, uint8_t Line, bool statusbar, bool fill, uint8_t endX);
|
||||
void UI_DisplayUnlockKeyboard(uint8_t shift);
|
||||
bool IsEmptyName(const char *name, uint8_t len);
|
||||
#endif
|
||||
|
||||
+267
-27
@@ -17,6 +17,10 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h> // abs()
|
||||
|
||||
#include "app/app.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
#include "app/action.h"
|
||||
#endif
|
||||
#include "app/chFrScanner.h"
|
||||
#include "app/dtmf.h"
|
||||
|
||||
@@ -67,6 +71,7 @@ center_line_t center_line = CENTER_LINE_NONE;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
|
||||
#define SCAN_PROGRESS_MR_CHANNEL_BYTES ((MR_CHANNELS_MAX + 7u) / 8u)
|
||||
#define SCAN_LIST_NAME_HOLD_500MS (2000u / 500u)
|
||||
|
||||
static bool gScanProgressSessionActive;
|
||||
static bool gScanProgressSessionIsMemory;
|
||||
@@ -81,6 +86,7 @@ static bool gScanProgressPrevResetVfosFlag;
|
||||
static bool gScanProgressForceRebuild;
|
||||
static uint16_t gScanProgressLastMemoryIndex;
|
||||
static uint8_t gScanProgressPriorityState;
|
||||
static uint8_t gScanListNameCountdown_500ms;
|
||||
#define SCAN_PROGRESS_PRIORITY_LABEL_MASK 0x03u
|
||||
#define SCAN_PROGRESS_PRIORITY_SEEN_SHIFT 2
|
||||
#define SCAN_PROGRESS_PRIORITY_SEEN_MASK 0x1cu
|
||||
@@ -89,15 +95,24 @@ static uint8_t gScanProgressPriorityState;
|
||||
#define SCAN_PROGRESS_PRIORITY_HOLD_FRAMES 6
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_BEAM) || defined(ENABLE_FEAT_F4HWN_SCAN_PROGRESS)
|
||||
// Shared center-line renderer: clear the line and print bold text on it.
|
||||
static void UI_MAIN_DrawCenterBoldLine(const char *text, uint8_t start)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
const uint8_t line = isMainOnly() ? 5 : 3;
|
||||
#else
|
||||
const uint8_t line = 3;
|
||||
#endif
|
||||
memset(gFrameBuffer[line], 0, LCD_WIDTH);
|
||||
UI_PrintStringSmallBold(text, start, LCD_WIDTH - 1, line);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
static void UI_MAIN_DrawBeamLine(void)
|
||||
{
|
||||
const char *text;
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
const unsigned int line = isMainOnly() ? 5 : 3;
|
||||
#else
|
||||
const unsigned int line = 3;
|
||||
#endif
|
||||
|
||||
switch (gBeamStatus) {
|
||||
case BEAM_STATUS_TX_WAIT:
|
||||
@@ -124,12 +139,11 @@ static void UI_MAIN_DrawBeamLine(void)
|
||||
break;
|
||||
}
|
||||
|
||||
memset(gFrameBuffer[line], 0, LCD_WIDTH);
|
||||
UI_PrintStringSmallBold(text, 2, 127, line);
|
||||
UI_MAIN_DrawCenterBoldLine(text, 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *VfoStateStr[] = {
|
||||
const char *const VfoStateStr[] = {
|
||||
[VFO_STATE_NORMAL]="",
|
||||
[VFO_STATE_BUSY]="BUSY",
|
||||
[VFO_STATE_BAT_LOW]="BAT LOW",
|
||||
@@ -139,6 +153,71 @@ const char *VfoStateStr[] = {
|
||||
[VFO_STATE_VOLTAGE_HIGH]="VOLT HIGH"
|
||||
};
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_FASTER) && defined(ENABLE_FEAT_F4HWN_SCAN_RSSI)
|
||||
static uint8_t UI_MAIN_GetScanRssiSparklineMask(uint8_t previousLevel, uint8_t level)
|
||||
{
|
||||
uint8_t mask = 0;
|
||||
const uint8_t bottom = 5;
|
||||
|
||||
if (level == 0)
|
||||
return (uint8_t)(1u << bottom);
|
||||
|
||||
const uint8_t top = bottom + 1 - level;
|
||||
uint8_t bridgeTop = top;
|
||||
uint8_t bridgeBottom = top;
|
||||
|
||||
if (previousLevel > 0)
|
||||
{
|
||||
const uint8_t previousTop = bottom + 1 - previousLevel;
|
||||
if (previousTop < bridgeTop)
|
||||
bridgeTop = previousTop;
|
||||
else if (previousTop > bridgeBottom)
|
||||
bridgeBottom = previousTop;
|
||||
}
|
||||
|
||||
// Solid crest line with a small vertical bridge to avoid broken diagonals.
|
||||
for (uint8_t y = bridgeTop; y <= bridgeBottom; y++)
|
||||
mask |= (uint8_t)(1u << y);
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static void UI_MAIN_DrawScanRssiSparkline(uint8_t line)
|
||||
{
|
||||
uint8_t *p_line = gFrameBuffer[line];
|
||||
const uint8_t x0 = 7;
|
||||
uint8_t level[CHFRSCANNER_RSSI_SPARKLINE_WIDTH];
|
||||
|
||||
if (!CHFRSCANNER_HasScanRssiSparkline())
|
||||
return;
|
||||
|
||||
for (uint8_t i = 0; i < CHFRSCANNER_RSSI_SPARKLINE_WIDTH; i++)
|
||||
level[i] = CHFRSCANNER_GetScanRssiSparklineLevel(i);
|
||||
|
||||
for (uint8_t i = 0; i < CHFRSCANNER_RSSI_SPARKLINE_WIDTH; i++)
|
||||
{
|
||||
const uint8_t previousRaw = (i > 0) ? level[i - 1] : 0;
|
||||
const uint8_t nextRaw = (i + 1 < CHFRSCANNER_RSSI_SPARKLINE_WIDTH) ? level[i + 1] : 0;
|
||||
uint8_t displayLevel = (uint8_t)((previousRaw + 2u * level[i] + nextRaw + 2u) >> 2);
|
||||
uint8_t previousLevel = previousRaw;
|
||||
|
||||
if (level[i] == 0 && previousRaw == 0 && nextRaw == 0)
|
||||
displayLevel = 0;
|
||||
else if (displayLevel == 0)
|
||||
displayLevel = 1;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
const uint8_t previousPreviousRaw = (i > 1) ? level[i - 2] : 0;
|
||||
previousLevel = (uint8_t)((previousPreviousRaw + 2u * previousRaw + level[i] + 2u) >> 2);
|
||||
}
|
||||
|
||||
const uint8_t mask = UI_MAIN_GetScanRssiSparklineMask(previousLevel, displayLevel);
|
||||
p_line[x0 + i] = (p_line[x0 + i] & 0x80) | mask;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
|
||||
static void ScanProgress_ResetSession(void)
|
||||
{
|
||||
@@ -148,6 +227,7 @@ static void ScanProgress_ResetSession(void)
|
||||
gScanProgressForceRebuild = false;
|
||||
gScanProgressLastMemoryIndex = 0;
|
||||
gScanProgressPriorityState = 0;
|
||||
gScanListNameCountdown_500ms = 0;
|
||||
}
|
||||
|
||||
void UI_MAIN_NotifyScanProgressDataChanged(void)
|
||||
@@ -156,6 +236,27 @@ void UI_MAIN_NotifyScanProgressDataChanged(void)
|
||||
gUpdateStatus = true;
|
||||
}
|
||||
|
||||
void UI_MAIN_NotifyScanListChanged(void)
|
||||
{
|
||||
UI_MAIN_NotifyScanProgressDataChanged();
|
||||
gScanListNameCountdown_500ms = SCAN_LIST_NAME_HOLD_500MS;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
|
||||
// True while the "SCAN LIST xxx" name actually occupies the screen. Used to
|
||||
// freeze the scan so it does not race ahead of the (hidden) progress gauge,
|
||||
// which would make the bar snap forward when it reappears.
|
||||
//
|
||||
// The IS_MR_CHANNEL() test mirrors show_memory in UI_DrawScanProgress(): the
|
||||
// name is only ever drawn during a memory (channel) scan. A frequency/range
|
||||
// scan can still arm the countdown (F + UP/DOWN cycles lists on any scan), but
|
||||
// no name is shown there, so it must NOT be held - otherwise the scanner would
|
||||
// stall ~2 s with nothing on screen to explain the pause.
|
||||
bool UI_MAIN_ShouldHoldScanResume(void)
|
||||
{
|
||||
return gScanListNameCountdown_500ms > 0 && IS_MR_CHANNEL(gNextMrChannel);
|
||||
}
|
||||
|
||||
static inline void ScanProgress_SetBit(uint8_t *map, uint16_t ch)
|
||||
{
|
||||
map[ch >> 3] |= (uint8_t)(1u << (ch & 7));
|
||||
@@ -177,6 +278,33 @@ static uint8_t ScanProgress_GetActiveScanList(void)
|
||||
return scan_list;
|
||||
}
|
||||
|
||||
static void UI_MAIN_DrawScanListName(void)
|
||||
{
|
||||
const uint8_t scan_list = ScanProgress_GetActiveScanList();
|
||||
char text[16];
|
||||
|
||||
// Manual formatting instead of snprintf: much smaller on a divide-less M0 core
|
||||
strcpy(text, "SCAN LIST ");
|
||||
char *p = text + 10; // sizeof("SCAN LIST ") - 1
|
||||
|
||||
if (scan_list > MR_CHANNELS_LIST) {
|
||||
*p++ = 'A'; *p++ = 'L'; *p++ = 'L';
|
||||
} else {
|
||||
const char *name = gListName[scan_list - 1];
|
||||
|
||||
if (IsEmptyName(name, sizeof(gListName[0]))) {
|
||||
*p++ = (char)('0' + scan_list / 10);
|
||||
*p++ = (char)('0' + scan_list % 10);
|
||||
} else {
|
||||
for (uint8_t i = 0; i < 3 && name[i]; i++)
|
||||
*p++ = name[i];
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
|
||||
UI_MAIN_DrawCenterBoldLine(text, 0);
|
||||
}
|
||||
|
||||
static bool ScanProgress_ChannelBelongsToList(uint16_t channel, const ChannelAttributes_t *att, uint8_t scan_list)
|
||||
{
|
||||
if (!IS_MR_CHANNEL(channel))
|
||||
@@ -286,8 +414,7 @@ static void ScanProgress_DrawGaugeLine(uint8_t line, uint32_t current_index, uin
|
||||
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;
|
||||
head_col = MIN(head_col, fill_cols - 1);
|
||||
|
||||
gFrameBuffer[line][gauge_left] = 0x0c;
|
||||
gFrameBuffer[line][gauge_left + 1] = 0x12;
|
||||
@@ -321,16 +448,9 @@ static void ScanProgress_DrawGaugeLine(uint8_t line, uint32_t current_index, uin
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t ScanProgress_DecimalDigits(uint32_t value)
|
||||
static inline uint8_t ScanProgress_DecimalDigits(uint32_t value)
|
||||
{
|
||||
uint8_t digits = 1;
|
||||
|
||||
while (value >= 10u) {
|
||||
value /= 10u;
|
||||
digits++;
|
||||
}
|
||||
|
||||
return digits;
|
||||
return sprintf(NULL, "%u", value);
|
||||
}
|
||||
|
||||
static void ScanProgress_FormatIndex(char *out, size_t out_size, uint32_t current_index, uint32_t total, uint8_t width)
|
||||
@@ -432,6 +552,12 @@ static bool UI_DrawScanProgress(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Right after a scan-list change, briefly show its name instead of the progress bar
|
||||
if (show_memory && gScanListNameCountdown_500ms > 0) {
|
||||
UI_MAIN_DrawScanListName();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (show_memory) {
|
||||
const uint8_t scan_list = ScanProgress_GetActiveScanList();
|
||||
const bool reset_vfos_edge = gFlagResetVfos && !gScanProgressPrevResetVfosFlag;
|
||||
@@ -853,6 +979,8 @@ void UI_DisplayAudioScope(void)
|
||||
void DisplayRSSIBar(const bool now)
|
||||
{
|
||||
#if defined(ENABLE_RSSI_BAR)
|
||||
if (APP_IsScreenSaverDisplayed())
|
||||
return;
|
||||
|
||||
const unsigned int txt_width = 7 * 8; // 8 text chars
|
||||
const unsigned int bar_x = 2 + txt_width + 4; // X coord of bar graph
|
||||
@@ -899,13 +1027,17 @@ void DisplayRSSIBar(const bool now)
|
||||
p_line0[i] = (p_line0[i] & 0x80) | BITMAP_VFO_Empty[i];
|
||||
}
|
||||
|
||||
ST7565_BlitLine(RxLine);
|
||||
ST7565_DrawLine(0, RxLine + 1, p_line0, sizeof(BITMAP_VFO_Default));
|
||||
}
|
||||
|
||||
#else
|
||||
const unsigned int line = 3;
|
||||
#endif
|
||||
uint8_t *p_line = gFrameBuffer[line];
|
||||
char str[16];
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
uint8_t oldLine[LCD_WIDTH];
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
const char plus[] = {
|
||||
@@ -930,8 +1062,15 @@ void DisplayRSSIBar(const bool now)
|
||||
)
|
||||
return; // display is in use
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
if (now) {
|
||||
memcpy(oldLine, p_line, LCD_WIDTH);
|
||||
memset(p_line, 0, LCD_WIDTH);
|
||||
}
|
||||
#else
|
||||
if (now)
|
||||
memset(p_line, 0, LCD_WIDTH);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
int16_t rssi_dBm =
|
||||
@@ -1021,8 +1160,13 @@ void DisplayRSSIBar(const bool now)
|
||||
UI_PrintStringSmallNormal(str, 2, 0, line);
|
||||
#endif
|
||||
DrawLevelBar(bar_x, line, s_level + overS9Bars, 13);
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
if (now && memcmp(oldLine, p_line, LCD_WIDTH) != 0)
|
||||
ST7565_BlitLine(line);
|
||||
#else
|
||||
if (now)
|
||||
ST7565_BlitLine(line);
|
||||
#endif
|
||||
#else
|
||||
int16_t rssi = BK4819_GetRSSI();
|
||||
uint8_t Level;
|
||||
@@ -1091,6 +1235,10 @@ void UI_MAIN_PrintAGC(bool now)
|
||||
void UI_MAIN_TimeSlice500ms(void)
|
||||
{
|
||||
if(gScreenToDisplay==DISPLAY_MAIN) {
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
|
||||
if (gScanListNameCountdown_500ms > 0 && --gScanListNameCountdown_500ms == 0)
|
||||
gUpdateDisplay = true;
|
||||
#endif
|
||||
#ifdef ENABLE_AGC_SHOW_DATA
|
||||
UI_MAIN_PrintAGC(true);
|
||||
return;
|
||||
@@ -1157,6 +1305,43 @@ static void UI_FormatFrequency(uint32_t freq, char *buffer) {
|
||||
sprintf(buffer, "%3u.%05u", freq / 100000, freq % 100000);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_SCAN_RANGES) && defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
static void UI_PrintScanRangeCss(char *String, uint8_t LabelX, uint8_t ValueX, uint8_t Line)
|
||||
{
|
||||
if (gScanRangeCssType == CODE_TYPE_CONTINUOUS_TONE)
|
||||
{
|
||||
strcpy(String, "CTCSS");
|
||||
UI_PrintStringSmallNormalInverse(String, LabelX, 0, Line);
|
||||
sprintf(String, "%u.%uHz", CTCSS_Options[gScanRangeCssCode] / 10, CTCSS_Options[gScanRangeCssCode] % 10);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(String, "DCS");
|
||||
UI_PrintStringSmallNormalInverse(String, LabelX, 0, Line);
|
||||
sprintf(String, "D%03o%c", DCS_Options[gScanRangeCssCode], gScanRangeCssType == CODE_TYPE_REVERSE_DIGITAL ? 'I' : 'N');
|
||||
}
|
||||
|
||||
UI_PrintStringSmallNormal(String, ValueX, 0, Line);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
static void UI_PrintActionPickerLabel(uint8_t index, uint8_t line, bool big)
|
||||
{
|
||||
char label[20];
|
||||
strcpy(label, gSubMenu_SIDEFUNCTIONS[index].name);
|
||||
|
||||
char *newline = strchr(label, '\n');
|
||||
if (newline != NULL)
|
||||
*newline = ' ';
|
||||
|
||||
if (big)
|
||||
UI_PrintString(label, 0, LCD_WIDTH, line, 8);
|
||||
else
|
||||
UI_PrintStringSmallNormal(label, 0, LCD_WIDTH, line);
|
||||
}
|
||||
#endif
|
||||
|
||||
void UI_DisplayMain(void)
|
||||
{
|
||||
char String[22];
|
||||
@@ -1177,6 +1362,25 @@ void UI_DisplayMain(void)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0) {
|
||||
const uint8_t selection = gActionPickerSelection[gActionPickerKey - 1];
|
||||
uint8_t previous = selection - 1;
|
||||
uint8_t next = selection + 1;
|
||||
|
||||
if (previous == 0)
|
||||
previous = gSubMenu_SIDEFUNCTIONS_size - 1;
|
||||
if (next >= gSubMenu_SIDEFUNCTIONS_size)
|
||||
next = 1;
|
||||
|
||||
UI_PrintActionPickerLabel(previous, 1, false);
|
||||
UI_PrintActionPickerLabel(selection, 2, true);
|
||||
UI_PrintActionPickerLabel(next, 4, false);
|
||||
ST7565_BlitFullScreen();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
if (gEeprom.KEY_LOCK && gKeypadLocked > 0)
|
||||
{ // tell user how to unlock the keyboard
|
||||
@@ -1255,6 +1459,11 @@ void UI_DisplayMain(void)
|
||||
UI_FormatFrequency(gScanRangeStop, String);
|
||||
UI_PrintStringSmallNormal(String, 56, 0, line + shift + 1);
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
if (!isMainOnly() && gScanRangeCssCode != 0xFF)
|
||||
UI_PrintScanRangeCss(String, 6, 48, line + 2);
|
||||
#endif
|
||||
|
||||
if (!isMainOnly())
|
||||
continue;
|
||||
}
|
||||
@@ -1268,6 +1477,12 @@ void UI_DisplayMain(void)
|
||||
UI_PrintStringSmallNormal(String, 56, 0, line);
|
||||
UI_FormatFrequency(gScanRangeStop, String);
|
||||
UI_PrintStringSmallNormal(String, 56, 0, line + 1);
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
if (gScanRangeCssCode != 0xFF)
|
||||
UI_PrintScanRangeCss(String, 2, 44, line + 2);
|
||||
#endif
|
||||
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
@@ -1419,7 +1634,11 @@ void UI_DisplayMain(void)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(RxOnVfofrequency == frequency && !isMainOnly()) {
|
||||
if(RxOnVfofrequency == frequency && !isMainOnly()
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_FASTER) && defined(ENABLE_FEAT_F4HWN_SCAN_RSSI)
|
||||
&& !CHFRSCANNER_HasScanRssiSparkline()
|
||||
#endif
|
||||
) {
|
||||
//UI_PrintStringSmallNormal(">>", 8, 0, line);
|
||||
//memcpy(p_line0 + 14, BITMAP_VFO_Default, sizeof(BITMAP_VFO_Default));
|
||||
GUI_DisplaySmallest(">>", 8, RxLine == 0 ? 1 : 33, false, true);
|
||||
@@ -1431,10 +1650,10 @@ void UI_DisplayMain(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
if(TX_freq_check(frequency) != 0 && gEeprom.VfoInfo[vfo_num].TX_LOCK == true)
|
||||
if((gScanStateDir == SCAN_OFF || vfo_num != gEeprom.RX_VFO) && TX_freq_check(frequency) != 0 && gEeprom.VfoInfo[vfo_num].TX_LOCK == true)
|
||||
{
|
||||
if (!FUNCTION_IsRx() || RxOnVfofrequency != frequency)
|
||||
memcpy(p_line0 + 25, BITMAP_VFO_Lock, sizeof(BITMAP_VFO_Lock));
|
||||
memcpy(p_line0 + 24, BITMAP_VFO_Lock, sizeof(BITMAP_VFO_Lock));
|
||||
}
|
||||
|
||||
if (IS_MR_CHANNEL(gEeprom.ScreenChannel[vfo_num]))
|
||||
@@ -1601,6 +1820,9 @@ void UI_DisplayMain(void)
|
||||
xStart = 117;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
GUI_DisplaySmallestInverse(displayStr, xStart + 2, line, false, true, 127);
|
||||
#else
|
||||
GUI_DisplaySmallest(displayStr, xStart + 2, line == 0 ? 1 : 33, false, true);
|
||||
|
||||
gFrameBuffer[line][xStart] ^= 0x3E;
|
||||
@@ -1608,10 +1830,9 @@ void UI_DisplayMain(void)
|
||||
gFrameBuffer[line][x] ^= 0x7F;
|
||||
}
|
||||
gFrameBuffer[line][127] ^= 0x3E;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1939,7 +2160,11 @@ void UI_DisplayMain(void)
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
const char dir_list[][2] = {"", "+", "-", "D"};
|
||||
|
||||
if(gTxVfo->TX_OFFSET_FREQUENCY_DIRECTION != 0 && gTxVfo->pTX == &gTxVfo->freq_config_RX && !vfoInfo->FrequencyReverse)
|
||||
if(gRemoveOffset &&
|
||||
vfoInfo == gTxVfo &&
|
||||
gTxVfo->TX_OFFSET_FREQUENCY_DIRECTION != 0 &&
|
||||
gTxVfo->pTX == &gTxVfo->freq_config_RX &&
|
||||
!vfoInfo->FrequencyReverse)
|
||||
{
|
||||
i = 3;
|
||||
}
|
||||
@@ -2082,11 +2307,21 @@ void UI_DisplayMain(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_SCAN_FASTER) && defined(ENABLE_FEAT_F4HWN_SCAN_RSSI)
|
||||
if (gScanStateDir != SCAN_OFF && !FUNCTION_IsRx())
|
||||
UI_MAIN_DrawScanRssiSparkline(isMainOnly() ? 0 : (uint8_t)(gEeprom.RX_VFO * 4u));
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_AGC_SHOW_DATA
|
||||
center_line = CENTER_LINE_IN_USE;
|
||||
UI_MAIN_PrintAGC(false);
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_SCAN_RANGES) && defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE) && ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE
|
||||
if (isMainOnly() && gScanRangeStart && gScanRangeCssCode != 0xFF)
|
||||
UI_PrintScanRangeCss(String, 2, 46, 6);
|
||||
#endif
|
||||
|
||||
if (center_line == CENTER_LINE_NONE)
|
||||
{ // we're free to use the middle line
|
||||
|
||||
@@ -2225,13 +2460,18 @@ void UI_DisplayMain(void)
|
||||
if (isMainOnly() && !gDTMF_InputMode)
|
||||
{
|
||||
sprintf(String, "VFO %s", activeTxVFO ? "B" : "A");
|
||||
GUI_DisplaySmallest(String, 107, 50, false, true);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
GUI_DisplaySmallestInverse(String, 107, 6, false, true, 127);
|
||||
#else
|
||||
GUI_DisplaySmallest(String, 107, 49, false, true);
|
||||
|
||||
gFrameBuffer[6][105] ^= 0x7C;
|
||||
for (uint8_t x = 106; x < 127; x++) {
|
||||
gFrameBuffer[6][x] ^= 0xFE;
|
||||
}
|
||||
gFrameBuffer[6][127] ^= 0x7C;
|
||||
#endif
|
||||
|
||||
/*
|
||||
UI_PrintStringSmallBold(String, 92, 0, 6);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define UI_MAIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum center_line_t {
|
||||
CENTER_LINE_NONE = 0,
|
||||
@@ -55,8 +56,12 @@ void UI_DisplayMain(void);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
|
||||
void UI_MAIN_NotifyScanProgressDataChanged(void);
|
||||
void UI_MAIN_NotifyScanListChanged(void);
|
||||
bool UI_MAIN_ShouldHoldScanResume(void);
|
||||
#else
|
||||
static inline void UI_MAIN_NotifyScanProgressDataChanged(void) {}
|
||||
static inline void UI_MAIN_NotifyScanListChanged(void) {}
|
||||
static inline bool UI_MAIN_ShouldHoldScanResume(void) { return false; }
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_AGC_SHOW_DATA
|
||||
|
||||
+358
-108
@@ -171,6 +171,9 @@ const t_menu_item MenuList[] =
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
{"SetScn", MENU_SET_SCN },
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
{"SetSav", MENU_SET_SAV },
|
||||
#endif
|
||||
#endif
|
||||
// hidden menu items from here on
|
||||
// enabled if pressing both the PTT and upper side button at power-on
|
||||
@@ -197,7 +200,7 @@ const t_menu_item MenuList[] =
|
||||
|
||||
const uint8_t FIRST_HIDDEN_MENU_ITEM = MENU_F_LOCK;
|
||||
|
||||
const char gSubMenu_TXP[][6] =
|
||||
const char* const gSubMenu_TXP[] =
|
||||
{
|
||||
"USER",
|
||||
"LOW 1",
|
||||
@@ -209,29 +212,26 @@ const char gSubMenu_TXP[][6] =
|
||||
"HIGH"
|
||||
};
|
||||
|
||||
const char gSubMenu_SFT_D[][4] =
|
||||
const char* const gSubMenu_SFT_D[] =
|
||||
{
|
||||
"OFF",
|
||||
"+",
|
||||
"-"
|
||||
};
|
||||
|
||||
const char gSubMenu_W_N[][7] =
|
||||
const char* const gSubMenu_W_N[] =
|
||||
{
|
||||
"WIDE",
|
||||
"NARROW"
|
||||
};
|
||||
|
||||
const char gSubMenu_OFF_ON[][4] =
|
||||
const char* const gSubMenu_OFF_ON[] =
|
||||
{
|
||||
"OFF",
|
||||
"ON"
|
||||
};
|
||||
|
||||
const char gSubMenu_NA[4] =
|
||||
{
|
||||
"N/A"
|
||||
};
|
||||
const char* gSubMenu_NA = "N/A";
|
||||
|
||||
const char* const gSubMenu_RXMode[] =
|
||||
{
|
||||
@@ -242,7 +242,7 @@ const char* const gSubMenu_RXMode[] =
|
||||
};
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
const char gSubMenu_VOICE[][4] =
|
||||
const char* const gSubMenu_VOICE[] =
|
||||
{
|
||||
"OFF",
|
||||
"CHI",
|
||||
@@ -259,7 +259,7 @@ const char* const gSubMenu_MDF[] =
|
||||
};
|
||||
|
||||
#ifdef ENABLE_ALARM
|
||||
const char gSubMenu_AL_MOD[][5] =
|
||||
const char* const gSubMenu_AL_MOD[] =
|
||||
{
|
||||
"SITE",
|
||||
"TONE"
|
||||
@@ -267,7 +267,7 @@ const char* const gSubMenu_MDF[] =
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DTMF_CALLING
|
||||
const char gSubMenu_D_RSP[][11] =
|
||||
const char* const gSubMenu_D_RSP[] =
|
||||
{
|
||||
"DO\nNOTHING",
|
||||
"RING",
|
||||
@@ -285,7 +285,7 @@ const char* const gSubMenu_PTT_ID[] =
|
||||
"APOLLO\nQUINDAR"
|
||||
};
|
||||
|
||||
const char gSubMenu_PONMSG[][8] =
|
||||
const char* const gSubMenu_PONMSG[] =
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
"ALL",
|
||||
@@ -301,20 +301,30 @@ const char gSubMenu_PONMSG[][8] =
|
||||
"NONE"
|
||||
};
|
||||
|
||||
const char gSubMenu_ROGER[][6] =
|
||||
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
|
||||
const char* const gSubMenu_SET_SAV[] =
|
||||
{
|
||||
"OFF",
|
||||
"LOGO",
|
||||
"LOGO+",
|
||||
"MATRIX"
|
||||
};
|
||||
#endif
|
||||
|
||||
const char* const gSubMenu_ROGER[] =
|
||||
{
|
||||
"OFF",
|
||||
"ROGER",
|
||||
"MDC"
|
||||
};
|
||||
|
||||
const char gSubMenu_RESET[][4] =
|
||||
const char* const gSubMenu_RESET[] =
|
||||
{
|
||||
"VFO",
|
||||
"ALL"
|
||||
};
|
||||
|
||||
const char * const gSubMenu_F_LOCK[] =
|
||||
const char* const gSubMenu_F_LOCK[] =
|
||||
{
|
||||
"DEFAULT+\n137-174\n400-470",
|
||||
"FCC HAM\n144-148\n420-450",
|
||||
@@ -335,7 +345,7 @@ const char * const gSubMenu_F_LOCK[] =
|
||||
"UNLOCK\nALL",
|
||||
};
|
||||
|
||||
const char gSubMenu_RX_TX[][6] =
|
||||
const char* const gSubMenu_RX_TX[] =
|
||||
{
|
||||
"OFF",
|
||||
"TX",
|
||||
@@ -343,14 +353,14 @@ const char gSubMenu_RX_TX[][6] =
|
||||
"TX/RX"
|
||||
};
|
||||
|
||||
const char gSubMenu_BAT_TXT[][8] =
|
||||
const char* const gSubMenu_BAT_TXT[] =
|
||||
{
|
||||
"NONE",
|
||||
"VOLTAGE",
|
||||
"PERCENT"
|
||||
};
|
||||
|
||||
const char gSubMenu_BATTYP[][12] =
|
||||
const char* const gSubMenu_BATTYP[] =
|
||||
{
|
||||
"1600mAh K5",
|
||||
"2200mAh K5",
|
||||
@@ -359,14 +369,14 @@ const char gSubMenu_BATTYP[][12] =
|
||||
"2500mAh K1"
|
||||
};
|
||||
|
||||
const char gSubMenu_SET_NAV[][17] =
|
||||
const char* const gSubMenu_SET_NAV[] =
|
||||
{
|
||||
"LEFT\nRIGHT\nUV-K1",
|
||||
"UP\nDOWN\nUV-K5(8)",
|
||||
};
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
const char gSubMenu_SCRAMBLER[][7] =
|
||||
const char* const gSubMenu_SCRAMBLER[] =
|
||||
{
|
||||
"OFF",
|
||||
"2600Hz",
|
||||
@@ -383,7 +393,7 @@ const char gSubMenu_SCRAMBLER[][7] =
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
const char gSubMenu_SET_PWR[][6] =
|
||||
const char* const gSubMenu_SET_PWR[] =
|
||||
{
|
||||
"< 20m",
|
||||
"125m",
|
||||
@@ -394,13 +404,13 @@ const char gSubMenu_SCRAMBLER[][7] =
|
||||
"5"
|
||||
};
|
||||
|
||||
const char gSubMenu_SET_PTT[][8] =
|
||||
const char* const gSubMenu_SET_PTT[] =
|
||||
{
|
||||
"CLASSIC",
|
||||
"ONEPUSH"
|
||||
};
|
||||
|
||||
const char gSubMenu_SET_TOT[][7] = // Use by SET_EOT too
|
||||
const char* const gSubMenu_SET_TOT[] =
|
||||
{
|
||||
"OFF",
|
||||
"SOUND",
|
||||
@@ -408,20 +418,22 @@ const char gSubMenu_SCRAMBLER[][7] =
|
||||
"ALL"
|
||||
};
|
||||
|
||||
const char gSubMenu_SET_LCK[][9] =
|
||||
const char* const gSubMenu_SET_LCK[] =
|
||||
{
|
||||
"KEYS",
|
||||
"KEYS+PTT"
|
||||
"KEYS\nACTIONS",
|
||||
"KEYS\nPTT",
|
||||
"KEYS\nACTIONS\nPTT"
|
||||
};
|
||||
|
||||
const char gSubMenu_SET_MET[][8] =
|
||||
const char* const gSubMenu_SET_MET[] =
|
||||
{
|
||||
"TINY",
|
||||
"CLASSIC"
|
||||
};
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
const char gSubMenu_SET_SCN[][7] =
|
||||
const char* const gSubMenu_SET_SCN[] =
|
||||
{
|
||||
"NORMAL",
|
||||
"FAST"
|
||||
@@ -429,7 +441,7 @@ const char gSubMenu_SCRAMBLER[][7] =
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
const char gSubMenu_SET_AUD_FM[][6] =
|
||||
const char* const gSubMenu_SET_AUD_FM[] =
|
||||
{
|
||||
"FLAT",
|
||||
"CLEAN",
|
||||
@@ -438,7 +450,7 @@ const char gSubMenu_SCRAMBLER[][7] =
|
||||
"MAX"
|
||||
};
|
||||
|
||||
const char gSubMenu_SET_AUD_AM[][6] =
|
||||
const char* const gSubMenu_SET_AUD_AM[] =
|
||||
{
|
||||
"SHARP",
|
||||
"STOCK",
|
||||
@@ -447,7 +459,7 @@ const char gSubMenu_SCRAMBLER[][7] =
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_NARROWER
|
||||
const char gSubMenu_SET_NFM[][9] =
|
||||
const char* const gSubMenu_SET_NFM[] =
|
||||
{
|
||||
"NARROW",
|
||||
"NARROWER"
|
||||
@@ -455,7 +467,7 @@ const char gSubMenu_SCRAMBLER[][7] =
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
const char gSubMenu_SET_KEY[][9] =
|
||||
const char* const gSubMenu_SET_KEY[] =
|
||||
{
|
||||
"KEY_MENU",
|
||||
"KEY_UP",
|
||||
@@ -486,17 +498,13 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
|
||||
#endif
|
||||
#ifdef ENABLE_TX1750
|
||||
{"1750Hz", ACTION_OPT_1750},
|
||||
#endif
|
||||
#ifdef ENABLE_REGA
|
||||
{"REGA\nALARM", ACTION_OPT_REGA_ALARM},
|
||||
{"REGA\nTEST", ACTION_OPT_REGA_TEST},
|
||||
#endif
|
||||
{"LOCK\nKEYPAD", ACTION_OPT_KEYLOCK},
|
||||
{"VFO A\nVFO B", ACTION_OPT_A_B},
|
||||
{"VFO\nMEM", ACTION_OPT_VFO_MR},
|
||||
{"MODE", ACTION_OPT_SWITCH_DEMODUL},
|
||||
#ifdef ENABLE_BLMIN_TMP_OFF
|
||||
{"BLMIN\nTMP OFF", ACTION_OPT_BLMIN_TMP_OFF}, //BackLight Minimum Temporay OFF
|
||||
{"BLMIN\nTMP OFF", ACTION_OPT_BLMIN_TMP_OFF}, //BackLight Minimum Temporary OFF
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
{"RX MODE", ACTION_OPT_RXMODE},
|
||||
@@ -514,6 +522,12 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
{"BEAM", ACTION_OPT_BEAM},
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
{"RF LOG", ACTION_OPT_RXTX_LOG},
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
{"FOX HUNT\nBEACON", ACTION_OPT_FOXHUNT},
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -521,9 +535,15 @@ const uint8_t gSubMenu_SIDEFUNCTIONS_size = ARRAY_SIZE(gSubMenu_SIDEFUNCTIONS);
|
||||
|
||||
bool gIsInSubMenu;
|
||||
uint8_t gMenuCursor;
|
||||
uint8_t gMenuIndices[ARRAY_SIZE(MenuList)]; // Etape 1: table position affichee -> index MenuList (vue courante)
|
||||
|
||||
int UI_MENU_GetCurrentMenuId() {
|
||||
if(gMenuCursor < ARRAY_SIZE(MenuList))
|
||||
return MenuList[gMenuCursor].menu_id;
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
return 0xFF; // pas d'item courant au niveau categories
|
||||
#endif
|
||||
if(gMenuCursor < gMenuListCount)
|
||||
return MenuList[gMenuIndices[gMenuCursor]].menu_id;
|
||||
|
||||
return MenuList[ARRAY_SIZE(MenuList)-1].menu_id;
|
||||
}
|
||||
@@ -536,6 +556,221 @@ uint8_t UI_MENU_GetMenuIdx(uint8_t id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Position dans la vue courante (gMenuIndices) du menu_id, ou gMenuCursor si absent.
|
||||
// En vue All (identite) equivaut a UI_MENU_GetMenuIdx ; en vue categorie, donne la
|
||||
// position filtree correcte.
|
||||
uint8_t UI_MENU_GetViewPos(uint8_t id)
|
||||
{
|
||||
for (uint8_t i = 0; i < gMenuListCount; i++)
|
||||
if (MenuList[gMenuIndices[i]].menu_id == id)
|
||||
return i;
|
||||
return gMenuCursor;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
// --- Etape 2a : donnee du classement par categorie (cible Fusion) ---
|
||||
// Chaque liste = les menu_id d'une categorie, DANS l'ordre d'affichage voulu
|
||||
// (ex. SetPwr colle a Power). CAT_ALL n'a pas de liste : il reprend MenuList
|
||||
// tel quel, donc ordre et numeros d'origine preserves.
|
||||
const char *const CategoryNames[CAT_COUNT] = {
|
||||
[CAT_CHANNELS] = "Channels",
|
||||
[CAT_SCAN] = "Scan",
|
||||
[CAT_KEYS] = "Keys",
|
||||
[CAT_POWER] = "Power",
|
||||
[CAT_DISPLAY] = "Display",
|
||||
[CAT_TIMERS] = "Timers",
|
||||
[CAT_AUDIO] = "Audio",
|
||||
[CAT_RADIO] = "Radio",
|
||||
[CAT_DTMF] = "DTMF",
|
||||
[CAT_SERVICE] = "Service",
|
||||
[CAT_ALL] = "All",
|
||||
};
|
||||
|
||||
// Les menu_id de sous-features optionnelles sont gardes exactement comme dans
|
||||
// l'enum (menu.h) : sur un preset qui ne les compile pas, ils ne sont pas
|
||||
// references (sinon build KO, ex. preset Custom). Les autres MENU_SET_* sont
|
||||
// sous ENABLE_FEAT_F4HWN, garanti par la dependance CMake (App/CMakeLists.txt).
|
||||
static const uint8_t CatChannels[] = {
|
||||
MENU_STEP, MENU_TXP, MENU_SET_PWR, MENU_R_DCS, MENU_R_CTCS, MENU_T_DCS,
|
||||
MENU_T_CTCS, MENU_SFT_D, MENU_OFFSET, MENU_W_N,
|
||||
#ifdef ENABLE_FEAT_F4HWN_NARROWER
|
||||
MENU_SET_NFM,
|
||||
#endif
|
||||
MENU_BCL, MENU_COMPAND, MENU_AM, MENU_TX_LOCK, MENU_PTT_ID, MENU_LIST_CH,
|
||||
MENU_MEM_CH, MENU_DEL_CH, MENU_MEM_NAME,
|
||||
};
|
||||
static const uint8_t CatScan[] = {
|
||||
MENU_S_LIST, MENU_S_PRI, MENU_S_PRI_CH_1, MENU_S_PRI_CH_2, MENU_SC_REV,
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
MENU_SET_SCN,
|
||||
#endif
|
||||
};
|
||||
static const uint8_t CatKeys[] = {
|
||||
MENU_F1SHRT, MENU_F1LONG, MENU_F2SHRT, MENU_F2LONG, MENU_MLONG, MENU_AUTOLK, MENU_SET_LCK,
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
MENU_SET_KEY,
|
||||
#endif
|
||||
MENU_SET_PTT, MENU_1_CALL,
|
||||
};
|
||||
static const uint8_t CatPower[] = {
|
||||
MENU_SAVE, MENU_BAT_TXT,
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
MENU_SET_OFF,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
MENU_SET_SAV,
|
||||
#endif
|
||||
};
|
||||
static const uint8_t CatDisplay[] = { MENU_MDF, MENU_PONMSG, MENU_ABR, MENU_ABR_MIN, MENU_ABR_MAX, MENU_ABR_ON_TX_RX, MENU_SET_CTR, MENU_SET_INV, MENU_SET_MET, MENU_SET_GUI, MENU_VOL };
|
||||
static const uint8_t CatTimers[] = { MENU_TOT, MENU_SET_TOT, MENU_SET_EOT, MENU_SET_TMR };
|
||||
static const uint8_t CatAudio[] = {
|
||||
MENU_MIC, MENU_MIC_BAR, MENU_BEEP,
|
||||
#ifdef ENABLE_FEAT_F4HWN_VOL
|
||||
MENU_SET_VOL,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
MENU_SET_AUD,
|
||||
#endif
|
||||
};
|
||||
static const uint8_t CatRadio[] = { MENU_SQL, MENU_STE, MENU_RP_STE, MENU_ROGER, MENU_VOX, MENU_TDR };
|
||||
static const uint8_t CatDtmf[] = { MENU_UPCODE, MENU_DWCODE, MENU_D_ST, MENU_D_PRE, MENU_D_LIVE_DEC };
|
||||
static const uint8_t CatService[] = { MENU_F_LOCK, MENU_350EN, MENU_BATCAL, MENU_BATTYP, MENU_SET_NAV, MENU_RESET };
|
||||
|
||||
typedef struct { const uint8_t *ids; uint8_t len; } cat_list_t;
|
||||
|
||||
static const cat_list_t CategoryLists[CAT_COUNT] = {
|
||||
[CAT_CHANNELS] = { CatChannels, ARRAY_SIZE(CatChannels) },
|
||||
[CAT_SCAN] = { CatScan, ARRAY_SIZE(CatScan) },
|
||||
[CAT_KEYS] = { CatKeys, ARRAY_SIZE(CatKeys) },
|
||||
[CAT_POWER] = { CatPower, ARRAY_SIZE(CatPower) },
|
||||
[CAT_DISPLAY] = { CatDisplay, ARRAY_SIZE(CatDisplay) },
|
||||
[CAT_TIMERS] = { CatTimers, ARRAY_SIZE(CatTimers) },
|
||||
[CAT_AUDIO] = { CatAudio, ARRAY_SIZE(CatAudio) },
|
||||
[CAT_RADIO] = { CatRadio, ARRAY_SIZE(CatRadio) },
|
||||
[CAT_DTMF] = { CatDtmf, ARRAY_SIZE(CatDtmf) },
|
||||
[CAT_SERVICE] = { CatService, ARRAY_SIZE(CatService) },
|
||||
[CAT_ALL] = { NULL, 0 },
|
||||
};
|
||||
|
||||
uint8_t gMenuCategory = CAT_ALL;
|
||||
|
||||
// Index de 'id' dans MenuList, ou 0xFF si absent (item non compile).
|
||||
static uint8_t menu_find_idx(uint8_t id)
|
||||
{
|
||||
for (uint8_t i = 0; MenuList[i].name[0] != '\0'; i++)
|
||||
if (MenuList[i].menu_id == id)
|
||||
return i;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
uint8_t gMenuLevel = MENU_LEVEL_CAT;
|
||||
uint8_t gCatOrder[CAT_COUNT];
|
||||
uint8_t gMenuCatCursor = 0;
|
||||
uint8_t gCatLastPos[CAT_COUNT]; // derniere position du curseur item, par categorie
|
||||
|
||||
// Nombre d'items presents (compiles) dans une categorie.
|
||||
uint8_t UI_MENU_CategoryItemCount(uint8_t cat)
|
||||
{
|
||||
uint8_t n = 0;
|
||||
|
||||
if (cat == CAT_ALL)
|
||||
{
|
||||
for (uint8_t i = 0; MenuList[i].name[0] != '\0'; i++)
|
||||
{
|
||||
if (!gF_LOCK && MenuList[i].menu_id == FIRST_HIDDEN_MENU_ITEM)
|
||||
break;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
const cat_list_t *cl = &CategoryLists[cat];
|
||||
for (uint8_t k = 0; k < cl->len; k++)
|
||||
if (menu_find_idx(cl->ids[k]) != 0xFF)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
// Construit l'ecran niveau categories : gCatOrder = categories visibles,
|
||||
// gMenuListCount = leur nombre. Service n'apparait que si gF_LOCK.
|
||||
void UI_MENU_BuildCategoryScreen(void)
|
||||
{
|
||||
gMenuListCount = 0;
|
||||
for (uint8_t c = 0; c < CAT_COUNT; c++)
|
||||
{
|
||||
if (c == CAT_SERVICE && !gF_LOCK)
|
||||
continue;
|
||||
gCatOrder[gMenuListCount++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Rendu de l'ecran des categories (niveau 1).
|
||||
static void UI_MENU_DrawCategories(void)
|
||||
{
|
||||
char str[16];
|
||||
const unsigned int sep = 64; // separateur decale a droite : noms longs (ex. "Channels")
|
||||
const unsigned int x1 = sep + 2;
|
||||
const unsigned int x2 = LCD_WIDTH - 1;
|
||||
|
||||
UI_DisplayClear();
|
||||
|
||||
UI_DrawLineBuffer(gFrameBuffer, sep, 0, sep, 55, 1);
|
||||
for (uint8_t i = 0; i < sep; i += 2)
|
||||
gFrameBuffer[5][i] = 0x40;
|
||||
|
||||
const int count = gMenuListCount;
|
||||
const int cur = gMenuCursor;
|
||||
|
||||
int prev = cur - 1; if (prev < 0) prev = count - 1;
|
||||
int next = cur + 1; if (next >= count) next = 0;
|
||||
|
||||
if (count > 1)
|
||||
UI_PrintStringSmallNormal(CategoryNames[gCatOrder[prev]], 0, 0, 1);
|
||||
UI_PrintString(CategoryNames[gCatOrder[cur]], 0, 0, 2, 8);
|
||||
if (count > 1)
|
||||
UI_PrintStringSmallNormal(CategoryNames[gCatOrder[next]], 0, 0, 4);
|
||||
|
||||
sprintf(str, "%02u/%02u", 1 + cur, count);
|
||||
UI_PrintStringSmallNormal(str, 6, 0, 6);
|
||||
|
||||
sprintf(str, "%02u", UI_MENU_CategoryItemCount(gCatOrder[cur]));
|
||||
UI_PrintString(str, x1, x2, 1, 8);
|
||||
UI_PrintStringSmallNormal("items", x1, x2, 5);
|
||||
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Construit la "vue" courante = table position affichee -> index MenuList.
|
||||
// Unique endroit qui fixe gMenuListCount + gMenuIndices.
|
||||
// CAT_ALL (defaut) = liste plate, identite -> ordre/numeros d'origine preserves.
|
||||
void UI_MENU_BuildView(void)
|
||||
{
|
||||
gMenuListCount = 0;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuCategory != CAT_ALL)
|
||||
{
|
||||
const cat_list_t *cl = &CategoryLists[gMenuCategory];
|
||||
for (uint8_t k = 0; k < cl->len; k++)
|
||||
{
|
||||
uint8_t idx = menu_find_idx(cl->ids[k]);
|
||||
if (idx != 0xFF)
|
||||
gMenuIndices[gMenuListCount++] = idx;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (uint8_t i = 0; MenuList[i].name[0] != '\0'; i++)
|
||||
{
|
||||
if (!gF_LOCK && MenuList[i].menu_id == FIRST_HIDDEN_MENU_ITEM)
|
||||
break;
|
||||
|
||||
gMenuIndices[gMenuListCount++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t gSubMenuSelection;
|
||||
|
||||
// edit box
|
||||
@@ -610,6 +845,14 @@ void UI_DisplayMenu(void)
|
||||
char String[64]; // bigger cuz we can now do multi-line in one string (use '\n' char)
|
||||
char top_right_badge[16];
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{
|
||||
UI_MENU_DrawCategories();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
const int m = UI_MENU_GetCurrentMenuId();
|
||||
|
||||
#ifdef ENABLE_DTMF_CALLING
|
||||
@@ -633,7 +876,7 @@ void UI_DisplayMenu(void)
|
||||
for (i = 0; i < 3; i++)
|
||||
if (gMenuCursor > 0 || i > 0)
|
||||
if ((gMenuListCount - 1) != gMenuCursor || i != 2)
|
||||
UI_PrintString(MenuList[gMenuCursor + i - 1].name, 0, 0, i * 2, 8);
|
||||
UI_PrintString(MenuList[gMenuIndices[gMenuCursor + i - 1]].name, 0, 0, i * 2, 8);
|
||||
|
||||
// invert the current menu list item pixels
|
||||
for (i = 0; i < (8 * menu_list_width); i++)
|
||||
@@ -658,58 +901,56 @@ void UI_DisplayMenu(void)
|
||||
#else
|
||||
{ // new menu layout .. experimental & unfinished
|
||||
const int menu_index = gMenuCursor; // current selected menu item
|
||||
i = 1;
|
||||
const int menu_count = (int)gMenuListCount;
|
||||
|
||||
if (!gIsInSubMenu) {
|
||||
while (i < 2)
|
||||
{ // leading menu items - small text
|
||||
const int k = menu_index + i - 2;
|
||||
if (k < 0)
|
||||
UI_PrintStringSmallNormal(MenuList[gMenuListCount + k].name, 0, 0, i); // wrap-a-round
|
||||
else if (k >= 0 && k < (int)gMenuListCount)
|
||||
UI_PrintStringSmallNormal(MenuList[k].name, 0, 0, i);
|
||||
i++;
|
||||
if (menu_index >= 0 && menu_index < menu_count)
|
||||
{
|
||||
if (!gIsInSubMenu)
|
||||
{
|
||||
// leading menu items - small text
|
||||
int prev_index = menu_index - 1;
|
||||
if (prev_index < 0) {
|
||||
prev_index = menu_count - 1;
|
||||
}
|
||||
UI_PrintStringSmallNormal(MenuList[gMenuIndices[prev_index]].name, 0, 0, 1);
|
||||
|
||||
// current menu item - keep big n fat
|
||||
UI_PrintString(MenuList[gMenuIndices[menu_index]].name, 0, 0, 2, 8);
|
||||
|
||||
// trailing menu item - small text
|
||||
int next_index = menu_index + 1;
|
||||
if (next_index >= menu_count) {
|
||||
next_index = 0;
|
||||
}
|
||||
UI_PrintStringSmallNormal(MenuList[gMenuIndices[next_index]].name, 0, 0, 4);
|
||||
|
||||
|
||||
// draw the menu index number/count
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
sprintf(String, "%2u.%u", 1 + menu_index, menu_count);
|
||||
UI_PrintStringSmallNormal(String, 2, 0, 6);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// current menu item
|
||||
// strcat(String, ":");
|
||||
UI_PrintString(MenuList[gMenuIndices[menu_index]].name, 0, 0, 0, 8);
|
||||
// UI_PrintStringSmallNormal(String, 0, 0, 0);
|
||||
}
|
||||
|
||||
// current menu item - keep big n fat
|
||||
if (menu_index >= 0 && menu_index < (int)gMenuListCount)
|
||||
UI_PrintString(MenuList[menu_index].name, 0, 0, 2, 8);
|
||||
i++;
|
||||
|
||||
while (i < 4)
|
||||
{ // trailing menu item - small text
|
||||
const int k = menu_index + i - 2;
|
||||
if (k >= 0 && k < (int)gMenuListCount)
|
||||
UI_PrintStringSmallNormal(MenuList[k].name, 0, 0, 1 + i);
|
||||
else if (k >= (int)gMenuListCount)
|
||||
UI_PrintStringSmallNormal(MenuList[gMenuListCount - k].name, 0, 0, 1 + i); // wrap-a-round
|
||||
i++;
|
||||
}
|
||||
|
||||
// draw the menu index number/count
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
sprintf(String, "%2u.%u", 1 + gMenuCursor, gMenuListCount);
|
||||
UI_PrintStringSmallNormal(String, 2, 0, 6);
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
sprintf(String, "%02u/%02u", 1 + menu_index, menu_count);
|
||||
UI_PrintStringSmallNormal(String, 6, 0, 6);
|
||||
#endif
|
||||
}
|
||||
else if (menu_index >= 0 && menu_index < (int)gMenuListCount)
|
||||
{ // current menu item
|
||||
// strcat(String, ":");
|
||||
UI_PrintString(MenuList[menu_index].name, 0, 0, 0, 8);
|
||||
// UI_PrintStringSmallNormal(String, 0, 0, 0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
sprintf(String, "%02u/%u", 1 + gMenuCursor, gMenuListCount);
|
||||
UI_PrintStringSmallNormal(String, 6, 0, 6);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// **************
|
||||
|
||||
memset(String, 0, sizeof(String));
|
||||
memset(top_right_badge, 0, sizeof(top_right_badge));
|
||||
String[0] = '\0';
|
||||
top_right_badge[0] = '\0';
|
||||
|
||||
bool already_printed = false;
|
||||
|
||||
@@ -923,6 +1164,12 @@ void UI_DisplayMenu(void)
|
||||
strcpy(String, gSubMenu_OFF_ON[gSubMenuSelection]);
|
||||
break;
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
|
||||
case MENU_SET_SAV:
|
||||
strcpy(String, gSubMenu_SET_SAV[gSubMenuSelection]);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case MENU_MEM_CH:
|
||||
case MENU_1_CALL:
|
||||
case MENU_DEL_CH:
|
||||
@@ -1103,11 +1350,19 @@ void UI_DisplayMenu(void)
|
||||
break;
|
||||
#endif
|
||||
case MENU_UPCODE:
|
||||
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_UP_CODE, gEeprom.DTMF_UP_CODE + 8);
|
||||
if (gEeprom.DTMF_UP_CODE[8] != '\0' && gEeprom.DTMF_UP_CODE[8] != 0xFF) {
|
||||
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_UP_CODE, gEeprom.DTMF_UP_CODE + 8);
|
||||
} else {
|
||||
sprintf(String, "%.8s", gEeprom.DTMF_UP_CODE);
|
||||
}
|
||||
break;
|
||||
|
||||
case MENU_DWCODE:
|
||||
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_DOWN_CODE, gEeprom.DTMF_DOWN_CODE + 8);
|
||||
if (gEeprom.DTMF_DOWN_CODE[8] != '\0' && gEeprom.DTMF_DOWN_CODE[8] != 0xFF) {
|
||||
sprintf(String, "%.8s\n%.8s", gEeprom.DTMF_DOWN_CODE, gEeprom.DTMF_DOWN_CODE + 8);
|
||||
} else {
|
||||
sprintf(String, "%.8s", gEeprom.DTMF_DOWN_CODE);
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef ENABLE_DTMF_CALLING
|
||||
@@ -1164,7 +1419,7 @@ void UI_DisplayMenu(void)
|
||||
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, DISPLAY_VERSION_STRING_2);
|
||||
UI_PrintStringSmallNormal(Edition, menu_item_x1 - 1, menu_item_x2, 6);
|
||||
#else
|
||||
sprintf(String, "%u.%02uV\n%u%%",
|
||||
@@ -1441,7 +1696,7 @@ void UI_DisplayMenu(void)
|
||||
unsigned int len = strlen(String);
|
||||
bool small = false;
|
||||
|
||||
if (len > 0)
|
||||
if (String[0] != '\0')
|
||||
{
|
||||
// count number of lines
|
||||
for (i = 0; i < len; i++)
|
||||
@@ -1508,30 +1763,25 @@ void UI_DisplayMenu(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m == MENU_R_CTCS ||
|
||||
m == MENU_T_CTCS) {
|
||||
const uint8_t approved_index =
|
||||
(gSubMenuSelection > 0) ? DCS_GetCtcssApprovedIndex(gSubMenuSelection - 1) : 0xFF;
|
||||
const bool is_ctcs = (m == MENU_R_CTCS || m == MENU_T_CTCS);
|
||||
const bool is_dcs = (m == MENU_R_DCS || m == MENU_T_DCS);
|
||||
|
||||
if (gSubMenuSelection == 0)
|
||||
sprintf(top_right_badge, "00/00");
|
||||
else if (approved_index != 0xFF)
|
||||
sprintf(top_right_badge, "%02u/%02u", (unsigned)gSubMenuSelection, (unsigned)approved_index + 1);
|
||||
else
|
||||
sprintf(top_right_badge, "%02u/--", (unsigned)gSubMenuSelection);
|
||||
}
|
||||
|
||||
if (m == MENU_R_DCS ||
|
||||
m == MENU_T_DCS) {
|
||||
const uint8_t approved_index =
|
||||
(gSubMenuSelection > 0) ? DCS_GetDcsApprovedIndex(gSubMenuSelection - 1) : 0xFF;
|
||||
if (is_ctcs || is_dcs) {
|
||||
if (gSubMenuSelection == 0) {
|
||||
strcpy(top_right_badge, is_ctcs ? "00/00" : "000/00");
|
||||
} else {
|
||||
const uint8_t approved_index = is_ctcs ?
|
||||
DCS_GetCtcssApprovedIndex(gSubMenuSelection - 1) :
|
||||
DCS_GetDcsApprovedIndex(gSubMenuSelection - 1);
|
||||
|
||||
const uint8_t width = is_ctcs ? 2 : 3;
|
||||
|
||||
if (gSubMenuSelection == 0)
|
||||
sprintf(top_right_badge, "000/00");
|
||||
else if (approved_index != 0xFF)
|
||||
sprintf(top_right_badge, "%03u/%02u", (unsigned)gSubMenuSelection, (unsigned)approved_index + 1);
|
||||
else
|
||||
sprintf(top_right_badge, "%03u/--", (unsigned)gSubMenuSelection);
|
||||
if (approved_index != 0xFF) {
|
||||
sprintf(top_right_badge, "%0*u/%02u", width, (unsigned)gSubMenuSelection, (unsigned)approved_index + 1);
|
||||
} else {
|
||||
sprintf(top_right_badge, "%0*u/--", width, (unsigned)gSubMenuSelection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DTMF_CALLING
|
||||
|
||||
+81
-35
@@ -23,7 +23,7 @@
|
||||
#include "audio.h" // VOICE_ID_t
|
||||
#include "settings.h"
|
||||
|
||||
typedef struct {
|
||||
typedef struct __attribute__((packed)) {
|
||||
const char name[7]; // menu display area only has room for 6 characters
|
||||
uint8_t menu_id;
|
||||
} t_menu_item;
|
||||
@@ -163,80 +163,124 @@ enum
|
||||
MENU_F2SHRT,
|
||||
MENU_F2LONG,
|
||||
MENU_MLONG,
|
||||
MENU_BATTYP
|
||||
MENU_BATTYP,
|
||||
#if defined(ENABLE_FEAT_F4HWN) && defined(ENABLE_FEAT_F4HWN_LOGO_SAV)
|
||||
MENU_SET_SAV
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
// Categories (niveau 1) — l'ordre = ordre d'affichage de l'ecran des categories.
|
||||
enum {
|
||||
CAT_CHANNELS = 0,
|
||||
CAT_SCAN,
|
||||
CAT_KEYS,
|
||||
CAT_POWER,
|
||||
CAT_DISPLAY,
|
||||
CAT_TIMERS,
|
||||
CAT_AUDIO,
|
||||
CAT_RADIO,
|
||||
CAT_DTMF,
|
||||
CAT_SERVICE, // menu cache : n'apparait que si gF_LOCK
|
||||
CAT_ALL, // liste plate complete, ordre et numeros d'origine
|
||||
CAT_COUNT
|
||||
};
|
||||
|
||||
#define MENU_LEVEL_CAT 0
|
||||
#define MENU_LEVEL_ITEMS 1
|
||||
|
||||
extern const char *const CategoryNames[];
|
||||
extern uint8_t gMenuCategory;
|
||||
extern uint8_t gMenuLevel;
|
||||
extern uint8_t gCatOrder[];
|
||||
extern uint8_t gMenuCatCursor;
|
||||
extern uint8_t gCatLastPos[];
|
||||
|
||||
void UI_MENU_BuildCategoryScreen(void);
|
||||
uint8_t UI_MENU_CategoryItemCount(uint8_t cat);
|
||||
#endif
|
||||
|
||||
extern const uint8_t FIRST_HIDDEN_MENU_ITEM;
|
||||
extern const t_menu_item MenuList[];
|
||||
|
||||
extern const char gSubMenu_TXP[8][6];
|
||||
extern const char gSubMenu_SFT_D[3][4];
|
||||
extern const char gSubMenu_W_N[2][7];
|
||||
extern const char gSubMenu_OFF_ON[2][4];
|
||||
extern const char gSubMenu_NA[4];
|
||||
extern const char gSubMenu_TOT[11][7];
|
||||
extern const char* const gSubMenu_RXMode[4];
|
||||
extern const char* const gSubMenu_TXP[8];
|
||||
extern const char* const gSubMenu_SFT_D[3];
|
||||
extern const char* const gSubMenu_W_N[2];
|
||||
extern const char* const gSubMenu_OFF_ON[2];
|
||||
extern const char* gSubMenu_NA;
|
||||
extern const char* const gSubMenu_TOT[11];
|
||||
extern const char* const gSubMenu_RXMode[4];
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
extern const char gSubMenu_VOICE[3][4];
|
||||
extern const char* const gSubMenu_VOICE[3];
|
||||
#endif
|
||||
extern const char* const gSubMenu_MDF[4];
|
||||
extern const char* const gSubMenu_MDF[4];
|
||||
#ifdef ENABLE_ALARM
|
||||
extern const char gSubMenu_AL_MOD[2][5];
|
||||
extern const char* const gSubMenu_AL_MOD[2];
|
||||
#endif
|
||||
#ifdef ENABLE_DTMF_CALLING
|
||||
extern const char gSubMenu_D_RSP[4][11];
|
||||
extern const char* const gSubMenu_D_RSP[4];
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
extern const char gSubMenu_SET_PWR[7][6];
|
||||
extern const char gSubMenu_SET_PTT[2][8];
|
||||
extern const char gSubMenu_SET_TOT[4][7];
|
||||
extern const char gSubMenu_SET_LCK[2][9];
|
||||
extern const char gSubMenu_SET_MET[2][8];
|
||||
extern const char* const gSubMenu_SET_PWR[7];
|
||||
extern const char* const gSubMenu_SET_PTT[2];
|
||||
extern const char* const gSubMenu_SET_TOT[4];
|
||||
extern const char* const gSubMenu_SET_LCK[];
|
||||
extern const char* const gSubMenu_SET_MET[2];
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
extern const char gSubMenu_SET_SCN[2][7];
|
||||
extern const char* const gSubMenu_SET_SCN[2];
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_NARROWER
|
||||
extern const char gSubMenu_SET_NFM[2][9];
|
||||
extern const char* const gSubMenu_SET_NFM[2];
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
extern const char gSubMenu_SET_KEY[][9];
|
||||
extern const char* const gSubMenu_SET_KEY[5];
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
extern const char gSubMenu_SET_AUD_FM[5][6];
|
||||
extern const char gSubMenu_SET_AUD_AM[3][6];
|
||||
extern const char* const gSubMenu_SET_AUD_FM[5];
|
||||
extern const char* const gSubMenu_SET_AUD_AM[3];
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
extern const char* const gSubMenu_SET_SAV[];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern const char* const gSubMenu_PTT_ID[5];
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO
|
||||
extern const char gSubMenu_PONMSG[6][8];
|
||||
extern const char* const gSubMenu_PONMSG[6];
|
||||
#else
|
||||
extern const char gSubMenu_PONMSG[5][8];
|
||||
extern const char* const gSubMenu_PONMSG[5];
|
||||
#endif
|
||||
#else
|
||||
extern const char gSubMenu_PONMSG[4][8];
|
||||
extern const char* const gSubMenu_PONMSG[4];
|
||||
#endif
|
||||
extern const char gSubMenu_ROGER[3][6];
|
||||
extern const char gSubMenu_RESET[2][4];
|
||||
extern const char* const gSubMenu_F_LOCK[F_LOCK_LEN];
|
||||
extern const char gSubMenu_RX_TX[4][6];
|
||||
extern const char gSubMenu_BAT_TXT[3][8];
|
||||
extern const char gSubMenu_BATTYP[5][12];
|
||||
|
||||
extern const char* const gSubMenu_ROGER[3];
|
||||
extern const char* const gSubMenu_RESET[2];
|
||||
extern const char* const gSubMenu_F_LOCK[F_LOCK_LEN];
|
||||
extern const char* const gSubMenu_RX_TX[4];
|
||||
extern const char* const gSubMenu_BAT_TXT[3];
|
||||
extern const char* const gSubMenu_BATTYP[5];
|
||||
extern const char* const gSubMenu_SET_NAV[2];
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
extern const char gSubMenu_SCRAMBLER[11][7];
|
||||
extern const char* const gSubMenu_SCRAMBLER[11];
|
||||
#endif
|
||||
|
||||
typedef struct {char* name; uint8_t id;} t_sidefunction;
|
||||
typedef struct /* __attribute__((packed)) */ {
|
||||
const char* name;
|
||||
uint8_t id;
|
||||
} t_sidefunction;
|
||||
|
||||
extern const uint8_t gSubMenu_SIDEFUNCTIONS_size;
|
||||
extern const t_sidefunction gSubMenu_SIDEFUNCTIONS[];
|
||||
extern const t_sidefunction gSubMenu_SIDEFUNCTIONS[];
|
||||
|
||||
extern bool gIsInSubMenu;
|
||||
|
||||
extern uint8_t gMenuCursor;
|
||||
extern uint8_t gMenuIndices[];
|
||||
|
||||
extern int32_t gSubMenuSelection;
|
||||
|
||||
@@ -248,5 +292,7 @@ extern bool edit_is_uppercase;
|
||||
void UI_DisplayMenu(void);
|
||||
int UI_MENU_GetCurrentMenuId();
|
||||
uint8_t UI_MENU_GetMenuIdx(uint8_t id);
|
||||
uint8_t UI_MENU_GetViewPos(uint8_t id);
|
||||
void UI_MENU_BuildView(void);
|
||||
|
||||
#endif
|
||||
+34
-36
@@ -26,58 +26,56 @@
|
||||
|
||||
void UI_DisplayScanner(void)
|
||||
{
|
||||
char String[16] = {0};
|
||||
char String[16];
|
||||
char *pPrintStr = String;
|
||||
bool bCentered;
|
||||
uint8_t Start;
|
||||
|
||||
UI_DisplayClear();
|
||||
|
||||
if (gScanSingleFrequency || (gScanCssState != SCAN_CSS_STATE_OFF && gScanCssState != SCAN_CSS_STATE_FAILED)) {
|
||||
sprintf(String, "FREQ:%u.%05u", gScanFrequency / 100000, gScanFrequency % 100000);
|
||||
// 1st line
|
||||
if (gScannerSaveState == SCAN_SAVE_CHANNEL) {
|
||||
pPrintStr = "Save?";
|
||||
} else if (gScannerSaveState == SCAN_SAVE_CHAN_SEL) {
|
||||
strcpy(String, "Save:");
|
||||
UI_GenerateChannelStringEx(String + 5, gShowChPrefix, gScanChannel);
|
||||
pPrintStr = String;
|
||||
} else if ((gScanCssState < SCAN_CSS_STATE_FOUND) && ((gScanProgressIndicator & 1u) != 0)) {
|
||||
pPrintStr = "";
|
||||
} else if (gScanCssState == SCAN_CSS_STATE_OFF) {
|
||||
pPrintStr = "Search Freq";
|
||||
} else if (gScanCssState == SCAN_CSS_STATE_SCANNING) {
|
||||
pPrintStr = "Search Tone";
|
||||
} else if (gScanCssState == SCAN_CSS_STATE_FOUND) {
|
||||
pPrintStr = "Scan Complete";
|
||||
} else {
|
||||
pPrintStr = "FREQ:**.*****";
|
||||
pPrintStr = "Scan Failed";
|
||||
}
|
||||
|
||||
UI_PrintString(pPrintStr, 2, 0, 1, 8);
|
||||
|
||||
if (gScanCssState < SCAN_CSS_STATE_FOUND || !gScanUseCssResult) {
|
||||
pPrintStr = "CTC:******";
|
||||
// 2nd line
|
||||
if (gScanSingleFrequency || (gScanCssState != SCAN_CSS_STATE_OFF && gScanCssState != SCAN_CSS_STATE_FAILED)) {
|
||||
sprintf(String, "Freq:%u.%05u", gScanFrequency / 100000, gScanFrequency % 100000);
|
||||
pPrintStr = String;
|
||||
} else {
|
||||
pPrintStr = "Freq:---.-----";
|
||||
}
|
||||
|
||||
UI_PrintString(pPrintStr, 2, 0, 3, 8);
|
||||
|
||||
// 3rd line
|
||||
if (gScanCssState < SCAN_CSS_STATE_FOUND) {
|
||||
pPrintStr = "Tone:---";
|
||||
} else if (!gScanUseCssResult) {
|
||||
pPrintStr = "Tone:None";
|
||||
} else if (gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {
|
||||
sprintf(String, "CTC:%u.%uHz", CTCSS_Options[gScanCssResultCode] / 10, CTCSS_Options[gScanCssResultCode] % 10);
|
||||
sprintf(String, "CTCSS:%u.%uHz", CTCSS_Options[gScanCssResultCode] / 10, CTCSS_Options[gScanCssResultCode] % 10);
|
||||
pPrintStr = String;
|
||||
} else {
|
||||
sprintf(String, "DCS:D%03oN", DCS_Options[gScanCssResultCode]);
|
||||
pPrintStr = String;
|
||||
}
|
||||
|
||||
UI_PrintString(pPrintStr, 2, 0, 3, 8);
|
||||
memset(String, 0, sizeof(String));
|
||||
if (gScannerSaveState == SCAN_SAVE_CHANNEL) {
|
||||
pPrintStr = "SAVE?";
|
||||
Start = 0;
|
||||
bCentered = 1;
|
||||
} else {
|
||||
Start = 2;
|
||||
bCentered = 0;
|
||||
|
||||
if (gScannerSaveState == SCAN_SAVE_CHAN_SEL) {
|
||||
strcpy(String, "SAVE:");
|
||||
UI_GenerateChannelStringEx(String + 5, gShowChPrefix, gScanChannel);
|
||||
pPrintStr = String;
|
||||
} else if (gScanCssState < SCAN_CSS_STATE_FOUND) {
|
||||
strcpy(String, "SCAN");
|
||||
memset(String + 4, '.', (gScanProgressIndicator & 7) + 1);
|
||||
pPrintStr = String;
|
||||
} else if (gScanCssState == SCAN_CSS_STATE_FOUND) {
|
||||
pPrintStr = "SCAN CMP.";
|
||||
} else {
|
||||
pPrintStr = "SCAN FAIL.";
|
||||
}
|
||||
}
|
||||
|
||||
UI_PrintString(pPrintStr, Start, bCentered ? 127 : 0, 5, 8);
|
||||
|
||||
UI_PrintString(pPrintStr, 2, 0, 5, 8);
|
||||
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
+141
-93
@@ -16,7 +16,11 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/chFrScanner.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FMRADIO
|
||||
#include "app/fm.h"
|
||||
#endif
|
||||
@@ -43,46 +47,76 @@ static void convertTime(uint8_t *line, uint8_t type)
|
||||
uint8_t m = t / 60;
|
||||
uint8_t s = t - (m * 60); // Replace modulo with subtraction for efficiency
|
||||
|
||||
gStatusLine[0] = gStatusLine[7] = gStatusLine[14] = 0x00; // Quick fix on display (on scanning I, II, etc.)
|
||||
|
||||
char str[6];
|
||||
sprintf(str, "%02u:%02u", m, s);
|
||||
UI_PrintStringSmallBufferNormal(str, line);
|
||||
|
||||
gUpdateStatus = true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void UI_DisplayStatus()
|
||||
{
|
||||
char str[8] = "";
|
||||
|
||||
gUpdateStatus = false;
|
||||
|
||||
if (APP_IsScreenSaverDisplayed())
|
||||
return;
|
||||
|
||||
UI_StatusClear();
|
||||
|
||||
uint8_t *line = gStatusLine;
|
||||
unsigned int x = 0;
|
||||
unsigned int x1 = 0;
|
||||
|
||||
#ifdef ENABLE_NOAA
|
||||
// NOAA indicator
|
||||
if (!(gScanStateDir != SCAN_OFF || SCANNER_IsScanning()) && gIsNoaaMode) { // NOASS SCAN indicator
|
||||
memcpy(line + x, BITMAP_NOAA, sizeof(BITMAP_NOAA));
|
||||
char str[8] = "";
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
// The filter label reuses the leftmost slot (pixels 2..16) normally
|
||||
// reserved by the power-save and scan indicators; those two are skipped
|
||||
// on the log screen so the rest of the bar keeps its usual layout.
|
||||
const bool isRxTxLogScreen = gScreenToDisplay == DISPLAY_RXTX_LOG;
|
||||
if (isRxTxLogScreen) {
|
||||
const char *filter = RXTX_LOG_GetFilterName();
|
||||
const uint8_t end = (filter[2] == 0) ? 10 : 14;
|
||||
|
||||
GUI_DisplaySmallestInverse(filter, 2, 0, true, true, end);
|
||||
}
|
||||
// Power Save indicator
|
||||
else if (gCurrentFunction == FUNCTION_POWER_SAVE) {
|
||||
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
|
||||
}
|
||||
x += 8;
|
||||
#else
|
||||
// Power Save indicator
|
||||
if (gCurrentFunction == FUNCTION_POWER_SAVE) {
|
||||
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
|
||||
}
|
||||
x += 8;
|
||||
#endif
|
||||
|
||||
unsigned int x1 = x;
|
||||
#if defined(ENABLE_FEAT_F4HWN_RX_TX_TIMER) && !defined(ENABLE_FEAT_F4HWN_DEBUG)
|
||||
bool isTransmit = gCurrentFunction == FUNCTION_TRANSMIT;
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
if (!isRxTxLogScreen && gSetting_set_tmr && (isTransmit || FUNCTION_IsRx())) {
|
||||
#else
|
||||
if (gSetting_set_tmr && (isTransmit || FUNCTION_IsRx())) {
|
||||
#endif
|
||||
convertTime(line, !isTransmit);
|
||||
x += 39;
|
||||
} else {
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
if (!isRxTxLogScreen)
|
||||
#endif
|
||||
{
|
||||
#ifdef ENABLE_NOAA
|
||||
// NOAA indicator
|
||||
if (!(gScanStateDir != SCAN_OFF || SCANNER_IsScanning()) && gIsNoaaMode) { // NOASS SCAN indicator
|
||||
memcpy(line + x, BITMAP_NOAA, sizeof(BITMAP_NOAA));
|
||||
}
|
||||
// Power Save indicator
|
||||
else if (gCurrentFunction == FUNCTION_POWER_SAVE) {
|
||||
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
|
||||
}
|
||||
#else
|
||||
// Power Save indicator
|
||||
if (gCurrentFunction == FUNCTION_POWER_SAVE) {
|
||||
memcpy(line + x, gFontPowerSave, sizeof(gFontPowerSave));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
x += 8;
|
||||
|
||||
x1 = x;
|
||||
|
||||
#ifdef ENABLE_DTMF_CALLING
|
||||
if (gSetting_KILLED) {
|
||||
@@ -92,15 +126,18 @@ void UI_DisplayStatus()
|
||||
else
|
||||
#endif
|
||||
{ // SCAN indicator
|
||||
if (gScanStateDir != SCAN_OFF || SCANNER_IsScanning()) {
|
||||
if ((gScanStateDir != SCAN_OFF || SCANNER_IsScanning())
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
&& !isRxTxLogScreen
|
||||
#endif
|
||||
) {
|
||||
if (IS_MR_CHANNEL(gNextMrChannel) && !SCANNER_IsScanning()) { // channel mode
|
||||
|
||||
uint8_t end = 0;
|
||||
|
||||
if(gEeprom.SCAN_LIST_DEFAULT == MR_CHANNELS_LIST + 1)
|
||||
{
|
||||
sprintf(str, gEeprom.SCAN_LIST_ENABLED ? "%s+" : "%s", "ALL");
|
||||
end = gEeprom.SCAN_LIST_ENABLED ? 18 : 14;
|
||||
strcpy(str, "ALL");
|
||||
end = 14;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -108,15 +145,22 @@ void UI_DisplayStatus()
|
||||
|
||||
// Check if name is valid
|
||||
if (!IsEmptyName(name, sizeof(gListName[0]))) {
|
||||
sprintf(str, "%.3s%s", name, gEeprom.SCAN_LIST_ENABLED ? "+" : "");
|
||||
end = gEeprom.SCAN_LIST_ENABLED ? 18 : 14;
|
||||
}
|
||||
else {
|
||||
sprintf(str, "%02d%s", gEeprom.SCAN_LIST_DEFAULT, gEeprom.SCAN_LIST_ENABLED ? "+" : "");
|
||||
end = gEeprom.SCAN_LIST_ENABLED ? 14 : 10;
|
||||
sprintf(str, "%.3s", name);
|
||||
end = 14;
|
||||
} else {
|
||||
sprintf(str, "%02d", gEeprom.SCAN_LIST_DEFAULT);
|
||||
end = 10;
|
||||
}
|
||||
}
|
||||
|
||||
if (gEeprom.SCAN_LIST_ENABLED) {
|
||||
strcat(str, "+");
|
||||
end += 4;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
GUI_DisplaySmallestInverse(str, 2, 0, true, true, end);
|
||||
#else
|
||||
GUI_DisplaySmallest(str, 2, 1, true, true);
|
||||
|
||||
gStatusLine[0] ^= 0x3E;
|
||||
@@ -125,6 +169,7 @@ void UI_DisplayStatus()
|
||||
gStatusLine[x] ^= 0x7F;
|
||||
}
|
||||
gStatusLine[end] ^= 0x3E;
|
||||
#endif
|
||||
}
|
||||
else { // frequency mode
|
||||
memcpy(line + x + 1, gFontS, sizeof(gFontS));
|
||||
@@ -154,54 +199,49 @@ void UI_DisplayStatus()
|
||||
#endif
|
||||
|
||||
if(!SCANNER_IsScanning()) {
|
||||
#ifdef ENABLE_FEAT_F4HWN_RX_TX_TIMER
|
||||
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
|
||||
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) {
|
||||
src = gFontRO;
|
||||
sSize = sizeof(gFontRO);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
uint8_t xb = (gEeprom.CROSS_BAND_RX_TX != CROSS_BAND_OFF);
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
if (gEeprom.MENU_LOCK) {
|
||||
src = gFontRO;
|
||||
sSize = sizeof(gFontRO);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
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);
|
||||
}
|
||||
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 = xb ? gFontXB : gFontMO; // XB - crossband
|
||||
sSize = xb ? sizeof(gFontXB) : sizeof(gFontMO); // MO - main only
|
||||
src = gFontHold;
|
||||
sOff = 3;
|
||||
sSize = sizeof(gFontHold);
|
||||
}
|
||||
} else {
|
||||
src = xb ? gFontXB : gFontMO; // XB - crossband
|
||||
sSize = xb ? sizeof(gFontXB) : sizeof(gFontMO); // MO - main only
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the memcpy if a source was selected
|
||||
if (src) {
|
||||
memcpy(line + x + sOff, src, sSize);
|
||||
}
|
||||
// Perform the memcpy if a source was selected
|
||||
if (src) {
|
||||
memcpy(line + x + sOff, src, sSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
x += sizeof(gFontDWR) + 3;
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_RX_TX_TIMER) && !defined(ENABLE_FEAT_F4HWN_DEBUG)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VOX
|
||||
// VOX indicator
|
||||
if (gEeprom.VOX_SWITCH) {
|
||||
@@ -248,8 +288,19 @@ void UI_DisplayStatus()
|
||||
}
|
||||
#endif
|
||||
else if (gBackLight) {
|
||||
src = gFontLight;
|
||||
size = sizeof(gFontLight);
|
||||
// Distinguish the manual backlight sub-state: BACKLIGHT_TIME == 0
|
||||
// means the light is currently off (hollow bulb), otherwise it is
|
||||
// forced on (bulb with filament).
|
||||
|
||||
if (gEeprom.BACKLIGHT_TIME == 0) {
|
||||
src = gFontLightOff;
|
||||
size = sizeof(gFontLightOff);
|
||||
}
|
||||
else
|
||||
{
|
||||
src = gFontLight;
|
||||
size = sizeof(gFontLight);
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_FEAT_F4HWN_CHARGING_C
|
||||
else if (gChargingWithTypeC) {
|
||||
@@ -268,38 +319,35 @@ void UI_DisplayStatus()
|
||||
|
||||
UI_DrawBattery(line + x2, gBatteryDisplayLevel, gLowBatteryBlink);
|
||||
|
||||
bool BatTxt = true;
|
||||
|
||||
switch (gSetting_battery_text) {
|
||||
default:
|
||||
case 0:
|
||||
BatTxt = false;
|
||||
break;
|
||||
|
||||
case 1: // voltage
|
||||
const uint16_t voltage = (gBatteryVoltageAverage <= 999) ? gBatteryVoltageAverage : 999; // limit to 9.99V
|
||||
sprintf(str, "%u.%02u", voltage / 100, voltage % 100);
|
||||
break;
|
||||
|
||||
case 1: // voltage
|
||||
case 2: // percentage
|
||||
//gBatteryVoltageAverage = 999;
|
||||
sprintf(str, "%02u%%", BATTERY_VoltsToPercent(gBatteryVoltageAverage));
|
||||
if (gSetting_battery_text == 1) {
|
||||
const uint16_t voltage = MIN(gBatteryVoltageAverage, 999); // limit to 9.99V
|
||||
sprintf(str, "%u.%02u", voltage / 100, voltage % 100);
|
||||
} else {
|
||||
//gBatteryVoltageAverage = 999;
|
||||
sprintf(str, "%02u%%", BATTERY_VoltsToPercent(gBatteryVoltageAverage));
|
||||
}
|
||||
|
||||
x2 -= (7 * strlen(str));
|
||||
UI_PrintStringSmallBufferNormal(str, line + x2);
|
||||
/*
|
||||
uint8_t shift = (strlen(str) < 5) ? 92 : 88;
|
||||
GUI_DisplaySmallest(str, shift, 1, true, true);
|
||||
|
||||
for (uint8_t i = shift - 2; i < 110; i++) {
|
||||
gStatusLine[i] ^= 0x7F; // invert
|
||||
}
|
||||
*/
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (BatTxt) {
|
||||
x2 -= (7 * strlen(str));
|
||||
UI_PrintStringSmallBufferNormal(str, line + x2);
|
||||
/*
|
||||
uint8_t shift = (strlen(str) < 5) ? 92 : 88;
|
||||
GUI_DisplaySmallest(str, shift, 1, true, true);
|
||||
|
||||
for (uint8_t i = shift - 2; i < 110; i++) {
|
||||
gStatusLine[i] ^= 0x7F; // invert
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// **************
|
||||
|
||||
ST7565_BlitStatusLine();
|
||||
|
||||
+5
-5
@@ -30,8 +30,8 @@
|
||||
#ifdef ENABLE_FMRADIO
|
||||
#include "ui/fmradio.h"
|
||||
#endif
|
||||
#ifdef ENABLE_REGA
|
||||
#include "app/rega.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
#include "app/rxtx_log.h"
|
||||
#endif
|
||||
#include "ui/inputbox.h"
|
||||
#include "ui/main.h"
|
||||
@@ -48,7 +48,7 @@ bool gAskToSave;
|
||||
bool gAskToDelete;
|
||||
|
||||
|
||||
void (*UI_DisplayFunctions[])(void) = {
|
||||
void (*const UI_DisplayFunctions[])(void) = {
|
||||
[DISPLAY_MAIN] = &UI_DisplayMain,
|
||||
[DISPLAY_MENU] = &UI_DisplayMenu,
|
||||
[DISPLAY_SCANNER] = &UI_DisplayScanner,
|
||||
@@ -61,8 +61,8 @@ void (*UI_DisplayFunctions[])(void) = {
|
||||
[DISPLAY_AIRCOPY] = &UI_DisplayAircopy,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_REGA
|
||||
[DISPLAY_REGA] = &UI_DisplayREGA,
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
[DISPLAY_RXTX_LOG] = &UI_DisplayRxTxLog,
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ enum GUI_DisplayType_t
|
||||
DISPLAY_AIRCOPY,
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_REGA
|
||||
DISPLAY_REGA,
|
||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
DISPLAY_RXTX_LOG,
|
||||
#endif
|
||||
|
||||
DISPLAY_N_ELEM,
|
||||
|
||||
+58
-37
@@ -20,6 +20,7 @@
|
||||
#include "driver/py25q16.h"
|
||||
#include "driver/st7565.h"
|
||||
#include "external/printf/printf.h"
|
||||
#include "font.h"
|
||||
#include "helper/battery.h"
|
||||
#include "settings.h"
|
||||
#include "misc.h"
|
||||
@@ -29,8 +30,8 @@
|
||||
#include "version.h"
|
||||
#include "bitmaps.h"
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
#include "screenshot.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
#include "k5viewer.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO
|
||||
@@ -44,6 +45,21 @@
|
||||
#define LOGO_FLASH_ADDR 0x011000
|
||||
#define LOGO_HEADER_SIZE 8
|
||||
#define LOGO_BITMAP_ADDR (LOGO_FLASH_ADDR + LOGO_HEADER_SIZE)
|
||||
|
||||
static void UI_LoadLogo(void)
|
||||
{
|
||||
// Skip 8-byte header, then read 128x64 bitmap (1024 B):
|
||||
// page 0 -> gStatusLine, pages 1..7 -> gFrameBuffer.
|
||||
PY25Q16_ReadBuffer(LOGO_BITMAP_ADDR, gStatusLine, sizeof(gStatusLine));
|
||||
PY25Q16_ReadBuffer(LOGO_BITMAP_ADDR + sizeof(gStatusLine), gFrameBuffer, sizeof(gFrameBuffer));
|
||||
}
|
||||
|
||||
void UI_DisplayLogo(void)
|
||||
{
|
||||
UI_LoadLogo();
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_QRCODE
|
||||
@@ -217,11 +233,6 @@ void UI_DisplayReleaseKeys(void)
|
||||
|
||||
void UI_DisplayWelcome(void)
|
||||
{
|
||||
char WelcomeString0[16];
|
||||
char WelcomeString1[16];
|
||||
char WelcomeString2[16];
|
||||
char WelcomeString3[32];
|
||||
|
||||
UI_StatusClear();
|
||||
|
||||
#if defined(ENABLE_FEAT_F4HWN_CTR) || defined(ENABLE_FEAT_F4HWN_INV)
|
||||
@@ -229,36 +240,30 @@ void UI_DisplayWelcome(void)
|
||||
#endif
|
||||
UI_DisplayClear();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO
|
||||
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_LOGO) {
|
||||
// Skip 8-byte header, then read 128x64 bitmap (1024 B):
|
||||
// page 0 -> gStatusLine, pages 1..7 -> gFrameBuffer.
|
||||
PY25Q16_ReadBuffer(LOGO_BITMAP_ADDR, gStatusLine, sizeof(gStatusLine));
|
||||
PY25Q16_ReadBuffer(LOGO_BITMAP_ADDR + sizeof(gStatusLine), gFrameBuffer, sizeof(gFrameBuffer));
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
SCREENSHOT_Update(true);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
|
||||
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_NONE || gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_SOUND) {
|
||||
ST7565_FillScreen(0x00);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_NONE || gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_FULL_SCREEN) {
|
||||
ST7565_FillScreen(0xFF);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO
|
||||
else if (gEeprom.POWER_ON_DISPLAY_MODE == POWER_ON_DISPLAY_MODE_LOGO) {
|
||||
UI_LoadLogo();
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
memset(WelcomeString0, 0, sizeof(WelcomeString0));
|
||||
memset(WelcomeString1, 0, sizeof(WelcomeString1));
|
||||
char WelcomeString0[16];
|
||||
char WelcomeString1[16];
|
||||
char WelcomeString2[16];
|
||||
char WelcomeString3[32];
|
||||
|
||||
// 0x0EB0
|
||||
PY25Q16_ReadBuffer(0x00A0C8, WelcomeString0, 16);
|
||||
@@ -308,17 +313,33 @@ void UI_DisplayWelcome(void)
|
||||
UI_PrintString(WelcomeString1, 0, 127, 2, 10);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
UI_PrintStringSmallNormal(Version, 0, 128, 4);
|
||||
const size_t version_width = strlen(DisplayVersion) * (ARRAY_SIZE(gFontSmall[0]) + 1u);
|
||||
const uint8_t version_x = version_width < LCD_WIDTH
|
||||
? (uint8_t)((LCD_WIDTH - version_width + 1u) / 2u)
|
||||
: 0u;
|
||||
const uint8_t capsule_left = version_x > 2u ? (uint8_t)(version_x - 3u) : 0u;
|
||||
const size_t capsule_right_candidate = version_x + version_width + 2u;
|
||||
const uint8_t capsule_right = capsule_right_candidate < LCD_WIDTH
|
||||
? (uint8_t)capsule_right_candidate
|
||||
: (LCD_WIDTH - 1u);
|
||||
|
||||
UI_DrawLineBuffer(gFrameBuffer, 0, 35, 18, 35, 1);
|
||||
gFrameBuffer[4][19] ^= 0x7F;
|
||||
for (uint8_t x = 20; x < 108; x++)
|
||||
UI_PrintStringSmallNormal(DisplayVersion, version_x, 0, 4);
|
||||
|
||||
if (capsule_left > 0u)
|
||||
{
|
||||
UI_DrawLineBuffer(gFrameBuffer, 0, 35, capsule_left - 1u, 35, 1);
|
||||
}
|
||||
gFrameBuffer[4][capsule_left] ^= 0x7F;
|
||||
for (uint8_t x = capsule_left + 1u; x < capsule_right; x++)
|
||||
{
|
||||
gFrameBuffer[4][x] ^= 0xFF;
|
||||
gFrameBuffer[3][x] ^= 0x80;
|
||||
}
|
||||
gFrameBuffer[4][108] ^= 0x7F;
|
||||
UI_DrawLineBuffer(gFrameBuffer, 109, 35, 127, 35, 1);
|
||||
gFrameBuffer[4][capsule_right] ^= 0x7F;
|
||||
if (capsule_right < LCD_WIDTH - 1u)
|
||||
{
|
||||
UI_DrawLineBuffer(gFrameBuffer, capsule_right + 1u, 35, LCD_WIDTH - 1u, 35, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
#ifdef ENABLE_FEAT_F4HWN_MEM
|
||||
@@ -346,12 +367,12 @@ void UI_DisplayWelcome(void)
|
||||
#else
|
||||
UI_PrintStringSmallNormal(Version, 0, 127, 6);
|
||||
#endif
|
||||
|
||||
//ST7565_BlitStatusLine(); // blank status line : I think it's useless
|
||||
ST7565_BlitFullScreen();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
|
||||
SCREENSHOT_Update(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
ST7565_BlitStatusLine();
|
||||
ST7565_BlitFullScreen();
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
|
||||
K5VIEWER_Update(true);
|
||||
#endif
|
||||
}
|
||||
+4
-1
@@ -23,6 +23,10 @@
|
||||
void UI_DisplayReleaseKeys(void);
|
||||
void UI_DisplayWelcome(void);
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO
|
||||
void UI_DisplayLogo(void);
|
||||
#endif
|
||||
|
||||
#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.
|
||||
@@ -35,4 +39,3 @@ void UI_GetMemPercents(uint16_t *flash_pct_x100, uint16_t *ram_pct_x100);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+6
-5
@@ -6,11 +6,12 @@
|
||||
#endif
|
||||
|
||||
#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;
|
||||
const char Version[] = AUTHOR_STRING_2 " " VERSION_STRING_2;
|
||||
const char DisplayVersion[] = AUTHOR_STRING_2 " " DISPLAY_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
|
||||
|
||||
@@ -23,6 +23,7 @@ extern const char UART_Version[];
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
extern const char DisplayVersion[];
|
||||
extern const char Edition[];
|
||||
extern const char BuildDate[];
|
||||
extern const char BuildTime[];
|
||||
|
||||
@@ -19,6 +19,8 @@ message("Build type: " ${CMAKE_BUILD_TYPE})
|
||||
|
||||
enable_language(C ASM)
|
||||
|
||||
option(DEV "Prepare the rolling Fusion development firmware" OFF)
|
||||
|
||||
find_package(Git QUIET)
|
||||
set(BUILD_COMMIT "unknown")
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
@@ -71,7 +73,17 @@ else()
|
||||
set(EXE_NAME "firmware")
|
||||
endif()
|
||||
|
||||
if(DEV)
|
||||
if(NOT "${TARGET}" STREQUAL "f4hwn.fusion")
|
||||
message(FATAL_ERROR "DEV is only supported for the Fusion target")
|
||||
endif()
|
||||
set(DISPLAY_VERSION_STRING_2 "${VERSION_STRING_2} Dev")
|
||||
else()
|
||||
set(DISPLAY_VERSION_STRING_2 "${VERSION_STRING_2}")
|
||||
endif()
|
||||
|
||||
message("EXE_NAME = " ${EXE_NAME})
|
||||
message("Development build = " ${DEV})
|
||||
|
||||
add_executable(${EXE_NAME})
|
||||
|
||||
@@ -110,6 +122,17 @@ add_custom_command(
|
||||
COMMENT "Generating .bin file"
|
||||
)
|
||||
|
||||
if(DEV)
|
||||
add_custom_command(
|
||||
TARGET ${EXE_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${EXE_NAME}.bin"
|
||||
"${CMAKE_SOURCE_DIR}/archive/f4hwn.fusion.development.bin"
|
||||
COMMENT "Preparing archive/f4hwn.fusion.development.bin"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Generate .hex file
|
||||
add_custom_command(
|
||||
TARGET ${EXE_NAME}
|
||||
|
||||
+28
-10
@@ -9,6 +9,7 @@
|
||||
"toolchainFile": "${sourceDir}/cmake/gcc-arm-none-eabi.cmake",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"DEV": false,
|
||||
"ENABLE_LTO": false,
|
||||
"ENABLE_FMRADIO": false,
|
||||
"ENABLE_UART": true,
|
||||
@@ -34,7 +35,7 @@
|
||||
"ENABLE_BOOT_BEEPS": false,
|
||||
"ENABLE_SHOW_CHARGE_LEVEL": false,
|
||||
"ENABLE_REVERSE_BAT_SYMBOL": false,
|
||||
"ENABLE_NO_CODE_SCAN_TIMEOUT": true,
|
||||
"ENABLE_NO_CODE_SCAN_TIMEOUT": false,
|
||||
"ENABLE_SQUELCH_MORE_SENSITIVE": true,
|
||||
"ENABLE_FASTER_CHANNEL_SCAN": true,
|
||||
"ENABLE_RSSI_BAR": true,
|
||||
@@ -44,11 +45,10 @@
|
||||
"ENABLE_BYP_RAW_DEMODULATORS": false,
|
||||
"ENABLE_BLMIN_TMP_OFF": false,
|
||||
"ENABLE_SCAN_RANGES": true,
|
||||
"ENABLE_REGA": false,
|
||||
"ENABLE_EXTRA_UART_CMD": false,
|
||||
"ENABLE_FEAT_F4HWN": true,
|
||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
||||
"ENABLE_FEAT_F4HWN_SCREENSHOT": false,
|
||||
"ENABLE_FEAT_F4HWN_K5VIEWER": false,
|
||||
"ENABLE_FEAT_F4HWN_SPECTRUM": true,
|
||||
"ENABLE_FEAT_F4HWN_RX_TX_TIMER": true,
|
||||
"ENABLE_FEAT_F4HWN_CHARGING_C": false,
|
||||
@@ -70,13 +70,22 @@
|
||||
"ENABLE_FEAT_F4HWN_MEM": false,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": false,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_FASTER": false,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_RSSI": false,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": false,
|
||||
"ENABLE_FEAT_F4HWN_BEAM": false,
|
||||
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
|
||||
"ENABLE_FEAT_F4HWN_ACTION_PICKER": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": false,
|
||||
"ENABLE_FEAT_F4HWN_LOGO": false,
|
||||
"ENABLE_FEAT_F4HWN_LOGO_SAV": false,
|
||||
"ENABLE_FEAT_F4HWN_MENU_CAT": false,
|
||||
"ENABLE_AGC_SHOW_DATA": false,
|
||||
"ENABLE_UART_RW_BK_REGS": false,
|
||||
"ENABLE_SWD": false,
|
||||
"VERSION_STRING_1": "v0.22",
|
||||
"VERSION_STRING_2": "v5.5.0"
|
||||
"VERSION_STRING_2": "v5.9.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -95,7 +104,7 @@
|
||||
"ENABLE_FMRADIO": false,
|
||||
"ENABLE_VOX": false,
|
||||
"ENABLE_AIRCOPY": true,
|
||||
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
|
||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
||||
@@ -112,7 +121,7 @@
|
||||
"ENABLE_FMRADIO": true,
|
||||
"ENABLE_VOX": true,
|
||||
"ENABLE_AIRCOPY": true,
|
||||
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
|
||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
||||
@@ -129,7 +138,7 @@
|
||||
"ENABLE_FMRADIO": true,
|
||||
"ENABLE_VOX": false,
|
||||
"ENABLE_AIRCOPY": false,
|
||||
"ENABLE_FEAT_F4HWN_SCREENSHOT": false,
|
||||
"ENABLE_FEAT_F4HWN_K5VIEWER": false,
|
||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
||||
"ENABLE_FEAT_F4HWN_SPECTRUM": false,
|
||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
||||
@@ -153,7 +162,7 @@
|
||||
"ENABLE_FMRADIO": false,
|
||||
"ENABLE_VOX": true,
|
||||
"ENABLE_AIRCOPY": true,
|
||||
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
|
||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
||||
@@ -171,7 +180,7 @@
|
||||
"ENABLE_FMRADIO": true,
|
||||
"ENABLE_VOX": false,
|
||||
"ENABLE_AIRCOPY": true,
|
||||
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
|
||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
||||
"ENABLE_FEAT_F4HWN_GAME": true,
|
||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
||||
@@ -188,7 +197,7 @@
|
||||
"ENABLE_FMRADIO": true,
|
||||
"ENABLE_VOX": true,
|
||||
"ENABLE_AIRCOPY": true,
|
||||
"ENABLE_FEAT_F4HWN_SCREENSHOT": true,
|
||||
"ENABLE_FEAT_F4HWN_K5VIEWER": true,
|
||||
"ENABLE_FEAT_F4HWN_GAME": true,
|
||||
"ENABLE_FEAT_F4HWN_PMR": true,
|
||||
"ENABLE_FEAT_F4HWN_GMRS_FRS_MURS": true,
|
||||
@@ -198,10 +207,19 @@
|
||||
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": true,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_FASTER": true,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_RSSI": true,
|
||||
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true,
|
||||
"ENABLE_FEAT_F4HWN_BEAM": true,
|
||||
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
|
||||
"ENABLE_FEAT_F4HWN_ACTION_PICKER": true,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG": true,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true,
|
||||
"ENABLE_FEAT_F4HWN_QRCODE": true,
|
||||
"ENABLE_FEAT_F4HWN_MEM": true,
|
||||
"ENABLE_FEAT_F4HWN_LOGO": true,
|
||||
"ENABLE_FEAT_F4HWN_LOGO_SAV": true,
|
||||
"ENABLE_FEAT_F4HWN_MENU_CAT": true,
|
||||
"ENABLE_SWD": true,
|
||||
"EDITION_STRING": "Fusion",
|
||||
"TARGET": "f4hwn.fusion"
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
F4HWN Edition Fusion firmware for Quansheng UV-K1 and UV-K5 V3
|
||||
================================================================
|
||||
|
||||
This product includes software originally developed by Dual Tachyon
|
||||
for the Quansheng UV-K5 open-source firmware project:
|
||||
|
||||
https://github.com/DualTachyon/uv-k5-firmware
|
||||
|
||||
This product includes work derived from Egzumer's UV-K5 custom
|
||||
firmware:
|
||||
|
||||
https://github.com/egzumer/uv-k5-firmware-custom
|
||||
|
||||
This product includes work developed by Armel / F4HWN and contributors
|
||||
for the F4HWN firmware projects, including the F4HWN Edition Fusion
|
||||
port for UV-K1 and UV-K5 V3 radios:
|
||||
|
||||
https://github.com/armel/uv-k5-firmware-custom
|
||||
https://github.com/armel/uv-k1-k5v3-firmware-custom
|
||||
|
||||
The UV-K1 and UV-K5 V3 port is the result of joint work by muzkr and
|
||||
Armel / F4HWN.
|
||||
|
||||
Additional attribution notices
|
||||
------------------------------
|
||||
|
||||
This product includes contributions and/or derived work from, among
|
||||
others:
|
||||
|
||||
Dual Tachyon
|
||||
OneOfEleven
|
||||
fagci
|
||||
Egzumer
|
||||
muzkr
|
||||
Armel / F4HWN
|
||||
|
||||
Project-specific derived components
|
||||
-----------------------------------
|
||||
|
||||
The spectrum analyzer implementation includes work originally developed
|
||||
by fagci:
|
||||
|
||||
https://github.com/fagci
|
||||
|
||||
Third-party components
|
||||
----------------------
|
||||
|
||||
This product includes CherryUSB by sakumisu, licensed under the
|
||||
Apache License, Version 2.0. Its license is included at:
|
||||
|
||||
Middlewares/CherryUSB/LICENSE
|
||||
|
||||
This product includes CMSIS Version 5 by Arm, licensed under the
|
||||
Apache License, Version 2.0. Its license is included at:
|
||||
|
||||
App/external/CMSIS_5/LICENSE.txt
|
||||
|
||||
This product includes the embedded printf implementation by Marco
|
||||
Paland, licensed under the MIT License. Its license is included at:
|
||||
|
||||
App/external/printf/LICENSE
|
||||
|
||||
License and endorsement notice
|
||||
------------------------------
|
||||
|
||||
This NOTICE file is provided for attribution purposes only and does not
|
||||
modify the terms of any applicable license.
|
||||
|
||||
The main firmware is licensed under the Apache License, Version 2.0.
|
||||
Redistributions of this product or derivative works, in source or object
|
||||
form, must comply with the Apache License, Version 2.0, including its
|
||||
conditions on license copies, modified-file notices, retained copyright
|
||||
and attribution notices, and NOTICE files.
|
||||
|
||||
Attribution in this file does not imply endorsement by Dual Tachyon,
|
||||
OneOfEleven, fagci, Egzumer, muzkr, Armel / F4HWN or any other
|
||||
contributor.
|
||||
@@ -36,151 +36,240 @@ Anyway, have fun.
|
||||
> _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.
|
||||
> EN - I recommend backing up your calibration data with [UV Studio](https://armel.github.io/uvstudio/#dump-calib) immediately after flashing this firmware. It is a good habit to adopt.
|
||||
>
|
||||
> _FR - Je recommande de sauvegarder vos données de calibration avec [uvtools2](https://armel.github.io/uvtools2/) juste après avoir flashé ce firmware. C'est un bon réflexe à avoir._
|
||||
> _FR - Je recommande de sauvegarder vos données de calibration avec [UV Studio](https://armel.github.io/uvstudio/#dump-calib) juste après avoir flashé ce firmware. C'est un bon réflexe à adopter._
|
||||
|
||||
# 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, 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 🙏🏻
|
||||
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 / F4WFS (3 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, Marcin Kusaj, Flavio Cottarelli, Bob N1MLZ, Carlos EA1IJ, Brian M7YLF, Giuseppe IT9LLH and 邓 月 for their [donations](https://www.paypal.com/paypalme/F4HWN). That’s so kind of them. Thanks so much 🙏🏻
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [My Features](#main-features)
|
||||
* [Main features and improvements from F4HWN](#main-features-and-improvements-from-f4hwn)
|
||||
* [Main Features from Egzumer](#main-features-from-egzumer)
|
||||
* [Manual](#manual)
|
||||
* [Compiling and Building from Docker](#compiling-and-Building-from-docker)
|
||||
* [Flashing the Firmware with UVTools2](#flashing-the-firmware-with-uvtools2)
|
||||
* [Flashing the Firmware with UV Studio](#flashing-the-firmware-with-uv-studio)
|
||||
* [Credits](#credits)
|
||||
* [Other sources of information](#other-sources-of-information)
|
||||
* [License](#license)
|
||||
|
||||
## Main features and improvements from F4HWN:
|
||||
## Main features and improvements from F4HWN
|
||||
|
||||
* 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,
|
||||
* High ~5W,
|
||||
* User (see SetPwr),
|
||||
* improve S-Meter (IARU Region 1 Technical Recommendation R.1 for VHF/UHF - [read more](https://hamwaves.com/decibel/en/)),
|
||||
* S-Meter (S0/S9) Level EEPROM settings that were introduced in the Egzumer firmware are now ignored and replaced by hardcoded values to comply with the IARU Recommendation.
|
||||
* improve bandscope (Spectrum Analyser):
|
||||
* add channel name,
|
||||
* add save of some spectrum parameters,
|
||||
* improve UI:
|
||||
* menu index is always visible, even if a menu is selected,
|
||||
* s-meter new design (Classic or Tiny),
|
||||
* MAIN ONLY screen mode,
|
||||
* DUAL and CROSS screen mode,
|
||||
* RX blink on VFO RX,
|
||||
* RX LED blink,
|
||||
* Squelch level and Monitor,
|
||||
* Step value,
|
||||
* CTCSS or DCS value,
|
||||
* KeyLock message,
|
||||
* last RX,
|
||||
* move BatTxt menu from 34/63 to 30/63 (just after BatSave menu 29/63),
|
||||
* rename BackLt to BLTime,
|
||||
* rename BltTRX to BLTxRx,
|
||||
* improve memory channel input,
|
||||
* 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),
|
||||
* add SetPTT menu to set PTT mode (Classic or OnePush),
|
||||
* add SetTOT menu to set TOT alert (Off, Sound, Visual, All),
|
||||
* add SetCtr menu to set contrast (0 to 15),
|
||||
* add SetInv menu to set screen in invert mode (Off or On),
|
||||
* add SetEOT menu to set EOT (End Of Transmission) alert (Off, Sound, Visual, All),
|
||||
* 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,
|
||||
* add SetScn menu to set Scan mode
|
||||
* rename BatVol menu (52/63) to SysInf, which displays the firmware version in addition to the battery status,
|
||||
* improve PonMsg menu,
|
||||
* improve BackLt menu,
|
||||
* improve TxTOut menu,
|
||||
* improve ScnRev menu (CARRIER from 250ms to 20s, STOP, TIMEOUT from 5s to 2m)
|
||||
* improve KeyLck menu (OFF, delay from 15s to 10m)
|
||||
* 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,
|
||||
* improve status bar:
|
||||
* add SetPtt mode in status bar,
|
||||
* change font and bitmaps,
|
||||
* move USB icon to left of battery information,
|
||||
* add RX and TX timers,
|
||||
* 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,
|
||||
* very fast scan mode (around 150 freq/s),
|
||||
* frequencies exclusions,
|
||||
* 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,
|
||||
* PTT,
|
||||
* WIDE NARROW,
|
||||
* 1750Hz,
|
||||
* MUTE,
|
||||
* POWER HIGH,
|
||||
* REMOVE OFFSET,
|
||||
* BEAM,
|
||||
* 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 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 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,
|
||||
* disabled SCRAMBLER,
|
||||
* remove 200Tx, 350Tx and 500Tx,
|
||||
* unlock TX on all bands needs only to be repeat 3 times,
|
||||
* code refactoring and many memory optimization,
|
||||
* stream the live screen of the Quansheng K5 to K5Viewer and capture screenshots over a USB-to-Serial cable,
|
||||
* and more...
|
||||
### Fusion edition
|
||||
|
||||
Fusion is the reference edition of the project. It provides an all-in-one firmware for the UV-K1 and UV-K5 V3, including:
|
||||
|
||||
- Fagci's spectrum analyzer,
|
||||
- broadcast FM radio,
|
||||
- VOX and AirCopy,
|
||||
- BEAM wireless channel transfer,
|
||||
- [UV Studio](https://armel.github.io/uvstudio/) with integrated K5Viewer screen mirroring, screenshots and remote keyboard control,
|
||||
- advanced RX audio profiles and Audio Scope,
|
||||
- first-responder-oriented controls,
|
||||
- the Breakout game,
|
||||
- automatic RX/TX activity logging with RF Log,
|
||||
- full Fox Hunt and Morse Beacon support.
|
||||
|
||||
### Radio and signal handling
|
||||
|
||||
- Reworked output-power levels:
|
||||
- `Low 1`: below approximately 20 mW,
|
||||
- `Low 2`: approximately 125 mW,
|
||||
- `Low 3`: approximately 250 mW,
|
||||
- `Low 4`: approximately 500 mW,
|
||||
- `Low 5`: approximately 1 W,
|
||||
- `Mid`: approximately 2 W,
|
||||
- `High`: approximately 5 W,
|
||||
- `User`: configurable through `SetPwr`.
|
||||
- S-meter calibrated according to the [IARU Region 1 recommendation for VHF/UHF](https://hamwaves.com/decibel/en/):
|
||||
- fixed S0 to S9+ values replace the former EEPROM S-meter thresholds,
|
||||
- Classic and Tiny display styles are available.
|
||||
- Configurable 12.5 kHz or 6.25 kHz narrow-FM bandwidth.
|
||||
- Per-channel TX lock.
|
||||
- Adjustable RX audio volume.
|
||||
- Advanced RX audio profiles for FM and AM reception.
|
||||
- Regional frequency-lock profiles for amateur bands, PMR446, FRS, GMRS and MURS.
|
||||
- Support for 1600, 2200 and 3500 mAh battery profiles.
|
||||
|
||||
### Spectrum analyzer
|
||||
|
||||
- Channel names displayed in the spectrum view.
|
||||
- Persistent spectrum settings.
|
||||
- Faster and smoother spectrum rendering.
|
||||
- Improved behavior when freezing the spectrum or disconnecting USB-C.
|
||||
- Spectrum analyzer state can be restored automatically at startup.
|
||||
|
||||
### Scanning
|
||||
|
||||
- Support for up to 24 named scan lists.
|
||||
- Each memory channel can be assigned to:
|
||||
- `OFF`,
|
||||
- one scan list from `01` to `24`,
|
||||
- `ALL`.
|
||||
- The `ALL` list scans every channel except those assigned to `OFF`.
|
||||
- Automatic selection of the next valid list when the requested list is empty.
|
||||
- Direct scan-list selection while scanning:
|
||||
- `00` selects `ALL`,
|
||||
- `01` to `24` select the corresponding list.
|
||||
- Long press on `MENU` while scanning to exclude the current memory channel.
|
||||
- Up to 64 frequency exclusions.
|
||||
- Very fast scanning mode, reaching approximately 150 frequencies per second.
|
||||
- Scan progress, RSSI and detected CTCSS/DCS information.
|
||||
- Configurable scan resume behavior.
|
||||
- Scan state can be restored automatically at startup.
|
||||
|
||||
### User interface
|
||||
|
||||
- Improved VFO screen with:
|
||||
- Classic and Tiny S-meter styles,
|
||||
- Classic and Tiny frequency-information layouts,
|
||||
- `MAIN ONLY`, `DUAL` and `CROSS` display modes,
|
||||
- RX activity indication on the active VFO,
|
||||
- optional RX LED blinking,
|
||||
- squelch, monitor, step and CTCSS/DCS information,
|
||||
- last-RX indication,
|
||||
- RX and TX timers.
|
||||
- Improved status bar with updated fonts and icons.
|
||||
- Menu index remains visible while editing an entry.
|
||||
- Improved frequency and memory-channel input.
|
||||
- Improved audio-level display.
|
||||
- AirCopy progress percentage and gauge.
|
||||
- Smooth backlight fading.
|
||||
- Manual backlight controls for quickly switching between minimum and maximum brightness.
|
||||
- Configurable contrast and inverted-display mode.
|
||||
- Configurable navigation layout for the different radio models.
|
||||
- Improved power-on message and optional startup logo.
|
||||
- System-information pages for:
|
||||
- firmware version and build information,
|
||||
- battery information,
|
||||
- Flash and SRAM usage,
|
||||
- project and documentation QR codes.
|
||||
|
||||
### Audio and transmission controls
|
||||
|
||||
- Classic and OnePush PTT modes.
|
||||
- Configurable timeout-timer alerts:
|
||||
- disabled,
|
||||
- sound,
|
||||
- visual,
|
||||
- sound and visual.
|
||||
- Configurable end-of-transmission alerts using the same modes.
|
||||
- Audio Scope during RX and TX.
|
||||
- Improved Audio Scope behavior with OnePush PTT, DTMF and the 1750 Hz tone.
|
||||
- Configurable automatic deep-sleep timeout.
|
||||
- Quick actions for:
|
||||
- RX mode,
|
||||
- main-VFO-only display,
|
||||
- PTT,
|
||||
- wide/narrow bandwidth,
|
||||
- 1750 Hz tone,
|
||||
- mute,
|
||||
- RX audio profile,
|
||||
- maximum power,
|
||||
- offset removal.
|
||||
|
||||
### Fox Hunt and Beacon
|
||||
|
||||
- Dedicated Fox Hunt receiver with:
|
||||
- calibrated S-meter display,
|
||||
- scrolling signal-history graph,
|
||||
- peak, minimum and trend indicators,
|
||||
- selectable RF attenuation,
|
||||
- silent, Geiger-style and received-audio modes,
|
||||
- long-press `F` keypad lock (attenuation stays adjustable with the arrow keys).
|
||||
- Integrated Morse Beacon transmitter with:
|
||||
- `MOE`, `MOI`, `MOS`, `MOH`, `MO5` and `MO` identifiers,
|
||||
- optional callsign identification,
|
||||
- configurable TX and idle periods,
|
||||
- live TX and idle countdowns,
|
||||
- persisted Beacon settings,
|
||||
- interactive control during transmission,
|
||||
- shared long-press `F` keypad lock,
|
||||
- TX-lock, modulation and battery-safety checks.
|
||||
- Fox Hunt and Beacon screens are mirrored to UV Studio's integrated K5Viewer.
|
||||
|
||||
### RF Log
|
||||
|
||||
- Automatic logging of RX and TX activity when RF Log is enabled.
|
||||
- Logs stored in the radio's external Flash memory.
|
||||
- Recorded information includes:
|
||||
- RX or TX direction,
|
||||
- frequency and channel,
|
||||
- channel name,
|
||||
- activity duration,
|
||||
- S-meter level,
|
||||
- battery voltage.
|
||||
- On-radio history with RX/TX filtering and detailed views.
|
||||
- Live RF Log dashboard and history access through UV Studio.
|
||||
|
||||
### Connectivity and data transfer
|
||||
|
||||
- Live screen streaming to [UV Studio](https://armel.github.io/uvstudio/) over a USB serial connection.
|
||||
- Screenshot capture and download.
|
||||
- Remote keyboard control through the integrated K5Viewer.
|
||||
- Automatic reconnection after a USB disconnect.
|
||||
- RF Log monitoring, analytics and CSV export.
|
||||
- Firmware flashing, calibration backup and restore, and boot-logo management from the same interface.
|
||||
- BEAM transfer of complete channel settings between compatible radios.
|
||||
- Improved AirCopy interface and progress reporting.
|
||||
|
||||
### Settings and controls
|
||||
|
||||
- New or extended menu entries:
|
||||
- `SetPwr`: configurable User output power,
|
||||
- `SetPTT`: Classic or OnePush PTT,
|
||||
- `SetTOT`: timeout-timer alert,
|
||||
- `SetEOT`: end-of-transmission alert,
|
||||
- `SetCtr`: display contrast,
|
||||
- `SetInv`: inverted display,
|
||||
- `SetLck`: keypad or keypad-and-PTT lock,
|
||||
- `SetMet`: S-meter style,
|
||||
- `SetGUI`: VFO information style,
|
||||
- `SetRxA`: RX audio profile,
|
||||
- `SetTmr`: RX and TX timers,
|
||||
- `SetOff`: automatic deep-sleep delay,
|
||||
- `SetNFM`: narrow-FM bandwidth,
|
||||
- `SetVol`: RX audio volume,
|
||||
- `SetScn`: scan mode,
|
||||
- `SetNav`: radio-specific navigation layout.
|
||||
- Improved `PonMsg`, `BackLt`, `TxTOut`, `ScnRev` and `KeyLck` menus.
|
||||
- Full VFO state restoration with a long press on `EXIT`.
|
||||
- Squelch changes made with `F + UP` or `F + DOWN` are persisted.
|
||||
|
||||
### Keyboard shortcuts and assignable actions
|
||||
|
||||
- `F + UP` or `F + DOWN`: adjust the squelch level.
|
||||
- `F + F1` or `F + F2`: adjust the frequency step.
|
||||
- `F + 8`: temporarily switch the backlight between minimum and maximum brightness.
|
||||
- `F + 9`: return to the configured backlight strategy.
|
||||
- Configurable short- and long-press actions include:
|
||||
- RX mode,
|
||||
- main-VFO-only display,
|
||||
- virtual PTT,
|
||||
- wide/narrow bandwidth,
|
||||
- 1750 Hz tone,
|
||||
- mute,
|
||||
- RX audio profile,
|
||||
- maximum power,
|
||||
- offset removal,
|
||||
- BEAM,
|
||||
- RF Log,
|
||||
- Fox Hunt / Beacon.
|
||||
|
||||
### Reliability and optimization
|
||||
|
||||
- Improved squelch and S-meter behavior.
|
||||
- Fixed DTMF overlay issues.
|
||||
- Fixed scan-range limits.
|
||||
- Cleaner startup display.
|
||||
- Removed PWM-related audio noise.
|
||||
- Improved serial and K5Viewer key handling.
|
||||
- Improved VFO persistence and restoration.
|
||||
- Extensive code refactoring and memory optimization.
|
||||
- DTMF calling and the scrambler remain disabled in Fusion.
|
||||
- Legacy AM Fix code is not used by this firmware.
|
||||
|
||||
## Main features from Egzumer:
|
||||
* many of OneOfEleven mods:
|
||||
@@ -284,35 +373,46 @@ Examples:
|
||||
./compile-with-docker.sh Fusion -DSQL_TONE=600
|
||||
```
|
||||
|
||||
To prepare the rolling development firmware:
|
||||
|
||||
```bash
|
||||
./compile-with-docker.sh Fusion -DDEV=ON
|
||||
```
|
||||
|
||||
This keeps the regular build output in `build/Fusion` and also updates
|
||||
`archive/f4hwn.fusion.development.bin`. The development build is identified as
|
||||
`DEV` in the firmware information screen. Publishing the updated archive file
|
||||
remains an explicit Git operation.
|
||||
|
||||
### Notes
|
||||
|
||||
- The first run may take a few minutes while Docker builds the base image.
|
||||
- Each build runs inside Docker, so your host environment remains clean.
|
||||
|
||||
## Flashing the Firmware with UVTools2
|
||||
## Flashing the Firmware with UV Studio
|
||||
|
||||
You can flash the UV-K5 V3 and UV-K1 directly from your web browser using the cross-platform WebSerial-based [UVTools2](https://armel.github.io/uvtools2/).
|
||||
You can flash the UV-K5 V3 and UV-K1 directly from your web browser using the Web Serial-based [UV Studio](https://armel.github.io/uvstudio/).
|
||||
|
||||
It works on Chrome, Chromium and Edge (desktop versions), and does not require installing any driver or software on your computer.
|
||||
UV Studio combines firmware flashing, calibration maintenance, boot-logo management, K5Viewer and RF Log in a single interface. It requires no application installation, server or account. Use a desktop browser with Web Serial support, such as Chrome, Brave, Edge, Opera or Firefox 151+.
|
||||
|
||||
## Steps to flash the firmware
|
||||
|
||||
- Open UVTools2 in [flash](https://armel.github.io/uvtools2/?mode=flash) mode (or click the Flash Firmware tab).
|
||||
- Open the [Flash Firmware](https://armel.github.io/uvstudio/#flash) view in UV Studio.
|
||||
- Connect your radio to your computer using a compatible USB programming cable (USB-C or Baofeng/Kenwood like double jack USB cable).
|
||||
- Make sure your radio is in **DFU mode (flash mode)**.
|
||||
- Select the firmware .bin file on your computer.
|
||||
- Select an official F4HWN Fusion release from the catalog or load a local `.bin` firmware file.
|
||||
- Click on `Flash Firmware`, then select the serial port associated with your radio.
|
||||
- The progress bar will guide you through the flashing steps.
|
||||
|
||||
Once finished, your radio restart with the new firmware.
|
||||
Once finished, your radio restarts with the new firmware.
|
||||
|
||||
## Steps to dump or restore calibration data
|
||||
|
||||
[UVTools2](https://armel.github.io/uvtools2/) can also dump and restore calibration data, which is highly recommended. It’s best to create a dump right after installing F4HWN firmware, and to restore it before installing another firmware (or when returning to the stock firmware, for example).
|
||||
[UV Studio](https://armel.github.io/uvstudio/) can also dump and restore calibration data, which is highly recommended. It is best to create a dump immediately after installing the F4HWN firmware, and to restore it before installing another firmware or returning to the stock firmware.
|
||||
|
||||
### Dump
|
||||
|
||||
- Open UVTools2 in [dump](https://armel.github.io/uvtools2/?mode=dump) mode (or click the Dump Calib tab).
|
||||
- Open the [Dump Calibration](https://armel.github.io/uvstudio/#dump-calib) view in UV Studio.
|
||||
- Power on your radio in **normal mode**.
|
||||
- Click `Dump Calibration Data`.
|
||||
|
||||
@@ -323,9 +423,9 @@ When the process is complete, click `Download calibration.dat` to save the file
|
||||
|
||||
### Restore
|
||||
|
||||
- Open UVTools2 in [restore](https://armel.github.io/uvtools2/?mode=restore) mode (or click the Restore Calib tab).
|
||||
- Open the [Restore Calibration](https://armel.github.io/uvstudio/#restore-calib) view in UV Studio.
|
||||
- Power on your radio in **normal mode**.
|
||||
- Select your calibration.dat file on your computer.
|
||||
- Select your `calibration.dat` file on your computer.
|
||||
|
||||
Click `Restore Calibration Data` and wait until the process fully completes.
|
||||
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -8,6 +8,7 @@ set -euo pipefail
|
||||
# ./compile-with-docker.sh Custom
|
||||
# ./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 Fusion -DDEV=ON
|
||||
# ./compile-with-docker.sh All
|
||||
# Default preset: "Custom"
|
||||
# ---------------------------------------------
|
||||
@@ -52,9 +53,10 @@ build_preset() {
|
||||
docker run --rm \
|
||||
-u $(id -u):$(id -g) \
|
||||
-it -v "$PWD":/src -w /src "$IMAGE" \
|
||||
bash -c "which arm-none-eabi-gcc && arm-none-eabi-gcc --version && \
|
||||
cmake --preset ${preset} ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"} && \
|
||||
cmake --build --preset ${preset} -j"
|
||||
bash -c 'which arm-none-eabi-gcc && arm-none-eabi-gcc --version &&
|
||||
cmake --preset "$1" "${@:2}" &&
|
||||
cmake --build --preset "$1" -j' \
|
||||
bash "${preset}" ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}
|
||||
echo "✅ Done: ${preset}"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# Quansheng K5Viewer
|
||||
|
||||
K5Viewer is a lightweight viewer that displays the live screen output of the Quansheng K5 transceiver (running F4HWN firmware), transmitted via UART to your computer through a Baofeng/Kenwood-style USB-to-Serial cable.
|
||||
|
||||
https://github.com/user-attachments/assets/578c8605-9820-4a2c-90e4-5a93b2be7d2b
|
||||
|
||||
> [!WARNING]
|
||||
> K5Viewer IS NOT A REMOTE CONTROL TOOL.
|
||||
|
||||
It simply displays the live screen of the Quansheng K5 on your computer and allows you to take screenshots.
|
||||
It can be useful for illustrating documentation, creating tutorials, or demonstrating how K5Viewer works — whether by screen sharing or using a video projector.
|
||||
It's also a handy tool for development and debugging, especially for display-related features.
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Realtime display of 128×64 monochrome screen via serial connection (UART)
|
||||
- Delta frame updates to minimize bandwidth usage
|
||||
- Capture screen snapshots in PNG format
|
||||
- Switch background color (gray, blue, or orange)
|
||||
- Toggle inverted video mode
|
||||
- Toggle LCD pixel rendering mode
|
||||
- Resize the window (zoom in/out)
|
||||
- Display current FPS in the window title
|
||||
|
||||
## 🛠️ Requirements
|
||||
|
||||
- Python **3.6+**
|
||||
- pip (Python package installer)
|
||||
|
||||
### 📦 Install dependencies:
|
||||
|
||||
If necessary, use this command to install dependencies:
|
||||
|
||||
```bash
|
||||
pip install pyserial pygame
|
||||
```
|
||||
|
||||
## ▶️ How to Run
|
||||
|
||||
1. Connect your **Quansheng K5** (running **F4HWN firmware** v4.2) to your computer using a Baofeng/Kenwood-style USB-to-Serial cable.
|
||||
|
||||
2. Run the `k5viewer` with the appropriate serial port:
|
||||
|
||||
```bash
|
||||
./k5viewer.py -port /dev/ttyUSB0 # Linux
|
||||
./k5viewer.py -port /dev/cu.usbserial-xxxx # macOS
|
||||
./k5viewer.py -port COM3 # Windows
|
||||
```
|
||||
> [!NOTE]
|
||||
> If no -port is provided, the script defaults to /dev/ttyUSB0.
|
||||
|
||||
Alternatively, you can manually edit the script and change the `DEFAULT_PORT` variable near the top of the file:
|
||||
|
||||
```python
|
||||
DEFAULT_PORT = '/dev/ttyUSB0' # Linux
|
||||
# or
|
||||
DEFAULT_PORT = '/dev/cu.usbserial-xxxx' # macOS
|
||||
# or
|
||||
DEFAULT_PORT = 'COM3' # Windows
|
||||
```
|
||||
|
||||
You can also list available serial ports to help you choose:
|
||||
|
||||
```bash
|
||||
./k5viewer.py --list-ports
|
||||
```
|
||||
|
||||
## 🎮 Controls
|
||||
|
||||
| Key | Action |
|
||||
|-----------|---------------------------------|
|
||||
| `Q` | Quit the viewer |
|
||||
| `G` | Set background to **gray** |
|
||||
| `B` | Set background to **blue** |
|
||||
| `O` | Set background to **orange** |
|
||||
| `W` | Set background to **white** |
|
||||
| `I` | Toggle **video inversion** |
|
||||
| `P` | Toggle **LCD pixel mode** |
|
||||
| `SPACE` | Save a screenshot as PNG |
|
||||
| `UP` | Increase window size |
|
||||
| `DOWN` | Decrease window size |
|
||||
|
||||
|
||||
Screenshots are saved as `screenshot_YYYYMMDD_HHMMSS.png` in the same directory.
|
||||
|
||||
## 📬 Contact
|
||||
|
||||
If you encounter issues or have suggestions, feel free to open an issue or submit a pull request. Enjoy building with your Quansheng K5! 📡
|
||||
@@ -1,228 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import argparse
|
||||
|
||||
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
|
||||
|
||||
import pygame
|
||||
import serial
|
||||
from serial.tools import list_ports
|
||||
|
||||
# Version
|
||||
VERSION = '1.0'
|
||||
|
||||
# Serial configuration
|
||||
DEFAULT_PORT = '/dev/ttyUSB0' # Change if needed (/dev/cu.usbserial-11130)
|
||||
BAUDRATE = 38400
|
||||
TIMEOUT = 0.5
|
||||
|
||||
# Screen configuration
|
||||
WIDTH, HEIGHT = 128, 64
|
||||
FRAME_SIZE = 1024
|
||||
|
||||
# Protocol
|
||||
HEADER = b'\xAA\x55'
|
||||
TYPE_SCREENSHOT = b'\x01'
|
||||
TYPE_DIFF = b'\x02'
|
||||
|
||||
# Framebuffer
|
||||
framebuffer = bytearray([0] * FRAME_SIZE)
|
||||
|
||||
|
||||
COLOR_SETS = { # {key: (name, foreground, background)}
|
||||
"g": ("Grey", pygame.Color(0, 0, 0), pygame.Color(202, 202, 202)),
|
||||
"o": ("Orange", pygame.Color(0, 0, 0), pygame.Color(255, 193, 37)),
|
||||
"b": ("Blue", pygame.Color(0, 0, 0), pygame.Color(28, 134, 228)),
|
||||
"w": ("White", pygame.Color(0, 0, 0), pygame.Color(255, 255, 255)),
|
||||
}
|
||||
|
||||
DEFAULT_COLOR = "g" # Must be a key of "COLOR_SETS"
|
||||
|
||||
def send_keepalive(ser: serial.Serial):
|
||||
# Send keepalive frame
|
||||
try:
|
||||
ser.write(b'\x55\xAA\x00\x00') # Keepalive frame
|
||||
except serial.SerialException:
|
||||
pass
|
||||
|
||||
def read_frame(ser: serial.Serial) -> bytearray:
|
||||
global framebuffer
|
||||
while True:
|
||||
try:
|
||||
b = ser.read(1)
|
||||
except serial.SerialException as e:
|
||||
#print(f"[ERROR] Serial read failed: {e}")
|
||||
print("[!] Your USB serial cable is probably being used by another application such as Chirp or Chrome.")
|
||||
sys.exit(1)
|
||||
if not b:
|
||||
return None
|
||||
if b == HEADER[0:1]:
|
||||
b2 = ser.read(1)
|
||||
if b2 == HEADER[1:2]:
|
||||
t = ser.read(1)
|
||||
size_bytes = ser.read(2)
|
||||
size = int.from_bytes(size_bytes, 'big')
|
||||
if t == TYPE_SCREENSHOT and size == FRAME_SIZE:
|
||||
payload = ser.read(FRAME_SIZE)
|
||||
framebuffer = bytearray(payload)
|
||||
return framebuffer
|
||||
elif t == TYPE_DIFF and size % 9 == 0:
|
||||
payload = ser.read(size)
|
||||
framebuffer = apply_diff(framebuffer, payload)
|
||||
return framebuffer
|
||||
|
||||
|
||||
def apply_diff(framebuffer: bytearray, diff_payload: bytes) -> bytearray:
|
||||
i = 0
|
||||
while i + 9 <= len(diff_payload):
|
||||
block_index = diff_payload[i]
|
||||
i += 1
|
||||
if block_index >= 128:
|
||||
break
|
||||
framebuffer[block_index * 8 : block_index * 8 + 8] = diff_payload[i : i + 8]
|
||||
i += 8
|
||||
return framebuffer
|
||||
|
||||
|
||||
def draw_frame(screen: pygame.Surface, framebuffer: bytearray, bg_color: pygame.Color, fg_color: pygame.Color, pixel_size: int = 4, pixel_lcd: int = 0) -> pygame.Surface:
|
||||
def get_bit(bit_idx):
|
||||
byte_idx = bit_idx // 8
|
||||
bit_pos = bit_idx % 8
|
||||
if byte_idx < len(framebuffer):
|
||||
return (framebuffer[byte_idx] >> bit_pos) & 0x01
|
||||
return 0
|
||||
|
||||
screen.fill(bg_color)
|
||||
bit_index = 0
|
||||
for y in range(64):
|
||||
for x in range(128):
|
||||
if get_bit(bit_index):
|
||||
px = x * (pixel_size - 1)
|
||||
py = y * pixel_size
|
||||
pygame.draw.rect(screen, fg_color, (px, py, pixel_size - 1 - pixel_lcd, pixel_size - pixel_lcd))
|
||||
bit_index += 1
|
||||
|
||||
pygame.display.flip()
|
||||
return pygame.display.get_surface().copy()
|
||||
|
||||
|
||||
def run_viewer(args: argparse.Namespace, ser: serial.Serial):
|
||||
pixel_size = 5
|
||||
pixel_lcd = 0
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((WIDTH * (pixel_size - 1), HEIGHT * pixel_size))
|
||||
base_title = f"Quansheng K5Viewer v{VERSION} by F4HWN"
|
||||
pygame.display.set_caption(f"{base_title} – No data")
|
||||
|
||||
fg_color, bg_color = COLOR_SETS[DEFAULT_COLOR][1:]
|
||||
last_surface = None
|
||||
frame_count = 0
|
||||
frame_lost = 0
|
||||
last_time = time.monotonic()
|
||||
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
raise KeyboardInterrupt
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_q:
|
||||
raise KeyboardInterrupt
|
||||
if event.key == pygame.K_SPACE and last_surface:
|
||||
filename = datetime.datetime.now().strftime("screenshot_%Y%m%d_%H%M%S.png")
|
||||
pygame.image.save(last_surface, filename)
|
||||
print(f"[✔] Screenshot saved: {filename}")
|
||||
elif event.key == pygame.K_p:
|
||||
pixel_lcd = 1 - pixel_lcd
|
||||
draw_frame(screen, framebuffer, bg_color, fg_color, pixel_size, pixel_lcd)
|
||||
elif event.key == pygame.K_i:
|
||||
if bg_color == pygame.Color(0, 0, 0):
|
||||
bg_color, fg_color = fg_color, pygame.Color(0, 0, 0)
|
||||
else:
|
||||
bg_color, fg_color = pygame.Color(0, 0, 0), bg_color
|
||||
draw_frame(screen, framebuffer, bg_color, fg_color, pixel_size, pixel_lcd)
|
||||
elif event.key == pygame.K_UP:
|
||||
if pixel_size < 12:
|
||||
pixel_size += 1
|
||||
#print(f"[✔] Resize: {pixel_size, (WIDTH * (pixel_size - 1)), HEIGHT * pixel_size}")
|
||||
screen = pygame.display.set_mode((WIDTH * (pixel_size - 1), HEIGHT * pixel_size))
|
||||
draw_frame(screen, framebuffer, bg_color, fg_color, pixel_size, pixel_lcd)
|
||||
elif event.key == pygame.K_DOWN:
|
||||
if pixel_size > 3:
|
||||
pixel_size -= 1
|
||||
#print(f"[✔] Resize: {pixel_size, (WIDTH * (pixel_size - 1)), HEIGHT * pixel_size}")
|
||||
screen = pygame.display.set_mode((WIDTH * (pixel_size - 1), HEIGHT * pixel_size))
|
||||
draw_frame(screen, framebuffer, bg_color, fg_color, pixel_size, pixel_lcd)
|
||||
pressed_key = event.unicode
|
||||
if pressed_key in COLOR_SETS.keys():
|
||||
fg_color, bg_color = COLOR_SETS[pressed_key][1:]
|
||||
frame = read_frame(ser)
|
||||
if frame:
|
||||
last_surface = draw_frame(screen, framebuffer, bg_color, fg_color, pixel_size, pixel_lcd)
|
||||
frame_count += 1
|
||||
now = time.monotonic()
|
||||
if now - last_time >= 1.0:
|
||||
fps = frame_count / (now - last_time)
|
||||
pygame.display.set_caption(f"{base_title} – FPS: {fps:>04.1f}")
|
||||
frame_count = 0
|
||||
last_time = now
|
||||
frame_lost = 0
|
||||
else:
|
||||
frame_lost = min(frame_lost + 1, 5)
|
||||
if frame_lost == 5:
|
||||
pygame.display.set_caption(f"{base_title} – No data")
|
||||
|
||||
send_keepalive(ser)
|
||||
|
||||
|
||||
def cmd_list_ports(args: argparse.Namespace):
|
||||
ports = list_ports.comports()
|
||||
print("Available ports:")
|
||||
for port in ports:
|
||||
if port.vid is None: # Skipping virtual or non-USB ports
|
||||
continue
|
||||
description = " - ".join(filter(None, (port.product, port.manufacturer)))
|
||||
if description:
|
||||
print(f"- {description} : {port.device}")
|
||||
else:
|
||||
print(f"- {port.device}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="K5Viewer",
|
||||
description="A live viewer for UV-K5 radios with F4HWN firmware",
|
||||
epilog="F4HWN repo: https://github.com/armel/uv-k5-firmware-custom"
|
||||
)
|
||||
parser.add_argument("--list-ports", action="store_true", help="list available ports and exit")
|
||||
parser.add_argument("--port", type=str, help="serial port to use (in place of 'DEFAULT_PORT')")
|
||||
parser.add_argument("--version", action="version", version=f"%(prog)s {VERSION}", help="show program's version number and exit")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.list_ports:
|
||||
cmd_list_ports(args)
|
||||
exit(0)
|
||||
# Running viewer
|
||||
if not args.port and not DEFAULT_PORT:
|
||||
print("Please specify the serial port to use or set 'DEFAULT_PORT', do 'k5viewer.py --help' for help")
|
||||
exit(1)
|
||||
serial_port = args.port or DEFAULT_PORT
|
||||
try:
|
||||
ser = serial.Serial(serial_port, BAUDRATE, timeout=TIMEOUT)
|
||||
except serial.SerialException as e:
|
||||
print(f"[!] Serial error: {e}")
|
||||
sys.exit(1)
|
||||
try:
|
||||
run_viewer(args, ser)
|
||||
except KeyboardInterrupt:
|
||||
print("[✔] Exiting")
|
||||
ser.close()
|
||||
pygame.quit()
|
||||
sys.exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user