Skip to content

Commit 5e89508

Browse files
committed
Merge branch 'feature/application_spectrum2d' into 'master'
Feature/application spectrum2d See merge request idf/esp-dsp!113
2 parents f09c138 + 93cf384 commit 5e89508

File tree

10 files changed

+404
-5
lines changed

10 files changed

+404
-5
lines changed

applications/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ The applications are grouped into subdirectories by category. Each category dire
1414
* [3d graphics](./azure_board_apps/apps/3d_graphics/README.md) application
1515
* [Kalman filter](./azure_board_apps/apps/kalman_filter/README.md) application
1616

17+
* [LyraT Board](./lyrat_board_app/README.md) application
18+
* [ESP32-S3-BOX-Lite](./spectrum_box_lite/README.md) application
19+
1720

applications/lyrat_board_app/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ The audio processing flow contains next blocks:
5252

5353
### Hardware required
5454

55-
This example does not require any special hardware, and can be run on any common development board.
55+
This example require LyraT development board.
5656

5757
### Configure the project
5858

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# For more information about build system see
2+
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
3+
# The following five lines of boilerplate have to be in your project's
4+
# CMakeLists in this exact order for cmake to work correctly
5+
cmake_minimum_required(VERSION 3.16)
6+
7+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
8+
project(spectrum_box_lite)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ESP-DSP ESP32-S3-BOX-Lite Board demo application
2+
3+
The demo applications are developed for the ESP32-S3-BOX-Lite development board and demonstrate the usage of FFT functions from the ESP-DSP library.
4+
This example showcases how to use FFT functionality to process audio stream data.
5+
The application record sound from two microphones and show the spectrum from them at display as 2D plot.
6+
7+
## Audio Processing Flow
8+
9+
The audio processing flow contains next blocks:
10+
1. Audio processing task
11+
* Read left and right channel data from the microphone
12+
* Apply window multiplication to both channels
13+
* Process FFT and apply bit reverse
14+
* Split complex spectrum from two channels to two spectrums of two real channels
15+
* Calculate absolute spectrum and convert it to dB
16+
* Calculate moving average of the spectrum
17+
2. Image Display Task
18+
* Read data from the
19+
* Write data from triple buffer to audio codec
20+
21+
## How to use the example
22+
23+
Just flash the application to the ESP32-S3-BOX-Lite development board, and play some music around or start to speak to the board microphones.
24+
The display will show the real-time spectrum.
25+
The microphone sensitivity could be adjusted in the code.
26+
27+
### Hardware required
28+
29+
This example does not require any special hardware, and can be run on any common development board.
30+
31+
### Configure the project
32+
33+
Under Component Config ---> DSP Library ---> DSP Optimization, it's possible to choose either the optimized or ANSI implementation, to compare them.
34+
35+
### Build and flash
36+
37+
Build the project and flash it to the board, then run monitor tool to view serial output (replace PORT with serial port name):
38+
39+
```
40+
idf.py flash monitor
41+
```
42+
43+
(To exit the serial monitor, type ``Ctrl-]``.)
44+
45+
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRCS "main.c"
2+
INCLUDE_DIRS ".")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dependencies:
2+
espressif/esp-box-lite: "^2.0.3"
3+
lvgl/lvgl: "^8.3.10"
4+
espressif/esp-dsp:
5+
version: '*'
6+
override_path: "../../../../esp-dsp"
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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(&microphone_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

Comments
 (0)