MX 1 anno fa
parent
commit
f5b4f83164

+ 8 - 3
README.md

@@ -24,6 +24,7 @@ Also see [Derek Jamison's demonstration](https://www.youtube.com/watch?v=iC5fBGw
 
 * Measures frequency of waveform in hertz
 * Measures voltage: min, max, Vpp
+* FFT option provides simple spectrum analyser
 
 ![Signal Generator](photos/sig.jpg)
 
@@ -31,7 +32,12 @@ Also see [Derek Jamison's demonstration](https://www.youtube.com/watch?v=iC5fBGw
 
 ![Rigol](photos/rigol.jpg)
 
-![Flipper Zero running flipperscope](photos/volt.jpg)
+![Flipper Zero running flipperscope - showing voltage measurements](photos/volt.jpg)
+
+In the following photo attached a MAX9814 electret microphone module to the flipper zero and used the spectrum
+analyser functionality, with an FFT window size of 1024 and played a 3kHz sine wave tone from a computer.
+
+![Flipper Zero running flipperscope - showing spectrum analyser](photos/fft.jpg)
 
 ## Processing captures
 
@@ -62,8 +68,6 @@ plt.show()
 
 * Customisable input pin
 * Trigger type mode
-* FFT
-* ...
 
 ## Inspiration
 
@@ -71,3 +75,4 @@ plt.show()
 * STM32 DMA example
 * VREFBUF information - https://community.st.com/s/question/0D53W00001awIlMSAU/enable-and-use-vrefbuf-for-adc-measures
 * Relocating vector table - https://community.nxp.com/t5/i-MX-Processors/Relocate-vector-table-to-ITCM/m-p/1302304
+* Uses FFT algorithm from - https://www.algorithm-archive.org/contents/cooley_tukey/cooley_tukey.html

+ 2 - 2
application.fam

@@ -9,6 +9,6 @@ App(
     fap_category="GPIO",
     fap_icon="scope_10px.png",
     fap_icon_assets="icons",
-    fap_version="0.2",
-    fap_description="Oscilloscope application - apply signal to pin 16/PC0, with a voltage ranging from 0V to 2.5V and ground to pin 18/GND"
+    fap_version="0.4",
+    fap_description="Oscilloscope application - apply signal to pin 16/PC0, with a voltage ranging from 0V to 2.5V and ground to pin 18/GND",
 )

+ 15 - 0
docs/CHANGELOG.md

@@ -0,0 +1,15 @@
+## v0.4
+
+Add simple spectrum analyser and basic software scaling support
+
+## v0.3
+
+Fix compilation issue
+
+## v0.2
+
+Small bug fixes and initial support for saving captures
+
+## v0.1
+
+Initial release

+ 21 - 0
docs/README.md

@@ -0,0 +1,21 @@
+## flipperscope
+
+Oscilloscope application - apply signal to pin 16/PC0, with a voltage ranging from 0V to 2.5V and ground to pin 18/GND.
+
+## Guide
+
+* **Center** = Pause/Un-pause display
+
+* In the setup screen you specify a time period of the analogue to digital converter, so 1ms, means sampling at 1000Hz.
+
+* Setup screen enables you to choose to measure the frequency of a signal with the Time option, in Hertz.
+
+* Setup screen enables you to choose to measure the maximum, minimum and peak-to-peak voltage, with the Voltage option.
+
+* Setup screen also enables you to choose the capture mode, to save samples to the SD card (currently 128 samples).  You can
+parse this data using the Python script in the flipperscope repo.
+
+* Setup screen allows you to choose an FFT option, to display a simple spectrum analyser.  You can alter the size of
+the FFT window also.
+
+* Setup screen allows you to scale the size of a signal via software.

BIN
photos/fft.jpg


BIN
photos/freq.jpg


BIN
photos/rigol.jpg


BIN
photos/sig.jpg


BIN
photos/sine.png


BIN
photos/volt.jpg


+ 1 - 0
scenes/scope_scene_about.c

@@ -13,6 +13,7 @@ void scope_scene_about_on_enter(void* context) {
     FuriString* temp_str;
     temp_str = furi_string_alloc();
     furi_string_printf(temp_str, "\e#%s\n", "Information");
+    furi_string_cat_printf(temp_str, "Version: %s\n\n", FAP_VERSION);
     furi_string_cat_printf(
         temp_str,
         "Provide signal to pin 16/PC0, with a voltage ranging from 0V to 2.5V and ground to pin 18/GND.\n\n");

+ 211 - 61
scenes/scope_scene_run.c

@@ -1,3 +1,6 @@
+#include <complex.h>
+#include <math.h>
+
 #include <float.h>
 #include <furi.h>
 #include <furi_hal.h>
@@ -27,14 +30,15 @@
 #include "../scope_app_i.h"
 #include "flipperscope_icons.h"
 
-#define DIGITAL_SCALE_12BITS ((uint32_t)0xFFF)
-#define VAR_CONVERTED_DATA_INIT_VALUE (DIGITAL_SCALE_12BITS + 1)
+#define USE_TIMEOUT                          0
+#define DIGITAL_SCALE_12BITS                 ((uint32_t)0xFFF)
+#define VAR_CONVERTED_DATA_INIT_VALUE        (DIGITAL_SCALE_12BITS + 1)
 #define VAR_CONVERTED_DATA_INIT_VALUE_16BITS (0xFFFF + 1U)
 #define __ADC_CALC_DATA_VOLTAGE(__VREFANALOG_VOLTAGE__, __ADC_DATA__) \
     ((__ADC_DATA__) * (__VREFANALOG_VOLTAGE__) / DIGITAL_SCALE_12BITS)
-#define VDDA_APPLI ((uint32_t)2500)
-#define TIMER_FREQUENCY_RANGE_MIN (1UL)
-#define TIMER_PRESCALER_MAX_VALUE (0xFFFF - 1UL)
+#define VDDA_APPLI                        ((uint32_t)2500)
+#define TIMER_FREQUENCY_RANGE_MIN         (1UL)
+#define TIMER_PRESCALER_MAX_VALUE         (0xFFFF - 1UL)
 #define ADC_DELAY_CALIB_ENABLE_CPU_CYCLES (LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * 32)
 
 // ramVector found from - https://community.nxp.com/t5/i-MX-Processors/Relocate-vector-table-to-ITCM/m-p/1302304
@@ -64,28 +68,30 @@ const uint32_t MSIRangeTable[16UL] = {
     0UL}; /* 0UL values are incorrect cases */
 
 char* time; // Current time period text
+float scale; // Current scale
 double freq; // Current samplerate
 uint8_t pause = 0; // Whether we want to pause output or not
 enum measureenum type; // Type of measurement we are performing
 int toggle = 0; // Used for toggling output GPIO, only used in testing
+uint32_t adc_buffer; // ADC buffer size
+int16_t* index_crossings; // Indexes of zero crossings
+float* data; // Shift data across virtual zero line
+float* crossings;
+float complex* fft_data; // Real data, transformed to complex data via FFT
+float* fft_power; // Power data from FFT
 
 void Error_Handler() {
     while(1) {
     }
 }
 
-__IO uint16_t
-    aADCxConvertedData[ADC_CONVERTED_DATA_BUFFER_SIZE]; // Array that ADC data is copied to, via DMA
-__IO uint16_t aADCxConvertedData_Voltage_mVoltA
-    [ADC_CONVERTED_DATA_BUFFER_SIZE]; // Data is converted to range from 0 to 2500
-__IO uint16_t aADCxConvertedData_Voltage_mVoltB
-    [ADC_CONVERTED_DATA_BUFFER_SIZE]; // Data is converted to range from 0 to 2500
+uint16_t* aADCxConvertedData; // Array that ADC data is copied to, via DMA
+__IO uint16_t* aADCxConvertedData_Voltage_mVoltA; // Data is converted to range from 0 to 2500
+__IO uint16_t* aADCxConvertedData_Voltage_mVoltB; // Data is converted to range from 0 to 2500
 __IO uint8_t ubDmaTransferStatus = 2; // DMA transfer status
 
-__IO uint16_t* mvoltWrite =
-    &aADCxConvertedData_Voltage_mVoltA[0]; // Pointer to area we write converted voltage data to
-__IO uint16_t* mvoltDisplay =
-    &aADCxConvertedData_Voltage_mVoltB[0]; // Pointer to area of memory we display
+__IO uint16_t* mvoltWrite; // Pointer to area we write converted voltage data to
+__IO uint16_t* mvoltDisplay; // Pointer to area of memory we display
 
 void AdcDmaTransferComplete_Callback();
 void AdcDmaTransferHalf_Callback();
@@ -152,10 +158,10 @@ static void MX_ADC1_Init(void) {
         DMA1,
         LL_DMA_CHANNEL_1,
         LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA),
-        (uint32_t)&aADCxConvertedData,
+        (uint32_t)aADCxConvertedData,
         LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
 
-    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ADC_CONVERTED_DATA_BUFFER_SIZE);
+    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, adc_buffer);
 
     LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
     LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1);
@@ -273,9 +279,7 @@ void swap(__IO uint16_t** a, __IO uint16_t** b) {
 
 void AdcDmaTransferComplete_Callback() {
     uint32_t tmp_index = 0;
-    for(tmp_index = (ADC_CONVERTED_DATA_BUFFER_SIZE / 2);
-        tmp_index < ADC_CONVERTED_DATA_BUFFER_SIZE;
-        tmp_index++) {
+    for(tmp_index = (adc_buffer / 2); tmp_index < adc_buffer; tmp_index++) {
         mvoltWrite[tmp_index] = __LL_ADC_CALC_DATA_TO_VOLTAGE(
             VDDA_APPLI, aADCxConvertedData[tmp_index], LL_ADC_RESOLUTION_12B);
     }
@@ -285,7 +289,7 @@ void AdcDmaTransferComplete_Callback() {
 
 void AdcDmaTransferHalf_Callback() {
     uint32_t tmp_index = 0;
-    for(tmp_index = 0; tmp_index < (ADC_CONVERTED_DATA_BUFFER_SIZE / 2); tmp_index++) {
+    for(tmp_index = 0; tmp_index < (adc_buffer / 2); tmp_index++) {
         mvoltWrite[tmp_index] = __LL_ADC_CALC_DATA_TO_VOLTAGE(
             VDDA_APPLI, aADCxConvertedData[tmp_index], LL_ADC_RESOLUTION_12B);
     }
@@ -294,7 +298,7 @@ void AdcDmaTransferHalf_Callback() {
 
 void Activate_ADC(void) {
     __IO uint32_t wait_loop_index = 0U;
-#if defined(USE_TIMEOUT) && (USE_TIMEOUT == 1)
+#if(USE_TIMEOUT == 1)
     uint32_t Timeout = 0U; /* Variable used for timeout management */
 #endif /* USE_TIMEOUT */
     if(LL_ADC_IsEnabled(ADC1) == 0) {
@@ -307,12 +311,12 @@ void Activate_ADC(void) {
         }
         LL_ADC_StartCalibration(ADC1, LL_ADC_SINGLE_ENDED);
 
-#if defined(USE_TIMEOUT) && (USE_TIMEOUT == 1)
+#if(USE_TIMEOUT == 1)
         Timeout = ADC_CALIBRATION_TIMEOUT_MS;
 #endif /* USE_TIMEOUT */
 
         while(LL_ADC_IsCalibrationOnGoing(ADC1) != 0) {
-#if defined(USE_TIMEOUT) && (USE_TIMEOUT == 1)
+#if(USE_TIMEOUT == 1)
             if(LL_SYSTICK_IsActiveCounterFlag()) {
                 if(Timeout-- == 0) {
                 }
@@ -324,11 +328,11 @@ void Activate_ADC(void) {
             wait_loop_index--;
         }
         LL_ADC_Enable(ADC1);
-#if defined(USE_TIMEOUT) && (USE_TIMEOUT == 1)
+#if(USE_TIMEOUT == 1)
         Timeout = ADC_ENABLE_TIMEOUT_MS;
 #endif /* USE_TIMEOUT */
         while(LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0) {
-#if defined(USE_TIMEOUT) && (USE_TIMEOUT == 1)
+#if(USE_TIMEOUT == 1)
             /* Check Systick counter flag to decrement the time-out value */
             if(LL_SYSTICK_IsActiveCounterFlag()) {
                 if(Timeout-- == 0) {
@@ -341,14 +345,63 @@ void Activate_ADC(void) {
     }
 }
 
+// Found from:
+// https://www.algorithm-archive.org/contents/cooley_tukey/cooley_tukey.html
+void bit_reverse(float complex* X, int N) {
+    for(int i = 0; i < N; ++i) {
+        int n = i;
+        int a = i;
+        int count = (int)ceil(log2((float)N)) - 1;
+
+        n >>= 1;
+        while(n > 0) {
+            a = (a << 1) | (n & 1);
+            count--;
+            n >>= 1;
+        }
+        n = (a << count) & (int)((1 << (int)ceil(log2((float)N))) - 1);
+
+        if(n > i) {
+            float complex tmp = X[i];
+            X[i] = X[n];
+            X[n] = tmp;
+        }
+    }
+}
+
+// Found from:
+// https://www.algorithm-archive.org/contents/cooley_tukey/cooley_tukey.html
+//
+// Adapted slightly to use ceil, otherwise didn't seem to calculate
+// FFT correctly on the flipper zero
+void iterative_cooley_tukey(float complex* X, int N) {
+    bit_reverse(X, N);
+
+    for(int i = 1; i <= ceil(log2((float)N)); ++i) {
+        int stride = (int)pow(2, i);
+        float complex w = cexp(-2.0 * I * M_PI / (float)stride);
+        for(int j = 0; j < N; j += stride) {
+            float complex v = 1.0;
+            for(int k = 0; k < stride / 2; ++k) {
+                X[k + j + stride / 2] = X[k + j] - v * X[k + j + stride / 2];
+                X[k + j] -= (X[k + j + stride / 2] - X[k + j]);
+                v *= w;
+            }
+        }
+    }
+}
+
+// Found from:
+// https://stackoverflow.com/questions/427477/fastest-way-to-clamp-a-real-fixed-floating-point-value
+double clamp(double d, double min, double max) {
+    const double t = d < min ? min : d;
+    return t > max ? max : t;
+}
+
 // Used to draw to display
 static void app_draw_callback(Canvas* canvas, void* ctx) {
     UNUSED(ctx);
-    static int16_t index[ADC_CONVERTED_DATA_BUFFER_SIZE];
-    static float data[ADC_CONVERTED_DATA_BUFFER_SIZE];
-    static float crossings[ADC_CONVERTED_DATA_BUFFER_SIZE];
     static char buf1[50];
-
     float max = 0.0;
     float min = FLT_MAX;
     int count = 0;
@@ -363,12 +416,12 @@ static void app_draw_callback(Canvas* canvas, void* ctx) {
     }
 
     if(pause)
-        canvas_draw_icon(canvas, 115, 0, &I_pause_10x10);
+        canvas_draw_icon(canvas, 116, 1, &I_pause_10x10);
     else
-        canvas_draw_icon(canvas, 115, 0, &I_play_10x10);
+        canvas_draw_icon(canvas, 116, 1, &I_play_10x10);
 
     // Calculate voltage measurements
-    for(uint32_t x = 0; x < ADC_CONVERTED_DATA_BUFFER_SIZE; x++) {
+    for(uint32_t x = 0; x < adc_buffer; x++) {
         if(mvoltDisplay[x] < min) min = mvoltDisplay[x];
         if(mvoltDisplay[x] > max) max = mvoltDisplay[x];
     }
@@ -377,34 +430,38 @@ static void app_draw_callback(Canvas* canvas, void* ctx) {
 
     switch(type) {
     case m_time: {
+        // Display current scale
+        snprintf(buf1, 50, "%.0fx", (double)scale);
+        canvas_draw_str(canvas, 95, 10, buf1);
         // Display current time period
         snprintf(buf1, 50, "Time: %s", time);
-        canvas_draw_str(canvas, 10, 10, buf1);
+        canvas_draw_str(canvas, 2, 10, buf1);
         // Shift waveform across a virtual 0 line, so it crosses 0
-        for(uint32_t x = 0; x < ADC_CONVERTED_DATA_BUFFER_SIZE; x++) {
-            index[x] = -1;
+        for(uint32_t x = 0; x < adc_buffer; x++) {
+            index_crossings[x] = -1;
             crossings[x] = -1.0;
             data[x] = ((float)mvoltDisplay[x] / 1000) - min;
             data[x] = ((2 / (max - min)) * data[x]) - 1;
         }
         // Find points at which waveform crosses virtual 0 line
-        for(uint32_t x = 1; x < ADC_CONVERTED_DATA_BUFFER_SIZE; x++) {
+        for(uint32_t x = 1; x < adc_buffer; x++) {
             if(data[x] >= 0 && data[x - 1] < 0) {
-                index[count++] = x - 1;
+                index_crossings[count++] = x - 1;
             }
         }
         count = 0;
         // Linear interpolation to find zero crossings
         // see https://gist.github.com/endolith/255291 for Python version
-        for(uint32_t x = 0; x < ADC_CONVERTED_DATA_BUFFER_SIZE; x++) {
-            if(index[x] == -1) break;
-            crossings[count++] =
-                (float)index[x] - data[index[x]] / (data[index[x] + 1] - data[index[x]]);
+        for(uint32_t x = 0; x < adc_buffer; x++) {
+            if(index_crossings[x] == -1) break;
+            crossings[count++] = (float)index_crossings[x] -
+                                 data[index_crossings[x]] /
+                                     (data[index_crossings[x] + 1] - data[index_crossings[x]]);
         }
         float avg = 0.0;
         float countv = 0.0;
-        for(uint32_t x = 0; x < ADC_CONVERTED_DATA_BUFFER_SIZE; x++) {
-            if(x + 1 >= ADC_CONVERTED_DATA_BUFFER_SIZE) break;
+        for(uint32_t x = 0; x < adc_buffer; x++) {
+            if(x + 1 >= adc_buffer) break;
             if(crossings[x] == -1 || crossings[x + 1] == -1) break;
             avg += crossings[x + 1] - crossings[x];
             countv += 1;
@@ -412,31 +469,91 @@ static void app_draw_callback(Canvas* canvas, void* ctx) {
         avg /= countv;
         // Display frequency of waveform
         snprintf(buf1, 50, "Freq: %.1f Hz", (double)((float)freq / avg));
-        canvas_draw_str(canvas, 10, 20, buf1);
+        canvas_draw_str(canvas, 2, 20, buf1);
+    } break;
+    case m_fft: {
+        for(uint32_t i = 0; i < adc_buffer; i++) {
+            fft_data[i] = ((float)mvoltDisplay[i] / 1000);
+        }
+
+        // Apply FFT
+        iterative_cooley_tukey(fft_data, adc_buffer);
+
+        // Find FFT bin, with highest power
+        float max_val = -1;
+        int idx = 0;
+        for(uint32_t i = 1; i < adc_buffer / 2; i++) {
+            float f = cabsf(fft_data[i]) * cabsf(fft_data[i]);
+            if(f > max_val) {
+                max_val = f;
+                idx = i;
+            }
+            fft_power[i] = f;
+        }
+
+        // Display frequency of waveform
+        snprintf(buf1, 50, "Freq: %.1fHz", (double)idx * ((double)freq / (double)adc_buffer));
+        canvas_draw_str(canvas, 2, 10, buf1);
     } break;
     case m_voltage: {
+        // Display current scale
+        snprintf(buf1, 50, "%.0fx", (double)scale);
+        canvas_draw_str(canvas, 95, 10, buf1);
         // Display max, min, peak-to-peak voltages
         snprintf(buf1, 50, "Max: %.2fV", (double)max);
-        canvas_draw_str(canvas, 10, 10, buf1);
+        canvas_draw_str(canvas, 2, 10, buf1);
         snprintf(buf1, 50, "Min: %.2fV", (double)min);
-        canvas_draw_str(canvas, 10, 20, buf1);
+        canvas_draw_str(canvas, 2, 20, buf1);
         snprintf(buf1, 50, "Vpp: %.2fV", (double)(max - min));
-        canvas_draw_str(canvas, 10, 30, buf1);
+        canvas_draw_str(canvas, 2, 30, buf1);
     } break;
     default:
         break;
     }
 
-    // Draw lines between each data point
-    for(uint32_t x = 1; x < ADC_CONVERTED_DATA_BUFFER_SIZE; x++) {
-        uint32_t prev = 64 - (mvoltDisplay[x - 1] / (VDDA_APPLI / 64));
-        uint32_t cur = 64 - (mvoltDisplay[x] / (VDDA_APPLI / 64));
-        canvas_draw_line(canvas, x - 1, prev, x, cur);
+    if(type != m_fft) {
+        // Draw lines between each data point
+        // y should range from 0 to 63
+        for(uint32_t x = 1; x < adc_buffer; x++) {
+            int32_t prev =
+                63 - (uint32_t)(((float)mvoltDisplay[x - 1] / (float)VDDA_APPLI) * scale * 63.0f);
+            int32_t cur =
+                63 - (uint32_t)(((float)mvoltDisplay[x] / (float)VDDA_APPLI) * scale * 63.0f);
+            if(!(prev < 0 && cur < 0))
+                canvas_draw_line(canvas, x - 1, clamp(prev, 0, 63), x, clamp(cur, 0, 63));
+        }
+    } else {
+        // Process FFT data - excluding bin 0
+        float max = 0;
+        for(uint32_t i = 1; i < adc_buffer / 2; i += adc_buffer / 2 / 128) {
+            float sum = 0;
+            for(uint32_t i2 = i; i2 < i + (adc_buffer / 2 / 128); i2++) {
+                sum += fft_power[i2];
+            }
+            if(sum > max) {
+                max = sum;
+            }
+        }
+
+        uint32_t xpos = 0;
+        // xpos: 0 to 126 for window size 256
+        // xpos: 0 to 127 for window size 512
+        // xpos: 0 to 127 for window size 1024
+        // y should range from 0 to 63
+        for(uint32_t i = 1; i < adc_buffer / 2; i += adc_buffer / 2 / 128) {
+            float sum = 0;
+            for(uint32_t i2 = i; i2 < i + (adc_buffer / 2 / 128); i2++) {
+                sum += fft_power[i2];
+            }
+            canvas_draw_line(canvas, xpos, 63, xpos, 63 - (uint32_t)(((sum / max) * 63.0f)));
+            xpos++;
+        }
     }
 
+    // Removing graph lines, to use extra pixel
     // Draw graph lines
-    canvas_draw_line(canvas, 0, 0, 0, 63);
-    canvas_draw_line(canvas, 0, 63, 128, 63);
+    //canvas_draw_line(canvas, 0, 0, 0, 63);
+    //canvas_draw_line(canvas, 0, 63, 127, 63);
 }
 
 static void app_input_callback(InputEvent* input_event, void* ctx) {
@@ -445,6 +562,18 @@ static void app_input_callback(InputEvent* input_event, void* ctx) {
     furi_message_queue_put(event_queue, input_event, FuriWaitForever);
 }
 
+// Free malloc'd data
+void free_all() {
+    free(aADCxConvertedData);
+    free((void*)aADCxConvertedData_Voltage_mVoltA);
+    free((void*)aADCxConvertedData_Voltage_mVoltB);
+    free(index_crossings);
+    free(data);
+    free(crossings);
+    free(fft_data);
+    free(fft_power);
+}
+
 void scope_scene_run_on_enter(void* context) {
     ScopeApp* app = context;
 
@@ -456,12 +585,32 @@ void scope_scene_run_on_enter(void* context) {
         }
     }
 
+    // Obtain scale value
+    scale = app->scale;
+
     // Currently un-paused
     pause = 0;
 
     // What type of measurement are we performing
     type = app->measurement;
 
+    adc_buffer = ADC_CONVERTED_DATA_BUFFER_SIZE;
+    if(type == m_fft) adc_buffer = app->fft;
+
+    aADCxConvertedData = malloc(adc_buffer * sizeof(uint16_t));
+    aADCxConvertedData_Voltage_mVoltA = malloc(adc_buffer * sizeof(uint16_t));
+    aADCxConvertedData_Voltage_mVoltB = malloc(adc_buffer * sizeof(uint16_t));
+
+    index_crossings = malloc(adc_buffer * sizeof(int16_t));
+    data = malloc(adc_buffer * sizeof(float));
+    crossings = malloc(adc_buffer * sizeof(float));
+    fft_data = malloc(adc_buffer * sizeof(float complex));
+    fft_power = malloc(adc_buffer * sizeof(float));
+
+    mvoltWrite =
+        &aADCxConvertedData_Voltage_mVoltA[0]; // Pointer to area we write converted voltage data to
+    mvoltDisplay = &aADCxConvertedData_Voltage_mVoltB[0]; // Pointer to area of memory we display
+
     // Copy vector table, modify to use our own IRQ handlers
     __disable_irq();
     memcpy(ramVector, (uint32_t*)(FLASH_BASE | SCB->VTOR), sizeof(uint32_t) * TABLE_SIZE);
@@ -492,8 +641,7 @@ void scope_scene_run_on_enter(void* context) {
     MX_ADC1_Init();
 
     // Setup initial values from ADC
-    for(tmp_index_adc_converted_data = 0;
-        tmp_index_adc_converted_data < ADC_CONVERTED_DATA_BUFFER_SIZE;
+    for(tmp_index_adc_converted_data = 0; tmp_index_adc_converted_data < adc_buffer;
         tmp_index_adc_converted_data++) {
         aADCxConvertedData[tmp_index_adc_converted_data] = VAR_CONVERTED_DATA_INIT_VALUE;
         aADCxConvertedData_Voltage_mVoltA[tmp_index_adc_converted_data] = 0;
@@ -519,7 +667,7 @@ void scope_scene_run_on_enter(void* context) {
     bool running = true;
     bool save = false;
     while(running) {
-        if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
+        if(furi_message_queue_get(event_queue, &event, 150) == FuriStatusOk) {
             if((event.type == InputTypePress) || (event.type == InputTypeRepeat)) {
                 switch(event.key) {
                 case InputKeyLeft:
@@ -565,6 +713,8 @@ void scope_scene_run_on_enter(void* context) {
         gui_remove_view_port(gui, view_port);
         view_port_free(view_port);
 
+        free_all();
+
         // Switch back to original scene
         furi_record_close(RECORD_GUI);
         scene_manager_previous_scene(app->scene_manager);
@@ -574,9 +724,9 @@ void scope_scene_run_on_enter(void* context) {
         gui_remove_view_port(gui, view_port);
         view_port_free(view_port);
 
-        app->data = malloc(sizeof(uint16_t) * ADC_CONVERTED_DATA_BUFFER_SIZE);
-        memcpy(
-            app->data, (uint16_t*)mvoltDisplay, sizeof(uint16_t) * ADC_CONVERTED_DATA_BUFFER_SIZE);
+        app->data = malloc(sizeof(uint16_t) * adc_buffer);
+        memcpy(app->data, (uint16_t*)mvoltDisplay, sizeof(uint16_t) * adc_buffer);
+        free_all();
         scene_manager_next_scene(app->scene_manager, ScopeSceneSave);
     }
 }

+ 36 - 0
scenes/scope_scene_setup.c

@@ -15,6 +15,22 @@ static void timeperiod_cb(VariableItem* item) {
     app->time = time_list[index].time;
 }
 
+static void scale_cb(VariableItem* item) {
+    ScopeApp* app = variable_item_get_context(item);
+    furi_assert(app);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, scale_list[index].str);
+    app->scale = scale_list[index].scale;
+}
+
+static void fft_cb(VariableItem* item) {
+    ScopeApp* app = variable_item_get_context(item);
+    furi_assert(app);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, fft_list[index].str);
+    app->fft = fft_list[index].window;
+}
+
 static void measurement_cb(VariableItem* item) {
     ScopeApp* app = variable_item_get_context(item);
     furi_assert(app);
@@ -38,6 +54,26 @@ void scope_scene_setup_on_enter(void* context) {
         }
     }
 
+    item = variable_item_list_add(var_item_list, "FFT window", COUNT_OF(fft_list), fft_cb, app);
+
+    for(uint32_t i = 0; i < COUNT_OF(fft_list); i++) {
+        if(fft_list[i].window == app->fft) {
+            variable_item_set_current_value_index(item, i);
+            variable_item_set_current_value_text(item, fft_list[i].str);
+            break;
+        }
+    }
+
+    item = variable_item_list_add(var_item_list, "Scale", COUNT_OF(scale_list), scale_cb, app);
+
+    for(uint32_t i = 0; i < COUNT_OF(scale_list); i++) {
+        if(scale_list[i].scale == app->scale) {
+            variable_item_set_current_value_index(item, i);
+            variable_item_set_current_value_text(item, scale_list[i].str);
+            break;
+        }
+    }
+
     item = variable_item_list_add(
         var_item_list, "Measurement", COUNT_OF(measurement_list), measurement_cb, app);
 

+ 1 - 1
scenes/scope_types.h

@@ -4,7 +4,7 @@
 #include <furi_hal.h>
 
 #define S_DEVELOPED "anfractuosity"
-#define S_GITHUB "https://github.com/anfractuosity/flipperscope"
+#define S_GITHUB    "https://github.com/anfractuosity/flipperscope"
 
 typedef enum {
     ScopeViewVariableItemList,

+ 2 - 1
scope.c

@@ -44,7 +44,6 @@ ScopeApp* scope_app_alloc() {
     // View Dispatcher
     app->view_dispatcher = view_dispatcher_alloc();
     app->scene_manager = scene_manager_alloc(&scope_scene_handlers, app);
-    
 
     view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
     view_dispatcher_set_custom_event_callback(
@@ -80,6 +79,8 @@ ScopeApp* scope_app_alloc() {
         app->view_dispatcher, ScopeViewSave, text_input_get_view(app->text_input));
 
     app->time = 0.001;
+    app->scale = 1.0f;
+    app->fft = 256;
     app->measurement = m_time;
 
     scene_manager_next_scene(app->scene_manager, ScopeSceneStart);

+ 27 - 7
scope_app_i.h

@@ -13,8 +13,8 @@
 #include <notification/notification_messages.h>
 
 #define ADC_CONVERTED_DATA_BUFFER_SIZE ((uint32_t)128)
-#define FLIPPERSCOPE_APP_EXTENSION ".dat"
-#define MAX_LEN_NAME 30
+#define FLIPPERSCOPE_APP_EXTENSION     ".dat"
+#define MAX_LEN_NAME                   30
 
 typedef struct ScopeApp ScopeApp;
 
@@ -26,17 +26,35 @@ typedef struct {
 static const timeperiod time_list[] =
     {{1.0, "1s"}, {0.1, "0.1s"}, {1e-3, "1ms"}, {0.1e-3, "0.1ms"}, {1e-6, "1us"}};
 
-enum measureenum { m_time, m_voltage, m_capture };
+typedef struct {
+    int window;
+    char* str;
+} fftwindow;
+
+static const fftwindow fft_list[] = {{256, "256"}, {512, "512"}, {1024, "1024"}};
+
+typedef struct {
+    float scale;
+    char* str;
+} scalesize;
+
+static const scalesize scale_list[] =
+    {{1.0f, "1x"}, {2.0f, "2x"}, {4.0f, "4x"}, {10.0f, "10x"}, {100.0f, "100x"}};
+
+enum measureenum {
+    m_time,
+    m_voltage,
+    m_capture,
+    m_fft
+};
 
 typedef struct {
     enum measureenum type;
     char* str;
 } measurement;
 
-static const measurement measurement_list[] = {
-    {m_time, "Time"},
-    {m_voltage, "Voltage"},
-    {m_capture, "Capture"}};
+static const measurement measurement_list[] =
+    {{m_time, "Time"}, {m_voltage, "Voltage"}, {m_capture, "Capture"}, {m_fft, "FFT"}};
 
 struct ScopeApp {
     Gui* gui;
@@ -48,6 +66,8 @@ struct ScopeApp {
     Widget* widget;
     TextInput* text_input;
     double time;
+    int fft;
+    float scale;
     enum measureenum measurement;
     char file_name_tmp[MAX_LEN_NAME];
     uint16_t* data;

BIN
screenshots/capture.png


BIN
screenshots/fft.png


BIN
screenshots/freq.png


BIN
screenshots/setup.png


BIN
screenshots/volt.png