mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Port BEAM to overlay apps
This commit is contained in:
1 parent
62906636ed
commit
902e19ba8d
12 files changed
+585
-9
No files matched your search
+18
-2
@@ -58,6 +58,10 @@
|
|||||||
#include "ui/menu.h"
|
#include "ui/menu.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) && !defined(ENABLE_FEAT_F4HWN_BEAM)
|
||||||
|
static void ACTION_Beam(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(ENABLE_FMRADIO)
|
#if defined(ENABLE_FMRADIO)
|
||||||
static void ACTION_Scan_FM(bool bRestart);
|
static void ACTION_Scan_FM(bool bRestart);
|
||||||
#endif
|
#endif
|
||||||
@@ -113,7 +117,7 @@ void (*const action_opt_table[ACTION_OPT_LEN])(void) = {
|
|||||||
[ACTION_OPT_REMOVE_OFFSET] = &ACTION_Remove_Offset,
|
[ACTION_OPT_REMOVE_OFFSET] = &ACTION_Remove_Offset,
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
#if defined(ENABLE_FEAT_F4HWN_BEAM) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
||||||
[ACTION_OPT_BEAM] = &ACTION_Beam,
|
[ACTION_OPT_BEAM] = &ACTION_Beam,
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||||
@@ -153,6 +157,10 @@ bool ACTION_IsAvailable(uint8_t action)
|
|||||||
#ifndef ENABLE_FEAT_F4HWN_BEACON
|
#ifndef ENABLE_FEAT_F4HWN_BEACON
|
||||||
case ACTION_OPT_BEACON:
|
case ACTION_OPT_BEACON:
|
||||||
return (APP_OverlayShortcutMask() & APP_SHORTCUT_BEACON) != 0;
|
return (APP_OverlayShortcutMask() & APP_SHORTCUT_BEACON) != 0;
|
||||||
|
#endif
|
||||||
|
#ifndef ENABLE_FEAT_F4HWN_BEAM
|
||||||
|
case ACTION_OPT_BEAM:
|
||||||
|
return (APP_OverlayShortcutMask() & APP_SHORTCUT_BEAM) != 0;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -338,7 +346,7 @@ inline static bool ACTION_IsBlockedInFM(uint8_t action)
|
|||||||
case ACTION_OPT_REMOVE_OFFSET:
|
case ACTION_OPT_REMOVE_OFFSET:
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
#if defined(ENABLE_FEAT_F4HWN_BEAM) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
||||||
case ACTION_OPT_BEAM:
|
case ACTION_OPT_BEAM:
|
||||||
#endif
|
#endif
|
||||||
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
||||||
@@ -495,6 +503,14 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
|||||||
ACTION_Execute(func);
|
ACTION_Execute(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) && !defined(ENABLE_FEAT_F4HWN_BEAM)
|
||||||
|
static void ACTION_Beam(void)
|
||||||
|
{
|
||||||
|
if (APP_LaunchOverlayShortcut(APP_SHORTCUT_BEAM) != APP_OK)
|
||||||
|
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
||||||
void ACTION_FoxHunt(void)
|
void ACTION_FoxHunt(void)
|
||||||
{
|
{
|
||||||
|
|||||||
+46
-1
@@ -40,7 +40,7 @@
|
|||||||
* reorder, a removal, or an append - MUST bump this. Keep in sync with the
|
* reorder, a removal, or an append - MUST bump this. Keep in sync with the
|
||||||
* value read by pack_app.py, which
|
* value read by pack_app.py, which
|
||||||
* stamps the blob the loader checks against. */
|
* stamps the blob the loader checks against. */
|
||||||
#define APP_ABI_VERSION 6u
|
#define APP_ABI_VERSION 8u
|
||||||
|
|
||||||
/* KEY codes mirrored from driver/keyboard.h (enum KEY_Code_e). Kept in sync by
|
/* KEY codes mirrored from driver/keyboard.h (enum KEY_Code_e). Kept in sync by
|
||||||
* value so the app stays independent of the firmware headers. */
|
* value so the app stays independent of the firmware headers. */
|
||||||
@@ -120,6 +120,41 @@ enum {
|
|||||||
APP_TRIVFO_TX_STATE = 3,
|
APP_TRIVFO_TX_STATE = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Pointer-free BEAM channel description. The resident bridge translates this
|
||||||
|
* stable ABI type to/from feature-dependent VFO_Info_t. */
|
||||||
|
typedef struct {
|
||||||
|
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];
|
||||||
|
} app_beam_channel_t;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(app_beam_channel_t) == 44u,
|
||||||
|
"BEAM ABI/wire channel layout changed");
|
||||||
|
|
||||||
|
enum {
|
||||||
|
APP_BEAM_RX_WAIT = 0,
|
||||||
|
APP_BEAM_RX_READY = 1,
|
||||||
|
APP_BEAM_RX_ERROR = 2,
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct app_api {
|
typedef struct app_api {
|
||||||
uint8_t abi_version; /* == APP_ABI_VERSION */
|
uint8_t abi_version; /* == APP_ABI_VERSION */
|
||||||
|
|
||||||
@@ -223,6 +258,16 @@ typedef struct app_api {
|
|||||||
uint16_t (*trivfo_step)(uint8_t vfo, int8_t direction);
|
uint16_t (*trivfo_step)(uint8_t vfo, int8_t direction);
|
||||||
uint8_t (*trivfo_tick)(void);
|
uint8_t (*trivfo_tick)(void);
|
||||||
uint8_t (*trivfo_ptt)(bool pressed); /* physical PTT edge; resident applies SetPTT */
|
uint8_t (*trivfo_ptt)(bool pressed); /* physical PTT edge; resident applies SetPTT */
|
||||||
|
|
||||||
|
/* ---- BEAM channel transfer (ABI 8) ---- */
|
||||||
|
void (*beam_prepare)(void); /* tune the fixed narrow-band FSK channel */
|
||||||
|
void (*beam_leave)(void); /* defensively stop FSK before app return */
|
||||||
|
void (*beam_get)(app_beam_channel_t *channel); /* export selected VFO */
|
||||||
|
uint16_t (*beam_save)(const app_beam_channel_t *channel); /* first free MR, or 0xffff */
|
||||||
|
void (*beam_send)(uint16_t *packet); /* transmit one 36-word FSK packet */
|
||||||
|
void (*beam_rx)(bool start); /* arm or stop FSK reception */
|
||||||
|
uint8_t (*beam_rx_poll)(uint16_t *packet); /* APP_BEAM_RX_* */
|
||||||
|
void (*beam_draw)(const char *status); /* MAIN display with one BEAM center line */
|
||||||
} app_api_t;
|
} app_api_t;
|
||||||
|
|
||||||
/* BK4819 AF modes for set_af (mirror driver/bk4819.h values). */
|
/* BK4819 AF modes for set_af (mirror driver/bk4819.h values). */
|
||||||
|
|||||||
+207
-1
@@ -525,6 +525,200 @@ static uint8_t app_trivfo_ptt(bool pressed)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- ABI 8: BEAM radio/channel bridge ------------------------------------
|
||||||
|
* The modal app owns the packet format, CRC, UI and state machine. Resident
|
||||||
|
* code only translates the stable ABI channel structure and performs the FSK
|
||||||
|
* operations which depend on VFO_Info_t and the BK4819 driver. */
|
||||||
|
static VFO_Info_t app_beam_vfo;
|
||||||
|
static uint8_t app_beam_fsk_index;
|
||||||
|
static uint16_t app_beam_pending_channel;
|
||||||
|
static bool app_beam_dirty;
|
||||||
|
|
||||||
|
static void app_beam_prepare(void)
|
||||||
|
{
|
||||||
|
const uint16_t channel = FREQ_CHANNEL_FIRST + BAND6_400MHz;
|
||||||
|
RADIO_InitInfo(&app_beam_vfo, channel, DEFAULT_FREQ);
|
||||||
|
app_beam_vfo.CHANNEL_BANDWIDTH = BANDWIDTH_NARROW;
|
||||||
|
app_beam_vfo.OUTPUT_POWER = OUTPUT_POWER_LOW1;
|
||||||
|
RADIO_ConfigureSquelchAndOutputPower(&app_beam_vfo);
|
||||||
|
|
||||||
|
gRxVfo = &app_beam_vfo;
|
||||||
|
gTxVfo = &app_beam_vfo;
|
||||||
|
gCurrentVfo = &app_beam_vfo;
|
||||||
|
RADIO_SetupRegisters(true);
|
||||||
|
BK4819_SetupAircopy();
|
||||||
|
BK4819_ResetFSK();
|
||||||
|
app_beam_fsk_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_beam_leave(void)
|
||||||
|
{
|
||||||
|
BK4819_ResetFSK();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_beam_get(app_beam_channel_t *out)
|
||||||
|
{
|
||||||
|
if (out == NULL)
|
||||||
|
return;
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
const VFO_Info_t *vfo = &gEeprom.VfoInfo[gEeprom.TX_VFO];
|
||||||
|
out->rx_frequency = vfo->freq_config_RX.Frequency;
|
||||||
|
out->tx_offset_frequency = vfo->TX_OFFSET_FREQUENCY;
|
||||||
|
out->rx_code = vfo->freq_config_RX.Code;
|
||||||
|
out->tx_code = vfo->freq_config_TX.Code;
|
||||||
|
out->rx_codetype = vfo->freq_config_RX.CodeType;
|
||||||
|
out->tx_codetype = vfo->freq_config_TX.CodeType;
|
||||||
|
out->modulation = vfo->Modulation;
|
||||||
|
out->tx_offset_direction = vfo->TX_OFFSET_FREQUENCY_DIRECTION;
|
||||||
|
out->tx_lock = vfo->TX_LOCK;
|
||||||
|
out->busy_channel_lock = vfo->BUSY_CHANNEL_LOCK;
|
||||||
|
out->output_power = vfo->OUTPUT_POWER;
|
||||||
|
out->channel_bandwidth = vfo->CHANNEL_BANDWIDTH;
|
||||||
|
out->frequency_reverse = vfo->FrequencyReverse;
|
||||||
|
out->dtmf_ptt_id_mode = vfo->DTMF_PTT_ID_TX_MODE;
|
||||||
|
#ifdef ENABLE_DTMF_CALLING
|
||||||
|
out->dtmf_decoding_enable = vfo->DTMF_DECODING_ENABLE;
|
||||||
|
#endif
|
||||||
|
out->step_setting = vfo->STEP_SETTING;
|
||||||
|
out->band = vfo->Band;
|
||||||
|
out->scanlist = vfo->SCANLIST_PARTICIPATION;
|
||||||
|
out->compander = vfo->Compander;
|
||||||
|
if (IS_MR_CHANNEL(vfo->CHANNEL_SAVE))
|
||||||
|
SETTINGS_FetchChannelName(out->name, vfo->CHANNEL_SAVE);
|
||||||
|
else
|
||||||
|
memcpy(out->name, vfo->Name, sizeof(out->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t app_beam_save(const app_beam_channel_t *in)
|
||||||
|
{
|
||||||
|
if (in == NULL)
|
||||||
|
return 0xFFFFu;
|
||||||
|
|
||||||
|
uint16_t channel = MR_CHANNEL_FIRST;
|
||||||
|
while (IS_MR_CHANNEL(channel) && RADIO_CheckValidChannel(channel, false, 0))
|
||||||
|
channel++;
|
||||||
|
if (!IS_MR_CHANNEL(channel))
|
||||||
|
return 0xFFFFu;
|
||||||
|
|
||||||
|
/* Reuse the temporary BEAM VFO as resident staging RAM. No external-flash
|
||||||
|
* write is allowed while the overlay still executes from that flash's
|
||||||
|
* 4 KiB sector cache. */
|
||||||
|
RADIO_InitInfo(&app_beam_vfo, channel, in->rx_frequency);
|
||||||
|
app_beam_vfo.TX_OFFSET_FREQUENCY = in->tx_offset_frequency;
|
||||||
|
app_beam_vfo.freq_config_RX.Code = in->rx_code;
|
||||||
|
app_beam_vfo.freq_config_TX.Code = in->tx_code;
|
||||||
|
app_beam_vfo.freq_config_RX.CodeType = in->rx_codetype;
|
||||||
|
app_beam_vfo.freq_config_TX.CodeType = in->tx_codetype;
|
||||||
|
app_beam_vfo.TX_OFFSET_FREQUENCY_DIRECTION = in->tx_offset_direction;
|
||||||
|
app_beam_vfo.Modulation = in->modulation;
|
||||||
|
app_beam_vfo.TX_LOCK = in->tx_lock;
|
||||||
|
app_beam_vfo.BUSY_CHANNEL_LOCK = in->busy_channel_lock;
|
||||||
|
app_beam_vfo.OUTPUT_POWER = in->output_power;
|
||||||
|
app_beam_vfo.CHANNEL_BANDWIDTH = in->channel_bandwidth;
|
||||||
|
app_beam_vfo.FrequencyReverse = in->frequency_reverse;
|
||||||
|
app_beam_vfo.DTMF_PTT_ID_TX_MODE = in->dtmf_ptt_id_mode;
|
||||||
|
#ifdef ENABLE_DTMF_CALLING
|
||||||
|
app_beam_vfo.DTMF_DECODING_ENABLE = in->dtmf_decoding_enable;
|
||||||
|
#endif
|
||||||
|
app_beam_vfo.STEP_SETTING = in->step_setting < STEP_N_ELEM ? in->step_setting : STEP_12_5kHz;
|
||||||
|
app_beam_vfo.StepFrequency = gStepFrequencyTable[app_beam_vfo.STEP_SETTING];
|
||||||
|
app_beam_vfo.SCANLIST_PARTICIPATION = in->scanlist;
|
||||||
|
app_beam_vfo.Compander = in->compander;
|
||||||
|
memcpy(app_beam_vfo.Name, in->name, sizeof(app_beam_vfo.Name));
|
||||||
|
app_beam_vfo.Name[sizeof(app_beam_vfo.Name) - 1u] = '\0';
|
||||||
|
|
||||||
|
app_beam_pending_channel = channel;
|
||||||
|
app_beam_dirty = true;
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called only after the overlay has returned and its code no longer executes
|
||||||
|
* from the PY25Q16 sector cache. */
|
||||||
|
static void app_beam_commit(void)
|
||||||
|
{
|
||||||
|
if (!app_beam_dirty)
|
||||||
|
return;
|
||||||
|
app_beam_dirty = false;
|
||||||
|
|
||||||
|
const uint16_t channel = app_beam_pending_channel;
|
||||||
|
|
||||||
|
SETTINGS_SaveChannel(channel, gEeprom.TX_VFO, &app_beam_vfo, 3);
|
||||||
|
#ifndef ENABLE_KEEP_MEM_NAME
|
||||||
|
SETTINGS_SaveChannelName(channel, app_beam_vfo.Name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gEeprom.MrChannel[gEeprom.TX_VFO] = channel;
|
||||||
|
gEeprom.ScreenChannel[gEeprom.TX_VFO] = channel;
|
||||||
|
RADIO_ConfigureChannel(gEeprom.TX_VFO, VFO_CONFIGURE_RELOAD);
|
||||||
|
RADIO_SelectVfos();
|
||||||
|
RADIO_SetupRegisters(true);
|
||||||
|
PY25Q16_InvalidateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_beam_send(uint16_t *packet)
|
||||||
|
{
|
||||||
|
if (packet == NULL)
|
||||||
|
return;
|
||||||
|
RADIO_SetTxParameters();
|
||||||
|
BK4819_SendFSKData(packet);
|
||||||
|
BK4819_SetupPowerAmplifier(0, 0);
|
||||||
|
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
|
||||||
|
RADIO_SelectVfos();
|
||||||
|
RADIO_SetupRegisters(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_beam_rx(bool start)
|
||||||
|
{
|
||||||
|
app_beam_fsk_index = 0;
|
||||||
|
if (start)
|
||||||
|
BK4819_PrepareFSKReceive();
|
||||||
|
else
|
||||||
|
BK4819_ResetFSK();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t app_beam_rx_poll(uint16_t *packet)
|
||||||
|
{
|
||||||
|
if (packet == NULL)
|
||||||
|
return APP_BEAM_RX_ERROR;
|
||||||
|
|
||||||
|
while (BK4819_ReadRegister(BK4819_REG_0C) & 1u) {
|
||||||
|
BK4819_WriteRegister(BK4819_REG_02, 0);
|
||||||
|
const uint16_t irq = BK4819_ReadRegister(BK4819_REG_02);
|
||||||
|
if (irq & (BK4819_REG_02_FSK_FIFO_ALMOST_FULL | BK4819_REG_02_FSK_RX_FINISHED)) {
|
||||||
|
const unsigned words = (irq & BK4819_REG_02_FSK_RX_FINISHED)
|
||||||
|
? (app_beam_fsk_index < 36u ? 36u - app_beam_fsk_index : 0u)
|
||||||
|
: 4u;
|
||||||
|
for (unsigned i = 0; i < words; i++) {
|
||||||
|
const uint16_t word = BK4819_ReadRegister(BK4819_REG_5F);
|
||||||
|
if (app_beam_fsk_index < 36u)
|
||||||
|
packet[app_beam_fsk_index++] = word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app_beam_fsk_index < 36u)
|
||||||
|
return APP_BEAM_RX_WAIT;
|
||||||
|
|
||||||
|
app_beam_fsk_index = 0;
|
||||||
|
const uint16_t status = BK4819_ReadRegister(BK4819_REG_0B);
|
||||||
|
BK4819_PrepareFSKReceive();
|
||||||
|
return (status & 0x0010u) ? APP_BEAM_RX_ERROR : APP_BEAM_RX_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_beam_draw(const char *status)
|
||||||
|
{
|
||||||
|
UI_DisplayStatus();
|
||||||
|
UI_DisplayMain();
|
||||||
|
#ifdef ENABLE_FEAT_F4HWN
|
||||||
|
const uint8_t line = (gEeprom.DUAL_WATCH == DUAL_WATCH_OFF &&
|
||||||
|
gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF) ? 5u : 3u;
|
||||||
|
#else
|
||||||
|
const uint8_t line = 3u;
|
||||||
|
#endif
|
||||||
|
memset(gFrameBuffer[line], 0, LCD_WIDTH);
|
||||||
|
UI_PrintStringSmallBold(status, 2, LCD_WIDTH - 1u, line);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- v2 radio wrappers ---- */
|
/* ---- v2 radio wrappers ---- */
|
||||||
static int16_t app_rssi_dbm(void) { return BK4819_GetRSSI_dBm() + dBmCorrTable[gRxVfo->Band]; }
|
static int16_t app_rssi_dbm(void) { return BK4819_GetRSSI_dBm() + dBmCorrTable[gRxVfo->Band]; }
|
||||||
static uint16_t app_bk_read(uint8_t r) { return BK4819_ReadRegister((BK4819_REGISTER_t)r); }
|
static uint16_t app_bk_read(uint8_t r) { return BK4819_ReadRegister((BK4819_REGISTER_t)r); }
|
||||||
@@ -669,13 +863,14 @@ uint8_t APP_ValidateSlot(uint8_t slot, app_header_t *out_header)
|
|||||||
|
|
||||||
static bool app_shortcuts_cached;
|
static bool app_shortcuts_cached;
|
||||||
static uint8_t app_shortcut_mask;
|
static uint8_t app_shortcut_mask;
|
||||||
static uint8_t app_shortcut_slots[3];
|
static uint8_t app_shortcut_slots[4];
|
||||||
|
|
||||||
static int8_t app_shortcut_index(uint8_t shortcut)
|
static int8_t app_shortcut_index(uint8_t shortcut)
|
||||||
{
|
{
|
||||||
if (shortcut == APP_SHORTCUT_FM) return 0;
|
if (shortcut == APP_SHORTCUT_FM) return 0;
|
||||||
if (shortcut == APP_SHORTCUT_FOXHUNT) return 1;
|
if (shortcut == APP_SHORTCUT_FOXHUNT) return 1;
|
||||||
if (shortcut == APP_SHORTCUT_BEACON) return 2;
|
if (shortcut == APP_SHORTCUT_BEACON) return 2;
|
||||||
|
if (shortcut == APP_SHORTCUT_BEAM) return 3;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,6 +1002,14 @@ static const app_api_t app_api = {
|
|||||||
.trivfo_step = app_trivfo_step,
|
.trivfo_step = app_trivfo_step,
|
||||||
.trivfo_tick = app_trivfo_tick,
|
.trivfo_tick = app_trivfo_tick,
|
||||||
.trivfo_ptt = app_trivfo_ptt,
|
.trivfo_ptt = app_trivfo_ptt,
|
||||||
|
.beam_prepare = app_beam_prepare,
|
||||||
|
.beam_leave = app_beam_leave,
|
||||||
|
.beam_get = app_beam_get,
|
||||||
|
.beam_save = app_beam_save,
|
||||||
|
.beam_send = app_beam_send,
|
||||||
|
.beam_rx = app_beam_rx,
|
||||||
|
.beam_rx_poll = app_beam_rx_poll,
|
||||||
|
.beam_draw = app_beam_draw,
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t APP_LaunchOverlay(uint8_t slot)
|
uint8_t APP_LaunchOverlay(uint8_t slot)
|
||||||
@@ -845,6 +1048,7 @@ uint8_t APP_LaunchOverlay(uint8_t slot)
|
|||||||
#ifdef ENABLE_FMRADIO
|
#ifdef ENABLE_FMRADIO
|
||||||
app_fm_dirty = false;
|
app_fm_dirty = false;
|
||||||
#endif
|
#endif
|
||||||
|
app_beam_dirty = false;
|
||||||
|
|
||||||
app_allow_screen_saver = (h.flags & APP_FLAG_SCREEN_SAVER) != 0;
|
app_allow_screen_saver = (h.flags & APP_FLAG_SCREEN_SAVER) != 0;
|
||||||
app_screen_saver_wake = false;
|
app_screen_saver_wake = false;
|
||||||
@@ -891,6 +1095,8 @@ uint8_t APP_LaunchOverlay(uint8_t slot)
|
|||||||
/* The overlay held app code, not a valid config sector. */
|
/* The overlay held app code, not a valid config sector. */
|
||||||
PY25Q16_InvalidateCache();
|
PY25Q16_InvalidateCache();
|
||||||
|
|
||||||
|
app_beam_commit();
|
||||||
|
|
||||||
if (app_trivfo_ab_dirty) {
|
if (app_trivfo_ab_dirty) {
|
||||||
SETTINGS_SaveVfoIndices();
|
SETTINGS_SaveVfoIndices();
|
||||||
app_trivfo_ab_dirty = false;
|
app_trivfo_ab_dirty = false;
|
||||||
|
|||||||
@@ -69,13 +69,14 @@
|
|||||||
#define APP_FLAG_COMMITTED 0x0001u
|
#define APP_FLAG_COMMITTED 0x0001u
|
||||||
#define APP_FLAG_SCREEN_SAVER 0x0002u
|
#define APP_FLAG_SCREEN_SAVER 0x0002u
|
||||||
#define APP_FLAG_SHORTCUT_SHIFT 8u
|
#define APP_FLAG_SHORTCUT_SHIFT 8u
|
||||||
#define APP_FLAG_SHORTCUT_MASK 0x0700u
|
#define APP_FLAG_SHORTCUT_MASK 0x0F00u
|
||||||
#define APP_NAME_LEN 16
|
#define APP_NAME_LEN 16
|
||||||
#define APP_VERSION_LEN 16
|
#define APP_VERSION_LEN 16
|
||||||
|
|
||||||
#define APP_SHORTCUT_FM 0x01u
|
#define APP_SHORTCUT_FM 0x01u
|
||||||
#define APP_SHORTCUT_FOXHUNT 0x02u
|
#define APP_SHORTCUT_FOXHUNT 0x02u
|
||||||
#define APP_SHORTCUT_BEACON 0x04u
|
#define APP_SHORTCUT_BEACON 0x04u
|
||||||
|
#define APP_SHORTCUT_BEAM 0x08u
|
||||||
|
|
||||||
typedef struct __attribute__((packed)) {
|
typedef struct __attribute__((packed)) {
|
||||||
uint32_t magic; /* APP_MAGIC */
|
uint32_t magic; /* APP_MAGIC */
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/* Overlay-app link script. */
|
||||||
|
APP_VMA = DEFINED(APP_VMA) ? APP_VMA : 0x20000280;
|
||||||
|
APP_LENGTH = 0x1000;
|
||||||
|
|
||||||
|
ENTRY(app_main)
|
||||||
|
|
||||||
|
MEMORY {
|
||||||
|
APP (rwx) : ORIGIN = APP_VMA, LENGTH = APP_LENGTH
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTIONS {
|
||||||
|
.app APP_VMA : {
|
||||||
|
KEEP(*(.text.entry))
|
||||||
|
*(.text .text.*)
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.data .data.*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__app_bss_start = .;
|
||||||
|
*(.bss .bss.* COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__app_bss_end = .;
|
||||||
|
} > APP
|
||||||
|
|
||||||
|
__app_end = .;
|
||||||
|
ASSERT(__app_end <= APP_VMA + APP_LENGTH,
|
||||||
|
"BEAM overlay app overflows the 4 KiB overlay")
|
||||||
|
|
||||||
|
/DISCARD/ : { *(.ARM.exidx*) *(.ARM.extab*) *(.eh_frame*) *(.comment) *(.note.*) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/* Copyright 2026 Armel F4HWN
|
||||||
|
* Licensed under the Apache License, Version 2.0.
|
||||||
|
*
|
||||||
|
* BEAM overlay app. Packet version 2 remains wire-compatible with the
|
||||||
|
* resident implementation: one selected VFO is sent and a received VFO is
|
||||||
|
* stored in the first free memory channel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "../app_api.h"
|
||||||
|
|
||||||
|
#define PACKET_MAGIC 0xBEA5u
|
||||||
|
#define PACKET_VERSION 2u
|
||||||
|
#define PACKET_START 0xABCDu
|
||||||
|
#define PACKET_END 0xDCBAu
|
||||||
|
#define PACKET_WORDS 36u
|
||||||
|
|
||||||
|
enum {
|
||||||
|
STATUS_READY = 0,
|
||||||
|
STATUS_TX_WAIT,
|
||||||
|
STATUS_TX_DONE,
|
||||||
|
STATUS_RX_WAIT,
|
||||||
|
STATUS_RX_SAVED,
|
||||||
|
STATUS_RX_FULL,
|
||||||
|
STATUS_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t magic;
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t pad;
|
||||||
|
app_beam_channel_t channel;
|
||||||
|
} beam_payload_t;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(beam_payload_t) == 48u,
|
||||||
|
"BEAM v2 wire payload layout changed");
|
||||||
|
|
||||||
|
static const uint16_t OBFUSCATION[8] = {
|
||||||
|
0x6C16, 0xE614, 0x912E, 0x400D, 0x3521, 0x40D5, 0x0313, 0x80E9
|
||||||
|
};
|
||||||
|
|
||||||
|
static const app_api_t *A;
|
||||||
|
static uint32_t packet_store[18]; /* 72 bytes, 4-byte aligned for the payload */
|
||||||
|
static uint16_t copiedChannel;
|
||||||
|
static uint8_t mode, status;
|
||||||
|
static bool receiving, running, dirty;
|
||||||
|
|
||||||
|
static uint16_t *packet(void) { return (uint16_t *)packet_store; }
|
||||||
|
static beam_payload_t *payload(void) { return (beam_payload_t *)&packet()[2]; }
|
||||||
|
|
||||||
|
static void clear_bytes(void *ptr, uint8_t count)
|
||||||
|
{
|
||||||
|
uint8_t *p = (uint8_t *)ptr;
|
||||||
|
while (count--) *p++ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t crc16(const void *buffer, uint16_t size)
|
||||||
|
{
|
||||||
|
const uint8_t *data = (const uint8_t *)buffer;
|
||||||
|
uint16_t crc = 0;
|
||||||
|
while (size--) {
|
||||||
|
crc ^= (uint16_t)*data++ << 8;
|
||||||
|
for (uint8_t bit = 0; bit < 8u; bit++)
|
||||||
|
crc = (crc & 0x8000u) ? (uint16_t)((crc << 1) ^ 0x1021u)
|
||||||
|
: (uint16_t)(crc << 1);
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void obfuscate(void)
|
||||||
|
{
|
||||||
|
uint16_t *p = packet();
|
||||||
|
for (uint8_t i = 0; i < 32u; i++)
|
||||||
|
p[i + 1u] ^= OBFUSCATION[i & 7u];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw(void)
|
||||||
|
{
|
||||||
|
const char *state;
|
||||||
|
switch (status) {
|
||||||
|
case STATUS_TX_WAIT: state = "SENDING"; break;
|
||||||
|
case STATUS_TX_DONE: state = "SENT"; break;
|
||||||
|
case STATUS_RX_WAIT: state = "WAITING"; break;
|
||||||
|
case STATUS_RX_SAVED: state = "RECEIVED"; break;
|
||||||
|
case STATUS_RX_FULL: state = "MEM FULL"; break;
|
||||||
|
case STATUS_ERROR: state = "ERROR"; break;
|
||||||
|
default: state = mode ? "BEAM RX" : "BEAM TX"; break;
|
||||||
|
}
|
||||||
|
A->beam_draw(state);
|
||||||
|
A->blit_status();
|
||||||
|
A->blit_full();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stop_rx(void)
|
||||||
|
{
|
||||||
|
if (receiving) {
|
||||||
|
A->beam_rx(false);
|
||||||
|
receiving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_packet(void)
|
||||||
|
{
|
||||||
|
uint16_t *p = packet();
|
||||||
|
clear_bytes(packet_store, sizeof(packet_store));
|
||||||
|
p[0] = PACKET_START;
|
||||||
|
payload()->magic = PACKET_MAGIC;
|
||||||
|
payload()->version = PACKET_VERSION;
|
||||||
|
A->beam_get(&payload()->channel);
|
||||||
|
p[34] = crc16(&p[1], 2u + 64u);
|
||||||
|
p[35] = PACKET_END;
|
||||||
|
obfuscate();
|
||||||
|
|
||||||
|
status = STATUS_TX_WAIT;
|
||||||
|
draw();
|
||||||
|
A->beam_send(p);
|
||||||
|
status = STATUS_TX_DONE;
|
||||||
|
for (uint8_t i = 0; i < 3u; i++) {
|
||||||
|
A->play_tone(880, 60);
|
||||||
|
if (i < 2u) A->delay_ms(20);
|
||||||
|
}
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void start(void)
|
||||||
|
{
|
||||||
|
stop_rx();
|
||||||
|
A->beam_prepare();
|
||||||
|
if (!mode) {
|
||||||
|
send_packet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
copiedChannel = 0xFFFFu;
|
||||||
|
A->beam_rx(true);
|
||||||
|
receiving = true;
|
||||||
|
status = STATUS_RX_WAIT;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_packet(void)
|
||||||
|
{
|
||||||
|
uint16_t *p = packet();
|
||||||
|
if (p[0] != PACKET_START || p[35] != PACKET_END)
|
||||||
|
return false;
|
||||||
|
obfuscate();
|
||||||
|
if (p[34] != crc16(&p[1], 2u + 64u))
|
||||||
|
return false;
|
||||||
|
return payload()->magic == PACKET_MAGIC && payload()->version == PACKET_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void poll_rx(void)
|
||||||
|
{
|
||||||
|
if (!receiving)
|
||||||
|
return;
|
||||||
|
const uint8_t result = A->beam_rx_poll(packet());
|
||||||
|
if (result == APP_BEAM_RX_WAIT)
|
||||||
|
return;
|
||||||
|
if (result == APP_BEAM_RX_ERROR || !valid_packet()) {
|
||||||
|
status = STATUS_ERROR;
|
||||||
|
A->backlight_on();
|
||||||
|
dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedChannel = A->beam_save(&payload()->channel);
|
||||||
|
A->beam_rx(false);
|
||||||
|
receiving = false;
|
||||||
|
status = copiedChannel == 0xFFFFu ? STATUS_RX_FULL : STATUS_RX_SAVED;
|
||||||
|
A->backlight_on();
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void key_press(uint8_t key)
|
||||||
|
{
|
||||||
|
switch (key) {
|
||||||
|
case APP_KEY_UP:
|
||||||
|
case APP_KEY_DOWN:
|
||||||
|
stop_rx();
|
||||||
|
mode ^= 1u;
|
||||||
|
status = STATUS_READY;
|
||||||
|
dirty = true;
|
||||||
|
break;
|
||||||
|
case APP_KEY_MENU:
|
||||||
|
start();
|
||||||
|
break;
|
||||||
|
case APP_KEY_EXIT:
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
case APP_KEY_PTT:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
A->play_tone(500, 60);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((section(".text.entry"), used))
|
||||||
|
void app_main(const app_api_t *api)
|
||||||
|
{
|
||||||
|
A = api;
|
||||||
|
mode = 0;
|
||||||
|
status = STATUS_READY;
|
||||||
|
copiedChannel = 0xFFFFu;
|
||||||
|
receiving = false;
|
||||||
|
running = true;
|
||||||
|
dirty = true;
|
||||||
|
A->backlight_on();
|
||||||
|
|
||||||
|
uint8_t previous = APP_KEY_INVALID;
|
||||||
|
uint8_t batteryTicks = 0;
|
||||||
|
while (running) {
|
||||||
|
const uint8_t key = A->get_key();
|
||||||
|
if (key == APP_KEY_SAVER) {
|
||||||
|
previous = APP_KEY_INVALID;
|
||||||
|
} else if (key == APP_KEY_WAKE) {
|
||||||
|
previous = APP_KEY_INVALID;
|
||||||
|
dirty = true;
|
||||||
|
} else {
|
||||||
|
if (key != previous && key != APP_KEY_INVALID) {
|
||||||
|
A->backlight_on();
|
||||||
|
key_press(key);
|
||||||
|
}
|
||||||
|
previous = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
poll_rx();
|
||||||
|
if (dirty) { draw(); dirty = false; }
|
||||||
|
A->delay_ms(10);
|
||||||
|
A->backlight_update();
|
||||||
|
if (++batteryTicks >= 50u) {
|
||||||
|
batteryTicks = 0;
|
||||||
|
A->battery_sample();
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_rx();
|
||||||
|
A->beam_leave();
|
||||||
|
}
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP="$(basename "$PWD")"
|
||||||
|
APP_NAME="BEAM"
|
||||||
|
APP_VER="1.0"
|
||||||
|
APP_VMA=${APP_VMA:-0x20000280}
|
||||||
|
OUT="${APP_NAME// /}"
|
||||||
|
|
||||||
|
CC=/opt/toolchain/bin/arm-none-eabi-gcc
|
||||||
|
OBJCOPY=/opt/toolchain/bin/arm-none-eabi-objcopy
|
||||||
|
command -v arm-none-eabi-gcc >/dev/null 2>&1 && { CC=arm-none-eabi-gcc; OBJCOPY=arm-none-eabi-objcopy; }
|
||||||
|
|
||||||
|
CFLAGS="-mcpu=cortex-m0plus -mthumb -Os -std=gnu11 -ffreestanding -fno-builtin -fno-common \
|
||||||
|
-fomit-frame-pointer -ffunction-sections -fdata-sections -Wall -Wextra"
|
||||||
|
LDFLAGS="-nostdlib -nostartfiles -T app.ld -Wl,--defsym,APP_VMA=${APP_VMA} \
|
||||||
|
-Wl,--gc-sections -Wl,-Map=${APP}.map -Wl,--build-id=none -Wl,--no-warn-rwx-segments"
|
||||||
|
|
||||||
|
rm -f ./*.app ./*.elf ./*.bin
|
||||||
|
|
||||||
|
step() { printf '\r 🔨 %-13s [%d/3] %-8s' "$APP_NAME" "$1" "$2"; }
|
||||||
|
trap 'printf "\r ❌ %-13s build failed \n" "$APP_NAME"' ERR
|
||||||
|
|
||||||
|
step 1 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf"
|
||||||
|
step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin"
|
||||||
|
step 3 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \
|
||||||
|
--name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" \
|
||||||
|
--shortcut beam >/dev/null
|
||||||
|
trap - ERR
|
||||||
|
|
||||||
|
BYTES=$(wc -c < "${APP}.bin")
|
||||||
|
if [ "$BYTES" -gt 4096 ]; then
|
||||||
|
printf '\r 🚨 %-13s OVERFLOWS 4 KiB (%d B) \n' "$APP_NAME" "$BYTES"; exit 1
|
||||||
|
fi
|
||||||
|
printf '\r ✅ %-13s %4d B (%d%% of 4 KiB) -> %s.app \n' \
|
||||||
|
"$APP_NAME" "$BYTES" "$(( BYTES * 100 / 4096 ))" "$OUT"
|
||||||
@@ -38,6 +38,7 @@ SHORTCUTS = {
|
|||||||
"fm": cdefine("app_overlay.h", "APP_SHORTCUT_FM"),
|
"fm": cdefine("app_overlay.h", "APP_SHORTCUT_FM"),
|
||||||
"foxhunt": cdefine("app_overlay.h", "APP_SHORTCUT_FOXHUNT"),
|
"foxhunt": cdefine("app_overlay.h", "APP_SHORTCUT_FOXHUNT"),
|
||||||
"beacon": cdefine("app_overlay.h", "APP_SHORTCUT_BEACON"),
|
"beacon": cdefine("app_overlay.h", "APP_SHORTCUT_BEACON"),
|
||||||
|
"beam": cdefine("app_overlay.h", "APP_SHORTCUT_BEAM"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def field(s: str, n: int) -> bytes:
|
def field(s: str, n: int) -> bytes:
|
||||||
|
|||||||
+1
-1
@@ -1095,7 +1095,7 @@ void BK4819_TurnsOffTones_TurnsOnRX(void)
|
|||||||
BK4819_REG_30_ENABLE_RX_DSP);
|
BK4819_REG_30_ENABLE_RX_DSP);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_AIRCOPY
|
#if defined(ENABLE_AIRCOPY) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
||||||
void BK4819_SetupAircopy(void)
|
void BK4819_SetupAircopy(void)
|
||||||
{
|
{
|
||||||
BK4819_WriteRegister(BK4819_REG_70, 0x00E0); // Enable Tone2, tuning gain 48
|
BK4819_WriteRegister(BK4819_REG_70, 0x00E0); // Enable Tone2, tuning gain 48
|
||||||
|
|||||||
+1
-1
@@ -114,7 +114,7 @@ void BK4819_EnterTxMute(void);
|
|||||||
void BK4819_ExitTxMute(void);
|
void BK4819_ExitTxMute(void);
|
||||||
void BK4819_Sleep(void);
|
void BK4819_Sleep(void);
|
||||||
void BK4819_TurnsOffTones_TurnsOnRX(void);
|
void BK4819_TurnsOffTones_TurnsOnRX(void);
|
||||||
#ifdef ENABLE_AIRCOPY
|
#if defined(ENABLE_AIRCOPY) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
||||||
void BK4819_SetupAircopy(void);
|
void BK4819_SetupAircopy(void);
|
||||||
#endif
|
#endif
|
||||||
void BK4819_ResetFSK(void);
|
void BK4819_ResetFSK(void);
|
||||||
|
|||||||
+1
-1
@@ -1133,7 +1133,7 @@ void BK4819_TurnsOffTones_TurnsOnRX(void)
|
|||||||
BK4819_REG_30_ENABLE_RX_DSP);
|
BK4819_REG_30_ENABLE_RX_DSP);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_AIRCOPY
|
#if defined(ENABLE_AIRCOPY) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
|
||||||
void BK4819_SetupAircopy(void)
|
void BK4819_SetupAircopy(void)
|
||||||
{
|
{
|
||||||
BK4819_WriteRegister(BK4819_REG_70, 0x00C3); // Enable Tone2, tuning gain 48
|
BK4819_WriteRegister(BK4819_REG_70, 0x00C3); // Enable Tone2, tuning gain 48
|
||||||
|
|||||||
+1
-1
@@ -180,7 +180,7 @@
|
|||||||
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
|
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
|
||||||
"ENABLE_FEAT_F4HWN_BEACON": false,
|
"ENABLE_FEAT_F4HWN_BEACON": false,
|
||||||
"ENABLE_AIRCOPY": true,
|
"ENABLE_AIRCOPY": true,
|
||||||
"ENABLE_FEAT_F4HWN_BEAM": true,
|
"ENABLE_FEAT_F4HWN_BEAM": false,
|
||||||
"ENABLE_FEAT_F4HWN_GAME": false,
|
"ENABLE_FEAT_F4HWN_GAME": false,
|
||||||
"ENABLE_FEAT_F4HWN_OVERLAY_APPS": true,
|
"ENABLE_FEAT_F4HWN_OVERLAY_APPS": true,
|
||||||
"EDITION_STRING": "Labs",
|
"EDITION_STRING": "Labs",
|
||||||
|
|||||||
Reference in new issue
Block a user