feat(color): spectrum analyzer peak hold (#6451)

This commit is contained in:
Vladimir Vishnevskii
2025-07-25 10:59:44 +10:00
committed by GitHub
parent 2a1d4b20bb
commit d3263df782
6 changed files with 100 additions and 33 deletions
+1
View File
@@ -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;
@@ -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));
@@ -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;
@@ -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 <uint8_t CNT>
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<int>(0,
getAverage(step, &reusableBuffer.spectrumAnalyser.bars[x])
getAverage<step>(&reusableBuffer.spectrumAnalyser.bars[x])
<< 1,
SCALE_TOP);
lv_coord_t max_yv =
SCALE_TOP - 1 -
limit<int>(
0, getAverage(step, &reusableBuffer.spectrumAnalyser.max[x]) << 1,
0, getAverage<step>(&reusableBuffer.spectrumAnalyser.max[x]) << 1,
SCALE_TOP);
lv_coord_t peak_yv =
SCALE_TOP - 1 -
limit<int>(0,
getAverage<step>(&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();
+10 -2
View File
@@ -416,8 +416,16 @@ static void processSpectrumAnalyserFrame(uint8_t module, const uint8_t * frame)
if (x < LCD_W) {
reusableBuffer.spectrumAnalyser.bars[x] = max<int>(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
}
}
+22 -6
View File
@@ -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)