|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: CC0-1.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#include <dirent.h> |
| 8 | +#include <math.h> |
| 9 | + |
| 10 | +#include "bsp/esp-bsp.h" |
| 11 | +#include "esp_log.h" |
| 12 | +#include "esp_dsp.h" |
| 13 | + |
| 14 | +#include "freertos/FreeRTOS.h" |
| 15 | +#include "freertos/task.h" |
| 16 | +#include "freertos/semphr.h" |
| 17 | + |
| 18 | +#include "esp_system.h" |
| 19 | +#include "esp_err.h" |
| 20 | +#include "esp_log.h" |
| 21 | +#include "esp_timer.h" |
| 22 | +#include <malloc.h> |
| 23 | + |
| 24 | +// Amount of audio channels |
| 25 | +#define I2S_CHANNEL_NUM (2) |
| 26 | +// Microphone Sample rate |
| 27 | +#define SAMPLE_RATE (10000) |
| 28 | + |
| 29 | +#define BITS_PER_CHANNEL 16 |
| 30 | +// Input buffer size |
| 31 | +#define BUFFER_PROCESS_SIZE 512 |
| 32 | + |
| 33 | +static const char *TAG = "main"; |
| 34 | + |
| 35 | +// Buffer to process output spectrum |
| 36 | +static float result_data[BUFFER_PROCESS_SIZE]; |
| 37 | + |
| 38 | +// Microphone read task |
| 39 | +static void microphone_read_task(void *arg) |
| 40 | +{ |
| 41 | + esp_codec_dev_handle_t mic_codec_dev = NULL; |
| 42 | + // Init board microphone |
| 43 | + mic_codec_dev = bsp_audio_codec_microphone_init(); |
| 44 | + if (mic_codec_dev == NULL) { |
| 45 | + ESP_LOGE(TAG, "Not possible to initialize microphone!"); |
| 46 | + return; |
| 47 | + } |
| 48 | + |
| 49 | + // Init esp-dsp library to use fft functionality |
| 50 | + esp_err_t ret = dsps_fft2r_init_sc16(NULL, CONFIG_DSP_MAX_FFT_SIZE); |
| 51 | + if (ret != ESP_OK) { |
| 52 | + ESP_LOGE(TAG, "Not possible to initialize FFT esp-dsp from library!"); |
| 53 | + return; |
| 54 | + } |
| 55 | + |
| 56 | + esp_codec_dev_sample_info_t fs = { |
| 57 | + .sample_rate = SAMPLE_RATE, |
| 58 | + .channel = I2S_CHANNEL_NUM, |
| 59 | + .channel_mask = 0, |
| 60 | + .bits_per_sample = BITS_PER_CHANNEL, |
| 61 | + }; |
| 62 | + |
| 63 | + int result = esp_codec_dev_open(mic_codec_dev, &fs); |
| 64 | + if (result != ESP_OK) { |
| 65 | + ESP_LOGE(TAG, "Not possible to open microphone!"); |
| 66 | + return; |
| 67 | + } |
| 68 | + // Set input microphone gain (from 1 to 100) |
| 69 | + ESP_LOGI(TAG, "Adjust microphone input volume in the code here..."); |
| 70 | + result |= esp_codec_dev_set_in_gain(mic_codec_dev, 20.0); |
| 71 | + if (result != ESP_OK) { |
| 72 | + ESP_LOGE(TAG, "Not possible to set up microphone gain!"); |
| 73 | + return; |
| 74 | + } |
| 75 | + |
| 76 | + int audio_chunksize = BUFFER_PROCESS_SIZE; |
| 77 | + |
| 78 | + // Allocate audio buffer and check for result |
| 79 | + int16_t *audio_buffer = (int16_t *)memalign(16, (audio_chunksize + 16) * sizeof(int16_t) * I2S_CHANNEL_NUM); |
| 80 | + // Allocate buffer for window |
| 81 | + int16_t *wind_buffer = (int16_t *)memalign(16, (audio_chunksize + 16) * sizeof(int16_t) * I2S_CHANNEL_NUM); |
| 82 | + // Generate window and convert it to int16_t |
| 83 | + dsps_wind_blackman_harris_f32(result_data, audio_chunksize); |
| 84 | + for (int i = 0 ; i < audio_chunksize; i++) { |
| 85 | + wind_buffer[i * 2 + 0] = (int16_t)(result_data[i] * 32767); |
| 86 | + wind_buffer[i * 2 + 1] = wind_buffer[i * 2 + 0]; |
| 87 | + } |
| 88 | + |
| 89 | + while (true) { |
| 90 | + |
| 91 | + // Read audio data from I2S bus |
| 92 | + result = esp_codec_dev_read(mic_codec_dev, audio_buffer, audio_chunksize * sizeof(int16_t) * I2S_CHANNEL_NUM); |
| 93 | + // Multiply input stream with window coefficients |
| 94 | + dsps_mul_s16_ansi(audio_buffer, wind_buffer, audio_buffer, audio_chunksize * 2, 1, 1, 1, 15); |
| 95 | + |
| 96 | + // Call FFT bit reverse |
| 97 | + dsps_fft2r_sc16_ae32(audio_buffer, audio_chunksize); |
| 98 | + dsps_bit_rev_sc16_ansi(audio_buffer, audio_chunksize); |
| 99 | + // Convert spectrum from two input channels to two |
| 100 | + // spectrums for two channels. |
| 101 | + dsps_cplx2reC_sc16(audio_buffer, audio_chunksize); |
| 102 | + |
| 103 | + // The output data array presented as moving average for input in dB |
| 104 | + for (int i = 0 ; i < audio_chunksize ; i++) { |
| 105 | + float spectrum_sqr = audio_buffer[i * 2 + 0] * audio_buffer[i * 2 + 0] + audio_buffer[i * 2 + 1] * audio_buffer[i * 2 + 1]; |
| 106 | + float spectrum_dB = 10 * log10f(0.1 + spectrum_sqr); |
| 107 | + // Multiply with sime coefficient for better view data on screen |
| 108 | + spectrum_dB = 4 * spectrum_dB; |
| 109 | + // Apply moving average of spectrum |
| 110 | + result_data[i] = 0.8 * result_data[i] + 0.2 * spectrum_dB; |
| 111 | + } |
| 112 | + vTaskDelay(10); |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +// Screen image width |
| 117 | +#define X_AXIS_SIZE (320) |
| 118 | +// Screen image height |
| 119 | +#define Y_AXIS_SIZE (240) |
| 120 | + |
| 121 | +static uint8_t screen_rgb_data[X_AXIS_SIZE * Y_AXIS_SIZE * LV_IMG_PX_SIZE_ALPHA_BYTE]; |
| 122 | + |
| 123 | +static const lv_img_dsc_t img_screen_rgb = { |
| 124 | + .header.always_zero = 0, |
| 125 | + .header.w = X_AXIS_SIZE, |
| 126 | + .header.h = Y_AXIS_SIZE, |
| 127 | + .data_size = X_AXIS_SIZE * Y_AXIS_SIZE * LV_IMG_PX_SIZE_ALPHA_BYTE, |
| 128 | + .header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA, |
| 129 | + .data = screen_rgb_data, |
| 130 | +}; |
| 131 | + |
| 132 | +// The function convert value to RGB565 color value |
| 133 | +static int8_t colors[3][3] = { {0, 0, 31}, {0, 63, 0}, {31, 0, 0} }; |
| 134 | +static uint16_t convert_to_rgb(uint8_t minval, uint8_t maxval, int8_t val) |
| 135 | +{ |
| 136 | + uint16_t result; |
| 137 | + |
| 138 | + float i_f = (float)(val - minval) / (float)(maxval - minval) * 2; |
| 139 | + |
| 140 | + int Ii = i_f; |
| 141 | + float If = i_f - Ii; |
| 142 | + |
| 143 | + int8_t *c1 = colors[Ii]; |
| 144 | + int8_t *c2 = colors[Ii + 1]; |
| 145 | + uint16_t res_colors[3]; |
| 146 | + |
| 147 | + res_colors[0] = c1[0] + If * (c2[0] - c1[0]); |
| 148 | + res_colors[1] = c1[1] + If * (c2[1] - c1[1]); |
| 149 | + res_colors[2] = c1[2] + If * (c2[2] - c1[2]); |
| 150 | + result = res_colors[2] | (res_colors[1] << 5) | (res_colors[0] << 11); |
| 151 | + return result; |
| 152 | +} |
| 153 | + |
| 154 | +// Init screen with blue values |
| 155 | +static void spectrum2d_picture_init() |
| 156 | +{ |
| 157 | + for (int y = 0 ; y < img_screen_rgb.header.h ; y++) { |
| 158 | + for (int x = 0 ; x < img_screen_rgb.header.w ; x++) { |
| 159 | + screen_rgb_data[(y * img_screen_rgb.header.w + x)*LV_IMG_PX_SIZE_ALPHA_BYTE + 0] = 0x0; |
| 160 | + screen_rgb_data[(y * img_screen_rgb.header.w + x)*LV_IMG_PX_SIZE_ALPHA_BYTE + 1] = 0x1f; |
| 161 | + screen_rgb_data[(y * img_screen_rgb.header.w + x)*LV_IMG_PX_SIZE_ALPHA_BYTE + 2] = 0xff; |
| 162 | + } |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +// Add spectrum data to the screen |
| 167 | +static void spectrum2d_picture() |
| 168 | +{ |
| 169 | + for (int y = 0 ; y < (img_screen_rgb.header.h - 1) ; y++) { |
| 170 | + for (int x = 0 ; x < img_screen_rgb.header.w ; x++) { |
| 171 | + for (int i = 0 ; i < LV_IMG_PX_SIZE_ALPHA_BYTE ; i++) { |
| 172 | + screen_rgb_data[(y * img_screen_rgb.header.w + x)*LV_IMG_PX_SIZE_ALPHA_BYTE + i] = screen_rgb_data[((y + 1) * img_screen_rgb.header.w + x) * LV_IMG_PX_SIZE_ALPHA_BYTE + i]; |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + // Add left channel to the screen |
| 178 | + // The order of the values inverted |
| 179 | + for (int x = 0 ; x < img_screen_rgb.header.w / 2 ; x++) { |
| 180 | + // Get inverted index value |
| 181 | + int in_index = img_screen_rgb.header.w / 2 - x - 1; |
| 182 | + float data = result_data[in_index]; |
| 183 | + |
| 184 | + // Limit input data |
| 185 | + if (data > 127) { |
| 186 | + data = 127; |
| 187 | + } |
| 188 | + if (data < 0) { |
| 189 | + data = 0; |
| 190 | + } |
| 191 | + |
| 192 | + // Convert input value in dB to the color |
| 193 | + uint16_t color_val = convert_to_rgb(0, 128, data); |
| 194 | + // Split 16 bit value to two bytes, to change the bytes order |
| 195 | + uint8_t *ref_val = (uint8_t *)&color_val; |
| 196 | + int out_index = x; |
| 197 | + screen_rgb_data[((img_screen_rgb.header.h - 1)*img_screen_rgb.header.w + out_index)*LV_IMG_PX_SIZE_ALPHA_BYTE + 0] = ref_val[1]; |
| 198 | + screen_rgb_data[((img_screen_rgb.header.h - 1)*img_screen_rgb.header.w + out_index)*LV_IMG_PX_SIZE_ALPHA_BYTE + 1] = ref_val[0]; |
| 199 | + // Set alpha value |
| 200 | + screen_rgb_data[((img_screen_rgb.header.h - 1)*img_screen_rgb.header.w + out_index)*LV_IMG_PX_SIZE_ALPHA_BYTE + 2] = 0xff; |
| 201 | + } |
| 202 | + |
| 203 | + // Add right channel to the screen |
| 204 | + for (int x = 0 ; x < img_screen_rgb.header.w / 2 ; x++) { |
| 205 | + // Get index of right channel |
| 206 | + int in_index = BUFFER_PROCESS_SIZE / 2 + x; |
| 207 | + float data = result_data[in_index]; |
| 208 | + |
| 209 | + // Limit input data |
| 210 | + if (data > 127) { |
| 211 | + data = 127; |
| 212 | + } |
| 213 | + if (data < 0) { |
| 214 | + data = 0; |
| 215 | + } |
| 216 | + |
| 217 | + // Convert input value in dB to the color |
| 218 | + uint16_t color_val = convert_to_rgb(0, 128, data); |
| 219 | + // Split 16 bit value to two bytes, to change the bytes order |
| 220 | + uint8_t *ref_val = (uint8_t *)&color_val; |
| 221 | + int out_index = img_screen_rgb.header.w / 2 + x; |
| 222 | + screen_rgb_data[((img_screen_rgb.header.h - 1)*img_screen_rgb.header.w + out_index)*LV_IMG_PX_SIZE_ALPHA_BYTE + 0] = ref_val[1]; |
| 223 | + screen_rgb_data[((img_screen_rgb.header.h - 1)*img_screen_rgb.header.w + out_index)*LV_IMG_PX_SIZE_ALPHA_BYTE + 1] = ref_val[0]; |
| 224 | + // Set alpha value |
| 225 | + screen_rgb_data[((img_screen_rgb.header.h - 1)*img_screen_rgb.header.w + out_index)*LV_IMG_PX_SIZE_ALPHA_BYTE + 2] = 0xff; |
| 226 | + } |
| 227 | +} |
| 228 | + |
| 229 | +static void image_display_task(void *arg) |
| 230 | +{ |
| 231 | + // LV_IMG_DECLARE(img_screen_rgb); |
| 232 | + lv_obj_t *img1 = lv_img_create(lv_scr_act()); |
| 233 | + lv_img_set_src(img1, &img_screen_rgb); |
| 234 | + spectrum2d_picture_init(); |
| 235 | + lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0); |
| 236 | + |
| 237 | + for (;;) { |
| 238 | + // Update image with new spectrum values |
| 239 | + spectrum2d_picture(); |
| 240 | + // Update screen with new image |
| 241 | + lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0); |
| 242 | + // Free CPU for a while |
| 243 | + vTaskDelay(1); |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | +void app_main(void) |
| 248 | +{ |
| 249 | + /* Initialize I2C (for touch and audio) */ |
| 250 | + bsp_i2c_init(); |
| 251 | + |
| 252 | + /* Initialize display and LVGL */ |
| 253 | + bsp_display_start(); |
| 254 | + |
| 255 | + /* Set display brightness to 100% */ |
| 256 | + bsp_display_backlight_on(); |
| 257 | + |
| 258 | + int ret_val = xTaskCreatePinnedToCore(µphone_read_task, "Microphone read Task", 8 * 1024, NULL, 3, NULL, 0); |
| 259 | + if (ret_val != pdPASS) { |
| 260 | + ESP_LOGE(TAG, "Not possible to allocate microphone task, ret_val = %i", ret_val); |
| 261 | + return; |
| 262 | + } |
| 263 | + |
| 264 | + ret_val = xTaskCreatePinnedToCore(&image_display_task, "Draw task", 10 * 1024, NULL, 5, NULL, 1); |
| 265 | + if (ret_val != pdPASS) { |
| 266 | + ESP_LOGE(TAG, "Not possible to allocate microphone task, ret_val= %i", ret_val); |
| 267 | + return; |
| 268 | + } |
| 269 | +} |
0 commit comments