mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Add beam feature behind ENABLE_FEAT_F4HWN_BEAM flag
This commit is contained in:
1 parent
59ed9cd827
commit
b8936ace0e
12 files changed
+562
-14
No files matched your search
+8
-1
@@ -123,7 +123,7 @@ enable_feature(ENABLE_FMRADIO
|
|||||||
ui/fmradio.c
|
ui/fmradio.c
|
||||||
)
|
)
|
||||||
enable_feature(ENABLE_AIRCOPY
|
enable_feature(ENABLE_AIRCOPY
|
||||||
app/aircopy.c
|
app/aircopy.c
|
||||||
ui/aircopy.c
|
ui/aircopy.c
|
||||||
)
|
)
|
||||||
enable_feature(ENABLE_NOAA)
|
enable_feature(ENABLE_NOAA)
|
||||||
@@ -206,6 +206,13 @@ enable_feature(ENABLE_FEAT_F4HWN_GMRS_FRS_MURS)
|
|||||||
enable_feature(ENABLE_FEAT_F4HWN_CA)
|
enable_feature(ENABLE_FEAT_F4HWN_CA)
|
||||||
enable_feature(ENABLE_FEAT_F4HWN_DEBUG)
|
enable_feature(ENABLE_FEAT_F4HWN_DEBUG)
|
||||||
enable_feature(ENABLE_FEAT_F4HWN_MEM)
|
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_QRCODE)
|
||||||
|
|
||||||
# ---- DEBUGGING ----
|
# ---- DEBUGGING ----
|
||||||
|
|||||||
@@ -45,6 +45,9 @@
|
|||||||
#ifdef ENABLE_REGA
|
#ifdef ENABLE_REGA
|
||||||
#include "app/rega.h"
|
#include "app/rega.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
#include "app/beam.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(ENABLE_FMRADIO)
|
#if defined(ENABLE_FMRADIO)
|
||||||
static void ACTION_Scan_FM(bool bRestart);
|
static void ACTION_Scan_FM(bool bRestart);
|
||||||
@@ -132,6 +135,9 @@ void (*action_opt_table[])(void) = {
|
|||||||
[ACTION_OPT_REGA_ALARM] = &ACTION_RegaAlarm,
|
[ACTION_OPT_REGA_ALARM] = &ACTION_RegaAlarm,
|
||||||
[ACTION_OPT_REGA_TEST] = &ACTION_RegaTest,
|
[ACTION_OPT_REGA_TEST] = &ACTION_RegaTest,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
[ACTION_OPT_BEAM] = &ACTION_Beam,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(ARRAY_SIZE(action_opt_table) == ACTION_OPT_LEN);
|
static_assert(ARRAY_SIZE(action_opt_table) == ACTION_OPT_LEN);
|
||||||
|
|||||||
+4
-4
@@ -172,9 +172,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];
|
g_FSK_Buffer[i + 1] ^= Obfuscation[i % 8];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ bool AIRCOPY_SendMessage(void)
|
|||||||
|
|
||||||
g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
|
g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
|
||||||
|
|
||||||
AIRCOPY_Obfuscation();
|
AIRCOPY_Obfuscate(34);
|
||||||
|
|
||||||
RADIO_SetTxParameters();
|
RADIO_SetTxParameters();
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ void AIRCOPY_StorePacket(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AIRCOPY_Obfuscation();
|
AIRCOPY_Obfuscate(34);
|
||||||
|
|
||||||
uint16_t Crc = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
|
uint16_t Crc = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
|
||||||
if (g_FSK_Buffer[34] != Crc) {
|
if (g_FSK_Buffer[34] != Crc) {
|
||||||
|
|||||||
@@ -106,5 +106,9 @@ void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
|
|||||||
|
|
||||||
const AIRCOPY_TransferMap_t* AIRCOPY_GetCurrentMap(void);
|
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 // ENABLE_AIRCOPY
|
||||||
#endif // APP_AIRCOPY_H
|
#endif // APP_AIRCOPY_H
|
||||||
+60
-8
@@ -24,6 +24,9 @@
|
|||||||
#ifdef ENABLE_AIRCOPY
|
#ifdef ENABLE_AIRCOPY
|
||||||
#include "app/aircopy.h"
|
#include "app/aircopy.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
#include "app/beam.h"
|
||||||
|
#endif
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
#include "app/chFrScanner.h"
|
#include "app/chFrScanner.h"
|
||||||
#include "app/dtmf.h"
|
#include "app/dtmf.h"
|
||||||
@@ -796,6 +799,25 @@ static void CheckRadioInterrupts(void)
|
|||||||
AIRCOPY_StorePacket();
|
AIRCOPY_StorePacket();
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1044,6 +1066,9 @@ void APP_Update(void)
|
|||||||
&& gScanStateDir == SCAN_OFF
|
&& gScanStateDir == SCAN_OFF
|
||||||
&& !gPttIsPressed
|
&& !gPttIsPressed
|
||||||
&& gCurrentFunction != FUNCTION_POWER_SAVE
|
&& gCurrentFunction != FUNCTION_POWER_SAVE
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
&& !gBeamActive
|
||||||
|
#endif
|
||||||
#ifdef ENABLE_VOICE
|
#ifdef ENABLE_VOICE
|
||||||
&& gVoiceWriteIndex == 0
|
&& gVoiceWriteIndex == 0
|
||||||
#endif
|
#endif
|
||||||
@@ -1122,7 +1147,11 @@ void APP_Update(void)
|
|||||||
|
|
||||||
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF &&
|
if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF &&
|
||||||
gScanStateDir == SCAN_OFF &&
|
gScanStateDir == SCAN_OFF &&
|
||||||
!gCssBackgroundScan)
|
!gCssBackgroundScan
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
&& !gBeamActive
|
||||||
|
#endif
|
||||||
|
)
|
||||||
{ // dual watch mode, toggle between the two VFO's
|
{ // dual watch mode, toggle between the two VFO's
|
||||||
DualwatchAlternate();
|
DualwatchAlternate();
|
||||||
goToSleep = false;
|
goToSleep = false;
|
||||||
@@ -1133,7 +1162,11 @@ void APP_Update(void)
|
|||||||
gPowerSave_10ms = power_save1_10ms; // come back here in a bit
|
gPowerSave_10ms = power_save1_10ms; // come back here in a bit
|
||||||
gRxIdleMode = false; // RX is awake
|
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
|
{ // dual watch mode off or scanning or rssi update request
|
||||||
// go back to sleep
|
// go back to sleep
|
||||||
|
|
||||||
@@ -1152,7 +1185,11 @@ void APP_Update(void)
|
|||||||
// Authentic device checked removed
|
// Authentic device checked removed
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
if (!gBeamActive)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
// toggle between the two VFO's
|
// toggle between the two VFO's
|
||||||
DualwatchAlternate();
|
DualwatchAlternate();
|
||||||
gPowerSave_10ms = power_save1_10ms;
|
gPowerSave_10ms = power_save1_10ms;
|
||||||
@@ -1608,11 +1645,14 @@ void APP_TimeSlice500ms(void)
|
|||||||
gWakeUp = false;
|
gWakeUp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx()
|
||||||
#ifdef ENABLE_AIRCOPY
|
#ifdef ENABLE_AIRCOPY
|
||||||
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx() && gScreenToDisplay != DISPLAY_AIRCOPY)
|
&& gScreenToDisplay != DISPLAY_AIRCOPY
|
||||||
#else
|
|
||||||
if(gCurrentFunction != FUNCTION_TRANSMIT && !FUNCTION_IsRx())
|
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
&& !gBeamActive
|
||||||
|
#endif
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (gSleepModeCountdown_500ms > 0 && --gSleepModeCountdown_500ms == 0) {
|
if (gSleepModeCountdown_500ms > 0 && --gSleepModeCountdown_500ms == 0) {
|
||||||
gBacklightCountdown_500ms = 0;
|
gBacklightCountdown_500ms = 0;
|
||||||
@@ -1698,6 +1738,9 @@ void APP_TimeSlice500ms(void)
|
|||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_AIRCOPY
|
#ifdef ENABLE_AIRCOPY
|
||||||
&& gScreenToDisplay != DISPLAY_AIRCOPY
|
&& gScreenToDisplay != DISPLAY_AIRCOPY
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
&& !gBeamActive
|
||||||
#endif
|
#endif
|
||||||
) {
|
) {
|
||||||
if (gEeprom.AUTO_KEYPAD_LOCK && gKeyLockCountdown > 0 && !gDTMF_InputMode
|
if (gEeprom.AUTO_KEYPAD_LOCK && gKeyLockCountdown > 0 && !gDTMF_InputMode
|
||||||
@@ -2104,12 +2147,21 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (gScreenToDisplay != DISPLAY_INVALID && (
|
else if (
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
gBeamActive ||
|
||||||
|
#endif
|
||||||
|
(gScreenToDisplay != DISPLAY_INVALID && (
|
||||||
(Key != KEY_SIDE1 && Key != KEY_SIDE2)
|
(Key != KEY_SIDE1 && Key != KEY_SIDE2)
|
||||||
#ifdef ENABLE_FEAT_F4HWN // For F + SIDE1 or F + SIDE2
|
#ifdef ENABLE_FEAT_F4HWN // For F + SIDE1 or F + SIDE2
|
||||||
|| (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2))
|
|| (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2))
|
||||||
#endif
|
#endif
|
||||||
)) {
|
))) {
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
if (gBeamActive)
|
||||||
|
BEAM_ProcessKeys(Key, bKeyPressed, bKeyHeld);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld);
|
ProcessKeysFunctions[gScreenToDisplay](Key, bKeyPressed, bKeyHeld);
|
||||||
}
|
}
|
||||||
else if (!SCANNER_IsScanning()
|
else if (!SCANNER_IsScanning()
|
||||||
|
|||||||
+363
@@ -0,0 +1,363 @@
|
|||||||
|
/* Copyright 2026
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 gBeamBackupVfo;
|
||||||
|
static VFO_Info_t gBeamRadioVfo;
|
||||||
|
static uint16_t gBeamBackupScreenChannel;
|
||||||
|
static uint8_t gBeamVfoIndex;
|
||||||
|
static uint8_t gBeamBackupRxVfoIndex;
|
||||||
|
static bool gBeamHasBackup;
|
||||||
|
|
||||||
|
static void BEAM_SaveBackup(void)
|
||||||
|
{
|
||||||
|
gBeamVfoIndex = gEeprom.TX_VFO;
|
||||||
|
memcpy(&gBeamBackupVfo, &gEeprom.VfoInfo[gBeamVfoIndex], sizeof(gBeamBackupVfo));
|
||||||
|
gBeamBackupScreenChannel = gEeprom.ScreenChannel[gBeamVfoIndex];
|
||||||
|
gBeamBackupRxVfoIndex = gEeprom.RX_VFO;
|
||||||
|
gBeamHasBackup = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BEAM_RestoreBackup(void)
|
||||||
|
{
|
||||||
|
if (!gBeamHasBackup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
memcpy(&gEeprom.VfoInfo[gBeamVfoIndex], &gBeamBackupVfo, sizeof(gBeamBackupVfo));
|
||||||
|
gEeprom.ScreenChannel[gBeamVfoIndex] = gBeamBackupScreenChannel;
|
||||||
|
gEeprom.RX_VFO = gBeamBackupRxVfoIndex;
|
||||||
|
RADIO_SelectVfos();
|
||||||
|
RADIO_SetupRegisters(true);
|
||||||
|
gBeamHasBackup = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_CopyNameFromActiveVfo(char *name)
|
||||||
|
{
|
||||||
|
memset(name, 0, 16);
|
||||||
|
|
||||||
|
if (IS_MR_CHANNEL(gBeamBackupVfo.CHANNEL_SAVE)) {
|
||||||
|
SETTINGS_FetchChannelName(name, gBeamBackupVfo.CHANNEL_SAVE);
|
||||||
|
} else {
|
||||||
|
memcpy(name, gBeamBackupVfo.Name, sizeof(gBeamBackupVfo.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BEAM_FillPayload(BEAM_Payload_t *payload)
|
||||||
|
{
|
||||||
|
memset(payload, 0, sizeof(*payload));
|
||||||
|
|
||||||
|
payload->magic = BEAM_PACKET_MAGIC;
|
||||||
|
payload->version = BEAM_PACKET_VERSION;
|
||||||
|
payload->rx_frequency = gBeamBackupVfo.freq_config_RX.Frequency;
|
||||||
|
payload->tx_offset_frequency = gBeamBackupVfo.TX_OFFSET_FREQUENCY;
|
||||||
|
payload->rx_code = gBeamBackupVfo.freq_config_RX.Code;
|
||||||
|
payload->tx_code = gBeamBackupVfo.freq_config_TX.Code;
|
||||||
|
payload->rx_codetype = gBeamBackupVfo.freq_config_RX.CodeType;
|
||||||
|
payload->tx_codetype = gBeamBackupVfo.freq_config_TX.CodeType;
|
||||||
|
payload->modulation = gBeamBackupVfo.Modulation;
|
||||||
|
payload->tx_offset_direction = gBeamBackupVfo.TX_OFFSET_FREQUENCY_DIRECTION;
|
||||||
|
payload->tx_lock = gBeamBackupVfo.TX_LOCK;
|
||||||
|
payload->busy_channel_lock = gBeamBackupVfo.BUSY_CHANNEL_LOCK;
|
||||||
|
payload->output_power = gBeamBackupVfo.OUTPUT_POWER;
|
||||||
|
payload->channel_bandwidth = gBeamBackupVfo.CHANNEL_BANDWIDTH;
|
||||||
|
payload->frequency_reverse = gBeamBackupVfo.FrequencyReverse;
|
||||||
|
payload->dtmf_ptt_id_mode = gBeamBackupVfo.DTMF_PTT_ID_TX_MODE;
|
||||||
|
#ifdef ENABLE_DTMF_CALLING
|
||||||
|
payload->dtmf_decoding_enable = gBeamBackupVfo.DTMF_DECODING_ENABLE;
|
||||||
|
#endif
|
||||||
|
payload->step_setting = gBeamBackupVfo.STEP_SETTING;
|
||||||
|
payload->scrambling_type = gBeamBackupVfo.SCRAMBLING_TYPE;
|
||||||
|
payload->band = gBeamBackupVfo.Band;
|
||||||
|
payload->scanlist = gBeamBackupVfo.SCANLIST_PARTICIPATION;
|
||||||
|
payload->compander = gBeamBackupVfo.Compander;
|
||||||
|
|
||||||
|
BEAM_CopyNameFromActiveVfo(payload->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BEAM_SendPacket(void)
|
||||||
|
{
|
||||||
|
BEAM_Payload_t payload;
|
||||||
|
|
||||||
|
BEAM_FillPayload(&payload);
|
||||||
|
|
||||||
|
memset(g_FSK_Buffer, 0, sizeof(g_FSK_Buffer));
|
||||||
|
g_FSK_Buffer[0] = 0xABCDu;
|
||||||
|
memcpy(&g_FSK_Buffer[2], &payload, sizeof(payload));
|
||||||
|
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 uint16_t BEAM_FindFirstFreeChannel(void)
|
||||||
|
{
|
||||||
|
for (uint16_t channel = MR_CHANNEL_FIRST; IS_MR_CHANNEL(channel); channel++) {
|
||||||
|
if (!RADIO_CheckValidChannel(channel, false, 0))
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xFFFFu;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool BEAM_SavePayloadToFirstFreeChannel(const BEAM_Payload_t *payload)
|
||||||
|
{
|
||||||
|
uint16_t channel = BEAM_FindFirstFreeChannel();
|
||||||
|
char name[16];
|
||||||
|
|
||||||
|
if (channel == 0xFFFFu) {
|
||||||
|
gBeamStatus = BEAM_STATUS_RX_FULL;
|
||||||
|
gUpdateDisplay = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(name, payload->name, sizeof(name));
|
||||||
|
name[sizeof(name) - 1] = 0;
|
||||||
|
|
||||||
|
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, name, sizeof(vfo.Name));
|
||||||
|
RADIO_ApplyOffset(&vfo);
|
||||||
|
RADIO_ConfigureSquelchAndOutputPower(&vfo);
|
||||||
|
|
||||||
|
SETTINGS_SaveChannel(channel, gBeamVfoIndex, &vfo, 3);
|
||||||
|
SETTINGS_SaveChannelName(channel, name);
|
||||||
|
|
||||||
|
gBeamCopiedChannel = channel;
|
||||||
|
gBeamStatus = BEAM_STATUS_RX_SAVED;
|
||||||
|
gUpdateDisplay = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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[gBeamVfoIndex] = gBeamCopiedChannel;
|
||||||
|
gEeprom.ScreenChannel[gBeamVfoIndex] = gBeamCopiedChannel;
|
||||||
|
RADIO_ConfigureChannel(gBeamVfoIndex, VFO_CONFIGURE_RELOAD);
|
||||||
|
RADIO_SelectVfos();
|
||||||
|
RADIO_SetupRegisters(true);
|
||||||
|
gBeamHasBackup = false;
|
||||||
|
} else {
|
||||||
|
BEAM_RestoreBackup();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI_SelectNextDisplay(DISPLAY_MAIN);
|
||||||
|
gRequestDisplayScreen = DISPLAY_MAIN;
|
||||||
|
gBeamActive = false;
|
||||||
|
gUpdateDisplay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ACTION_Beam(void)
|
||||||
|
{
|
||||||
|
BEAM_SaveBackup();
|
||||||
|
gBeamMode = BEAM_MODE_TX;
|
||||||
|
gBeamStatus = BEAM_STATUS_READY;
|
||||||
|
gBeamCopiedChannel = 0xFFFFu;
|
||||||
|
gBeamRxWordCount = 0;
|
||||||
|
gBeamActive = true;
|
||||||
|
GUI_SelectNextDisplay(DISPLAY_MAIN);
|
||||||
|
gRequestDisplayScreen = DISPLAY_MAIN;
|
||||||
|
gUpdateDisplay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (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 payload;
|
||||||
|
memcpy(&payload, &g_FSK_Buffer[2], sizeof(payload));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ENABLE_FEAT_F4HWN_BEAM
|
||||||
@@ -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
|
||||||
@@ -127,6 +127,9 @@ enum ACTION_OPT_t {
|
|||||||
#ifdef ENABLE_REGA
|
#ifdef ENABLE_REGA
|
||||||
ACTION_OPT_REGA_ALARM,
|
ACTION_OPT_REGA_ALARM,
|
||||||
ACTION_OPT_REGA_TEST,
|
ACTION_OPT_REGA_TEST,
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
ACTION_OPT_BEAM,
|
||||||
#endif
|
#endif
|
||||||
ACTION_OPT_LEN
|
ACTION_OPT_LEN
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
#include "app/chFrScanner.h"
|
#include "app/chFrScanner.h"
|
||||||
#include "app/dtmf.h"
|
#include "app/dtmf.h"
|
||||||
|
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
#include "app/beam.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_AM_FIX
|
#ifdef ENABLE_AM_FIX
|
||||||
#include "am_fix.h"
|
#include "am_fix.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -84,6 +89,46 @@ static uint8_t gScanProgressPriorityState;
|
|||||||
#define SCAN_PROGRESS_PRIORITY_HOLD_FRAMES 6
|
#define SCAN_PROGRESS_PRIORITY_HOLD_FRAMES 6
|
||||||
#endif
|
#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[] = {
|
const char *VfoStateStr[] = {
|
||||||
[VFO_STATE_NORMAL]="",
|
[VFO_STATE_NORMAL]="",
|
||||||
[VFO_STATE_BUSY]="BUSY",
|
[VFO_STATE_BUSY]="BUSY",
|
||||||
@@ -2047,6 +2092,13 @@ void UI_DisplayMain(void)
|
|||||||
|
|
||||||
const bool rx = FUNCTION_IsRx();
|
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
|
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
|
||||||
if (!rx && gScanStateDir != SCAN_OFF && UI_DrawScanProgress())
|
if (!rx && gScanStateDir != SCAN_OFF && UI_DrawScanProgress())
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-1
@@ -28,7 +28,10 @@ enum center_line_t {
|
|||||||
CENTER_LINE_RSSI,
|
CENTER_LINE_RSSI,
|
||||||
CENTER_LINE_AM_FIX_DATA,
|
CENTER_LINE_AM_FIX_DATA,
|
||||||
CENTER_LINE_DTMF_DEC,
|
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{
|
enum Vfo_txtr_mode{
|
||||||
|
|||||||
@@ -487,6 +487,9 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
|
|||||||
#ifdef ENABLE_REGA
|
#ifdef ENABLE_REGA
|
||||||
{"REGA\nALARM", ACTION_OPT_REGA_ALARM},
|
{"REGA\nALARM", ACTION_OPT_REGA_ALARM},
|
||||||
{"REGA\nTEST", ACTION_OPT_REGA_TEST},
|
{"REGA\nTEST", ACTION_OPT_REGA_TEST},
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
{"BEAM", ACTION_OPT_BEAM},
|
||||||
#endif
|
#endif
|
||||||
{"LOCK\nKEYPAD", ACTION_OPT_KEYLOCK},
|
{"LOCK\nKEYPAD", ACTION_OPT_KEYLOCK},
|
||||||
{"VFO A\nVFO B", ACTION_OPT_A_B},
|
{"VFO A\nVFO B", ACTION_OPT_A_B},
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
"ENABLE_FEAT_F4HWN_MEM": false,
|
"ENABLE_FEAT_F4HWN_MEM": false,
|
||||||
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": false,
|
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": false,
|
||||||
"ENABLE_FEAT_F4HWN_SCAN_FASTER": false,
|
"ENABLE_FEAT_F4HWN_SCAN_FASTER": false,
|
||||||
|
"ENABLE_FEAT_F4HWN_BEAM": false,
|
||||||
"ENABLE_AGC_SHOW_DATA": false,
|
"ENABLE_AGC_SHOW_DATA": false,
|
||||||
"ENABLE_UART_RW_BK_REGS": false,
|
"ENABLE_UART_RW_BK_REGS": false,
|
||||||
"ENABLE_SWD": false,
|
"ENABLE_SWD": false,
|
||||||
@@ -196,6 +197,7 @@
|
|||||||
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
|
"ENABLE_FEAT_F4HWN_AUDIO_SCOPE": true,
|
||||||
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": true,
|
"ENABLE_FEAT_F4HWN_SCAN_PROGRESS": true,
|
||||||
"ENABLE_FEAT_F4HWN_SCAN_FASTER": true,
|
"ENABLE_FEAT_F4HWN_SCAN_FASTER": true,
|
||||||
|
"ENABLE_FEAT_F4HWN_BEAM": true,
|
||||||
"ENABLE_FEAT_F4HWN_QRCODE": true,
|
"ENABLE_FEAT_F4HWN_QRCODE": true,
|
||||||
"ENABLE_FEAT_F4HWN_MEM": true,
|
"ENABLE_FEAT_F4HWN_MEM": true,
|
||||||
"ENABLE_SWD": true,
|
"ENABLE_SWD": true,
|
||||||
|
|||||||
Reference in new issue
Block a user