UHSDR/UHSDR-active-devel/mchf-eclipse/drivers/ui/lcd/ui_spectrum.c

2248 lines
106 KiB
C
Raw Normal View History

2022-11-08 16:13:55 +01:00
/* -*- mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8 -*- */
/************************************************************************************
** **
** mcHF QRP Transceiver **
** K Atanassov - M0NKA 2014 **
** **
**---------------------------------------------------------------------------------**
** **
** File name: **
** Description: **
** Last Modified: **
** Licence: GNU GPLv3 **
************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "ui_spectrum.h"
#include "ui_lcd_hy28.h"
// For spectrum display struct
#include "audio_driver.h"
#include "ui_driver.h"
#include "ui_menu.h"
#include "waterfall_colours.h"
#include "radio_management.h"
#include "rtty.h"
#include "cw_decoder.h"
#include "audio_nr.h"
#include "psk.h"
#include "uhsdr_math.h"
/*
#if defined(USE_DISP_480_320) || defined(USE_EXPERIMENTAL_MULTIRES)
#define USE_DISP_480_320_SPEC
#endif
*/
typedef struct
{
const int16_t SCOPE_GRID_VERT_COUNT;
const int16_t SCOPE_GRID_HORIZ;
} pos_spectrum_display_t;
SpectrumAreas_t slayout;
// full area = x[const]=58, y[const]= 128, w[const]= 262, h[const]=94
// draw area = x[const]=full_area.x +2, y[const]=full_area.y + 2, w[const]=full_area.w - (2 + 2), h[const]=full_area.h - (2 + 2)
// title area = x[const]=draw_area.x, y[const]=draw_area.y, w[const]=draw_area.w, h[var]=big?0:16
// scope area = x[const]=draw_area.x, y[var]=title_area.y+title_area.h, w[const]=draw_area.w, h[var]=scope_disabled?0:(draw_area.h - title_area.h - graticule_area.h)/(wfall_disabled?1:2)
// graticule area = x[const]=draw_area.x, y[var]=scope_area.y+scope_area.h, w[const]=draw_area.w, h[const]=16
// wfall area = x[const]=draw_area.x, y[var]=graticule_area.y + graticule.h, w[const]=draw_area.w, h[var]=wfall_disabled?0:(draw_area.h - title_area.h - graticule_area.h)/(scope_disabled?1:2)
/*
* @brief Implements the full calculation of coordinates for a variable sized spectrum display
* This algorithm can also be used to calculate the layout statically offline (we don't do this yet).
*/
void UiSpectrum_CalculateLayout(const bool is_big, const UiArea_t* full_ptr, const uint16_t padding)
{
sd.Slayout=&slayout;
slayout.full.x = full_ptr->x;
slayout.full.y = full_ptr->y;
slayout.full.w = full_ptr->w;
slayout.full.h = full_ptr->h;
slayout.draw.x = slayout.full.x + padding;
slayout.draw.y = slayout.full.y + padding;
slayout.draw.w = slayout.full.w - 2*padding;
slayout.draw.h = slayout.full.h - 2*padding;
slayout.title.x = slayout.draw.x;
slayout.title.y = slayout.draw.y;
slayout.title.w = slayout.draw.w;
slayout.title.h = is_big?0:16; // hide title if big
slayout.graticule.x = slayout.draw.x;
slayout.graticule.w = slayout.draw.w;
// slayout.graticule.h = 16;
slayout.graticule.h = 13;
slayout.scope.x = slayout.draw.x;
slayout.scope.y = slayout.title.y + slayout.title.h;
slayout.scope.w = slayout.draw.w;
// UiSpectrum_SetNewGraticulePosition(ts.graticulePowerupYpos);
UiSpectrum_ResetSpectrum();
slayout.scope.h = slayout.graticule.y - slayout.scope.y;
slayout.wfall.x = slayout.draw.x;
slayout.wfall.y = slayout.graticule.y + slayout.graticule.h;
slayout.wfall.w = slayout.draw.w;
slayout.wfall.h = slayout.draw.y+slayout.draw.h-slayout.wfall.y;
}
//sets graticule position according to control bits (to default for particular case)
void UiSpectrum_ResetSpectrum()
{
switch(ts.flags1&(FLAGS1_SCOPE_ENABLED | FLAGS1_WFALL_ENABLED))
{
case FLAGS1_SCOPE_ENABLED:
slayout.graticule.y=slayout.draw.y+slayout.draw.h-slayout.graticule.h;
break;
case FLAGS1_WFALL_ENABLED:
slayout.graticule.y=slayout.draw.y+slayout.title.h;
break;
case (FLAGS1_SCOPE_ENABLED | FLAGS1_WFALL_ENABLED):
slayout.graticule.y=UiSprectrum_CheckNewGraticulePos(ts.graticulePowerupYpos);
break;
default:
break;
}
}
uint16_t UiSprectrum_CheckNewGraticulePos(uint16_t new_y)
{
if((new_y<sd.Slayout->draw.y))
{
new_y=sd.Slayout->draw.y+sd.Slayout->draw.h/2-sd.Slayout->graticule.h/2; //the default setting for graticule location if invalid value detected (first powerup issue)
}
if((new_y<sd.Slayout->draw.y+MinimumScopeSize))
{
new_y=sd.Slayout->draw.y+MinimumScopeSize;
}
if(new_y>(sd.Slayout->draw.y+sd.Slayout->draw.h-MinimumWaterfallSize-sd.Slayout->graticule.h))
{
new_y=sd.Slayout->draw.y+sd.Slayout->draw.h-MinimumWaterfallSize-sd.Slayout->graticule.h;
}
return new_y;
}
const pos_spectrum_display_t pos_spectrum_set[] =
{
#ifdef USE_DISP_320_240
{
#if 0
.WIDTH = 256,
.DRAW_Y_TOP = POS_SPECTRUM_DRAW_Y_TOP,
.NORMAL_HEIGHT = SPECTRUM_HEIGHT,
.BIG_HEIGHT= (SPECTRUM_HEIGHT + SPEC_LIGHT_MORE_POINTS),
.DRAW_X_LEFT= (POS_SPECTRUM_IND_X - 2),
.DRAW_HEIGHT= (94),
.DRAW_WIDTH= (262),
.NORMAL_START_Y= (SPECTRUM_START_Y),
.BIG_START_Y= (SPECTRUM_START_Y - SPEC_LIGHT_MORE_POINTS),
.GRATICULE_Y = (POS_SPECTRUM_IND_Y + 60),
#endif
// .GRID_VERT_START = POS_SPECTRUM_GRID_VERT_START,
// .GRID_HORIZ_START = POS_SPECTRUM_GRID_HORIZ_START,
// .SCOPE_GRID_VERT = SPECTRUM_SCOPE_GRID_VERT,
.SCOPE_GRID_VERT_COUNT = SPECTRUM_SCOPE_GRID_VERT_COUNT,
.SCOPE_GRID_HORIZ = SPECTRUM_SCOPE_GRID_HORIZ,
#if 0
.IND_Y = POS_SPECTRUM_IND_Y,
.IND_X = POS_SPECTRUM_IND_X,
.START_X = POS_SPECTRUM_IND_X, // (SPECTRUM_START_X) seems to be a duplicate; used for waterfall left x
.IND_W = POS_SPECTRUM_IND_W,
.FREQ_BAR_Y = POS_SPECTRUM_FREQ_BAR_Y,
.FREQ_BAR_H = POS_SPECTRUM_FREQ_BAR_H,
.NORMAL_WATERFALL_START_Y = (SPECTRUM_START_Y + SPECTRUM_SCOPE_TOP_LIMIT),
.NORMAL_WATERFALL_HEIGHT = (SPECTRUM_HEIGHT - SPECTRUM_SCOPE_TOP_LIMIT),
.BIG_WATERFALL_START_Y = SPECTRUM_START_Y - WFALL_MEDIUM_ADDITIONAL,
.BIG_WATERFALL_HEIGHT = SPECTRUM_HEIGHT + WFALL_MEDIUM_ADDITIONAL,
#endif
},
#endif
//#ifdef USE_DISP_480_320_SPEC
{
.SCOPE_GRID_HORIZ = SPECTRUM_SCOPE_GRID_HORIZ,
.SCOPE_GRID_VERT_COUNT = SPECTRUM_SCOPE_GRID_VERT_COUNT,
},
//#endif
};
// in single resolution case we can set both of the to const, then the compiler will optimize
// it all memory access to the data away and the code is as performant as with all constant coordinates.
// const pos_spectrum_display_t* pos_spectrum = &pos_spectrum_set[0];
// const disp_resolution_t disp_resolution;
const pos_spectrum_display_t* pos_spectrum = &pos_spectrum_set[0];
// ------------------------------------------------
// Spectrum display public
SpectrumDisplay __MCHF_SPECIALMEM sd;
// this data structure is now located in the Core Connected Memory of the STM32F4
// this is highly hardware specific code. This data structure nicely fills the 64k with roughly 60k.
// If this data structure is being changed, be aware of the 64k limit. See linker script arm-gcc-link.ld
//
// scaling factors for the various dB/division settings
//
#define DB_SCALING_5 63.2456 // 5dB/division scaling
#define DB_SCALING_7 42.1637 // 7.5dB/division scaling
#define DB_SCALING_10 31.6228 // 10dB/division scaling
#define DB_SCALING_15 21.0819 // 15dB/division scaling
#define DB_SCALING_20 15.8114 // 20dB/division scaling
#define DB_SCALING_S1 52.7046 // 1 S unit (6 dB)/division scaling
#define DB_SCALING_S2 26.3523 // 2 S unit (12 dB)/division scaling
#define DB_SCALING_S3 17.5682 // 3 S unit (18 dB)/division scaling
//
typedef struct
{
float32_t value;
const char* label;
} scope_scaling_info_t;
/*
static const scope_scaling_info_t scope_scaling_factors[SCOPE_SCALE_NUM+1] =
{
// scaling factors for the various dB/division settings
{ 0, "Waterfall " }, // just a small trick, the scope will never use scaling index 0
{ DB_SCALING_5, "SC(5dB/div) " },
{ DB_SCALING_7, "SC(7.5dB/div) " },
{ DB_SCALING_10, "SC(10dB/div) " },
{ DB_SCALING_15, "SC(15dB/div) " },
{ DB_SCALING_20, "SC(20dB/div) " },
{ DB_SCALING_S1, "SC(1S-Unit/div)" },
{ DB_SCALING_S2, "SC(2S-Unit/div)" },
{ DB_SCALING_S3, "SC(3S-Unit/div)" },
{ 0, "Dual (10dB/div)" }, // just a small trick, the scope will never use scaling index SCOPE_SCALE_NUM
};*/
static const scope_scaling_info_t scope_scaling_factors[SCOPE_SCALE_NUM+1] =
{
// scaling factors for the various dB/division settings
{ 0, "Waterfall " }, // just a small trick, the scope will never use scaling index 0
{ DB_SCALING_5, "(5dB/div) " },
{ DB_SCALING_7, "(7.5dB/div)" },
{ DB_SCALING_10, "(10dB/div) " },
{ DB_SCALING_15, "(15dB/div) " },
{ DB_SCALING_20, "(20dB/div) " },
{ DB_SCALING_S1, "(1S/div) " },
{ DB_SCALING_S2, "(2S/div) " },
{ DB_SCALING_S3, "(3S/div) " },
};
static void UiSpectrum_DrawFrequencyBar(void);
static void UiSpectrum_CalculateDBm(void);
// FIXME: This is partially application logic and should be moved to UI and/or radio management
// instead of monitoring change, changes should trigger update of spectrum configuration (from pull to push)
static void UiSpectrum_UpdateSpectrumPixelParameters(void)
{
static uint16_t old_magnify = 0xFF;
static bool old_lsb = false;
static uint8_t old_dmod_mode = 0xFF;
static uint8_t old_iq_freq_mode = 0xFF;
static uint16_t old_cw_sidetone_freq = 0;
static uint16_t old_rtty_shift = 0;
static uint8_t old_digital_mode = 0xFF;
// static int32_t old_iq_freq_delta = 0;
static int32_t old_iq_freq_delta = -20000;
static bool force_update = true;
if (sd.magnify != old_magnify || force_update)
{
old_magnify = sd.magnify;
sd.hz_per_pixel = IQ_SAMPLE_RATE_F/((1 << sd.magnify) * slayout.scope.w); // magnify mode is on
force_update = true;
}
// if (ts.iq_freq_mode != old_iq_freq_mode || force_update)
// if (ts.iq_freq_mode != old_iq_freq_mode || force_update || (ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE && ts.iq_freq_delta != old_iq_freq_delta))
if ((ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE && ts.iq_freq_delta != old_iq_freq_delta) || force_update)
{
// old_iq_freq_mode = ts.iq_freq_mode;
old_iq_freq_delta = ts.iq_freq_delta;
force_update = true;
}
if (ts.iq_freq_mode != old_iq_freq_mode || force_update)
{
old_iq_freq_mode = ts.iq_freq_mode;
force_update = true;
if(!sd.magnify) // is magnify mode on?
{
sd.rx_carrier_pos = slayout.scope.w/2 - 0.5 - (AudioDriver_GetTranslateFreq()/sd.hz_per_pixel);
}
else // magnify mode is on
{
// sd.rx_carrier_pos = slayout.scope.w/2 -0.5; // line is always in center in "magnify" mode
if (ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE) {
sd.rx_carrier_pos = slayout.scope.w/2 - 0.5 - (AudioDriver_GetTranslateFreq()/sd.hz_per_pixel);
} else {
sd.rx_carrier_pos = slayout.scope.w/2 -0.5; // line is always in center in "magnify" mode
}
}
}
bool cur_lsb = RadioManagement_LSBActive(ts.dmod_mode);
if (cur_lsb != old_lsb || ts.cw_sidetone_freq != old_cw_sidetone_freq || old_rtty_shift != rtty_ctrl_config.shift_idx || ts.dmod_mode != old_dmod_mode || ts.digital_mode != old_digital_mode || force_update)
{
old_lsb = cur_lsb;
old_cw_sidetone_freq = ts.cw_sidetone_freq;
old_dmod_mode = ts.dmod_mode;
old_digital_mode = ts.digital_mode;
old_rtty_shift = rtty_ctrl_config.shift_idx;
// float32_t tx_vfo_offset = ((float32_t)(((int32_t)RadioManagement_GetTXDialFrequency() - (int32_t)RadioManagement_GetRXDialFrequency())))/sd.hz_per_pixel;
float32_t tx_vfo_offset = 0;
if(!is_splitmode())
{
tx_vfo_offset = ((float32_t)(((int32_t)RadioManagement_GetTXDialFrequency() - (int32_t)RadioManagement_GetRXDialFrequency())))/sd.hz_per_pixel;
}
// FIXME: DOES NOT WORK PROPERLY IN SPLIT MODE
sd.marker_num_prev = sd.marker_num;
float32_t mode_marker_offset[SPECTRUM_MAX_MARKER];
switch(ts.dmod_mode)
{
case DEMOD_CW:
mode_marker_offset[0] =(ts.cw_lsb?-1.0:1.0)*((float32_t)ts.cw_sidetone_freq / sd.hz_per_pixel);
sd.marker_num = 1;
break;
case DEMOD_DIGI:
{
float32_t mode_marker[SPECTRUM_MAX_MARKER];
switch(ts.digital_mode)
{
#ifdef USE_FREEDV
case DigitalMode_FreeDV:
// 1500 +/- 625Hz
mode_marker[0] = 875;
mode_marker[1] = 2125;
// sd.marker_num = 2;
mode_marker[2] = 0;
sd.marker_num = 3;
break;
#endif
case DigitalMode_RTTY:
mode_marker[0] = 915; // Mark Frequency
mode_marker[1] = mode_marker[0] + rtty_shifts[rtty_ctrl_config.shift_idx].value;
// sd.marker_num = 2;
mode_marker[2] = 0;
sd.marker_num = 3;
break;
case DigitalMode_BPSK:
mode_marker[0] = PSK_OFFSET - psk_speeds[psk_ctrl_config.speed_idx].value / 2;
mode_marker[1] = PSK_OFFSET + psk_speeds[psk_ctrl_config.speed_idx].value / 2;
// sd.marker_num = 2;
mode_marker[2] = 0;
sd.marker_num = 3;
break;
default:
mode_marker[0] = 0;
sd.marker_num = 1;
}
for (uint16_t idx = 0; idx < sd.marker_num; idx++)
{
mode_marker_offset[idx] = (ts.digi_lsb?-1.0:1.0)*(mode_marker[idx] / sd.hz_per_pixel);
}
}
break;
default:
mode_marker_offset[0] = 0;
sd.marker_num = 1;
}
for (uint16_t idx = 0; idx < sd.marker_num; idx++)
{
sd.marker_offset[idx] = tx_vfo_offset + mode_marker_offset[idx];
sd.marker_pos[idx] = sd.rx_carrier_pos + sd.marker_offset[idx];
}
for (uint16_t idx = sd.marker_num; idx < SPECTRUM_MAX_MARKER; idx++)
{
sd.marker_offset[idx] = 0;
// sd.marker_pos[idx] = slayout.scope.w; // this is an invalid position out of screen
sd.marker_pos[idx] = slayout.scope.w * 2; // this is an invalid position out of screen
}
force_update = false;
}
}
#ifdef USE_PREDEFINED_WINDOW_DATA
// Information on these windowing functions may be found on the internet - check the Wikipedia article "Window Function"
// KA7OEI - 20150602
// we should hardcode the windows in order to save a considerable amount of processing time DD4WH, Feb 2018
// const von_Hann_256 [256] = {0,0.00015177,0.000607,0.00136541,0.00242654,0.00378975,0.0054542,0.00741888,0.00968261,0.01224402,0.01510153,0.01825343,0.02169779,0.02543253,0.02945537,0.03376389,0.03835545,0.04322727,0.0483764,0.05379971,0.0594939,0.06545553,0.07168096,0.07816643,0.08490798,0.09190154,0.09914286,0.10662753,0.11435102,0.12230863,0.13049554,0.13890677,0.14753723,0.15638166,0.1654347,0.17469085,0.1841445,0.1937899,0.2036212,0.21363243,0.22381751,0.23417027,0.2446844,0.25535354,0.2661712,0.27713082,0.28822574,0.29944923,0.31079447,0.32225458,0.3338226,0.3454915,0.35725421,0.36910357,0.3810324,0.39303346,0.40509945,0.41722306,0.42939692,0.44161365,0.45386582,0.466146,0.47844673,0.49076055,0.50307997,0.51539753,0.52770574,0.53999713,0.55226423,0.56449961,0.57669583,0.58884548,0.6009412,0.61297564,0.62494149,0.6368315,0.64863843,0.66035512,0.67197446,0.6834894,0.69489294,0.70617816,0.71733821,0.72836632,0.73925578,0.75,0.76059244,0.77102668,0.78129638,0.7913953,0.80131732,0.81105641,0.82060666,0.82996227,0.83911756,0.84806697,0.85680508,0.86532657,0.87362627,0.88169914,0.88954029,0.89714494,0.9045085,0.91162647,0.91849455,0.92510857,0.9314645,0.93755849,0.94338684,0.94894602,0.95423264,0.95924349,0.96397554,0.96842592,0.97259191,0.976471,0.98006082,0.9833592,0.98636414,0.9890738,0.99148655,0.99360092,0.99541563,0.99692957,0.99814183,0.99905166,0.99965853,0.99996206,0.99996206,0.99965853,0.99905166,0.99814183,0.99692957,0.99541563,0.99360092,0.99148655,0.9890738,0.98636414,0.9833592,0.98006082,0.976471,0.97259191,0.96842592,0.96397554,0.95924349,0.95423264,0.94894602,0.94338684,0.93755849,0.9314645,0.92510857,0.91849455,0.91162647,0.9045085,0.89714494,0.88954029,0.88169914,0.87362627,0.86532657,0.85680508,0.84806697,0.83911756,0.82996227,0.82060666,0.81105641,0.80131732,0.7913953,0.78129638,0.77102668,0.76059244,0.75,0.73925578,0.72836632,0.71733821,0.70617816,0.69489294,0.6834894,0.67197446,0.66035512,0.64863843,0.6368315,0.62494149,0.61297564,0.6009412,0.58884548,0.57669583,0.56449961,0.55226423,0.53999713,0.52770574,0.51539753,0.50307997,0.49076055,0.47844673,0.466146,0.45386582,0.44161365,0.42939692,0.41722306,0.40509945,0.39303346,0.3810324,0.36910357,0.35725421,0.3454915,0.3338226,0.32225458,0.31079447,0.29944923,0.28822574,0.27713082,0.2661712,0.25535354,0.2446844,0.23417027,0.22381751,0.21363243,0.2036212,0.1937899,0.1841445,0.17469085,0.1654347,0.15638166,0.14753723,0.13890677,0.13049554,0.12230863,0.11435102,0.10662753,0.09914286,0.09190154,0.08490798,0.07816643,0.07168096,0.06545553,0.0594939,0.05379971,0.0483764,0.04322727,0.03835545,0.03376389,0.02945537,0.02543253,0.02169779,0.01825343,0.01510153,0.01224402,0.00968261,0.00741888,0.0054542,0.00378975,0.00242654,0.00136541,0.000607,0.00015177,0};
//we have to provide both (512 and 1024) of the window coefficients because of automatic screen resolution change, SP9BSL Feb 2018
const float32_t von_Hann_512 [512] = {0,3.77966E-05,0.000151181,0.000340135,0.000604631,0.000944629,0.001360077,0.001850913,0.002417062,0.003058438,0.003774946,0.004566476,0.005432908,0.006374113,0.007389947,0.008480256,0.009644877,0.010883633,0.012196337,0.013582789,0.015042782,0.016576093,0.018182492,0.019861734,0.021613567,0.023437725,0.025333934,0.027301905,0.029341341,0.031451935,0.033633367,0.035885307,0.038207415,0.040599339,0.043060719,0.045591181,0.048190344,0.050857815,0.053593189,0.056396054,0.059265986,0.062202551,0.065205305,0.068273794,0.071407554,0.074606112,0.077868983,0.081195674,0.084585683,0.088038497,0.091553594,0.095130442,0.098768501,0.102467221,0.106226043,0.110044397,0.113921708,0.117857388,0.121850843,0.125901469,0.130008654,0.134171776,0.138390206,0.142663307,0.146990432,0.151370928,0.155804131,0.160289372,0.164825973,0.169413247,0.174050502,0.178737036,0.18347214,0.188255099,0.19308519,0.197961681,0.202883837,0.207850913,0.212862158,0.217916814,0.223014117,0.228153297,0.233333576,0.238554171,0.243814294,0.249113148,0.254449933,0.259823842,0.265234062,0.270679775,0.276160159,0.281674384,0.287221617,0.292801019,0.298411747,0.304052952,0.309723782,0.315423378,0.321150881,0.326905422,0.332686134,0.338492141,0.344322565,0.350176526,0.356053138,0.361951513,0.36787076,0.373809982,0.379768282,0.38574476,0.391738511,0.397748631,0.403774209,0.409814335,0.415868096,0.421934577,0.42801286,0.434102027,0.440201156,0.446309327,0.452425614,0.458549094,0.464678841,0.470813928,0.476953428,0.483096412,0.489241951,0.495389117,0.50153698,0.507684611,0.51383108,0.519975458,0.526116815,0.532254225,0.538386758,0.544513487,0.550633486,0.556745831,0.562849596,0.568943859,0.575027699,0.581100196,0.587160431,0.593207489,0.599240456,0.605258418,0.611260467,0.617245695,0.623213197,0.62916207,0.635091417,0.641000339,0.646887944,0.652753341,0.658595644,0.66441397,0.670207439,0.675975174,0.681716305,0.687429962,0.693115283,0.698771407,0.70439748,0.709992651,
0.715556073,0.721086907,0.726584315,0.732047467,0.737475536,0.742867702,0.74822315,0.75354107,0.758820659,0.764061117,0.769261652,0.774421479,0.779539817,0.784615893,0.789648938,0.794638193,0.799582902,0.804482319,0.809335702,0.814142317,0.818901439,0.823612347,0.828274329,0.832886681,0.837448705,0.841959711,0.846419017,0.85082595,0.855179843,0.859480037,0.863725883,0.867916738,0.87205197,0.876130952,0.880153069,0.884117711,0.888024281,0.891872186,0.895660845,0.899389686,0.903058145,0.906665667,0.910211707,0.913695728,0.917117204,0.920475618,0.923770461,0.927001237,0.930167455,0.933268638,0.936304317,0.939274033,0.942177336,0.945013788,0.94778296,0.950484434,0.9531178,0.955682662,0.95817863,0.960605328,0.962962389,0.965249456,0.967466184,0.969612237,0.971687291,0.973691033,0.975623159,0.977483377,0.979271407,0.980986977,0.982629829,0.984199713,0.985696393,0.987119643,0.988469246,0.989745,0.990946711,0.992074198,0.99312729,0.994105827,0.995009663,0.99583866,0.996592693,0.997271648,0.997875422,0.998403924,0.998857075,0.999234805,0.999537058,0.999763787,0.999914959,0.999990551,0.999990551,0.999914959,0.999763787,0.999537058,0.999234805,0.998857075,0.998403924,0.997875422,0.997271648,0.996592693,0.99583866,0.995009663,0.994105827,0.99312729,0.992074198,0.990946711,0.989745,0.988469246,0.987119643,0.985696393,0.984199713,0.982629829,0.980986977,0.979271407,0.977483377,0.975623159,0.973691033,0.971687291,0.969612237,0.967466184,0.965249456,0.962962389,0.960605328,0.95817863,0.955682662,0.9531178,0.950484434,0.94778296,0.945013788,0.942177336,0.939274033,0.936304317,0.933268638,0.930167455,0.927001237,0.923770461,0.920475618,0.917117204,0.913695728,0.910211707,0.906665667,0.903058145,0.899389686,0.895660845,0.891872186,0.888024281,0.884117711,0.880153069,0.876130952,0.87205197,0.867916738,0.863725883,0.859480037,0.855179843,0.85082595,0.846419017,0.841959711,0.837448705,0.832886681,0.828274329,0.823612347,0.818901439,0.814142317,0.809335702,0.804482319,0.799582902,0.794638193,0.789648938,0.784615893,0.779539817,0.774421479,0.769261652,0.764061117,0.758820659,0.75354107,0.74822315,0.742867702,0.737475536,0.732047467,0.726584315,0.721086907,0.715556073,0.709992651,0.70439748,
0.698771407,0.693115283,0.687429962,0.681716305,0.675975174,0.670207439,0.66441397,0.658595644,0.652753341,0.646887944,0.641000339,0.635091417,0.62916207,0.623213197,0.617245695,0.611260467,0.605258418,0.599240456,0.593207489,0.587160431,0.581100196,0.575027699,0.568943859,0.562849596,0.556745831,0.550633486,0.544513487,0.538386758,0.532254225,0.526116815,0.519975458,0.51383108,0.507684611,0.50153698,0.495389117,0.489241951,0.483096412,0.476953428,0.470813928,0.464678841,0.458549094,0.452425614,0.446309327,0.440201156,0.434102027,0.42801286,0.421934577,0.415868096,0.409814335,0.403774209,0.397748631,0.391738511,0.38574476,0.379768282,0.373809982,0.36787076,0.361951513,0.356053138,0.350176526,0.344322565,0.338492141,0.332686134,0.326905422,0.321150881,0.315423378,0.309723782,0.304052952,0.298411747,0.292801019,0.287221617,0.281674384,0.276160159,0.270679775,0.265234062,0.259823842,0.254449933,0.249113148,0.243814294,0.238554171,0.233333576,0.228153297,0.223014117,0.217916814,0.212862158,0.207850913,0.202883837,0.197961681,0.19308519,0.188255099,0.18347214,0.178737036,0.174050502,0.169413247,0.164825973,0.160289372,0.155804131,0.151370928,0.146990432,0.142663307,0.138390206,0.134171776,0.130008654,0.125901469,0.121850843,0.117857388,0.113921708,0.110044397,0.106226043,0.102467221,0.098768501,0.095130442,0.091553594,0.088038497,0.084585683,0.081195674,0.077868983,0.074606112,0.071407554,0.068273794,0.065205305,0.062202551,0.059265986,0.056396054,0.053593189,0.050857815,0.048190344,0.045591181,0.043060719,0.040599339,0.038207415,0.035885307,0.033633367,0.031451935,0.029341341,0.027301905,0.025333934,0.023437725,0.021613567,0.019861734,0.018182492,0.016576093,0.015042782,0.013582789,0.012196337,0.010883633,0.009644877,0.008480256,0.007389947,0.006374113,0.005432908,0.004566476,0.003774946,0.003058438,0.002417062,0.001850913,0.001360077,0.000944629,0.000604631,0.000340135,0.000151181,3.77966E-05,0};
const float32_t von_Hann_1024 [1024] = {0,9.43077E-06,3.77227E-05,8.48748E-05,0.000150885,0.000235751,0.00033947,0.000462038,0.00060345,0.0007637,0.000942783,0.001140693,0.00135742,0.001592958,0.001847298,0.002120429,0.002412342,0.002723026,0.003052468,0.003400657,0.003767578,0.00415322,0.004557566,0.004980602,0.005422311,0.005882678,0.006361684,0.006859311,0.007375542,0.007910355,0.008463732,0.009035651,0.009626091,0.010235029,0.010862443,0.011508308,0.012172601,0.012855296,0.013556368,0.01427579,0.015013535,0.015769575,0.016543882,0.017336426,0.018147178,0.018976107,0.019823181,0.020688369,0.021571639,0.022472956,0.023392287,0.024329597,0.025284851,0.026258012,0.027249045,0.028257911,0.029284572,0.030328991,0.031391127,0.03247094,0.03356839,0.034683435,0.035816033,0.036966142,0.038133718,0.039318717,0.040521094,0.041740804,0.042977801,0.044232039,0.045503469,0.046792044,0.048097716,0.049420435,0.050760151,0.052116814,0.053490372,0.054880775,0.056287968,0.0577119,0.059152516,0.060609762,0.062083583,0.063573924,0.065080728,0.066603939,0.068143498,0.069699349,0.071271432,0.072859688,0.074464057,0.076084478,0.077720891,0.079373234,0.081041443,0.082725458,
0.084425213,0.086140645,0.087871689,0.089618279,0.091380351,0.093157837,0.09495067,0.096758783,0.098582108,0.100420575,0.102274115,0.104142659,0.106026136,0.107924475,0.109837605,0.111765452,0.113707945,0.115665009,0.117636572,0.11962256,0.121622896,0.123637505,0.125666312,0.127709241,0.129766213,0.131837151,0.133921978,0.136020614,0.138132981,0.140258998,0.142398586,0.144551664,0.146718151,0.148897964,0.151091022,0.153297242,0.15551654,0.157748834,0.159994038,0.162252069,0.16452284,0.166806267,0.169102262,0.17141074,0.173731614,0.176064795,0.178410196,0.180767729,0.183137304,0.185518832,0.187912223,0.190317387,0.192734233,0.195162671,0.197602607,0.200053951,0.20251661,0.204990491,0.207475501,0.209971545,0.21247853,0.214996362,0.217524945,0.220064183,0.222613982,0.225174244,0.227744874,0.230325774,0.232916847,0.235517995,0.23812912,0.240750124,0.243380907,0.246021371,0.248671416,0.251330942,0.253999848,0.256678034,0.259365399,0.262061841,0.26476726,0.267481552,0.270204615,
0.272936347,0.275676644,0.278425404,0.281182522,0.283947894,0.286721417,0.289502985,0.292292493,0.295089838,0.297894912,0.30070761,0.303527825,0.306355453,0.309190385,0.312032515,0.314881736,0.31773794,0.32060102,0.323470867,0.326347374,0.329230431,0.33211993,0.335015762,0.337917817,0.340825987,0.343740162,0.346660231,0.349586084,0.352517612,0.355454703,0.358397247,0.361345132,0.364298248,0.367256483,0.370219726,0.373187864,0.376160786,0.379138379,0.382120532,0.385107132,0.388098066,0.391093221,0.394092484,0.397095742,0.400102883,0.403113792,0.406128355,0.40914646,0.412167992,0.415192837,0.418220882,0.421252011,0.424286111,0.427323067,0.430362765,0.43340509,0.436449927,0.439497161,0.442546678,0.445598362,0.448652098,0.451707771,0.454765266,0.457824467,0.460885259,0.463947527,0.467011155,0.470076027,0.473142028,0.476209042,0.479276954,0.482345647,0.485415006,0.488484916,0.49155526,0.494625922,0.497696788,0.50076774,0.503838663,0.506909441,0.509979959,0.5130501,0.516119749,0.51918879,0.522257107,0.525324584,0.528391106,0.531456558,0.534520822,0.537583784,0.540645329,0.54370534,0.546763702,
0.549820301,0.55287502,0.555927744,0.558978359,0.562026748,0.565072798,0.568116393,0.571157419,0.57419576,0.577231303,0.580263932,0.583293533,0.586319992,0.589343195,0.592363028,0.595379376,0.598392127,0.601401166,0.604406379,0.607407654,0.610404877,0.613397936,0.616386717,0.619371107,0.622350994,0.625326266,0.62829681,0.631262515,0.634223267,0.637178957,0.640129471,0.6430747,0.646014531,0.648948854,0.651877559,0.654800534,0.657717669,0.660628855,0.663533982,0.666432939,0.669325618,0.67221191,0.675091705,0.677964896,0.680831372,0.683691028,0.686543754,0.689389443,0.692227987,0.695059281,0.697883216,0.700699686,0.703508585,0.706309807,0.709103247,0.711888798,0.714666357,0.717435818,0.720197076,0.722950028,0.725694569,0.728430596,0.731158007,0.733876697,0.736586565,0.739287508,0.741979424,0.744662213,0.747335771,0.75,0.752654798,0.755300065,0.757935701,0.760561607,0.763177684,0.765783833,0.768379955,0.770965954,0.773541731,0.776107189,0.778662232,0.781206762,0.783740685,0.786263904,0.788776324,0.791277851,0.793768389,
0.796247846,0.798716128,0.801173141,0.803618793,0.806052991,0.808475644,0.810886661,0.81328595,0.815673421,0.818048983,0.820412548,0.822764026,0.825103329,0.827430367,0.829745054,0.832047301,0.834337023,0.836614133,0.838878544,0.841130172,0.843368932,0.845594738,0.847807508,0.850007157,0.852193603,0.854366763,0.856526556,0.858672899,0.860805712,0.862924914,0.865030425,0.867122167,0.869200059,0.871264024,0.873313984,0.875349861,0.877371579,0.879379061,0.881372232,0.883351017,0.88531534,0.887265128,0.889200307,0.891120804,0.893026547,0.894917464,0.896793484,0.898654535,0.900500547,0.902331452,0.904147179,0.905947661,0.907732829,0.909502616,0.911256955,0.912995781,0.914719027,0.916426628,0.918118521,0.919794641,0.921454925,0.92309931,0.924727735,0.926340138,0.927936458,0.929516635,0.931080609,0.932628322,0.934159714,0.935674728,0.937173308,0.938655396,0.940120937,0.941569875,0.943002155,0.944417724,0.945816529,0.947198515,0.948563632,0.949911828,0.951243052,0.952557254,0.953854383,0.955134392,0.956397232,0.957642855,0.958871214,0.960082264,0.961275958,0.96245225,0.963611098,0.964752457,0.965876284,0.966982537,0.968071174,0.969142153,0.970195436,0.971230981,0.972248749,0.973248703,0.974230805,0.975195017,0.976141303,0.977069628,0.977979956,
0.978872254,0.979746487,0.980602622,0.981440628,0.982260472,0.983062124,0.983845553,0.98461073,0.985357626,0.986086213,0.986796463,0.98748835,0.988161847,0.988816929,0.989453572,0.990071751,0.990671442,0.991252625,0.991815275,0.992359373,0.992884898,0.993391829,0.993880148,0.994349837,0.994800877,0.995233251,0.995646944,0.99604194,0.996418223,0.99677578,0.997114597,0.997434662,0.997735961,0.998018485,0.998282221,0.998527161,0.998753295,0.998960615,0.999149112,0.99931878,0.999469611,0.999601602,0.999714745,0.999809038,0.999884477,0.999941059,0.999978781,0.999997642,0.999997642,0.999978781,0.999941059,0.999884477,0.999809038,0.999714745,0.999601602,0.999469611,0.99931878,0.999149112,0.998960615,0.998753295,0.998527161,0.998282221,0.998018485,0.997735961,0.997434662,0.997114597,0.99677578,0.996418223,0.99604194,0.995646944,0.995233251,0.994800877,0.994349837,0.993880148,0.993391829,0.992884898,0.992359373,0.991815275,0.991252625,0.990671442,0.990071751,0.989453572,0.988816929,0.988161847,0.98748835,0.986796463,0.986086213,0.985357626,0.98461073,0.983845553,0.983062124,0.982260472,0.981440628,0.980602622,0.979746487,0.978872254,0.977979956,0.977069628,0.976141303,0.975195017,0.974230805,0.973248703,0.972248749,0.971230981,0.970195436,0.969142153,0.968071174,0.966982537,0.965876284,0.964752457,0.963611098,0.96245225,0.961275958,0.960082264,0.958871214,0.957642855,0.956397232,0.955134392,0.953854383,0.952557254,0.951243052,0.949911828,0.948563632,0.947198515,0.945816529,0.944417724,0.943002155,0.941569875,0.940120937,0.938655396,0.937173308,0.935674728,0.934159714,0.932628322,
0.931080609,0.929516635,0.927936458,0.926340138,0.924727735,0.92309931,0.921454925,0.919794641,0.918118521,0.916426628,0.914719027,0.912995781,0.911256955,0.909502616,0.907732829,0.905947661,0.904147179,0.902331452,0.900500547,0.898654535,0.896793484,0.894917464,0.893026547,0.891120804,0.889200307,0.887265128,0.88531534,0.883351017,0.881372232,0.879379061,0.877371579,0.875349861,0.873313984,0.871264024,0.869200059,0.867122167,0.865030425,0.862924914,0.860805712,0.858672899,0.856526556,0.854366763,0.852193603,0.850007157,0.847807508,0.845594738,0.843368932,0.841130172,0.838878544,0.836614133,0.834337023,0.832047301,0.829745054,0.827430367,0.825103329,0.822764026,0.820412548,0.818048983,0.815673421,0.81328595,0.810886661,0.808475644,0.806052991,0.803618793,0.801173141,0.798716128,0.796247846,0.793768389,0.791277851,0.788776324,0.786263904,0.783740685,0.781206762,0.778662232,0.776107189,0.773541731,0.770965954,0.768379955,0.765783833,0.763177684,0.760561607,0.757935701,0.755300065,0.752654798,0.75,0.747335771,0.744662213,0.741979424,0.739287508,0.736586565,0.733876697,0.731158007,0.728430596,0.725694569,0.722950028,0.720197076,0.717435818,0.714666357,0.711888798,0.709103247,0.706309807,0.703508585,0.700699686,0.697883216,0.695059281,0.692227987,0.689389443,0.686543754,0.683691028,0.680831372,0.677964896,0.675091705,0.67221191,0.669325618,0.666432939,0.663533982,0.660628855,0.657717669,0.654800534,0.651877559,0.648948854,0.646014531,0.6430747,0.640129471,0.637178957,0.634223267,0.631262515,0.62829681,0.625326266,0.622350994,0.619371107,0.616386717,0.613397936,0.610404877,0.607407654,0.604406379,0.601401166,0.598392127,0.595379376,0.592363028,0.589343195,0.586319992,0.583293533,0.580263932,
0.577231303,0.57419576,0.571157419,0.568116393,0.565072798,0.562026748,0.558978359,0.555927744,0.55287502,0.549820301,0.546763702,0.54370534,0.540645329,0.537583784,0.534520822,0.531456558,0.528391106,0.525324584,0.522257107,0.51918879,0.516119749,0.5130501,0.509979959,0.506909441,0.503838663,0.50076774,0.497696788,0.494625922,0.49155526,0.488484916,0.485415006,0.482345647,0.479276954,0.476209042,0.473142028,0.470076027,0.467011155,0.463947527,0.460885259,0.457824467,0.454765266,0.451707771,0.448652098,0.445598362,0.442546678,0.439497161,0.436449927,0.43340509,0.430362765,0.427323067,0.424286111,0.421252011,0.418220882,0.415192837,0.412167992,0.40914646,0.406128355,0.403113792,0.400102883,0.397095742,0.394092484,0.391093221,0.388098066,0.385107132,0.382120532,0.379138379,0.376160786,0.373187864,0.370219726,0.367256483,0.364298248,0.361345132,0.358397247,0.355454703,0.352517612,0.349586084,0.346660231,0.343740162,0.340825987,0.337917817,0.335015762,0.33211993,0.329230431,0.326347374,0.323470867,0.32060102,0.31773794,0.314881736,0.312032515,0.309190385,0.306355453,0.303527825,0.30070761,0.297894912,0.295089838,0.292292493,0.289502985,0.286721417,0.283947894,0.281182522,0.278425404,0.275676644,0.272936347,0.270204615,0.267481552,0.26476726,0.262061841,0.259365399,0.256678034,0.253999848,0.251330942,0.248671416,0.246021371,0.243380907,0.240750124,0.23812912,0.235517995,0.232916847,0.230325774,0.227744874,0.225174244,0.222613982,0.220064183,0.217524945,0.214996362,0.21247853,0.209971545,0.207475501,0.204990491,0.20251661,0.200053951,0.197602607,0.195162671,0.192734233,0.190317387,
0.187912223,0.185518832,0.183137304,0.180767729,0.178410196,0.176064795,0.173731614,0.17141074,0.169102262,0.166806267,0.16452284,0.162252069,0.159994038,0.157748834,0.15551654,0.153297242,0.151091022,0.148897964,0.146718151,0.144551664,0.142398586,0.140258998,0.138132981,0.136020614,0.133921978,0.131837151,0.129766213,0.127709241,0.125666312,0.123637505,0.121622896,0.11962256,0.117636572,0.115665009,0.113707945,0.111765452,0.109837605,0.107924475,0.106026136,0.104142659,0.102274115,0.100420575,0.098582108,0.096758783,0.09495067,0.093157837,0.091380351,0.089618279,0.087871689,0.086140645,0.084425213,0.082725458,0.081041443,0.079373234,0.077720891,0.076084478,0.074464057,0.072859688,0.071271432,0.069699349,0.068143498,0.066603939,0.065080728,0.063573924,0.062083583,0.060609762,0.059152516,0.0577119,0.056287968,0.054880775,0.053490372,0.052116814,0.050760151,0.049420435,0.048097716,0.046792044,0.045503469,0.044232039,0.042977801,0.041740804,0.040521094,0.039318717,0.038133718,0.036966142,0.035816033,0.034683435,0.03356839,0.03247094,0.031391127,0.030328991,0.029284572,0.028257911,0.027249045,0.026258012,0.025284851,0.024329597,0.023392287,0.022472956,0.021571639,0.020688369,0.019823181,0.018976107,0.018147178,0.017336426,0.016543882,0.015769575,0.015013535,0.01427579,0.013556368,0.012855296,0.012172601,0.011508308,0.010862443,0.010235029,0.009626091,0.009035651,0.008463732,0.007910355,0.007375542,0.006859311,0.006361684,0.005882678,0.005422311,0.004980602,0.004557566,0.00415322,0.003767578,0.003400657,0.003052468,0.002723026,0.002412342,0.002120429,0.001847298,0.001592958,0.00135742,0.001140693,0.000942783,0.0007637,0.00060345,0.000462038,0.00033947,0.000235751,0.000150885,8.48748E-05,3.77227E-05,9.43077E-06,0};
#endif
static void UiSpectrum_FFTWindowFunction(char mode)
{
const uint16_t fft_iq_m1_half = (sd.fft_iq_len-1)/2;
switch(mode)
{
case FFT_WINDOW_RECTANGULAR: // No processing at all
break;
case FFT_WINDOW_COSINE: // Sine window function (a.k.a. "Cosine Window"). Kind of wide...
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] = arm_sin_f32((PI * (float32_t)i)/sd.fft_iq_len - 1) * sd.FFT_Samples[i];
}
break;
case FFT_WINDOW_BARTLETT: // a.k.a. "Triangular" window - Bartlett (or Fej?r) window is special case where demonimator is "N-1". Somewhat better-behaved than Rectangular
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] = (1 - fabs(i - ((float32_t)fft_iq_m1_half))/(float32_t)fft_iq_m1_half) * sd.FFT_Samples[i];
}
break;
case FFT_WINDOW_WELCH: // Parabolic window function, fairly wide, comparable to Bartlett
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] = (1 - ((i - ((float32_t)fft_iq_m1_half))/(float32_t)fft_iq_m1_half)*((i - ((float32_t)fft_iq_m1_half))/(float32_t)fft_iq_m1_half)) * sd.FFT_Samples[i];
}
break;
case FFT_WINDOW_HANN: // Raised Cosine Window (non zero-phase version) - This has the best sidelobe rejection of what is here, but not as narrow as Hamming.
#ifndef USE_PREDEFINED_WINDOW_DATA
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] = 0.5 * (float32_t)((1 - (arm_cos_f32(PI*2 * (float32_t)i / (float32_t)(sd.fft_iq_len-1)))) * sd.FFT_Samples[i]);
}
#else
{
const float32_t* WindowCoefficients= sd.fft_iq_len==512?von_Hann_512:von_Hann_1024;
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] *= WindowCoefficients[i];
}
}
#endif
break;
case FFT_WINDOW_HAMMING: // Another Raised Cosine window - This is the narrowest with reasonably good sidelobe rejection.
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] = (0.53836 - (0.46164 * arm_cos_f32(PI*2 * (float32_t)i / (float32_t)(sd.fft_iq_len-1)))) * sd.FFT_Samples[i];
}
break;
case FFT_WINDOW_BLACKMAN: // Approx. same "narrowness" as Hamming but not as good sidelobe rejection - probably best for "default" use.
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] = (0.42659 - (0.49656*arm_cos_f32((2*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1)) + (0.076849*arm_cos_f32((4*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1))) * sd.FFT_Samples[i];
}
break;
case FFT_WINDOW_NUTTALL: // Slightly wider than Blackman, comparable sidelobe rejection.
for(int i = 0; i < sd.fft_iq_len; i++)
{
sd.FFT_Samples[i] = (0.355768 - (0.487396*arm_cos_f32((2*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1)) + (0.144232*arm_cos_f32((4*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1)) - (0.012604*arm_cos_f32((6*PI*(float32_t)i)/(float32_t)sd.fft_iq_len-1))) * sd.FFT_Samples[i];
}
break;
}
float32_t gcalc = 1.0/ads.codec_gain_calc; // Get gain setting of codec and convert to multiplier factor
arm_scale_f32(sd.FFT_Samples,gcalc, sd.FFT_Samples, sd.fft_iq_len);
}
static void UiSpectrum_SpectrumTopBar_GetText(char* wfbartext)
{
if(is_waterfallmode() && is_scopemode()) // dual waterfall
{
sprintf(wfbartext,"Dual%s < Magnify %2ux >",scope_scaling_factors[ts.spectrum_db_scale].label, (1<<sd.magnify));
}
else if(is_waterfallmode()) //waterfall
{
sprintf(wfbartext," %s < Magnify %2ux >",scope_scaling_factors[0].label, (1<<sd.magnify)); // a small trick, we use the empty location of index 0 in the table
}
else // scope
{
sprintf(wfbartext," SC %s < Magnify %2ux >",scope_scaling_factors[ts.spectrum_db_scale].label, (1<<sd.magnify)); // a small trick, we use the empty location of index 0 in the table
}
}
/**
* tells us if x is on a vertical grid line. Called for all slayout.scope.w lines
*/
static bool UiSpectrum_Draw_XposIsOnVertGrid(const uint16_t x)
{
bool repaint_v_grid = false;
#if 0
int k;
// Enumerate all saved x positions
for(k = 0; k < 7; k++)
{
// Exit on match
if(x == sd.vert_grid_id[k])
{
repaint_v_grid = true;
break;
// leave loop, found match
}
else if (sd.vert_grid_id[k]>x)
{
// leave loop, no need to look further, we passed the area where x could have hit a vertical grid line.
// assume low to high order in sd.vert_grid_id
break;
}
}
#else
// this should be faster in case of power of 2 vert grid distance than the generic lookup code above
// since the compiler should detect that module power 2 means just masking the higher bits
// we have special check if we are on the rightmost pixel, this one is not a vertical line.
return ((x + 1) % sd.vert_grid == 0) && (x != (slayout.scope.w - 1));
#endif
return repaint_v_grid;
}
static void UiSpectrum_DrawLine(uint16_t x, uint16_t y_pos_prev, uint16_t y_pos, uint16_t clr)
{
if(y_pos - y_pos_prev > 1) // && x !=(SPECTRUM_START_X + x_offset))
{ // plot line upwards
UiLcdHy28_DrawStraightLine(x, y_pos_prev + 1, y_pos - y_pos_prev,LCD_DIR_VERTICAL, clr);
}
else if (y_pos - y_pos_prev < -1) // && x !=(SPECTRUM_START_X + x_offset))
{ // plot line downwards
UiLcdHy28_DrawStraightLine(x, y_pos, y_pos_prev - y_pos,LCD_DIR_VERTICAL, clr);
}
else
{
UiLcdHy28_DrawStraightLine(x, y_pos,1,LCD_DIR_VERTICAL, clr);
}
}
static void UiSpectrum_ScopeStandard_UpdateVerticalDataLine(uint16_t x, uint16_t y_old_pos, uint16_t y_new_pos, uint16_t clr_scope, uint16_t clr_bgr, bool is_carrier_line)
{
// normal scope
if(y_old_pos > y_new_pos)
{
// is old line going to be overwritten by new line, anyway?
UiLcdHy28_DrawStraightLine(x, y_new_pos,y_old_pos-y_new_pos, LCD_DIR_VERTICAL, clr_scope);
}
else if (y_old_pos < y_new_pos )
{
// is old line is longer than the new line?
// repaint the vertical grid
bool repaint_v_grid = UiSpectrum_Draw_XposIsOnVertGrid(x - slayout.scope.x);
// we need to delete by overwriting with background color or grid color if we are on a vertical grid line
uint16_t clr_bg =
is_carrier_line?
sd.scope_centre_grid_colour_active
:
( repaint_v_grid ? sd.scope_grid_colour_active : clr_bgr )
;
UiLcdHy28_DrawStraightLine(x,y_old_pos,y_new_pos-y_old_pos,LCD_DIR_VERTICAL,clr_bg);
if (!repaint_v_grid && is_carrier_line == false)
{
// now we repaint the deleted points of the horizontal grid lines
// but only if we are not on a vertical grid line, we already painted that in this case
for(uint16_t k = 0; k < sd.upper_horiz_gridline && y_old_pos <= sd.horz_grid_id[k]; k++)
{ // we run if pixel positions are inside of deleted area (i.e. not lower than y_old_pos) or if there are no more lines
if(y_old_pos <= sd.horz_grid_id[k] && sd.horz_grid_id[k] < y_new_pos )
{
UiLcdHy28_DrawStraightLine(x,sd.horz_grid_id[k],1,LCD_DIR_HORIZONTAL, sd.scope_grid_colour_active);
}
}
}
}
}
static void UiSpectrum_CreateDrawArea(void)
{
//Since we have now highlighted spectrum, the grid is to be drawn in UiSpectrum_DrawScope().
//Here we only calculate positions of grid and write it to appropriate arrays
//Also the vertical grid array is used for frequency labels in freq ruler
UiSpectrum_UpdateSpectrumPixelParameters();
//const bool is_scope_light = (ts.flags1 & FLAGS1_SCOPE_LIGHT_ENABLE) != 0;
// get grid colour of all but center line
UiMenu_MapColors(ts.scope_grid_colour,NULL, &sd.scope_grid_colour_active);
// Get color of center vertical line of spectrum scope
UiMenu_MapColors(ts.spectrum_centre_line_colour,NULL, &sd.scope_centre_grid_colour_active);
// Clear screen where frequency information will be under graticule
UiLcdHy28_DrawFullRect(slayout.graticule.x, slayout.graticule.y, slayout.graticule.h, slayout.graticule.w, Black); // Clear screen under spectrum scope by drawing a single, black block (faster with SPI!)
//sd.wfall_DrawDirection=1;
// was used on 320x240, we may reactivate that at some point for all resolutions
#if 0
// Frequency bar separator
UiLcdHy28_DrawHorizLineWithGrad(pos_spectrum->IND_X,(pos_spectrum->IND_Y + pos_spectrum->IND_H - 20),pos_spectrum->IND_W,COL_SPECTRUM_GRAD);
// Draw control left and right border
UiLcdHy28_DrawStraightLineDouble((pos_spectrum->DRAW_X_LEFT),
(pos_spectrum->IND_Y - 20),
(pos_spectrum->IND_H + 12),
LCD_DIR_VERTICAL,
// RGB(COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD));
sd.scope_grid_colour_active);
UiLcdHy28_DrawStraightLineDouble( (pos_spectrum->IND_X + pos_spectrum->IND_W - 2),
(pos_spectrum->IND_Y - 20),
(pos_spectrum->IND_H + 12),
LCD_DIR_VERTICAL,
// RGB(COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD,COL_SPECTRUM_GRAD));
sd.scope_grid_colour_active);
#endif
if(slayout.title.h != 0) //don't draw text bar if there is no space allocated for it
{
// UiLcdHy28_PrintText(292,118," ",Blue,Black,0);
#ifndef SDR_AMBER_480_320
#ifndef OVI40_MOD_480_320
UiLcdHy28_PrintText(292,118 + (!ts.show_wide_spectrum?0:7)," ",Blue,Black,0);
#else
UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:7)," ",Blue,Black,0);
#endif
#else
UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:7)," ",Blue,Black,0);
#endif
// Draw top band = grey box in which text is printed
for(int i = 0; i < 16; i++)
{
UiLcdHy28_DrawHorizLineWithGrad(slayout.title.x,slayout.title.y + i, slayout.title.w, COL_SPECTRUM_GRAD);
}
char bartext[34];
// Top band text - middle caption
UiSpectrum_SpectrumTopBar_GetText(bartext);
UiLcdHy28_PrintTextCentered(
slayout.title.x,
slayout.title.y + (slayout.title.h - UiLcdHy28_TextHeight(0))/2,
slayout.title.w,
bartext,
White,
// RGB((COL_SPECTRUM_GRAD*2),(COL_SPECTRUM_GRAD*2),(COL_SPECTRUM_GRAD*2)),
RGB(64,64,64),
0);
}
else
{
char fwmltext[3];
sprintf(fwmltext,"x%u ", (1<<sd.magnify));
// UiLcdHy28_PrintText(292,118,fwmltext,Blue,Black,0);
#ifndef SDR_AMBER_480_320
#ifndef OVI40_MOD_480_320
UiLcdHy28_PrintText(292,118 + (!ts.show_wide_spectrum?0:6),fwmltext,Blue,Black,0);
#else
UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:6),fwmltext,Blue,Black,0);
#endif
#else
UiLcdHy28_PrintText(452,107 + (!ts.show_wide_spectrum?0:6),fwmltext,Blue,Black,0);
#endif
}
// Horizontal grid lines
sd.upper_horiz_gridline = slayout.scope.h / pos_spectrum->SCOPE_GRID_HORIZ;
// array must be filled from higher to the y coordinates
// the lookup code for a match counts on this.
for(int i = 0; i < sd.upper_horiz_gridline; i++)
{
// Save y position for grid draw and repaint
sd.horz_grid_id[i] = (slayout.scope.y + slayout.scope.h - ((i+1) * pos_spectrum->SCOPE_GRID_HORIZ));
}
// Vertical grid lines
// array must be filled from low to higher x coordinates
// the lookup code for a match counts on this.
sd.vert_grid = slayout.scope.w / pos_spectrum->SCOPE_GRID_VERT_COUNT;
for(int i = 1; i < pos_spectrum->SCOPE_GRID_VERT_COUNT; i++)
{
// Save x position for grid draw and repaint
sd.vert_grid_id[i - 1] = (i*sd.vert_grid - 1);
}
if (is_waterfallmode() == true && ts.waterfall.speed == 0)
{
// print "disabled" in the middle of the screen if the waterfall or scope was disabled
UiLcdHy28_PrintTextCentered(
slayout.wfall.x,
slayout.wfall.y + (slayout.scope.h - UiLcdHy28_TextHeight(0))/2 ,
slayout.wfall.w,
"DISABLED",
Grey, Black,0);
}
if (is_scopemode() == true && ts.scope_speed == 0)
{
// print "disabled" in the middle of the screen if the waterfall or scope was disabled
UiLcdHy28_PrintTextCentered(
slayout.scope.x,
slayout.scope.y + (slayout.scope.h - UiLcdHy28_TextHeight(0))/2 ,
slayout.scope.w,
"DISABLED",
Grey, Black,0);
}
// Draw Frequency bar text after arrays with coordinates are set
UiSpectrum_DrawFrequencyBar();
//show highlighted filter bandwidth on the spectrum
sd.old_left_filter_border_pos=slayout.scope.x;
sd.old_right_filter_border_pos=slayout.scope.w+slayout.scope.x;
}
void UiSpectrum_Clear()
{
#ifndef SDR_AMBER_480_320
#ifndef OVI40_MOD_480_320
UiLcdHy28_DrawFullRect(slayout.full.x, slayout.full.y + (ts.show_wide_spectrum?1:0), slayout.full.h - (ts.show_wide_spectrum?1:0), slayout.full.w + (ts.show_wide_spectrum?1:0), Black); // Clear screen under spectrum scope by drawing a single, black block (faster with SPI!)
#else
UiLcdHy28_DrawFullRect(slayout.full.x, slayout.full.y + 1, slayout.full.h - 1, slayout.full.w, Black);
#endif
#else
UiLcdHy28_DrawFullRect(slayout.full.x, slayout.full.y + 1, slayout.full.h - 1, slayout.full.w, Black);
#endif
ts.VirtualKeysShown_flag=false; //if virtual keypad was shown, switch it off
}
// This version of "Draw Scope" is revised from the original in that it interleaves the erasure with the drawing
// of the spectrum to minimize visible flickering (KA7OEI, 20140916, adapted from original)
//
// 20141004 NOTE: This has been somewhat optimized to prevent drawing vertical line segments that would need to be re-drawn:
// - New lines that were shorter than old ones are NOT erased
// - Line segments that are to be erased are only erased starting at the position of the new line segment.
//
// This should reduce the amount of CGRAM access - especially via SPI mode - to a minimum.
static void UiSpectrum_DrawScope(uint16_t *old_pos, float32_t *fft_new)
{
// before accessing pixel parameters, request update according to configuration
UiSpectrum_UpdateSpectrumPixelParameters();
const bool is_scope_light = (ts.flags1 & FLAGS1_SCOPE_LIGHT_ENABLE) != 0;
const uint16_t spec_height_limit = sd.scope_size - 1;
const uint16_t spec_top_y = sd.scope_ystart + sd.scope_size;
uint32_t clr_scope_normal, clr_scope_fltr, clr_scope_fltrbg;
uint16_t clr_bg;
//calculations of bandwidth highlight parameters and colours
float32_t filter_width_; // calculate width of BW highlight in pixels
float32_t left_filter_border_pos_; // first pixel of filter
UiSpectrum_CalculateDisplayFilterBW(&filter_width_,&left_filter_border_pos_);
uint16_t left_filter_border_pos=left_filter_border_pos_;
uint16_t right_filter_border_pos=left_filter_border_pos_+filter_width_;
if (right_filter_border_pos >= slayout.scope.w)
{
right_filter_border_pos = slayout.scope.w - 1;
}
//mapping the colours of highlighted bandwidth
//foreground of highlighted bandwidth is one of predefined colours selected fromm array, so simply map it
//background is the percentage of foreground, so we must disassemly the rgb data(16 bit) into seperate RGB channels,
//then scale it and assembly to 16 bit
UiMenu_MapColors(ts.scope_trace_colour, NULL, &clr_scope_normal); //foreground colour
UiMenu_MapColors(ts.scope_trace_BW_colour, NULL, &clr_scope_fltr);//background colour of highlight
uint16_t BWHbgr=ts.scope_backgr_BW_colour;
BWHbgr<<=8;
BWHbgr/=100;
uint16_t colR=(clr_scope_fltr>>8)&0xf8;
uint16_t colG=(clr_scope_fltr>>3)&0xfc;
uint16_t colB=(clr_scope_fltr<<3)&0xf8;
colR=(colR*BWHbgr)>>8;
colG=(colG*BWHbgr)>>8;
colB=(colB*BWHbgr)>>8;
clr_scope_fltrbg=RGB(colR,colG,colB); //background color of the active demodulation filter highlight
left_filter_border_pos+=slayout.scope.x; //left boundary of highlighted spectrum in absolute pixels
right_filter_border_pos+=slayout.scope.x; //right boundary of highlighted spectrum in absolute pixels
if((sd.old_left_filter_border_pos!=left_filter_border_pos ) || (sd.old_right_filter_border_pos!=right_filter_border_pos ))
{
//BW changed so we must refresh the highlighted spectrum
uint16_t x_start=sd.old_left_filter_border_pos;
uint16_t x_end=sd.old_right_filter_border_pos;
if(sd.old_left_filter_border_pos>left_filter_border_pos)
{
x_start=left_filter_border_pos;
}
if(sd.old_right_filter_border_pos<right_filter_border_pos)
{
x_end=right_filter_border_pos;
}
for(uint16_t xh=x_start;xh<=x_end;xh++)
{
uint16_t clr_scope;
if((xh>=left_filter_border_pos)&&(xh<=right_filter_border_pos)) //BW highlight control
{
clr_scope=clr_scope_fltr;
clr_bg=clr_scope_fltrbg;
}
else
{
clr_scope=clr_scope_normal;
clr_bg=Black;
}
if (is_scope_light)
{
UiSpectrum_DrawLine(xh, spec_top_y, spec_top_y-spec_height_limit, clr_bg);
}
else
{
UiSpectrum_ScopeStandard_UpdateVerticalDataLine(xh, spec_top_y-spec_height_limit, spec_top_y, clr_scope, clr_bg, false);
}
old_pos[xh-slayout.scope.x]=spec_top_y;
}
//causing the redraw of all marker lines within the bandwidth area
for (uint16_t idx = 0; idx < SPECTRUM_MAX_MARKER; idx++)
{
if((sd.marker_line_pos_prev[idx]>=x_start) && (sd.marker_line_pos_prev[idx]<=x_end))
{
sd.marker_line_pos_prev[idx]=65535;
}
}
}
//Correction of the SSB carrier upper marker disappearing bug when changing modulation
sd.marker_line_pos_prev[2]=65535;
sd.old_left_filter_border_pos=left_filter_border_pos;
sd.old_right_filter_border_pos=right_filter_border_pos;
uint16_t marker_line_pos[SPECTRUM_MAX_MARKER];
for (uint16_t idx = 0; idx < SPECTRUM_MAX_MARKER; idx++)
{
marker_line_pos[idx] = slayout.scope.x + sd.marker_pos[idx];
// this is the tx carrier line, we redraw only if line changes place around,
// init code must take care to reset prev position to 0xffff in order to get initialization done after clean start
if (marker_line_pos[idx] != sd.marker_line_pos_prev[idx])
{
if ((sd.marker_line_pos_prev[idx] < slayout.scope.x + slayout.scope.w)&&
(sd.marker_line_pos_prev[idx] > slayout.scope.x))
{
if((sd.marker_line_pos_prev[idx]>=left_filter_border_pos)&&(sd.marker_line_pos_prev[idx]<=right_filter_border_pos)) //BW highlight control
{
clr_bg=clr_scope_fltrbg;
}
else
{
clr_bg=Black;
}
// delete old line if previously inside screen limits
if(is_scope_light)
{
UiLcdHy28_DrawStraightLine( sd.marker_line_pos_prev[idx],
spec_top_y - spec_height_limit,
spec_height_limit,
LCD_DIR_VERTICAL,
clr_bg);
}
else
{
UiSpectrum_ScopeStandard_UpdateVerticalDataLine(
sd.marker_line_pos_prev[idx],
spec_top_y - spec_height_limit /* old = max pos */ ,
spec_top_y /* new = min pos */,
clr_scope_normal, clr_bg, //TODO: add highlight color here
false);
// we erase the memory for this location, so that it is fully redrawn
if (sd.marker_line_pos_prev[idx] < slayout.scope.x + slayout.scope.w)
{
old_pos[sd.marker_line_pos_prev[idx] - slayout.scope.x] = spec_top_y;
}
}
}
if ((marker_line_pos[idx] < slayout.scope.x + slayout.scope.w)&&
(marker_line_pos[idx] > slayout.scope.x))
{
// draw new line if inside screen limits
UiLcdHy28_DrawStraightLine( marker_line_pos[idx],
spec_top_y - spec_height_limit,
spec_height_limit,
LCD_DIR_VERTICAL,
sd.scope_centre_grid_colour_active);
if (is_scope_light == false)
{
// we erase the memory for this location, so that it is fully redrawn
if (marker_line_pos[idx] < slayout.scope.x + slayout.scope.w)
{
old_pos[marker_line_pos[idx] - slayout.scope.x] = spec_top_y;
}
}
}
// done, remember where line has been drawn
sd.marker_line_pos_prev[idx] = marker_line_pos[idx];
}
}
uint16_t marker_lines_togo = sd.marker_num;
// we stop if there is a ptt_request and go straight out of the display update
for(uint16_t x = slayout.scope.x, idx = 0; ts.ptt_req == false && idx < slayout.scope.w; x++, idx++)
{
uint16_t clr_scope;
if((x>=left_filter_border_pos)&&(x<=right_filter_border_pos)) //BW highlight control
{
clr_scope=clr_scope_fltr;
clr_bg=clr_scope_fltrbg;
}
else
{
clr_scope=clr_scope_normal;
clr_bg=Black;
}
// averaged FFT data scaled to height and (if necessary) limited here to current max height
uint16_t y_height = (fft_new[idx] < spec_height_limit ? fft_new[idx] : spec_height_limit);
// Data to y position by subtraction from the lowest spectrum y,
// since scope y goes from high to low coordinates if y goes from low to high
// due to screen 0,0 reference being the top left corner.
uint16_t y_new_pos = spec_top_y - y_height;
// we get the old y position value of last scope draw run
// and remember the new position value for next round in same place
uint16_t y_old_pos = old_pos[idx];
old_pos[idx] = y_new_pos;
if (is_scope_light)
{
static uint16_t y_new_pos_prev = 0, y_old_pos_prev = 0;
// ATTENTION: CODE ONLY UPDATES THESE IF IN LIGHT SCOPE MODE !!!
// pixel values from the previous column left to the current x position
// these are static so that when we do the right half of the spectrum, we get the previous value from the left side of the screen
// special case of first x position of spectrum, we don't have a left side neighbor
if (idx == 0)
{
y_old_pos_prev = y_old_pos;
y_new_pos_prev = y_new_pos;
}
// x position is not on vertical center line (the one that indicates the tx carrier frequency)
// we draw a line if y_new_pos and the last drawn pixel (y_old_pos) are more than 1 pixel apart in the vertical axis
// makes the spectrum display look more complete . . .
// uint16_t clr_bg = Black;
// TODO: we could find out the lowest marker_line and do not process search before that line
for (uint16_t marker_idx = 0; marker_lines_togo > 0 && marker_idx < sd.marker_num; marker_idx++)
{
if (x == marker_line_pos[marker_idx])
{
clr_bg = sd.scope_centre_grid_colour_active;
marker_lines_togo--; // once we marked all, skip further tests;
break;
}
}
UiSpectrum_DrawLine(x, y_old_pos_prev, y_old_pos, clr_bg);
UiSpectrum_DrawLine(x, y_new_pos_prev, y_new_pos, clr_scope);
// we are done, lets remember this information for next round
y_new_pos_prev = y_new_pos;
y_old_pos_prev = y_old_pos;
}
else
{
bool is_marker_line = false;
for (uint16_t idx = 0; !(is_marker_line) && idx < sd.marker_num; idx++)
{
is_marker_line = x == marker_line_pos[idx];
}
// we just draw our vertical line in a optimized fashion here
// handles also the grid (re)drawing if necessary
UiSpectrum_ScopeStandard_UpdateVerticalDataLine(x, y_old_pos, y_new_pos, clr_scope, clr_bg, is_marker_line);
}
}
}
/**
* @brief init data strctures for both "Scope Display" and "Waterfall" Display
*/
static void UiSpectrum_InitSpectrumDisplayData(void)
{
//init colour of markers
UiMenu_MapColors(ts.spectrum_centre_line_colour,NULL, &sd.scope_centre_grid_colour_active);
// Init publics
sd.state = 0;
sd.samp_ptr = 0;
sd.enabled = 0;
ts.dial_moved = 0;
sd.RedrawType = 0;
switch(disp_resolution)
{
case RESOLUTION_320_240:
// sd.spec_len = 256;
// sd.fft_iq_len = 512;
// sd.cfft_instance = &arm_cfft_sR_f32_len256;
sd.spec_len = 512;
sd.fft_iq_len = 1024;
sd.cfft_instance = &arm_cfft_sR_f32_len512;
break;
case RESOLUTION_480_320:
sd.spec_len = 512;
sd.fft_iq_len = 1024;
sd.cfft_instance = &arm_cfft_sR_f32_len512;
break;
case RESOLUTION_800_480: //FIXME: fill with correct values (move to layouts.c ??)
sd.spec_len = 512;
sd.fft_iq_len = 1024;
sd.cfft_instance = &arm_cfft_sR_f32_len512;
break;
}
sd.agc_rate = ((float32_t)ts.spectrum_agc_rate) / SPECTRUM_AGC_SCALING; // calculate agc rate
//
sd.wfall_line_update = 0; // init count used for incrementing number of lines for each vertical waterfall screen update
//
// load buffer containing waterfall colours
//
const uint16_t* wfall_scheme = NULL;
switch(ts.waterfall.color_scheme)
{
case WFALL_HOT_COLD:
wfall_scheme = &waterfall_cold_hot[0];
break;
case WFALL_FLAME:
wfall_scheme = &waterfall_flame[0];
break;
case WFALL_SUNSET:
wfall_scheme = &waterfall_sunset[0];
break;
case WFALL_MATRIX:
wfall_scheme = &waterfall_matrix[0];
break;
case WFALL_RAINBOW:
wfall_scheme = &waterfall_rainbow[0];
break;
case WFALL_BLUE:
wfall_scheme = &waterfall_blue[0];
break;
case WFALL_GRAY_INVERSE:
wfall_scheme = &waterfall_grey_inverse[0];
break;
case WFALL_GRAY:
default:
wfall_scheme = &waterfall_grey[0];
break;
}
memcpy(sd.waterfall_colours,wfall_scheme,sizeof(*wfall_scheme)* NUMBER_WATERFALL_COLOURS);
// erase previous position memory
// Load "top" color of palette (the 65th) with that to be used for the center grid color
sd.waterfall_colours[NUMBER_WATERFALL_COLOURS] = sd.scope_centre_grid_colour_active;
if (ts.spectrum_db_scale >= SCOPE_SCALE_NUM)
{
ts.spectrum_db_scale = DB_DIV_ADJUST_DEFAULT;
}
sd.db_scale = scope_scaling_factors[ts.spectrum_db_scale].value;
// if we later scale the spectrum width from the fft width, we incorporate
// the required negative gain for the downsampling here.
// makes spectrum_width float32_t multiplications less per waterfall update
if (sd.spec_len != slayout.scope.w)
{
sd.db_scale *= (float32_t)slayout.scope.w/(float32_t)sd.spec_len;
}
sd.scope_ystart = slayout.scope.y;
sd.scope_size = slayout.scope.h;
sd.wfall_ystart = slayout.wfall.y;
sd.wfall_size = slayout.wfall.h;
// now make sure we fit in
// please note, this works only if we have enough memory for have the lines
// otherwise we will reduce size of displayed waterfall
if((sd.wfall_size * slayout.scope.w) > sizeof(sd.waterfall))
{
//sd.doubleWaterfallLine = 1;
if (sd.wfall_size/2 * slayout.scope.w > sizeof(sd.waterfall))
{
// we caculate how many lines we can do with the amount of memory
// and adjust displayed line count accordingly.
sd.wfall_size = sizeof(sd.waterfall)/(slayout.scope.w);
// FIXME: Notify user of issue
// if memory is too small even with doubled
// lines
}
else
{
sd.wfall_size /= 2;
}
}
/* else
{
sd.repeatWaterfallLine = 0;
}*/
sd.repeatWaterfallLine = slayout.wfall.h/(sd.wfall_size);
//no need for this variable because we have slayout.wfall.h
//sd.wfall_disp_lines = sd.wfall_size * (sd.doubleWaterfallLine==true? 2:1);
for (uint16_t idx = 0; idx < slayout.scope.w; idx++)
{
sd.Old_PosData[idx] = sd.scope_ystart + sd.scope_size;
}
sd.wfall_contrast = (float)ts.waterfall.contrast / 100.0; // calculate scaling for contrast
for (uint16_t marker_idx = 0; marker_idx < SPECTRUM_MAX_MARKER; marker_idx++)
{
sd.marker_line_pos_prev[marker_idx] = 0xffff; // off screen
}
sd.enabled = 1;
}
void UiSpectrum_WaterfallClearData()
{
// this assume sd.watefall being an array, not a pointer to one!
memset(sd.waterfall,0, sizeof(sd.waterfall));
memset(sd.waterfall_frequencies,0,sizeof(sd.waterfall_frequencies));
}
static void UiSpectrum_DrawWaterfall(void)
{
sd.wfall_line %= sd.wfall_size; // make sure that the circular buffer is clipped to the size of the display area
// Contrast: 100 = 1.00 multiply factor: 125 = multiply by 1.25 - "sd.wfall_contrast" already converted to 100=1.00
arm_scale_f32(sd.FFT_Samples, sd.wfall_contrast, sd.FFT_Samples, sd.spec_len);
UiSpectrum_UpdateSpectrumPixelParameters(); // before accessing pixel parameters, request update according to configuration
uint16_t marker_line_pixel_pos[SPECTRUM_MAX_MARKER];
for (uint16_t idx = 0; idx < sd.marker_num; idx++)
{
marker_line_pixel_pos[idx] = sd.marker_pos[idx];
}
// After the above manipulation, clip the result to make sure that it is within the range of the palette table
//for(uint16_t i = 0; i < sd.spec_len; i++)
uint8_t * const waterfallline_ptr = &sd.waterfall[sd.wfall_line*slayout.wfall.w];
for(uint16_t i = 0; i < slayout.wfall.w; i++)
{
if(sd.FFT_Samples[i] >= NUMBER_WATERFALL_COLOURS) // is there an illegal color value?
{
sd.FFT_Samples[i] = NUMBER_WATERFALL_COLOURS - 1; // yes - clip it
}
waterfallline_ptr[i] = sd.FFT_Samples[i]; // save the manipulated value in the circular waterfall buffer
}
sd.waterfall_frequencies[sd.wfall_line] = sd.FFT_frequency;
static uint8_t doubleLineStart=0;
// Draw lines from buffer
doubleLineStart--; //changing the size of repeat of first line (for low RAM devices) to make it look more smooth
if(doubleLineStart==0xff)
{
doubleLineStart=sd.repeatWaterfallLine;
sd.wfall_line++; // bump to the next line in the circular buffer for next go-around
}
uint32_t lptr = sd.wfall_line; // get current line of "bottom" of waterfall in circular buffer
sd.wfall_line_update++; // update waterfall line count
sd.wfall_line_update %= ts.waterfall.vert_step_size; // clip it to number of lines per iteration
if(!sd.wfall_line_update) // if it's count is zero, it's time to move the waterfall up
{
// can't use modulo here, doesn't work if we use uint16_t,
// since it 0-1 == 65536 and not -1 (it is an unsigned integer after all)
lptr = lptr?lptr-1 : sd.wfall_size-1;
lptr %= sd.wfall_size; // do modulus limit of spectrum high
// set up LCD for bulk write, limited only to area of screen with waterfall display. This allow data to start from the
// bottom-left corner and advance to the right and up to the next line automatically without ever needing to address
// the location of any of the display data - as long as we "blindly" write precisely the correct number of pixels per
// line and the number of lines.
UiLcdHy28_BulkPixel_OpenWrite(slayout.wfall.x, slayout.wfall.w, slayout.wfall.y, slayout.wfall.h);
uint16_t spectrum_pixel_buf[slayout.wfall.w];
const int32_t cur_center_hz = sd.FFT_frequency;
uint8_t doubleLine=doubleLineStart;
// we update the display unless there is a ptt request, in this case we skip to the end.
for(uint16_t lcnt = 0; ts.ptt_req == false && lcnt < slayout.wfall.h;) // set up counter for number of lines defining height of waterfall
{
uint8_t * const waterfallline_ptr = &sd.waterfall[lptr*slayout.wfall.w];
const int32_t line_center_hz = sd.waterfall_frequencies[lptr];
// if our old_center is lower than cur_center_hz -> find start idx in waterfall_line, end_idx is line end, and pad with black pixels;
// if our old_center is higher than cur_center_hz -> find start pixel x in end_idx is line end, first pad with black pixels until this point and then use pixel buffer;
// if identical -> well, no padding.
const int32_t diff_centers = (line_center_hz - cur_center_hz);
int32_t offset_pixel = diff_centers/sd.hz_per_pixel;
uint16_t pixel_start, pixel_count, left_padding_count, right_padding_count;
// here we actually create a single line pixel by pixel.
if (offset_pixel >= slayout.wfall.w || offset_pixel <= -slayout.wfall.w)
{
offset_pixel = slayout.wfall.w-1;
}
if (offset_pixel <= 0)
{
// we have to start -offset_pixel later and then pad with black
left_padding_count = 0;
pixel_start = -offset_pixel;
right_padding_count = -offset_pixel;
pixel_count = slayout.wfall.w + offset_pixel;
}
else
{
// we to start with offset_pixel black padding and then draw the pixels until we reach spectrum width
left_padding_count = offset_pixel;
pixel_start = 0;
right_padding_count = 0;
pixel_count = slayout.wfall.w - offset_pixel;
}
uint16_t* pixel_buf_ptr = &spectrum_pixel_buf[0];
// fill from the left border with black pixels
for(uint16_t i = 0; i < left_padding_count; i++)
{
*pixel_buf_ptr++ = Black;
}
for(uint16_t idx = pixel_start, i = 0; i < pixel_count; i++,idx++)
{
*pixel_buf_ptr++ = sd.waterfall_colours[waterfallline_ptr[idx]]; // write to memory using waterfall color from palette
}
// fill to the right border with black pixels
for(uint16_t i = 0; i < right_padding_count; i++)
{
*pixel_buf_ptr++ = Black;
}
for (uint16_t idx = 0; idx < sd.marker_num; idx ++)
{
// Place center line marker on screen: Location [64] (the 65th) of the palette is reserved is a special color reserved for this
if (marker_line_pixel_pos[idx] < slayout.wfall.w)
{
spectrum_pixel_buf[marker_line_pixel_pos[idx]] = sd.waterfall_colours[NUMBER_WATERFALL_COLOURS];
}
}
for(;doubleLine<sd.repeatWaterfallLine+1;doubleLine++)
{
UiLcdHy28_BulkPixel_PutBuffer(spectrum_pixel_buf, slayout.wfall.w);
lcnt++;
if(lcnt==slayout.wfall.h) //preventing the window overlap if doubling oversize the display window
{
break;
}
}
doubleLine=0;
lptr = lptr?lptr-1 : sd.wfall_size-1;
lptr %= sd.wfall_size; // clip to display height
}
UiLcdHy28_BulkPixel_CloseWrite(); // we are done updating the display - return to normal full-screen mode
}
}
static float32_t UiSpectrum_ScaleFFTValue(const float32_t value, float32_t* min_p)
{
float32_t sig = sd.display_offset + Math_log10f_fast(value) * sd.db_scale; // take FFT data, do a log10 and multiply it to scale 10dB (fixed)
// apply "AGC", vertical "sliding" offset (or brightness for waterfall)
if (sig < *min_p)
{
*min_p = sig;
}
return (sig < 1)? 1 : sig;
}
static void UiSpectrum_ScaleFFT(float32_t dest[], float32_t source[], float32_t* min_p )
{
// not an in-place algorithm
assert(dest != source);
for(uint16_t i = 0; i < (sd.spec_len/2); i++)
{
dest[sd.spec_len - i - 1] = UiSpectrum_ScaleFFTValue(source[i + sd.spec_len/2], min_p);
// take FFT data, do a log10 and multiply it to scale 10dB (fixed)
// apply "AGC", vertical "sliding" offset (or brightness for waterfall)
}
for(uint16_t i = (sd.spec_len/2); i < (sd.spec_len); i++)
{
// build right half of spectrum data
dest[sd.spec_len - i - 1] = UiSpectrum_ScaleFFTValue(source[i - sd.spec_len/2], min_p);
// take FFT data, do a log10 and multiply it to scale 10dB (fixed)
// apply "AGC", vertical "sliding" offset (or brightness for waterfall)
}
}
/**
* @brief simple algorithm to scale down in place to a fractional scale
* Please note, that there is no gain correction in this algorithm,
* We rely on the gain correction taking place elsewhere!
* required gain correction is (float32_t)to_len/(float32_t)from_len
*/
static void UiSpectrum_ScaleFFT2SpectrumWidth(float32_t samples[], uint16_t from_len, uint16_t to_len)
{
assert(from_len >= to_len);
const float32_t full_amount = (float32_t)from_len/(float32_t)to_len;
// init loop values
float32_t amount = full_amount;
uint16_t idx_new = 0;
uint16_t idx_old = 0;
float32_t value = 0;
// iterate over both arrays from index 0 towards (to_len-1) or (from_len-1)
do
{
while (amount >=1)
{
value += samples[idx_old];
idx_old++;
amount-= 1.0;
}
float32_t for_next = (1-amount) * samples[idx_old];
value += samples[idx_old] - for_next;
samples[idx_new] = value;
idx_new++;
if (idx_new < to_len)
{
value = for_next;
idx_old++;
amount = full_amount - (1-amount);
}
}
while(idx_new < to_len);
}
// Spectrum Display code rewritten by C. Turner, KA7OEI, September 2014, May 2015
// Waterfall Display code written by C. Turner, KA7OEI, May 2015 entirely from "scratch"
// - which is to say that I did not borrow any of it
// from anywhere else, aside from keeping some of the general functions found in "Case 1".
/**
* @briefs implement a staged calculation and drawing of the spectrum scope / waterfall
* should not be called directly, go through UiSpectrum_Redraw which implements a rate limiter
* it relies on the audio driver implementing the first stage of data collection.
*/
static void UiSpectrum_RedrawSpectrum(void)
{
bool is_RedrawActive=(ts.menu_mode == false) //if this flag is false we do only dBm calculation (for S-meter and tune helper)
&& (sd.enabled == true)
&& (ts.mem_disp == false)
&& (ts.xvtr_disp == false)
&& (ts.SpectrumResize_flag == false)
&& (ts.VirtualKeysShown_flag ==false);
// Process implemented as state machine
switch(sd.state)
{
case 0:
sd.reading_ringbuffer = true;
__DSB();
// we make sure the interrupt sees this variable value by ensure all memory operations have been done
// after this point
arm_copy_f32(&sd.FFT_RingBuffer[sd.samp_ptr],&sd.FFT_Samples[0],sd.fft_iq_len-sd.samp_ptr);
arm_copy_f32(&sd.FFT_RingBuffer[0],&sd.FFT_Samples[sd.fft_iq_len-sd.samp_ptr],sd.samp_ptr);
sd.reading_ringbuffer = false;
// Apply gain to collected IQ samples and then do FFT
// Scale input according to A/D gain and apply Window function
// UiSpectrum_FFTWindowFunction(ts.fft_window_type); // do windowing function on input data to get less "Bin Leakage" on FFT data
// fixed window type to Hann Window, because it provides excellent bin leakage behaviour AND
// it corresponds very well with the coefficients in the quadratic interpolation algorithm that is used
// for the SNAP carrier function
UiSpectrum_FFTWindowFunction(FFT_WINDOW_HANN);
sd.state++;
break;
case 1: // Do FFT and calculate complex magnitude
{
arm_cfft_f32(sd.cfft_instance, sd.FFT_Samples,0,1); // Do FFT
sd.state++;
break;
}
case 2:
{
// Calculate magnitude
arm_cmplx_mag_f32( sd.FFT_Samples, sd.FFT_MagData ,sd.spec_len);
// FIXME:
// just for debugging purposes
// display the spectral noise reduction bin gain values in the second 64 pixels of the spectrum display
if((is_dsp_nr()) && ts.nr_gain_display != 0)
{
if(ts.nr_gain_display == 1)
{
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
{
sd.FFT_MagData[(nr_params.NR_FFT_L / 2 - 1) - bindx] = NR2.Hk[bindx] * 150.0;
}
}
/* else
if(ts.nr_gain_display == 2)
{
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
{
sd.FFT_MagData[(nr_params.NR_FFT_L / 2 - 1) - bindx] = NR2.long_tone_gain[bindx] * 150.0;
}
}
else
if(ts.nr_gain_display == 3)
{
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
{
sd.FFT_MagData[(nr_params.NR_FFT_L / 2 - 1) - bindx] = NR.Hk[bindx] * NR2.long_tone_gain[bindx] * 150.0;
}
} */
// set all other pixels to a low value
for(int bindx = nr_params.NR_FFT_L / 2; bindx < sd.spec_len; bindx++)
{
sd.FFT_MagData[bindx] = 10.0;
}
}
sd.state++;
break;
}
// Low-pass filter amplitude magnitude data
case 3:
{
float32_t filt_factor = 1/(float)ts.spectrum_filter; // use stored filter setting inverted to allow multiplication
arm_scale_f32(sd.FFT_AVGData, filt_factor, sd.FFT_Samples, sd.spec_len); // get scaled version of previous data
arm_sub_f32(sd.FFT_AVGData, sd.FFT_Samples, sd.FFT_AVGData, sd.spec_len); // subtract scaled information from old, average data
arm_scale_f32(sd.FFT_MagData, filt_factor, sd.FFT_Samples, sd.spec_len); // get scaled version of new, input data
arm_add_f32(sd.FFT_Samples, sd.FFT_AVGData, sd.FFT_AVGData, sd.spec_len); // add portion new, input data into average
for(uint32_t i = 0; i < sd.spec_len; i++) // guarantee that the result will always be >= 0
{
if(sd.FFT_AVGData[i] < 1)
{
sd.FFT_AVGData[i] = 1;
}
}
UiSpectrum_CalculateDBm();
if (is_RedrawActive)
{ //continue if there is no objection to display spectrum or waterfall
if(ts.dial_moved)
{
ts.dial_moved = 0; // Dial moved - reset indicator
UiSpectrum_DrawFrequencyBar(); // redraw frequency bar on the bottom of the display
}
sd.state++;
}
else
{
sd.RedrawType=0;
sd.state=0;
}
break;
}
// De-linearize and normalize display data and do AGC processing
case 4:
if (is_RedrawActive) //this is needed for overwrite prevention if menu was drawn when sd.state>4
{
float32_t min1=100000;
// De-linearize data with dB/division
// Transfer data to the waterfall display circular buffer, putting the bins in frequency-sequential order!
// TODO: if we would use a different data structure here (e.g. q15), we could speed up collection of enough samples in driver
// we could let it run as soon as last FFT_Samples read has been done here
UiSpectrum_ScaleFFT(sd.FFT_Samples,sd.FFT_AVGData,&min1);
if (sd.spec_len != slayout.scope.w)
{
// in place downscaling (!)
UiSpectrum_ScaleFFT2SpectrumWidth(sd.FFT_Samples,sd.spec_len, slayout.scope.w);
}
// Adjust the sliding window so that the lowest signal is always black
sd.display_offset -= sd.agc_rate*min1/5;
}
sd.state++;
break;
case 5: // rescale waterfall horizontally, apply brightness/contrast, process pallate and put vertical line on screen, if enabled.
if (is_RedrawActive) //this is needed for overwrite prevention if menu was drawn when sd.state>4
{
if(sd.RedrawType&Redraw_SCOPE)
{
UiSpectrum_DrawScope(sd.Old_PosData, sd.FFT_Samples);
}
if(sd.RedrawType&Redraw_WATERFALL)
{
UiSpectrum_DrawWaterfall();
}
if(((ts.waterfall.speed == 0)
&&(ts.scope_speed == 0))
||(sd.RedrawType != 0)
)
{
sd.state = 0;
}
}
else
{
sd.state = 0;
}
sd.RedrawType=0;
break;
default:
sd.state = 0;
break;
}
}
/**
* @brief Initialize data and display for spectrum display
*/
void UiSpectrum_Init()
{
#ifdef USE_EXPERIMENTAL_MULTIRES
if (disp_resolution == RESOLUTION_480_320)
{
disp_resolution = RESOLUTION_320_240;
pos_spectrum = &pos_spectrum_set[0];
}
else
{
disp_resolution = RESOLUTION_480_320;
pos_spectrum = &pos_spectrum_set[1];
}
UiSpectrum_WaterfallClearData();
#endif
/*
switch(disp_resolution)
{
#ifdef USE_DISP_480_320
case RESOLUTION_480_320:
{
UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, is_scopemode(), is_waterfallmode(), &ts.Layout->SpectrumWindow, 0);
break;
}
#endif
#ifdef USE_DISP_320_240
case RESOLUTION_320_240:
{
const UiArea_t area_320_240 = { .x = 58, .y = 128, .w = 260, .h = 94 };
UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, is_scopemode(), is_waterfallmode(), &area_320_240, 2);
break;
}
#endif
}
*/
// UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, &ts.Layout->SpectrumWindow, ts.Layout->SpectrumWindowPadding);
// if(!ts.show_wide_spectrum)
// {
UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, &ts.Layout->SpectrumWindow, ts.Layout->SpectrumWindowPadding);
// }
// else
// {
// const UiArea_t widearea_320_240 = { .x = 2, .y = 128, .w = 316, .h = 94 };
// UiSpectrum_CalculateLayout(ts.spectrum_size == SPECTRUM_BIG, &widearea_320_240, ts.Layout->SpectrumWindowPadding);
// }
UiSpectrum_InitSpectrumDisplayData();
UiSpectrum_Clear(); // clear display under spectrum scope
UiSpectrum_CreateDrawArea();
UiSpectrum_DisplayFilterBW(); // Update on-screen indicator of filter bandwidth
if(ts.show_wide_spectrum && ts.spectrum_size == SPECTRUM_BIG)
{
UiLcdHy28_DrawStraightLine(0, 136, 320, LCD_DIR_HORIZONTAL, sd.scope_grid_colour_active);
}
//Test
#ifdef SDR_AMBER_480_320
if(ts.spectrum_size == SPECTRUM_BIG)
{
UiLcdHy28_DrawStraightLine(0, 123, 480, LCD_DIR_HORIZONTAL, sd.scope_grid_colour_active);
}
#endif
}
/**
* @brief Calculate parameters for display filter bar. This function is used also for spectrum BW highlight.
*/
void UiSpectrum_CalculateDisplayFilterBW(float32_t* width_pixel_, float32_t* left_filter_border_pos_)
{
const FilterPathDescriptor* path_p = &FilterPathInfo[ts.filter_path];
const FilterDescriptor* filter_p = &FilterInfo[path_p->id];
const float32_t width = filter_p->width;
const float32_t offset = path_p->offset!=0 ? path_p->offset : width/2;
UiSpectrum_UpdateSpectrumPixelParameters(); // before accessing pixel parameters, request update according to configuration
float32_t width_pixel = width/sd.hz_per_pixel; // calculate width of line in pixels
float32_t offset_pixel = offset/sd.hz_per_pixel; // calculate filter center frequency offset in pixels
float32_t left_filter_border_pos;
if(RadioManagement_UsesBothSidebands(ts.dmod_mode)) // special cases - AM, SAM and FM, which are double-sidebanded
{
left_filter_border_pos = sd.rx_carrier_pos - width_pixel; // line starts "width" below center
width_pixel *= 2; // the width is double in AM & SAM, above and below center
}
else if(RadioManagement_LSBActive(ts.dmod_mode)) // not AM, but LSB: calculate position of line, compensating for both width and the fact that SSB/CW filters are not centered
{
left_filter_border_pos = sd.rx_carrier_pos - (offset_pixel + (width_pixel/2)); // if LSB it will be below zero Hz
}
else // USB mode
{
left_filter_border_pos = sd.rx_carrier_pos + (offset_pixel - (width_pixel/2)); // if USB it will be above zero Hz
}
if(left_filter_border_pos < 0) // prevents line to leave left border
{
width_pixel = width_pixel + left_filter_border_pos;
left_filter_border_pos = 0;
}
if(left_filter_border_pos + width_pixel > slayout.scope.w) // prevents line to leave right border
{
width_pixel = (float32_t)slayout.scope.w - left_filter_border_pos;
}
*width_pixel_=width_pixel;
*left_filter_border_pos_=left_filter_border_pos;
}
/**
* @brief Show a small horizontal line below the spectrum to indicate the rx passband of the currently active filter
*/
void UiSpectrum_DisplayFilterBW()
{
if((ts.menu_mode == 0) && (ts.VirtualKeysShown_flag == 0))
{// bail out if in menu mode
// Update screen indicator - first get the width and center-frequency offset of the currently-selected filter
float32_t width_pixel; // calculate width of line in pixels
float32_t left_filter_border_pos;
UiSpectrum_CalculateDisplayFilterBW(&width_pixel,&left_filter_border_pos);
uint16_t pos_bw_y = slayout.graticule.y + slayout.graticule.h - 2;
UiLcdHy28_DrawStraightLineDouble(slayout.graticule.x, pos_bw_y, slayout.graticule.w, LCD_DIR_HORIZONTAL, Black);
uint32_t clr;
// get color for line
UiMenu_MapColors(ts.filter_disp_colour,NULL, &clr);
// draw line
UiLcdHy28_DrawStraightLineDouble(((float32_t)slayout.graticule.x + roundf(left_filter_border_pos)), pos_bw_y, roundf(width_pixel), LCD_DIR_HORIZONTAL, clr);
}
}
/**
* @brief Draw the frequency information on the frequency bar at the bottom of the spectrum scope based on the current frequency
*/
static void UiSpectrum_DrawFrequencyBar()
{
char txt[16];
UiSpectrum_UpdateSpectrumPixelParameters();
if (ts.spectrum_freqscale_colour != SPEC_BLACK) // don't bother updating frequency scale if it is black (invisible)!
{
float32_t grat = 6.0f / (float32_t)(1 << sd.magnify);
// This function draws the frequency bar at the bottom of the spectrum scope, putting markers every at every graticule and the full frequency
// (rounded to the nearest kHz) in the "center". (by KA7OEI, 20140913)
// get color for frequency scale
uint32_t clr;
UiMenu_MapColors(ts.spectrum_freqscale_colour,NULL, &clr);
float32_t freq_calc = RadioManagement_GetRXDialFrequency() + (ts.dmod_mode == DEMOD_CW ? RadioManagement_GetCWDialOffset() : 0 ); // get current tune frequency in Hz
// if (sd.magnify == 0)
if (sd.magnify == 0 || ts.iq_freq_mode == FREQ_IQ_CONV_SLIDE)
{
freq_calc += AudioDriver_GetTranslateFreq();
// correct for display center not being RX center frequency location
}
if(sd.magnify < 3)
{
freq_calc = roundf(freq_calc/1000); // round graticule frequency to the nearest kHz
}
else if (sd.magnify < 5)
{
freq_calc = roundf(freq_calc/100) / 10; // round graticule frequency to the nearest 100Hz
}
else if(sd.magnify == 5)
{
freq_calc = roundf(freq_calc/50) / 20; // round graticule frequency to the nearest 50Hz
}
int16_t centerIdx = -100; // UiSpectrum_GetGridCenterLine(0);
uint16_t idx2pos[pos_spectrum->SCOPE_GRID_VERT_COUNT+1];
// remainder of frequency/graticule markings
for(int i=1;i<pos_spectrum->SCOPE_GRID_VERT_COUNT;i++)
{
idx2pos[i]=sd.vert_grid_id[i-1];
}
idx2pos[0]=0;
idx2pos[pos_spectrum->SCOPE_GRID_VERT_COUNT]=slayout.scope.w-1;
if(sd.magnify > 2)
{
idx2pos[pos_spectrum->SCOPE_GRID_VERT_COUNT-1]-=9;
}
// FIXME: This code expect 8 vertical lines)
for (int idx = -4; idx < 5; idx += (sd.magnify < 2) ? 1 : 2 )
{
int pos = idx2pos[idx+4];
const uint8_t graticule_font = 4;
const uint16_t number_width = UiLcdHy28_TextWidth(" ",graticule_font);
const uint16_t pos_number_y = (slayout.graticule.y + (slayout.graticule.h - UiLcdHy28_TextHeight(graticule_font))/2);
if (idx != centerIdx)
{
char *c;
if(sd.magnify < 3)
{
snprintf(txt,16, "%02lu", ((uint32_t)(freq_calc+(idx*grat)))%100); // build string for middle-left frequency (1khz precision)
c = txt; // point at 2nd character from the end
}
else
{
float32_t disp_freq = freq_calc+(idx*grat);
int bignum = disp_freq;
int smallnum = roundf((disp_freq-bignum)*100);
snprintf(txt,16, " %u.%02u", bignum, smallnum); // build string for middle-left frequency (10Hz precision)
c = &txt[strlen(txt)-4]; // point at 5th character from the end
}
if (idx == -4) // left border
{
UiLcdHy28_PrintText( slayout.graticule.x + pos, pos_number_y,c,clr,Black,graticule_font);
}
else if (idx == 4) // right border
{
UiLcdHy28_PrintTextRight( slayout.graticule.x + pos, pos_number_y,c,clr,Black,graticule_font);
}
else
{
UiLcdHy28_PrintTextCentered(slayout.graticule.x + pos - number_width/2,pos_number_y, number_width,c,clr,Black,graticule_font);
}
}
}
}
}
void UiSpectrum_Redraw()
{
// Only in RX mode and NOT while powering down or in menu mode or if displaying memory information
if (
(ts.txrx_mode == TRX_MODE_RX)
//&& (ts.menu_mode == false)
&& (ts.powering_down == false)
//&& (ts.mem_disp == false)
//&& (sd.enabled == true)
&& (ts.lcd_blanking_flag == false)
//&& (ts.SpectrumResize_flag == false)
)
{
if(ts.waterfall.scheduler == 0 && is_waterfallmode()) // is waterfall mode enabled?
{
if(ts.waterfall.speed > 0) // is it time to update the scan, or is this scope to be disabled?
{
//ts.waterfall.scheduler = (ts.waterfall.speed)*(sd.doubleWaterfallLine?50:25); // we need to use half the speed if in double line drawing mode
ts.waterfall.scheduler = (ts.waterfall.speed)*(10*(sd.repeatWaterfallLine)); // we need to use half the speed if in double line drawing mode
sd.RedrawType|=Redraw_WATERFALL;
}
}
if(ts.scope_scheduler == 0 && is_scopemode()) // is waterfall mode enabled?
{
if(ts.scope_speed > 0) // is it time to update the scan, or is this scope to be disabled?
{
ts.scope_scheduler = (ts.scope_speed)*50;
sd.RedrawType|=Redraw_SCOPE;
}
}
UiSpectrum_RedrawSpectrum();
}
}
//void ui_spectrum_init_cw_snap_display (bool visible)
void UiSpectrum_InitCwSnapDisplay (bool visible)
{
int color = Green;
if(!visible)
{
color = Black;
// also erase yellow indicator
UiLcdHy28_DrawFullRect(ts.Layout->SNAP_CARRIER.x-27, ts.Layout->SNAP_CARRIER.y, 6, 58, Black);
}
//Horizontal lines of box
UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x-27,
ts.Layout->SNAP_CARRIER.y + 6,
27,
LCD_DIR_HORIZONTAL,
color);
UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x+5,
ts.Layout->SNAP_CARRIER.y + 6,
27,
LCD_DIR_HORIZONTAL,
color);
UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x-27,
ts.Layout->SNAP_CARRIER.y - 1,
27,
LCD_DIR_HORIZONTAL,
color);
UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x+5,
ts.Layout->SNAP_CARRIER.y - 1,
27,
LCD_DIR_HORIZONTAL,
color);
// vertical lines of box
UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x-27,
ts.Layout->SNAP_CARRIER.y - 1,
8,
LCD_DIR_VERTICAL,
color);
UiLcdHy28_DrawStraightLine(ts.Layout->SNAP_CARRIER.x+31,
ts.Layout->SNAP_CARRIER.y - 1,
8,
LCD_DIR_VERTICAL,
color);
}
void UiSpectrum_CwSnapDisplay (float32_t delta)
{
#define max_delta 140.0
#define divider 5.0
#define alpha 0.7
#if defined(STM32F7) || defined(STM32H7)
static float32_t old_delta = 0.0;
#endif
static int old_delta_p = 0;
if(delta > max_delta)
{
delta = max_delta;
}
else if(delta < -max_delta)
{
delta = -max_delta;
}
// lowpass filtering only for fast processors
#if defined(STM32F7) || defined(STM32H7)
delta = (1.0 - alpha) * delta + alpha * old_delta;
#endif
int delta_p = (int)(0.5 + (delta / divider));
if(delta_p != old_delta_p)
{
UiLcdHy28_DrawStraightLineDouble( ts.Layout->SNAP_CARRIER.x + old_delta_p + 1,
ts.Layout->SNAP_CARRIER.y,
6,
LCD_DIR_VERTICAL,
Black);
UiLcdHy28_DrawStraightLineDouble( ts.Layout->SNAP_CARRIER.x + delta_p + 1,
ts.Layout->SNAP_CARRIER.y,
6,
LCD_DIR_VERTICAL,
Yellow);
#if defined(STM32F7) || defined(STM32H7)
old_delta = delta;
#endif
old_delta_p = delta_p;
}
}
void UiSpectrum_CalculateSnap(float32_t Lbin, float32_t Ubin, int posbin, float32_t bin_BW)
{
// SNAP is used to estimate the frequency of a carrier and subsequently tune the Rx frequency to that carrier frequency
// At the moment (January 2018), it is usable in the following demodulation modes:
// AM & SAM
// CW -> a morse activity detector (built-in in the CW decoding algorithm) detects whenever a CW signal is present and allows
// frequency estimation update ONLY when a carrier is present
// DIGIMODE -> BPSK
// DD4WH, Jan 2018
//
if(ads.CW_signal || (ts.dmod_mode == DEMOD_AM || ts.dmod_mode == DEMOD_SAM || (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)))
// this is only done, if there has been a pulse from the CW station that exceeds the threshold
// in the CW decoder section
// OR if we are in AM/SAM/Digi BPSK mode
{
static float32_t freq_old = 10000000.0;
float32_t help_freq = df.tune_old;
// 1. lowpass filter all the relevant bins over 2 to 20 FFTs (?)
// lowpass filtering already exists in the spectrum/waterfall display driver
// 2. determine bin with maximum value inside these samples
// look for maximum value and save the bin # for frequency delta calculation
float32_t maximum = 0.0;
float32_t maxbin = 1.0;
float32_t delta1 = 0.0;
float32_t delta2 = 0.0;
float32_t delta = 0.0;
for (int c = (int)Lbin; c <= (int)Ubin; c++) // search for FFT bin with highest value = carrier and save the no. of the bin in maxbin
{
if (maximum < sd.FFT_Samples[c])
{
maximum = sd.FFT_Samples[c];
maxbin = c;
}
}
// 3. first frequency carrier offset calculation
// ok, we have found the maximum, now save first delta frequency
// delta1 = (maxbin - (float32_t)posbin) * bin_BW;
delta1 = ((maxbin + 1.0) - (float32_t)posbin) * bin_BW;
// 4. second frequency carrier offset calculation
if(maxbin < 1.0)
{
maxbin = 1.0;
}
float32_t bin1 = sd.FFT_Samples[(int)maxbin-1];
float32_t bin2 = sd.FFT_Samples[(int)maxbin];
float32_t bin3 = sd.FFT_Samples[(int)maxbin+1];
if (bin1+bin2+bin3 == 0.0) bin1= 0.00000001; // prevent divide by 0
// estimate frequency of carrier by three-point-interpolation of bins around maxbin
// formula by (Jacobsen & Kootsookos 2007) equation (4) P=1.36 for Hanning window FFT function
// delta2 = (bin_BW * (1.75 * (bin3 - bin1)) / (bin1 + bin2 + bin3));
delta2 = (bin_BW * (1.36 * (bin3 - bin1)) / (bin1 + bin2 + bin3));
if(delta2 > bin_BW) delta2 = 0.0;
delta = delta1 + delta2;
if(ts.dmod_mode == DEMOD_CW)
{ // only add offset, if in CW mode, not in AM/SAM etc.
const float32_t cw_offset = (ts.cw_lsb?1.0:-1.0)*(float32_t)ts.cw_sidetone_freq;
delta = delta + cw_offset;
}
if(ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)
{
if(ts.digi_lsb)
{
delta = delta + PSK_OFFSET; //
}
else
{
delta = delta - PSK_OFFSET; //
}
}
// make 10 frequency measurements and after that take the lowpass filtered frequency to tune to
help_freq = help_freq + delta;
// do we need a lowpass filter?
help_freq = 0.2 * help_freq + 0.8 * freq_old;
ads.snap_carrier_freq = (ulong) (help_freq);
freq_old = help_freq;
static uint8_t snap_counter = 0;
#if defined(STM32F7) || defined(STM32H7)
const int SNAP_COUNT_MAX = 10;
#else
const int SNAP_COUNT_MAX = 6;
#endif
if(sc.snap == true)
{
snap_counter++;
if(snap_counter >= SNAP_COUNT_MAX) // take low pass filtered 6 freq measurements
{
// tune to frequency
// set frequency of Si570 with 4 * dialfrequency
df.tune_new = help_freq;
// reset counter
snap_counter = 0;
sc.snap = false;
}
}
// graphical TUNE HELPER display
UiSpectrum_CwSnapDisplay (delta);
}
}
static void UiSpectrum_CalculateDBm()
{
//###########################################################################################################################################
//###########################################################################################################################################
// dBm/Hz-display DD4WH June, 9th 2016
// the dBm/Hz display gives an absolute measure of the signal strength of the sum of all signals inside the passband of the filter
// we take the FFT-magnitude values of the spectrum display FFT for this purpose (which are already calculated for the spectrum display),
// so the additional processor load and additional RAM usage should be close to zero
// this code also calculates the basis for the S-Meter (in sm.dbm and sm.dbmhz)
//
if( ts.txrx_mode == TRX_MODE_RX)
{
const float32_t slope = 19.8; // 19.6; --> empirical values derived from measurements by DL8MBY, 2016/06/30, Thanks!
const float32_t cons = ts.dbm_constant - 225 - (sd.fft_iq_len == 1024?3:0);
// the last term is for correcting the dbm value when a twice as large fft is being used (512 vs. 256)
const int buff_len_int = sd.fft_iq_len;
const float32_t buff_len = buff_len_int;
// width of a 256 tap FFT bin = 187.5Hz
// we have to take into account the magnify mode
// --> recalculation of bin_BW
// correct bin bandwidth is determined by the Zoom FFT display setting
const float32_t bin_BW = IQ_SAMPLE_RATE_F * 2.0 / (buff_len * (1 << sd.magnify)) ;
float32_t width = FilterInfo[ts.filters_p->id].width;
float32_t offset = ts.filters_p->offset;
if (offset == 0)
{
offset = width/2;
}
const float32_t lf_freq = offset - width/2;
const float32_t uf_freq = offset + width/2;
// determine Lbin and Ubin from ts.dmod_mode and FilterInfo.width
// = determine bandwith separately for lower and upper sideband
float32_t bw_LOWER = 0.0;
float32_t bw_UPPER = 0.0;
if (RadioManagement_UsesBothSidebands(ts.dmod_mode) == true)
{
if((ts.expflags1 & EXPFLAGS1_SMETER_AM_NOT_LO) && (ts.dmod_mode == DEMOD_AM))
{
bw_UPPER = uf_freq; // Calculate USB
bw_LOWER = 300; // 300 Hz
}
else
{
bw_UPPER = uf_freq;
bw_LOWER = -uf_freq;
}
}
else if (RadioManagement_LSBActive(ts.dmod_mode) == true)
{
bw_UPPER = -lf_freq;
bw_LOWER = -uf_freq;
}
else if (is_demod_psk())
{ // this is for experimental SNAP of BPSK carriers
bw_LOWER = PSK_OFFSET - PSK_SNAP_RANGE;
bw_UPPER = PSK_OFFSET + PSK_SNAP_RANGE;
}
else // USB
{
bw_UPPER = uf_freq;
bw_LOWER = lf_freq;
}
// determine posbin (where we receive at the moment) from ts.iq_freq_mode
// frequency translation off, IF = 0 Hz OR
// in all magnify cases (2x up to 32x) the posbin is in the centre of the spectrum display
// const int32_t bin_offset = sd.magnify != 0 ? 0 : (- (buff_len_int * AudioDriver_GetTranslateFreq( )) / (2 * IQ_SAMPLE_RATE));
// const int32_t posbin = buff_len_int / 4 + bin_offset; // right in the middle! const int32_t bin_offset = (sd.magnify != 0 && ts.iq_freq_mode != FREQ_IQ_CONV_SLIDE) ? 0 : (- (buff_len_int * AudioDriver_GetTranslateFreq( )) / (2 * IQ_SAMPLE_RATE));
const int32_t bin_offset = (sd.magnify != 0 && ts.iq_freq_mode != FREQ_IQ_CONV_SLIDE) ? 0 : (- (buff_len_int * AudioDriver_GetTranslateFreq( )) / (2 * IQ_SAMPLE_RATE));
const int32_t posbin = buff_len_int / 4 + bin_offset * (1 << sd.magnify); // right in the middle!
// calculate upper and lower limit for determination of signal strength
// = filter passband is between the lower bin Lbin and the upper bin Ubin
float32_t Lbin = (float32_t)posbin + roundf(bw_LOWER / bin_BW);
float32_t Ubin = (float32_t)posbin + roundf(bw_UPPER / bin_BW); // the bin on the upper sideband side
if(ts.dmod_mode == DEMOD_SAM && ads.sam_sideband == SAM_SIDEBAND_USB) // workaround to make SNAP and carrier offset display work with sideband-selected SAM
{
Lbin = Lbin - 1.0;
}
// take care of filter bandwidths that are larger than the displayed FFT bins
if(Lbin < 0)
{
Lbin = 0;
}
if (Ubin > (sd.spec_len-1))
{
Ubin = sd.spec_len-1;
}
for(int32_t i = 0; i < (buff_len_int/4); i++)
{
sd.FFT_Samples[sd.spec_len - i - 1] = sd.FFT_MagData[i + buff_len_int/4] * SCOPE_PREAMP_GAIN; // get data
}
for(int32_t i = buff_len_int/4; i < (buff_len_int/2); i++)
{
sd.FFT_Samples[sd.spec_len - i - 1] = sd.FFT_MagData[i - buff_len_int/4] * SCOPE_PREAMP_GAIN; // get data
}
// here would be the right place to start with the SNAP mode!
if(cw_decoder_config.snap_enable && (ts.dmod_mode == DEMOD_CW || ts.dmod_mode == DEMOD_AM || ts.dmod_mode == DEMOD_SAM || (ts.dmod_mode == DEMOD_DIGI && ts.digital_mode == DigitalMode_BPSK)))
{
UiSpectrum_CalculateSnap(Lbin, Ubin, posbin, bin_BW);
}
float32_t sum_db = 0.0;
// determine the sum of all the bin values in the passband
for (int c = Lbin; c <= (int)Ubin; c++) // sum up all the values of all the bins in the passband
{
sum_db = sum_db + sd.FFT_Samples[c]; // / (float32_t)(1<<sd.magnify);
}
// we have to account for the larger number of bins that are summed up when using higher
// magnifications
// for example: if we have 34 bins to sum up for sd.magnify == 1, we sum up 68 bins for sd.magnify == 2
// sum_db /= (float32_t)sd.magnify + 1;
// cons = cons - 3.0 * (sd.magnify);
if (sum_db > 0)
{
sm.dbm_cur = slope * Math_log10f_fast (sum_db) + cons;
sm.dbmhz_cur = sm.dbm_cur - 10 * Math_log10f_fast ((float32_t)(((int)Ubin-(int)Lbin) * bin_BW)) ;
}
else
{
sm.dbm_cur = -145.0;
sm.dbmhz_cur = -145.0;
}
}
}
/*
#ifdef X_USE_DISP_480_320_SPEC
//Waterfall memory pointer allocation.
//It sets memory pointer to Height/2 array located in CCM for f4 devices with low ram amount. For all rest allocates memory by calling malloc.
void UiSpectrum_SetWaterfallMemoryPointer(uint16_t ramsize)
{
if(ramsize<256)
{
sd.waterfall=sd.waterfall_mem; //CCM memory for devices with low ram amount. Used with each line doubled.
}
else
{
sd.waterfall=(uint8_t (*)[SPECTRUM_WIDTH]) malloc(WATERFALL_HEIGHT*SPECTRUM_WIDTH); //malloc returns pointer in normal ram (for F4 devices CCM memory is always 64kB)
}
}
#endif
*/