diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cd105d..5f4153f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.5.0) file(GLOB_RECURSE USER_SOURCES "User/*.c") file(GLOB_RECURSE MX_SOURCES "Src/*.c") file(GLOB_RECURSE HAL_SOURCES "Drivers/STM32F1xx_HAL_Driver/Src/*.c") -file(GLOB_RECURSE CMSIS_SOURCES "Drivers/CMSIS/DSP_Lib/Source/*.c") +file(GLOB_RECURSE CMSIS_SOURCES "Drivers/CMSIS/DSP_Lib/Source/*.c" "Drivers/CMSIS/DSP_Lib/Source/*.S") add_library(HAL ${HAL_SOURCES}) add_library(CMSIS ${CMSIS_SOURCES} diff --git a/Src/tim.c b/Src/tim.c index 2d7f21a..16d5366 100644 --- a/Src/tim.c +++ b/Src/tim.c @@ -50,7 +50,7 @@ void MX_TIM3_Init(void) htim3.Instance = TIM3; htim3.Init.Prescaler = 0; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; - htim3.Init.Period = 1800; + htim3.Init.Period = 3600; //1800; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_OC_Init(&htim3) != HAL_OK) { diff --git a/User/user_main.c b/User/user_main.c index 2205693..e0b5a62 100644 --- a/User/user_main.c +++ b/User/user_main.c @@ -3,20 +3,22 @@ // #include -#include #include +#include #include #include "dotmatrix.h" -#include "mxconstants.h" -#include "stm32f1xx_hal.h" #include "adc.h" #include "tim.h" #include "user_main.h" #include "debounce.h" #include "debug.h" -#define SAMPLE_COUNT 256 +// 512 = show 0-5 kHz +// 256 = show 0-10 kHz +// smaller range is OK since we have only a limited reception of higher frequencies anyway +#define SAMPLE_COUNT 512 #define BIN_COUNT (SAMPLE_COUNT/2) +#define CFFT_INST arm_cfft_sR_f32_len256 #define SCREEN_W 32 #define SCREEN_H 16 @@ -28,20 +30,51 @@ #define BTN_UP 3 #define BTN_DOWN 4 -static uint32_t audio_samples[SAMPLE_COUNT * 2]; // 2x size needed for complex FFT -static float *audio_samples_f = (float *) audio_samples; +// Y axis scaling factors +#define WAVEFORM_SCALE 0.008f +#define FFT_SCALE 0.25f * 0.3f +#define FFT_SPINDLE_SCALE_MULT 0.5f + +uint32_t audio_samples[SAMPLE_COUNT * 2]; // 2x size needed for complex FFT +float *audio_samples_f = (float *) audio_samples; + +/** Dot matrix display instance */ +DotMatrix_Cfg *disp; + +/** Capture in progress flag */ +volatile bool capture_pending = false; -static DotMatrix_Cfg *disp; +/** scale & brightness config fields. Initial values. */ +float y_scale = 3; +uint8_t brightness = 4; -static volatile bool capture_pending = false; +/** active rendering mode (visualisation preset) */ +enum { + MODE_WAVEFORM = 0, + MODE_SPECTRUM = 1, + MODE_SPECTRUM2 = 2, +} render_mode; -static float waveform_scale = 3; +#define MODE_MAX MODE_SPECTRUM2 + +bool up_pressed = false; +bool down_pressed = false; +bool left_pressed = false; +bool right_pressed = false; static void display_wave(); + +static void calculate_fft(); + static void display_fft(); +static void display_fft_spindle(); + +static void start_render(); + // region Audio capture & display +/** Start DMA to capture audio */ void capture_start() { if (capture_pending) return; @@ -56,7 +89,22 @@ void capture_start() /** This callback is called by HAL after the transfer is complete */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { - display_wave(); + switch (render_mode) { + case MODE_WAVEFORM: + display_wave(); + break; + + case MODE_SPECTRUM: + calculate_fft(); + display_fft(); + break; + + case MODE_SPECTRUM2: + calculate_fft(); + display_fft_spindle(); + break; + } + capture_pending = false; } @@ -93,14 +141,12 @@ void spread_samples_for_fft() /** Display waveform preview */ void display_wave() { - float wave_y_mult = 0.0078125f; - samples_to_float(); int x_offset = 0; for (int i = 1; i < SAMPLE_COUNT; i++) { - if (audio_samples_f[i] > 0 && audio_samples_f[i-1] < 0) { + if (audio_samples_f[i] > 0 && audio_samples_f[i - 1] < 0) { x_offset = i; break; } @@ -111,15 +157,18 @@ void display_wave() x_offset = 0; } - dmtx_clear(disp); + float totalmult = WAVEFORM_SCALE * y_scale; + + start_render(); for (int i = 0; i < SCREEN_W; i++) { - dmtx_set(disp, i, 7 + roundf(audio_samples_f[i + x_offset] * wave_y_mult * waveform_scale), 1); + dmtx_set(disp, i, 7 + roundf(audio_samples_f[i + x_offset] * totalmult), 1); } + dmtx_show(disp); } /** Calculate and display FFT */ -static void display_fft() +static void calculate_fft() { float *bins = audio_samples_f; @@ -127,41 +176,66 @@ static void display_fft() spread_samples_for_fft(); const arm_cfft_instance_f32 *S; - S = &arm_cfft_sR_f32_len128; + S = &CFFT_INST; arm_cfft_f32(S, bins, 0, true); // bit reversed FFT arm_cmplx_mag_f32(bins, bins, BIN_COUNT); // get magnitude (extract real values) // Normalize & display - dmtx_clear(dmtx); + start_render(); - float factor = (1.0f / BIN_COUNT) * 0.25f; + float factor = (1.0f / BIN_COUNT) * FFT_SCALE * y_scale; for (int i = 0; i < BIN_COUNT; i++) { // +1 because bin 0 is always 0 bins[i] *= factor; } +} + +/** Render classic FFT */ +static void display_fft() +{ + float *bins = audio_samples_f; - // TODO implement offset using gamepad buttons (?) for (int x = 0; x < SCREEN_W; x++) { for (int j = 0; j < 1 + floorf(bins[x]); j++) { - dmtx_set(dmtx, x, j, 1); + dmtx_set(disp, x, j, 1); + } + } + + dmtx_show(disp); +} + +/** Render FFT "spindle" */ +static void display_fft_spindle() +{ + float *bins = audio_samples_f; + + for (int x = 0; x < SCREEN_W; x++) { + for (int j = 0; j < 1 + floorf(bins[x] * FFT_SPINDLE_SCALE_MULT); j++) { + dmtx_set(disp, x, 7 + j, 1); + dmtx_set(disp, x, 7 - j, 1); } } - dmtx_show(dmtx); + dmtx_show(disp); } // endregion -// Increment timebase counter each ms -void HAL_SYSTICK_Callback(void) +// region UI + +/** Clear screen & render "HUD" - button feedback lights */ +void start_render() { - timebase_ms_cb(); -} + dmtx_clear(disp); -bool up_pressed = false; -bool down_pressed = false; + if (up_pressed) dmtx_set(disp, SCREEN_W - 2, SCREEN_H - 1, 1); + if (down_pressed) dmtx_set(disp, SCREEN_W - 2, SCREEN_H - 3, 1); + if (left_pressed) dmtx_set(disp, SCREEN_W - 3, SCREEN_H - 2, 1); + if (right_pressed) dmtx_set(disp, SCREEN_W - 1, SCREEN_H - 2, 1); +} +/** Callback when button press state changes */ static void gamepad_button_cb(uint32_t btn, bool press) { dbg("Button press %d, state %d", btn, press); @@ -170,13 +244,47 @@ static void gamepad_button_cb(uint32_t btn, bool press) case BTN_UP: up_pressed = press; break; + case BTN_DOWN: down_pressed = press; break; + + case BTN_LEFT: + left_pressed = press; + break; + + case BTN_RIGHT: + right_pressed = press; + break; + + case BTN_CENTER: + if (!press) { + // center button released + // cycle through modes + if (render_mode++ == MODE_MAX) { + render_mode = 0; + } + } + + info("Switched to render mode %d", render_mode); + break; } } -void user_init() { +// endregion + +/** + * Increment timebase counter each ms. + * This is called by HAL, weak override. + */ +void HAL_SYSTICK_Callback(void) +{ + timebase_ms_cb(); +} + +/** Init the application */ +void user_init() +{ // Enable audio input HAL_GPIO_WritePin(AUDIO_NSTBY_GPIO_Port, AUDIO_NSTBY_Pin, 1); @@ -189,7 +297,7 @@ void user_init() { disp_init.SPIx = SPI1; disp = dmtx_init(&disp_init); - dmtx_intensity(disp, 7); + dmtx_intensity(disp, brightness); dmtx_clear(disp); dmtx_show(disp); @@ -229,7 +337,7 @@ void user_init() { debo_register_pin(&debo); } - +/** Main function, called from MX-generated main.c */ void user_main() { banner("== USER CODE STARTING =="); @@ -237,30 +345,53 @@ void user_main() user_init(); ms_time_t counter1 = 0; - uint32_t counter2 = 0; - uint32_t btn_scale_cnt = 0; + ms_time_t counter2 = 0; + ms_time_t btn_scale_cnt = 0; + ms_time_t btn_brt_cnt = 0; while (1) { if (ms_loop_elapsed(&counter1, 500)) { // Blink HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); } + // hold-to-repeat for up/down buttons (sensitivity) + // This is not the correct way to do it, but good enough if (ms_loop_elapsed(&btn_scale_cnt, 50)) { if (up_pressed) { - waveform_scale += 0.1; + y_scale += 0.1; } if (down_pressed) { - if (waveform_scale > 0.1) { - waveform_scale -= 0.1; + if (y_scale > 0.1) { + y_scale -= 0.1; } } if (up_pressed || down_pressed) { - dbg("scale = %.1f", waveform_scale); + dbg("scale = %.1f", y_scale); } } + // hold-to-repeat for left/right buttons (brightness) + if (ms_loop_elapsed(&btn_brt_cnt, 200)) { + if (left_pressed) { + if (brightness > 0) { + brightness--; + } + } + + if (right_pressed) { + if (brightness < 15) { + brightness++; + } + } + + if (left_pressed || right_pressed) { + dmtx_intensity(disp, brightness); + } + } + + // capture a sample to update display if (!capture_pending) { capture_start(); } @@ -269,6 +400,7 @@ void user_main() //region Error handlers +/** Called from MX-generated HAL error handler */ void user_Error_Handler() { error("HAL error occurred.\n"); @@ -276,12 +408,12 @@ void user_Error_Handler() } /** - * @brief Reports the name of the source file and the source line number - * where the assert_param error has occurred. - * @param file: pointer to the source file name - * @param line: assert_param error line source number - * @retval None - */ + * @brief Reports the name of the source file and the source line number + * where the assert_param error has occurred. + * @param file: pointer to the source file name + * @param line: assert_param error line source number + * @retval None + */ void user_assert_failed(uint8_t *file, uint32_t line) { user_error_file_line("Assert failed", (const char *) file, line);