From 048e7e483bd99758e8a265c5534e0996d8905e0b Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Sun, 30 Aug 2026 17:02:35 +0200 Subject: [PATCH] Optimize overlay apps and shared audio scope --- App/apps/app_api.h | 3 +- App/apps/app_overlay.c | 5 +- App/apps/foxhunt/foxhunt_app.c | 45 +++++++++++----- App/apps/trivfo/trivfo_app.c | 48 ++--------------- App/ui/main.c | 94 ++++++++++++++++------------------ App/ui/main.h | 3 ++ 6 files changed, 90 insertions(+), 108 deletions(-) diff --git a/App/apps/app_api.h b/App/apps/app_api.h index e78f41b7..a1384ae0 100644 --- a/App/apps/app_api.h +++ b/App/apps/app_api.h @@ -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) */ diff --git a/App/apps/app_overlay.c b/App/apps/app_overlay.c index 9270097d..4fc849f0 100644 --- a/App/apps/app_overlay.c +++ b/App/apps/app_overlay.c @@ -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, diff --git a/App/apps/foxhunt/foxhunt_app.c b/App/apps/foxhunt/foxhunt_app.c index f906c359..1588c584 100644 --- a/App/apps/foxhunt/foxhunt_app.c +++ b/App/apps/foxhunt/foxhunt_app.c @@ -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_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=(ydraw_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; diff --git a/App/apps/trivfo/trivfo_app.c b/App/apps/trivfo/trivfo_app.c index f4fe1896..72a09347 100644 --- a/App/apps/trivfo/trivfo_app.c +++ b/App/apps/trivfo/trivfo_app.c @@ -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=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;imax) 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=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;iaudio_scope(0u,true); else { - scopeTx=false; + A->audio_scope(0u,false); for(uint8_t i=0;itrivfo_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; diff --git a/App/ui/main.c b/App/ui/main.c index 5405312f..670fcf90 100644 --- a/App/ui/main.c +++ b/App/ui/main.c @@ -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 diff --git a/App/ui/main.h b/App/ui/main.h index 84033f05..309c15b9 100644 --- a/App/ui/main.h +++ b/App/ui/main.h @@ -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