Port BEAM to overlay apps

This commit is contained in:
Armel FAUVEAU committed 2026-08-31 18:28:38 +02:00
1 parent 62906636ed
commit 902e19ba8d
12 files changed
+585 -9

No files matched your search

+18 -2
View File
@@ -58,6 +58,10 @@
#include "ui/menu.h"
#endif
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) && !defined(ENABLE_FEAT_F4HWN_BEAM)
static void ACTION_Beam(void);
#endif
#if defined(ENABLE_FMRADIO)
static void ACTION_Scan_FM(bool bRestart);
#endif
@@ -113,7 +117,7 @@ void (*const action_opt_table[ACTION_OPT_LEN])(void) = {
[ACTION_OPT_REMOVE_OFFSET] = &ACTION_Remove_Offset,
#endif
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
#if defined(ENABLE_FEAT_F4HWN_BEAM) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
[ACTION_OPT_BEAM] = &ACTION_Beam,
#endif
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
@@ -153,6 +157,10 @@ bool ACTION_IsAvailable(uint8_t action)
#ifndef ENABLE_FEAT_F4HWN_BEACON
case ACTION_OPT_BEACON:
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
default:
break;
@@ -338,7 +346,7 @@ inline static bool ACTION_IsBlockedInFM(uint8_t action)
case ACTION_OPT_REMOVE_OFFSET:
#endif
#endif
#ifdef ENABLE_FEAT_F4HWN_BEAM
#if defined(ENABLE_FEAT_F4HWN_BEAM) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
case ACTION_OPT_BEAM:
#endif
#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);
}
#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)
void ACTION_FoxHunt(void)
{
+46 -1
View File
@@ -40,7 +40,7 @@
* reorder, a removal, or an append - MUST bump this. Keep in sync with the
* value read by pack_app.py, which
* 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
* value so the app stays independent of the firmware headers. */
@@ -120,6 +120,41 @@ enum {
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 {
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);
uint8_t (*trivfo_tick)(void);
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;
/* BK4819 AF modes for set_af (mirror driver/bk4819.h values). */
+207 -1
View File
@@ -525,6 +525,200 @@ static uint8_t app_trivfo_ptt(bool pressed)
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 ---- */
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); }
@@ -669,13 +863,14 @@ uint8_t APP_ValidateSlot(uint8_t slot, app_header_t *out_header)
static bool app_shortcuts_cached;
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)
{
if (shortcut == APP_SHORTCUT_FM) return 0;
if (shortcut == APP_SHORTCUT_FOXHUNT) return 1;
if (shortcut == APP_SHORTCUT_BEACON) return 2;
if (shortcut == APP_SHORTCUT_BEAM) return 3;
return -1;
}
@@ -807,6 +1002,14 @@ static const app_api_t app_api = {
.trivfo_step = app_trivfo_step,
.trivfo_tick = app_trivfo_tick,
.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)
@@ -845,6 +1048,7 @@ uint8_t APP_LaunchOverlay(uint8_t slot)
#ifdef ENABLE_FMRADIO
app_fm_dirty = false;
#endif
app_beam_dirty = false;
app_allow_screen_saver = (h.flags & APP_FLAG_SCREEN_SAVER) != 0;
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. */
PY25Q16_InvalidateCache();
app_beam_commit();
if (app_trivfo_ab_dirty) {
SETTINGS_SaveVfoIndices();
app_trivfo_ab_dirty = false;
+2 -1
View File
@@ -69,13 +69,14 @@
#define APP_FLAG_COMMITTED 0x0001u
#define APP_FLAG_SCREEN_SAVER 0x0002u
#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_VERSION_LEN 16
#define APP_SHORTCUT_FM 0x01u
#define APP_SHORTCUT_FOXHUNT 0x02u
#define APP_SHORTCUT_BEACON 0x04u
#define APP_SHORTCUT_BEAM 0x08u
typedef struct __attribute__((packed)) {
uint32_t magic; /* APP_MAGIC */
+30
View File
@@ -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.*) }
}
+241
View File
@@ -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();
}
+36
View File
@@ -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"
+1
View File
@@ -38,6 +38,7 @@ SHORTCUTS = {
"fm": cdefine("app_overlay.h", "APP_SHORTCUT_FM"),
"foxhunt": cdefine("app_overlay.h", "APP_SHORTCUT_FOXHUNT"),
"beacon": cdefine("app_overlay.h", "APP_SHORTCUT_BEACON"),
"beam": cdefine("app_overlay.h", "APP_SHORTCUT_BEAM"),
}
def field(s: str, n: int) -> bytes:
+1 -1
View File
@@ -1095,7 +1095,7 @@ void BK4819_TurnsOffTones_TurnsOnRX(void)
BK4819_REG_30_ENABLE_RX_DSP);
}
#ifdef ENABLE_AIRCOPY
#if defined(ENABLE_AIRCOPY) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void BK4819_SetupAircopy(void)
{
BK4819_WriteRegister(BK4819_REG_70, 0x00E0); // Enable Tone2, tuning gain 48
+1 -1
View File
@@ -114,7 +114,7 @@ void BK4819_EnterTxMute(void);
void BK4819_ExitTxMute(void);
void BK4819_Sleep(void);
void BK4819_TurnsOffTones_TurnsOnRX(void);
#ifdef ENABLE_AIRCOPY
#if defined(ENABLE_AIRCOPY) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void BK4819_SetupAircopy(void);
#endif
void BK4819_ResetFSK(void);
+1 -1
View File
@@ -1133,7 +1133,7 @@ void BK4819_TurnsOffTones_TurnsOnRX(void)
BK4819_REG_30_ENABLE_RX_DSP);
}
#ifdef ENABLE_AIRCOPY
#if defined(ENABLE_AIRCOPY) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void BK4819_SetupAircopy(void)
{
BK4819_WriteRegister(BK4819_REG_70, 0x00C3); // Enable Tone2, tuning gain 48
+1 -1
View File
@@ -180,7 +180,7 @@
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
"ENABLE_FEAT_F4HWN_BEACON": false,
"ENABLE_AIRCOPY": true,
"ENABLE_FEAT_F4HWN_BEAM": true,
"ENABLE_FEAT_F4HWN_BEAM": false,
"ENABLE_FEAT_F4HWN_GAME": false,
"ENABLE_FEAT_F4HWN_OVERLAY_APPS": true,
"EDITION_STRING": "Labs",