Optimize overlay apps and shared audio scope

This commit is contained in:
Armel FAUVEAU committed 2026-08-30 17:02:35 +02:00
1 parent 88e76b0e9b
commit 048e7e483b
6 files changed
+90 -108

No files matched your search

+2 -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 5u
#define APP_ABI_VERSION 6u
/* 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. */
@@ -174,6 +174,7 @@ typedef struct app_api {
void (*battery_sample)(void); /* periodic ADC sample so the level stays live */
void (*backlight_on)(void); /* BACKLIGHT_TurnOn */
void (*backlight_update)(void); /* BACKLIGHT_Update (fade step) */
void (*audio_scope)(uint8_t line, bool active); /* shared MAIN microphone scope */
uint8_t *status_line; /* -> gStatusLine (for status-bar icons) */
+4 -1
View File
@@ -35,6 +35,7 @@
#include "driver/backlight.h"
#include "app/app.h"
#include "ui/helper.h"
#include "ui/main.h"
#include "ui/status.h"
#include "board.h"
#include "audio.h"
@@ -276,7 +277,8 @@ static void app_trivfo_get(uint8_t index, app_trivfo_info_t *info)
#endif
info->code_type = vfo->pRX->CodeType;
info->code = vfo->pRX->Code;
info->offset_direction = vfo->TX_OFFSET_FREQUENCY_DIRECTION;
info->offset_direction = (vfo->freq_config_RX.Frequency != vfo->freq_config_TX.Frequency)
? vfo->TX_OFFSET_FREQUENCY_DIRECTION : 0u;
info->reverse = vfo->FrequencyReverse;
info->squelch = gEeprom.SQUELCH_LEVEL;
if (index == app_trivfo_selected) info->flags |= APP_TRIVFO_SELECTED;
@@ -640,6 +642,7 @@ static const app_api_t app_api = {
.battery_sample = app_battery_sample,
.backlight_on = BACKLIGHT_TurnOn,
.backlight_update = BACKLIGHT_Update,
.audio_scope = UI_DisplayAudioScopeOverlay,
.status_line = gStatusLine,
.tx_state = app_tx_state,
.tx_set_params = app_tx_set_params,
+32 -13
View File
@@ -104,9 +104,10 @@ static char str[16];
static uint8_t slen(const char *s){ uint8_t n=0; while(s[n])n++; return n; }
static char *put(char *o,const char *s){ while(*s)*o++=*s++; return o; }
static char *puti(char *o,int v){
if(v<0){*o++='-';v=-v;}
uint32_t u;
if(v<0){*o++='-';u=(uint32_t)(-v);} else u=(uint32_t)v;
char t[6]; int8_t n=0;
do{t[n++]=(char)('0'+v%10);v/=10;}while(v&&n<6);
do{t[n++]=(char)('0'+u%10u);u/=10u;}while(u&&n<6);
while(n--)*o++=t[n];
return o;
}
@@ -131,7 +132,11 @@ static void setAudio(void){
static int32_t lerp(int16_t dbm,int32_t lo,int32_t hi){
if(dbm<=DBM_FLOOR) return lo;
if(dbm>=DBM_CEIL) return hi;
return lo + ((int32_t)(dbm-DBM_FLOOR)*(hi-lo))/(DBM_CEIL-DBM_FLOOR);
const int32_t delta = hi - lo;
const uint32_t distance = ((uint32_t)(dbm-DBM_FLOOR) *
(uint32_t)(delta < 0 ? -delta : delta)) /
(uint32_t)(DBM_CEIL-DBM_FLOOR);
return lo + (delta < 0 ? -(int32_t)distance : (int32_t)distance);
}
static void blip(uint16_t freq){
A->prepare_tone();
@@ -144,23 +149,37 @@ static void blip(uint16_t freq){
static uint8_t fillCount(int16_t dbm){
int16_t n;
if(dbm<-141) return 0;
if(dbm<=-93) n=1+(dbm+141)/6;
else n=9+(dbm+93)/10;
if(dbm<=-93) n=(int16_t)(1u+(uint16_t)(dbm+141)/6u);
else n=(int16_t)(9u+(uint16_t)(dbm+93)/10u);
if(n>SEG_COUNT) n=SEG_COUNT;
return (uint8_t)n;
}
static void buildS(char *out,int16_t dbm){
if(dbm>=-93){ int16_t o=dbm-(-93); if(o>40)o=40; char *p=put(out,"S9+"); if(o<10)*p++='0'; i2str(p,o); }
else if(dbm<-141) put(out,"S0")[0]='\0';
else { char *p=put(out,"S"); i2str(p,(dbm+147)/6); }
else { char *p=put(out,"S"); i2str(p,(int)((uint16_t)(dbm+147)/6u)); }
}
static int16_t div_trunc_pow2(int32_t v, uint8_t shift)
{
if (v < 0)
return (int16_t)-((uint32_t)(-v) >> shift);
return (int16_t)((uint32_t)v >> shift);
}
static uint8_t cycleIndex(uint8_t value, uint8_t count, int8_t dir)
{
if (dir > 0)
return ++value < count ? value : 0u;
return value > 0u ? (uint8_t)(value - 1u) : (uint8_t)(count - 1u);
}
static void histSample(void){
histEma += ((int16_t)curDbm*8 - histEma)/4;
histEma += div_trunc_pow2((int32_t)curDbm * 8 - histEma, 2u);
if(++histTick<HIST_DECIM) return;
histTick=0;
histBuf[histHead]=fillCount((int16_t)(histEma/8));
histHead=(uint8_t)((histHead+1)%HIST_LEN);
histBuf[histHead]=fillCount(div_trunc_pow2(histEma, 3u));
if(++histHead>=HIST_LEN) histHead=0;
}
static void rebase(void){
curDbm=A->rssi_dbm();
@@ -171,7 +190,7 @@ static void rebase(void){
histHead=0; histTick=0; histEma=(int16_t)(curDbm*8);
}
static void attCycle(int8_t dir){
attStep=(uint8_t)((attStep + (dir>0?1:ATT_COUNT-1))%ATT_COUNT);
attStep=cycleIndex(attStep,ATT_COUNT,dir);
applyAtt();
A->delay_ms(ATT_SETTLE);
rebase();
@@ -198,7 +217,7 @@ static void drawHist(void){
uint8_t lvl=histBuf[idx]; if(++idx>=HIST_LEN)idx=0;
if(lvl>SEG_COUNT)lvl=SEG_COUNT;
uint8_t x=GRAPH_X0+c;
uint8_t y=(uint8_t)(floorY-(lvl*span)/SEG_COUNT);
uint8_t y=(uint8_t)(floorY-((uint32_t)lvl*span)/(uint32_t)SEG_COUNT);
for(uint8_t yy=y+1;yy<=floorY;yy++) if(((x+yy)&1)==0) A->draw_line(A->fb,x,yy,x,yy,true);
if(c==0){ A->draw_line(A->fb,x,y,x,y,true); }
else { uint8_t lo=(y<prevY)?y:prevY, hi=(y<prevY)?prevY:y; A->draw_line(A->fb,x,lo,x,hi,true); }
@@ -240,7 +259,7 @@ static void draw(void){
i2str(put(str,"PK "),peakDbm);
tag(str,4,2);
i2str(put(str,"MN "),minDbm);
tag(str,(uint8_t)((LCD_WIDTH-slen(str)*4)/2),2);
tag(str,(uint8_t)(((uint32_t)LCD_WIDTH-(uint32_t)slen(str)*4u)/2u),2);
buildS(sMeter,curDbm);
A->print_inverse(sMeter,(uint8_t)(126-slen(sMeter)*4),2,false,true,125);
@@ -295,7 +314,7 @@ static void handleKeys(void){
case APP_KEY_EXIT: running=false; break;
case APP_KEY_1: foxGraphMode^=1; break;
case APP_KEY_2:
foxAudioMode=(uint8_t)((foxAudioMode+(dir>0?1:2))%3); setAudio();
foxAudioMode=cycleIndex(foxAudioMode,3u,dir); setAudio();
if(foxAudioMode==AUDIO_BEEP){ audioTick=RATE_SLOW; }
break;
case APP_KEY_3: attCycle(dir); break;
+4 -44
View File
@@ -14,17 +14,13 @@
#define CFG_MAGIC 0xC3u
#define VFO_COUNT 3u
#define MR_MAX 1024u
#define SCOPE_SAMPLES 43u
#define SCOPE_FLOOR 200u
static const app_api_t *A;
static app_trivfo_info_t vi[VFO_COUNT];
static uint8_t selected, state;
static uint16_t cChannel;
static bool running, fArm, txDenied, channelLabelOn, showFrequency;
static uint16_t scopeBuf[SCOPE_SAMPLES], scopeFloor;
static uint8_t scopeWrite, scopeReady, batteryTicks;
static bool scopeTx;
static uint8_t batteryTicks;
static char text[16];
/* Resident status.c uses this exact 8-column inverted glyph at x=69. Keep
@@ -75,7 +71,6 @@ static void formatCode(char *s,const app_trivfo_info_t *v){
} else return formatStep(s,v->step);
*o='\0';
}
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;
@@ -98,37 +93,6 @@ static void drawMeter(const app_trivfo_info_t *v){
}
}
static void drawAudioScope(void){
if(!scopeTx){
for(uint8_t i=0;i<SCOPE_SAMPLES;i++) scopeBuf[i]=SCOPE_FLOOR;
scopeFloor=SCOPE_FLOOR; scopeWrite=scopeReady=0; scopeTx=true;
}
if(scopeReady>=7u) scopeBuf[scopeWrite]=A->bk_read(0x64u);
else scopeReady++;
if(scopeBuf[scopeWrite]==0u) scopeBuf[scopeWrite]=SCOPE_FLOOR;
scopeWrite=(uint8_t)((scopeWrite+1u)%SCOPE_SAMPLES);
uint16_t min=scopeBuf[0], max=scopeBuf[0];
for(uint8_t i=1;i<SCOPE_SAMPLES;i++){
if(scopeBuf[i]<min) min=scopeBuf[i];
if(scopeBuf[i]>max) max=scopeBuf[i];
}
if(scopeFloor>min) scopeFloor=(uint16_t)(scopeFloor-((scopeFloor-min)>>3)-1u);
else scopeFloor=(uint16_t)(scopeFloor+2u);
uint16_t range=max>scopeFloor?(uint16_t)(max-scopeFloor):0u;
for(uint8_t i=0;i<SCOPE_SAMPLES;i++){
uint8_t idx=(uint8_t)((scopeWrite+i)%SCOPE_SAMPLES), height=0;
if(range>=50u){
uint16_t value=scopeBuf[idx]>scopeFloor?(uint16_t)(scopeBuf[idx]-scopeFloor):0u;
height=(uint8_t)((uint32_t)value*7u/range);
}
uint8_t mask=height?(uint8_t)((0x7fu<<(7u-height))&0x7fu):0x40u;
A->fb[0][i*3u]=mask; A->fb[0][i*3u+1u]=mask;
}
}
static void drawVfo(uint8_t n){
const app_trivfo_info_t *v=&vi[n];
uint8_t mainLine=(uint8_t)(n*2u+1u), techLine=(uint8_t)(mainLine+1u);
@@ -151,22 +115,18 @@ static void drawVfo(uint8_t n){
const char *mod=v->modulation==0u?"FM":v->modulation==1u?"AM":v->modulation==2u?"USB":v->modulation==3u?"BYP":v->modulation==4u?"RAW":"?";
A->print_tiny(mod,3,techY,false,true);
static const char *const power[7]={"LOW1","LOW2","LOW3","LOW4","LOW5","MID","HIGH"};
uint8_t p=(v->power>=1u&&v->power<=7u)?(uint8_t)(v->power-1u):0u;
A->print_tiny(power[p],24,techY,false,true);
if(v->flags&APP_TRIVFO_USER_POWER){
A->fb[techLine][19]=0x3e; A->fb[techLine][20]=0x1c; A->fb[techLine][21]=0x08;
}
if(v->offset_direction==1u) A->print_normal("+",41,0,techLine);
else if(v->offset_direction==2u) A->print_normal("-",41,0,techLine);
if(v->reverse) A->print_tiny("R",51,techY,false,true);
if(v->code_type==1u){ A->print_tiny("CT",58,techY,false,true); formatCode(text,v); A->print_tiny(text,68,techY,false,true); }
else if(v->code_type==2u||v->code_type==3u){ A->print_tiny("DC",58,techY,false,true); formatCode(text,v); A->print_tiny(text,68,techY,false,true); }
else { formatStep(text,v->step); A->print_tiny(text,58,techY,false,true); }
A->print_tiny(v->bandwidth==0u?"WIDE":v->bandwidth==1u?"NAR":"NAR+",91,techY,false,true);
text[0]='S'; text[1]='Q'; text[2]='L'; text[3]=(char)('0'+(v->squelch%10u)); text[4]='\0';
A->print_tiny(text,110,techY,false,true);
@@ -187,9 +147,9 @@ static void draw(void){
if(fArm){ for(uint8_t i=0;i<8u;i++) A->status_line[69u+i]=fontF[i]; }
for(uint8_t i=0;i<VFO_COUNT;i++) drawVfo(i);
if(state==APP_TRIVFO_TX_STATE&&(vi[selected].flags&APP_TRIVFO_AUDIO_BAR))
drawAudioScope();
A->audio_scope(0u,true);
else {
scopeTx=false;
A->audio_scope(0u,false);
for(uint8_t i=0;i<VFO_COUNT;i++)
if(vi[i].flags&APP_TRIVFO_RECEIVING){ drawMeter(&vi[i]); break; }
}
@@ -205,7 +165,7 @@ static void saveCfg(void){ uint8_t c[3]={CFG_MAGIC,(uint8_t)cChannel,(uint8_t)(c
__attribute__((section(".text.entry"),used))
void app_main(const app_api_t *api){
A=api; running=true; fArm=false; txDenied=false; channelLabelOn=true; showFrequency=false; selected=scopeWrite=scopeReady=batteryTicks=0; scopeTx=false; scopeFloor=SCOPE_FLOOR; loadCfg();
A=api; running=true; fArm=false; txDenied=false; channelLabelOn=true; showFrequency=false; selected=batteryTicks=0; loadCfg();
cChannel=A->trivfo_enter(cChannel); A->trivfo_select(0); A->backlight_on();
uint8_t held=APP_KEY_INVALID, blinkTicks=0; uint16_t heldMs=0; bool longDone=false, ptt=false;
+45 -49
View File
@@ -843,15 +843,14 @@ void UI_DisplayAudioBar(void)
}
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO_SCOPE
#if defined(ENABLE_FEAT_F4HWN_AUDIO_SCOPE) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
#define SCOPE_SAMPLES 43 // number of columns (43 × 3px = 128px wide)
#define SCOPE_NOISE_GATE 50u // minimum range below which the display shows baseline
#define SCOPE_FLOOR_RISE 2u // floor rise per frame (+100 units/s at 20ms/frame)
#define SCOPE_FLOOR_DROP_SHR 3u // floor drop IIR shift: drop by (floor-min) >> N per frame (~160ms to halve)
#define SCOPE_VOLUME_MIN 200u // let's assume that the sound level in silence is 200
void UI_DisplayAudioScope(void)
void UI_DisplayAudioScopeOverlay(const uint8_t line, const bool active)
{
static uint16_t g_scope_buf[SCOPE_SAMPLES];
static uint8_t g_scope_write = 0;
@@ -865,22 +864,11 @@ void UI_DisplayAudioScope(void)
static bool s_was_tx = false;
if (gCurrentFunction != FUNCTION_TRANSMIT) {
if (!active) {
s_was_tx = false;
return;
}
// This prevents a sudden spike on the bar caused by release the PTT button
if (!GPIO_IsPttPressed()
#ifdef ENABLE_VOX
&& !gEeprom.VOX_SWITCH
#endif
#ifdef ENABLE_FEAT_F4HWN
&& !gSetting_set_ptt_session
#endif
)
return;
if (!s_was_tx) {
// TX entry: full reset so every new transmission starts from a clean state
for (uint8_t i = 0; i < SCOPE_SAMPLES; i++) g_scope_buf[i] = SCOPE_VOLUME_MIN;
@@ -903,32 +891,6 @@ void UI_DisplayAudioScope(void)
g_scope_write = (g_scope_write + 1u) % SCOPE_SAMPLES;
// --------------------------------- Refresh display ---------------------------------
if (gLowBattery && !gLowBatteryConfirmed)
return;
if (gScreenToDisplay != DISPLAY_MAIN
#ifdef ENABLE_DTMF_CALLING
|| gDTMF_CallState != DTMF_CALL_STATE_NONE
#endif
)
return;
#ifdef ENABLE_TX1750
if (gTx1750Active)
return;
#endif
#ifdef ENABLE_FEAT_F4HWN
RxBlinkLed = 0;
RxBlinkLedCounter = 0;
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
const unsigned int line = isMainOnly() ? 5 : 3;
#else
const unsigned int line = 3;
#endif
uint8_t *p_line = gFrameBuffer[line];
memset(p_line, 0, LCD_WIDTH);
@@ -968,9 +930,50 @@ void UI_DisplayAudioScope(void)
}
}
#ifdef ENABLE_FEAT_F4HWN_AUDIO_SCOPE
void UI_DisplayAudioScope(void)
{
const unsigned int line = isMainOnly() ? 5u : 3u;
/* Keep MAIN's original gating and side effects outside the shared renderer. */
if (gCurrentFunction != FUNCTION_TRANSMIT) {
UI_DisplayAudioScopeOverlay((uint8_t)line, false);
return;
}
if (!GPIO_IsPttPressed()
#ifdef ENABLE_VOX
&& !gEeprom.VOX_SWITCH
#endif
#ifdef ENABLE_FEAT_F4HWN
&& !gSetting_set_ptt_session
#endif
)
return;
if (gLowBattery && !gLowBatteryConfirmed)
return;
if (gScreenToDisplay != DISPLAY_MAIN
#ifdef ENABLE_DTMF_CALLING
|| gDTMF_CallState != DTMF_CALL_STATE_NONE
#endif
)
return;
#ifdef ENABLE_TX1750
if (gTx1750Active)
return;
#endif
#ifdef ENABLE_FEAT_F4HWN
RxBlinkLed = 0;
RxBlinkLedCounter = 0;
BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);
#endif
UI_DisplayAudioScopeOverlay((uint8_t)line, true);
ST7565_BlitLine(line);
}
#endif // ENABLE_FEAT_F4HWN_AUDIO_SCOPE
#endif
#endif // ENABLE_FEAT_F4HWN_AUDIO_SCOPE || ENABLE_FEAT_F4HWN_OVERLAY_APPS
void DisplayRSSIBar(const bool now)
{
@@ -2086,9 +2089,6 @@ void UI_DisplayMain(void)
}
GUI_DisplaySmallest(String, 68 + shift, line == 0 ? 17 : 49, false, true);
//sprintf(String, "%d.%02u", vfoInfo->StepFrequency / 100, vfoInfo->StepFrequency % 100);
//GUI_DisplaySmallest(String, 91, line == 0 ? 2 : 34, false, true);
}
#else
UI_PrintStringSmallNormal(s, LCD_WIDTH + 24, 0, line + 1);
@@ -2123,8 +2123,6 @@ void UI_DisplayMain(void)
else
{
const char pwr_long[][5] = {"LOW1", "LOW2", "LOW3", "LOW4", "LOW5", "MID", "HIGH"};
//sprintf(String, "%s", pwr_long[currentPower]);
//GUI_DisplaySmallest(String, 24, line == 0 ? 17 : 49, false, true);
GUI_DisplaySmallest(pwr_long[currentPower], 24, line == 0 ? 17 : 49, false, true);
}
@@ -2162,9 +2160,7 @@ void UI_DisplayMain(void)
{
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
if(i == 3)
{
GUI_DisplaySmallest(dir_list[i], 43, line == 0 ? 17 : 49, false, true);
}
else
{
#endif
+3
View File
@@ -47,6 +47,9 @@ extern center_line_t center_line;
#ifdef ENABLE_AUDIO_BAR
void UI_DisplayAudioBar(void);
#endif
#if defined(ENABLE_FEAT_F4HWN_AUDIO_SCOPE) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void UI_DisplayAudioScopeOverlay(uint8_t line, bool active);
#endif
#ifdef ENABLE_FEAT_F4HWN_AUDIO_SCOPE
void UI_DisplayAudioScope(void);
#endif