From d3263df78234361770c6a831681ef4b28495c4ad Mon Sep 17 00:00:00 2001 From: Vladimir Vishnevskii <1594938+smagellan@users.noreply.github.com> Date: Fri, 25 Jul 2025 03:59:44 +0300 Subject: [PATCH] feat(color): spectrum analyzer peak hold (#6451) --- radio/src/edgetx.h | 1 + radio/src/gui/colorlcd/libui/etx_lv_theme.cpp | 5 ++ radio/src/gui/colorlcd/libui/etx_lv_theme.h | 1 + .../radio/radio_spectrum_analyser.cpp | 86 +++++++++++++------ radio/src/telemetry/frsky_pxx2.cpp | 12 ++- radio/src/telemetry/multi.cpp | 28 ++++-- 6 files changed, 100 insertions(+), 33 deletions(-) diff --git a/radio/src/edgetx.h b/radio/src/edgetx.h index 614c47555..c82bfbacb 100644 --- a/radio/src/edgetx.h +++ b/radio/src/edgetx.h @@ -717,6 +717,7 @@ union ReusableBuffer struct { uint8_t bars[LCD_W]; uint8_t max[LCD_W]; + uint8_t peak[LCD_W]; uint32_t freq; uint32_t span; uint32_t step; diff --git a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp index 7f2c1abfc..1e3179c3c 100644 --- a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp +++ b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp @@ -240,6 +240,7 @@ EdgeTxStyles::EdgeTxStyles() lv_style_init(&graph_position_line); lv_style_init(&div_line); lv_style_init(&div_line_edit); + lv_style_init(&div_line_warn); lv_style_init(&div_line_black); lv_style_init(&div_line_white); @@ -264,6 +265,9 @@ EdgeTxStyles::EdgeTxStyles() lv_style_set_line_width(&div_line_edit, 1); lv_style_set_line_opa(&div_line_edit, LV_OPA_COVER); + lv_style_set_line_width(&div_line_warn, 1); + lv_style_set_line_opa(&div_line_warn, LV_OPA_COVER); + lv_style_set_line_width(&div_line_black, 1); lv_style_set_line_opa(&div_line_black, LV_OPA_COVER); @@ -313,6 +317,7 @@ void EdgeTxStyles::applyColors() makeLvColor(COLOR_THEME_ACTIVE)); lv_style_set_line_color(&div_line, makeLvColor(COLOR_THEME_SECONDARY1)); lv_style_set_line_color(&div_line_edit, makeLvColor(COLOR_THEME_EDIT)); + lv_style_set_line_color(&div_line_warn, makeLvColor(COLOR_THEME_WARNING)); lv_style_set_line_color(&div_line_black, makeLvColor(COLOR_THEME_PRIMARY1)); lv_style_set_line_color(&div_line_white, makeLvColor(COLOR_THEME_PRIMARY2)); diff --git a/radio/src/gui/colorlcd/libui/etx_lv_theme.h b/radio/src/gui/colorlcd/libui/etx_lv_theme.h index 2d294cf0c..05b3b75f3 100644 --- a/radio/src/gui/colorlcd/libui/etx_lv_theme.h +++ b/radio/src/gui/colorlcd/libui/etx_lv_theme.h @@ -220,6 +220,7 @@ class EdgeTxStyles lv_style_t graph_position_line; lv_style_t div_line; lv_style_t div_line_edit; + lv_style_t div_line_warn; lv_style_t div_line_black; lv_style_t div_line_white; diff --git a/radio/src/gui/colorlcd/radio/radio_spectrum_analyser.cpp b/radio/src/gui/colorlcd/radio/radio_spectrum_analyser.cpp index 3cad7ec69..e0acecad5 100644 --- a/radio/src/gui/colorlcd/radio/radio_spectrum_analyser.cpp +++ b/radio/src/gui/colorlcd/radio/radio_spectrum_analyser.cpp @@ -21,22 +21,33 @@ #include "radio_spectrum_analyser.h" -#include "libopenui.h" #include "edgetx.h" +#include "libopenui.h" #define SET_DIRTY() storageDirty(EE_GENERAL) LAYOUT_VAL_SCALED(SCALE_HEIGHT, 15) -constexpr coord_t SPECTRUM_HEIGHT = LCD_H - EdgeTxStyles::MENU_HEADER_HEIGHT - SCALE_HEIGHT - EdgeTxStyles::UI_ELEMENT_HEIGHT; +constexpr coord_t SPECTRUM_HEIGHT = LCD_H - EdgeTxStyles::MENU_HEADER_HEIGHT - + SCALE_HEIGHT - + EdgeTxStyles::UI_ELEMENT_HEIGHT; -coord_t getAverage(uint8_t number, const uint8_t* value) +template +coord_t getAverage(const uint8_t* value) { uint16_t sum = 0; - for (uint8_t i = 0; i < number; i++) { + for (uint8_t i = 0; i < CNT; i++) { sum += value[i]; } if (SPECTRUM_HEIGHT > 300) sum = sum * 2; - return sum / number; + return sum / CNT; +} + +template <> +coord_t getAverage<4>(const uint8_t* value) +{ + uint16_t sum = value[0] + value[1] + value[2] + value[3]; + if (SPECTRUM_HEIGHT > 300) sum = sum * 2; + return sum / 4; } class SpectrumFooterWindow : public Window @@ -53,13 +64,18 @@ class SpectrumFooterWindow : public Window // Frequency sprintf(label, "T: %dMHz", int(reusableBuffer.spectrumAnalyser.freq / 1000000)); - (new StaticText(this, rect_t{PAD_TINY, 0, FLD_W, EdgeTxStyles::UI_ELEMENT_HEIGHT}, label)) + (new StaticText( + this, rect_t{PAD_TINY, 0, FLD_W, EdgeTxStyles::UI_ELEMENT_HEIGHT}, + label)) ->padTop(PAD_MEDIUM); // Span sprintf(label, "S: %dMHz", int(reusableBuffer.spectrumAnalyser.span / 1000000)); - (new StaticText(this, rect_t{PAD_TINY + FLD_W, 0, FLD_W, EdgeTxStyles::UI_ELEMENT_HEIGHT}, label)) + (new StaticText( + this, + rect_t{PAD_TINY + FLD_W, 0, FLD_W, EdgeTxStyles::UI_ELEMENT_HEIGHT}, + label)) ->padTop(PAD_MEDIUM); } else { // Frequency @@ -84,7 +100,9 @@ class SpectrumFooterWindow : public Window // Tracker auto tracker = new NumberEdit( - this, rect_t{(PAD_TINY + FLD_W) * 2, 0, FLD_W, EdgeTxStyles::UI_ELEMENT_HEIGHT}, + this, + rect_t{(PAD_TINY + FLD_W) * 2, 0, FLD_W, + EdgeTxStyles::UI_ELEMENT_HEIGHT}, (reusableBuffer.spectrumAnalyser.freq - reusableBuffer.spectrumAnalyser.span / 2) / 1000000, @@ -122,8 +140,8 @@ class SpectrumScaleWindow : public Window int x = (frequency - startFreq) / reusableBuffer.spectrumAnalyser.step; if (x >= LCD_W - 1) break; formatNumberAsString(s, 16, frequency / 1000000, 16); - new StaticText(this, {x - PAD_LARGE * 2, 0, PAD_LARGE * 4, SCALE_HEIGHT}, s, - COLOR_THEME_PRIMARY1_INDEX, FONT(XS) | CENTERED); + new StaticText(this, {x - PAD_LARGE * 2, 0, PAD_LARGE * 4, SCALE_HEIGHT}, + s, COLOR_THEME_PRIMARY1_INDEX, FONT(XS) | CENTERED); } } @@ -146,8 +164,7 @@ class SpectrumScaleWindow : public Window class SpectrumWindow : public Window { public: - SpectrumWindow(Window* parent, const rect_t& rect) : - Window(parent, rect) + SpectrumWindow(Window* parent, const rect_t& rect) : Window(parent, rect) { lv_style_init(&style); lv_style_set_line_width(&style, PAD_THREE); @@ -179,6 +196,10 @@ class SpectrumWindow : public Window line = lv_line_create(lvobj); lv_obj_add_style(line, &style, LV_PART_MAIN); barLines[i] = line; + + line = lv_line_create(lvobj); + etx_obj_add_style(line, styles->div_line_warn, LV_PART_MAIN); + peakLines[i] = line; } warning = new StaticText( @@ -200,13 +221,14 @@ class SpectrumWindow : public Window #if defined(SIMU) // Geneate random data for simu - for (coord_t x = 0; x < width() - 1; x++) { + for (coord_t x = 0; x < width(); x++) { uint8_t power = rand() % 80; reusableBuffer.spectrumAnalyser.bars[x] = power; - reusableBuffer.spectrumAnalyser.bars[x + 1] = power; if (power > reusableBuffer.spectrumAnalyser.max[x]) { reusableBuffer.spectrumAnalyser.max[x] = power; - reusableBuffer.spectrumAnalyser.max[x + 1] = power; + } + if (power > reusableBuffer.spectrumAnalyser.peak[x]) { + reusableBuffer.spectrumAnalyser.peak[x] = power; } } #endif @@ -215,19 +237,29 @@ class SpectrumWindow : public Window lv_coord_t yv = SCALE_TOP - 1 - limit(0, - getAverage(step, &reusableBuffer.spectrumAnalyser.bars[x]) + getAverage(&reusableBuffer.spectrumAnalyser.bars[x]) << 1, SCALE_TOP); lv_coord_t max_yv = SCALE_TOP - 1 - limit( - 0, getAverage(step, &reusableBuffer.spectrumAnalyser.max[x]) << 1, + 0, getAverage(&reusableBuffer.spectrumAnalyser.max[x]) << 1, SCALE_TOP); + lv_coord_t peak_yv = + SCALE_TOP - 1 - + limit(0, + getAverage(&reusableBuffer.spectrumAnalyser.peak[x]) + << 1, + SCALE_TOP); maxPts[i] = {x, max_yv}; maxPts[i + 1] = {(lv_coord_t)(x + 3), max_yv}; lv_line_set_points(maxLines[i / 2], &maxPts[i], 2); + peakPts[i] = {x, peak_yv}; + peakPts[i + 1] = {(lv_coord_t)(x + 3), peak_yv}; + lv_line_set_points(peakLines[i / 2], &peakPts[i], 2); + barPts[i] = {(lv_coord_t)(x + 1), yv}; barPts[i + 1] = {(lv_coord_t)(x + 1), SCALE_TOP}; lv_line_set_points(barLines[i / 2], &barPts[i], 2); @@ -252,7 +284,8 @@ class SpectrumWindow : public Window reusableBuffer.spectrumAnalyser.span / 2; for (uint32_t frequency = (startFreq / 10000000) * 10000000 + 10000000;; frequency += 10000000) { - lv_coord_t x = (frequency - startFreq) / reusableBuffer.spectrumAnalyser.step; + lv_coord_t x = + (frequency - startFreq) / reusableBuffer.spectrumAnalyser.step; if (x >= LCD_W - 1) break; vAxisPts[i * 2] = {x, 0}; vAxisPts[i * 2 + 1] = {x, h}; @@ -269,16 +302,17 @@ class SpectrumWindow : public Window } protected: - static LAYOUT_VAL_SCALED(LINE_SPACE, 40) - static LAYOUT_VAL_SCALED(WARN_YO, 20) + static LAYOUT_VAL_SCALED(LINE_SPACE, 40) static LAYOUT_VAL_SCALED(WARN_YO, 20) - lv_style_t style; + lv_style_t style; lv_point_t maxPts[2 * LCD_W / 4]; lv_point_t barPts[2 * LCD_W / 4]; + lv_point_t peakPts[2 * LCD_W / 4]; lv_point_t hAxisPts[2 * SPECTRUM_HEIGHT / LINE_SPACE]; lv_point_t vAxisPts[2 * 8]; lv_obj_t* maxLines[LCD_W / 4]; lv_obj_t* barLines[LCD_W / 4]; + lv_obj_t* peakLines[LCD_W / 4]; lv_obj_t* vAxisLines[8]; StaticText* warning = nullptr; @@ -309,9 +343,10 @@ void RadioSpectrumAnalyser::buildBody(Window* window) { new SpectrumWindow(window, {0, 0, LCD_W, SPECTRUM_HEIGHT}); new SpectrumScaleWindow(window, {0, SPECTRUM_HEIGHT, LCD_W, SCALE_HEIGHT}); - new SpectrumFooterWindow( - window, {0, SPECTRUM_HEIGHT + SCALE_HEIGHT, LCD_W, EdgeTxStyles::UI_ELEMENT_HEIGHT}, - moduleIdx); + new SpectrumFooterWindow(window, + {0, SPECTRUM_HEIGHT + SCALE_HEIGHT, LCD_W, + EdgeTxStyles::UI_ELEMENT_HEIGHT}, + moduleIdx); } void RadioSpectrumAnalyser::checkEvents() @@ -325,7 +360,8 @@ void RadioSpectrumAnalyser::checkEvents() if (x != trackX) { trackX = x; trackPts[0] = {(lv_coord_t)x, EdgeTxStyles::MENU_HEADER_HEIGHT}; - trackPts[1] = {(lv_coord_t)x, (lv_coord_t)(height() - EdgeTxStyles::UI_ELEMENT_HEIGHT)}; + trackPts[1] = {(lv_coord_t)x, + (lv_coord_t)(height() - EdgeTxStyles::UI_ELEMENT_HEIGHT)}; lv_line_set_points(trackLine, trackPts, 2); } Page::checkEvents(); diff --git a/radio/src/telemetry/frsky_pxx2.cpp b/radio/src/telemetry/frsky_pxx2.cpp index 153bba346..e47a1b41d 100644 --- a/radio/src/telemetry/frsky_pxx2.cpp +++ b/radio/src/telemetry/frsky_pxx2.cpp @@ -416,8 +416,16 @@ static void processSpectrumAnalyserFrame(uint8_t module, const uint8_t * frame) if (x < LCD_W) { reusableBuffer.spectrumAnalyser.bars[x] = max(0, -SPECTRUM_ANALYSER_POWER_FLOOR + power); // we remove everything below -120dB #if defined(COLORLCD) - if (reusableBuffer.spectrumAnalyser.bars[x] > reusableBuffer.spectrumAnalyser.max[x]) - reusableBuffer.spectrumAnalyser.max[x] = reusableBuffer.spectrumAnalyser.bars[x]; + if (reusableBuffer.spectrumAnalyser.bars[x] > + reusableBuffer.spectrumAnalyser.max[x]) { + reusableBuffer.spectrumAnalyser.max[x] = + reusableBuffer.spectrumAnalyser.bars[x]; + } + if (reusableBuffer.spectrumAnalyser.bars[x] > + reusableBuffer.spectrumAnalyser.peak[x]) { + reusableBuffer.spectrumAnalyser.peak[x] = + reusableBuffer.spectrumAnalyser.bars[x]; + } #endif } } diff --git a/radio/src/telemetry/multi.cpp b/radio/src/telemetry/multi.cpp index a8c6b3fbf..4a88f4099 100644 --- a/radio/src/telemetry/multi.cpp +++ b/radio/src/telemetry/multi.cpp @@ -164,25 +164,41 @@ static void processMultiScannerPacket(const uint8_t *data, const uint8_t moduleI coord_t x = cur_channel*2; if (x < LCD_W) { reusableBuffer.spectrumAnalyser.bars[x] = power; - reusableBuffer.spectrumAnalyser.bars[x+1] = power; + if (x + 1 < LCD_W) { + reusableBuffer.spectrumAnalyser.bars[x + 1] = power; + } if (power > reusableBuffer.spectrumAnalyser.max[x]) { reusableBuffer.spectrumAnalyser.max[x] = power; - reusableBuffer.spectrumAnalyser.max[x+1] = power; + if (x + 1 < LCD_W) { + reusableBuffer.spectrumAnalyser.max[x + 1] = power; + } + } + if (power > reusableBuffer.spectrumAnalyser.peak[x]) { + reusableBuffer.spectrumAnalyser.peak[x] = power; + if (x + 1 < LCD_W) { + reusableBuffer.spectrumAnalyser.peak[x + 1] = power; + } } #elif LCD_W == 212 - coord_t x = cur_channel; - if (x <= LCD_W) { + coord_t x = cur_channel; + if (x < LCD_W) { reusableBuffer.spectrumAnalyser.bars[x] = power; if (power > reusableBuffer.spectrumAnalyser.max[x]) { reusableBuffer.spectrumAnalyser.max[x] = power; } + if (power > reusableBuffer.spectrumAnalyser.peak[x]) { + reusableBuffer.spectrumAnalyser.peak[x] = power; + } #else - coord_t x = cur_channel/2 + 1; - if (x <= LCD_W) { + coord_t x = cur_channel / 2 + 1; + if (x < LCD_W) { reusableBuffer.spectrumAnalyser.bars[x] = power; if (power > reusableBuffer.spectrumAnalyser.max[x]) { reusableBuffer.spectrumAnalyser.max[x] = power; } + if (power > reusableBuffer.spectrumAnalyser.peak[x]) { + reusableBuffer.spectrumAnalyser.peak[x] = power; + } #endif } if (++cur_channel > MULTI_SCANNER_MAX_CHANNEL)