Просмотр исходного кода

Merge oscilloscope from https://github.com/anfractuosity/flipperscope

# Conflicts:
#	oscilloscope/application.fam
#	oscilloscope/scope_app_i.h
Willy-JL 1 год назад
Родитель
Сommit
a56b020135

+ 8 - 3
oscilloscope/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

+ 1 - 1
oscilloscope/application.fam

@@ -9,6 +9,6 @@ App(
     fap_category="GPIO",
     fap_icon="scope_10px.png",
     fap_icon_assets="icons",
-    fap_version="0.3",
+    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",
 )

+ 4 - 0
oscilloscope/docs/CHANGELOG.md

@@ -1,3 +1,7 @@
+## v0.4
+
+Add simple spectrum analyser and basic software scaling support
+
 ## v0.3
 
 Fix compilation issue

+ 5 - 0
oscilloscope/docs/README.md

@@ -14,3 +14,8 @@ Oscilloscope application - apply signal to pin 16/PC0, with a voltage ranging fr
 
 * 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
oscilloscope/photos/fft.jpg


+ 1 - 0
oscilloscope/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");

+ 197 - 47
oscilloscope/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>
@@ -65,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();
@@ -153,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);
@@ -274,8 +279,8 @@ 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;
+    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);
@@ -286,7 +291,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);
     }
@@ -342,14 +347,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;
@@ -364,12 +418,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];
     }
@@ -378,34 +432,37 @@ 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;
+        for(uint32_t x = 0; x < adc_buffer; x++) {
+            if(index_crossings[x] == -1) break;
             crossings[count++] =
-                (float)index[x] - data[index[x]] / (data[index[x] + 1] - data[index[x]]);
+                (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;
@@ -413,31 +470,89 @@ 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) {
@@ -446,6 +561,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;
 
@@ -457,12 +584,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);
@@ -494,7 +641,7 @@ void scope_scene_run_on_enter(void* context) {
 
     // Setup initial values from ADC
     for(tmp_index_adc_converted_data = 0;
-        tmp_index_adc_converted_data < ADC_CONVERTED_DATA_BUFFER_SIZE;
+        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;
@@ -520,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:
@@ -566,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);
@@ -575,9 +724,10 @@ 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);
+        app->data = malloc(sizeof(uint16_t) * adc_buffer);
         memcpy(
-            app->data, (uint16_t*)mvoltDisplay, sizeof(uint16_t) * ADC_CONVERTED_DATA_BUFFER_SIZE);
+            app->data, (uint16_t*)mvoltDisplay, sizeof(uint16_t) * adc_buffer);
+        free_all();
         scene_manager_next_scene(app->scene_manager, ScopeSceneSave);
     }
 }

+ 38 - 0
oscilloscope/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,28 @@ 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);
 

+ 2 - 0
oscilloscope/scope.c

@@ -79,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);

+ 21 - 5
oscilloscope/scope_app_i.h

@@ -26,10 +26,26 @@ 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"}};
 
+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_capture,
+    m_fft
 };
 
 typedef struct {
@@ -37,10 +53,8 @@ typedef struct {
     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;
@@ -52,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
oscilloscope/screenshots/capture.png


BIN
oscilloscope/screenshots/fft.png


BIN
oscilloscope/screenshots/freq.png


BIN
oscilloscope/screenshots/setup.png


BIN
oscilloscope/screenshots/volt.png