2248 lines
106 KiB
C
2248 lines
106 KiB
C
/* -*- 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
|
|
*/
|