Optimize overlay ABI and reduce flash footprint

This commit is contained in:
Armel FAUVEAU committed 2026-08-30 13:15:16 +02:00
1 parent 8c78d28f0d
commit 88e76b0e9b
14 files changed
+126 -126

No files matched your search

+4
View File
@@ -1985,10 +1985,14 @@ void APP_TimeSlice500ms(void)
if ((gBatteryCheckCounter & 1) == 0)
{
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT)
BATTERY_Sample(true);
#else
BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++], &gBatteryCurrent);
if (gBatteryVoltageIndex > 3)
gBatteryVoltageIndex = 0;
BATTERY_GetReadings(true);
#endif
}
}
+1 -4
View File
@@ -683,10 +683,7 @@ static void FOXHUNT_IdleHousekeeping(void)
// hunt loop, and the beacon idle gap, never a burst), so the reading is not
// pulled down by TX load — keeping the status icon live and letting the
// beacon's battery gate react to a pack draining under a long run.
BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++], &gBatteryCurrent);
if (gBatteryVoltageIndex > 3)
gBatteryVoltageIndex = 0;
BATTERY_GetReadings(false);
BATTERY_Sample(false);
// Persist any changed setting within ~0.5 s, so it survives a power-off (not
// just a clean EXIT). No-op when nothing changed.
+1 -7
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 4u
#define APP_ABI_VERSION 5u
/* 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. */
@@ -139,15 +139,10 @@ typedef struct app_api {
/* ---- input / system ---- */
uint8_t (*get_key)(void); /* KEYBOARD_GetKey, returns an APP_KEY_* code */
void (*delay_ms)(uint32_t ms); /* SYSTEM_DelayMs */
void (*backlight_tick)(void); /* BACKLIGHT_UpdateTickless */
/* ---- audio / indicator ---- */
void (*play_tone)(uint16_t tone, uint16_t ms); /* full BK4819 tone burst + AF path */
void (*led)(bool on); /* green GPIO indicator */
/* ---- data provided at launch ---- */
uint32_t seed; /* resident-computed PRNG seed */
/* ==== appended when the ABI moved 1 -> 2; now an integral part of ABI 2.
* A firmware and an app that agree on abi_version agree on this whole layout,
* so these must never be reached through a table that does not include them. == */
@@ -197,7 +192,6 @@ typedef struct app_api {
void (*fm_enter)(uint16_t freq, uint8_t band); /* BK1080_Init + antenna filter + audio on */
void (*fm_exit)(void); /* audio off + BK1080_Init0 + restore filter */
void (*fm_set_freq)(uint16_t freq, uint8_t band);/* BK1080_SetFrequency (freq in 0.1 MHz) */
uint16_t (*fm_read)(uint8_t reg); /* BK1080_ReadRegister (RSSI / valid) */
uint16_t (*fm_lo)(uint8_t band); /* band low limit (0.1 MHz) */
uint16_t (*fm_hi)(uint8_t band); /* band high limit (0.1 MHz) */
void (*fm_mute)(bool mute); /* BK1080_Mute */
+73 -100
View File
@@ -28,6 +28,7 @@
#include "app/fm.h"
#endif
#include "driver/keyboard.h"
#include "driver/mb_flash.h"
#include "driver/py25q16.h"
#include "driver/st7565.h"
#include "driver/system.h"
@@ -47,22 +48,7 @@
_Static_assert(sizeof(app_header_t) == 64, "app_header_t must be 64 bytes");
/* CRC-32 (zlib) over RAM bytes - matches App/apps/pack_app.py and the firmware's
* mb_ext_image_crc32 (init 0xFFFFFFFF, poly 0xEDB88320, final XOR). */
static uint32_t app_crc32(const uint8_t *p, uint32_t len)
{
uint32_t crc = 0xFFFFFFFFu;
while (len--) {
crc ^= *p++;
for (int k = 0; k < 8; k++)
crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u)));
}
return crc ^ 0xFFFFFFFFu;
}
/* ---- ABI wrappers: the few resident calls that are not a direct signature match ---- */
static void app_display_clear(void) { UI_DisplayClear(); }
static void app_status_clear(void) { UI_StatusClear(); }
static uint8_t app_get_key(void) { return (uint8_t)KEYBOARD_GetKey(); }
static int8_t app_nav_dir(uint8_t key)
{
@@ -87,14 +73,6 @@ static void app_play_tone(uint16_t tone, uint16_t ms)
AUDIO_AudioPathOff();
}
static uint32_t app_make_seed(void)
{
/* Reproduces the original Breakout seed source. */
return BK4819_ReadRegister(BK4819_REG_67) & 0x01FF
* gBatteryVoltageAverage
* gEeprom.VfoInfo[0].pRX->Frequency;
}
/* ---- ABI 4: resident triple-VFO engine ---------------------------------
* The overlay owns the UI and key timing, while this resident engine owns all
* radio details. Keeping VFO_Info_t and BK4819 sequencing on this side makes
@@ -519,10 +497,7 @@ static void app_battery_sample(void)
if (app_trivfo_transmitting)
return;
BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++], &gBatteryCurrent);
if (gBatteryVoltageIndex > 3)
gBatteryVoltageIndex = 0;
BATTERY_GetReadings(false);
BATTERY_Sample(false);
}
/* ---- v2 TX (beacon) ---- */
@@ -569,7 +544,6 @@ static void app_fm_exit(void)
BK4819_PickRXFilterPathBasedOnFrequency(gRxVfo->pRX->Frequency); /* restore RX filter */
}
static void app_fm_set_freq(uint16_t f, uint8_t b) { BK1080_SetFrequency(f, b); }
static uint16_t app_fm_read(uint8_t r) { return BK1080_ReadRegister((BK1080_Register_t)r); }
static uint16_t app_fm_lo(uint8_t b) { return BK1080_GetFreqLoLimit(b); }
static uint16_t app_fm_hi(uint8_t b) { return BK1080_GetFreqHiLimit(b); }
static void app_fm_mute(bool m) { BK1080_Mute(m); }
@@ -628,6 +602,75 @@ uint8_t APP_SlotInfo(uint8_t slot, app_header_t *out_header)
return (h.magic == APP_MAGIC) ? APP_OK : APP_ERR_MAGIC;
}
/* All services are immutable. Keeping the table in flash avoids rebuilding a
* roughly quarter-kilobyte automatic object on every launch and removes that
* object from the launcher's stack frame. */
static const app_api_t app_api = {
.abi_version = APP_ABI_VERSION,
.fb = gFrameBuffer,
.display_clear = UI_DisplayClear,
.status_clear = UI_StatusClear,
.draw_line = UI_DrawLineBuffer,
.draw_rect = UI_DrawRectangleBuffer,
.print_bold = UI_PrintStringSmallBold,
.print_tiny = GUI_DisplaySmallest,
.blit_full = ST7565_BlitFullScreen,
.blit_line = ST7565_BlitLine,
.blit_status = ST7565_BlitStatusLine,
.get_key = app_get_key,
.delay_ms = SYSTEM_DelayMs,
.play_tone = app_play_tone,
.led = app_led,
.print_normal = UI_PrintStringSmallNormal,
.print_inverse = GUI_DisplaySmallestInverse,
.display_freq = UI_DisplayFrequency,
.rssi_dbm = app_rssi_dbm,
.bk_read = app_bk_read,
.bk_write = app_bk_write,
.set_agc = BK4819_SetAGC,
.set_af = app_set_af,
.audio_path = app_audio_path,
.prepare_tone = app_prepare_tone,
.play_tone_raw = app_play_tone_raw,
.tones_off_rx = app_tones_off_rx,
.rx_freq = app_rx_freq,
.cfg_load = app_cfg_load,
.cfg_save = app_cfg_save,
.draw_battery = app_draw_battery,
.battery_sample = app_battery_sample,
.backlight_on = BACKLIGHT_TurnOn,
.backlight_update = BACKLIGHT_Update,
.status_line = gStatusLine,
.tx_state = app_tx_state,
.tx_set_params = app_tx_set_params,
.tx_tone = app_tx_tone,
.tx_mute = app_tx_mute,
.tx_end = app_tx_end,
.tx_freq = app_tx_freq,
.boot_callsign = app_boot_callsign,
.print_string = UI_PrintString,
#ifdef ENABLE_FMRADIO
.fm_enter = app_fm_enter,
.fm_exit = app_fm_exit,
.fm_set_freq = app_fm_set_freq,
.fm_lo = app_fm_lo,
.fm_hi = app_fm_hi,
.fm_mute = app_fm_mute,
.fm_valid = app_fm_valid,
.fm_channels = gFM_Channels,
.fm_state = app_fm_state,
.fm_commit = app_fm_commit,
#endif
.nav_dir = app_nav_dir,
.trivfo_enter = app_trivfo_enter,
.trivfo_leave = app_trivfo_leave,
.trivfo_get = app_trivfo_get,
.trivfo_select = app_trivfo_select,
.trivfo_step = app_trivfo_step,
.trivfo_tick = app_trivfo_tick,
.trivfo_ptt = app_trivfo_ptt,
};
uint8_t APP_LaunchOverlay(uint8_t slot)
{
app_header_t h;
@@ -650,7 +693,7 @@ uint8_t APP_LaunchOverlay(uint8_t slot)
memset(ws, 0, APP_OVERLAY_MAX);
PY25Q16_ReadBuffer(APP_SLOT_BASE(slot) + APP_CODE_OFFSET, ws, h.code_size);
if (app_crc32(ws, h.code_size) != h.code_crc32) {
if (MB_Crc32Bytes(ws, h.code_size) != h.code_crc32) {
PY25Q16_InvalidateCache();
return APP_ERR_CRC;
}
@@ -659,76 +702,6 @@ uint8_t APP_LaunchOverlay(uint8_t slot)
__DSB();
__ISB();
const app_api_t api = {
.abi_version = APP_ABI_VERSION,
.fb = gFrameBuffer,
.display_clear = app_display_clear,
.status_clear = app_status_clear,
.draw_line = UI_DrawLineBuffer,
.draw_rect = UI_DrawRectangleBuffer,
.print_bold = UI_PrintStringSmallBold,
.print_tiny = GUI_DisplaySmallest,
.blit_full = ST7565_BlitFullScreen,
.blit_line = ST7565_BlitLine,
.blit_status = ST7565_BlitStatusLine,
.get_key = app_get_key,
.delay_ms = SYSTEM_DelayMs,
.backlight_tick = BACKLIGHT_UpdateTickless,
.play_tone = app_play_tone,
.led = app_led,
.seed = app_make_seed(),
/* v2 */
.print_normal = UI_PrintStringSmallNormal,
.print_inverse = GUI_DisplaySmallestInverse,
.display_freq = UI_DisplayFrequency,
.rssi_dbm = app_rssi_dbm,
.bk_read = app_bk_read,
.bk_write = app_bk_write,
.set_agc = BK4819_SetAGC,
.set_af = app_set_af,
.audio_path = app_audio_path,
.prepare_tone = app_prepare_tone,
.play_tone_raw = app_play_tone_raw,
.tones_off_rx = app_tones_off_rx,
.rx_freq = app_rx_freq,
.cfg_load = app_cfg_load,
.cfg_save = app_cfg_save,
.draw_battery = app_draw_battery,
.battery_sample = app_battery_sample,
.backlight_on = BACKLIGHT_TurnOn,
.backlight_update = BACKLIGHT_Update,
.status_line = gStatusLine,
.tx_state = app_tx_state,
.tx_set_params = app_tx_set_params,
.tx_tone = app_tx_tone,
.tx_mute = app_tx_mute,
.tx_end = app_tx_end,
.tx_freq = app_tx_freq,
.boot_callsign = app_boot_callsign,
.print_string = UI_PrintString,
#ifdef ENABLE_FMRADIO
.fm_enter = app_fm_enter,
.fm_exit = app_fm_exit,
.fm_set_freq = app_fm_set_freq,
.fm_read = app_fm_read,
.fm_lo = app_fm_lo,
.fm_hi = app_fm_hi,
.fm_mute = app_fm_mute,
.fm_valid = app_fm_valid,
.fm_channels = gFM_Channels,
.fm_state = app_fm_state,
.fm_commit = app_fm_commit,
#endif
.nav_dir = app_nav_dir,
.trivfo_enter = app_trivfo_enter,
.trivfo_leave = app_trivfo_leave,
.trivfo_get = app_trivfo_get,
.trivfo_select = app_trivfo_select,
.trivfo_step = app_trivfo_step,
.trivfo_tick = app_trivfo_tick,
.trivfo_ptt = app_trivfo_ptt,
};
app_run_slot = slot; /* for cfg_load / cfg_save */
app_cfg_len = 0;
#ifdef ENABLE_FMRADIO
@@ -750,7 +723,7 @@ uint8_t APP_LaunchOverlay(uint8_t slot)
RADIO_SetupRegisters(true);
app_entry_t entry = (app_entry_t)(((uint32_t)ws + h.entry_off) | 1u);
entry(&api);
entry(&app_api);
/* A defensive leave also covers an app returning through an error path. */
app_trivfo_leave();
+4 -1
View File
@@ -91,7 +91,10 @@ static bool settingKey(uint8_t key,int8_t dir){
switch(key){
case APP_KEY_1: beaconTx =rangeStep(beaconTx, TX_MIN, TX_MAX, TX_STEP, dir); return true;
case APP_KEY_2: beaconIdle=rangeStep(beaconIdle,IDLE_MIN,IDLE_MAX,IDLE_STEP,dir); return true;
case APP_KEY_3: foxFox=(uint8_t)((foxFox+(dir>0?1:FOX_COUNT-1))%FOX_COUNT); return true;
case APP_KEY_3:
if(dir>0){ if(++foxFox>=FOX_COUNT) foxFox=0; }
else foxFox=foxFox?(uint8_t)(foxFox-1u):(uint8_t)(FOX_COUNT-1u);
return true;
default: return false;
}
}
+5 -2
View File
@@ -242,9 +242,12 @@ void app_main(const app_api_t *api) {
kbdPrev = APP_KEY_INVALID; kbdCur = APP_KEY_INVALID;
uint8_t swap = 0;
srand_custom(api->seed);
/* The low BK4819 counter bits vary continuously; mix them with the tuned
* frequency instead of carrying a launch-only seed field in every ABI
* table. */
srand_custom(((uint32_t)A->bk_read(0x67u) << 16) ^ A->rx_freq());
A->led(false);
A->backlight_tick();
A->backlight_on();
A->display_clear();
reset(); initWall(); initRacket(); initBall();
+5 -1
View File
@@ -238,7 +238,11 @@ static void menu(void)
static void upDown(int8_t step)
{
if(scanState){ scanState=step; scanTuneNext(); return; } /* continue scan, new direction */
if(askSave){ savePos=(uint8_t)((savePos+step+CHMAX)%CHMAX); return; }
if(askSave){
if(step>0){ if(++savePos>=CHMAX) savePos=0; }
else savePos=savePos?(uint8_t)(savePos-1u):(uint8_t)(CHMAX-1u);
return;
}
if(st.is_mr){
uint8_t c=findNext((uint8_t)(st.sel_ch+step),step);
if(c!=0xFF && c!=st.sel_ch){ st.sel_ch=c; st.freq_playing=ch[c]; tune(); }
+4 -1
View File
@@ -80,7 +80,10 @@ static void drawMeter(const app_trivfo_info_t *v){
int16_t dbm=v->rssi_dbm; if(dbm>-53)dbm=-53;
uint8_t s=0,over=0;
if(dbm>=-93){ s=9; over=(uint8_t)(dbm+93); if(over>40)over=40; }
else if(dbm>=-141) s=(uint8_t)((dbm+147)/6);
/* dbm + 147 is non-negative in this branch. Keep the division unsigned:
* otherwise GCC pulls the ~460-byte signed division helper into the 4 KiB
* overlay even though no signed quotient is required. */
else if(dbm>=-141) s=(uint8_t)((uint16_t)(dbm+147)/6u);
char *o=text; if(dbm>-100)*o++=' '; o=puti(o,dbm); o=put(o," dBm"); *o='\0';
A->print_tiny(text,2,1,false,true);
+6 -6
View File
@@ -790,7 +790,7 @@ _Static_assert(offsetof(mb_state_t, firmware_slot) == 16u,
_Static_assert(offsetof(mb_state_t, state_crc32) == 20u,
"state CRC must cover the first 20 bytes (magic..bank_inv)");
static uint32_t mb_crc32_bytes(const uint8_t *p, uint32_t len)
uint32_t MB_Crc32Bytes(const uint8_t *p, uint32_t len)
{
uint32_t crc = 0xFFFFFFFFu;
@@ -848,7 +848,7 @@ static mb_mark_status_t mb_read_state_copy(uint32_t base, mb_state_t *st)
return MB_MARK_CORRUPT;
if (st->image_size == 0u || st->image_size > MB_INT_APP_SIZE)
return MB_MARK_CORRUPT;
if (mb_crc32_bytes((const uint8_t *)st,
if (MB_Crc32Bytes((const uint8_t *)st,
sizeof(*st) - sizeof(st->state_crc32)) != st->state_crc32)
return MB_MARK_CORRUPT;
@@ -857,7 +857,7 @@ static mb_mark_status_t mb_read_state_copy(uint32_t base, mb_state_t *st)
st->slot_inv = (uint8_t)~legacy_index;
st->config_bank = legacy_index;
st->bank_inv = (uint8_t)~legacy_index;
st->state_crc32 = mb_crc32_bytes((const uint8_t *)st,
st->state_crc32 = MB_Crc32Bytes((const uint8_t *)st,
sizeof(*st) - sizeof(st->state_crc32));
return MB_MARK_VALID;
}
@@ -871,7 +871,7 @@ static mb_mark_status_t mb_read_state_copy(uint32_t base, mb_state_t *st)
if (st->config_bank >= MB_BANK_COUNT) return MB_MARK_CORRUPT;
if (st->image_size == 0u ||
st->image_size > MB_INT_APP_SIZE) return MB_MARK_CORRUPT;
if (mb_crc32_bytes((const uint8_t *)st,
if (MB_Crc32Bytes((const uint8_t *)st,
sizeof(*st) - sizeof(st->state_crc32)) != st->state_crc32)
return MB_MARK_CORRUPT;
return MB_MARK_VALID;
@@ -965,7 +965,7 @@ static uint8_t mb_commit_state(mb_state_t *st,
st->generation = (current_status == MB_MARK_VALID) ? current->generation + 1u : 0u;
st->slot_inv = (uint8_t)~st->firmware_slot;
st->bank_inv = (uint8_t)~st->config_bank;
st->state_crc32 = mb_crc32_bytes((const uint8_t *)st,
st->state_crc32 = MB_Crc32Bytes((const uint8_t *)st,
sizeof(*st) - sizeof(st->state_crc32));
const uint32_t target_base = ((current_status == MB_MARK_VALID ||
@@ -1089,7 +1089,7 @@ static void mb_copy_str(char *dst, uint8_t cap, const char *src)
static uint32_t mb_int_image_crc32(uint32_t len)
{
const uint8_t *p = (const uint8_t *)MB_INT_APP_BASE;
return mb_crc32_bytes(p, len);
return MB_Crc32Bytes(p, len);
}
static bool mb_internal_matches(uint32_t image_size, uint32_t image_crc32)
+5
View File
@@ -84,6 +84,11 @@ enum {
MB_ERR_RAM_LOAD /* restore stub RAM copy mismatch */
};
/* CRC-32 (zlib) over a resident memory buffer. Multiboot and overlay apps
* share this implementation; keeping it here avoids carrying two identical
* bitwise CRC loops in the MCU flash. */
uint32_t MB_Crc32Bytes(const uint8_t *data, uint32_t len);
/* Multi-slot API used by the boot selector. Validation always covers the full
* image CRC before restore. progress_line may point to a 128-byte LCD page; the
* RAM copier then fills it while reflashing. Pass NULL to disable LCD updates.
+11
View File
@@ -17,6 +17,7 @@
#include <assert.h>
#include "battery.h"
#include "board.h"
#include "driver/backlight.h"
#include "driver/st7565.h"
#include "functions.h"
@@ -129,6 +130,16 @@ unsigned int BATTERY_VoltsToPercent(const unsigned int voltage_10mV)
return 0;
}
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT)
void BATTERY_Sample(const bool bDisplayBatteryLevel)
{
BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++], &gBatteryCurrent);
if (gBatteryVoltageIndex > 3u)
gBatteryVoltageIndex = 0u;
BATTERY_GetReadings(bDisplayBatteryLevel);
}
#endif
void BATTERY_GetReadings(const bool bDisplayBatteryLevel)
{
const uint8_t PreviousBatteryLevel = gBatteryDisplayLevel;
+3
View File
@@ -45,6 +45,9 @@ typedef enum {
unsigned int BATTERY_VoltsToPercent(unsigned int voltage_10mV);
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT)
void BATTERY_Sample(bool bDisplayBatteryLevel);
#endif
void BATTERY_GetReadings(bool bDisplayBatteryLevel);
void BATTERY_TimeSlice500ms(void);
+2 -2
View File
@@ -402,12 +402,12 @@ void UI_DisplayPopup(const char *string)
UI_PrintStringSmallNormal("Press EXIT", 9, 118, 6);
}
void UI_DisplayClear()
void UI_DisplayClear(void)
{
memset(gFrameBuffer, 0, sizeof(gFrameBuffer));
}
void UI_StatusClear()
void UI_StatusClear(void)
{
memset(gStatusLine, 0, sizeof(gStatusLine));
}
+2 -2
View File
@@ -44,7 +44,7 @@ void UI_DrawPixelBuffer(uint8_t (*buffer)[128], uint8_t x, uint8_t y, bool black
void UI_DrawLineBuffer(uint8_t (*buffer)[128], int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool black);
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();
void UI_DisplayClear(void);
void UI_StatusClear(void);
#endif