/* -*- 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 #include #include #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_ydraw.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_ydraw.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<",scope_scaling_factors[0].label, (1<",scope_scaling_factors[ts.spectrum_db_scale].label, (1<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<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=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_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= 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;iSCOPE_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; } //Menu Contest ts.maxi_pos_contest = 24; ts.maxi_pos_contesttry = 10; // 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< 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 */