diff --git a/fft.h b/fft.h new file mode 100644 index 0000000..bf5cb11 --- /dev/null +++ b/fft.h @@ -0,0 +1,78 @@ +/* + * fft.h is Based on + * Free FFT and convolution (C) + * + * Copyright (c) 2019 Project Nayuki. (MIT License) + * https://www.nayuki.io/page/free-small-fft-in-multiple-languages + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + + +#include +#include + +static uint8_t reverse_bits(uint8_t x, int n) { + uint8_t result = 0; + for (int i = 0; i < n; i++, x >>= 1) + result = (result << 1) | (x & 1U); + return result; +} + +/*** + * dir = forward: 0, inverse: 1 + */ +void fft(float array[][2], uint8_t n, uint8_t dir) { + int levels = 0; // Compute levels = floor(log2(n)) + for (uint8_t temp = n; temp > 1U; temp >>= 1) + levels++; + + uint8_t real = dir & 1; + uint8_t imag = ~real & 1; + + for (uint8_t i = 0; i < n; i++) { + uint8_t j = reverse_bits(i, levels); + if (j > i) { + float temp = array[i][real]; + array[i][real] = array[j][real]; + array[j][real] = temp; + temp = array[i][imag]; + array[i][imag] = array[j][imag]; + array[j][imag] = temp; + } + } + + // Cooley-Tukey decimation-in-time radix-2 FFT + for (uint8_t size = 2; size <= n; size *= 2) { + uint8_t halfsize = size / 2; + uint8_t tablestep = n / size; + for (uint8_t i = 0; i < n; i += size) { + for (uint8_t j = i, k = 0; j < i + halfsize; j++, k += tablestep) { + uint8_t l = j + halfsize; + float tpre = array[l][real] * cos(2 * M_PI * k / n) + array[l][imag] * sin(2 * M_PI * k / n); + float tpim = -array[l][real] * sin(2 * M_PI * k / n) + array[l][imag] * cos(2 * M_PI * k / n); + array[l][real] = array[j][real] - tpre; + array[l][imag] = array[j][imag] - tpim; + array[j][real] += tpre; + array[j][imag] += tpim; + } + } + if (size == n) // Prevent overflow in 'size *= 2' + break; + } +} + diff --git a/main.c b/main.c index 4bafd45..23feb52 100644 --- a/main.c +++ b/main.c @@ -23,6 +23,7 @@ #include "usbcfg.h" #include "si5351.h" #include "nanovna.h" +#include "fft.h" #include #include @@ -36,6 +37,7 @@ static void apply_error_term(void); static void apply_error_term_at(int i); static void cal_interpolate(int s); +static void transform_domain(void); void sweep(void); @@ -54,6 +56,8 @@ int8_t redraw_requested = FALSE; int8_t stop_the_world = FALSE; int16_t vbat = 0; +uint8_t domain = DOMAIN_TIME; +uint8_t tdrfunc = TDR_IMPULSE; static THD_WORKING_AREA(waThread1, 640); static THD_FUNCTION(Thread1, arg) { @@ -82,6 +86,7 @@ static THD_FUNCTION(Thread1, arg) draw_battery_status(); } + transform_domain(); /* calculate trace coordinates */ plot_into_index(measured); /* plot trace as raster */ @@ -107,6 +112,35 @@ toggle_sweep(void) sweep_enabled = !sweep_enabled; } +static +void +transform_domain(void) +{ + if (domain != DOMAIN_TIME) return; // nothing to do for freq domain + // use spi_buffer as temporary buffer + // and calculate ifft for time domain + float* tmp = (float*)spi_buffer; + for (int ch = 0; ch < 2; ch++) { + for (int i = 0; i < 128; i++) { + tmp[i*2+0] = 0.0; + tmp[i*2+1] = 0.0; + } + memcpy(spi_buffer, measured[ch], sizeof(measured[0])); + fft((float(*)[2])tmp, 128, 1); + memcpy(measured[ch], spi_buffer, sizeof(measured[0])); + for (int i = 0; i < 101; i++) { + measured[ch][i][0] /= 128.0; + measured[ch][i][1] /= 128.0; + } + if (tdrfunc == TDR_STEP) { + for (int i = 1; i < 101; i++) { + measured[ch][i][0] += measured[ch][i-1][0]; + measured[ch][i][1] += measured[ch][i-1][1]; + } + } + } +} + static void cmd_pause(BaseSequentialStream *chp, int argc, char *argv[]) { (void)chp; diff --git a/nanovna.h b/nanovna.h index 655e78e..a33cf5e 100644 --- a/nanovna.h +++ b/nanovna.h @@ -22,8 +22,20 @@ /* * main.c */ + extern float measured[2][101][2]; +enum { + DOMAIN_FREQ, DOMAIN_TIME +}; + +enum { + TDR_IMPULSE, TDR_STEP +}; + +extern uint8_t domain; +extern uint8_t tdrfunc; + #define CAL_LOAD 0 #define CAL_OPEN 1 #define CAL_SHORT 2 diff --git a/plot.c b/plot.c index 2367372..91ef03a 100644 --- a/plot.c +++ b/plot.c @@ -1367,8 +1367,15 @@ cell_draw_marker_info(int m, int n, int w, int h) chsnprintf(buf, sizeof buf, "%d:", active_marker + 1); cell_drawstring_5x7(w, h, buf, xpos, ypos, 0xffff); xpos += 16; - frequency_string(buf, sizeof buf, frequencies[idx]); - cell_drawstring_5x7(w, h, buf, xpos, ypos, 0xffff); + if (domain == DOMAIN_FREQ) { + frequency_string(buf, sizeof buf, frequencies[idx]); + cell_drawstring_5x7(w, h, buf, xpos, ypos, 0xffff); + } else { +#define SPEED_OF_LIGHT 299792458 + float distance = ((float)idx * (float)SPEED_OF_LIGHT) / ( (float)(frequencies[1] - frequencies[0]) * 128.0 * 2.0); + chsnprintf(buf, sizeof buf, "%f m", distance); + cell_drawstring_5x7(w, h, buf, xpos, ypos, 0xffff); + } // draw marker delta if (previous_marker >= 0 && active_marker != previous_marker && markers[previous_marker].enabled) { diff --git a/ui.c b/ui.c index 0012db5..59f2255 100644 --- a/ui.c +++ b/ui.c @@ -664,6 +664,24 @@ menu_channel_cb(int item) ui_mode_normal(); } +static void +menu_tdr_cb(int item) +{ + switch (item) { + case 0: + domain = (domain == DOMAIN_FREQ) ? DOMAIN_TIME : DOMAIN_FREQ; + break; + case 1: + tdrfunc = TDR_IMPULSE; + break; + case 2: + tdrfunc = TDR_STEP; + break; + } + + ui_mode_normal(); +} + static void choose_active_marker(void) { @@ -874,11 +892,20 @@ const menuitem_t menu_channel[] = { { MT_NONE, NULL, NULL } // sentinel }; +const menuitem_t menu_tdr[] = { + { MT_CALLBACK, "TDR MODE", menu_tdr_cb }, + { MT_CALLBACK, "IMPULSE", menu_tdr_cb }, + { MT_CALLBACK, "STEP", menu_tdr_cb }, + { MT_CANCEL, S_LARROW" BACK", NULL }, + { MT_NONE, NULL, NULL } // sentinel +}; + const menuitem_t menu_display[] = { { MT_SUBMENU, "TRACE", menu_trace }, { MT_SUBMENU, "FORMAT", menu_format }, { MT_SUBMENU, "SCALE", menu_scale }, { MT_SUBMENU, "CHANNEL", menu_channel }, + { MT_SUBMENU, "TDR", menu_tdr }, { MT_CANCEL, S_LARROW" BACK", NULL }, { MT_NONE, NULL, NULL } // sentinel }; @@ -1235,6 +1262,14 @@ menu_item_modify_attribute(const menuitem_t *menu, int item, *bg = 0x0000; *fg = 0xffff; } + } else if (menu == menu_tdr) { + if ((item == 0 && domain == DOMAIN_TIME) + || (item == 1 && tdrfunc == TDR_IMPULSE) + || (item == 2 && tdrfunc == TDR_STEP) + ) { + *bg = 0x0000; + *fg = 0xffff; + } } }