|
|
@@ -0,0 +1,997 @@
|
|
|
+#include "bsp_board.h"
|
|
|
+
|
|
|
+#include <stdbool.h>
|
|
|
+#include "esp_err.h"
|
|
|
+#include "freertos/FreeRTOS.h"
|
|
|
+#include "freertos/semphr.h"
|
|
|
+#include "freertos/task.h"
|
|
|
+#include "string.h"
|
|
|
+#include "driver/gpio.h"
|
|
|
+#include "esp_err.h"
|
|
|
+#include "esp_log.h"
|
|
|
+#include "esp_rom_sys.h"
|
|
|
+#include "esp_check.h"
|
|
|
+#include "esp_vfs_fat.h"
|
|
|
+#include "sdmmc_cmd.h"
|
|
|
+#include "driver/i2s_std.h"
|
|
|
+#include "driver/i2s_types.h"
|
|
|
+#include "driver/i2s_pdm.h"
|
|
|
+#include "driver/i2s_tdm.h"
|
|
|
+
|
|
|
+#include <sys/unistd.h>
|
|
|
+#include <sys/stat.h>
|
|
|
+#include "dirent.h"
|
|
|
+#include "driver/sdmmc_host.h"
|
|
|
+#include <errno.h>
|
|
|
+#include <math.h>
|
|
|
+
|
|
|
+#if ((SOC_SDMMC_HOST_SUPPORTED) && (FUNC_SDMMC_EN))
|
|
|
+#include "driver/sdmmc_host.h"
|
|
|
+#endif /* ((SOC_SDMMC_HOST_SUPPORTED) && (FUNC_SDMMC_EN)) */
|
|
|
+
|
|
|
+
|
|
|
+#define ADC_I2S_CHANNEL 2
|
|
|
+#define ADC_BITS_PER_SAMPLE 32
|
|
|
+
|
|
|
+static sdmmc_card_t *card;
|
|
|
+static const char *TAG = "board";
|
|
|
+static int s_play_sample_rate = 16000;
|
|
|
+static int s_play_channel_format = 1;
|
|
|
+static int s_bits_per_chan = 16;
|
|
|
+
|
|
|
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
|
+static i2s_chan_handle_t tx_handle = NULL; // I2S tx channel handler
|
|
|
+static i2s_chan_handle_t rx_handle = NULL; // I2S rx channel handler
|
|
|
+#endif
|
|
|
+static const audio_codec_data_if_t *record_data_if = NULL;
|
|
|
+static const audio_codec_ctrl_if_t *record_ctrl_if = NULL;
|
|
|
+static const audio_codec_if_t *record_codec_if = NULL;
|
|
|
+static esp_codec_dev_handle_t record_dev = NULL;
|
|
|
+
|
|
|
+static const audio_codec_data_if_t *play_data_if = NULL;
|
|
|
+static const audio_codec_ctrl_if_t *play_ctrl_if = NULL;
|
|
|
+static const audio_codec_gpio_if_t *play_gpio_if = NULL;
|
|
|
+static const audio_codec_if_t *play_codec_if = NULL;
|
|
|
+static esp_codec_dev_handle_t play_dev = NULL;
|
|
|
+static i2c_master_bus_handle_t i2c_bus_handle = NULL;
|
|
|
+static uint32_t SDCard_Size = 0;
|
|
|
+static bool s_volume_buttons_task_started = false;
|
|
|
+
|
|
|
+#define AEC_FAR_QUEUE_SAMPLES 4096
|
|
|
+#define AEC_FILTER_TAPS 128
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ bool initialized;
|
|
|
+ int frame_samples;
|
|
|
+ float mu;
|
|
|
+ float eps;
|
|
|
+ float w[AEC_FILTER_TAPS];
|
|
|
+ float xhist[AEC_FILTER_TAPS];
|
|
|
+ int16_t far_queue[AEC_FAR_QUEUE_SAMPLES];
|
|
|
+ int far_head;
|
|
|
+ int far_tail;
|
|
|
+ int far_count;
|
|
|
+ SemaphoreHandle_t lock;
|
|
|
+} simple_aec_t;
|
|
|
+
|
|
|
+static simple_aec_t s_aec = {
|
|
|
+ .initialized = false,
|
|
|
+ .frame_samples = 160,
|
|
|
+ .mu = 0.08f,
|
|
|
+ .eps = 1e-3f,
|
|
|
+ .far_head = 0,
|
|
|
+ .far_tail = 0,
|
|
|
+ .far_count = 0,
|
|
|
+ .lock = NULL,
|
|
|
+};
|
|
|
+
|
|
|
+static inline int16_t aec_clamp_int16(float v)
|
|
|
+{
|
|
|
+ if (v > 32767.0f) {
|
|
|
+ return 32767;
|
|
|
+ }
|
|
|
+ if (v < -32768.0f) {
|
|
|
+ return -32768;
|
|
|
+ }
|
|
|
+ return (int16_t)v;
|
|
|
+}
|
|
|
+
|
|
|
+static void aec_internal_reset_locked(void)
|
|
|
+{
|
|
|
+ memset(s_aec.w, 0, sizeof(s_aec.w));
|
|
|
+ memset(s_aec.xhist, 0, sizeof(s_aec.xhist));
|
|
|
+ s_aec.far_head = 0;
|
|
|
+ s_aec.far_tail = 0;
|
|
|
+ s_aec.far_count = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int aec_pop_far_block(int16_t *dst, int samples)
|
|
|
+{
|
|
|
+ int copied = 0;
|
|
|
+ if (!s_aec.lock) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (xSemaphoreTake(s_aec.lock, pdMS_TO_TICKS(20)) != pdTRUE) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (copied < samples && s_aec.far_count > 0) {
|
|
|
+ dst[copied++] = s_aec.far_queue[s_aec.far_tail];
|
|
|
+ s_aec.far_tail = (s_aec.far_tail + 1) % AEC_FAR_QUEUE_SAMPLES;
|
|
|
+ s_aec.far_count--;
|
|
|
+ }
|
|
|
+
|
|
|
+ xSemaphoreGive(s_aec.lock);
|
|
|
+ return copied;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_aec_init(int frame_samples)
|
|
|
+{
|
|
|
+ if (frame_samples <= 0) {
|
|
|
+ return ESP_ERR_INVALID_ARG;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!s_aec.lock) {
|
|
|
+ s_aec.lock = xSemaphoreCreateMutex();
|
|
|
+ if (!s_aec.lock) {
|
|
|
+ return ESP_ERR_NO_MEM;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (xSemaphoreTake(s_aec.lock, pdMS_TO_TICKS(100)) != pdTRUE) {
|
|
|
+ return ESP_ERR_TIMEOUT;
|
|
|
+ }
|
|
|
+
|
|
|
+ s_aec.frame_samples = frame_samples;
|
|
|
+ s_aec.mu = 0.08f;
|
|
|
+ s_aec.eps = 1e-3f;
|
|
|
+ aec_internal_reset_locked();
|
|
|
+ s_aec.initialized = true;
|
|
|
+
|
|
|
+ xSemaphoreGive(s_aec.lock);
|
|
|
+ ESP_LOGI(TAG, "AEC initialized: frame=%d taps=%d", frame_samples, AEC_FILTER_TAPS);
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+void esp_aec_deinit(void)
|
|
|
+{
|
|
|
+ if (!s_aec.lock) {
|
|
|
+ s_aec.initialized = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (xSemaphoreTake(s_aec.lock, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
|
+ s_aec.initialized = false;
|
|
|
+ aec_internal_reset_locked();
|
|
|
+ xSemaphoreGive(s_aec.lock);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_aec_reset(void)
|
|
|
+{
|
|
|
+ if (!s_aec.initialized || !s_aec.lock) {
|
|
|
+ return ESP_ERR_INVALID_STATE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (xSemaphoreTake(s_aec.lock, pdMS_TO_TICKS(100)) != pdTRUE) {
|
|
|
+ return ESP_ERR_TIMEOUT;
|
|
|
+ }
|
|
|
+
|
|
|
+ aec_internal_reset_locked();
|
|
|
+ xSemaphoreGive(s_aec.lock);
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_aec_feed_far(const int16_t *far_data, int samples)
|
|
|
+{
|
|
|
+ if (!s_aec.initialized || !s_aec.lock) {
|
|
|
+ return ESP_ERR_INVALID_STATE;
|
|
|
+ }
|
|
|
+ if (!far_data || samples <= 0) {
|
|
|
+ return ESP_ERR_INVALID_ARG;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (xSemaphoreTake(s_aec.lock, pdMS_TO_TICKS(20)) != pdTRUE) {
|
|
|
+ return ESP_ERR_TIMEOUT;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < samples; i++) {
|
|
|
+ if (s_aec.far_count == AEC_FAR_QUEUE_SAMPLES) {
|
|
|
+ s_aec.far_tail = (s_aec.far_tail + 1) % AEC_FAR_QUEUE_SAMPLES;
|
|
|
+ s_aec.far_count--;
|
|
|
+ }
|
|
|
+ s_aec.far_queue[s_aec.far_head] = far_data[i];
|
|
|
+ s_aec.far_head = (s_aec.far_head + 1) % AEC_FAR_QUEUE_SAMPLES;
|
|
|
+ s_aec.far_count++;
|
|
|
+ }
|
|
|
+
|
|
|
+ xSemaphoreGive(s_aec.lock);
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_aec_process_near(const int16_t *near_in, int16_t *near_out, int samples)
|
|
|
+{
|
|
|
+ if (!near_in || !near_out || samples <= 0) {
|
|
|
+ return ESP_ERR_INVALID_ARG;
|
|
|
+ }
|
|
|
+ if (!s_aec.initialized) {
|
|
|
+ memcpy(near_out, near_in, (size_t)samples * sizeof(int16_t));
|
|
|
+ return ESP_ERR_INVALID_STATE;
|
|
|
+ }
|
|
|
+
|
|
|
+ int16_t far_block[320] = {0};
|
|
|
+ if (samples > (int)(sizeof(far_block) / sizeof(far_block[0]))) {
|
|
|
+ return ESP_ERR_INVALID_SIZE;
|
|
|
+ }
|
|
|
+
|
|
|
+ int popped = aec_pop_far_block(far_block, samples);
|
|
|
+ for (int i = popped; i < samples; i++) {
|
|
|
+ far_block[i] = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int n = 0; n < samples; n++) {
|
|
|
+ for (int k = AEC_FILTER_TAPS - 1; k > 0; k--) {
|
|
|
+ s_aec.xhist[k] = s_aec.xhist[k - 1];
|
|
|
+ }
|
|
|
+ s_aec.xhist[0] = (float)far_block[n];
|
|
|
+
|
|
|
+ float y = 0.0f;
|
|
|
+ float power = s_aec.eps;
|
|
|
+ for (int k = 0; k < AEC_FILTER_TAPS; k++) {
|
|
|
+ y += s_aec.w[k] * s_aec.xhist[k];
|
|
|
+ power += s_aec.xhist[k] * s_aec.xhist[k];
|
|
|
+ }
|
|
|
+
|
|
|
+ float e = (float)near_in[n] - y;
|
|
|
+ float g = (s_aec.mu * e) / power;
|
|
|
+
|
|
|
+ for (int k = 0; k < AEC_FILTER_TAPS; k++) {
|
|
|
+ s_aec.w[k] += g * s_aec.xhist[k];
|
|
|
+ }
|
|
|
+
|
|
|
+ near_out[n] = aec_clamp_int16(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+int mic_level = 0;
|
|
|
+esp_codec_dev_handle_t esp_ret_play_dev(void)
|
|
|
+{
|
|
|
+ return play_dev;
|
|
|
+}
|
|
|
+
|
|
|
+i2c_master_bus_handle_t esp_ret_i2c_handle(void)
|
|
|
+{
|
|
|
+ return i2c_bus_handle;
|
|
|
+}
|
|
|
+
|
|
|
+static esp_err_t i2c_master_init(void)
|
|
|
+{
|
|
|
+ const i2c_master_bus_config_t bus_config = {
|
|
|
+ .i2c_port = I2C_NUM,
|
|
|
+ .sda_io_num = GPIO_I2C_SDA,
|
|
|
+ .scl_io_num = GPIO_I2C_SCL,
|
|
|
+ .clk_source = I2C_CLK_SRC_DEFAULT,
|
|
|
+ };
|
|
|
+
|
|
|
+ esp_err_t ret = i2c_new_master_bus(&bus_config, &i2c_bus_handle);
|
|
|
+ if (ret != ESP_OK) {
|
|
|
+ ESP_LOGE(TAG, "Failed to initialize I2C bus: %s", esp_err_to_name(ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ ESP_LOGI(TAG, "I2C bus initialized successfully");
|
|
|
+ return ESP_OK; // 返回 ESP_OK 表示成功
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+esp_err_t bsp_codec_adc_init(int sample_rate)
|
|
|
+{ ESP_LOGI(TAG,"ADC INIT START");
|
|
|
+ esp_err_t ret_val = ESP_OK;
|
|
|
+
|
|
|
+ audio_codec_i2s_cfg_t i2s_cfg = {
|
|
|
+ .port = I2S_NUM_1,
|
|
|
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
|
+ .rx_handle = rx_handle,
|
|
|
+ .tx_handle = NULL,
|
|
|
+#endif
|
|
|
+ };
|
|
|
+
|
|
|
+ record_data_if = audio_codec_new_i2s_data(&i2s_cfg);
|
|
|
+
|
|
|
+ if(record_data_if == NULL)
|
|
|
+{
|
|
|
+ ESP_LOGE(TAG,"record_data_if creation failed");
|
|
|
+ return ESP_FAIL;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ audio_codec_i2c_cfg_t i2c_cfg = {
|
|
|
+ .addr = ES7210_CODEC_DEFAULT_ADDR,
|
|
|
+ .bus_handle = i2c_bus_handle
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ record_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
|
|
+
|
|
|
+
|
|
|
+ es7210_codec_cfg_t es7210_cfg = {
|
|
|
+ .ctrl_if = record_ctrl_if,
|
|
|
+ .mic_selected =
|
|
|
+ ES7210_SEL_MIC1 |
|
|
|
+ ES7210_SEL_MIC2,
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ record_codec_if = es7210_codec_new(&es7210_cfg);
|
|
|
+
|
|
|
+ ESP_LOGI(TAG,"ES7210 IF=%p", record_codec_if);
|
|
|
+
|
|
|
+
|
|
|
+ esp_codec_dev_cfg_t dev_cfg = {
|
|
|
+ .codec_if = record_codec_if,
|
|
|
+ .data_if = record_data_if,
|
|
|
+ .dev_type = ESP_CODEC_DEV_TYPE_IN,
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ record_dev = esp_codec_dev_new(&dev_cfg);
|
|
|
+
|
|
|
+
|
|
|
+ if(record_dev == NULL)
|
|
|
+ {
|
|
|
+ ESP_LOGE(TAG,"Failed creating ADC codec");
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ esp_codec_dev_sample_info_t fs = {
|
|
|
+ .sample_rate = sample_rate,
|
|
|
+ .channel = 2,
|
|
|
+ .bits_per_sample = 16,
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ ret_val = esp_codec_dev_open(record_dev, &fs);
|
|
|
+
|
|
|
+
|
|
|
+ if(ret_val != ESP_OK)
|
|
|
+ {
|
|
|
+ ESP_LOGE(TAG,
|
|
|
+ "Codec open failed %s",
|
|
|
+ esp_err_to_name(ret_val));
|
|
|
+ return ret_val;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ esp_codec_dev_set_in_channel_gain(
|
|
|
+ record_dev,
|
|
|
+ ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
|
|
|
+ RECORD_VOLUME);
|
|
|
+
|
|
|
+ esp_codec_dev_set_in_channel_gain(
|
|
|
+ record_dev,
|
|
|
+ ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1),
|
|
|
+ RECORD_VOLUME);
|
|
|
+
|
|
|
+
|
|
|
+ return ret_val;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t bsp_codec_dac_init(int sample_rate, int channel_format, int bits_per_chan)
|
|
|
+{
|
|
|
+ esp_err_t ret_val = ESP_OK;
|
|
|
+
|
|
|
+ // Do initialize of related interface: data_if, ctrl_if and gpio_if
|
|
|
+ audio_codec_i2s_cfg_t i2s_cfg = {
|
|
|
+ .port = I2S_NUM_1,
|
|
|
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
|
+ .rx_handle = NULL,
|
|
|
+ .tx_handle = tx_handle,
|
|
|
+#endif
|
|
|
+ };
|
|
|
+ play_data_if = audio_codec_new_i2s_data(&i2s_cfg);
|
|
|
+
|
|
|
+ audio_codec_i2c_cfg_t i2c_cfg = {.addr = ES8311_CODEC_DEFAULT_ADDR,.bus_handle = i2c_bus_handle};
|
|
|
+ play_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
|
|
|
+ play_gpio_if = audio_codec_new_gpio();
|
|
|
+ // New output codec interface
|
|
|
+ es8311_codec_cfg_t es8311_cfg = {
|
|
|
+ .codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC,
|
|
|
+ .ctrl_if = play_ctrl_if,
|
|
|
+ .gpio_if = play_gpio_if,
|
|
|
+ .pa_pin = GPIO_PWR_CTRL,
|
|
|
+ .use_mclk = false,
|
|
|
+ };
|
|
|
+ play_codec_if = es8311_codec_new(&es8311_cfg);
|
|
|
+ // New output codec device
|
|
|
+ esp_codec_dev_cfg_t dev_cfg = {
|
|
|
+ .codec_if = play_codec_if,
|
|
|
+ .data_if = play_data_if,
|
|
|
+ .dev_type = ESP_CODEC_DEV_TYPE_OUT,
|
|
|
+ };
|
|
|
+ play_dev = esp_codec_dev_new(&dev_cfg);
|
|
|
+
|
|
|
+ esp_codec_dev_sample_info_t fs = {
|
|
|
+ .bits_per_sample = bits_per_chan,
|
|
|
+ .sample_rate = sample_rate,
|
|
|
+ .channel = channel_format,
|
|
|
+ };
|
|
|
+ esp_codec_dev_set_out_vol(play_dev, PLAYER_VOLUME);
|
|
|
+ esp_codec_dev_open(play_dev, &fs);
|
|
|
+
|
|
|
+ return ret_val;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_audio_set_play_vol(int volume)
|
|
|
+{
|
|
|
+ if (!play_dev) {
|
|
|
+ ESP_LOGE(TAG, "DAC codec init fail");
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
+ esp_codec_dev_set_out_vol(play_dev, volume);
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_audio_get_play_vol(int *volume)
|
|
|
+{
|
|
|
+ if (!play_dev) {
|
|
|
+ ESP_LOGE(TAG, "DAC codec init fail");
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
+ esp_codec_dev_get_out_vol(play_dev, volume);
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+// static esp_err_t bsp_i2s_init(i2s_port_t i2s_num, uint32_t sample_rate, i2s_channel_fmt_t channel_format, i2s_bits_per_chan_t bits_per_chan)
|
|
|
+static esp_err_t bsp_i2s_init(int i2s_num, uint32_t sample_rate, int channel_format, int bits_per_chan)
|
|
|
+{
|
|
|
+ esp_err_t ret_val = ESP_OK;
|
|
|
+
|
|
|
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
|
+ if (channel_format == 1) {
|
|
|
+ } else if (channel_format == 2) {
|
|
|
+ } else {
|
|
|
+ ESP_LOGE(TAG, "Unable to configure channel_format %d", channel_format);
|
|
|
+ channel_format = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bits_per_chan != 16 && bits_per_chan != 32) {
|
|
|
+ ESP_LOGE(TAG, "Unable to configure bits_per_chan %d", bits_per_chan);
|
|
|
+ bits_per_chan = 32;
|
|
|
+ }
|
|
|
+
|
|
|
+ i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(i2s_num, I2S_ROLE_MASTER);
|
|
|
+ ret_val |= i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle);
|
|
|
+ i2s_std_config_t std_cfg = I2S_CONFIG_DEFAULT(sample_rate, I2S_SLOT_MODE_STEREO, bits_per_chan);
|
|
|
+ ret_val |= i2s_channel_init_std_mode(tx_handle, &std_cfg);
|
|
|
+ ret_val |= i2s_channel_init_std_mode(rx_handle, &std_cfg);
|
|
|
+ ret_val |= i2s_channel_enable(tx_handle);
|
|
|
+ ret_val |= i2s_channel_enable(rx_handle);
|
|
|
+#else
|
|
|
+ i2s_channel_fmt_t channel_fmt = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
|
|
+ if (channel_format == 1) {
|
|
|
+ channel_fmt = I2S_CHANNEL_FMT_ONLY_LEFT;
|
|
|
+ } else if (channel_format == 2) {
|
|
|
+ channel_fmt = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
|
|
+ } else {
|
|
|
+ ESP_LOGE(TAG, "Unable to configure channel_format %d", channel_format);
|
|
|
+ channel_format = 1;
|
|
|
+ channel_fmt = I2S_CHANNEL_FMT_ONLY_LEFT;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bits_per_chan != 16 && bits_per_chan != 32) {
|
|
|
+ ESP_LOGE(TAG, "Unable to configure bits_per_chan %d", bits_per_chan);
|
|
|
+ bits_per_chan = 16;
|
|
|
+ }
|
|
|
+
|
|
|
+ i2s_config_t i2s_config = I2S_CONFIG_DEFAULT(sample_rate, channel_fmt, bits_per_chan);
|
|
|
+
|
|
|
+ i2s_pin_config_t pin_config = {
|
|
|
+ .bck_io_num = GPIO_I2S_SCLK,
|
|
|
+ .ws_io_num = GPIO_I2S_LRCK,
|
|
|
+ .data_out_num = GPIO_I2S_DOUT,
|
|
|
+ .data_in_num = GPIO_I2S_SDIN,
|
|
|
+ .mck_io_num = GPIO_I2S_MCLK,
|
|
|
+ };
|
|
|
+
|
|
|
+ ret_val |= i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
|
|
|
+ ret_val |= i2s_set_pin(i2s_num, &pin_config);
|
|
|
+#endif
|
|
|
+
|
|
|
+ return ret_val;
|
|
|
+}
|
|
|
+
|
|
|
+static esp_err_t bsp_codec_init(
|
|
|
+ int adc_sample_rate,
|
|
|
+ int dac_sample_rate,
|
|
|
+ int dac_channel_format,
|
|
|
+ int dac_bits_per_chan)
|
|
|
+{
|
|
|
+ //ESP_LOGE(TAG, "bsp_codec_adc_init START");
|
|
|
+ esp_err_t ret;
|
|
|
+
|
|
|
+ ret = bsp_codec_adc_init(adc_sample_rate);
|
|
|
+
|
|
|
+ if(ret != ESP_OK)
|
|
|
+ {
|
|
|
+ ESP_LOGE(TAG,"ADC init failed: %s",
|
|
|
+ esp_err_to_name(ret));
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ ret = bsp_codec_dac_init(
|
|
|
+ dac_sample_rate,
|
|
|
+ dac_channel_format,
|
|
|
+ dac_bits_per_chan);
|
|
|
+
|
|
|
+
|
|
|
+ if(ret != ESP_OK)
|
|
|
+ {
|
|
|
+ ESP_LOGE(TAG,"DAC init failed: %s",
|
|
|
+ esp_err_to_name(ret));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_audio_play(const int16_t* data, int length, uint32_t ticks_to_wait)
|
|
|
+{
|
|
|
+ esp_err_t ret = ESP_OK;
|
|
|
+ if (!play_dev) {
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ int out_length= length;
|
|
|
+ int audio_time = 1;
|
|
|
+ audio_time *= (16000 / s_play_sample_rate);
|
|
|
+ audio_time *= (2 / s_play_channel_format);
|
|
|
+
|
|
|
+ int *data_out = NULL;
|
|
|
+ if (s_bits_per_chan != 32) {
|
|
|
+ out_length = length * 2;
|
|
|
+ data_out = malloc(out_length);
|
|
|
+ for (int i = 0; i < length / sizeof(int16_t); i++) {
|
|
|
+ int ret = data[i];
|
|
|
+ data_out[i] = ret << 16;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int *data_out_1 = NULL;
|
|
|
+ if (s_play_channel_format != 2 || s_play_sample_rate != 16000) {
|
|
|
+ out_length *= audio_time;
|
|
|
+ data_out_1 = malloc(out_length);
|
|
|
+ int *tmp_data = NULL;
|
|
|
+ if (data_out != NULL) {
|
|
|
+ tmp_data = data_out;
|
|
|
+ } else {
|
|
|
+ tmp_data = (int *)data;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < out_length / (audio_time * sizeof(int)); i++) {
|
|
|
+ for (int j = 0; j < audio_time; j++) {
|
|
|
+ data_out_1[audio_time * i + j] = tmp_data[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (data_out != NULL) {
|
|
|
+ free(data_out);
|
|
|
+ data_out = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data_out != NULL) {
|
|
|
+ ret = esp_codec_dev_write(play_dev, (void *)data_out, out_length);
|
|
|
+ free(data_out);
|
|
|
+ } else if (data_out_1 != NULL) {
|
|
|
+ ret = esp_codec_dev_write(play_dev, (void *)data_out_1, out_length);
|
|
|
+ free(data_out_1);
|
|
|
+ } else {
|
|
|
+ ret = esp_codec_dev_write(play_dev, (void *)data, length);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+int get_mic_level (void) {
|
|
|
+
|
|
|
+ return mic_level;
|
|
|
+}
|
|
|
+esp_err_t esp_get_feed_data(
|
|
|
+ bool is_get_raw_channel,
|
|
|
+ int16_t *buffer,
|
|
|
+ int buffer_len)
|
|
|
+{
|
|
|
+
|
|
|
+ ESP_LOGE(TAG, "NEW esp_get_feed_data CALLED");
|
|
|
+
|
|
|
+ if(record_dev == NULL)
|
|
|
+ {
|
|
|
+ ESP_LOGE(TAG,"record_dev NULL");
|
|
|
+ return ESP_ERR_INVALID_STATE;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ ESP_LOGI(TAG,
|
|
|
+ "read buffer=%p size=%d",
|
|
|
+ buffer,
|
|
|
+ buffer_len);
|
|
|
+
|
|
|
+
|
|
|
+ esp_err_t ret;
|
|
|
+
|
|
|
+ ret = esp_codec_dev_read(
|
|
|
+ record_dev,
|
|
|
+ buffer,
|
|
|
+ buffer_len);
|
|
|
+
|
|
|
+
|
|
|
+ ESP_LOGI(TAG,
|
|
|
+ "codec read ret=%s",
|
|
|
+ esp_err_to_name(ret));
|
|
|
+
|
|
|
+
|
|
|
+ if(ret != ESP_OK)
|
|
|
+ {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // Check microphone data
|
|
|
+ int64_t sum = 0;
|
|
|
+
|
|
|
+ for(int i=0;i<buffer_len/sizeof(int16_t);i++)
|
|
|
+ {
|
|
|
+ sum += abs(buffer[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ ESP_LOGI(TAG,
|
|
|
+ "MIC level=%lld",
|
|
|
+ sum/(buffer_len/sizeof(int16_t)));
|
|
|
+
|
|
|
+
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+int esp_get_feed_channel(void)
|
|
|
+{
|
|
|
+ return ADC_I2S_CHANNEL;
|
|
|
+}
|
|
|
+
|
|
|
+char* esp_get_input_format(void)
|
|
|
+{
|
|
|
+ return "RM";
|
|
|
+}
|
|
|
+
|
|
|
+static void bsp_volume_buttons_task(void *arg)
|
|
|
+{
|
|
|
+ int last_key1 = 1;
|
|
|
+ int last_key3 = 1;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ int key1 = gpio_get_level(GPIO_KEY1_PIN);
|
|
|
+ int key3 = gpio_get_level(GPIO_KEY3_PIN);
|
|
|
+
|
|
|
+ if (key1 == 0 && last_key1 != 0) {
|
|
|
+ int current_vol = 0;
|
|
|
+ if (esp_audio_get_play_vol(¤t_vol) == ESP_OK) {
|
|
|
+ current_vol += VOLUME_STEP;
|
|
|
+ if (current_vol > VOLUME_MAX_LEVEL) {
|
|
|
+ current_vol = VOLUME_MAX_LEVEL;
|
|
|
+ }
|
|
|
+ if (esp_audio_set_play_vol(current_vol) == ESP_OK) {
|
|
|
+ ESP_LOGI(TAG, "Volume up -> %d", current_vol);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (key3 == 0 && last_key3 != 0) {
|
|
|
+ int current_vol = 0;
|
|
|
+ if (esp_audio_get_play_vol(¤t_vol) == ESP_OK) {
|
|
|
+ current_vol -= VOLUME_STEP;
|
|
|
+ if (current_vol < VOLUME_MIN_LEVEL) {
|
|
|
+ current_vol = VOLUME_MIN_LEVEL;
|
|
|
+ }
|
|
|
+ if (esp_audio_set_play_vol(current_vol) == ESP_OK) {
|
|
|
+ ESP_LOGI(TAG, "Volume down -> %d", current_vol);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ last_key1 = key1;
|
|
|
+ last_key3 = key3;
|
|
|
+ vTaskDelay(pdMS_TO_TICKS(VOLUME_BUTTON_POLL_MS));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static esp_err_t bsp_volume_buttons_init(void)
|
|
|
+{
|
|
|
+ if (s_volume_buttons_task_started) {
|
|
|
+ return ESP_OK;
|
|
|
+ }
|
|
|
+
|
|
|
+ gpio_config_t io_conf = {
|
|
|
+ .pin_bit_mask = (1ULL << GPIO_KEY1_PIN) | (1ULL << GPIO_KEY3_PIN),
|
|
|
+ .mode = GPIO_MODE_INPUT,
|
|
|
+ .pull_up_en = GPIO_PULLUP_ENABLE,
|
|
|
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
|
+ .intr_type = GPIO_INTR_DISABLE,
|
|
|
+ };
|
|
|
+
|
|
|
+ esp_err_t ret = gpio_config(&io_conf);
|
|
|
+ if (ret != ESP_OK) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ BaseType_t task_created = xTaskCreate(bsp_volume_buttons_task, "vol_btns", 4096, NULL, 5, NULL);
|
|
|
+ if (task_created != pdPASS) {
|
|
|
+ return ESP_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ s_volume_buttons_task_started = true;
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_board_init(uint32_t sample_rate, int channel_format, int bits_per_chan)
|
|
|
+{
|
|
|
+ /*!< Initialize I2C bus, used for audio codec*/
|
|
|
+
|
|
|
+ i2c_master_init();
|
|
|
+ s_play_sample_rate = sample_rate;
|
|
|
+
|
|
|
+ if (channel_format != 2 && channel_format != 1) {
|
|
|
+ ESP_LOGE(TAG, "Unable to configure channel_format");
|
|
|
+ channel_format = 2;
|
|
|
+ }
|
|
|
+ s_play_channel_format = channel_format;
|
|
|
+
|
|
|
+ if (bits_per_chan != 32 && bits_per_chan != 16) {
|
|
|
+ ESP_LOGE(TAG, "Unable to configure bits_per_chan");
|
|
|
+ bits_per_chan = 32;
|
|
|
+ }
|
|
|
+ s_bits_per_chan = bits_per_chan;
|
|
|
+
|
|
|
+ bsp_i2s_init(I2S_NUM_1, 16000, 2, 16);
|
|
|
+ // Because record and play use the same i2s.
|
|
|
+esp_err_t codec_ret;
|
|
|
+
|
|
|
+codec_ret = bsp_codec_init(16000,16000,2,16);
|
|
|
+
|
|
|
+if(codec_ret != ESP_OK)
|
|
|
+{
|
|
|
+ ESP_LOGE(TAG,
|
|
|
+ "bsp_codec_init failed: %s",
|
|
|
+ esp_err_to_name(codec_ret));
|
|
|
+}
|
|
|
+else
|
|
|
+{
|
|
|
+ ESP_LOGI(TAG,"bsp_codec_init OK");
|
|
|
+ esp_err_t aec_ret = esp_aec_init(160);
|
|
|
+ if (aec_ret != ESP_OK) {
|
|
|
+ ESP_LOGW(TAG, "AEC init failed: %s", esp_err_to_name(aec_ret));
|
|
|
+ }
|
|
|
+}
|
|
|
+ if (GPIO_PWR_CTRL >= 0) {
|
|
|
+ int pa_pin = GPIO_PWR_CTRL;
|
|
|
+ uint64_t pa_mask = 0;
|
|
|
+ if (pa_pin < 64) {
|
|
|
+ pa_mask = (1ULL << pa_pin);
|
|
|
+ }
|
|
|
+
|
|
|
+ gpio_config_t io_conf = {
|
|
|
+ .pin_bit_mask = pa_mask,
|
|
|
+ .mode = GPIO_MODE_OUTPUT,
|
|
|
+ .pull_up_en = GPIO_PULLUP_DISABLE,
|
|
|
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
|
+ .intr_type = GPIO_INTR_DISABLE,
|
|
|
+ };
|
|
|
+ esp_err_t gpio_ret = gpio_config(&io_conf);
|
|
|
+ if (gpio_ret == ESP_OK) {
|
|
|
+ gpio_set_level(GPIO_PWR_CTRL, GPIO_PWR_ON_LEVEL);
|
|
|
+ ESP_LOGI(TAG, "Audio PA GPIO enabled on pin %d", GPIO_PWR_CTRL);
|
|
|
+ } else {
|
|
|
+ ESP_LOGW(TAG, "Audio PA GPIO config failed: %s", esp_err_to_name(gpio_ret));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ESP_LOGW(TAG, "GPIO_PWR_CTRL is invalid, using IO expander for audio power");
|
|
|
+ }
|
|
|
+
|
|
|
+ esp_err_t button_ret = bsp_volume_buttons_init();
|
|
|
+ if (button_ret != ESP_OK) {
|
|
|
+ ESP_LOGW(TAG, "Failed to initialize volume buttons: %s", esp_err_to_name(button_ret));
|
|
|
+ }
|
|
|
+
|
|
|
+ return ESP_OK;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_sdcard_init(char *mount_point, size_t max_files)
|
|
|
+{
|
|
|
+ if (NULL != card) {
|
|
|
+ return ESP_ERR_INVALID_STATE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check if SD crad is supported */
|
|
|
+ if (!FUNC_SDMMC_EN && !FUNC_SDSPI_EN) {
|
|
|
+ ESP_LOGE(TAG, "SDMMC and SDSPI not supported on this board!");
|
|
|
+ return ESP_ERR_NOT_SUPPORTED;
|
|
|
+ }
|
|
|
+
|
|
|
+ esp_err_t ret_val = ESP_OK;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @brief Options for mounting the filesystem.
|
|
|
+ * If format_if_mount_failed is set to true, SD card will be partitioned and
|
|
|
+ * formatted in case when mounting fails.
|
|
|
+ *
|
|
|
+ */
|
|
|
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
|
|
+ .format_if_mount_failed = false,
|
|
|
+ .max_files = max_files,
|
|
|
+ .allocation_unit_size = 16 * 1024
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @brief Use settings defined above to initialize SD card and mount FAT filesystem.
|
|
|
+ * Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
|
|
|
+ * Please check its source code and implement error recovery when developing
|
|
|
+ * production applications.
|
|
|
+ *
|
|
|
+ */
|
|
|
+ sdmmc_host_t host =
|
|
|
+#if FUNC_SDMMC_EN
|
|
|
+ SDMMC_HOST_DEFAULT();
|
|
|
+#else
|
|
|
+ SDSPI_HOST_DEFAULT();
|
|
|
+ spi_bus_config_t bus_cfg = {
|
|
|
+ .mosi_io_num = GPIO_SDSPI_MOSI,
|
|
|
+ .miso_io_num = GPIO_SDSPI_MISO,
|
|
|
+ .sclk_io_num = GPIO_SDSPI_SCLK,
|
|
|
+ .quadwp_io_num = GPIO_NUM_NC,
|
|
|
+ .quadhd_io_num = GPIO_NUM_NC,
|
|
|
+ .max_transfer_sz = 4000,
|
|
|
+ };
|
|
|
+ ret_val = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CH_AUTO);
|
|
|
+ if (ret_val != ESP_OK) {
|
|
|
+ ESP_LOGE(TAG, "Failed to initialize bus.");
|
|
|
+ return ret_val;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @brief This initializes the slot without card detect (CD) and write protect (WP) signals.
|
|
|
+ * Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
|
|
|
+ *
|
|
|
+ */
|
|
|
+#if FUNC_SDMMC_EN
|
|
|
+ sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
|
|
+#else
|
|
|
+ sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
|
|
+#endif
|
|
|
+
|
|
|
+#if FUNC_SDMMC_EN
|
|
|
+ /* Config SD data width. 0, 4 or 8. Currently for SD card, 8 bit is not supported. */
|
|
|
+ slot_config.width = SDMMC_BUS_WIDTH;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @brief On chips where the GPIOs used for SD card can be configured, set them in
|
|
|
+ * the slot_config structure.
|
|
|
+ *
|
|
|
+ */
|
|
|
+#if SOC_SDMMC_USE_GPIO_MATRIX
|
|
|
+ slot_config.clk = GPIO_SDMMC_CLK;
|
|
|
+ slot_config.cmd = GPIO_SDMMC_CMD;
|
|
|
+ slot_config.d0 = GPIO_SDMMC_D0;
|
|
|
+ slot_config.d1 = GPIO_SDMMC_D1;
|
|
|
+ slot_config.d2 = GPIO_SDMMC_D2;
|
|
|
+ slot_config.d3 = GPIO_SDMMC_D3;
|
|
|
+#endif
|
|
|
+ slot_config.cd = GPIO_SDMMC_DET;
|
|
|
+ slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
|
|
|
+#else
|
|
|
+ slot_config.gpio_cs = GPIO_SDSPI_CS;
|
|
|
+ slot_config.host_id = host.slot;
|
|
|
+#endif
|
|
|
+ /**
|
|
|
+ * @brief Enable internal pullups on enabled pins. The internal pullups
|
|
|
+ * are insufficient however, please make sure 10k external pullups are
|
|
|
+ * connected on the bus. This is for debug / example purpose only.
|
|
|
+ */
|
|
|
+
|
|
|
+ /* get FAT filesystem on SD card registered in VFS. */
|
|
|
+ ret_val =
|
|
|
+#if FUNC_SDMMC_EN
|
|
|
+ esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
|
|
|
+#else
|
|
|
+ esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* Check for SDMMC mount result. */
|
|
|
+ if (ret_val != ESP_OK) {
|
|
|
+ if (ret_val == ESP_FAIL) {
|
|
|
+ ESP_LOGE(TAG, "Failed to mount filesystem. "
|
|
|
+ "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
|
|
|
+ } else {
|
|
|
+ ESP_LOGE(TAG, "Failed to initialize the card (%s). "
|
|
|
+ "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret_val));
|
|
|
+ }
|
|
|
+ return ret_val;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Card has been initialized, print its properties. */
|
|
|
+ sdmmc_card_print_info(stdout, card);
|
|
|
+ SDCard_Size = ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024);
|
|
|
+ return ret_val;
|
|
|
+}
|
|
|
+
|
|
|
+esp_err_t esp_sdcard_deinit(char *mount_point)
|
|
|
+{
|
|
|
+ if (NULL == mount_point) {
|
|
|
+ return ESP_ERR_INVALID_STATE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Unmount an SD card from the FAT filesystem and release resources acquired */
|
|
|
+ esp_err_t ret_val = esp_vfs_fat_sdcard_unmount(mount_point, card);
|
|
|
+
|
|
|
+ /* Make SD/MMC card information structure pointer NULL */
|
|
|
+ card = NULL;
|
|
|
+
|
|
|
+ return ret_val;
|
|
|
+}
|
|
|
+
|
|
|
+#define MOUNT_POINT "/sdcard"
|
|
|
+
|
|
|
+static const char *SD_TAG = "SD";
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][MAX_FILE_NAME_SIZE], uint16_t maxFiles)
|
|
|
+{
|
|
|
+ DIR *dir = opendir(directory);
|
|
|
+ if (dir == NULL) {
|
|
|
+ ESP_LOGE(SD_TAG, "Path: <%s> does not exist", directory);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ uint16_t fileCount = 0;
|
|
|
+ struct dirent *entry;
|
|
|
+
|
|
|
+ while ((entry = readdir(dir)) != NULL && fileCount < maxFiles) {
|
|
|
+
|
|
|
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const char *dot = strrchr(entry->d_name, '.');
|
|
|
+ if (dot != NULL && dot != entry->d_name) {
|
|
|
+
|
|
|
+ if (strcasecmp(dot, fileExtension) == 0) {
|
|
|
+ strncpy(File_Name[fileCount], entry->d_name, MAX_FILE_NAME_SIZE - 1);
|
|
|
+ File_Name[fileCount][MAX_FILE_NAME_SIZE - 1] = '\0';
|
|
|
+
|
|
|
+ char filePath[MAX_PATH_SIZE];
|
|
|
+ snprintf(filePath, MAX_PATH_SIZE, "%s/%s", directory, entry->d_name);
|
|
|
+
|
|
|
+ printf("File found: %s\r\n", filePath);
|
|
|
+ fileCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else{
|
|
|
+
|
|
|
+ // printf("No extension found for file: %s\r\n", entry->d_name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ closedir(dir);
|
|
|
+
|
|
|
+ if (fileCount > 0) {
|
|
|
+ ESP_LOGI(SD_TAG, "Retrieved %d files with extension '%s'", fileCount, fileExtension);
|
|
|
+ } else {
|
|
|
+ ESP_LOGW(SD_TAG, "No files with extension '%s' found in directory: %s", fileExtension, directory);
|
|
|
+ }
|
|
|
+
|
|
|
+ return fileCount;
|
|
|
+}
|