From 22e1eba41737d8eed83a4e42195269c8f5243e74 Mon Sep 17 00:00:00 2001 From: Dan Nechita Date: Wed, 1 Jul 2015 14:52:31 +0300 Subject: [PATCH] Spectrum Analyzer: Add the first working version Still work in progress. Signed-off-by: Dan Nechita --- Makefile | 3 +- datatypes.c | 6 +- datatypes.h | 27 +- fmcomms2.glade | 213 ++++++------ osc.h | 1 + oscplot.c | 350 +++++++++++++++++-- oscplot.h | 4 + plugins/fmcomms2.c | 16 + plugins/spectrum_analyzer.c | 658 ++++++++++++++++++++++++++++++++++++ spectrum_analyzer.glade | 275 +++++++++++++++ 10 files changed, 1415 insertions(+), 138 deletions(-) create mode 100644 plugins/spectrum_analyzer.c create mode 100644 spectrum_analyzer.glade diff --git a/Makefile b/Makefile index f9c3a8b3b..c7a4816db 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ CFLAGS := $(shell $(PKG_CONFIG) --cflags $(DEPENDENCIES)) \ -Wall -Wclobbered -Wempty-body -Wignored-qualifiers -Wmissing-field-initializers \ -Wmissing-parameter-type -Wold-style-declaration -Woverride-init \ -Wsign-compare -Wtype-limits -Wuninitialized -Wunused-but-set-parameter \ - -Werror -g -std=gnu90 -D_GNU_SOURCE -O2 -DPREFIX='"$(PREFIX)"' \ + -Werror -g -std=gnu90 -D_GNU_SOURCE -ggdb -DPREFIX='"$(PREFIX)"' \ -DFRU_FILES=\"$(FRU_FILES)\" -DGIT_VERSION=\"$(GIT_VERSION)\" \ -DGIT_COMMIT_TIMESTAMP='"$(GIT_COMMIT_TIMESTAMP)"' \ -DOSC_VERSION=\"$(GIT_BRANCH)-g$(GIT_HASH)\" \ @@ -70,6 +70,7 @@ PLUGINS=\ plugins/motor_control.$(SO) \ plugins/dmm.$(SO) \ plugins/debug.$(SO) \ + plugins/spectrum_analyzer.$(SO) \ $(if $(WITH_MINGW),,plugins/scpi.so) ifdef V diff --git a/datatypes.c b/datatypes.c index 9754d93ac..baa1666b0 100644 --- a/datatypes.c +++ b/datatypes.c @@ -91,7 +91,7 @@ void Transform_attach_settings(Transform *tr, void *settings) tr->settings = settings; } -void Transform_attach_function(Transform *tr, void (*f)(Transform *tr , gboolean init_transform)) +void Transform_attach_function(Transform *tr, bool (*f)(Transform *tr , gboolean init_transform)) { tr->transform_function = f; } @@ -101,9 +101,9 @@ void Transform_setup(Transform *tr) tr->transform_function(tr, TRUE); } -void Transform_update_output(Transform *tr) +bool Transform_update_output(Transform *tr) { - tr->transform_function(tr, FALSE); + return tr->transform_function(tr, FALSE); } TrList* TrList_new(void) diff --git a/datatypes.h b/datatypes.h index 01ba6dd5b..74e0f2c3c 100644 --- a/datatypes.h +++ b/datatypes.h @@ -35,6 +35,7 @@ enum { CONSTELLATION_TRANSFORM, COMPLEX_FFT_TRANSFORM, CROSS_CORRELATION_TRANSFORM, + FREQ_SPECTRUM_TRANSFORM, TRANSFORMS_TYPES_COUNT }; @@ -104,7 +105,7 @@ struct _transform { GdkColor *graph_color; bool has_the_marker; void *settings; - void (*transform_function)(Transform *tr, gboolean init_transform); + bool (*transform_function)(Transform *tr, gboolean init_transform); }; struct _tr_list { @@ -160,6 +161,26 @@ struct _cross_correlation_settings { enum marker_types *marker_type; }; +struct _freq_spectrum_settings { + gfloat *real_source; + gfloat *imag_source; + gfloat *freq_axis_source; + gfloat *magn_axis_source; + unsigned freq_axis_size; + unsigned magn_axis_size; + unsigned fft_index; + unsigned fft_count; + double freq_sweep_start; + double filter_bandwidth; + unsigned int fft_size; + unsigned int fft_avg; + gfloat fft_pwr_off; + unsigned fft_lower_clipping_limit; + unsigned fft_upper_clipping_limit; + struct _fft_alg_data *ffts_alg_data; + gfloat fft_corr; +}; + Transform* Transform_new(int tr_type); void Transform_destroy(Transform *tr); void Transform_resize_x_axis(Transform *tr, int new_size); @@ -167,9 +188,9 @@ void Transform_resize_y_axis(Transform *tr, int new_size); gfloat* Transform_get_x_axis_ref(Transform *tr); gfloat* Transform_get_y_axis_ref(Transform *tr); void Transform_attach_settings(Transform *tr, void *settings); -void Transform_attach_function(Transform *tr, void (*f)(Transform *tr , gboolean init_transform)); +void Transform_attach_function(Transform *tr, bool (*f)(Transform *tr , gboolean init_transform)); void Transform_setup(Transform *tr); -void Transform_update_output(Transform *tr); +bool Transform_update_output(Transform *tr); TrList* TrList_new(void); void TrList_destroy(TrList *list); diff --git a/fmcomms2.glade b/fmcomms2.glade index f801e881b..b57719ee3 100644 --- a/fmcomms2.glade +++ b/fmcomms2.glade @@ -1,6 +1,7 @@ + -180 180 @@ -156,46 +157,6 @@ 1 10 - - -91 - 1 - 10 - - - -91 - 1 - 10 - - - -91 - 1 - 10 - - - -91 - 1 - 10 - - - -91 - 1 - 10 - - - -91 - 1 - 10 - - - -91 - 1 - 10 - - - -91 - 1 - 10 - True False @@ -462,7 +423,7 @@ configuration: 1 2 GTK_FILL - + @@ -472,10 +433,8 @@ configuration: Enable Rx FIR Filter only - False True False - False 0 True enable_fir_filter_tx_rx @@ -489,10 +448,8 @@ configuration: Enable Tx FIR Filter only - False True False - False 0 True enable_fir_filter_tx_rx @@ -506,10 +463,8 @@ configuration: Enable Rx & Tx FIR Filters - False True False - False 0 True True @@ -523,10 +478,8 @@ configuration: Disable All - False True False - False 0 True enable_fir_filter_tx_rx @@ -658,6 +611,10 @@ configuration: True True + False + False + True + True adjustment_dcxo_coarse_tune @@ -685,6 +642,10 @@ configuration: True True + False + False + True + True adjustment_dcxo_fine_tune @@ -704,11 +665,9 @@ configuration: FMComms3 with AD-FREQCVT1-EBZ - False True True False - False 0 True @@ -732,23 +691,21 @@ configuration: True False - - - False - True - False - False - Zoom In - True - gtk-go-down - True - - - True - True - 0 - - + + + True + False + Zoom In + True + gtk-go-down + True + + + True + True + 0 + + True @@ -823,6 +780,10 @@ configuration: True True + False + False + True + True adjustment_rf_bw_rx 3 @@ -848,6 +809,10 @@ configuration: True True + False + False + True + True adjustment_sampl_freq_tx 6 @@ -875,6 +840,10 @@ configuration: True True + False + False + True + True adjustment_rx_lo_freq 6 @@ -950,11 +919,9 @@ configuration: Store - False True True True - False GTK_FILL @@ -963,11 +930,9 @@ configuration: Recall - False True True True - False 1 @@ -1031,6 +996,10 @@ configuration: True True + False + False + True + True adjustment_hw_gain_rx1 2 @@ -1185,6 +1154,10 @@ configuration: True True + False + False + True + True adjustment_hw_gain_rx2 2 @@ -1336,11 +1309,9 @@ configuration: Quadrature - False True True False - False 0 True @@ -1353,11 +1324,9 @@ configuration: RF DC - False True True False - False 0 True True @@ -1371,11 +1340,9 @@ configuration: BB DC - False True True False - False 0 True True @@ -1415,10 +1382,8 @@ configuration: False - False True False - False Zoom In True gtk-go-down @@ -1505,6 +1470,10 @@ configuration: True True + False + False + True + True adjustment_rf_bw_tx 3 @@ -1535,6 +1504,10 @@ configuration: True True + False + False + True + True adjustment_sampl_freq_tx 6 @@ -1580,6 +1553,10 @@ configuration: True True + False + False + True + True adjustment_tx_lo_freq 6 @@ -1652,21 +1629,17 @@ configuration: Store - False True True True - False Recall - False True True True - False 1 @@ -1732,6 +1705,10 @@ configuration: True True + False + False + True + True adjustment_hw_gain_tx1 2 @@ -1827,6 +1804,10 @@ configuration: True True + False + False + True + True adjustment_hw_gain_tx2 2 @@ -1903,10 +1884,8 @@ configuration: False - False True False - False Zoom In True gtk-go-down @@ -2051,6 +2030,10 @@ configuration: True True + False + False + True + True adjust_rx1_phase 4 @@ -2118,6 +2101,10 @@ configuration: True True + False + False + True + True adjust_rx2_phase 4 @@ -2176,10 +2163,8 @@ configuration: False - False True False - False Zoom In True gtk-go-down @@ -2234,11 +2219,9 @@ configuration: Reload Settings - False True True True - False False @@ -2292,10 +2275,8 @@ configuration: False - False True False - False Previous True gtk-go-back @@ -2307,10 +2288,8 @@ configuration: - False True False - False Zoom In True gtk-zoom-in @@ -2322,10 +2301,8 @@ configuration: - False True False - False Zoom Out True gtk-zoom-out @@ -2337,10 +2314,8 @@ configuration: - False True False - False Reset Zoom True gtk-zoom-fit @@ -2352,10 +2327,8 @@ configuration: - False True False - False Next True gtk-go-forward @@ -2421,6 +2394,46 @@ configuration: + + -91 + 1 + 10 + + + -91 + 1 + 10 + + + -91 + 1 + 10 + + + -91 + 1 + 10 + + + -91 + 1 + 10 + + + -91 + 1 + 10 + + + -91 + 1 + 10 + + + -91 + 1 + 10 + diff --git a/osc.h b/osc.h index 033db6e2d..80d25708c 100644 --- a/osc.h +++ b/osc.h @@ -76,6 +76,7 @@ enum marker_types { #define FFT_PLOT 1 #define XY_PLOT 2 #define XCORR_PLOT 3 +#define SPECTRUM_PLOT 4 #define USE_INTERN_SAMPLING_FREQ -1.0 diff --git a/oscplot.c b/oscplot.c index d4630ae3a..6ff5402f7 100644 --- a/oscplot.c +++ b/oscplot.c @@ -51,7 +51,7 @@ static void capture_button_clicked_cb (GtkToggleToolButton *btn, gpointer data); static void single_shot_clicked_cb (GtkToggleToolButton *btn, gpointer data); static void add_grid(OscPlot *plot); static void rescale_databox(OscPlotPrivate *priv, GtkDatabox *box, gfloat border); -static void call_all_transform_functions(OscPlotPrivate *priv); +static bool call_all_transform_functions(OscPlotPrivate *priv); static void capture_start(OscPlotPrivate *priv); static void plot_profile_save(OscPlot *plot, char *filename); static void transform_add_plot_markers(OscPlot *plot, Transform *transform); @@ -215,6 +215,7 @@ struct math_channel_settings { #define FFT_SETTINGS(obj) ((struct _fft_settings *)obj->settings) #define CONSTELLATION_SETTINGS(obj) ((struct _constellation_settings *)obj->settings) #define XCORR_SETTINGS(obj) ((struct _cross_correlation_settings *)obj->settings) +#define FREQ_SPECTRUM_SETTINGS(obj) ((struct _freq_spectrum_settings *)obj->settings) #define MATH_SETTINGS(obj) ((struct _math_settings *)obj->settings) #define PLOT_CHN(obj) ((PlotChn *)obj) @@ -351,12 +352,19 @@ struct _OscPlotPrivate GtkDataboxGraph *grid; gfloat gridy[25], gridx[25]; + /* Spectrum mode - Parameters */ + unsigned fft_count; + double start_freq; + double filter_bw; + gint line_thickness; gint redraw_function; gboolean stop_redraw; gboolean redraw; + bool spectrum_data_ready; + gboolean fullscreen_state; bool profile_loaded_scale; @@ -473,8 +481,8 @@ struct iio_buffer * osc_plot_get_buffer(OscPlot *plot) void osc_plot_data_update (OscPlot *plot) { - call_all_transform_functions(plot->priv); - plot->priv->redraw = TRUE; + if (call_all_transform_functions(plot->priv)) + plot->priv->redraw = TRUE; if (plot->priv->single_shot_mode) { plot->priv->single_shot_mode = false; @@ -651,7 +659,8 @@ bool osc_plot_set_sample_count (OscPlot *plot, gdouble count) if (gtk_toggle_tool_button_get_active((GtkToggleToolButton *)priv->capture_button)) return false; - if (gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)) == FFT_PLOT) { + if (gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)) == FFT_PLOT || + gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)) == SPECTRUM_PLOT) { char s_count[32]; snprintf(s_count, sizeof(s_count), "%d", (int)count); ret = comboboxtext_set_active_by_string(GTK_COMBO_BOX(priv->fft_size_widget), s_count); @@ -677,7 +686,8 @@ double osc_plot_get_sample_count (OscPlot *plot) { OscPlotPrivate *priv = plot->priv; int count; - if (gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)) == FFT_PLOT) + if (gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)) == FFT_PLOT || + gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)) == SPECTRUM_PLOT) count = comboboxtext_get_active_text_as_int(GTK_COMBO_BOX_TEXT(priv->fft_size_widget)); else count = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->sample_count_widget)); @@ -754,6 +764,54 @@ void osc_plot_set_id(OscPlot *plot, int id) plot->priv->object_id = id; } +void osc_plot_spect_mode(OscPlot *plot, bool enable) +{ + OscPlotPrivate *priv = plot->priv; + GtkComboBox *cbox = GTK_COMBO_BOX(priv->plot_domain); + + g_return_if_fail(plot); + + if (enable) { + if (!comboboxtext_set_active_by_string(cbox, "Spectrum Mode")) + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(cbox), + "Spectrum Mode"); + gtk_widget_hide(priv->capture_button); + gtk_widget_hide(priv->ss_button); + } else { + GtkTreeIter iter; + GtkTreeModel *model; + + model = gtk_combo_box_get_model(cbox); + if (gtk_tree_model_get_iter_from_string(model, &iter, + "Spectrum Mode")) { + gtk_list_store_remove(GTK_LIST_STORE(model), &iter); + } + gtk_widget_show(priv->capture_button); + gtk_widget_show(priv->ss_button); + } +} + +void osc_plot_spect_set_start_f(OscPlot *plot, double freq_mhz) +{ + g_return_if_fail(plot); + + plot->priv->start_freq = freq_mhz; +} + +void osc_plot_spect_set_len(OscPlot *plot, unsigned fft_count) +{ + g_return_if_fail(plot); + + plot->priv->fft_count = fft_count; +} + +void osc_plot_spect_set_filter_bw(OscPlot *plot, double bw) +{ + g_return_if_fail(plot); + + plot->priv->filter_bw = bw; +} + static void osc_plot_dispose(GObject *object) { G_OBJECT_CLASS(osc_plot_parent_class)->dispose(object); @@ -1030,6 +1088,105 @@ static void do_fft(Transform *tr) } } +static void do_no_markers_complex_fft(Transform *tr) +{ + struct _freq_spectrum_settings *settings = tr->settings; + struct _fft_alg_data *fft = &settings->ffts_alg_data[settings->fft_index]; + gfloat *in_data = settings->real_source; + gfloat *in_data_c = settings->imag_source; + gfloat *out_data = tr->y_axis + (settings->fft_index * + (settings->fft_upper_clipping_limit - + settings->fft_lower_clipping_limit)); + int fft_size = settings->fft_size; + int i, j, k; + int cnt; + gfloat mag; + double avg, pwr_offset; + gfloat plugin_fft_corr; + + if ((fft->cached_fft_size == -1) || (fft->cached_fft_size != fft_size) || + (fft->cached_num_active_channels != fft->num_active_channels)) { + + if (fft->cached_fft_size != -1) { + fftw_destroy_plan(fft->plan_forward); + fftw_free(fft->win); + fftw_free(fft->out); + if (fft->in != NULL) + fftw_free(fft->in); + if (fft->in_c != NULL) + fftw_free(fft->in_c); + fft->in_c = NULL; + fft->in = NULL; + } + + fft->win = fftw_malloc(sizeof(double) * fft_size); + fft->m = fft_size; + fft->in_c = fftw_malloc(sizeof(fftw_complex) * fft_size); + fft->in = NULL; + fft->out = fftw_malloc(sizeof(fftw_complex) * (fft->m + 1)); + fft->plan_forward = fftw_plan_dft_1d(fft_size, fft->in_c, fft->out, FFTW_FORWARD, FFTW_ESTIMATE); + + for (i = 0; i < fft_size; i ++) + fft->win[i] = win_hanning(i, fft_size); + + fft->cached_fft_size = fft_size; + fft->cached_num_active_channels = fft->num_active_channels; + } + + for (cnt = 0, i = 0; cnt < fft_size; cnt++) { + /* normalization and scaling see fft_corr */ + fft->in_c[cnt] = in_data[i] * fft->win[cnt] + I * in_data_c[i] * fft->win[cnt]; + i++; + } + + struct iio_device *iio_dev = transform_get_device_parent(tr); + struct extra_dev_info *dev_info = iio_device_get_data(iio_dev); + plugin_fft_corr = dev_info->plugin_fft_corr; + + fftw_execute(fft->plan_forward); + avg = (double)settings->fft_avg; + if (avg && avg != 128 ) + avg = 1.0f / avg; + + pwr_offset = settings->fft_pwr_off; + + for (i = 0, k = 0; i < fft->m; ++i) { + if ((unsigned)i < settings->fft_lower_clipping_limit || (unsigned)i >= settings->fft_upper_clipping_limit) + continue; + if (i < (fft->m / 2)) + j = i + (fft->m / 2); + else + j = i - (fft->m / 2); + + if (creal(fft->out[j]) == 0 && cimag(fft->out[j]) == 0) + fft->out[j] = FLT_MIN + I * FLT_MIN; + + mag = 10 * log10((creal(fft->out[j]) * creal(fft->out[j]) + + cimag(fft->out[j]) * cimag(fft->out[j])) / ((unsigned long long)fft->m * fft->m)) + + settings->fft_corr + pwr_offset + plugin_fft_corr; + /* it's better for performance to have separate loops, + * rather than do these tests inside the loop, but it makes + * the code harder to understand... Oh well... + ***/ + if (out_data[k] == FLT_MAX) { + /* Don't average the first iteration */ + out_data[k] = mag; + } else if (!avg) { + /* keep peaks */ + if (out_data[k] <= mag) + out_data[k] = mag; + } else if (avg == 128) { + /* keep min */ + if (out_data[k] >= mag) + out_data[k] = mag; + } else { + /* do an average */ + out_data[k] = ((1 - avg) * out_data[k]) + (avg * mag); + } + k++; + } +} + /* sections of the xcorr function are borrowed (under the GPL) from * http://blog.dmaggot.org/2010/06/cross-correlation-using-fftw3/ * which is copyright 2010 David E. Narváez @@ -1112,7 +1269,7 @@ static void xcorr(fftw_complex *signala, fftw_complex *signalb, fftw_complex *re return; } -void time_transform_function(Transform *tr, gboolean init_transform) +bool time_transform_function(Transform *tr, gboolean init_transform) { struct _time_settings *settings = tr->settings; unsigned axis_length = settings->num_samples; @@ -1142,7 +1299,7 @@ void time_transform_function(Transform *tr, gboolean init_transform) tr->y_axis = settings->data_source; } - return; + return true; } if (tr->plot_channels_type == PLOT_MATH_CHANNEL) { @@ -1153,11 +1310,11 @@ void time_transform_function(Transform *tr, gboolean init_transform) if (!settings->apply_inverse_funct && !settings->apply_multiply_funct && !settings->apply_add_funct) - return; + return true; in_data = plot_channels_get_nth_data_ref(tr->plot_channels, 0); if (!in_data) - return; + return false; for (i = 0; i < tr->y_axis_size; i++) { if (settings->apply_inverse_funct) { @@ -1174,9 +1331,11 @@ void time_transform_function(Transform *tr, gboolean init_transform) tr->y_axis[i] += settings->add_value; } } + + return true; } -void cross_correlation_transform_function(Transform *tr, gboolean init_transform) +bool cross_correlation_transform_function(Transform *tr, gboolean init_transform) { struct _cross_correlation_settings *settings = tr->settings; unsigned axis_length = settings->num_samples; @@ -1217,7 +1376,7 @@ void cross_correlation_transform_function(Transform *tr, gboolean init_transform } tr->y_axis_size = 2 * axis_length - 1; - return; + return true; } GSList *node; @@ -1293,7 +1452,7 @@ void cross_correlation_transform_function(Transform *tr, gboolean init_transform } if (!settings->markers) - return; + return true; /* now we know where the peaks are, we estimate the actual peaks, * by quadratic interpolation of existing spectral peaks, which is explained: @@ -1323,9 +1482,71 @@ void cross_correlation_transform_function(Transform *tr, gboolean init_transform g_mutex_unlock(settings->marker_lock); } } + + return true; } -void fft_transform_function(Transform *tr, gboolean init_transform) +bool freq_spectrum_transform_function(Transform *tr, gboolean init_transform) +{ + struct iio_channel *chn; + struct _freq_spectrum_settings *settings = tr->settings; + unsigned i, j, k, axis_length, fft_size, bits_used; + int ret; + double sampling_freq; + bool complete_transform = false; + + if (init_transform) { + fft_size = settings->fft_size; + chn = PLOT_IIO_CHN(tr->plot_channels->data)->iio_chn; + ret = iio_channel_attr_read_double(chn, "sampling_frequency", + &sampling_freq); + if (ret < 0) + return false; + sampling_freq /= 1000000; /* Hz to MHz*/ + + bits_used = iio_channel_get_data_format(chn)->bits; + + if (!bits_used) + return false; + + /* Compute FFT normalization and scaling offset */ + settings->fft_corr = 20 * log10(2.0 / (1ULL << (bits_used - 1))); + + settings->fft_lower_clipping_limit = (fft_size / 2) - (settings->filter_bandwidth * fft_size) / (2 * sampling_freq); + settings->fft_upper_clipping_limit = (fft_size / 2) + (settings->filter_bandwidth * fft_size) / (2 * sampling_freq); + + settings->real_source = plot_channels_get_nth_data_ref(tr->plot_channels, 0); + settings->imag_source = plot_channels_get_nth_data_ref(tr->plot_channels, 1); + + axis_length = (settings->fft_upper_clipping_limit - settings->fft_lower_clipping_limit) * settings->fft_count; + Transform_resize_x_axis(tr, axis_length); + Transform_resize_y_axis(tr, axis_length); + + for (i = 0, k = 0; i < settings->fft_count; i++) { + for (j = 0; j < fft_size; j++) { + if (j >= settings->fft_lower_clipping_limit && j < settings->fft_upper_clipping_limit) { + tr->x_axis[k] = (j * sampling_freq / settings->fft_size - sampling_freq / 2) + settings->freq_sweep_start + (settings->filter_bandwidth) * i; + tr->y_axis[k] = FLT_MAX; + k++; + } + } + } + + return true; + } + + do_no_markers_complex_fft(tr); + settings->fft_index++; + + if (settings->fft_index == settings->fft_count) { + settings->fft_index = 0; + complete_transform = true; + } + + return complete_transform; +} + +bool fft_transform_function(Transform *tr, gboolean init_transform) { struct iio_device *dev; struct extra_dev_info *dev_info; @@ -1345,7 +1566,7 @@ void fft_transform_function(Transform *tr, gboolean init_transform) /* Initialize axis */ dev = transform_get_device_parent(tr); if (!dev) - return; + return false; dev_info = iio_device_get_data(dev); num_samples = dev_info->sample_count; if (dev_info->channel_trigger_enabled) @@ -1367,7 +1588,7 @@ void fft_transform_function(Transform *tr, gboolean init_transform) bits_used = iio_channel_get_data_format(iio_chn)->bits; if (!bits_used) - return; + return false; axis_length = settings->fft_size * settings->fft_alg_data.num_active_channels / 2; Transform_resize_x_axis(tr, axis_length); Transform_resize_y_axis(tr, axis_length); @@ -1390,7 +1611,7 @@ void fft_transform_function(Transform *tr, gboolean init_transform) if (settings->markers[i].bin >= axis_length) settings->markers[i].bin = 0; - return; + return true; } GSList *node; @@ -1402,9 +1623,11 @@ void fft_transform_function(Transform *tr, gboolean init_transform) m->data_ref, settings->fft_size); } do_fft(tr); + + return true; } -void constellation_transform_function(Transform *tr, gboolean init_transform) +bool constellation_transform_function(Transform *tr, gboolean init_transform) { struct _constellation_settings *settings = tr->settings; unsigned axis_length = settings->num_samples; @@ -1420,7 +1643,7 @@ void constellation_transform_function(Transform *tr, gboolean init_transform) tr->x_axis = settings->x_source; tr->y_axis = settings->y_source; - return; + return true; } GSList *node; @@ -1431,6 +1654,8 @@ void constellation_transform_function(Transform *tr, gboolean init_transform) m->math_expression(m->iio_channels_data, m->data_ref, settings->num_samples); } + + return true; } @@ -1958,6 +2183,7 @@ static struct iio_device * transform_get_device_parent(Transform *transform) static void update_transform_settings(OscPlot *plot, Transform *transform) { OscPlotPrivate *priv = plot->priv; + unsigned i; int plot_type; plot_type = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)); @@ -2007,6 +2233,19 @@ static void update_transform_settings(OscPlot *plot, Transform *transform) XCORR_SETTINGS(transform)->marker_lock = NULL; XCORR_SETTINGS(transform)->marker_type = NULL; XCORR_SETTINGS(transform)->max_x_axis = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->sample_count_widget)); + } else if (plot_type == SPECTRUM_PLOT) { + FREQ_SPECTRUM_SETTINGS(transform)->ffts_alg_data = calloc(sizeof(struct _fft_alg_data), priv->fft_count); + FREQ_SPECTRUM_SETTINGS(transform)->fft_count = priv->fft_count; + FREQ_SPECTRUM_SETTINGS(transform)->freq_sweep_start = priv->start_freq; + FREQ_SPECTRUM_SETTINGS(transform)->filter_bandwidth = priv->filter_bw; + FREQ_SPECTRUM_SETTINGS(transform)->fft_size = comboboxtext_get_active_text_as_int(GTK_COMBO_BOX_TEXT(priv->fft_size_widget)); + FREQ_SPECTRUM_SETTINGS(transform)->fft_avg = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->fft_avg_widget)); + FREQ_SPECTRUM_SETTINGS(transform)->fft_pwr_off = gtk_spin_button_get_value(GTK_SPIN_BUTTON(priv->fft_pwr_offset_widget)); + for (i = 0; i < priv->fft_count; i++) { + FREQ_SPECTRUM_SETTINGS(transform)->ffts_alg_data[i].cached_fft_size = -1; + FREQ_SPECTRUM_SETTINGS(transform)->ffts_alg_data[i].cached_num_active_channels = -1; + FREQ_SPECTRUM_SETTINGS(transform)->ffts_alg_data[i].num_active_channels = g_slist_length(transform->plot_channels); + } } } @@ -2018,6 +2257,7 @@ static Transform* add_transform_to_list(OscPlot *plot, int tr_type, GSList *chan struct _fft_settings *fft_settings; struct _constellation_settings *constellation_settings; struct _cross_correlation_settings *xcross_settings; + struct _freq_spectrum_settings *freq_spectrum_settings; GSList *node; transform = Transform_new(tr_type); @@ -2061,6 +2301,11 @@ static Transform* add_transform_to_list(OscPlot *plot, int tr_type, GSList *chan xcross_settings = (struct _cross_correlation_settings *)calloc(sizeof(struct _cross_correlation_settings), 1); Transform_attach_settings(transform, xcross_settings); break; + case FREQ_SPECTRUM_TRANSFORM: + Transform_attach_function(transform, freq_spectrum_transform_function); + freq_spectrum_settings = (struct _freq_spectrum_settings *)calloc(sizeof(struct _freq_spectrum_settings), 1); + Transform_attach_settings(transform, freq_spectrum_settings); + break; default: fprintf(stderr, "Invalid transform\n"); return NULL; @@ -2125,6 +2370,8 @@ static void remove_transform_from_list(OscPlot *plot, Transform *tr) priv->tr_with_marker = NULL; transform_remove_own_markers(tr); + if (tr->type_id == FREQ_SPECTRUM_TRANSFORM) + free(FREQ_SPECTRUM_SETTINGS(tr)->ffts_alg_data); TrList_remove_transform(list, tr); Transform_destroy(tr); if (list->size == 0) { @@ -2540,19 +2787,20 @@ static void device_rx_info_update(OscPlot *plot) } } -static void call_all_transform_functions(OscPlotPrivate *priv) +static bool call_all_transform_functions(OscPlotPrivate *priv) { TrList *tr_list = priv->transform_list; Transform *tr; bool show_diff_phase = false; + bool valid = true; int i = 0; if (priv->redraw_function <= 0) - return; + return false; for (; i < tr_list->size; i++) { tr = tr_list->transforms[i]; - Transform_update_output(tr); + valid = valid && Transform_update_output(tr); if (tr->has_the_marker) { show_diff_phase = true; draw_marker_values(priv, tr); @@ -2560,6 +2808,8 @@ static void call_all_transform_functions(OscPlotPrivate *priv) } if (show_diff_phase) markers_phase_diff_show(priv); + + return valid; } static int enabled_channels_of_device(GtkTreeView *treeview, const char *name, unsigned *enabled_mask) @@ -2719,6 +2969,11 @@ static void channels_transform_assignment(GtkTreeModel *model, prm->ch_settings = g_slist_reverse(prm->ch_settings); transform = add_transform_to_list(plot, CROSS_CORRELATION_TRANSFORM, prm->ch_settings); } + case SPECTRUM_PLOT: + if (prm->enabled_channels == 2 && num_added_chs == 2) { + prm->ch_settings = g_slist_reverse(prm->ch_settings); + transform = add_transform_to_list(plot, FREQ_SPECTRUM_TRANSFORM, prm->ch_settings); + } default: break; } @@ -2812,9 +3067,9 @@ static gboolean plot_redraw(OscPlotPrivate *priv) if (!GTK_IS_DATABOX(priv->databox)) return FALSE; if (priv->redraw) { - auto_scale_databox(priv, GTK_DATABOX(priv->databox)); - gtk_widget_queue_draw(priv->databox); - fps_counter(priv); + auto_scale_databox(priv, GTK_DATABOX(priv->databox)); + gtk_widget_queue_draw(priv->databox); + fps_counter(priv); } if (priv->stop_redraw == TRUE) priv->redraw_function = 0; @@ -2889,6 +3144,13 @@ static void plot_setup(OscPlot *plot) else if (priv->active_transform_type == CONSTELLATION_TRANSFORM && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->enable_auto_scale))) gtk_databox_set_total_limits(GTK_DATABOX(priv->databox), -1000.0, 1000.0, 1000, -1000); + else if (priv->active_transform_type == FREQ_SPECTRUM_TRANSFORM) { + double end_freq = priv->start_freq + priv->filter_bw * priv->fft_count; + double width = end_freq - priv->start_freq; + gtk_databox_set_total_limits(GTK_DATABOX(priv->databox), + -0.08 * width + priv->start_freq, end_freq + 0.08 * width, + 0.0, -100.0); + } } bool show_phase_info = false; @@ -3562,12 +3824,11 @@ static void add_grid(OscPlot *plot) fill_axis(priv->gridx, 0, 10, 15); fill_axis(priv->gridy, 10, -10, 15); priv->grid = gtk_databox_grid_array_new (15, 15, priv->gridy, priv->gridx, &color_grid, 1); - }else if (priv->active_transform_type == COMPLEX_FFT_TRANSFORM) { + } else if (priv->active_transform_type == COMPLEX_FFT_TRANSFORM) { fill_axis(priv->gridx, -30, 10, 15); fill_axis(priv->gridy, 10, -10, 15); priv->grid = gtk_databox_grid_array_new (15, 15, priv->gridy, priv->gridx, &color_grid, 1); - } - else if (priv->active_transform_type == CONSTELLATION_TRANSFORM) { + } else if (priv->active_transform_type == CONSTELLATION_TRANSFORM) { fill_axis(priv->gridx, -80000, 10000, 18); fill_axis(priv->gridy, -80000, 10000, 18); priv->grid = gtk_databox_grid_array_new (18, 18, priv->gridy, priv->gridx, &color_grid, 1); @@ -3628,6 +3889,24 @@ static void rescale_databox(OscPlotPrivate *priv, GtkDatabox *box, gfloat border gtk_databox_set_total_limits(box, min_x, max_x, max_x, min_x); + } else if (priv->active_transform_type == FREQ_SPECTRUM_TRANSFORM) { + gfloat min_x; + gfloat max_x; + gfloat min_y; + gfloat max_y; + gfloat width; + + gint extrema_success = gtk_databox_calculate_extrema(box, + &min_x, &max_x, &min_y, &max_y); + if (extrema_success) + return; + if (min_x == 0) { + min_x = priv->start_freq + 5; + max_x += 5; + } + width = max_x - min_x; + + gtk_databox_set_total_limits(box, min_x - 0.08 * width, max_x + 0.08 * width, max_y, min_y); } else { gtk_databox_auto_rescale(box, border); } @@ -5339,7 +5618,8 @@ static void plot_domain_changed_cb(GtkComboBox *box, OscPlot *plot) static gboolean domain_is_fft(GBinding *binding, const GValue *source_value, GValue *target_value, gpointer user_data) { - g_value_set_boolean(target_value, g_value_get_int(source_value) == FFT_PLOT); + g_value_set_boolean(target_value, g_value_get_int(source_value) == FFT_PLOT || + g_value_get_int(source_value) == SPECTRUM_PLOT); return TRUE; } @@ -5354,7 +5634,8 @@ static gboolean domain_is_xcorr_fft(GBinding *binding, const GValue *source_value, GValue *target_value, gpointer user_data) { g_value_set_boolean(target_value, g_value_get_int(source_value) == FFT_PLOT || - g_value_get_int(source_value) == XCORR_PLOT); + g_value_get_int(source_value) == XCORR_PLOT || + g_value_get_int(source_value) == SPECTRUM_PLOT); return TRUE; } @@ -5370,15 +5651,22 @@ static void fft_avg_value_changed_cb(GtkSpinButton *button, OscPlot *plot) FFT_SETTINGS(priv->transform_list->transforms[i])->fft_avg = gtk_spin_button_get_value(button); else if (plot_type == XCORR_PLOT) XCORR_SETTINGS(priv->transform_list->transforms[i])->avg = gtk_spin_button_get_value(button); + else if (plot_type == SPECTRUM_PLOT) + FREQ_SPECTRUM_SETTINGS(priv->transform_list->transforms[i])->fft_avg = gtk_spin_button_get_value(button); } } static void fft_pwr_offset_value_changed_cb(GtkSpinButton *button, OscPlot *plot) { OscPlotPrivate *priv = plot->priv; - int i; + int i, plot_type; + + plot_type = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->plot_domain)); for (i = 0; i < priv->transform_list->size; i++) { - FFT_SETTINGS(priv->transform_list->transforms[i])->fft_pwr_off = gtk_spin_button_get_value(button); + if (plot_type == FFT_PLOT) + FFT_SETTINGS(priv->transform_list->transforms[i])->fft_pwr_off = gtk_spin_button_get_value(button); + else if (plot_type == SPECTRUM_PLOT) + FREQ_SPECTRUM_SETTINGS(priv->transform_list->transforms[i])->fft_pwr_off = gtk_spin_button_get_value(button); } } diff --git a/oscplot.h b/oscplot.h index 448ce0098..876a659dc 100644 --- a/oscplot.h +++ b/oscplot.h @@ -69,6 +69,10 @@ void osc_plot_set_quit_callback(OscPlot *plot, void (*qcallback)(void * void osc_plot_reset_numbering (void); int osc_plot_get_id (OscPlot *plot); void osc_plot_set_id (OscPlot *plot, int id); +void osc_plot_spect_mode (OscPlot *plot, bool enable); +void osc_plot_spect_set_start_f(OscPlot *plot, double freq_mhz); +void osc_plot_spect_set_len (OscPlot *plot, unsigned fft_count); +void osc_plot_spect_set_filter_bw(OscPlot *plot, double bw); G_END_DECLS diff --git a/plugins/fmcomms2.c b/plugins/fmcomms2.c index 223683f95..795afef3f 100644 --- a/plugins/fmcomms2.c +++ b/plugins/fmcomms2.c @@ -194,6 +194,21 @@ static const char * fmcomms2_driver_attribs[] = { "dac_buf_filename", }; +static void sampling_freq_changed_plugin_broadcast(void) +{ + struct osc_plugin *plugin; + GSList *node; + + for (node = plugin_list; node; node = g_slist_next(node)) { + plugin = node->data; + if (plugin && (!strcmp(plugin->name, "Spectrum Analyzer"))) { + if (plugin->handle_external_request) { + plugin->handle_external_request("AD9361 Sampling Rate Changed"); + } + } + } +} + static void glb_settings_update_labels(void) { float rates[6]; @@ -308,6 +323,7 @@ static void sample_frequency_changed_cb(void *data) { glb_settings_update_labels(); rx_freq_info_update(); + sampling_freq_changed_plugin_broadcast(); } static void rssi_update_label(GtkWidget *label, const char *chn, bool is_tx) diff --git a/plugins/spectrum_analyzer.c b/plugins/spectrum_analyzer.c new file mode 100644 index 000000000..bb42281ea --- /dev/null +++ b/plugins/spectrum_analyzer.c @@ -0,0 +1,658 @@ +/** + * Copyright (C) 2012-2015 Analog Devices, Inc. + * + * Licensed under the GPL-2. + * + **/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../datatypes.h" +#include "../osc.h" +#include "../iio_widget.h" +#include "../libini2.h" +#include "../osc_plugin.h" +#include "../config.h" +#include "dac_data_manager.h" + +#define THIS_DRIVER "Spectrum Analyzer" +#define PHY_DEVICE "ad9361-phy" +#define CAP_DEVICE "cf-ad9361-lpc" +#define ARRAY_SIZE(x) (!sizeof(x) ?: sizeof(x) / sizeof((x)[0])) +#define MHZ_TO_KHZ(x) ((x) * 1000) +#define HZ_TO_MHZ(x) ((x) / 1E6) + +#define HANNING_ENBW 1.50 + +enum receivers { + RX1, + RX2 +}; + +typedef struct _plugin_setup { + double start_freq; + double stop_freq; + double resolution_bw; + unsigned int fft_size; + enum receivers rx; +} plugin_setup; + +typedef struct _fastlock_profile { + unsigned index; + long long frequency; + char data[66]; +} fastlock_profile; + +static struct iio_context *ctx; +static struct iio_device *dev, *cap; +static struct iio_channel *alt_ch0; +static struct iio_buffer *capture_buffer; +static bool is_2rx_2tx; +static GSList *rx_profiles; +static char *rx_fastlock_store_name; +static char *rx_fastlock_save_name; +static volatile bool kill_sweep_thread; +static double sweep_freq_step = 18; /* 18 MHz */ +static unsigned int profile_count; +static GtkWidget *spectrum_window; +static GMutex profile_recall_lock; +static plugin_setup psetup; + +/* Plugin threads */ +static GThread *freq_sweep_thread, *capture_thread; + +/* Control Widgets */ +static GtkWidget *center_freq; +static GtkWidget *freq_bw; +static GtkWidget *available_RBWs; +static GtkWidget *receiver1; +static GtkWidget *start_button; +static GtkWidget *stop_button; + +/* Default plugin variables */ +static gint this_page; +static GtkNotebook *nbook; +static GtkWidget *analyzer_panel; +static gboolean plugin_detached; + +GTimer *gtimer; + +static ssize_t demux_sample(const struct iio_channel *chn, + void *sample, size_t size, void *d) +{ + struct extra_info *info = iio_channel_get_data(chn); + struct extra_dev_info *dev_info = iio_device_get_data(info->dev); + const struct iio_data_format *format = iio_channel_get_data_format(chn); + + /* Prevent buffer overflow */ + if ((unsigned long) info->offset == (unsigned long) dev_info->sample_count) + return 0; + + if (size == 1) { + int8_t val; + iio_channel_convert(chn, &val, sample); + if (format->is_signed) + *(info->data_ref + info->offset++) = (gfloat) val; + else + *(info->data_ref + info->offset++) = (gfloat) (uint8_t)val; + } else if (size == 2) { + int16_t val; + iio_channel_convert(chn, &val, sample); + if (format->is_signed) + *(info->data_ref + info->offset++) = (gfloat) val; + else + *(info->data_ref + info->offset++) = (gfloat) (uint16_t)val; + } else { + int32_t val; + iio_channel_convert(chn, &val, sample); + if (format->is_signed) + *(info->data_ref + info->offset++) = (gfloat) val; + else + *(info->data_ref + info->offset++) = (gfloat) (uint32_t)val; + } + + return size; +} + +static double device_get_rx_sampling_freq(struct iio_device *dev) +{ + double freq = 0.0; + struct iio_channel *ch0; + + ch0 = iio_device_find_channel(dev, "voltage0", false); + if (ch0) + iio_channel_attr_read_double(ch0, "sampling_frequency", &freq); + else + fprintf(stderr, "Failed to retrieve iio channel in %s\n", __func__); + + return freq; +} + +/* Generate available values for the Resolution Bandwidth. + * RBW = Sampling Rate / N FFT Bins + * In oscplot.c FFT sizes are: 32 <= N <= 65536 + */ +static void comboboxtext_rbw_fill(GtkComboBoxText *box, double sampling_freq) +{ + GtkListStore *liststore; + unsigned fft_size; + char buf[64]; + + g_return_if_fail(box); + + liststore = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(box))); + gtk_list_store_clear(liststore); + + for (fft_size = 65536; fft_size >= 32; fft_size >>= 1) { + snprintf(buf, sizeof(buf), "%.3f", + MHZ_TO_KHZ(sampling_freq) / (double)fft_size); + gtk_combo_box_text_append_text(box, buf); + } +} + +static void log_before_sweep_starts(plugin_setup *setup) +{ + FILE *fp; + GSList *node; + + fp = fopen("spectrum_setup_log.txt", "w"); + if (!fp) { + fprintf(stderr, "Could not open/create spectrum_setup_log.txt " + "file for writing\n"); + return; + } + + fprintf(fp, "Spectrum Setup Log File\n\n"); + fprintf(fp, "Profile count: %d\n", g_slist_length(rx_profiles)); + for (node = rx_profiles; node; node = g_slist_next(node)) { + fastlock_profile *profile = node->data; + fprintf(fp, "Index: %d\n", profile->index); + fprintf(fp, "Frequency: %lld\n", profile->frequency); + fprintf(fp, "Raw Data: %s\n", profile->data); + } + + fclose(fp); +} + +static void init_device_list(struct iio_context *ctx) +{ + unsigned int i, j, num_devices; + + num_devices = iio_context_get_devices_count(ctx); + + for (i = 0; i < num_devices; i++) { + struct iio_device *dev = iio_context_get_device(ctx, i); + unsigned int nb_channels = iio_device_get_channels_count(dev); + struct extra_dev_info *dev_info = calloc(1, sizeof(*dev_info)); + iio_device_set_data(dev, dev_info); + dev_info->input_device = is_input_device(dev); + dev_info->plugin_fft_corr = 20 * log10(1/sqrt(HANNING_ENBW)); + + for (j = 0; j < nb_channels; j++) { + struct iio_channel *ch = iio_device_get_channel(dev, j); + struct extra_info *info = calloc(1, sizeof(*info)); + info->dev = dev; + iio_channel_set_data(ch, info); + } + } +} + +static void plugin_gather_user_setup(plugin_setup *setup) +{ + double center, bw; + + g_return_if_fail(setup); + + center = gtk_spin_button_get_value(GTK_SPIN_BUTTON(center_freq)); + bw = gtk_spin_button_get_value(GTK_SPIN_BUTTON(freq_bw)); + setup->start_freq = center - bw / 2; + setup->stop_freq = center + bw / 2; + setup->fft_size = 65536 >> gtk_combo_box_get_active(GTK_COMBO_BOX(available_RBWs)); + if (!is_2rx_2tx) { + setup->rx = RX1; + } else { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(receiver1))) + setup->rx = RX1; + else + setup->rx = RX2; + } +} + +static void build_profiles_for_entire_sweep(plugin_setup *setup) +{ + double start, stop, step, f; + fastlock_profile *profile; + static unsigned char prev_alc = 0; + unsigned char alc; + char *last_byte; + unsigned int i = 0; + + g_return_if_fail(setup); + + /* Clear any previous profiles */ + g_slist_free_full(rx_profiles, (GDestroyNotify)free); + rx_profiles = NULL; + + start = setup->start_freq; + stop = setup->stop_freq; + + step = sweep_freq_step; + + for (f = start; (step > 0) ? f <= stop : f >= stop; f += step) { + iio_channel_attr_write_longlong(alt_ch0, "frequency", + (long long)f * 1000000ul); + iio_channel_attr_write_longlong(alt_ch0, + rx_fastlock_store_name, 0); + profile = malloc(sizeof(fastlock_profile)); + if (!profile) + return; + iio_channel_attr_read(alt_ch0, rx_fastlock_save_name, + profile->data, sizeof(profile->data)); + profile->frequency = (long long)f * 1000000ul; + profile->index = i++; + rx_profiles = g_slist_prepend(rx_profiles, profile); + + /* Make sure two consecutive profiles do not have the same ALC. + * Disregard the LBS of the ALC when comparing. + More on: https://ez.analog.com/message/151702#151702 */ + last_byte = g_strrstr(profile->data, ",") + 1; + alc = atoi(last_byte); + if (abs(alc - prev_alc) < 2) + alc += 2; + prev_alc = alc; + sprintf(last_byte, "%d", alc); + + } + rx_profiles = g_slist_reverse(rx_profiles); + profile_count = g_slist_length(rx_profiles); + log_before_sweep_starts(setup); +} + +static void configure_spectrum_window(plugin_setup *setup) +{ + unsigned int i; + bool enable; + + g_return_if_fail(setup); + + osc_plot_spect_mode(OSC_PLOT(spectrum_window), true); + osc_plot_set_domain(OSC_PLOT(spectrum_window), SPECTRUM_PLOT); + osc_plot_set_sample_count(OSC_PLOT(spectrum_window), setup->fft_size); + for (i = 0; i < iio_device_get_channels_count(cap); i++) { + enable = i / 2 == setup->rx; + osc_plot_set_channel_state(OSC_PLOT(spectrum_window), CAP_DEVICE, + i, enable); + } + osc_plot_spect_set_len(OSC_PLOT(spectrum_window), profile_count); + osc_plot_spect_set_start_f(OSC_PLOT(spectrum_window), setup->start_freq); + osc_plot_spect_set_filter_bw(OSC_PLOT(spectrum_window), sweep_freq_step); + osc_plot_set_visible(OSC_PLOT(spectrum_window), true); +} + +static void spectrum_window_destroyed_cb(OscPlot *plot); +static void build_spectrum_window(plugin_setup *setup) +{ + g_return_if_fail(setup); + + spectrum_window = osc_plot_new(ctx); + configure_spectrum_window(setup); + g_signal_connect(spectrum_window, "osc-destroy-event", + G_CALLBACK(spectrum_window_destroyed_cb), NULL); +} + +static void configure_data_capture(plugin_setup *setup) +{ + struct iio_channel *chn; + struct extra_info *info; + struct extra_dev_info *dev_info; + unsigned int i; + + g_return_if_fail(setup); + + dev_info = iio_device_get_data(cap); + dev_info->sample_count = setup->fft_size; + for (i = 0; i < iio_device_get_channels_count(cap); i++) { + chn = iio_device_get_channel(cap, i); + info = iio_channel_get_data(chn); + if (info->data_ref) { + g_free(info->data_ref); + info->data_ref = NULL; + } + if (i / 2 == setup->rx) { + iio_channel_enable(chn); + info->data_ref = (gfloat *) g_new0(gfloat, setup->fft_size); + } else { + iio_channel_disable(chn); + } + } +} + +static gpointer capture_and_display(plugin_setup *setup) +{ + g_return_val_if_fail(setup, NULL); + + g_mutex_lock(&profile_recall_lock); + if (capture_buffer) { + iio_buffer_destroy(capture_buffer); + capture_buffer = NULL; + if (spectrum_window) + osc_plot_data_update(OSC_PLOT(spectrum_window)); + } + + capture_buffer = iio_device_create_buffer(cap, setup->fft_size, false); + if (!capture_buffer) { + fprintf(stderr, "Could not create iio buffer in %s\n", __func__); + g_mutex_unlock(&profile_recall_lock); + goto end; + } + + /* Reset the data offset for all channels */ + unsigned int i; + for (i = 0; i < iio_device_get_channels_count(cap); i++) { + struct iio_channel *ch = iio_device_get_channel(cap, i); + struct extra_info *info = iio_channel_get_data(ch); + info->offset = 0; + } + /* Get captured data */ + ssize_t ret = iio_buffer_refill(capture_buffer); + if (ret < 0) { + fprintf(stderr, "Error while refilling iio buffer: %s\n", strerror(-ret)); + goto end; + } + g_mutex_unlock(&profile_recall_lock); + /* Demux captured data */ + ret /= iio_buffer_step(capture_buffer); + if ((unsigned)ret >= setup->fft_size) + iio_buffer_foreach_sample(capture_buffer, demux_sample, NULL); + +end: + return NULL; +} + +static gpointer rx_lo_frequency_sweep(plugin_setup *setup) +{ + GSList *node = rx_profiles; + fastlock_profile *profile; + ssize_t ret; + + g_return_val_if_fail(setup, NULL); + + /* Set the first profile */ + profile = (fastlock_profile *)node->data; + + ret = iio_channel_attr_write(alt_ch0, "fastlock_load", + profile->data); + if (ret < 0) + fprintf(stderr, "Could not write to fastlock_load" + "attribute in %s\n", __func__); + ret = iio_channel_attr_write_longlong(alt_ch0, + "fastlock_recall", 0); + if (ret < 0) + fprintf(stderr, "Could not write to fastlock_recall" + "attribute in %s\n", __func__); +gtimer = g_timer_new(); + node = g_slist_next(node); + /* Start the Sweep */ + while (!kill_sweep_thread) { + capture_thread = g_thread_new("Capture", + (GThreadFunc)capture_and_display, setup); + + /* When only one profile exists */ + if (!node) + goto skip_profile_load; + + profile = (fastlock_profile *)node->data; + + ret = iio_channel_attr_write(alt_ch0, "fastlock_load", + profile->data); + if (ret < 0) + fprintf(stderr, "Could not write to fastlock_load" + "attribute in %s\n", __func__); + g_mutex_lock(&profile_recall_lock); + ret = iio_channel_attr_write_longlong(alt_ch0, + "fastlock_recall", 0); + g_mutex_unlock(&profile_recall_lock); + if (ret < 0) + fprintf(stderr, "Could not write to fastlock_recall" + "attribute in %s\n", __func__); +skip_profile_load: + node = g_slist_next(node); + if (!node) { + node = rx_profiles; + g_timer_stop(gtimer); + fprintf(stderr, "%f\n", g_timer_elapsed(gtimer, NULL)); + g_timer_start(gtimer); + } + g_thread_join(capture_thread); + capture_thread = NULL; + } + + return NULL; +} + +static void start_sweep_clicked(GtkButton *btn, gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(btn), false); + + plugin_gather_user_setup(&psetup); + build_profiles_for_entire_sweep(&psetup); + configure_data_capture(&psetup); + if (!spectrum_window) + build_spectrum_window(&psetup); + else + configure_spectrum_window(&psetup); + osc_plot_draw_start(OSC_PLOT(spectrum_window)); + + kill_sweep_thread = false; + freq_sweep_thread = g_thread_new("RX LO Sweep", + (GThreadFunc)rx_lo_frequency_sweep, &psetup); + + gtk_widget_set_sensitive(GTK_WIDGET(stop_button), true); +} + +static void stop_sweep_clicked(GtkButton *btn, gpointer data) +{ + gtk_widget_set_sensitive(GTK_WIDGET(btn), false); + + if (spectrum_window) + osc_plot_draw_stop(OSC_PLOT(spectrum_window)); + if (freq_sweep_thread) { + kill_sweep_thread = true; + g_thread_join(freq_sweep_thread); + freq_sweep_thread = NULL; + } + if (capture_buffer) { + iio_buffer_destroy(capture_buffer); + capture_buffer = NULL; + } + + gtk_widget_set_sensitive(GTK_WIDGET(start_button), true); +} + +static void center_freq_changed(GtkSpinButton *btn, gpointer data) +{ + GtkSpinButton *bw_spin = GTK_SPIN_BUTTON(freq_bw); + GtkAdjustment *bw_adj = gtk_spin_button_get_adjustment(bw_spin); + double center = gtk_spin_button_get_value(btn); + double bw = gtk_spin_button_get_value(GTK_SPIN_BUTTON(freq_bw)); + double upper, upper1, upper2 = 0; + + upper1 = (center - 70) * 2; + upper2 = (6000 - center) * 2; + upper = (upper1 < upper2) ? upper1 : upper2; + gtk_adjustment_set_upper(bw_adj, upper); + if (bw > upper) + gtk_spin_button_set_value(bw_spin, upper); + + //~ if (center - bw / 2 < 100.72) { + //~ upper = (center - 100.72) * 2; +//~ + //~ } else if (center + bw / 2 > 5969.28) { + //~ upper = (5969.28 - center) * 2; + //~ } else { + //~ return; + //~ } +} + +static void spectrum_window_destroyed_cb(OscPlot *plot) +{ + stop_sweep_clicked(GTK_BUTTON(stop_button), NULL); + spectrum_window = NULL; +} + +static GtkWidget * analyzer_init(GtkWidget *notebook, const char *ini_fn) +{ + GtkBuilder *builder; + struct iio_channel *ch1; + + ctx = osc_create_context(); + if (!ctx) + return NULL; + + dev = iio_context_find_device(ctx, PHY_DEVICE); + if (!dev) + return NULL; + cap = iio_context_find_device(ctx, CAP_DEVICE); + if (!cap) + return NULL; + alt_ch0 = iio_device_find_channel(dev, "altvoltage0", true); + if (!alt_ch0) + return NULL; + + ch1 = iio_device_find_channel(dev, "voltage1", false); + is_2rx_2tx = ch1 && iio_channel_find_attr(ch1, "hardwaregain"); + + init_device_list(ctx); + + if (iio_channel_find_attr(alt_ch0, "fastlock_store")) + rx_fastlock_store_name = "fastlock_store"; + else + rx_fastlock_store_name = "RX_LO_fastlock_store"; + if (iio_channel_find_attr(alt_ch0, "fastlock_save")) + rx_fastlock_save_name = "fastlock_save"; + else + rx_fastlock_save_name = "RX_LO_fastlock_save"; + + builder = gtk_builder_new(); + nbook = GTK_NOTEBOOK(notebook); + + if (!gtk_builder_add_from_file(builder, "spectrum_analyzer.glade", NULL)) + gtk_builder_add_from_file(builder, + OSC_GLADE_FILE_PATH "spectrum_analyzer.glade", NULL); + + analyzer_panel = GTK_WIDGET(gtk_builder_get_object(builder, + "spectrum_analyzer_panel")); + center_freq = GTK_WIDGET(gtk_builder_get_object(builder, + "spin_center_freq")); + freq_bw = GTK_WIDGET(gtk_builder_get_object(builder, + "spin_freq_bw")); + available_RBWs = GTK_WIDGET(gtk_builder_get_object(builder, + "cmb_available_rbw")); + receiver1 = GTK_WIDGET(gtk_builder_get_object(builder, + "radiobutton_rx1")); + start_button = GTK_WIDGET(gtk_builder_get_object(builder, + "start_sweep_btn")); + stop_button = GTK_WIDGET(gtk_builder_get_object(builder, + "stop_sweep_btn")); + + /* Widgets initialization */ + comboboxtext_rbw_fill(GTK_COMBO_BOX_TEXT(available_RBWs), + HZ_TO_MHZ(device_get_rx_sampling_freq(cap))); + gtk_combo_box_set_active(GTK_COMBO_BOX(available_RBWs), 6); + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(receiver1), true); + if (!is_2rx_2tx) + gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(builder, + "frame_receiver_selection"))); + + gtk_widget_set_sensitive(GTK_WIDGET(stop_button), false); + + /* Connect signals */ + g_builder_connect_signal(builder, "start_sweep_btn", "clicked", + G_CALLBACK(start_sweep_clicked), NULL); + g_builder_connect_signal(builder, "stop_sweep_btn", "clicked", + G_CALLBACK(stop_sweep_clicked), NULL); + g_builder_connect_signal(builder, "spin_center_freq", "value-changed", + G_CALLBACK(center_freq_changed), NULL); + g_signal_connect_swapped(freq_bw, "value-changed", + G_CALLBACK(center_freq_changed), center_freq); + + return analyzer_panel; +} + +static int handle_external_request(const char *request) +{ + int ret = 0; + + if (!strcmp(request, "AD9361 Sampling Rate Changed")) { + if (available_RBWs) { + comboboxtext_rbw_fill(GTK_COMBO_BOX_TEXT(available_RBWs), + HZ_TO_MHZ(device_get_rx_sampling_freq(cap))); + gtk_combo_box_set_active(GTK_COMBO_BOX(available_RBWs), 6); + ret = 1; + } + } + + return ret; +} + +static void update_active_page(gint active_page, gboolean is_detached) +{ + this_page = active_page; + plugin_detached = is_detached; +} + +static void analyzer_get_preferred_size(int *width, int *height) +{ + if (width) + *width = 640; + if (height) + *height = 480; +} + +static void context_destroy(const char *ini_fn) +{ + if (capture_buffer) { + iio_buffer_destroy(capture_buffer); + capture_buffer = NULL; + } + g_source_remove_by_user_data(ctx); + + iio_context_destroy(ctx); +} + +struct osc_plugin plugin; + +static bool analyzer_identify(void) +{ + /* Use the OSC's IIO context just to detect the devices */ + struct iio_context *osc_ctx = get_context_from_osc(); + + return !!iio_context_find_device(osc_ctx, PHY_DEVICE); +} + +struct osc_plugin plugin = { + .name = THIS_DRIVER, + .identify = analyzer_identify, + .init = analyzer_init, + .handle_external_request = handle_external_request, + .update_active_page = update_active_page, + .get_preferred_size = analyzer_get_preferred_size, + .destroy = context_destroy, +}; diff --git a/spectrum_analyzer.glade b/spectrum_analyzer.glade new file mode 100644 index 000000000..cac80ef4a --- /dev/null +++ b/spectrum_analyzer.glade @@ -0,0 +1,275 @@ + + + + + + 70 + 6000 + 2400 + 1 + 50 + + + 3000 + 400 + 1 + 50 + + + radiobutton + True + True + False + True + True + + + True + False + + + True + False + + + True + False + 5 + + + True + False + 3 + 2 + 5 + 2 + + + True + False + 0 + Center Frequency (MHz): + + + GTK_FILL + GTK_FILL + + + + + True + False + 0 + Span (MHz): + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + True + + False + False + True + True + adj_center_freq + 2 + True + + + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + True + + False + False + True + True + adj_freq_bw + 2 + True + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + True + False + 0 + RBW(KHz): + + + 2 + 3 + + + + + True + False + + + 1 + 2 + 2 + 3 + + + + + False + True + 0 + + + + + True + False + + + Start Sweep + True + True + True + + + True + True + 0 + + + + + Stop Sweep + True + True + True + + + True + True + 1 + + + + + True + True + 1 + + + + + False + True + 12 + 0 + + + + + True + False + + + True + False + 0 + none + + + True + False + 12 + + + True + False + + + RX1 + True + True + False + True + True + radiobutton1 + + + True + True + 0 + + + + + RX2 + True + True + False + True + radiobutton1 + + + True + True + 1 + + + + + + + + + True + False + <b>Receiver</b> + True + + + + + False + True + 0 + + + + + False + True + 1 + + + + + False + True + 12 + 0 + + + +