Merge pull request #338 from armel/feature_update_v5

Feature update v5
This commit is contained in:
Armel FAUVEAU authored and GitHub committed 2026-05-08 03:18:49 +02:00
commit e21d472609
47 files changed
+1981 -603

No files matched your search

+10 -1
View File
@@ -123,7 +123,7 @@ enable_feature(ENABLE_FMRADIO
ui/fmradio.c
)
enable_feature(ENABLE_AIRCOPY
app/aircopy.c
app/aircopy.c
ui/aircopy.c
)
enable_feature(ENABLE_NOAA)
@@ -196,6 +196,7 @@ enable_feature(ENABLE_FEAT_F4HWN_NARROWER)
enable_feature(ENABLE_FEAT_F4HWN_INV)
enable_feature(ENABLE_FEAT_F4HWN_CTR)
enable_feature(ENABLE_FEAT_F4HWN_SCAN_PROGRESS)
enable_feature(ENABLE_FEAT_F4HWN_SCAN_FASTER)
enable_feature(ENABLE_FEAT_F4HWN_RESCUE_OPS)
enable_feature(ENABLE_FEAT_F4HWN_VOL)
enable_feature(ENABLE_FEAT_F4HWN_AUDIO)
@@ -205,7 +206,15 @@ enable_feature(ENABLE_FEAT_F4HWN_GMRS_FRS_MURS)
enable_feature(ENABLE_FEAT_F4HWN_CA)
enable_feature(ENABLE_FEAT_F4HWN_DEBUG)
enable_feature(ENABLE_FEAT_F4HWN_MEM)
enable_feature(ENABLE_FEAT_F4HWN_BEAM
app/beam.c
)
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)
# ---- DEBUGGING ----
+17 -16
View File
@@ -40,10 +40,14 @@
#include "misc.h"
#include "settings.h"
#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
#if defined(ENABLE_FMRADIO)
static void ACTION_Scan_FM(bool bRestart);
@@ -131,6 +135,9 @@ void (*action_opt_table[])(void) = {
[ACTION_OPT_REGA_ALARM] = &ACTION_RegaAlarm,
[ACTION_OPT_REGA_TEST] = &ACTION_RegaTest,
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
[ACTION_OPT_BEAM] = &ACTION_Beam,
#endif
};
static_assert(ARRAY_SIZE(action_opt_table) == ACTION_OPT_LEN);
@@ -212,7 +219,7 @@ void ACTION_Scan(bool bRestart)
DTMF_clear_RX();
#endif
gDTMF_RX_live_timeout = 0;
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
RADIO_SelectVfos();
@@ -237,15 +244,14 @@ void ACTION_Scan(bool bRestart)
// channel mode. Keep scanning but toggle between scan lists
RADIO_NextValidList(1);
UI_MAIN_NotifyScanProgressDataChanged();
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
SETTINGS_WriteCurrentState();
#endif
// jump to the next channel
CHFRSCANNER_Start(false, gScanStateDir);
gScanPauseDelayIn_10ms = 1;
gScheduleScanListen = false;
CHFRSCANNER_ManualResume(gScanStateDir);
} else {
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
if(gScanRangeStart == 0) // No ScanRange
@@ -343,22 +349,14 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
break;
}
if (!bKeyHeld && bKeyPressed) // button pushed
{
if (bKeyHeld != bKeyPressed) { // button pushed or released after hold
// (!bKeyHeld && bKeyPressed) or (bKeyHeld && !bKeyPressed)
return;
}
// held or released beyond this point
// held or released after short press
if(!(bKeyHeld && !bKeyPressed)) // don't beep on released after hold
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (bKeyHeld && !bKeyPressed) // button released after hold
{
return;
}
// held or released after short press beyond this point
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
#ifdef ENABLE_FMRADIO
if (gFmRadioMode) { // do not run these actions in FM radio mode
@@ -382,6 +380,9 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
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;
+18 -35
View File
@@ -90,12 +90,14 @@ 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)
};
// total_blocks = ceil(0x170/64) + ceil(0x60/64) + ceil(0xD6/64) = 6 + 2 + 4 = 12
static const AIRCOPY_TransferMap_t AIRCOPY_Map_Settings = {
.segments = AIRCOPY_Segments_Settings,
.num_segments = 2,
.total_blocks = 8
.num_segments = 3,
.total_blocks = 12
};
// Finally
@@ -158,8 +160,10 @@ static inline const AIRCOPY_Segment_t *AIRCOPY_FindSegmentForOffset(uint16_t off
return NULL;
}
static inline void AIRCOPY_CheckComplete(void)
static inline void AIRCOPY_CheckComplete(uint16_t *num)
{
*num = *num + 1;
const AIRCOPY_TransferMap_t *map = AIRCOPY_GetCurrentMap();
uint16_t done = gAirCopyBlockNumber + gErrorsDuringAirCopy;
@@ -172,9 +176,9 @@ static inline void AIRCOPY_CheckComplete(void)
}
}
static inline void AIRCOPY_Obfuscation(void)
void AIRCOPY_Obfuscate(unsigned int count)
{
for (unsigned int i = 0; i < 34; i++) {
for (unsigned int i = 0; i < count; i++) {
g_FSK_Buffer[i + 1] ^= Obfuscation[i % 8];
}
}
@@ -230,7 +234,7 @@ bool AIRCOPY_SendMessage(void)
g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
AIRCOPY_Obfuscation();
AIRCOPY_Obfuscate(34);
RADIO_SetTxParameters();
@@ -257,21 +261,18 @@ void AIRCOPY_StorePacket(void)
BK4819_PrepareFSKReceive();
if ((Status & 0x0010U) != 0 || g_FSK_Buffer[0] != 0xABCD || g_FSK_Buffer[35] != 0xDCBA) {
gErrorsDuringAirCopy++;
BK4819_ResetFSK(); // <- important
BK4819_PrepareFSKReceive(); // <- re-arm proprement
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gErrorsDuringAirCopy);
return;
}
AIRCOPY_Obfuscation();
AIRCOPY_Obfuscate(34);
uint16_t Crc = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
if (g_FSK_Buffer[34] != Crc) {
gErrorsDuringAirCopy++;
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gErrorsDuringAirCopy);
return;
}
@@ -280,36 +281,18 @@ void AIRCOPY_StorePacket(void)
const AIRCOPY_Segment_t *seg = AIRCOPY_FindSegmentForOffset(Offset);
if (seg == NULL) {
gErrorsDuringAirCopy++;
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gErrorsDuringAirCopy);
return;
}
if (seg->write_mode == AIRCOPY_WRITE_BYTES)
{
/* Raw bytes stream, written in 8-byte EEPROM chunks */
const uint8_t *p8 = (const uint8_t *)&g_FSK_Buffer[2];
const uint8_t *pData = (const uint8_t *)&g_FSK_Buffer[2];
for (unsigned int i = 0; i < 8; i++)
{
EEPROM_WriteBuffer(Offset + (i * 8), p8 + (i * 8));
}
}
else
for (unsigned int i = 0; i < 8; i++)
{
/* Structured data path (kept as-is) */
const uint16_t *pData = &g_FSK_Buffer[2];
for (unsigned int i = 0; i < 8; i++)
{
EEPROM_WriteBuffer(Offset, pData);
pData += 4;
Offset += 8;
}
EEPROM_WriteBuffer(Offset + (i * 8), pData + (i * 8));
}
gAirCopyBlockNumber++;
AIRCOPY_CheckComplete();
AIRCOPY_CheckComplete(&gAirCopyBlockNumber);
}
static void AIRCOPY_InitTransfer(bool isSendMode)
+4
View File
@@ -106,5 +106,9 @@ void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
const AIRCOPY_TransferMap_t* AIRCOPY_GetCurrentMap(void);
// XOR-obfuscate `count` words of g_FSK_Buffer starting at index 1.
// Self-inverse: applying twice restores the original buffer.
void AIRCOPY_Obfuscate(unsigned int count);
#endif // ENABLE_AIRCOPY
#endif // APP_AIRCOPY_H
+65 -12
View File
@@ -24,6 +24,9 @@
#ifdef ENABLE_AIRCOPY
#include "app/aircopy.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include "app/beam.h"
#endif
#include "app/app.h"
#include "app/chFrScanner.h"
#include "app/dtmf.h"
@@ -796,6 +799,25 @@ static void CheckRadioInterrupts(void)
AIRCOPY_StorePacket();
}
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
if ((interrupts.fskFifoAlmostFull || interrupts.fskRxFinied) &&
gBeamActive &&
gBeamMode == BEAM_MODE_RX &&
(gBeamStatus == BEAM_STATUS_RX_WAIT || gBeamStatus == BEAM_STATUS_ERROR))
{
const unsigned int wordsToRead = interrupts.fskRxFinied ? (36 - gFSKWriteIndex) : 4;
for (unsigned int i = 0; i < wordsToRead; i++) {
const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F);
if (gFSKWriteIndex < 36)
g_FSK_Buffer[gFSKWriteIndex++] = word;
}
gBeamRxWordCount = gFSKWriteIndex;
gUpdateDisplay = true;
BEAM_StorePacket();
}
#endif
}
}
@@ -994,7 +1016,7 @@ void APP_Update(void)
APP_EndTransmission();
AUDIO_PlayBeep(BEEP_880HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
RADIO_SetVfoState(VFO_STATE_TIMEOUT);
@@ -1044,6 +1066,9 @@ void APP_Update(void)
&& gScanStateDir == SCAN_OFF
&& !gPttIsPressed
&& gCurrentFunction != FUNCTION_POWER_SAVE
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
#ifdef ENABLE_VOICE
&& gVoiceWriteIndex == 0
#endif
@@ -1122,7 +1147,11 @@ void APP_Update(void)
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF &&
gScanStateDir == SCAN_OFF &&
!gCssBackgroundScan)
!gCssBackgroundScan
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
)
{ // dual watch mode, toggle between the two VFO's
DualwatchAlternate();
goToSleep = false;
@@ -1133,7 +1162,11 @@ void APP_Update(void)
gPowerSave_10ms = power_save1_10ms; // come back here in a bit
gRxIdleMode = false; // RX is awake
}
else if (gEeprom.DUAL_WATCH == DUAL_WATCH_OFF || gScanStateDir != SCAN_OFF || gCssBackgroundScan || goToSleep)
else if (
#ifdef ENABLE_FEAT_F4HWN_BEAM
!gBeamActive &&
#endif
(gEeprom.DUAL_WATCH == DUAL_WATCH_OFF || gScanStateDir != SCAN_OFF || gCssBackgroundScan || goToSleep))
{ // dual watch mode off or scanning or rssi update request
// go back to sleep
@@ -1152,7 +1185,11 @@ void APP_Update(void)
// Authentic device checked removed
}
else {
else
#ifdef ENABLE_FEAT_F4HWN_BEAM
if (!gBeamActive)
#endif
{
// toggle between the two VFO's
DualwatchAlternate();
gPowerSave_10ms = power_save1_10ms;
@@ -1560,7 +1597,7 @@ void APP_TimeSlice500ms(void)
{
if (gDTMF_RX_live[0] != 0)
{
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
gUpdateDisplay = true;
}
}
@@ -1608,11 +1645,14 @@ void APP_TimeSlice500ms(void)
gWakeUp = false;
}
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx()
#ifdef ENABLE_AIRCOPY
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx() && gScreenToDisplay != DISPLAY_AIRCOPY)
#else
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx())
&& gScreenToDisplay != DISPLAY_AIRCOPY
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
)
{
if (gSleepModeCountdown_500ms > 0 && --gSleepModeCountdown_500ms == 0) {
gBacklightCountdown_500ms = 0;
@@ -1698,6 +1738,9 @@ void APP_TimeSlice500ms(void)
#endif
#ifdef ENABLE_AIRCOPY
&& gScreenToDisplay != DISPLAY_AIRCOPY
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
&& !gBeamActive
#endif
) {
if (gEeprom.AUTO_KEYPAD_LOCK && gKeyLockCountdown > 0 && !gDTMF_InputMode
@@ -1908,7 +1951,7 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
if (Key == KEY_EXIT && bKeyHeld) { // exit key held pressed
// clear the live DTMF decoder
if (gDTMF_RX_live[0] != 0) {
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
gDTMF_RX_live_timeout = 0;
gUpdateDisplay = true;
}
@@ -2088,7 +2131,8 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
}
}
#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
else if ((!bKeyHeld && bKeyPressed) || (gAlarmState == ALARM_STATE_TX1750 && bKeyHeld && !bKeyPressed)) {
// else if ((!bKeyHeld && bKeyPressed) || (gAlarmState == ALARM_STATE_TX1750 && bKeyHeld && !bKeyPressed)) {
else if ((bKeyHeld != bKeyPressed) && (gAlarmState == ALARM_STATE_TX1750 || bKeyPressed)) {
ALARM_Off();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0)
@@ -2103,12 +2147,21 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
}
#endif
}
else if (gScreenToDisplay != DISPLAY_INVALID && (
else if (
#ifdef ENABLE_FEAT_F4HWN_BEAM
gBeamActive ||
#endif
(gScreenToDisplay != DISPLAY_INVALID && (
(Key != KEY_SIDE1 && Key != KEY_SIDE2)
#ifdef ENABLE_FEAT_F4HWN // For F + SIDE1 or F + SIDE2
|| (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2))
#endif
)) {
))) {
#ifdef ENABLE_FEAT_F4HWN_BEAM
if (gBeamActive)
BEAM_ProcessKeys(Key, bKeyPressed, bKeyHeld);
else
#endif
ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld);
}
else if (!SCANNER_IsScanning()
+325
View File
@@ -0,0 +1,325 @@
/* 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.
*/
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include <assert.h>
#include <string.h>
#include "app/aircopy.h"
#include "app/beam.h"
#include "audio.h"
#include "driver/bk4819.h"
#include "driver/crc.h"
#include "driver/st7565.h"
#include "frequencies.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/main.h"
#include "ui/ui.h"
#define BEAM_PACKET_MAGIC 0xBEA5u
#define BEAM_PACKET_VERSION 2u
// One byte per field instead of bit-packing — payload still fits in 64 bytes,
// and avoids costly shift/mask/store sequences on Cortex-M.
typedef struct {
uint16_t magic;
uint8_t version;
uint8_t _pad;
uint32_t rx_frequency;
uint32_t tx_offset_frequency;
uint8_t rx_code;
uint8_t tx_code;
uint8_t rx_codetype;
uint8_t tx_codetype;
uint8_t modulation;
uint8_t tx_offset_direction;
uint8_t tx_lock;
uint8_t busy_channel_lock;
uint8_t output_power;
uint8_t channel_bandwidth;
uint8_t frequency_reverse;
uint8_t dtmf_ptt_id_mode;
uint8_t dtmf_decoding_enable;
uint8_t step_setting;
uint8_t scrambling_type;
uint8_t band;
uint8_t scanlist;
uint8_t compander;
char name[16];
} BEAM_Payload_t;
static_assert(sizeof(BEAM_Payload_t) <= 64);
BEAM_Mode_t gBeamMode = BEAM_MODE_TX;
BEAM_Status_t gBeamStatus = BEAM_STATUS_READY;
uint16_t gBeamCopiedChannel = 0xFFFFu;
uint8_t gBeamRxWordCount;
bool gBeamActive;
static VFO_Info_t gBeamRadioVfo;
static void BEAM_SetRadioToBeamFrequency(void)
{
const uint16_t channel = FREQ_CHANNEL_FIRST + BAND6_400MHz;
RADIO_InitInfo(&gBeamRadioVfo, channel, DEFAULT_FREQ);
gBeamRadioVfo.CHANNEL_BANDWIDTH = BANDWIDTH_NARROW;
gBeamRadioVfo.OUTPUT_POWER = OUTPUT_POWER_LOW1;
RADIO_ConfigureSquelchAndOutputPower(&gBeamRadioVfo);
gRxVfo = &gBeamRadioVfo;
gTxVfo = &gBeamRadioVfo;
gCurrentVfo = gRxVfo;
RADIO_SetupRegisters(true);
BK4819_SetupAircopy();
BK4819_ResetFSK();
}
static void BEAM_SendPacket(void)
{
memset(g_FSK_Buffer, 0, sizeof(g_FSK_Buffer));
g_FSK_Buffer[0] = 0xABCDu;
BEAM_Payload_t * const payload = (BEAM_Payload_t *)&g_FSK_Buffer[2];
const VFO_Info_t *vfo = &gEeprom.VfoInfo[gEeprom.TX_VFO];
payload->magic = BEAM_PACKET_MAGIC;
payload->version = BEAM_PACKET_VERSION;
payload->rx_frequency = vfo->freq_config_RX.Frequency;
payload->tx_offset_frequency = vfo->TX_OFFSET_FREQUENCY;
payload->rx_code = vfo->freq_config_RX.Code;
payload->tx_code = vfo->freq_config_TX.Code;
payload->rx_codetype = vfo->freq_config_RX.CodeType;
payload->tx_codetype = vfo->freq_config_TX.CodeType;
payload->modulation = vfo->Modulation;
payload->tx_offset_direction = vfo->TX_OFFSET_FREQUENCY_DIRECTION;
payload->tx_lock = vfo->TX_LOCK;
payload->busy_channel_lock = vfo->BUSY_CHANNEL_LOCK;
payload->output_power = vfo->OUTPUT_POWER;
payload->channel_bandwidth = vfo->CHANNEL_BANDWIDTH;
payload->frequency_reverse = vfo->FrequencyReverse;
payload->dtmf_ptt_id_mode = vfo->DTMF_PTT_ID_TX_MODE;
#ifdef ENABLE_DTMF_CALLING
payload->dtmf_decoding_enable = vfo->DTMF_DECODING_ENABLE;
#endif
payload->step_setting = vfo->STEP_SETTING;
payload->scrambling_type = vfo->SCRAMBLING_TYPE;
payload->band = vfo->Band;
payload->scanlist = vfo->SCANLIST_PARTICIPATION;
payload->compander = vfo->Compander;
if (IS_MR_CHANNEL(vfo->CHANNEL_SAVE)) {
SETTINGS_FetchChannelName(payload->name, vfo->CHANNEL_SAVE);
} else {
memcpy(payload->name, vfo->Name, sizeof(vfo->Name));
}
g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
g_FSK_Buffer[35] = 0xDCBAu;
AIRCOPY_Obfuscate(32);
// Show "SENDING" before the blocking FSK transmission so the user gets
// immediate visual feedback instead of a frozen "BEAM TX" screen.
gBeamStatus = BEAM_STATUS_TX_WAIT;
UI_DisplayMain();
ST7565_BlitFullScreen();
RADIO_SetTxParameters();
BK4819_SendFSKData(g_FSK_Buffer);
BK4819_SetupPowerAmplifier(0, 0);
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
gBeamStatus = BEAM_STATUS_TX_DONE;
gBeepToPlay = BEEP_880HZ_60MS_TRIPLE_BEEP;
gUpdateDisplay = true;
}
static void BEAM_SavePayloadToFirstFreeChannel(const BEAM_Payload_t *payload)
{
uint16_t channel = 0xFFFFu;
for (uint16_t c = MR_CHANNEL_FIRST; IS_MR_CHANNEL(c); c++) {
if (!RADIO_CheckValidChannel(c, false, 0)) {
channel = c;
break;
}
}
if (channel == 0xFFFFu) {
gBeamStatus = BEAM_STATUS_RX_FULL;
gUpdateDisplay = true;
return;
}
VFO_Info_t vfo;
RADIO_InitInfo(&vfo, channel, payload->rx_frequency);
// CRC + magic + version already validate the payload — no need to clamp fields.
vfo.TX_OFFSET_FREQUENCY = payload->tx_offset_frequency;
vfo.freq_config_RX.Code = payload->rx_code;
vfo.freq_config_TX.Code = payload->tx_code;
vfo.freq_config_RX.CodeType = payload->rx_codetype;
vfo.freq_config_TX.CodeType = payload->tx_codetype;
vfo.TX_OFFSET_FREQUENCY_DIRECTION = payload->tx_offset_direction;
vfo.Modulation = payload->modulation;
vfo.TX_LOCK = payload->tx_lock;
vfo.BUSY_CHANNEL_LOCK = payload->busy_channel_lock;
vfo.OUTPUT_POWER = payload->output_power;
vfo.CHANNEL_BANDWIDTH = payload->channel_bandwidth;
vfo.FrequencyReverse = payload->frequency_reverse;
vfo.DTMF_PTT_ID_TX_MODE = payload->dtmf_ptt_id_mode;
#ifdef ENABLE_DTMF_CALLING
vfo.DTMF_DECODING_ENABLE = payload->dtmf_decoding_enable;
#endif
vfo.STEP_SETTING = payload->step_setting;
vfo.StepFrequency = gStepFrequencyTable[vfo.STEP_SETTING];
vfo.SCRAMBLING_TYPE = payload->scrambling_type;
vfo.Band = payload->band;
vfo.SCANLIST_PARTICIPATION = payload->scanlist;
vfo.Compander = payload->compander;
memcpy(vfo.Name, payload->name, sizeof(vfo.Name));
vfo.Name[sizeof(vfo.Name) - 1] = '\0';
RADIO_ApplyOffset(&vfo);
RADIO_ConfigureSquelchAndOutputPower(&vfo);
SETTINGS_SaveChannel(channel, gEeprom.TX_VFO, &vfo, 3);
SETTINGS_SaveChannelName(channel, vfo.Name);
gBeamCopiedChannel = channel;
gBeamStatus = BEAM_STATUS_RX_SAVED;
gUpdateDisplay = true;
BACKLIGHT_TurnOn();
return;
}
static void BEAM_KeyMenu(void)
{
BEAM_SetRadioToBeamFrequency();
if (gBeamMode == BEAM_MODE_TX) {
BEAM_SendPacket();
} else {
gBeamStatus = BEAM_STATUS_RX_WAIT;
gBeamCopiedChannel = 0xFFFFu;
gBeamRxWordCount = 0;
gFSKWriteIndex = 0;
BK4819_PrepareFSKReceive();
}
}
static void BEAM_KeyExit(void)
{
BK4819_ResetFSK();
if (gBeamMode == BEAM_MODE_RX && gBeamCopiedChannel != 0xFFFFu) {
gEeprom.MrChannel[gEeprom.TX_VFO] = gBeamCopiedChannel;
gEeprom.ScreenChannel[gEeprom.TX_VFO] = gBeamCopiedChannel;
RADIO_ConfigureChannel(gEeprom.TX_VFO, VFO_CONFIGURE_RELOAD);
}
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
GUI_SelectNextDisplay(DISPLAY_MAIN);
gBeamActive = false;
}
void ACTION_Beam(void)
{
gBeamMode = BEAM_MODE_TX;
gBeamStatus = BEAM_STATUS_READY;
gBeamCopiedChannel = 0xFFFFu;
gBeamRxWordCount = 0;
gBeamActive = true;
GUI_SelectNextDisplay(DISPLAY_MAIN);
}
void BEAM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
if (bKeyHeld || !bKeyPressed)
return;
if (Key != KEY_PTT)
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
switch (Key) {
case KEY_UP:
case KEY_DOWN:
gBeamMode ^= 1; // (gBeamMode == BEAM_MODE_TX) ? BEAM_MODE_RX : BEAM_MODE_TX
gBeamStatus = BEAM_STATUS_READY;
gBeamRxWordCount = 0;
break;
case KEY_MENU:
BEAM_KeyMenu();
break;
case KEY_EXIT:
BEAM_KeyExit();
return;
case KEY_PTT:
break;
default:
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
gUpdateDisplay = true;
}
void BEAM_StorePacket(void)
{
if (gFSKWriteIndex < 36)
return;
gBeamRxWordCount = gFSKWriteIndex;
gFSKWriteIndex = 0;
const uint16_t Status = BK4819_ReadRegister(BK4819_REG_0B);
BK4819_PrepareFSKReceive();
if ((Status & 0x0010U) != 0 || g_FSK_Buffer[0] != 0xABCDu || g_FSK_Buffer[35] != 0xDCBAu)
goto error;
AIRCOPY_Obfuscate(32);
if (g_FSK_Buffer[34] != CRC_Calculate(&g_FSK_Buffer[1], 2 + 64))
goto error;
BEAM_Payload_t * const payload = (BEAM_Payload_t *)&g_FSK_Buffer[2];
if (payload->magic != BEAM_PACKET_MAGIC || payload->version != BEAM_PACKET_VERSION)
goto error;
BEAM_SavePayloadToFirstFreeChannel(payload);
BK4819_ResetFSK();
return;
error:
gBeamStatus = BEAM_STATUS_ERROR;
gBeamRxWordCount = 0;
gUpdateDisplay = true;
BACKLIGHT_TurnOn();
}
#endif // ENABLE_FEAT_F4HWN_BEAM
+53
View File
@@ -0,0 +1,53 @@
/* 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_BEAM_H
#define APP_BEAM_H
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include <stdbool.h>
#include <stdint.h>
#include "driver/keyboard.h"
typedef enum {
BEAM_MODE_TX = 0,
BEAM_MODE_RX
} BEAM_Mode_t;
typedef enum {
BEAM_STATUS_READY = 0,
BEAM_STATUS_TX_DONE,
BEAM_STATUS_RX_WAIT,
BEAM_STATUS_RX_SAVED,
BEAM_STATUS_RX_FULL,
BEAM_STATUS_ERROR,
BEAM_STATUS_TX_WAIT
} BEAM_Status_t;
extern BEAM_Mode_t gBeamMode;
extern BEAM_Status_t gBeamStatus;
extern uint16_t gBeamCopiedChannel;
extern uint8_t gBeamRxWordCount;
extern bool gBeamActive;
void ACTION_Beam(void);
void BEAM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
void BEAM_StorePacket(void);
#endif // ENABLE_FEAT_F4HWN_BEAM
#endif // APP_BEAM_H
+104 -208
View File
@@ -25,9 +25,8 @@ static uint8_t blockAnim = 0;
static bool isInitialized = false;
bool isPaused = false;
bool isBeep = false;
uint8_t levelCountBreackout = 1;
uint8_t levelCountBreakout = 1;
uint16_t tone = 0;
uint16_t score = 0;
@@ -42,115 +41,7 @@ Brick brick[BRICK_NUMBER];
Racket racket;
Ball ball;
const uint8_t BITMAP_block[4][15] =
{
{
0b00011110,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00011110,
},
{
0b00011110,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00011110,
},
{
0b00011110,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00011110,
},
{
0b00011110,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00110001,
0b00101001,
0b00100101,
0b00100011,
0b00011110,
}
};
const uint8_t BITMAP_blockOn[15] =
{
0b00011110,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00111111,
0b00011110,
};
const uint8_t BITMAP_blockEmpty[15] =
{
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
};
static const uint8_t BRICK_ANIM_PATTERNS[4] = {0b00110001, 0b00101001, 0b00100101, 0b00100011};
// Initialise seed
void srand_custom(uint32_t seed) {
@@ -172,18 +63,16 @@ int randInt(int min, int max) {
void reset(void)
{
ballCount = BALL_NUMBER;
levelCountBreackout = 1;
levelCountBreakout = 1;
score = 0;
}
// PlayBeep
void playBeep(uint16_t tone)
{
BK4819_PlayTone(tone, true); // 500 Hz ON
BK4819_PrepareToPlayTone(true);
AUDIO_AudioPathOn();
BK4819_ExitTxMute();
SYSTEM_DelayMs(100);
BK4819_EnterTxMute();
BK4819_PlayToneRaw(tone, 100);
AUDIO_AudioPathOff();
}
@@ -191,10 +80,10 @@ void playBeep(uint16_t tone)
void drawScore()
{
// Clean status line
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
// Level
sprintf(str, "Level %02u", levelCountBreackout);
sprintf(str, "Level %02u", levelCountBreakout);
GUI_DisplaySmallest(str, 0, 1, true, true);
// Ball
@@ -206,6 +95,12 @@ void drawScore()
GUI_DisplaySmallest(str, 88, 1, true, true);
}
// Render the ball
void renderBall(bool state) {
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w - 1, ball.y + ball.h - 1, state);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, state);
}
// Init ball
void initBall() {
ball.x = 62;
@@ -215,21 +110,25 @@ void initBall() {
ball.dx = 0;
ball.dy = 1;
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w -1, ball.y + ball.h - 1, true);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, true);
renderBall(true);
}
// Calculate the direction of the bounced ball
void directionBall(int16_t x, uint8_t w, int8_t num) {
ball.dx = map(x + w - ball.x, 0, w, num, -num);
ball.dy *= -1;
}
// Draw ball
void drawBall() {
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w -1, ball.y + ball.h -1, false);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, false);
renderBall(false);
ball.x += ball.dx;
ball.y += ball.dy;
if (ball.y <= 0) // Up
{
ball.dx = map(randInt(0, 7), 0, 7, 3, -3);
ball.dx = randInt(-3, 3);
ball.dy = 1;
}
else if (ball.x <= 2) // Left
@@ -243,9 +142,7 @@ void drawBall() {
// And now Down...
if (ball.y == 47) {
if (ball.x + 1 >= racket.x && ball.x - 1 <= racket.x + racket.w) {
ball.dx = map(racket.x + racket.w - ball.x, 0, racket.w, 3, -3);
ball.dy *= -1;
isBeep = true;
directionBall(racket.x, racket.w, 3);
tone = 400;
}
}
@@ -253,7 +150,6 @@ void drawBall() {
ballCount--;
UI_DisplayClear();
drawScore();
isBeep = true;
tone = 800;
if (ballCount < 0) {
reset();
@@ -261,15 +157,13 @@ void drawBall() {
drawWall();
isPaused = true;
UI_PrintStringSmallBold("GAME", 32, 0, 4);
UI_PrintStringSmallBold("OVER", 66, 0, 4);
UI_PrintStringSmallBold("GAME OVER", 0, LCD_WIDTH - 1, 4);
}
initRacket();
initBall();
}
UI_DrawRectangleBuffer(gFrameBuffer, ball.x, ball.y, ball.x + ball.w -1, ball.y + ball.h - 1, true);
UI_DrawLineBuffer(gFrameBuffer, ball.x - 1, ball.y + 1, ball.x + ball.w, ball.y + 1, true);
renderBall(true);
}
// Init wall
@@ -289,7 +183,6 @@ void initWall() {
brick[i].y = -8 + 8 * k;
brick[i].w = 14;
brick[i].h = 5;
brick[i].s = randInt(0, 3);
brick[i].destroy = false;
j++;
@@ -302,6 +195,11 @@ void drawWall() {
for (i = 0; i < BRICK_NUMBER; i++) {
if (brick[i].destroy == false) {
uint8_t *fb_ptr = gFrameBuffer[brick[i].y / 8] + brick[i].x;
fb_ptr[0] = 0b00011110;
fb_ptr[14] = 0b00011110;
if ((ball.x + 1 >= brick[i].x &&
ball.x - 1 <= brick[i].x + brick[i].w) &&
((ball.y + 1 >= brick[i].y &&
@@ -309,32 +207,37 @@ void drawWall() {
brick[i].destroy = true;
score++;
ball.dx = map(brick[i].x + brick[i].w - ball.x, 0, brick[i].w, 2, -2);
ball.dy *= -1;
directionBall(brick[i].x, brick[i].w, 2);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, true);
memcpy(gFrameBuffer[brick[i].y / 8] + brick[i].x, BITMAP_blockOn, sizeof(BITMAP_blockOn));
memset(fb_ptr + 1, 0b00111111, 13);
ST7565_BlitLine(brick[i].y / 8);
playBeep(600);
memcpy(gFrameBuffer[brick[i].y / 8] + brick[i].x, BITMAP_blockEmpty, sizeof(BITMAP_blockEmpty));
memset(fb_ptr + 0, 0b00000000, 15);
ST7565_BlitLine(brick[i].y / 8);
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
if (score % BRICK_NUMBER == 0) {
levelCountBreackout++;
levelCountBreakout++;
ballCount++;
initWall();
return;
}
}
if (brick[i].destroy == false) {
memcpy(gFrameBuffer[brick[i].y / 8] + brick[i].x, BITMAP_block[(brick[i].s + blockAnim) % 4], sizeof(BITMAP_block[(brick[i].s + blockAnim) % 4]));
} else {
for(uint8_t k = 0; k < 13; k++) {
fb_ptr[k + 1] = BRICK_ANIM_PATTERNS[(blockAnim + k) % 4];
}
}
}
}
}
// Render the racket shape
void renderRacket(int x, bool state) {
UI_DrawRectangleBuffer(gFrameBuffer, x + 1, racket.y, x + racket.w - 2, racket.y + racket.h, state);
UI_DrawLineBuffer(gFrameBuffer, x, racket.y + 1, x + racket.w - 1, racket.y + 1, state);
}
// Init racket
void initRacket() {
racket.w = 24;
@@ -343,18 +246,15 @@ void initRacket() {
racket.h = 2;
racket.p = racket.x;
UI_DrawRectangleBuffer(gFrameBuffer, racket.x + 1, racket.y, racket.x + racket.w - 2, racket.y + racket.h, true);
UI_DrawLineBuffer(gFrameBuffer, racket.x, racket.y + 1, racket.x + racket.w - 1, racket.y + 1, true);
renderRacket(racket.x, true);
}
// Draw racket
void drawRacket() {
if (racket.p != racket.x) {
UI_DrawRectangleBuffer(gFrameBuffer, racket.p + 1, racket.y, racket.p + racket.w - 2, racket.y + racket.h, false);
UI_DrawLineBuffer(gFrameBuffer, racket.p, racket.y + 1, racket.p + racket.w - 1, racket.y + 1, false);
renderRacket(racket.p, false);
racket.p = racket.x;
UI_DrawRectangleBuffer(gFrameBuffer, racket.x + 1, racket.y, racket.x + racket.w - 2, racket.y + racket.h, true);
UI_DrawLineBuffer(gFrameBuffer, racket.x, racket.y + 1, racket.x + racket.w - 1, racket.y + 1, true);
renderRacket(racket.x, true);
}
}
@@ -382,7 +282,7 @@ static void OnKeyDown(uint8_t key)
kbd.counter = 0;
if(isPaused)
{
UI_PrintStringSmallBold("PAUSE", 0, 128, 4);
UI_PrintStringSmallBold("PAUSE", 0, LCD_WIDTH - 1, 4);
}
break;
case KEY_EXIT:
@@ -416,17 +316,13 @@ static bool HandleUserInput()
if (kbd.current == KEY_INVALID)
return true;
// Movement keys: dispatch on every tick while held
if (kbd.current == KEY_UP || kbd.current == KEY_DOWN ||
kbd.current == KEY_4 || kbd.current == KEY_0)
{
OnKeyDown(kbd.current);
return true;
}
// Action keys (MENU, EXIT): dispatch only on rising edge
if (kbd.current != kbd.prev)
{
if (
// Movement keys: dispatch on every tick while held
(kbd.current == KEY_UP || kbd.current == KEY_DOWN ||
kbd.current == KEY_4 || kbd.current == KEY_0) ||
// Action keys (MENU, EXIT): dispatch only on rising edge
(kbd.current != kbd.prev)
){
OnKeyDown(kbd.current);
}
@@ -441,62 +337,62 @@ static void Tick()
// APP_RunBreakout
void APP_RunBreakout(void) {
static uint8_t swap = 0;
static uint8_t swap = 0;
// Init seed
srand_custom(BK4819_ReadRegister(BK4819_REG_67) & 0x01FF * gBatteryVoltageAverage * gEeprom.VfoInfo[0].pRX->Frequency);
// Init seed
srand_custom(BK4819_ReadRegister(BK4819_REG_67) & 0x01FF * gBatteryVoltageAverage * gEeprom.VfoInfo[0].pRX->Frequency);
// Init led
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
// Init led
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
// Finish the brightness fade if it is in progress
BACKLIGHT_UpdateTickless();
// Finish the brightness fade if it is in progress
BACKLIGHT_UpdateTickless();
// Init game
UI_DisplayClear();
reset();
initWall();
initRacket();
initBall();
memset(gStatusLine, 0, sizeof(gStatusLine));
isInitialized = true;
// Init game
UI_DisplayClear();
reset();
initWall();
initRacket();
initBall();
UI_StatusClear();
isInitialized = true;
while(isInitialized)
while(isInitialized)
{
Tick();
if(!isPaused)
{
Tick();
if(!isPaused)
if(swap == 0)
{
if(swap == 0)
{
blockAnim = (blockAnim + 1) % 4;
}
swap = (swap + 1) % 4;
drawScore();
drawWall();
drawRacket();
drawBall();
if(isBeep)
{
playBeep(tone);
isBeep = false;
}
else
{
SYSTEM_DelayMs(40 - MIN(levelCountBreackout - 1, 20)); // Add more fun...
}
blockAnim = (blockAnim + 1) % 4;
}
swap = (swap + 1) % 4;
ST7565_BlitStatusLine(); // Blank status line
ST7565_BlitFullScreen();
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
if(isPaused || swap == 0)
{
SCREENSHOT_Update(false);
}
#endif
drawScore();
drawWall();
drawRacket();
drawBall();
if(tone != 0)
{
playBeep(tone);
tone = 0;
}
else
{
SYSTEM_DelayMs(40 - MIN(levelCountBreakout - 1, 20)); // Add more fun...
}
}
}
ST7565_BlitStatusLine(); // Blank status line
ST7565_BlitFullScreen();
#ifdef ENABLE_FEAT_F4HWN_SCREENSHOT
if(isPaused || swap == 0)
{
SCREENSHOT_Update(false);
}
#endif
}
}
-1
View File
@@ -49,7 +49,6 @@ typedef struct {
uint8_t y; // y
uint8_t w; // width
uint8_t h; // height
uint8_t s; // style
bool destroy; // active, if true, check this button, else bypass
} Brick;
+692 -6
View File
@@ -1,9 +1,16 @@
#include <stddef.h>
#include "app/app.h"
#include "app/chFrScanner.h"
#include "audio.h"
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
#include "driver/systick.h"
#endif
#include "functions.h"
#include "misc.h"
#include "settings.h"
#include "ui/main.h"
//#include "debugging.h"
int8_t gScanStateDir;
@@ -13,6 +20,22 @@ bool gScanPauseMode;
#ifdef ENABLE_SCAN_RANGES
uint32_t gScanRangeStart;
uint32_t gScanRangeStop;
#define SCAN_RANGE_SKIP_MAX 32
#if (SCAN_RANGE_SKIP_MAX & (SCAN_RANGE_SKIP_MAX - 1)) != 0
#error SCAN_RANGE_SKIP_MAX must be a power of two
#endif
typedef struct {
uint16_t sample[SCAN_RANGE_SKIP_MAX];
uint32_t start;
uint32_t stop;
uint16_t step;
uint8_t count;
uint8_t next;
} ScanRangeSkipList_t;
static ScanRangeSkipList_t scanRangeSkip;
#endif
typedef enum {
@@ -36,13 +59,566 @@ uint8_t initialCROSS_BAND_RX_TX;
static void NextFreqChannel(void);
static void NextMemChannel(void);
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
static void ScanFastResetState(void);
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
static bool ScanFastEnabled(void)
{
return gSetting_set_scn;
}
#endif
#ifdef ENABLE_SCAN_RANGES
static void ScanRangeSkipClear(void)
{
scanRangeSkip.count = 0;
scanRangeSkip.next = 0;
}
static void ScanRangeSkipSync(void)
{
const uint16_t step = gRxVfo->StepFrequency;
if (scanRangeSkip.start == gScanRangeStart &&
scanRangeSkip.stop == gScanRangeStop &&
scanRangeSkip.step == step)
{
return;
}
ScanRangeSkipClear();
scanRangeSkip.start = gScanRangeStart;
scanRangeSkip.stop = gScanRangeStop;
scanRangeSkip.step = step;
}
static uint16_t ScanRangeSkipSampleForFrequency(uint32_t frequency)
{
const uint16_t step = gRxVfo->StepFrequency;
if (!gScanRangeStart || step == 0 || frequency < gScanRangeStart || frequency > gScanRangeStop)
return 0;
const uint32_t sample = (frequency - gScanRangeStart) / step;
if (sample >= 0xFFFFu)
return 0;
return (uint16_t)(sample + 1);
}
static bool ScanRangeSkipContainsFrequency(uint32_t frequency)
{
const uint16_t sample = ScanRangeSkipSampleForFrequency(frequency);
if (sample == 0)
return false;
for (uint8_t i = 0; i < scanRangeSkip.count; ++i)
if (scanRangeSkip.sample[i] == sample)
return true;
return false;
}
static uint32_t ScanRangeNextFrequency(void)
{
uint32_t frequency;
uint8_t guard = SCAN_RANGE_SKIP_MAX + 1;
ScanRangeSkipSync();
do
{
frequency = APP_SetFreqByStepAndLimits(gRxVfo, gScanStateDir, gScanRangeStart, gScanRangeStop);
gRxVfo->freq_config_RX.Frequency = frequency;
} while (--guard && ScanRangeSkipContainsFrequency(frequency));
return frequency;
}
bool CHFRSCANNER_ExcludeCurrentScanRange(void)
{
ScanRangeSkipSync();
uint16_t sample = ScanRangeSkipSampleForFrequency(lastFoundFrqOrChan);
if (sample == 0)
sample = ScanRangeSkipSampleForFrequency(gRxVfo->freq_config_RX.Frequency);
if (sample == 0)
return false;
for (uint8_t i = 0; i < scanRangeSkip.count; ++i)
if (scanRangeSkip.sample[i] == sample)
return true;
scanRangeSkip.sample[scanRangeSkip.next] = sample;
scanRangeSkip.next = (scanRangeSkip.next + 1) & (SCAN_RANGE_SKIP_MAX - 1);
if (scanRangeSkip.count < SCAN_RANGE_SKIP_MAX)
scanRangeSkip.count++;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
ScanFastResetState();
#endif
return true;
}
bool CHFRSCANNER_HasScanRangeExcludedOrdinal(uint32_t first_ordinal, uint32_t last_ordinal)
{
if (first_ordinal == 0)
first_ordinal = 1;
if (last_ordinal < first_ordinal)
return false;
if (first_ordinal > 0xFFFFu)
return false;
if (last_ordinal > 0xFFFFu)
last_ordinal = 0xFFFFu;
for (uint8_t i = 0; i < scanRangeSkip.count; ++i) {
const uint16_t sample = scanRangeSkip.sample[i];
if (sample >= first_ordinal && sample <= last_ordinal)
return true;
}
return false;
}
#endif
static void CHFRSCANNER_AbortActiveReception(void)
{
if (!FUNCTION_IsRx())
return;
AUDIO_AudioPathOff();
gEnableSpeaker = false;
gMonitor = false;
gRxReceptionMode = RX_MODE_NONE;
gScanPauseMode = false;
FUNCTION_Init();
FUNCTION_Select(FUNCTION_FOREGROUND);
}
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
#define SCAN_FAST_PRECHECK_STEPS 6
#define SCAN_FAST_RSSI_MARGIN 16
#define SCAN_FAST_SQUELCH_MARGIN 8
#define SCAN_FAST_WEAK_MARGIN 8
#define SCAN_FAST_RECHECK_DELAY_US 350
#define SCAN_FAST_FINE_STEP_LIMIT 250
#define SCAN_FAST_FINE_REFINE_SPAN 1000
#define SCAN_FAST_FINE_REFINE_MAX 80
#define SCAN_FAST_FINE_RSSI_DROP 8
#define SCAN_FAST_RSSI_MAX 65535u
// Settle loop guard: at most this many 1us waits while the BK4819 glitch
// indicator stays above SCAN_FAST_GLITCH_THRESHOLD. Caps the worst-case
// settling time per step at ~50us before we read the RSSI anyway.
#define SCAN_FAST_GLITCH_GUARD_MAX 50
#define SCAN_FAST_GLITCH_THRESHOLD 200
// HF/VHF boundary in Hz: BK4819_PickRXFilterPathBasedOnFrequency() switches
// the front-end filter path here, so we only re-run that (relatively
// expensive) call when we actually cross the boundary.
#define SCAN_FAST_HF_VHF_BOUNDARY_HZ 28000000u
typedef enum {
SCAN_FAST_DISABLED,
SCAN_FAST_QUIET_BATCH,
SCAN_FAST_CANDIDATE
} scan_fast_result_t;
static uint16_t scanFastReg30;
static uint16_t scanFastNoiseFloor = SCAN_FAST_RSSI_MAX;
static uint32_t scanFastPrevFrequency;
static bool scanFastLastFullTuneCandidate;
static VFO_Info_t scanFastDisplayVfo;
static bool scanFastDisplayVfoValid;
static void ScanFastResetState(void)
{
// Called on every scan (re)start, after a reception, and on each
// wraparound to the start of the channel list / range. The noise
// floor is re-warmed up from current conditions instead of carrying
// stale calibration into a later sweep.
scanFastNoiseFloor = SCAN_FAST_RSSI_MAX;
scanFastPrevFrequency = 0;
scanFastLastFullTuneCandidate = false;
scanFastDisplayVfoValid = false;
}
static void ScanFastResetNoiseFloor(void)
{
scanFastNoiseFloor = SCAN_FAST_RSSI_MAX;
}
static uint16_t ScanFastReadRssi(void)
{
uint8_t guard = SCAN_FAST_GLITCH_GUARD_MAX;
while (guard-- && BK4819_GetGlitchIndicator() >= SCAN_FAST_GLITCH_THRESHOLD)
{
SYSTICK_DelayUs(1);
}
// Discard first read: after fast tuning the RSSI/AGC value may still be stale.
BK4819_GetRSSI();
return BK4819_GetRSSI();
}
static void ScanFastTune(uint32_t frequency)
{
if (scanFastPrevFrequency == 0 ||
((frequency < SCAN_FAST_HF_VHF_BOUNDARY_HZ) !=
(scanFastPrevFrequency < SCAN_FAST_HF_VHF_BOUNDARY_HZ)))
{
BK4819_PickRXFilterPathBasedOnFrequency(frequency);
}
scanFastPrevFrequency = frequency;
BK4819_SetFrequency(frequency);
BK4819_WriteRegister(BK4819_REG_30, 0);
BK4819_WriteRegister(BK4819_REG_30, scanFastReg30);
}
const VFO_Info_t *CHFRSCANNER_GetScanDisplayVfo(void)
{
if (!ScanFastEnabled() || !scanFastDisplayVfoValid || gScanStateDir == SCAN_OFF || FUNCTION_IsRx())
return NULL;
return &scanFastDisplayVfo;
}
static bool ScanFastUpdateDisplayVfo(uint16_t channel, uint32_t *frequency, ModulationMode_t *modulation)
{
ChannelScanDisplayInfo_t info;
if (!SETTINGS_FetchChannelScanDisplayInfo(channel, &info))
{
scanFastDisplayVfoValid = false;
return false;
}
scanFastDisplayVfo = gEeprom.VfoInfo[gEeprom.RX_VFO];
scanFastDisplayVfo.CHANNEL_SAVE = channel;
scanFastDisplayVfo.freq_config_RX = info.rx;
scanFastDisplayVfo.freq_config_TX = info.tx;
scanFastDisplayVfo.TX_OFFSET_FREQUENCY = info.offset;
scanFastDisplayVfo.StepFrequency = info.stepFrequency;
scanFastDisplayVfo.STEP_SETTING = info.stepSetting;
scanFastDisplayVfo.Modulation = info.modulation;
scanFastDisplayVfo.TX_OFFSET_FREQUENCY_DIRECTION = info.txOffsetFrequencyDirection;
scanFastDisplayVfo.OUTPUT_POWER = info.outputPower;
scanFastDisplayVfo.FrequencyReverse = info.frequencyReverse;
scanFastDisplayVfo.CHANNEL_BANDWIDTH = info.channelBandwidth;
scanFastDisplayVfo.BUSY_CHANNEL_LOCK = info.busyChannelLock;
scanFastDisplayVfo.TX_LOCK = info.txLock;
#ifdef ENABLE_DTMF_CALLING
scanFastDisplayVfo.DTMF_DECODING_ENABLE = info.dtmfDecodingEnable;
#endif
scanFastDisplayVfo.DTMF_PTT_ID_TX_MODE = info.dtmfPttIdTxMode;
if (!scanFastDisplayVfo.FrequencyReverse)
{
scanFastDisplayVfo.pRX = &scanFastDisplayVfo.freq_config_RX;
scanFastDisplayVfo.pTX = &scanFastDisplayVfo.freq_config_TX;
}
else
{
scanFastDisplayVfo.pRX = &scanFastDisplayVfo.freq_config_TX;
scanFastDisplayVfo.pTX = &scanFastDisplayVfo.freq_config_RX;
}
scanFastDisplayVfoValid = true;
if (frequency)
*frequency = info.rx.Frequency;
if (modulation)
*modulation = info.modulation;
return true;
}
static uint16_t ScanFastSaturatingAdd(uint16_t value, uint16_t add)
{
return (value > SCAN_FAST_RSSI_MAX - add) ? SCAN_FAST_RSSI_MAX : (uint16_t)(value + add);
}
static uint16_t ScanFastSaturatingSub(uint16_t value, uint16_t sub)
{
return (value > sub) ? (uint16_t)(value - sub) : 0;
}
static uint16_t ScanFastGetNoiseTrigger(void)
{
return ScanFastSaturatingAdd(scanFastNoiseFloor, SCAN_FAST_RSSI_MARGIN);
}
static uint16_t ScanFastGetSquelchTrigger(void)
{
return ScanFastSaturatingSub(gRxVfo->SquelchOpenRSSIThresh, SCAN_FAST_SQUELCH_MARGIN);
}
static bool ScanFastIsNearCandidate(uint16_t rssi)
{
const uint16_t rssiWithMargin = ScanFastSaturatingAdd(rssi, SCAN_FAST_WEAK_MARGIN);
const uint16_t squelchTrigger = ScanFastGetSquelchTrigger();
if (scanFastNoiseFloor == SCAN_FAST_RSSI_MAX)
return gRxVfo->SquelchOpenRSSIThresh > 0 &&
rssiWithMargin >= gRxVfo->SquelchOpenRSSIThresh;
return rssiWithMargin >= ScanFastGetNoiseTrigger() &&
rssiWithMargin >= squelchTrigger;
}
static uint16_t ScanFastReadCandidateRssi(void)
{
uint16_t rssi = ScanFastReadRssi();
if (ScanFastIsNearCandidate(rssi))
{
SYSTICK_DelayUs(SCAN_FAST_RECHECK_DELAY_US);
const uint16_t retryRssi = ScanFastReadRssi();
if (retryRssi > rssi)
rssi = retryRssi;
}
return rssi;
}
static bool ScanFastIsCandidate(uint16_t rssi)
{
const uint16_t squelchTrigger = ScanFastGetSquelchTrigger();
if (scanFastNoiseFloor == SCAN_FAST_RSSI_MAX)
{
scanFastNoiseFloor = rssi;
return gRxVfo->SquelchOpenRSSIThresh > 0 &&
rssi >= gRxVfo->SquelchOpenRSSIThresh;
}
const uint16_t noiseTrigger = ScanFastGetNoiseTrigger();
const uint16_t rssiWithMargin = ScanFastSaturatingAdd(rssi, SCAN_FAST_WEAK_MARGIN);
if ((rssi >= noiseTrigger && rssi >= squelchTrigger) ||
(rssiWithMargin >= noiseTrigger && rssiWithMargin >= squelchTrigger))
{
return true;
}
if (rssi < scanFastNoiseFloor)
scanFastNoiseFloor = rssi;
else
scanFastNoiseFloor = (uint16_t)((7u * scanFastNoiseFloor + rssi + 4u) >> 3);
return false;
}
static void ScanFastApplyChannelShape(ModulationMode_t modulation)
{
const bool modulationChanged = gRxVfo->Modulation != modulation;
const bool bandwidthChanged = gRxVfo->CHANNEL_BANDWIDTH != BANDWIDTH_WIDE;
if (!modulationChanged && !bandwidthChanged)
return;
gRxVfo->Modulation = modulation;
gRxVfo->CHANNEL_BANDWIDTH = BANDWIDTH_WIDE;
if (modulationChanged)
RADIO_SetModulation(modulation);
if (modulation == MODULATION_AM)
{
BK4819_SetFilterBandwidth(RADIO_GetAMFilterBandwidth(gRxVfo), true);
}
else
{
#ifdef ENABLE_AM_FIX
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_WIDE, true);
#else
BK4819_SetFilterBandwidth(BK4819_FILTER_BW_WIDE, false);
#endif
}
if (modulationChanged)
{
// AM and FM use different demod/AGC profiles, so their RSSI
// baselines are not directly comparable. Relearn the floor after
// crossing that boundary instead of treating the next FM block as
// a wall of candidates.
ScanFastResetNoiseFloor();
}
}
#endif
#if defined(ENABLE_FEAT_F4HWN_SCAN_FASTER) && defined(ENABLE_SCAN_RANGES)
static void ScanRangeFastRefineCandidate(uint16_t firstRssi)
{
const uint16_t step = gRxVfo->StepFrequency;
if (step == 0 || step >= SCAN_FAST_FINE_STEP_LIMIT)
return;
uint16_t maxSteps = SCAN_FAST_FINE_REFINE_SPAN / step;
if (maxSteps == 0)
maxSteps = 1;
if (maxSteps > SCAN_FAST_FINE_REFINE_MAX)
maxSteps = SCAN_FAST_FINE_REFINE_MAX;
uint16_t bestRssi = firstRssi;
uint32_t bestFrequency = gRxVfo->freq_config_RX.Frequency;
uint8_t fallingSteps = 0;
for (uint16_t i = 0; i < maxSteps; ++i)
{
const uint32_t prevRxFrequency = gRxVfo->pRX->Frequency;
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
RADIO_ApplyOffset(gRxVfo);
const uint32_t freq = gRxVfo->pRX->Frequency;
if ((gScanStateDir > 0 && freq < prevRxFrequency) ||
(gScanStateDir < 0 && freq > prevRxFrequency))
{
break;
}
ScanFastTune(freq);
const uint16_t rssi = ScanFastReadRssi();
if (rssi > bestRssi)
{
bestRssi = rssi;
bestFrequency = gRxVfo->freq_config_RX.Frequency;
fallingSteps = 0;
}
else if (bestRssi > rssi && bestRssi - rssi >= SCAN_FAST_FINE_RSSI_DROP)
{
if (++fallingSteps >= 3)
break;
}
}
gRxVfo->freq_config_RX.Frequency = bestFrequency;
RADIO_ApplyOffset(gRxVfo);
ScanFastTune(gRxVfo->pRX->Frequency);
}
static scan_fast_result_t ScanRangeFastPrecheck(void)
{
if (!gScanRangeStart)
return SCAN_FAST_DISABLED;
if (gRxVfo->SquelchOpenRSSIThresh == 0)
return SCAN_FAST_DISABLED;
// Mute AF DAC during the precheck sweep: avoids audio glitches and
// saves a few uA on each silent step. The bit is restored on the real
// tune by RADIO_SetupRegisters() in NextFreqChannel().
scanFastReg30 = BK4819_ReadRegister(BK4819_REG_30) & ~BK4819_REG_30_MASK_ENABLE_AF_DAC;
for (uint8_t i = 0; i < SCAN_FAST_PRECHECK_STEPS; ++i)
{
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
RADIO_ApplyOffset(gRxVfo);
const uint32_t freq = gRxVfo->pRX->Frequency;
// Detect wraparound: scanning forward but the new freq is lower
// than the previous one (or scanning backward but it's higher)
// means the range has wrapped from stop back to start. Reset the
// noise floor so the new pass re-warms up from current conditions.
if (scanFastPrevFrequency != 0 &&
((gScanStateDir > 0 && freq < scanFastPrevFrequency) ||
(gScanStateDir < 0 && freq > scanFastPrevFrequency)))
{
ScanFastResetState();
}
ScanFastTune(freq);
const uint16_t rssi = ScanFastReadCandidateRssi();
if (ScanFastIsCandidate(rssi))
{
ScanRangeFastRefineCandidate(rssi);
return SCAN_FAST_CANDIDATE;
}
}
return SCAN_FAST_QUIET_BATCH;
}
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
static bool MemChannelFastPrecheck(uint16_t channel)
{
uint32_t frequency;
ModulationMode_t modulation;
if (gRxVfo->SquelchOpenRSSIThresh == 0)
{
scanFastLastFullTuneCandidate = false;
return true;
}
if (!ScanFastUpdateDisplayVfo(channel, &frequency, &modulation))
{
scanFastLastFullTuneCandidate = false;
return true;
}
ScanFastApplyChannelShape(modulation);
// Mute AF DAC for the same reason as in ScanRangeFastPrecheck().
scanFastReg30 = BK4819_ReadRegister(BK4819_REG_30) & ~BK4819_REG_30_MASK_ENABLE_AF_DAC;
ScanFastTune(frequency);
if (ScanFastIsCandidate(ScanFastReadCandidateRssi()))
{
scanFastLastFullTuneCandidate = true;
return true; // signal detected: let the full tune path follow
}
// No signal here: still mirror the probed frequency in the VFO so the
// status line (channel name + frequency) keeps in sync as we skip.
// RADIO_ConfigureChannel() will overwrite these values cleanly when a
// candidate is eventually retained.
scanFastLastFullTuneCandidate = false;
gRxVfo->freq_config_RX.Frequency = frequency;
return false;
}
static void AdvanceMemScanList(const bool enabled)
{
if (enabled)
if (++currentScanList >= SCAN_NEXT_NUM)
currentScanList = SCAN_NEXT_CHAN_SCANLIST1;
}
static void SetMemScanProgressChannel(uint16_t channel)
{
gEeprom.MrChannel[ gEeprom.RX_VFO] = channel;
gEeprom.ScreenChannel[gEeprom.RX_VFO] = channel;
gRxVfo->CHANNEL_SAVE = channel;
}
#endif
#if defined(ENABLE_FEAT_F4HWN_RESUME_STATE) || defined(ENABLE_SCAN_RANGES)
void CHFRSCANNER_ScanRange(void) {
gScanRangeStart = gScanRangeStart ? 0 : gTxVfo->pRX->Frequency;
if (gScanRangeStart) {
gScanRangeStart = 0;
return;
}
gScanRangeStart = gTxVfo->pRX->Frequency;
gScanRangeStop = gEeprom.VfoInfo[!gEeprom.TX_VFO].freq_config_RX.Frequency;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
ScanFastResetState();
#endif
if(gScanRangeStart > gScanRangeStop)
SWAP(gScanRangeStart, gScanRangeStop);
ScanRangeSkipSync();
}
#endif
@@ -55,16 +631,21 @@ void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_directi
}
RADIO_SelectVfos();
CHFRSCANNER_AbortActiveReception();
gNextMrChannel = gRxVfo->CHANNEL_SAVE;
currentScanList = SCAN_NEXT_CHAN_SCANLIST1;
gScanStateDir = scan_direction;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
ScanFastResetState();
#endif
if (IS_MR_CHANNEL(gNextMrChannel))
{
if(!RADIO_CheckValidList(gEeprom.SCAN_LIST_DEFAULT)) {
RADIO_NextValidList(1);
UI_MAIN_NotifyScanProgressDataChanged();
}
// channel mode
@@ -93,6 +674,16 @@ void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_directi
gScanPauseMode = false;
}
void CHFRSCANNER_ManualResume(const int8_t scan_direction)
{
CHFRSCANNER_Start(false, scan_direction);
gScanPauseDelayIn_10ms = (gRxVfo->SquelchOpenRSSIThresh == 0)
? scan_pause_delay_in_3_10ms
: 1;
gScheduleScanListen = false;
}
/*
void CHFRSCANNER_ContinueScanning(void)
{
@@ -119,6 +710,18 @@ void CHFRSCANNER_ContinueScanning(void)
void CHFRSCANNER_ContinueScanning(void)
{
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
if (scanFastLastFullTuneCandidate &&
gCurrentFunction != FUNCTION_INCOMING &&
!g_SquelchLost)
{
// A rejected full-tune candidate is just a false RSSI hit. Keep the
// learned floor; resetting here can make the next channel blind when
// it is the real signal, especially while scanning down.
scanFastLastFullTuneCandidate = false;
}
#endif
if (gCurrentFunction == FUNCTION_INCOMING &&
(IS_FREQ_CHANNEL(gNextMrChannel) || gCurrentCodeType == CODE_TYPE_OFF))
{
@@ -136,6 +739,15 @@ void CHFRSCANNER_ContinueScanning(void)
void CHFRSCANNER_Found(void)
{
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
// After a real reception the BK4819 AGC has shifted, biasing the next
// few RSSI readings high. Reset the precheck state so it warms up from
// the current noise floor instead of carrying stale calibration into
// the post-reception scan, which otherwise turns nearly every channel
// into a CANDIDATE and erases the speed gain.
ScanFastResetState();
#endif
if (gEeprom.SCAN_RESUME_MODE > 80) {
if (!gScanPauseMode) {
gScanPauseDelayIn_10ms = scan_pause_delay_in_5_10ms * (gEeprom.SCAN_RESUME_MODE - 80) * 5;
@@ -243,11 +855,46 @@ static void NextFreqChannel(void)
{
#ifdef ENABLE_SCAN_RANGES
if(gScanRangeStart) {
gRxVfo->freq_config_RX.Frequency = APP_SetFreqByStepAndLimits(gRxVfo, gScanStateDir, gScanRangeStart, gScanRangeStop);
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
if (ScanFastEnabled())
{
const scan_fast_result_t fastResult = ScanRangeFastPrecheck();
if (fastResult == SCAN_FAST_QUIET_BATCH)
{
scanFastLastFullTuneCandidate = false;
gScanPauseDelayIn_10ms = 1;
gUpdateDisplay = true;
return;
}
if (fastResult == SCAN_FAST_DISABLED)
{
scanFastLastFullTuneCandidate = false;
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
}
else
{
scanFastLastFullTuneCandidate = true;
}
}
else
{
scanFastLastFullTuneCandidate = false;
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
}
#else
gRxVfo->freq_config_RX.Frequency = ScanRangeNextFrequency();
#endif
}
else
#endif
{
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
scanFastLastFullTuneCandidate = false;
#endif
gRxVfo->freq_config_RX.Frequency = APP_SetFrequencyByStep(gRxVfo, gScanStateDir);
}
RADIO_ApplyOffset(gRxVfo);
RADIO_ConfigureSquelchAndOutputPower(gRxVfo);
@@ -348,23 +995,58 @@ static void NextMemChannel(void)
}
if (!enabled || chan == 0xFFFF)
{
{
const uint16_t searchStart = gNextMrChannel;
chan = RADIO_FindNextChannel(gNextMrChannel + gScanStateDir, gScanStateDir, true, gEeprom.SCAN_LIST_DEFAULT);
if (chan == 0xFFFF)
{ // no valid channel found
{ // no valid channel found -> wrapping back to the first channel
chan = MR_CHANNEL_FIRST;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
// Wraparound: re-warm the precheck noise floor on the new pass
// so it tracks current RF conditions instead of an EMA that
// accumulated drift over the previous full sweep.
ScanFastResetState();
#endif
}
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
else if ((gScanStateDir > 0 && chan < searchStart) ||
(gScanStateDir < 0 && chan > searchStart))
{
// RADIO_FindNextChannel() wraps internally, so 0xFFFF is not
// returned on a normal full-sweep wrap. Detect that transition
// here and restart the RSSI floor learning for the new pass.
ScanFastResetState();
}
#endif
gNextMrChannel = chan;
//sprintf(str, "----> Chan %d\n", chan + 1);
//LogUart(str);
}
if (gNextMrChannel != prev_chan)
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
SetMemScanProgressChannel(gNextMrChannel);
if (ScanFastEnabled() && !MemChannelFastPrecheck(gNextMrChannel))
{
gScanPauseDelayIn_10ms = 1;
gUpdateDisplay = true;
AdvanceMemScanList(enabled);
return;
}
#endif
if (gNextMrChannel != prev_chan
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|| scanFastLastFullTuneCandidate
#endif
)
{
#ifndef ENABLE_FEAT_F4HWN_SCAN_FASTER
gEeprom.MrChannel[ gEeprom.RX_VFO] = gNextMrChannel;
gEeprom.ScreenChannel[gEeprom.RX_VFO] = gNextMrChannel;
#endif
RADIO_ConfigureChannel(gEeprom.RX_VFO, VFO_CONFIGURE_RELOAD);
RADIO_SetupRegisters(true);
@@ -378,7 +1060,11 @@ static void NextMemChannel(void)
gScanPauseDelayIn_10ms = scan_pause_delay_in_3_10ms;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
AdvanceMemScanList(enabled);
#else
if (enabled)
if (++currentScanList >= SCAN_NEXT_NUM)
currentScanList = SCAN_NEXT_CHAN_SCANLIST1; // back round we go
#endif
}
+9 -1
View File
@@ -4,6 +4,8 @@
#include <stdbool.h>
#include <stdint.h>
#include "radio.h"
// scan direction, if not equal SCAN_OFF indicates
// that we are in a process of scanning channels/frequencies
extern int8_t gScanStateDir;
@@ -13,12 +15,18 @@ extern bool gScanPauseMode;
#ifdef ENABLE_SCAN_RANGES
extern uint32_t gScanRangeStart;
extern uint32_t gScanRangeStop;
bool CHFRSCANNER_ExcludeCurrentScanRange(void);
bool CHFRSCANNER_HasScanRangeExcludedOrdinal(uint32_t first_ordinal, uint32_t last_ordinal);
#endif
void CHFRSCANNER_Found(void);
void CHFRSCANNER_Stop(void);
void CHFRSCANNER_Start(const bool storeBackupSettings, const int8_t scan_direction);
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);
#endif
#if defined(ENABLE_FEAT_F4HWN_RESUME_STATE) || defined(ENABLE_SCAN_RANGES)
void CHFRSCANNER_ScanRange(void);
@@ -29,4 +37,4 @@ void CHFRSCANNER_ContinueScanning(void);
extern uint32_t lastFoundFrqOrChanOld;
#endif
#endif
#endif
+4
View File
@@ -212,6 +212,10 @@ DTMF_CallMode_t DTMF_CheckGroupCall(const char *pMsg, const unsigned int size)
}
#endif
void DTMF_clear_input_box_memory() {
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
}
void DTMF_clear_input_box(void)
{
memset(gDTMF_InputBox, 0, sizeof(gDTMF_InputBox));
+1
View File
@@ -82,6 +82,7 @@ extern DTMF_ReplyState_t gDTMF_ReplyState;
bool DTMF_ValidateCodes(char *pCode, const unsigned int size);
char DTMF_GetCharacter(const unsigned int code);
void DTMF_clear_input_box_memory(void);
void DTMF_clear_input_box(void);
void DTMF_Append(const char code);
void DTMF_Reply(void);
+53 -45
View File
@@ -262,34 +262,51 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
break;
case KEY_7:
#ifdef ENABLE_VOX
ACTION_Vox();
//#else
// toggle_chan_scanlist();
#ifdef ENABLE_FEAT_F4HWN_GAME
if (!beep) {
APP_RunBreakout();
} else {
#endif
#ifdef ENABLE_VOX
ACTION_Vox();
//#else
// toggle_chan_scanlist();
#endif
#ifdef ENABLE_FEAT_F4HWN_GAME
}
#endif
break;
case KEY_8:
gTxVfo->FrequencyReverse = gTxVfo->FrequencyReverse == false;
gRequestSaveChannel = 1;
if (!beep) {
ACTION_BackLightOnDemand();
}
else {
gTxVfo->FrequencyReverse = gTxVfo->FrequencyReverse == false;
gRequestSaveChannel = 1;
}
break;
case KEY_9:
if (RADIO_CheckValidChannel(gEeprom.CHAN_1_CALL, false, 0)) {
gEeprom.MrChannel[Vfo] = gEeprom.CHAN_1_CALL;
gEeprom.ScreenChannel[Vfo] = gEeprom.CHAN_1_CALL;
if (!beep) {
ACTION_BackLight();
}
else {
if (RADIO_CheckValidChannel(gEeprom.CHAN_1_CALL, false, 0)) {
gEeprom.MrChannel[Vfo] = gEeprom.CHAN_1_CALL;
gEeprom.ScreenChannel[Vfo] = gEeprom.CHAN_1_CALL;
#ifdef ENABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_CHANNEL_MODE);
AUDIO_SetDigitVoice(1, gEeprom.CHAN_1_CALL + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
AUDIO_SetVoiceID(0, VOICE_ID_CHANNEL_MODE);
AUDIO_SetDigitVoice(1, gEeprom.CHAN_1_CALL + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
#endif
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
break;
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
}
}
if (beep)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
#ifdef ENABLE_FEAT_F4HWN // Set Squelch F + UP or Down and Step F + SIDE1 or F + SIDE2
@@ -300,6 +317,7 @@ static void processFKeyFunction(const KEY_Code_t Key, const bool beep)
if (gScanStateDir != SCAN_OFF) {
RADIO_NextValidList(isKeyUp ? 1 : -1);
UI_MAIN_NotifyScanProgressDataChanged();
} else {
// Adjust squelch: UP increments, DOWN decrements
if (gSquelchLevelOriginal == 10)
@@ -482,6 +500,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();
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
SETTINGS_WriteCurrentState();
#endif
@@ -500,6 +519,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();
#ifdef ENABLE_FEAT_F4HWN_RESUME_STATE
SETTINGS_WriteCurrentState();
@@ -652,30 +672,6 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
HideFKeyIcon();
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
if(gEeprom.MENU_LOCK == true && Key != 2) {
return;
}
#endif
if(Key == 8)
{
ACTION_BackLightOnDemand();
return;
}
else if(Key == 9)
{
ACTION_BackLight();
return;
}
#ifdef ENABLE_FEAT_F4HWN_GAME
else if(Key == 7)
{
APP_RunBreakout();
return;
}
#endif
processFKeyFunction(Key, false);
}
@@ -756,11 +752,25 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld)
if (bKeyPressed) { // long press MENU key
#ifdef ENABLE_FEAT_F4HWN
// Exclude channel
// Exclude current scan entry
if(gScanStateDir != SCAN_OFF)
{
if(FUNCTION_IsRx() || gScanPauseDelayIn_10ms > 9)
{
#ifdef ENABLE_SCAN_RANGES
if(gScanRangeStart && !IS_MR_CHANNEL(gNextMrChannel))
{
if(CHFRSCANNER_ExcludeCurrentScanRange())
{
UI_MAIN_NotifyScanProgressDataChanged();
lastFoundFrqOrChan = lastFoundFrqOrChanOld;
CHFRSCANNER_ContinueScanning();
}
return;
}
#endif
ChannelAttributes_t *att = MR_GetChannelAttributes(lastFoundFrqOrChan);
att->exclude = true;
@@ -1010,9 +1020,7 @@ static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
}
// jump to the next channel
CHFRSCANNER_Start(false, Direction);
gScanPauseDelayIn_10ms = 1;
gScheduleScanListen = false;
CHFRSCANNER_ManualResume(Direction);
gPttWasReleased = true;
}
+41 -24
View File
@@ -426,6 +426,12 @@ int MENU_GetLimits(uint8_t menu_id, int32_t *pMin, int32_t *pMax)
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SET_MET) - 1;
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
//*pMin = 0;
*pMax = ARRAY_SIZE(gSubMenu_SET_SCN) - 1;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
//*pMin = 0;
@@ -800,7 +806,7 @@ void MENU_AcceptSetting(void)
case MENU_D_LIVE_DEC:
gSetting_live_DTMF_decoder = gSubMenuSelection;
gDTMF_RX_live_timeout = 0;
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
if (!gSetting_live_DTMF_decoder)
BK4819_DisableDTMF();
gFlagReconfigureVfos = true;
@@ -989,6 +995,11 @@ void MENU_AcceptSetting(void)
case MENU_SET_GUI:
gSetting_set_gui = gSubMenuSelection;
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
gSetting_set_scn = gSubMenuSelection;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM)
@@ -1453,6 +1464,11 @@ void MENU_ShowCurrentSetting(void)
case MENU_SET_GUI:
gSubMenuSelection = gSetting_set_gui;
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
gSubMenuSelection = gSetting_set_scn;
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM)
@@ -1507,6 +1523,13 @@ static const char* const char_map[10] = {
"wxyz9" // KEY_9
};
static bool MENU_IsEditingName() {
return !gCssBackgroundScan
&& UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME
&& gIsInSubMenu
&& edit_index >= 0;
}
static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
uint8_t Offset;
@@ -1708,12 +1731,7 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
static void MENU_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
const bool editing_name = !gCssBackgroundScan
&& UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME
&& gIsInSubMenu
&& edit_index >= 0;
if (editing_name)
if (MENU_IsEditingName())
{
if (!bKeyPressed)
{
@@ -1813,9 +1831,9 @@ Skip:
static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
{
if (bKeyHeld || !bKeyPressed)
if (!bKeyPressed || (bKeyHeld && (!MENU_IsEditingName() || gAskForConfirmation)))
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gRequestDisplayScreen = DISPLAY_MENU;
@@ -1884,8 +1902,12 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
{ // editing the channel name characters
edit_last_key = 255;
if (++edit_index < 10)
return; // next char
if (bKeyHeld) {
edit_index = 10;
}
else if (++edit_index < 10) {
return;
}
// exit
gFlagAcceptSetting = false;
@@ -2013,23 +2035,18 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
uint16_t Channel;
bool bCheckScanList;
if (!bKeyPressed)
return;
if (!bKeyHeld) {
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
if (!gEeprom.SET_NAV && gIsInSubMenu) {
Direction = -Direction;
}
if (!bKeyHeld)
{
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gInputBoxIndex = 0;
}
else
if (!bKeyPressed)
return;
if (UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME && gIsInSubMenu && edit_index >= 0)
{ // change the character
if (edit_index < 10 && Direction != 0)
+5 -8
View File
@@ -243,16 +243,13 @@ static void SCANNER_Key_STAR(bool bKeyPressed, bool bKeyHeld)
return;
}
static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool pKeyHeld, int8_t Direction)
static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
{
if (pKeyHeld) {
if (!bKeyPressed)
return;
if (!bKeyPressed) {
return;
}
else {
if (!bKeyPressed)
return;
if (!bKeyHeld) {
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
@@ -266,7 +263,7 @@ static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool pKeyHeld, int8_t Directio
gShowChPrefix = RADIO_CheckValidChannel(gScanChannel, false, 0);
gRequestDisplayScreen = DISPLAY_SCANNER;
}
else
else if (!bKeyHeld)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
+71 -44
View File
@@ -209,32 +209,56 @@ uint16_t statuslineUpdateTimer = 0;
#ifdef ENABLE_FEAT_F4HWN_SPECTRUM
static void LoadSettings()
{
uint8_t Data[8] = {0};
PY25Q16_ReadBuffer(0x00A158, Data, sizeof(Data));
uint8_t Data[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
PY25Q16_ReadBuffer(0x00A148, Data, sizeof(Data));
settings.scanStepIndex = ((Data[3] & 0xF0) >> 4);
// Data[0]: scanStepIndex (7:4), stepsCount (3:2), listenBw (1:0)
settings.scanStepIndex = (Data[0] >> 4) & 0x0F;
if (settings.scanStepIndex > 14)
settings.scanStepIndex = S_STEP_25_0kHz;
settings.stepsCount = ((Data[3] & 0x0F) & 0b1100) >> 2;
settings.stepsCount = (Data[0] >> 2) & 0x03;
if (settings.stepsCount > 3)
settings.stepsCount = STEPS_64;
settings.listenBw = ((Data[3] & 0x0F) & 0b0011);
settings.listenBw = Data[0] & 0x03;
if (settings.listenBw > 2)
settings.listenBw = BK4819_FILTER_BW_WIDE;
// Data[1]: manualSetFlag (0), autoSensitivity (2:1)
manualSetFlag = Data[1] & 0x01;
autoSensitivity = (Data[1] >> 1) & 0x03;
if (autoSensitivity >= AUTO_SENS_N_ELEM)
autoSensitivity = AUTO_SENS_NORMAL;
// Data[2]: dbMax encoded as (dbMax + 130) / 5
if (Data[2] <= 28)
settings.dbMax = (int)Data[2] * 5 - 130;
// Data[3]: rssiTriggerLevel as uint8_t (0xFF = auto)
settings.rssiTriggerLevel = (Data[3] == 0xFF) ? RSSI_MAX_VALUE : Data[3];
// Data[4] ~ Data[7] are free (for the moment...)
}
static void SaveSettings()
{
uint8_t Data[8] = {0};
PY25Q16_ReadBuffer(0x00A158, Data, sizeof(Data));
PY25Q16_ReadBuffer(0x00A148, Data, sizeof(Data));
Data[3] = (settings.scanStepIndex << 4) | (settings.stepsCount << 2) | settings.listenBw;
// Data[0]: scanStepIndex (7:4), stepsCount (3:2), listenBw (1:0)
Data[0] = (settings.scanStepIndex << 4) | (settings.stepsCount << 2) | settings.listenBw;
// Data[1]: manualSetFlag (0), autoSensitivity (2:1)
Data[1] = (manualSetFlag & 0x01) | ((autoSensitivity & 0x03) << 1);
PY25Q16_WriteBuffer(0x00A158, Data, sizeof(Data), false);
// Data[2]: dbMax encoded as (dbMax + 130) / 5
Data[2] = (uint8_t)((settings.dbMax + 130) / 5);
// Data[3]: rssiTriggerLevel as uint8_t (0xFF = auto)
Data[3] = (settings.rssiTriggerLevel == RSSI_MAX_VALUE) ? 0xFF : (uint8_t)settings.rssiTriggerLevel;
PY25Q16_WriteBuffer(0x00A148, Data, sizeof(Data), false);
}
#endif
@@ -438,8 +462,6 @@ static void SetFScan(uint32_t f)
// Spectrum related
bool IsPeakOverLevel() { return peak.rssi >= settings.rssiTriggerLevel; }
static bool IsPeakOverOpenLevel()
{
uint16_t openLevel = settings.rssiTriggerLevel;
@@ -618,7 +640,11 @@ static void ToggleRX(bool on)
if (on)
{
listenLowCount = 0;
listenPrevRssi = RSSI_MAX_VALUE;
// Seed with the RSSI that opened the squelch so the very first measure
// can already detect an abrupt drop (quick-PTT case where the operator
// released before listen was actually engaged).
// listenPrevRssi = RSSI_MAX_VALUE; // previous behavior
listenPrevRssi = peak.rssi;
#ifdef ENABLE_FEAT_F4HWN_SPECTRUM
listenT = 25;
BK4819_WriteRegister(0x43, listenBWRegValues[settings.listenBw]);
@@ -966,25 +992,15 @@ static void UpdateAutoSensitivity(bool inc)
static void UpdateRssiTriggerLevel(bool inc)
{
if (inc && isListening && IsPeakOverLevel())
{
// One-press escape: jump threshold above the active carrier without
// clamping — the interferer may exceed dbMax so ClampRssiTriggerLevel
// would silently prevent the escape.
settings.rssiTriggerLevel = peak.rssi + 8;
}
if (inc)
settings.rssiTriggerLevel += 2;
else
{
if (inc)
settings.rssiTriggerLevel += 2;
else
settings.rssiTriggerLevel -= 2;
settings.rssiTriggerLevel -= 2;
if (settings.rssiTriggerLevel > dbm2rssi(settings.dbMax))
UpdateDbMax(true);
else
ClampRssiTriggerLevel();
}
if (settings.rssiTriggerLevel > dbm2rssi(settings.dbMax))
UpdateDbMax(true);
else
ClampRssiTriggerLevel();
redrawScreen = true;
redrawStatus = true;
@@ -1509,7 +1525,11 @@ static void BuildCurrentSpectrumTopY(uint8_t *topY)
#endif
BuildSpectrumTopY(topY, bars);
SmoothTopY(topY);
// Skip cosmetic smoothing in manual mode so the rendered curve matches
// the raw RSSI used by the squelch detector — narrow peaks must visibly
// cross the trigger line when the radio opens the squelch.
if (!manualSetFlag)
SmoothTopY(topY);
}
static void DrawStatus()
@@ -1574,6 +1594,11 @@ static void ShowChannelName(uint32_t f)
static uint32_t channelF = 0;
static char channelName[12];
// 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.
// Clear first so a shorter name doesn't leave stale pixels.
memset(&gStatusLine[43], 0, 116 - 43);
if (isListening)
{
if (f != channelF) {
@@ -1593,19 +1618,9 @@ static void ShowChannelName(uint32_t f)
}
}
if (channelName[0] != 0) {
// 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.
// Clear first so a shorter name doesn't leave stale pixels.
memset(&gStatusLine[43], 0, 116 - 43);
UI_PrintStringSmallBufferNormal(channelName, gStatusLine + 43);
} else {
memset(&gStatusLine[43], 0, 116 - 43);
}
}
else
{
memset(&gStatusLine[43], 0, 116 - 43);
}
ST7565_BlitStatusLine();
}
@@ -1660,8 +1675,20 @@ static void DrawNums()
FormatFrequency(GetFStart(), String);
GUI_DisplaySmallest(String, 0, 49, false, true);
sprintf(String, "\x7F%u.%02uk", settings.frequencyChangeStep / 100,
settings.frequencyChangeStep % 100);
#ifdef ENABLE_SCAN_RANGES
if (gScanRangeStart)
{
// Scan-range mode: UP/DOWN are blocked, frequencyChangeStep is unused.
// Show the visible bandwidth instead, which is meaningful here.
uint32_t bw = gScanRangeStop - gScanRangeStart;
sprintf(String, "%u.%02uk", bw / 100, bw % 100);
}
else
#endif
{
sprintf(String, "\x7F%u.%02uk", settings.frequencyChangeStep / 100,
settings.frequencyChangeStep % 100);
}
GUI_DisplaySmallest(String, 48, 49, false, true);
FormatFrequency(GetFEnd(), String);
@@ -1948,7 +1975,7 @@ static void RenderFreqInput() { UI_PrintString(freqInputString, 2, 127, 0, 8); }
static void RenderStatus()
{
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
DrawStatus();
ST7565_BlitStatusLine();
}
@@ -2570,8 +2597,8 @@ void APP_RunSpectrum()
// Reset dynamic spectrum state on every entry.
// Persisted settings are step/count/listenBW only; trigger and dB window
// are runtime values and should not carry over between sessions.
manualSetFlag = false;
settings.rssiTriggerLevel = RSSI_MAX_VALUE;
// manualSetFlag = false;
// settings.rssiTriggerLevel = RSSI_MAX_VALUE;
RearmRuntimeState();
+28 -93
View File
@@ -33,31 +33,32 @@
#include "settings.h"
#include "ui/ui.h"
static const uint16_t BEEP_Classic_array[][3] = { /* Tone Duration Repeats */
[BEEP_NONE] = {0, 0, 0 },
[BEEP_1KHZ_60MS_OPTIONAL] = {1000, 60, 1 },
[BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL] = {500, 60, 2 },
#ifdef ENABLE_DTMF_CALLING
[BEEP_880HZ_200MS] = {880, 200, 1 },
[BEEP_880HZ_500MS] = {880, 500, 1 },
#endif
#ifdef ENABLE_FEAT_F4HWN
[BEEP_400HZ_30MS] = {400, 30, 1 },
[BEEP_500HZ_30MS] = {500, 30, 1 },
[BEEP_600HZ_30MS] = {600, 30, 1 },
#endif
[BEEP_880HZ_60MS_TRIPLE_BEEP] = {880, 60, 3 }
};
BEEP_Type_t gBeepToPlay = BEEP_NONE;
static void AUDIO_PlayBeepPulse(void) {
BK4819_ExitTxMute();
SYSTEM_DelayMs(60);
BK4819_EnterTxMute();
SYSTEM_DelayMs(20);
}
void AUDIO_PlayBeep(BEEP_Type_t Beep)
{
if (Beep == BEEP_NONE)
return;
if (Beep != BEEP_880HZ_60MS_DOUBLE_BEEP &&
Beep != BEEP_500HZ_60MS_DOUBLE_BEEP &&
#ifdef ENABLE_DTMF_CALLING
Beep != BEEP_880HZ_200MS &&
Beep != BEEP_880HZ_500MS &&
#endif
#ifdef ENABLE_FEAT_F4HWN
Beep != BEEP_400HZ_30MS &&
Beep != BEEP_500HZ_30MS &&
Beep != BEEP_600HZ_30MS &&
#endif
!gEeprom.BEEP_CONTROL)
if ((Beep == BEEP_1KHZ_60MS_OPTIONAL ||
Beep == BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL) &&
!gEeprom.BEEP_CONTROL)
return;
if (gCurrentFunction == FUNCTION_RECEIVE)
@@ -66,6 +67,9 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
if (gCurrentFunction == FUNCTION_MONITOR)
return;
if (Beep >= ARRAY_SIZE(BEEP_Classic_array))
return;
#ifdef ENABLE_FMRADIO
if (gFmRadioMode)
BK1080_Mute(true);
@@ -80,40 +84,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
uint16_t ToneConfig = BK4819_ReadRegister(BK4819_REG_71);
uint16_t ToneFrequency;
switch (Beep)
{
default:
case BEEP_NONE:
ToneFrequency = 220;
break;
case BEEP_1KHZ_60MS_OPTIONAL:
ToneFrequency = 1000;
break;
case BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL:
case BEEP_500HZ_60MS_DOUBLE_BEEP:
ToneFrequency = 500;
break;
case BEEP_880HZ_60MS_DOUBLE_BEEP:
#ifndef ENABLE_FEAT_F4HWN
case BEEP_880HZ_200MS:
case BEEP_880HZ_500MS:
#endif
ToneFrequency = 880;
break;
#ifdef ENABLE_FEAT_F4HWN
case BEEP_400HZ_30MS:
ToneFrequency = 400;
break;
case BEEP_500HZ_30MS:
ToneFrequency = 500;
break;
case BEEP_600HZ_30MS:
ToneFrequency = 600;
break;
#endif
}
#ifdef ENABLE_FEAT_F4HWN
if(Beep == BEEP_400HZ_30MS || Beep == BEEP_500HZ_30MS || Beep == BEEP_600HZ_30MS)
{
@@ -121,7 +91,7 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
}
#endif
BK4819_PlayTone(ToneFrequency, true);
BK4819_PrepareToPlayTone(true);
SYSTEM_DelayMs(2);
@@ -129,45 +99,12 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
SYSTEM_DelayMs(60);
uint16_t Duration;
switch (Beep)
{
case BEEP_880HZ_60MS_DOUBLE_BEEP:
AUDIO_PlayBeepPulse();
[[fallthrough]];
case BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL:
case BEEP_500HZ_60MS_DOUBLE_BEEP:
AUDIO_PlayBeepPulse();
[[fallthrough]];
case BEEP_1KHZ_60MS_OPTIONAL:
BK4819_ExitTxMute();
Duration = 60;
break;
#ifdef ENABLE_FEAT_F4HWN
case BEEP_400HZ_30MS:
case BEEP_500HZ_30MS:
case BEEP_600HZ_30MS:
BK4819_ExitTxMute();
Duration = 30;
break;
#endif
#ifndef ENABLE_FEAT_F4HWN
case BEEP_880HZ_200MS:
BK4819_ExitTxMute();
Duration = 200;
break;
case BEEP_880HZ_500MS:
#endif
default:
BK4819_ExitTxMute();
Duration = 500;
break;
for (uint8_t i = 0; i < BEEP_Classic_array[Beep][BEEP_REPEATS]; i++) {
BK4819_PlayToneRaw( BEEP_Classic_array[Beep][BEEP_TONE],
BEEP_Classic_array[Beep][BEEP_DURATION]);
SYSTEM_DelayMs(20);
}
SYSTEM_DelayMs(Duration);
BK4819_EnterTxMute();
SYSTEM_DelayMs(20);
AUDIO_AudioPathOff();
SYSTEM_DelayMs(5);
@@ -182,7 +119,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
SYSTEM_DelayMs(10);
#endif
if (gEnableSpeaker)
AUDIO_AudioPathOn();
@@ -197,7 +133,6 @@ void AUDIO_PlayBeep(BEEP_Type_t Beep)
#ifdef ENABLE_VOX
gVoxResumeCountdown = 80;
#endif
}
#ifdef ENABLE_VOICE
+8 -2
View File
@@ -22,6 +22,13 @@
#include "driver/gpio.h"
enum {
BEEP_TONE = 0,
BEEP_DURATION,
BEEP_REPEATS
};
enum BEEP_Type_t
{
BEEP_NONE = 0,
@@ -31,13 +38,12 @@ enum BEEP_Type_t
BEEP_880HZ_200MS,
BEEP_880HZ_500MS,
#endif
BEEP_500HZ_60MS_DOUBLE_BEEP,
#ifdef ENABLE_FEAT_F4HWN
BEEP_400HZ_30MS,
BEEP_500HZ_30MS,
BEEP_600HZ_30MS,
#endif
BEEP_880HZ_60MS_DOUBLE_BEEP
BEEP_880HZ_60MS_TRIPLE_BEEP
};
typedef enum BEEP_Type_t BEEP_Type_t;
+23 -5
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <stddef.h>
#include "dcs.h"
#include "misc.h"
@@ -48,6 +49,13 @@ const uint16_t DCS_Options[104] = {
0x01C3, 0x01CA, 0x01D3, 0x01D9, 0x01DA, 0x01DC, 0x01E3, 0x01EC,
};
// Indices in DCS_Options for the 21 non-PMR446 DCS codes.
static const uint8_t DCS_ExtraIdx[21] = {
5, 9, 19, 25, 34, 36, 41, 43, 44, 48,
50, 54, 56, 60, 71, 72, 73, 74, 75, 82,
83
};
static uint32_t DCS_CalculateGolay(uint32_t CodeWord)
{
unsigned int i;
@@ -115,18 +123,18 @@ uint8_t DCS_GetCtcssCode(int Code)
return Result;
}
uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option)
static uint8_t DCS_GetApprovedIndex(uint8_t Option, size_t options_size, size_t extraidx_size, const uint8_t *extraidx)
{
unsigned int i;
unsigned int extra_pos = 0;
uint8_t approved_index = 0;
if (Option >= ARRAY_SIZE(CTCSS_Options))
if (Option >= options_size)
return 0xFF;
for (i = 0; i < ARRAY_SIZE(CTCSS_Options); i++) {
const bool is_extra = extra_pos < ARRAY_SIZE(CTCSS_ExtraIdx) &&
i == CTCSS_ExtraIdx[extra_pos];
for (i = 0; i < options_size; i++) {
const bool is_extra = extra_pos < extraidx_size &&
i == extraidx[extra_pos];
if (is_extra) {
extra_pos++;
@@ -141,3 +149,13 @@ uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option)
return 0xFF;
}
uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option)
{
return DCS_GetApprovedIndex(Option, ARRAY_SIZE(CTCSS_Options), ARRAY_SIZE(CTCSS_ExtraIdx), CTCSS_ExtraIdx);
}
uint8_t DCS_GetDcsApprovedIndex(uint8_t Option)
{
return DCS_GetApprovedIndex(Option, ARRAY_SIZE(DCS_Options), ARRAY_SIZE(DCS_ExtraIdx), DCS_ExtraIdx);
}
+1
View File
@@ -41,5 +41,6 @@ uint32_t DCS_GetGolayCodeWord(DCS_CodeType_t CodeType, uint8_t Option);
uint8_t DCS_GetCdcssCode(uint32_t Code);
uint8_t DCS_GetCtcssCode(int Code);
uint8_t DCS_GetCtcssApprovedIndex(uint8_t Option);
uint8_t DCS_GetDcsApprovedIndex(uint8_t Option);
#endif
+2 -2
View File
@@ -109,8 +109,8 @@ 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)
{
AUDIO_PlayBeep(BEEP_880HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
AUDIO_PlayBeep(BEEP_880HZ_60MS_TRIPLE_BEEP);
}
gK5startup = false;
+2
View File
@@ -105,7 +105,9 @@ void BK4819_SetCompander(const unsigned int mode);
void BK4819_DisableVox(void);
void BK4819_DisableDTMF(void);
void BK4819_EnableDTMF(void);
void BK4819_PrepareToPlayTone(bool bTuningGainSwitch);
void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch);
void BK4819_PlayToneRaw(const unsigned int tone_Hz, const unsigned int delay);
void BK4819_PlaySingleTone(const unsigned int tone_Hz, const unsigned int delay, const unsigned int level, const bool play_speaker);
void BK4819_EnterTxMute(void);
void BK4819_ExitTxMute(void);
+17 -17
View File
@@ -1031,7 +1031,7 @@ void BK4819_EnableDTMF(void)
(15u << BK4819_REG_24_SHIFT_MAX_SYMBOLS)); // 0 ~ 15
}
void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch)
void BK4819_PrepareToPlayTone(bool bTuningGainSwitch)
{
uint16_t ToneConfig = BK4819_REG_70_ENABLE_TONE1;
@@ -1046,10 +1046,23 @@ void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch)
BK4819_WriteRegister(BK4819_REG_30, 0);
BK4819_WriteRegister(BK4819_REG_30, BK4819_REG_30_ENABLE_AF_DAC | BK4819_REG_30_ENABLE_DISC_MODE | BK4819_REG_30_ENABLE_TX_DSP);
}
void BK4819_PlayTone(uint16_t Frequency, bool bTuningGainSwitch)
{
BK4819_PrepareToPlayTone(bTuningGainSwitch);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(Frequency));
}
void BK4819_PlayToneRaw(const unsigned int tone_Hz, const unsigned int delay) {
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone_Hz));
BK4819_ExitTxMute();
SYSTEM_DelayMs(delay);
BK4819_EnterTxMute();
}
// level 0 ~ 127
void BK4819_PlaySingleTone(const unsigned int tone_Hz, const unsigned int delay, const unsigned int level, const bool play_speaker)
{
@@ -1069,11 +1082,7 @@ void BK4819_PlaySingleTone(const unsigned int tone_Hz, const unsigned int delay,
BK4819_EnableTXLink();
SYSTEM_DelayMs(50);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone_Hz));
BK4819_ExitTxMute();
SYSTEM_DelayMs(delay);
BK4819_EnterTxMute();
BK4819_PlayToneRaw(tone_Hz, delay);
if (play_speaker)
{
@@ -1767,17 +1776,8 @@ static void BK4819_PlayRogerNormal(void)
BK4819_EnableTXLink();
SYSTEM_DelayMs(50);
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone1_Hz));
BK4819_ExitTxMute();
SYSTEM_DelayMs(80);
BK4819_EnterTxMute();
BK4819_WriteRegister(BK4819_REG_71, scale_freq(tone2_Hz));
BK4819_ExitTxMute();
SYSTEM_DelayMs(80);
BK4819_EnterTxMute();
BK4819_PlayToneRaw(tone1_Hz, 80);
BK4819_PlayToneRaw(tone2_Hz, 80);
BK4819_WriteRegister(BK4819_REG_70, 0x0000);
BK4819_WriteRegister(BK4819_REG_30, 0xC1FE); // 1 1 0000 0 1 1111 1 1 1 0
+8 -2
View File
@@ -59,13 +59,19 @@ static const AddrMapping_t ADDR_MAPPINGS[] = {
// MR FM * 128 Bytes (0x003000) 0x00A028 -> 0x00A0A8
// Settings * 80 Bytes (0x007000) 0x00A0A8 -> 0x00A0F8
// Settings * 56 Bytes (0x008000) 0x00A0F8 -> 0x00A130
// Settings Scanlist * 8 Bytes (0x009000) 0x00A130 -> 0x00A140
// Settings AES * 16 Bytes (0x00A000) 0x00A140 -> 0x00A150
// Settings Scanlist * 8 Bytes (0x009000) 0x00A130 -> 0x00A138
// Settings AES * 16 Bytes (0x00A000) 0x00A138 -> 0x00A148
// Settings Spectrum * 8 Bytes 0x00A148 -> 0x00A150
// Settings * 8 Bytes (0x00B000) 0x00A150 -> 0x00A158
// Settings F4HWN * 8 Bytes (0x00C000) 0x00A158 -> 0x00A160
// Settings Version * 16 Bytes 0x00A160 -> 0x00A170
_MK_MAPPING(0x010000, 0x00B000, 0x00B200), // Calibration 512 Bytes!!!
_MK_MAPPING(0x011000, 0x00C000, 0x00D000), // Boot Logo sector (4 KB):
// [0x00..0x07] 8-byte header (reserved)
// [0x08..0x407] 128x64 monochrome bitmap, 1024 Bytes
// ST7565-native: 8 pages * 128 columns, column-major LSB-top
};
static void AddrTranslate(uint16_t EEPROM_Addr, uint16_t Size, uint32_t *PY25Q16_Addr_out, uint16_t *Size_out, bool *End_out);
+1 -1
View File
@@ -151,7 +151,7 @@ void FUNCTION_Transmit()
// clear the DTMF RX live decoder buffer
gDTMF_RX_live_timeout = 0;
memset(gDTMF_RX_live, 0, sizeof(gDTMF_RX_live));
DTMF_clear_input_box_memory();
#if defined(ENABLE_FMRADIO)
if (gFmRadioMode)
+2 -2
View File
@@ -228,7 +228,7 @@ void BATTERY_TimeSlice500ms(void)
if (lowBatteryCountdown < lowBatteryPeriod) {
if (lowBatteryCountdown == lowBatteryPeriod-1 && !gChargingWithTypeC && !gLowBatteryConfirmed) {
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL);
}
return;
}
@@ -241,7 +241,7 @@ void BATTERY_TimeSlice500ms(void)
// not on charge
if (!gLowBatteryConfirmed) {
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP);
AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL);
#ifdef ENABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_LOW_VOLTAGE);
#endif
+5 -1
View File
@@ -116,6 +116,10 @@ enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
bool gWakeUp = false;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
bool gSetting_set_scn = 1;
#endif
#ifdef ENABLE_FEAT_F4HWN
uint8_t gSetting_set_pwr = 1;
bool gSetting_set_ptt = 0;
@@ -649,4 +653,4 @@ void MR_PrintCacheStats(void)
return false;
}
#endif
#endif
+4
View File
@@ -176,6 +176,10 @@ extern enum BacklightOnRxTx_t gSetting_backlight_on_tx_rx;
extern bool gWakeUp;
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
extern bool gSetting_set_scn;
#endif
#ifdef ENABLE_FEAT_F4HWN
extern uint8_t gSetting_set_pwr;
extern bool gSetting_set_ptt;
+1
View File
@@ -156,6 +156,7 @@ uint16_t RADIO_FindNextChannel(uint16_t ChNum, int8_t Direction, bool bCheckScan
void RADIO_InitInfo(VFO_Info_t *pInfo, const uint16_t ChannelSave, const uint32_t Frequency);
void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure);
void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo);
void RADIO_ValidateAndSetCode(FREQ_Config_t *pFreq_Config, uint8_t tmp);
void RADIO_ApplyOffset(VFO_Info_t *pInfo);
void RADIO_SelectVfos(void);
void RADIO_SetupRegisters(bool switchToForeground);
+32
View File
@@ -98,6 +98,38 @@ void SysTick_Handler(void)
if (gCurrentFunction != FUNCTION_MONITOR && gCurrentFunction != FUNCTION_TRANSMIT)
DECREMENT_AND_TRIGGER(gScanPauseDelayIn_10ms, gScheduleScanListen);
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
// Scan stall watchdog: if the scan-resume countdown has expired
// (gScanPauseDelayIn_10ms == 0) but no resume was actually scheduled
// (gScheduleScanListen == false) and no real reception is happening
// (squelch closed -> green LED off, not in RX/TX/MONITOR), the scan
// loop is stuck. Force a resume after this many 10ms ticks. The
// gScanPauseDelayIn_10ms == 0 guard ensures we never override the
// user-configured ScnRev (SCAN_RESUME_MODE) pause: while that pause
// is decrementing, the watchdog stays asleep.
#define SCAN_FAST_STALL_WATCHDOG_10ms 25 // 250 ms
static uint16_t scanFastStallCounter;
if (gSetting_set_scn
&& gScanStateDir != SCAN_OFF
&& gScanPauseDelayIn_10ms == 0
&& !gScheduleScanListen
&& !g_SquelchLost
&& gCurrentFunction != FUNCTION_RECEIVE
&& gCurrentFunction != FUNCTION_TRANSMIT
&& gCurrentFunction != FUNCTION_MONITOR)
{
if (++scanFastStallCounter >= SCAN_FAST_STALL_WATCHDOG_10ms) {
scanFastStallCounter = 0;
gScheduleScanListen = true;
}
}
else
{
scanFastStallCounter = 0;
}
#endif
DECREMENT_AND_TRIGGER(gTailNoteEliminationCountdown_10ms, gFlagTailNoteEliminationComplete);
#ifdef ENABLE_VOICE
+130 -6
View File
@@ -454,8 +454,12 @@ 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;
gSetting_set_pwr = (((Data[7] & 0xF0) >> 4) < 7) ? ((Data[7] & 0xF0) >> 4) : 0;
gSetting_set_ptt = (((Data[7] & 0x0F)) < 2) ? ((Data[7] & 0x0F)) : 0;
gSetting_set_ptt = (set_ptt_scn < 4) ? (set_ptt_scn & 0x01) : 0;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
gSetting_set_scn = (set_ptt_scn < 4) ? ((set_ptt_scn & 0x02) == 0) : 1;
#endif
gSetting_set_tot = (((Data[6] & 0xF0) >> 4) < 4) ? ((Data[6] & 0xF0) >> 4) : 0;
gSetting_set_eot = (((Data[6] & 0x0F)) < 4) ? ((Data[6] & 0x0F)) : 0;
@@ -495,10 +499,6 @@ gEeprom.FreqChannel[1] = IS_FREQ_CHANNEL(Data16[5]) ? Data16[5] : (FREQ_CHANNE
gSetting_set_off = (Data[4] >> 1) > 120 ? 60 : (Data[4] >> 1);
#endif
// Warning
// Be aware, Data[3] is use by Spectrum
// Warning
// And set special session settings for actions
gSetting_set_ptt_session = gSetting_set_ptt;
gEeprom.KEY_LOCK_PTT = gSetting_set_lck;
@@ -583,6 +583,124 @@ uint32_t SETTINGS_FetchChannelFrequency(const uint16_t channel)
return info.frequency;
}
bool SETTINGS_FetchChannelScanInfo(const uint16_t channel, uint32_t *frequency, ModulationMode_t *modulation)
{
struct
{
uint32_t frequency;
uint32_t offset;
uint8_t settings[4];
} __attribute__((packed)) info;
PY25Q16_ReadBuffer(channel * 16, &info, sizeof(info));
if (frequency)
*frequency = info.frequency;
if (modulation)
{
uint8_t mode = info.settings[3] >> 4;
if (mode >= MODULATION_UKNOWN)
mode = MODULATION_FM;
*modulation = (ModulationMode_t)mode;
}
return info.frequency != 0 && info.frequency != 0xFFFFFFFF;
}
bool SETTINGS_FetchChannelScanDisplayInfo(const uint16_t channel, ChannelScanDisplayInfo_t *info)
{
if (info == NULL)
return false;
struct
{
uint32_t frequency;
uint32_t offset;
uint8_t data[8];
} __attribute__((packed)) raw;
PY25Q16_ReadBuffer(channel * 16, &raw, sizeof(raw));
if (raw.frequency == 0 || raw.frequency == 0xFFFFFFFF)
return false;
memset(info, 0, sizeof(*info));
info->rx.Frequency = raw.frequency;
info->tx.Frequency = raw.frequency;
info->offset = (raw.offset >= _1GHz_in_KHz) ? (_1GHz_in_KHz / 100) : raw.offset;
info->rx.CodeType = (raw.data[2] >> 0) & 0x0F;
info->tx.CodeType = (raw.data[2] >> 4) & 0x0F;
RADIO_ValidateAndSetCode(&info->rx, raw.data[0]);
RADIO_ValidateAndSetCode(&info->tx, raw.data[1]);
uint8_t tmp = raw.data[3] & 0x0F;
if (tmp > TX_OFFSET_FREQUENCY_DIRECTION_SUB)
tmp = TX_OFFSET_FREQUENCY_DIRECTION_OFF;
info->txOffsetFrequencyDirection = tmp;
tmp = raw.data[3] >> 4;
if (tmp >= MODULATION_UKNOWN)
tmp = MODULATION_FM;
info->modulation = (ModulationMode_t)tmp;
tmp = raw.data[6];
if (tmp >= STEP_N_ELEM)
tmp = STEP_12_5kHz;
info->stepSetting = (STEP_Setting_t)tmp;
info->stepFrequency = gStepFrequencyTable[tmp];
if (raw.data[4] == 0xFF)
{
info->frequencyReverse = false;
info->channelBandwidth = BANDWIDTH_WIDE;
info->outputPower = OUTPUT_POWER_LOW1;
info->busyChannelLock = false;
info->txLock = true;
}
else
{
const uint8_t d4 = raw.data[4];
info->frequencyReverse = !!((d4 >> 0) & 1u);
info->channelBandwidth = !!((d4 >> 1) & 1u);
info->outputPower = ((d4 >> 2) & 7u);
info->busyChannelLock = !!((d4 >> 5) & 1u);
info->txLock = !!((d4 >> 6) & 1u);
}
switch (info->txOffsetFrequencyDirection)
{
case TX_OFFSET_FREQUENCY_DIRECTION_ADD:
info->tx.Frequency = raw.frequency + info->offset;
break;
case TX_OFFSET_FREQUENCY_DIRECTION_SUB:
info->tx.Frequency = raw.frequency - info->offset;
break;
default:
break;
}
if (raw.data[5] == 0xFF)
{
#ifdef ENABLE_DTMF_CALLING
info->dtmfDecodingEnable = false;
#endif
info->dtmfPttIdTxMode = PTT_ID_OFF;
}
else
{
#ifdef ENABLE_DTMF_CALLING
info->dtmfDecodingEnable = (raw.data[5] >> 0) & 1u;
#endif
const uint8_t pttId = (raw.data[5] >> 1) & 7u;
info->dtmfPttIdTxMode = pttId < ARRAY_SIZE(gSubMenu_PTT_ID) ? pttId : PTT_ID_OFF;
}
return true;
}
void SETTINGS_FetchChannelName(char *s, const uint16_t channel)
{
if (s == NULL)
@@ -1013,7 +1131,13 @@ void SETTINGS_SaveSettings(void)
State[5] = ((tmp << 4) | (gSetting_set_ctr & 0x0F));
State[6] = ((gSetting_set_tot << 4) | (gSetting_set_eot & 0x0F));
State[7] = ((gSetting_set_pwr << 4) | (gSetting_set_ptt & 0x0F));
uint8_t set_ptt_scn = gSetting_set_ptt & 0x01;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
if (!gSetting_set_scn)
set_ptt_scn |= 0x02;
#endif
State[7] = ((gSetting_set_pwr << 4) | set_ptt_scn);
gEeprom.KEY_LOCK_PTT = gSetting_set_lck;
+28 -1
View File
@@ -34,7 +34,10 @@ enum POWER_OnDisplayMode_t {
#endif
POWER_ON_DISPLAY_MODE_MESSAGE,
POWER_ON_DISPLAY_MODE_VOLTAGE,
POWER_ON_DISPLAY_MODE_NONE
#ifdef ENABLE_FEAT_F4HWN_LOGO
POWER_ON_DISPLAY_MODE_LOGO,
#endif
POWER_ON_DISPLAY_MODE_NONE,
};
typedef enum POWER_OnDisplayMode_t POWER_OnDisplayMode_t;
@@ -127,6 +130,9 @@ enum ACTION_OPT_t {
#ifdef ENABLE_REGA
ACTION_OPT_REGA_ALARM,
ACTION_OPT_REGA_TEST,
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
ACTION_OPT_BEAM,
#endif
ACTION_OPT_LEN
};
@@ -307,9 +313,30 @@ typedef struct {
extern EEPROM_Config_t gEeprom;
typedef struct {
FREQ_Config_t rx;
FREQ_Config_t tx;
uint32_t offset;
uint16_t stepFrequency;
STEP_Setting_t stepSetting;
ModulationMode_t modulation;
uint8_t txOffsetFrequencyDirection;
uint8_t outputPower;
bool frequencyReverse;
uint8_t channelBandwidth;
uint8_t busyChannelLock;
uint8_t txLock;
#ifdef ENABLE_DTMF_CALLING
uint8_t dtmfDecodingEnable;
#endif
PTT_ID_t dtmfPttIdTxMode;
} ChannelScanDisplayInfo_t;
void SETTINGS_InitEEPROM(void);
void SETTINGS_LoadCalibration(void);
uint32_t SETTINGS_FetchChannelFrequency(const uint16_t channel);
bool SETTINGS_FetchChannelScanInfo(const uint16_t channel, uint32_t *frequency, ModulationMode_t *modulation);
bool SETTINGS_FetchChannelScanDisplayInfo(const uint16_t channel, ChannelScanDisplayInfo_t *info);
void SETTINGS_FetchChannelName(char *s, const uint16_t channel);
void SETTINGS_FactoryReset(bool bIsAll);
#ifdef ENABLE_FMRADIO
+5
View File
@@ -406,3 +406,8 @@ void UI_DisplayClear()
{
memset(gFrameBuffer, 0, sizeof(gFrameBuffer));
}
void UI_StatusClear()
{
memset(gStatusLine, 0, sizeof(gStatusLine));
}
+1
View File
@@ -45,5 +45,6 @@ void UI_DrawLineBuffer(uint8_t (*buffer)[128], int16_t x1, int16_t y1, int16_t x
void UI_DrawRectangleBuffer(uint8_t (*buffer)[128], int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool black);
void UI_DisplayClear();
void UI_StatusClear();
#endif
+2 -11
View File
@@ -34,7 +34,7 @@ static void Render(void)
unsigned int i;
char String[7];
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
UI_DisplayClear();
UI_PrintString("PASSWORD", 0, 127, 1, 10);
@@ -80,16 +80,7 @@ void UI_DisplayLock(void)
switch (Key)
{
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9:
case KEY_0...KEY_9:
INPUTBOX_Append(Key - KEY_0);
if (gInputBoxIndex < 6) // 6 frequency digits
+84 -20
View File
@@ -19,6 +19,11 @@
#include "app/chFrScanner.h"
#include "app/dtmf.h"
#ifdef ENABLE_FEAT_F4HWN_BEAM
#include "app/beam.h"
#endif
#ifdef ENABLE_AM_FIX
#include "am_fix.h"
#endif
@@ -84,6 +89,46 @@ static uint8_t gScanProgressPriorityState;
#define SCAN_PROGRESS_PRIORITY_HOLD_FRAMES 6
#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:
text = "SENDING";
break;
case BEAM_STATUS_TX_DONE:
text = "SENT";
break;
case BEAM_STATUS_RX_WAIT:
text = "WAITING";
break;
case BEAM_STATUS_RX_SAVED:
text = "RECEIVED";
break;
case BEAM_STATUS_RX_FULL:
text = "MEM FULL";
break;
case BEAM_STATUS_ERROR:
text = "ERROR";
break;
case BEAM_STATUS_READY:
default:
text = (gBeamMode == BEAM_MODE_TX) ? "BEAM TX" : "BEAM RX";
break;
}
memset(gFrameBuffer[line], 0, LCD_WIDTH);
UI_PrintStringSmallBold(text, 2, 127, line);
}
#endif
const char *VfoStateStr[] = {
[VFO_STATE_NORMAL]="",
[VFO_STATE_BUSY]="BUSY",
@@ -108,6 +153,7 @@ static void ScanProgress_ResetSession(void)
void UI_MAIN_NotifyScanProgressDataChanged(void)
{
gScanProgressForceRebuild = true;
gUpdateStatus = true;
}
static inline void ScanProgress_SetBit(uint8_t *map, uint16_t ch)
@@ -221,7 +267,7 @@ static bool ScanProgress_BucketHasExcludedOrdinal(uint32_t first_ordinal, uint32
return false;
}
static void ScanProgress_DrawGaugeLine(uint8_t line, uint32_t current_index, uint32_t total, uint8_t width, bool memory_mode, uint8_t extra_left_offset)
static void ScanProgress_DrawGaugeLine(uint8_t line, uint32_t current_index, uint32_t total, uint8_t width, bool memory_mode, bool range_mode, uint8_t extra_left_offset)
{
const bool forward = ScanProgress_IsForward();
@@ -260,6 +306,10 @@ static void ScanProgress_DrawGaugeLine(uint8_t line, uint32_t current_index, uin
if (memory_mode)
excluded = ScanProgress_BucketHasExcludedOrdinal(first_ordinal, last_ordinal);
#ifdef ENABLE_SCAN_RANGES
else if (range_mode)
excluded = CHFRSCANNER_HasScanRangeExcludedOrdinal(first_ordinal, last_ordinal);
#endif
if (processed && !excluded) {
pixel = 0x2d;
@@ -502,7 +552,7 @@ static bool UI_DrawScanProgress(void)
UI_PrintStringSmallNormal(text, 2, 0, line);
#endif
ScanProgress_DrawGaugeLine(line, current_index, total, width, show_memory, extra_offset);
ScanProgress_DrawGaugeLine(line, current_index, total, width, show_memory, show_range, extra_offset);
return true;
}
@@ -920,6 +970,7 @@ void DisplayRSSIBar(const bool now)
overS9dBm = (uint8_t)MIN(rssi_dBm - (-93), 40);
overS9Bars = overS9dBm / 10;
}
const int16_t display_rssi_dBm = (rssi_dBm > -53) ? -53 : rssi_dBm;
#else
const int16_t s0_dBm = -gEeprom.S0_LEVEL; // S0 .. base level
const int16_t rssi_dBm =
@@ -938,12 +989,12 @@ void DisplayRSSIBar(const bool now)
#ifdef ENABLE_FEAT_F4HWN
if (gSetting_set_gui)
{
sprintf(str, "%3d", rssi_dBm);
sprintf(str, "%3d", display_rssi_dBm);
UI_PrintStringSmallNormal(str, LCD_WIDTH + 8, 0, line - 1);
}
else
{
sprintf(str, "% 4d %s", rssi_dBm, "dBm");
sprintf(str, "% 4d %s", display_rssi_dBm, "dBm");
if(isMainOnly())
GUI_DisplaySmallest(str, 2, 41, false, true);
else
@@ -1198,7 +1249,7 @@ void UI_DisplayMain(void)
shift = 3;
}
UI_PrintString("ScnRng", 5, 0, line + shift, 8);
UI_PrintString("ScnRng", 7, 0, line + shift, 8);
UI_FormatFrequency(gScanRangeStart, String);
UI_PrintStringSmallNormal(String, 56, 0, line + shift);
UI_FormatFrequency(gScanRangeStop, String);
@@ -1212,7 +1263,7 @@ void UI_DisplayMain(void)
gScanRangeStart = 0;
}
#else
UI_PrintString("ScnRng", 5, 0, line, 8);
UI_PrintString("ScnRng", 7, 0, line, 8);
UI_FormatFrequency(gScanRangeStart, String);
UI_PrintStringSmallNormal(String, 56, 0, line);
UI_FormatFrequency(gScanRangeStop, String);
@@ -1297,14 +1348,6 @@ void UI_DisplayMain(void)
uint32_t frequency = gEeprom.VfoInfo[vfo_num].pRX->Frequency;
if(TX_freq_check(frequency) != 0 && gEeprom.VfoInfo[vfo_num].TX_LOCK == true && !FUNCTION_IsRx())
{
if(isMainOnly())
memcpy(p_line0 + 25, BITMAP_VFO_Lock, sizeof(BITMAP_VFO_Lock));
else
memcpy(p_line0 + 25, BITMAP_VFO_Lock, sizeof(BITMAP_VFO_Lock));
}
if (gCurrentFunction == FUNCTION_TRANSMIT)
{ // transmitting
@@ -1388,6 +1431,12 @@ void UI_DisplayMain(void)
#endif
}
if(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));
}
if (IS_MR_CHANNEL(gEeprom.ScreenChannel[vfo_num]))
{ // channel mode
const unsigned int x = 1;
@@ -1747,6 +1796,11 @@ void UI_DisplayMain(void)
String[0] = '\0';
const VFO_Info_t *vfoInfo = &gEeprom.VfoInfo[vfo_num];
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
const VFO_Info_t *scanDisplayVfo = CHFRSCANNER_GetScanDisplayVfo();
if (vfo_num == gEeprom.RX_VFO && scanDisplayVfo != NULL)
vfoInfo = scanDisplayVfo;
#endif
// show the modulation symbol
const char * s = "";
@@ -1936,10 +1990,12 @@ void UI_DisplayMain(void)
#endif
#if ENABLE_FEAT_F4HWN
const uint8_t displayBandwidth = vfoInfo->CHANNEL_BANDWIDTH;
#ifdef ENABLE_FEAT_F4HWN_NARROWER
bool narrower = 0;
if(vfoInfo->CHANNEL_BANDWIDTH == BANDWIDTH_NARROW && gSetting_set_nfm == 1)
if(displayBandwidth == BANDWIDTH_NARROW && gSetting_set_nfm == 1)
{
narrower = 1;
}
@@ -1947,23 +2003,23 @@ void UI_DisplayMain(void)
if (gSetting_set_gui)
{
const char *bandWidthNames[] = {"W", "N", "N+"};
UI_PrintStringSmallNormal(bandWidthNames[vfoInfo->CHANNEL_BANDWIDTH + narrower], LCD_WIDTH + 80, 0, line + 1);
UI_PrintStringSmallNormal(bandWidthNames[displayBandwidth + narrower], LCD_WIDTH + 80, 0, line + 1);
}
else
{
const char *bandWidthNames[] = {"WIDE", "NAR", "NAR+"};
GUI_DisplaySmallest(bandWidthNames[vfoInfo->CHANNEL_BANDWIDTH + narrower], 91, line == 0 ? 17 : 49, false, true);
GUI_DisplaySmallest(bandWidthNames[displayBandwidth + narrower], 91, line == 0 ? 17 : 49, false, true);
}
#else
if (gSetting_set_gui)
{
const char *bandWidthNames[] = {"W", "N"};
UI_PrintStringSmallNormal(bandWidthNames[vfoInfo->CHANNEL_BANDWIDTH], LCD_WIDTH + 80, 0, line + 1);
UI_PrintStringSmallNormal(bandWidthNames[displayBandwidth], LCD_WIDTH + 80, 0, line + 1);
}
else
{
const char *bandWidthNames[] = {"WIDE", "NAR"};
GUI_DisplaySmallest(bandWidthNames[vfoInfo->CHANNEL_BANDWIDTH], 91, line == 0 ? 17 : 49, false, true);
GUI_DisplaySmallest(bandWidthNames[displayBandwidth], 91, line == 0 ? 17 : 49, false, true);
}
#endif
#else
@@ -2036,10 +2092,18 @@ void UI_DisplayMain(void)
const bool rx = FUNCTION_IsRx();
#ifdef ENABLE_FEAT_F4HWN_BEAM
if (gBeamActive) {
center_line = CENTER_LINE_BEAM;
UI_MAIN_DrawBeamLine();
}
else
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
if (gScanStateDir != SCAN_OFF && UI_DrawScanProgress())
if (!rx && gScanStateDir != SCAN_OFF && gKeypadLocked == 0)
{
center_line = CENTER_LINE_SCAN_PROGRESS;
UI_DrawScanProgress();
}
else
#endif
+4 -1
View File
@@ -28,7 +28,10 @@ enum center_line_t {
CENTER_LINE_RSSI,
CENTER_LINE_AM_FIX_DATA,
CENTER_LINE_DTMF_DEC,
CENTER_LINE_CHARGE_DATA
CENTER_LINE_CHARGE_DATA,
#ifdef ENABLE_FEAT_F4HWN_BEAM
CENTER_LINE_BEAM
#endif
};
enum Vfo_txtr_mode{
+63 -30
View File
@@ -168,6 +168,9 @@ const t_menu_item MenuList[] =
#ifdef ENABLE_NOAA
{"SetNWR", MENU_NOAA_S },
#endif
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
{"SetScn", MENU_SET_SCN },
#endif
#endif
// hidden menu items from here on
// enabled if pressing both the PTT and upper side button at power-on
@@ -292,6 +295,9 @@ const char gSubMenu_PONMSG[][8] =
#endif
"MESSAGE",
"VOLTAGE",
#ifdef ENABLE_FEAT_F4HWN_LOGO
"LOGO",
#endif
"NONE"
};
@@ -414,6 +420,14 @@ const char gSubMenu_SCRAMBLER[][7] =
"CLASSIC"
};
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
const char gSubMenu_SET_SCN[][7] =
{
"NORMAL",
"FAST"
};
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
const char gSubMenu_SET_AUD_FM[][6] =
{
@@ -497,6 +511,9 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
{"POWER\nHIGH", ACTION_OPT_POWER_HIGH},
{"REMOVE\nOFFSET", ACTION_OPT_REMOVE_OFFSET},
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
{"BEAM", ACTION_OPT_BEAM},
#endif
#endif
};
@@ -593,6 +610,8 @@ 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];
const int m = UI_MENU_GetCurrentMenuId();
#ifdef ENABLE_DTMF_CALLING
char Contact[16];
#endif
@@ -706,7 +725,7 @@ void UI_DisplayMenu(void)
uint8_t gaugeMax = 0;
//#endif
switch (UI_MENU_GetCurrentMenuId())
switch (m)
{
case MENU_SQL:
sprintf(String, "%d", gSubMenuSelection);
@@ -1059,7 +1078,7 @@ void UI_DisplayMenu(void)
case MENU_S_LIST:
if (gSubMenuSelection == MR_CHANNELS_LIST + 1)
strcpy(String, "ALL");
else if (gSubMenuSelection == 0 && UI_MENU_GetCurrentMenuId() == MENU_LIST_CH)
else if (gSubMenuSelection == 0 && m == MENU_LIST_CH)
strcpy(String, "OFF");
else {
const char *name = gListName[gSubMenuSelection - 1];
@@ -1348,6 +1367,12 @@ void UI_DisplayMenu(void)
strcpy(String, gSubMenu_SET_MET[gSubMenuSelection]); // Same as SET_MET
break;
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
case MENU_SET_SCN:
strcpy(String, gSubMenu_SET_SCN[gSubMenuSelection]);
break;
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO
case MENU_SET_AUD:
if(gTxVfo->Modulation == MODULATION_AM) {
@@ -1466,16 +1491,16 @@ void UI_DisplayMenu(void)
}
}
if (UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_1 || UI_MENU_GetCurrentMenuId() == MENU_S_PRI_CH_2)
if (m == MENU_S_PRI_CH_1 || m == MENU_S_PRI_CH_2)
{
}
if ((UI_MENU_GetCurrentMenuId() == MENU_R_CTCS || UI_MENU_GetCurrentMenuId() == MENU_R_DCS) && gCssBackgroundScan)
if ((m == MENU_R_CTCS || m == MENU_R_DCS) && gCssBackgroundScan)
UI_PrintString("SCAN", menu_item_x1, menu_item_x2, 4, 8);
#ifdef ENABLE_DTMF_CALLING
if (UI_MENU_GetCurrentMenuId() == MENU_D_LIST && gIsDtmfContactValid) {
if (m == MENU_D_LIST && gIsDtmfContactValid) {
Contact[11] = 0;
memcpy(&gDTMF_ID, Contact + 8, 4);
sprintf(String, "ID:%4s", gDTMF_ID);
@@ -1483,38 +1508,46 @@ void UI_DisplayMenu(void)
}
#endif
if (UI_MENU_GetCurrentMenuId() == MENU_R_CTCS ||
UI_MENU_GetCurrentMenuId() == MENU_T_CTCS ||
UI_MENU_GetCurrentMenuId() == MENU_R_DCS ||
UI_MENU_GetCurrentMenuId() == MENU_T_DCS
#ifdef ENABLE_DTMF_CALLING
|| UI_MENU_GetCurrentMenuId() == MENU_D_LIST
#endif
) {
if (UI_MENU_GetCurrentMenuId() == MENU_R_CTCS ||
UI_MENU_GetCurrentMenuId() == MENU_T_CTCS) {
const uint8_t approved_index =
(gSubMenuSelection > 0) ? DCS_GetCtcssApprovedIndex(gSubMenuSelection - 1) : 0xFF;
if (m == MENU_R_CTCS ||
m == MENU_T_CTCS) {
const uint8_t approved_index =
(gSubMenuSelection > 0) ? DCS_GetCtcssApprovedIndex(gSubMenuSelection - 1) : 0xFF;
if (gSubMenuSelection == 0)
sprintf(top_right_badge, "00/00");
else if (approved_index != 0xFF)
sprintf(top_right_badge, "%02u/%02u", (unsigned)gSubMenuSelection, approved_index + 1);
else
sprintf(top_right_badge, "%02u/--", (unsigned)gSubMenuSelection);
} else {
sprintf(top_right_badge, "%03d", gSubMenuSelection);
}
if (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 (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);
}
#ifdef ENABLE_DTMF_CALLING
if (m == MENU_D_LIST) {
sprintf(top_right_badge, "%03d", gSubMenuSelection);
}
#endif
if (top_right_badge[0] != '\0') {
UI_MENU_DrawTopRightRoundedBadge(top_right_badge, 1, true, menu_item_x1, menu_item_x2);
}
if ((UI_MENU_GetCurrentMenuId() == MENU_RESET ||
UI_MENU_GetCurrentMenuId() == MENU_MEM_CH ||
UI_MENU_GetCurrentMenuId() == MENU_MEM_NAME ||
UI_MENU_GetCurrentMenuId() == MENU_DEL_CH) && gAskForConfirmation)
if ((m == MENU_RESET ||
m == MENU_MEM_CH ||
m == MENU_MEM_NAME ||
m == MENU_DEL_CH) && gAskForConfirmation)
{ // display confirmation
char *pPrintStr = (gAskForConfirmation == 1) ? "SURE?" : "WAIT!";
UI_PrintString(pPrintStr, menu_item_x1, menu_item_x2, 5, 8);
+11 -1
View File
@@ -137,6 +137,9 @@ enum
MENU_SET_MET,
MENU_SET_GUI,
MENU_SET_TMR,
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
MENU_SET_SCN,
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
MENU_SET_NFM,
#endif
@@ -191,6 +194,9 @@ extern const char gSubMenu_D_RSP[4][11];
extern const char gSubMenu_SET_TOT[4][7];
extern const char gSubMenu_SET_LCK[2][9];
extern const char gSubMenu_SET_MET[2][8];
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
extern const char gSubMenu_SET_SCN[2][7];
#endif
#ifdef ENABLE_FEAT_F4HWN_NARROWER
extern const char gSubMenu_SET_NFM[2][9];
#endif
@@ -205,7 +211,11 @@ extern const char gSubMenu_D_RSP[4][11];
extern const char* const gSubMenu_PTT_ID[5];
#ifdef ENABLE_FEAT_F4HWN
extern const char gSubMenu_PONMSG[5][8];
#ifdef ENABLE_FEAT_F4HWN_LOGO
extern const char gSubMenu_PONMSG[6][8];
#else
extern const char gSubMenu_PONMSG[5][8];
#endif
#else
extern const char gSubMenu_PONMSG[4][8];
#endif
+1 -1
View File
@@ -59,7 +59,7 @@ void UI_DisplayStatus()
char str[8] = "";
gUpdateStatus = false;
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
uint8_t *line = gStatusLine;
unsigned int x = 0;
+34 -4
View File
@@ -33,6 +33,19 @@
#include "screenshot.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_LOGO
// Boot logo storage in PY25Q16 external flash, aligned on a 4 KB sector,
// placed past the calibration zone (0x010000-0x010200).
//
// Layout inside the sector starting at LOGO_FLASH_ADDR:
// [0x00..0x07] : 8-byte header (reserved for future magic/version/flags)
// [0x08..0x407]: 128x64 monochrome bitmap (1024 B)
// ST7565-native: 8 pages * 128 columns, column-major LSB-top
#define LOGO_FLASH_ADDR 0x011000
#define LOGO_HEADER_SIZE 8
#define LOGO_BITMAP_ADDR (LOGO_FLASH_ADDR + LOGO_HEADER_SIZE)
#endif
#ifdef ENABLE_FEAT_F4HWN_QRCODE
// QR code (version 4, 33x33 modules, EC level L) encoding:
// https://github.com/armel/uv-k1-k5v3-firmware-custom
@@ -189,7 +202,7 @@ void UI_DrawQRCode(bool wiki, uint8_t origin_x, uint8_t origin_y)
void UI_DisplayReleaseKeys(void)
{
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
#if defined(ENABLE_FEAT_F4HWN_CTR) || defined(ENABLE_FEAT_F4HWN_INV)
ST7565_ContrastAndInv();
#endif
@@ -209,24 +222,41 @@ void UI_DisplayWelcome(void)
char WelcomeString2[16];
char WelcomeString3[32];
memset(gStatusLine, 0, sizeof(gStatusLine));
UI_StatusClear();
#if defined(ENABLE_FEAT_F4HWN_CTR) || defined(ENABLE_FEAT_F4HWN_INV)
ST7565_ContrastAndInv();
#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);
}
#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);
}
#endif
} else {
else {
memset(WelcomeString0, 0, sizeof(WelcomeString0));
memset(WelcomeString1, 0, sizeof(WelcomeString1));
+8 -2
View File
@@ -69,11 +69,14 @@
"ENABLE_FEAT_F4HWN_QRCODE": false,
"ENABLE_FEAT_F4HWN_MEM": false,
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": false,
"ENABLE_FEAT_F4HWN_SCAN_FASTER": false,
"ENABLE_FEAT_F4HWN_BEAM": false,
"ENABLE_FEAT_F4HWN_LOGO": false,
"ENABLE_AGC_SHOW_DATA": false,
"ENABLE_UART_RW_BK_REGS": false,
"ENABLE_SWD": false,
"VERSION_STRING_1": "v0.22",
"VERSION_STRING_2": "v5.4.0"
"VERSION_STRING_2": "v5.5.0"
}
},
{
@@ -194,8 +197,11 @@
"ENABLE_FEAT_F4HWN_AUDIO": true,
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": true,
"ENABLE_FEAT_F4HWN_SCAN_FASTER": true,
"ENABLE_FEAT_F4HWN_BEAM": true,
"ENABLE_FEAT_F4HWN_QRCODE": true,
"ENABLE_FEAT_F4HWN_MEM": true,
"ENABLE_FEAT_F4HWN_LOGO": true,
"ENABLE_SWD": true,
"EDITION_STRING": "Fusion",
"TARGET": "f4hwn.fusion"
@@ -232,4 +238,4 @@
"configurePreset": "Fusion"
}
]
}
}
+4
View File
@@ -114,6 +114,7 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* 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,
@@ -138,6 +139,8 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* `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,
@@ -151,6 +154,7 @@ Special thanks to Jean-Cyrille F6IWW (3 times), Fabrice 14RC123, David F4BPP, Ol
* 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,
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB