Record files. but 40s to 20s
This commit is contained in:
parent
bda0fd6096
commit
157336a955
|
|
@ -40,6 +40,12 @@
|
|||
"gpio.h": "c",
|
||||
"string.h": "c",
|
||||
"i2s_reg.h": "c",
|
||||
"complex": "c"
|
||||
"complex": "c",
|
||||
"errno.h": "c",
|
||||
"freertos.h": "c",
|
||||
"stdbool.h": "c",
|
||||
"esp_afe_sr_models.h": "c",
|
||||
"esp_afe_config.h": "c",
|
||||
"esp_afe_sr_iface.h": "c"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
idf_component_register(SRCS "main.c"
|
||||
idf_component_register(SRCS "main.c" "app_sr.c" "usb_msc.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
|
@ -0,0 +1,383 @@
|
|||
|
||||
#include "app_sr.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
// #include "app_ui.h"
|
||||
// #include "audio_player.h"
|
||||
// #include "esp32_s3_szp.h"
|
||||
|
||||
#include "esp_afe_sr_iface.h"
|
||||
#include "esp_afe_sr_models.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mn_iface.h"
|
||||
#include "esp_mn_models.h"
|
||||
#include "esp_mn_speech_commands.h"
|
||||
#include "esp_process_sdkconfig.h"
|
||||
#include "esp_wn_iface.h"
|
||||
#include "esp_wn_models.h"
|
||||
#include "format_wav.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "model_path.h"
|
||||
|
||||
static const char* TAG = "app_sr";
|
||||
|
||||
srmodel_list_t* models = NULL;
|
||||
static esp_afe_sr_iface_t* afe_handle = NULL;
|
||||
static esp_afe_sr_data_t* afe_data = NULL;
|
||||
|
||||
int detect_flag = 0;
|
||||
static volatile int task_flag = 0;
|
||||
|
||||
FILE* f1;
|
||||
FILE* f2;
|
||||
size_t wav_written1;
|
||||
size_t wav_written2;
|
||||
|
||||
esp_err_t record_start1() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
const wav_header_t wav_header1 =
|
||||
WAV_HEADER_PCM_DEFAULT(16000 * 2 * 16 / 8 * 20, 16, 16000, 2);
|
||||
|
||||
ESP_LOGI(TAG, "Opening file %s", "/sdcard/aaaaa.wav");
|
||||
f1 = fopen("/sdcard/aaaaa.wav", "w");
|
||||
ESP_LOGI(TAG, "fopen error: %s", strerror(errno));
|
||||
if (f1 == ESP_FAIL) ESP_LOGI(TAG, "error while opening wav file");
|
||||
if (fwrite(&wav_header1, sizeof(wav_header_t), 1, f1) == 1)
|
||||
ESP_LOGI(TAG, "error while writing wav header");
|
||||
|
||||
wav_written1 = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t record_start2() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
const wav_header_t wav_header2 =
|
||||
WAV_HEADER_PCM_DEFAULT(16000 * 1 * 16 / 8 * 20, 16, 16000, 1);
|
||||
|
||||
f2 = fopen("/sdcard/bbbbb.wav", "w");
|
||||
ESP_LOGI(TAG, "fopen error: %s", strerror(errno));
|
||||
if (f2 == ESP_FAIL) ESP_LOGI(TAG, "error while opening wav file");
|
||||
if (fwrite(&wav_header2, sizeof(wav_header_t), 1, f2) == 1)
|
||||
ESP_LOGI(TAG, "error while writing wav header");
|
||||
|
||||
wav_written2 = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t record_add1(int16_t* data1, uint32_t size) {
|
||||
uint32_t byte_rate = 16000 * 2 * 16 / 8;
|
||||
uint32_t wav_size = byte_rate * 20;
|
||||
|
||||
if (wav_written1 < wav_size) {
|
||||
fwrite(data1, size, 1, f1);
|
||||
// ESP_RETURN_ON_FALSE(fwrite(data1, size, 1, f1), ESP_FAIL, TAG,
|
||||
// "error while writing samples to wav file");
|
||||
wav_written1 += size;
|
||||
return ESP_OK;
|
||||
} else {
|
||||
if (wav_written1 < 0xFFFFFFFF) {
|
||||
ESP_LOGI(TAG, "Recording done 1! Flushing file buffer");
|
||||
fclose(f1);
|
||||
wav_written1 = 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool record_end1() { return wav_written1 == 0xFFFFFFFF; }
|
||||
bool record_end2() { return wav_written2 == 0xFFFFFFFF; }
|
||||
|
||||
esp_err_t record_add2(int16_t* data2, uint32_t size) {
|
||||
uint32_t byte_rate = 16000 * 1 * 16 / 8;
|
||||
uint32_t wav_size = byte_rate * 20;
|
||||
|
||||
if (wav_written2 < wav_size) {
|
||||
fwrite(data2, size, 1, f2);
|
||||
// ESP_RETURN_ON_FALSE(fwrite(data2, size, 1, f2), ESP_FAIL, TAG,
|
||||
// "error while writing samples to wav file");
|
||||
wav_written2 += size;
|
||||
return ESP_OK;
|
||||
} else {
|
||||
if (wav_written2 < 0xFFFFFFFF) {
|
||||
ESP_LOGI(TAG, "Recording done 2! Flushing file buffer");
|
||||
fclose(f2);
|
||||
wav_written2 = 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void afe(esp_afe_sr_data_t* afe_data, int16_t* i2s_buff) {
|
||||
afe_handle->feed(afe_data, i2s_buff); // 把获取到的I2S数据输入给afe_data
|
||||
afe_fetch_result_t* res = afe_handle->fetch(afe_data);
|
||||
}
|
||||
|
||||
void feed_Task(void* arg) {
|
||||
esp_afe_sr_data_t* afe_data = arg; // 获取参数
|
||||
int audio_chunksize = afe_handle->get_feed_chunksize(afe_data); // 获取帧长度
|
||||
int nch = afe_handle->get_channel_num(afe_data); // 获取声道数
|
||||
// int feed_channel = bsp_get_feed_channel(); // 获取ADC输入通道数
|
||||
int feed_channel = 2;
|
||||
assert(nch <= feed_channel);
|
||||
int16_t* i2s_buff = heap_caps_malloc(
|
||||
audio_chunksize * sizeof(int16_t) * feed_channel,
|
||||
MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); // 分配获取I2S数据的缓存大小
|
||||
assert(i2s_buff);
|
||||
|
||||
printf("AFE audio_chunksize : %d\n", audio_chunksize);
|
||||
printf("AFE nch: %d\n", nch);
|
||||
printf("AFE feed_channel: %d\n", feed_channel);
|
||||
|
||||
while (task_flag) {
|
||||
// bsp_get_feed_data(
|
||||
// false, i2s_buff,
|
||||
// audio_chunksize * sizeof(int16_t) * feed_channel); // 获取I2S数据
|
||||
record_add1(i2s_buff, audio_chunksize * sizeof(int16_t) * 2);
|
||||
|
||||
afe_handle->feed(afe_data, i2s_buff); // 把获取到的I2S数据输入给afe_data
|
||||
}
|
||||
if (i2s_buff) {
|
||||
free(i2s_buff);
|
||||
i2s_buff = NULL;
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void detect_Task(void* arg) {
|
||||
esp_afe_sr_data_t* afe_data = arg; // 接收参数
|
||||
int afe_chunksize =
|
||||
afe_handle->get_fetch_chunksize(afe_data); // 获取fetch帧长度
|
||||
char* mn_name = esp_srmodel_filter(models, ESP_MN_PREFIX,
|
||||
ESP_MN_CHINESE); // 初始化命令词模型
|
||||
printf("multinet:%s\n", mn_name); // 打印命令词模型名称
|
||||
esp_mn_iface_t* multinet = esp_mn_handle_from_name(mn_name);
|
||||
model_iface_data_t* model_data =
|
||||
multinet->create(mn_name, 6000); // 设置唤醒后等待事件 6000代表6000毫秒
|
||||
esp_mn_commands_clear(); // 清除当前的命令词列表
|
||||
esp_mn_commands_add(1, "bo fang yin yue"); // 播放音乐
|
||||
esp_mn_commands_add(2, "zan ting"); // 暂停
|
||||
esp_mn_commands_add(3, "ji xu"); // 继续
|
||||
esp_mn_commands_add(4, "shang yi shou"); // 上一首
|
||||
esp_mn_commands_add(5, "xia yi shou"); // 下一首
|
||||
esp_mn_commands_add(6, "sheng yin da yi dian"); // 声音大一点
|
||||
esp_mn_commands_add(7, "sheng yin xiao yi dian"); // 声音小一点
|
||||
esp_mn_commands_update(); // 更新命令词
|
||||
int mu_chunksize =
|
||||
multinet->get_samp_chunksize(model_data); // 获取samp帧长度
|
||||
assert(mu_chunksize == afe_chunksize);
|
||||
|
||||
// 打印所有的命令
|
||||
multinet->print_active_speech_commands(model_data);
|
||||
printf("------------detect start------------\n");
|
||||
|
||||
while (task_flag) {
|
||||
afe_fetch_result_t* res = afe_handle->fetch(afe_data); // 获取模型输出结果
|
||||
|
||||
record_add2(res->data, res->data_size);
|
||||
|
||||
// int16_t *data; // the data of audio.
|
||||
// int data_size; // the size of data. The unit is
|
||||
// byte. float data_volume; // the volume of input
|
||||
// audio, the unit is decibel(dB). This value is calculated before agc.
|
||||
// (note: invalid in vc).
|
||||
// // if enable wakenet, the window
|
||||
// length is the receptive fields of
|
||||
// wakenet(about 1.5s), otherwise is
|
||||
// the frame length.
|
||||
// wakenet_state_t wakeup_state; // the value is wakenet_state_t
|
||||
// int wake_word_index; // if the wake word is detected.
|
||||
// It will store the wake word index which start from 1. int
|
||||
// wakenet_model_index; // if there are multiple wakenets,
|
||||
// this value identifies which model be wakes up. Index start from 1.
|
||||
// afe_vad_state_t vad_state; // the value is afe_vad_state_t
|
||||
// int trigger_channel_id; // the channel index of output
|
||||
// int wake_word_length; // the length of wake word. It's
|
||||
// unit is the number of samples. int ret_value; //
|
||||
// the return state of fetch function void* reserved; // reserved for future
|
||||
// use
|
||||
|
||||
if (res->vad_state > 0) {
|
||||
printf("AFE data_size: %d\n", res->data_size);
|
||||
printf("AFE data_volume: %f\n", res->data_volume);
|
||||
printf("AFE vad_state: %d\n", res->vad_state);
|
||||
printf("AFE ret_value: %d\n", res->ret_value);
|
||||
}
|
||||
|
||||
if (!res || res->ret_value == ESP_FAIL) {
|
||||
printf("fetch error!\n");
|
||||
break;
|
||||
}
|
||||
if (res->wakeup_state == WAKENET_DETECTED) {
|
||||
printf("WAKEWORD DETECTED\n");
|
||||
multinet->clean(model_data); // clean all status of multinet
|
||||
} else if (res->wakeup_state == WAKENET_CHANNEL_VERIFIED) { // 检测到唤醒词
|
||||
// play_voice = -1;
|
||||
afe_handle->disable_wakenet(afe_data); // 关闭唤醒词识别
|
||||
detect_flag = 1; // 标记已检测到唤醒词
|
||||
printf("AFE_FETCH_CHANNEL_VERIFIED, channel index: %d\n",
|
||||
res->trigger_channel_id);
|
||||
}
|
||||
|
||||
if (detect_flag == 1) {
|
||||
esp_mn_state_t mn_state =
|
||||
multinet->detect(model_data, res->data); // 检测命令词
|
||||
|
||||
if (mn_state == ESP_MN_STATE_DETECTING) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mn_state == ESP_MN_STATE_DETECTED) { // 已检测到命令词
|
||||
esp_mn_results_t* mn_result =
|
||||
multinet->get_results(model_data); // 获取检测词结果
|
||||
for (int i = 0; i < mn_result->num; i++) { // 打印获取到的命令词
|
||||
printf("TOP %d, command_id: %d, phrase_id: %d, string:%s prob: %f\n",
|
||||
i + 1, mn_result->command_id[i], mn_result->phrase_id[i],
|
||||
mn_result->string, mn_result->prob[i]);
|
||||
}
|
||||
// 根据命令词 执行相应动作
|
||||
// switch (mn_result->command_id[0]) {
|
||||
// case 1: // bo fang yin yue 播放音乐
|
||||
// ai_play();
|
||||
// break;
|
||||
// case 2: // zan ting 暂停
|
||||
// ai_pause();
|
||||
// break;
|
||||
// case 3: // ji xu 继续
|
||||
// ai_resume();
|
||||
// break;
|
||||
// case 4: // shang yi shou 上一首
|
||||
// ai_prev_music();
|
||||
// break;
|
||||
// case 5: // xia yi shou 下一首
|
||||
// ai_next_music();
|
||||
// break;
|
||||
// case 6: // sheng yin da yi dian 声音大一点
|
||||
// ai_volume_up();
|
||||
// break;
|
||||
// case 7: // sheng yin xiao yi dian 声音小一点
|
||||
// ai_volume_down();
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
printf("\n-----------listening-----------\n");
|
||||
}
|
||||
|
||||
if (mn_state == ESP_MN_STATE_TIMEOUT) { // 达到最大检测命令词时间
|
||||
esp_mn_results_t* mn_result = multinet->get_results(model_data);
|
||||
printf("timeout, string:%s\n", mn_result->string);
|
||||
afe_handle->enable_wakenet(afe_data); // 重新打开唤醒词识别
|
||||
detect_flag = 0; // 清除标记
|
||||
printf("\n-----------awaits to be waken up-----------\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (model_data) {
|
||||
multinet->destroy(model_data);
|
||||
model_data = NULL;
|
||||
}
|
||||
printf("detect exit\n");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_afe_sr_data_t* sr_init() {
|
||||
// 获取模型 名称“model”和分区表中装载模型的名称一致
|
||||
models = esp_srmodel_init("model");
|
||||
|
||||
afe_handle = (esp_afe_sr_iface_t*)&ESP_AFE_SR_HANDLE;
|
||||
// 先配置afe句柄
|
||||
// 随后才可以调用afe接口
|
||||
// afe_config_t afe_config = AFE_CONFIG_DEFAULT(); // 配置afe
|
||||
afe_config_t afe_config = {
|
||||
.aec_init = true,
|
||||
.se_init = true,
|
||||
.vad_init = true,
|
||||
.wakenet_init = true,
|
||||
.voice_communication_init = false,
|
||||
.voice_communication_agc_init = false,
|
||||
.voice_communication_agc_gain = 15,
|
||||
.vad_mode = VAD_MODE_3,
|
||||
.wakenet_model_name = NULL,
|
||||
.wakenet_model_name_2 = NULL,
|
||||
.wakenet_mode = DET_MODE_2CH_90,
|
||||
.afe_mode = SR_MODE_LOW_COST,
|
||||
.afe_perferred_core = 0,
|
||||
.afe_perferred_priority = 5,
|
||||
.afe_ringbuf_size = 50,
|
||||
.memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM,
|
||||
.afe_linear_gain = 1.0,
|
||||
.agc_mode = AFE_MN_PEAK_AGC_MODE_2,
|
||||
.pcm_config =
|
||||
{
|
||||
.total_ch_num = 3,
|
||||
.mic_num = 2,
|
||||
.ref_num = 1,
|
||||
.sample_rate = 16000,
|
||||
},
|
||||
.debug_init = false,
|
||||
.debug_hook = {{AFE_DEBUG_HOOK_MASE_TASK_IN, NULL},
|
||||
{AFE_DEBUG_HOOK_FETCH_TASK_IN, NULL}},
|
||||
.afe_ns_mode = NS_MODE_SSP,
|
||||
.afe_ns_model_name = NULL,
|
||||
.fixed_first_channel = true,
|
||||
};
|
||||
|
||||
// 配置唤醒模型 必须在create_from_config之前配置
|
||||
|
||||
afe_config.aec_init = true;
|
||||
afe_config.se_init = true;
|
||||
afe_config.vad_init = true;
|
||||
afe_config.wakenet_init = false;
|
||||
afe_config.voice_communication_init = false;
|
||||
afe_config.voice_communication_agc_init = true;
|
||||
afe_config.voice_communication_agc_gain = 15;
|
||||
afe_config.vad_mode = VAD_MODE_3;
|
||||
afe_config.wakenet_model_name = NULL;
|
||||
afe_config.wakenet_model_name_2 = NULL;
|
||||
afe_config.wakenet_mode = DET_MODE_2CH_90;
|
||||
afe_config.afe_mode = SR_MODE_LOW_COST;
|
||||
afe_config.afe_perferred_core = 0;
|
||||
afe_config.afe_perferred_priority = 5;
|
||||
afe_config.afe_ringbuf_size = 50;
|
||||
afe_config.memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
|
||||
afe_config.afe_linear_gain = 4.0;
|
||||
afe_config.agc_mode = AFE_MN_PEAK_AGC_MODE_2;
|
||||
afe_config.pcm_config.total_ch_num = 3;
|
||||
afe_config.pcm_config.mic_num = 2;
|
||||
afe_config.pcm_config.ref_num = 1;
|
||||
afe_config.pcm_config.sample_rate = 16000;
|
||||
afe_config.debug_init = false;
|
||||
// afe_config.debug_hook = {{AFE_DEBUG_HOOK_MASE_TASK_IN, NULL},
|
||||
// {AFE_DEBUG_HOOK_FETCH_TASK_IN, NULL}};
|
||||
afe_config.afe_ns_mode = NS_MODE_SSP;
|
||||
afe_config.afe_ns_model_name = NULL;
|
||||
|
||||
// afe_config.wakenet_model_name =
|
||||
// esp_srmodel_filter(models, ESP_WN_PREFIX, NULL);
|
||||
return afe_handle->create_from_config(&afe_config); // 创建afe_data
|
||||
// ESP_LOGI(TAG, "wakenet:%s", afe_config.wakenet_model_name); //
|
||||
// 打印唤醒名称
|
||||
}
|
||||
|
||||
void app_sr_init(void) {
|
||||
afe_data = sr_init();
|
||||
task_flag = 1;
|
||||
|
||||
record_start1();
|
||||
record_start2();
|
||||
|
||||
xTaskCreatePinnedToCore(&detect_Task, "detect", 8 * 1024, (void*)afe_data, 5,
|
||||
NULL, 1);
|
||||
xTaskCreatePinnedToCore(&feed_Task, "feed", 8 * 1024, (void*)afe_data, 5,
|
||||
NULL, 0);
|
||||
while (wav_written2 != 0xFFFFFFFF || wav_written1 != 0xFFFFFFFF) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_afe_sr_iface.h"
|
||||
#include "esp_afe_sr_models.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
void app_sr_init(void);
|
||||
|
||||
void app_sr_init(void);
|
||||
|
||||
void afe(esp_afe_sr_data_t* afe_data, int16_t* i2s_buff);
|
||||
|
||||
bool record_end1();
|
||||
bool record_end2();
|
||||
|
||||
esp_err_t record_start1();
|
||||
esp_err_t record_start2();
|
||||
|
||||
esp_err_t record_add1(int16_t* data1, uint32_t size);
|
||||
esp_err_t record_add2(int16_t* data1, uint32_t size);
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Header structure for WAV file with only one data chunk
|
||||
*
|
||||
* @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/
|
||||
*
|
||||
* @note Assignment to variables in this struct directly is only possible for
|
||||
* little endian architectures (including Xtensa & RISC-V)
|
||||
*/
|
||||
typedef struct {
|
||||
struct {
|
||||
char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */
|
||||
uint32_t chunk_size; /*!< This is the size of the rest of the chunk
|
||||
following this number */
|
||||
char chunk_format[4]; /*!< Contains the letters "WAVE" */
|
||||
} descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
|
||||
struct {
|
||||
char subchunk_id[4]; /*!< Contains the letters "fmt " */
|
||||
uint32_t subchunk_size; /*!< This is the size of the rest of the Subchunk
|
||||
which follows this number */
|
||||
uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form
|
||||
of compression */
|
||||
uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */
|
||||
uint32_t sample_rate; /*!< 8000, 44100, etc. */
|
||||
uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */
|
||||
uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */
|
||||
uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */
|
||||
} fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
|
||||
struct {
|
||||
char subchunk_id[4]; /*!< Contains the letters "data" */
|
||||
uint32_t
|
||||
subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */
|
||||
int16_t data[0]; /*!< Holds raw audio data */
|
||||
} data_chunk; /*!< The "data" subchunk contains the size of the data and the
|
||||
actual sound */
|
||||
} wav_header_t;
|
||||
|
||||
/**
|
||||
* @brief Default header for PCM format WAV files
|
||||
*
|
||||
*/
|
||||
#define WAV_HEADER_PCM_DEFAULT(wav_sample_size, wav_sample_bits, \
|
||||
wav_sample_rate, wav_channel_num) \
|
||||
{ \
|
||||
.descriptor_chunk = {.chunk_id = {'R', 'I', 'F', 'F'}, \
|
||||
.chunk_size = \
|
||||
(wav_sample_size) + sizeof(wav_header_t) - 8, \
|
||||
.chunk_format = {'W', 'A', 'V', 'E'}}, \
|
||||
.fmt_chunk = {.subchunk_id = {'f', 'm', 't', ' '}, \
|
||||
.subchunk_size = 16, /* 16 for PCM */ \
|
||||
.audio_format = 1, /* 1 for PCM */ \
|
||||
.num_of_channels = (wav_channel_num), \
|
||||
.sample_rate = (wav_sample_rate), \
|
||||
.byte_rate = (wav_sample_bits) * (wav_sample_rate) * \
|
||||
(wav_channel_num) / 8, \
|
||||
.block_align = (wav_sample_bits) * (wav_channel_num) / 8, \
|
||||
.bits_per_sample = (wav_sample_bits)}, \
|
||||
.data_chunk = { \
|
||||
.subchunk_id = {'d', 'a', 't', 'a'}, \
|
||||
.subchunk_size = (wav_sample_size) \
|
||||
} \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -3,6 +3,8 @@ dependencies:
|
|||
espressif/esp_tinyusb:
|
||||
version: "^2.0.0"
|
||||
espressif/es7210: "^1.0.0"
|
||||
chmorgan/esp-file-iterator: "1.0.0" # 获取文件
|
||||
espressif/esp-sr: "^1.6.0" # 语音识别
|
||||
idf:
|
||||
version: ">=5.4.0"
|
||||
|
||||
|
|
|
|||
357
main/main.c
357
main/main.c
|
|
@ -1,21 +1,68 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app_sr.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/i2s_std.h"
|
||||
#include "driver/i2s_tdm.h"
|
||||
#include "driver/i2s_types.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "es7210.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_pm.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
/* I2C port and GPIOs */
|
||||
#define I2C_NUM (0)
|
||||
#define I2C_SDA_IO (1)
|
||||
#define I2C_SCL_IO (2)
|
||||
|
||||
#define BSP_I2C_SDA (GPIO_NUM_1) // SDA引脚
|
||||
#define BSP_I2C_SCL (GPIO_NUM_2) // SCL引脚
|
||||
#define BSP_I2C_NUM (0) // I2C外设
|
||||
#define BSP_I2C_FREQ_HZ 100000 // 100kHz
|
||||
|
||||
/* I2S port and GPIOs */
|
||||
#define I2S_NUM (0)
|
||||
#define I2S_MCK_IO (38)
|
||||
#define I2S_BCK_IO (14)
|
||||
#define I2S_WS_IO (13)
|
||||
#define I2S_DI_IO (12)
|
||||
|
||||
/* SD card GPIOs */
|
||||
#define SD_CMD_IO (48)
|
||||
#define SD_CLK_IO (47)
|
||||
#define SD_DAT0_IO (21)
|
||||
|
||||
/* SD card & recording configurations */
|
||||
#define RECORD_TIME_SEC (20)
|
||||
#define SD_MOUNT_POINT "/sdcard"
|
||||
|
||||
/* I2S configurations */
|
||||
#define I2S_TDM_FORMAT (ES7210_I2S_FMT_I2S)
|
||||
#define I2S_CHAN_NUM (2)
|
||||
#define I2S_SAMPLE_RATE (16000)
|
||||
#define I2S_MCLK_MULTIPLE (I2S_MCLK_MULTIPLE_256)
|
||||
#define I2S_SAMPLE_BITS (I2S_DATA_BIT_WIDTH_16BIT)
|
||||
#define I2S_TDM_SLOT_MASK (I2S_TDM_SLOT0 | I2S_TDM_SLOT1)
|
||||
|
||||
/* ES7210 configurations */
|
||||
#define ES7210_I2C_ADDR (0x41)
|
||||
#define ES7210_I2C_CLK (100000)
|
||||
#define ES7210_MIC_GAIN (ES7210_MIC_GAIN_30DB)
|
||||
#define ES7210_MIC_BIAS (ES7210_MIC_BIAS_2V87)
|
||||
#define ES7210_ADC_VOLUME (32)
|
||||
|
||||
// 配置参数
|
||||
#define SAMPLE_RATE 16000
|
||||
#define I2S_PORT I2S_NUM_0
|
||||
|
|
@ -36,7 +83,7 @@ static QueueHandle_t i2s_event_queue = NULL;
|
|||
static SemaphoreHandle_t data_ready_sem = NULL;
|
||||
|
||||
// 中断服务程序的数据处理任务句柄
|
||||
static TaskHandle_t i2s_dma_task_handle = NULL;
|
||||
static TaskHandle_t rx_task_handle = NULL;
|
||||
|
||||
// 自定义数据结构用于队列传递
|
||||
typedef struct {
|
||||
|
|
@ -44,17 +91,113 @@ typedef struct {
|
|||
size_t size;
|
||||
} i2s_queue_data_t;
|
||||
|
||||
// 初始化GPIO
|
||||
static void gpio_init(void) {
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << LED_PIN),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(LED_PIN, 0);
|
||||
// static i2s_chan_handle_t es7210_i2s_init(void) {
|
||||
// i2s_chan_handle_t i2s_rx_chan = NULL; // 定义接收通道句柄
|
||||
// ESP_LOGI(TAG, "Create I2S receive channel");
|
||||
// i2s_chan_config_t i2s_rx_conf = I2S_CHANNEL_DEFAULT_CONFIG(
|
||||
// I2S_NUM_AUTO, I2S_ROLE_MASTER); // 配置接收通道
|
||||
// ESP_ERROR_CHECK(
|
||||
// i2s_new_channel(&i2s_rx_conf, NULL, &i2s_rx_chan)); // 创建i2s通道
|
||||
|
||||
// ESP_LOGI(TAG, "Configure I2S receive channel to TDM mode");
|
||||
// // 定义接收通道为I2S TDM模式 并配置
|
||||
// i2s_tdm_config_t i2s_tdm_rx_conf = {
|
||||
// .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
|
||||
// I2S_SAMPLE_BITS, I2S_SLOT_MODE_STEREO, I2S_TDM_SLOT_MASK),
|
||||
// .clk_cfg = {.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
// .sample_rate_hz = I2S_SAMPLE_RATE,
|
||||
// .mclk_multiple = I2S_MCLK_MULTIPLE},
|
||||
// .gpio_cfg = {.mclk = I2S_MCK_IO,
|
||||
// .bclk = I2S_BCK_IO,
|
||||
// .ws = I2S_WS_IO,
|
||||
// .dout = -1, // ES7210 only has ADC capability
|
||||
// .din = I2S_DI_IO},
|
||||
// };
|
||||
|
||||
// ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(
|
||||
// i2s_rx_chan, &i2s_tdm_rx_conf)); // 初始化I2S通道为TDM模式
|
||||
|
||||
// return i2s_rx_chan;
|
||||
// }
|
||||
|
||||
sdmmc_card_t* mount_sdcard(void) {
|
||||
sdmmc_card_t* sdmmc_card = NULL;
|
||||
|
||||
ESP_LOGI(TAG, "Mounting SD card");
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
.allocation_unit_size = 8 * 1024};
|
||||
|
||||
ESP_LOGI(TAG, "Initializing SD card");
|
||||
ESP_LOGI(TAG, "Using SDMMC peripheral");
|
||||
|
||||
sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT(); // SDMMC主机接口配置
|
||||
sdmmc_slot_config_t slot_config =
|
||||
SDMMC_SLOT_CONFIG_DEFAULT(); // SDMMC插槽配置
|
||||
slot_config.width = 1; // 设置为1线SD模式
|
||||
slot_config.clk = SD_CLK_IO;
|
||||
slot_config.cmd = SD_CMD_IO;
|
||||
slot_config.d0 = SD_DAT0_IO;
|
||||
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // 打开内部上拉电阻
|
||||
|
||||
ESP_LOGI(TAG, "Mounting filesystem");
|
||||
|
||||
esp_err_t ret;
|
||||
while (1) {
|
||||
ret = esp_vfs_fat_sdmmc_mount(SD_MOUNT_POINT, &sdmmc_host, &slot_config,
|
||||
&mount_config, &sdmmc_card);
|
||||
if (ret == ESP_OK) {
|
||||
break;
|
||||
} else if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount filesystem.");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize the card (%s). ",
|
||||
esp_err_to_name(ret));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
ESP_LOGI(
|
||||
TAG, "Card size: %lluMB, speed: %dMHz",
|
||||
(((uint64_t)sdmmc_card->csd.capacity) * sdmmc_card->csd.sector_size) >>
|
||||
20,
|
||||
sdmmc_card->max_freq_khz / 1000);
|
||||
|
||||
return sdmmc_card;
|
||||
}
|
||||
|
||||
esp_err_t i2c_init(void) {
|
||||
i2c_config_t i2c_conf = {.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = BSP_I2C_SDA,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = BSP_I2C_SCL,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = BSP_I2C_FREQ_HZ};
|
||||
i2c_param_config(BSP_I2C_NUM, &i2c_conf);
|
||||
|
||||
return i2c_driver_install(BSP_I2C_NUM, i2c_conf.mode, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void es7210_codec_init(void) {
|
||||
// 创建es7210器件句柄
|
||||
es7210_dev_handle_t es7210_handle = NULL;
|
||||
es7210_i2c_config_t es7210_i2c_conf = {.i2c_port = I2C_NUM,
|
||||
.i2c_addr = ES7210_I2C_ADDR};
|
||||
ESP_ERROR_CHECK(es7210_new_codec(&es7210_i2c_conf, &es7210_handle));
|
||||
|
||||
// 初始化es7210芯片
|
||||
ESP_LOGI(TAG, "Configure ES7210 codec parameters");
|
||||
es7210_codec_config_t codec_conf = {
|
||||
.i2s_format = I2S_TDM_FORMAT,
|
||||
.mclk_ratio = I2S_MCLK_MULTIPLE,
|
||||
.sample_rate_hz = I2S_SAMPLE_RATE,
|
||||
.bit_width = (es7210_i2s_bits_t)I2S_SAMPLE_BITS,
|
||||
.mic_bias = ES7210_MIC_BIAS,
|
||||
.mic_gain = ES7210_MIC_GAIN,
|
||||
.flags.tdm_enable = true};
|
||||
ESP_ERROR_CHECK(es7210_config_codec(es7210_handle, &codec_conf));
|
||||
ESP_ERROR_CHECK(es7210_config_volume(es7210_handle, ES7210_ADC_VOLUME));
|
||||
}
|
||||
|
||||
// I2S数据接收任务 - 从DMA缓冲区读取数据并放入队列
|
||||
|
|
@ -74,7 +217,6 @@ static void i2s_rx_task(void* arg) {
|
|||
ESP_LOGI(TAG, "I2S接收任务开始运行");
|
||||
|
||||
while (1) {
|
||||
// 从I2S通道读取数据 - 使用 short timeout to avoid blocking indefinitely
|
||||
esp_err_t ret = ESP_OK;
|
||||
ret = i2s_channel_read(rx_chan, data_buffer, DMA_BUF_LEN * sizeof(int16_t),
|
||||
&bytes_read, pdMS_TO_TICKS(1));
|
||||
|
|
@ -100,16 +242,14 @@ static void i2s_rx_task(void* arg) {
|
|||
// } else {
|
||||
// ESP_LOGE(TAG, "无法分配内存来复制I2S数据");
|
||||
// }
|
||||
ESP_LOGI(TAG, "处理 %d 字节的I2S数据", bytes_read);
|
||||
// vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
esp_sleep_enable_timer_wakeup(50 * 1000);
|
||||
esp_light_sleep_start();
|
||||
record_add1(data_buffer, bytes_read);
|
||||
if (!record_end1()) ESP_LOGI(TAG, "处理 %d 字节的I2S数据", bytes_read);
|
||||
|
||||
// 点亮LED指示数据处理
|
||||
// gpio_set_level(LED_PIN, 1);
|
||||
// vTaskDelay(pdMS_TO_TICKS(5)); // 短暂点亮LED
|
||||
// gpio_set_level(LED_PIN, 0);
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
// esp_sleep_enable_timer_wakeup(50 * 1000);
|
||||
// esp_light_sleep_start();
|
||||
} else if (ret != ESP_OK && ret != ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "I2S读取错误: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
|
@ -137,19 +277,16 @@ static void i2s_process_task(void* arg) {
|
|||
// 处理接收到的数据
|
||||
ESP_LOGI(TAG, "处理 %d 字节的I2S数据", queue_data.size);
|
||||
|
||||
// 点亮LED指示数据处理
|
||||
gpio_set_level(LED_PIN, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(5)); // 短暂点亮LED
|
||||
gpio_set_level(LED_PIN, 0);
|
||||
|
||||
// 在这里可以添加对音频数据的处理逻辑
|
||||
// 例如:音频分析、滤波、存储等
|
||||
|
||||
// 释放数据缓冲区
|
||||
if (queue_data.buffer) {
|
||||
free(queue_data.buffer);
|
||||
}
|
||||
// if (queue_data.buffer) {
|
||||
// free(queue_data.buffer);
|
||||
// }
|
||||
}
|
||||
esp_light_sleep_start();
|
||||
|
||||
} else {
|
||||
// 超时未收到信号,可以执行其他操作或简单地继续循环
|
||||
// 这样可以避免任务无限期阻塞
|
||||
|
|
@ -158,21 +295,59 @@ static void i2s_process_task(void* arg) {
|
|||
}
|
||||
}
|
||||
|
||||
// I2S事件回调函数 - 当DMA完成数据传输时被调用 (由DMA中断触发)
|
||||
static bool IRAM_ATTR i2s_evt_callback(i2s_chan_handle_t handle,
|
||||
i2s_event_data_t* event,
|
||||
void* user_ctx) {
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
|
||||
if (event && event->size > 0) {
|
||||
// 复制数据到新分配的缓冲区以便在任务中处理
|
||||
// void* data_buffer = malloc(event->size);
|
||||
// if (data_buffer) {
|
||||
// memcpy(data_buffer, event->data,
|
||||
// event->size); // 使用 event->data instead of event->buffer
|
||||
|
||||
// 创建要发送到队列的事件数据
|
||||
i2s_queue_data_t queue_event = {.buffer = NULL, .size = event->size};
|
||||
|
||||
// 发送数据到队列,供处理任务使用
|
||||
if (xQueueSendFromISR(i2s_event_queue, &queue_event, &need_yield) !=
|
||||
pdTRUE) {
|
||||
// 队列满,丢弃数据并释放缓冲区
|
||||
// free(data_buffer);
|
||||
} else {
|
||||
// 通知数据处理任务
|
||||
if (data_ready_sem) {
|
||||
xSemaphoreGiveFromISR(data_ready_sem, &need_yield);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
// ESP_LOGI(TAG, "处理 %d 字节的I2S数据", event->size);
|
||||
// vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
// esp_sleep_enable_timer_wakeup(50 * 1000);
|
||||
// esp_light_sleep_start();
|
||||
}
|
||||
|
||||
return (need_yield == pdTRUE);
|
||||
}
|
||||
|
||||
// 初始化I2S
|
||||
static esp_err_t i2s_init(void) {
|
||||
// 创建I2S事件队列
|
||||
// i2s_event_queue = xQueueCreate(DMA_BUF_COUNT, sizeof(i2s_queue_data_t));
|
||||
// if (!i2s_event_queue) {
|
||||
// ESP_LOGE(TAG, "创建I2S事件队列失败");
|
||||
// return ESP_FAIL;
|
||||
// }
|
||||
i2s_event_queue = xQueueCreate(DMA_BUF_COUNT, sizeof(i2s_queue_data_t));
|
||||
if (!i2s_event_queue) {
|
||||
ESP_LOGE(TAG, "创建I2S事件队列失败");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// 创建数据就绪信号量
|
||||
// data_ready_sem = xSemaphoreCreateBinary();
|
||||
// if (!data_ready_sem) {
|
||||
// ESP_LOGE(TAG, "创建数据就绪信号量失败");
|
||||
// return ESP_FAIL;
|
||||
// }
|
||||
data_ready_sem = xSemaphoreCreateBinary();
|
||||
if (!data_ready_sem) {
|
||||
ESP_LOGE(TAG, "创建数据就绪信号量失败");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// 配置I2S通道
|
||||
i2s_chan_config_t chan_cfg =
|
||||
|
|
@ -183,28 +358,53 @@ static esp_err_t i2s_init(void) {
|
|||
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_chan));
|
||||
|
||||
// 配置STD模式
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
||||
.slot_cfg =
|
||||
I2S_STD_MSB_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_MONO),
|
||||
.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,
|
||||
.bclk = I2S_BCK_PIN,
|
||||
.ws = I2S_WS_PIN,
|
||||
.dout = I2S_GPIO_UNUSED,
|
||||
.din = I2S_DATA_PIN,
|
||||
.invert_flags = {
|
||||
.mclk_inv = false,
|
||||
.bclk_inv = false,
|
||||
.ws_inv = false,
|
||||
}}};
|
||||
// 定义接收通道为I2S TDM模式 并配置
|
||||
i2s_tdm_config_t i2s_tdm_rx_conf = {
|
||||
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
|
||||
I2S_SAMPLE_BITS, I2S_SLOT_MODE_STEREO, I2S_TDM_SLOT_MASK),
|
||||
.clk_cfg = {.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.sample_rate_hz = I2S_SAMPLE_RATE,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE},
|
||||
.gpio_cfg = {.mclk = I2S_MCK_IO,
|
||||
.bclk = I2S_BCK_IO,
|
||||
.ws = I2S_WS_IO,
|
||||
.dout = -1, // ES7210 only has ADC capability
|
||||
.din = I2S_DI_IO},
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(
|
||||
rx_chan, &i2s_tdm_rx_conf)); // 初始化I2S通道为TDM模式
|
||||
|
||||
// // 配置STD模式
|
||||
// i2s_std_config_t std_cfg = {
|
||||
// .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
||||
// .slot_cfg =
|
||||
// I2S_STD_MSB_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_MONO),
|
||||
// .gpio_cfg = {.mclk = I2S_GPIO_UNUSED,
|
||||
// .bclk = I2S_BCK_PIN,
|
||||
// .ws = I2S_WS_PIN,
|
||||
// .dout = I2S_GPIO_UNUSED,
|
||||
// .din = I2S_DATA_PIN,
|
||||
// .invert_flags = {
|
||||
// .mclk_inv = false,
|
||||
// .bclk_inv = false,
|
||||
// .ws_inv = false,
|
||||
// }}};
|
||||
|
||||
// ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
|
||||
|
||||
// 注册事件回调 - 这是DMA中断触发的回调
|
||||
// i2s_event_callbacks_t callbacks = {
|
||||
// .on_recv = i2s_evt_callback,
|
||||
// .on_recv_q_ovf = NULL,
|
||||
// };
|
||||
// ESP_ERROR_CHECK(
|
||||
// i2s_channel_register_event_callback(rx_chan, &callbacks, NULL));
|
||||
|
||||
// 启用通道
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));
|
||||
|
||||
ESP_LOGI(TAG, "I2S初始化完成,使用DMA");
|
||||
ESP_LOGI(TAG, "I2S初始化完成,使用DMA中断");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
|
@ -231,9 +431,9 @@ void config_power_manager() {
|
|||
// 8 18.8
|
||||
}
|
||||
|
||||
// 主函数
|
||||
void init_msc(sdmmc_card_t* card);
|
||||
|
||||
void app_main(void) {
|
||||
// 初始化NVS
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
|
|
@ -242,35 +442,37 @@ void app_main(void) {
|
|||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
ESP_LOGI(TAG, "启动I2S DMA接收示例");
|
||||
sdmmc_card_t* sdmmc_card = mount_sdcard();
|
||||
|
||||
// 初始化GPIO
|
||||
gpio_init();
|
||||
config_power_manager();
|
||||
i2c_init();
|
||||
|
||||
// 初始化I2S
|
||||
if (i2s_init() != ESP_OK) {
|
||||
ESP_LOGE(TAG, "I2S初始化失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建I2S接收任务
|
||||
TaskHandle_t rx_task_handle = NULL;
|
||||
// i2s_chan_handle_t i2s_rx_chan = es7210_i2s_init();
|
||||
es7210_codec_init();
|
||||
|
||||
ESP_LOGI(TAG, "启动I2S DMA接收示例");
|
||||
|
||||
// app_sr_init(); // 语音识别初始化
|
||||
|
||||
config_power_manager();
|
||||
|
||||
record_start1();
|
||||
|
||||
BaseType_t task_ret = xTaskCreate(i2s_rx_task, "i2s_rx_task", 4096, NULL,
|
||||
tskIDLE_PRIORITY + 2, &rx_task_handle);
|
||||
// BaseType_t task_ret =
|
||||
// xTaskCreate(i2s_process_task, "i2s_process_task", 4096, NULL,
|
||||
// tskIDLE_PRIORITY + 1, &rx_task_handle);
|
||||
|
||||
if (task_ret != pdTRUE) {
|
||||
ESP_LOGE(TAG, "创建I2S接收任务失败");
|
||||
ESP_LOGE(TAG, "创建I2S数据处理任务失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建I2S数据处理任务
|
||||
// task_ret = xTaskCreate(i2s_process_task, "i2s_process_task", 4096, NULL,
|
||||
// tskIDLE_PRIORITY + 1, &i2s_dma_task_handle);
|
||||
// if (task_ret != pdTRUE) {
|
||||
// ESP_LOGE(TAG, "创建I2S数据处理任务失败");
|
||||
// return;
|
||||
// }
|
||||
|
||||
ESP_LOGI(TAG, "系统启动完成,I2S使用DMA处理数据");
|
||||
|
||||
// 主循环可以执行其他任务
|
||||
|
|
@ -278,6 +480,13 @@ void app_main(void) {
|
|||
// // 这里可以执行其他非I2S相关的任务
|
||||
// vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
// }
|
||||
|
||||
while (!record_end1()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
init_msc(sdmmc_card);
|
||||
esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, sdmmc_card);
|
||||
}
|
||||
|
||||
// 应用程序结束时的清理函数
|
||||
|
|
|
|||
421
main/main.c.bacl
421
main/main.c.bacl
|
|
@ -1,421 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "app_sr.h"
|
||||
#include "app_ui.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/i2s_std.h"
|
||||
#include "driver/i2s_tdm.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "es7210.h"
|
||||
#include "esp32_s3_szp.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
#include "esp_pm.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_sntp.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "format_wav.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
|
||||
/* I2C port and GPIOs */
|
||||
#define I2C_NUM (0)
|
||||
#define I2C_SDA_IO (1)
|
||||
#define I2C_SCL_IO (2)
|
||||
|
||||
/* I2S port and GPIOs */
|
||||
#define I2S_NUM (0)
|
||||
#define I2S_MCK_IO (38)
|
||||
#define I2S_BCK_IO (14)
|
||||
#define I2S_WS_IO (13)
|
||||
#define I2S_DI_IO (12)
|
||||
|
||||
/* SD card GPIOs */
|
||||
#define SD_CMD_IO (48)
|
||||
#define SD_CLK_IO (47)
|
||||
#define SD_DAT0_IO (21)
|
||||
|
||||
/* I2S configurations */
|
||||
#define I2S_TDM_FORMAT (ES7210_I2S_FMT_I2S)
|
||||
#define I2S_CHAN_NUM (2)
|
||||
#define I2S_SAMPLE_RATE (16000)
|
||||
#define I2S_MCLK_MULTIPLE (I2S_MCLK_MULTIPLE_256)
|
||||
#define I2S_SAMPLE_BITS (I2S_DATA_BIT_WIDTH_16BIT)
|
||||
#define I2S_TDM_SLOT_MASK (I2S_TDM_SLOT0 | I2S_TDM_SLOT1)
|
||||
|
||||
/* ES7210 configurations */
|
||||
#define ES7210_I2C_ADDR (0x41)
|
||||
#define ES7210_I2C_CLK (100000)
|
||||
#define ES7210_MIC_GAIN (ES7210_MIC_GAIN_30DB)
|
||||
#define ES7210_MIC_BIAS (ES7210_MIC_BIAS_2V87)
|
||||
#define ES7210_ADC_VOLUME (0)
|
||||
|
||||
/* SD card & recording configurations */
|
||||
#define RECORD_TIME_SEC (20)
|
||||
#define SD_MOUNT_POINT "/sdcard"
|
||||
|
||||
static const char* TAG = "main";
|
||||
static char* record_file_name = NULL;
|
||||
|
||||
static i2s_chan_handle_t es7210_i2s_init(void) {
|
||||
i2s_chan_handle_t i2s_rx_chan = NULL; // 定义接收通道句柄
|
||||
ESP_LOGI(TAG, "Create I2S receive channel");
|
||||
i2s_chan_config_t i2s_rx_conf = I2S_CHANNEL_DEFAULT_CONFIG(
|
||||
I2S_NUM_AUTO, I2S_ROLE_MASTER); // 配置接收通道
|
||||
ESP_ERROR_CHECK(
|
||||
i2s_new_channel(&i2s_rx_conf, NULL, &i2s_rx_chan)); // 创建i2s通道
|
||||
|
||||
ESP_LOGI(TAG, "Configure I2S receive channel to TDM mode");
|
||||
// 定义接收通道为I2S TDM模式 并配置
|
||||
i2s_tdm_config_t i2s_tdm_rx_conf = {
|
||||
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
|
||||
I2S_SAMPLE_BITS, I2S_SLOT_MODE_STEREO, I2S_TDM_SLOT_MASK),
|
||||
.clk_cfg = {.clk_src = I2S_CLK_SRC_DEFAULT,
|
||||
.sample_rate_hz = I2S_SAMPLE_RATE,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE},
|
||||
.gpio_cfg = {.mclk = I2S_MCK_IO,
|
||||
.bclk = I2S_BCK_IO,
|
||||
.ws = I2S_WS_IO,
|
||||
.dout = -1, // ES7210 only has ADC capability
|
||||
.din = I2S_DI_IO},
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(
|
||||
i2s_rx_chan, &i2s_tdm_rx_conf)); // 初始化I2S通道为TDM模式
|
||||
|
||||
return i2s_rx_chan;
|
||||
}
|
||||
|
||||
sdmmc_card_t* mount_sdcard(void) {
|
||||
sdmmc_card_t* sdmmc_card = NULL;
|
||||
|
||||
ESP_LOGI(TAG, "Mounting SD card");
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 5,
|
||||
.allocation_unit_size = 8 * 1024};
|
||||
|
||||
ESP_LOGI(TAG, "Initializing SD card");
|
||||
ESP_LOGI(TAG, "Using SDMMC peripheral");
|
||||
|
||||
sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT(); // SDMMC主机接口配置
|
||||
sdmmc_slot_config_t slot_config =
|
||||
SDMMC_SLOT_CONFIG_DEFAULT(); // SDMMC插槽配置
|
||||
slot_config.width = 1; // 设置为1线SD模式
|
||||
slot_config.clk = SD_CLK_IO;
|
||||
slot_config.cmd = SD_CMD_IO;
|
||||
slot_config.d0 = SD_DAT0_IO;
|
||||
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // 打开内部上拉电阻
|
||||
|
||||
ESP_LOGI(TAG, "Mounting filesystem");
|
||||
|
||||
esp_err_t ret;
|
||||
while (1) {
|
||||
ret = esp_vfs_fat_sdmmc_mount(SD_MOUNT_POINT, &sdmmc_host, &slot_config,
|
||||
&mount_config, &sdmmc_card);
|
||||
if (ret == ESP_OK) {
|
||||
break;
|
||||
} else if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount filesystem.");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize the card (%s). ",
|
||||
esp_err_to_name(ret));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
ESP_LOGI(
|
||||
TAG, "Card size: %lluMB, speed: %dMHz",
|
||||
(((uint64_t)sdmmc_card->csd.capacity) * sdmmc_card->csd.sector_size) >>
|
||||
20,
|
||||
sdmmc_card->max_freq_khz / 1000);
|
||||
|
||||
return sdmmc_card;
|
||||
}
|
||||
|
||||
static void es7210_codec_init(void) {
|
||||
// 创建es7210器件句柄
|
||||
es7210_dev_handle_t es7210_handle = NULL;
|
||||
es7210_i2c_config_t es7210_i2c_conf = {.i2c_port = I2C_NUM,
|
||||
.i2c_addr = ES7210_I2C_ADDR};
|
||||
ESP_ERROR_CHECK(es7210_new_codec(&es7210_i2c_conf, &es7210_handle));
|
||||
|
||||
// 初始化es7210芯片
|
||||
ESP_LOGI(TAG, "Configure ES7210 codec parameters");
|
||||
es7210_codec_config_t codec_conf = {
|
||||
.i2s_format = I2S_TDM_FORMAT,
|
||||
.mclk_ratio = I2S_MCLK_MULTIPLE,
|
||||
.sample_rate_hz = I2S_SAMPLE_RATE,
|
||||
.bit_width = (es7210_i2s_bits_t)I2S_SAMPLE_BITS,
|
||||
.mic_bias = ES7210_MIC_BIAS,
|
||||
.mic_gain = ES7210_MIC_GAIN,
|
||||
.flags.tdm_enable = true};
|
||||
ESP_ERROR_CHECK(es7210_config_codec(es7210_handle, &codec_conf));
|
||||
ESP_ERROR_CHECK(es7210_config_volume(es7210_handle, ES7210_ADC_VOLUME));
|
||||
}
|
||||
|
||||
// wifi事件组
|
||||
static EventGroupHandle_t s_wifi_event_group = NULL;
|
||||
// wifi事件
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
#define WIFI_START_BIT BIT2
|
||||
#define WIFI_GET_SNTP_BIT BIT3
|
||||
// wifi最大重连次数
|
||||
#define EXAMPLE_ESP_MAXIMUM_RETRY 3
|
||||
|
||||
typedef struct {
|
||||
char wifi_ssid[32]; // 获取wifi名称
|
||||
char wifi_password[64]; // 获取wifi密码
|
||||
char back_flag; // 是否退出
|
||||
} wifi_account_t;
|
||||
|
||||
static void wifi_connect() {
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
assert(esp_netif_create_default_wifi_sta());
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
// ESP_ERROR_CHECK(esp_event_handler_instance_register(
|
||||
// WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
|
||||
// ESP_ERROR_CHECK(esp_event_handler_instance_register(
|
||||
// IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL,
|
||||
// &instance_got_ip));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
|
||||
wifi_account_t wifi_account;
|
||||
strcpy(wifi_account.wifi_ssid, "Liang");
|
||||
strcpy(wifi_account.wifi_password, "wifi1234");
|
||||
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", wifi_account.wifi_ssid,
|
||||
wifi_account.wifi_password);
|
||||
wifi_account.back_flag = 0; // 正常连接
|
||||
|
||||
wifi_config_t wifi_config = {
|
||||
.sta =
|
||||
{
|
||||
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
|
||||
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
|
||||
.sae_h2e_identifier = "",
|
||||
},
|
||||
};
|
||||
strcpy((char*)wifi_config.sta.ssid, wifi_account.wifi_ssid);
|
||||
strcpy((char*)wifi_config.sta.password, wifi_account.wifi_password);
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
esp_wifi_connect();
|
||||
}
|
||||
|
||||
time_t now;
|
||||
struct tm timeinfo;
|
||||
|
||||
static void get_time_task() {
|
||||
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("ntp.aliyun.com");
|
||||
esp_netif_sntp_init(&config);
|
||||
// wait for time to be set
|
||||
int retry = 0;
|
||||
// const int retry_count = 6;
|
||||
while (esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) ==
|
||||
ESP_ERR_TIMEOUT) {
|
||||
ESP_LOGI(TAG, "Waiting for system time to be set... (%d)", retry++);
|
||||
}
|
||||
|
||||
esp_netif_sntp_deinit();
|
||||
// 设置时区
|
||||
setenv("TZ", "CST-8", 1);
|
||||
tzset();
|
||||
// 获取系统时间
|
||||
time(&now);
|
||||
localtime_r(&now, &timeinfo);
|
||||
|
||||
ESP_LOGI(TAG, "%d年%02d月%02d日", timeinfo.tm_year + 1900,
|
||||
timeinfo.tm_mon + 1, timeinfo.tm_mday);
|
||||
ESP_LOGI(TAG, "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min,
|
||||
timeinfo.tm_sec);
|
||||
}
|
||||
|
||||
static char* get_file_name_from_time(void) {
|
||||
get_time_task();
|
||||
strftime(record_file_name, 48, "/sdcard/Record_%Y%m%d%H%M%S.wav", &timeinfo);
|
||||
return record_file_name;
|
||||
}
|
||||
|
||||
static esp_err_t record_wav(i2s_chan_handle_t i2s_rx_chan) {
|
||||
ESP_RETURN_ON_FALSE(i2s_rx_chan, ESP_FAIL, TAG,
|
||||
"invalid i2s channel handle pointer");
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
uint32_t byte_rate = I2S_SAMPLE_RATE * I2S_CHAN_NUM * I2S_SAMPLE_BITS / 8;
|
||||
uint32_t wav_size = byte_rate * RECORD_TIME_SEC;
|
||||
|
||||
const wav_header_t wav_header = WAV_HEADER_PCM_DEFAULT(
|
||||
wav_size, I2S_SAMPLE_BITS, I2S_SAMPLE_RATE, I2S_CHAN_NUM);
|
||||
|
||||
get_file_name_from_time();
|
||||
ESP_LOGI(TAG, "Opening file %s", record_file_name);
|
||||
FILE* f = fopen(record_file_name, "w");
|
||||
ESP_LOGI(TAG, "fopen error: %s", strerror(errno));
|
||||
ESP_RETURN_ON_FALSE(f, ESP_FAIL, TAG, "error while opening wav file");
|
||||
|
||||
ESP_GOTO_ON_FALSE(fwrite(&wav_header, sizeof(wav_header_t), 1, f), ESP_FAIL,
|
||||
err, TAG, "error while writing wav header");
|
||||
|
||||
/* Start recording */
|
||||
size_t wav_written = 0;
|
||||
static int16_t i2s_readraw_buff[4096];
|
||||
ESP_GOTO_ON_ERROR(i2s_channel_enable(i2s_rx_chan), err, TAG,
|
||||
"error while starting i2s rx channel");
|
||||
while (wav_written < wav_size) {
|
||||
if (wav_written % byte_rate < sizeof(i2s_readraw_buff)) {
|
||||
ESP_LOGI(TAG, "Recording: %" PRIu32 "/%ds", wav_written / byte_rate + 1,
|
||||
RECORD_TIME_SEC);
|
||||
}
|
||||
size_t bytes_read = 0;
|
||||
/* Read RAW samples from ES7210 */
|
||||
ESP_GOTO_ON_ERROR(i2s_channel_read(i2s_rx_chan, i2s_readraw_buff,
|
||||
sizeof(i2s_readraw_buff), &bytes_read,
|
||||
pdMS_TO_TICKS(1000)),
|
||||
err, TAG, "error while reading samples from i2s");
|
||||
/* Write the samples to the WAV file */
|
||||
ESP_GOTO_ON_FALSE(fwrite(i2s_readraw_buff, bytes_read, 1, f), ESP_FAIL, err,
|
||||
TAG, "error while writing samples to wav file");
|
||||
wav_written += bytes_read;
|
||||
}
|
||||
|
||||
err:
|
||||
i2s_channel_disable(i2s_rx_chan);
|
||||
ESP_LOGI(TAG, "Recording done! Flushing file buffer");
|
||||
fclose(f);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void init_msc(sdmmc_card_t* card);
|
||||
|
||||
void config_power_manager() {
|
||||
// 自动降频至8MHz 双核都空闲时进入 Light Sleep
|
||||
esp_err_t ret;
|
||||
esp_pm_config_esp32_t pm_config = {
|
||||
.max_freq_mhz = 240, .min_freq_mhz = 8, .light_sleep_enable = true};
|
||||
ret = esp_pm_configure(&pm_config);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("PM", "Power management configuration failed (0x%x)", ret);
|
||||
}
|
||||
uint32_t cpu_freq = esp_clk_cpu_freq();
|
||||
printf("当前CPU频率: %lu MHz\n", cpu_freq / 1000000);
|
||||
// vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
// light_sleep_off
|
||||
// deep sleep 5
|
||||
// 240 44.8
|
||||
// 160 38.4
|
||||
// 80 32.9
|
||||
// 8 18.8
|
||||
}
|
||||
|
||||
void taskOnCore0(void* pvParam) {
|
||||
while (1) {
|
||||
uint32_t cpu_freq = esp_clk_cpu_freq();
|
||||
printf("taskOnCore0 当前CPU频率: %lu MHz\n", cpu_freq / 1000000);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
// esp_sleep_enable_timer_wakeup(1 * 1000000);
|
||||
// esp_light_sleep_start();
|
||||
}
|
||||
}
|
||||
void taskOnCore1(void* pvParam) {
|
||||
while (1) {
|
||||
uint32_t cpu_freq = esp_clk_cpu_freq();
|
||||
printf("taskOnCore1 当前CPU频率: %lu MHz\n", cpu_freq / 1000000);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
// esp_sleep_enable_timer_wakeup(1 * 1000000);
|
||||
// esp_light_sleep_start();
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
esp_err_t ret;
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
sdmmc_card_t* sdmmc_card = mount_sdcard();
|
||||
|
||||
bsp_i2c_init(); // I2C初始化
|
||||
// pca9557_init(); // IO扩展芯片初始化
|
||||
// bsp_lvgl_start(); // 初始化液晶屏lvgl接口
|
||||
|
||||
// bsp_spiffs_mount(); // SPIFFS文件系统初始化
|
||||
bsp_codec_init(); // 音频初始化
|
||||
// mp3_player_init(); // MP3播放器初始化
|
||||
|
||||
// app_sr_init(); // 语音识别初始化
|
||||
// esp_deep_sleep_start();
|
||||
// esp_light_sleep_start();
|
||||
|
||||
i2s_chan_handle_t i2s_rx_chan = es7210_i2s_init();
|
||||
es7210_codec_init();
|
||||
wifi_connect();
|
||||
|
||||
// esp_wifi_stop();
|
||||
// esp_wifi_set_mode(WIFI_MODE_NULL);
|
||||
// esp_wifi_deinit();
|
||||
// esp_bt_controller_disable();
|
||||
// esp_bt_controller_deinit();
|
||||
// esp_bluedroid_disable();
|
||||
// esp_bluedroid_deinit();
|
||||
// esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
|
||||
|
||||
// 主动休眠
|
||||
// esp_deep_sleep_start();
|
||||
// esp_light_sleep_start();
|
||||
|
||||
// config_power_manager();
|
||||
// xTaskCreatePinnedToCore(taskOnCore0, // 任务函数
|
||||
// "Core0_Task", // 任务名称
|
||||
// 4096, // 堆栈大小
|
||||
// NULL, // 参数
|
||||
// 1, // 优先级
|
||||
// NULL, // 任务句柄
|
||||
// 0 // 绑定到核心0
|
||||
// );
|
||||
// xTaskCreatePinnedToCore(taskOnCore1, // 任务函数
|
||||
// "Core1_Task", // 任务名称
|
||||
// 4096, // 堆栈大小
|
||||
// NULL, // 参数
|
||||
// 1, // 优先级
|
||||
// NULL, // 任务句柄
|
||||
// 1 // 绑定到核心0
|
||||
// );
|
||||
|
||||
record_file_name = malloc(64);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
esp_err_t err = record_wav(i2s_rx_chan);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Audio success recorded into %s.", record_file_name);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Record failed, %s .", record_file_name);
|
||||
}
|
||||
}
|
||||
|
||||
init_msc(sdmmc_card);
|
||||
|
||||
esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, sdmmc_card);
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/* DESCRIPTION:
|
||||
* This example contains code to make ESP32 based device recognizable by
|
||||
* USB-hosts as a USB Mass Storage Device. It either allows the embedded
|
||||
* application i.e. example to access the partition or Host PC accesses the
|
||||
* partition over USB MSC. They can't be allowed to access the partition at the
|
||||
* same time. For different scenarios and behaviour, Refer to README of this
|
||||
* example.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_partition.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "tinyusb.h"
|
||||
#include "tinyusb_default_config.h"
|
||||
#include "tinyusb_msc.h"
|
||||
|
||||
/*
|
||||
* We warn if a secondary serial console is enabled. A secondary serial console
|
||||
* is always output-only and hence not very useful for interactive console
|
||||
* applications. If you encounter this warning, consider disabling the secondary
|
||||
* serial console in menuconfig unless you know what you are doing.
|
||||
*/
|
||||
#if SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
#if !CONFIG_ESP_CONSOLE_SECONDARY_NONE
|
||||
#warning \
|
||||
"A secondary serial console is not useful when using the console component. Please disable it in menuconfig."
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static const char* TAG = "msc";
|
||||
static esp_console_repl_t* repl = NULL;
|
||||
|
||||
/* Storage global variables */
|
||||
tinyusb_msc_storage_handle_t storage_hdl = NULL;
|
||||
tinyusb_msc_mount_point_t mp;
|
||||
|
||||
static SemaphoreHandle_t _wait_console_smp = NULL;
|
||||
|
||||
/* TinyUSB descriptors
|
||||
********************************************************************* */
|
||||
#define EPNUM_MSC 1
|
||||
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
|
||||
|
||||
enum { ITF_NUM_MSC = 0, ITF_NUM_TOTAL };
|
||||
|
||||
enum {
|
||||
EDPT_CTRL_OUT = 0x00,
|
||||
EDPT_CTRL_IN = 0x80,
|
||||
|
||||
EDPT_MSC_OUT = 0x01,
|
||||
EDPT_MSC_IN = 0x81,
|
||||
};
|
||||
|
||||
static tusb_desc_device_t descriptor_config = {
|
||||
.bLength = sizeof(descriptor_config),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = TUSB_CLASS_MISC,
|
||||
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed
|
||||
// according to Users / Customers
|
||||
.idProduct = 0x4002,
|
||||
.bcdDevice = 0x100,
|
||||
.iManufacturer = 0x01,
|
||||
.iProduct = 0x02,
|
||||
.iSerialNumber = 0x03,
|
||||
.bNumConfigurations = 0x01};
|
||||
|
||||
static uint8_t const msc_fs_configuration_desc[] = {
|
||||
// Config number, interface count, string index, total length, attribute,
|
||||
// power in mA
|
||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,
|
||||
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
||||
|
||||
// Interface number, string index, EP Out & EP In address, EP size
|
||||
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64),
|
||||
};
|
||||
|
||||
#if (TUD_OPT_HIGH_SPEED)
|
||||
static const tusb_desc_device_qualifier_t device_qualifier = {
|
||||
.bLength = sizeof(tusb_desc_device_qualifier_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = TUSB_CLASS_MISC,
|
||||
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
.bNumConfigurations = 0x01,
|
||||
.bReserved = 0};
|
||||
|
||||
static uint8_t const msc_hs_configuration_desc[] = {
|
||||
// Config number, interface count, string index, total length, attribute,
|
||||
// power in mA
|
||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,
|
||||
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
||||
|
||||
// Interface number, string index, EP Out & EP In address, EP size
|
||||
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 512),
|
||||
};
|
||||
#endif // TUD_OPT_HIGH_SPEED
|
||||
|
||||
static char const* string_desc_arr[] = {
|
||||
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
|
||||
"TinyUSB", // 1: Manufacturer
|
||||
"TinyUSB Device", // 2: Product
|
||||
"123456", // 3: Serials
|
||||
"Example MSC", // 4. MSC
|
||||
};
|
||||
/*********************************************************************** TinyUSB
|
||||
* descriptors*/
|
||||
|
||||
#define BASE_PATH "/sdcard" // base path to mount the partition
|
||||
|
||||
#define PROMPT_STR CONFIG_IDF_TARGET
|
||||
static int console_unmount(int argc, char** argv);
|
||||
static int console_read(int argc, char** argv);
|
||||
static int console_write(int argc, char** argv);
|
||||
static int console_size(int argc, char** argv);
|
||||
static int console_status(int argc, char** argv);
|
||||
static int console_exit(int argc, char** argv);
|
||||
const esp_console_cmd_t cmds[] = {
|
||||
{
|
||||
.command = "read",
|
||||
.help = "read BASE_PATH/README.MD and print its contents",
|
||||
.hint = NULL,
|
||||
.func = &console_read,
|
||||
},
|
||||
{
|
||||
.command = "write",
|
||||
.help = "create file BASE_PATH/README.MD if it does not exist",
|
||||
.hint = NULL,
|
||||
.func = &console_write,
|
||||
},
|
||||
{
|
||||
.command = "size",
|
||||
.help = "show storage size and sector size",
|
||||
.hint = NULL,
|
||||
.func = &console_size,
|
||||
},
|
||||
{
|
||||
.command = "expose",
|
||||
.help = "Expose Storage to Host",
|
||||
.hint = NULL,
|
||||
.func = &console_unmount,
|
||||
},
|
||||
{
|
||||
.command = "status",
|
||||
.help = "Status of storage exposure over USB",
|
||||
.hint = NULL,
|
||||
.func = &console_status,
|
||||
},
|
||||
{
|
||||
.command = "exit",
|
||||
.help = "exit from application",
|
||||
.hint = NULL,
|
||||
.func = &console_exit,
|
||||
}};
|
||||
|
||||
// Set mount point to the application and list files in BASE_PATH by filesystem
|
||||
// API
|
||||
static void mount(void) {
|
||||
ESP_LOGI(TAG, "Mount storage...");
|
||||
ESP_ERROR_CHECK(tinyusb_msc_set_storage_mount_point(
|
||||
storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP));
|
||||
|
||||
// List all the files in this directory
|
||||
ESP_LOGI(TAG, "\nls command output:");
|
||||
struct dirent* d;
|
||||
DIR* dh = opendir(BASE_PATH);
|
||||
if (!dh) {
|
||||
if (errno == ENOENT) {
|
||||
// If the directory is not found
|
||||
ESP_LOGE(TAG, "Directory doesn't exist %s", BASE_PATH);
|
||||
} else {
|
||||
// If the directory is not readable then throw error and exit
|
||||
ESP_LOGE(TAG, "Unable to read directory %s", BASE_PATH);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// While the next entry is not readable we will print directory files
|
||||
while ((d = readdir(dh)) != NULL) {
|
||||
printf("%s\n", d->d_name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// unmount storage
|
||||
static int console_unmount(int argc, char** argv) {
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_mount_point(storage_hdl, &mp));
|
||||
if (mp == TINYUSB_MSC_STORAGE_MOUNT_USB) {
|
||||
ESP_LOGE(TAG, "Storage is already exposed");
|
||||
return -1;
|
||||
}
|
||||
ESP_LOGI(TAG, "Unmount storage...");
|
||||
ESP_ERROR_CHECK(tinyusb_msc_set_storage_mount_point(
|
||||
storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// read BASE_PATH/README.MD and print its contents
|
||||
static int console_read(int argc, char** argv) {
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_mount_point(storage_hdl, &mp));
|
||||
if (mp == TINYUSB_MSC_STORAGE_MOUNT_USB) {
|
||||
ESP_LOGE(TAG,
|
||||
"Storage exposed over USB. Application can't read from storage.");
|
||||
return -1;
|
||||
}
|
||||
ESP_LOGD(TAG, "read from storage:");
|
||||
const char* filename = BASE_PATH "/README.MD";
|
||||
FILE* ptr = fopen(filename, "r");
|
||||
if (ptr == NULL) {
|
||||
ESP_LOGE(TAG, "Filename not present - %s", filename);
|
||||
return -1;
|
||||
}
|
||||
char buf[1024];
|
||||
while (fgets(buf, 1000, ptr) != NULL) {
|
||||
printf("%s", buf);
|
||||
}
|
||||
fclose(ptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// create file BASE_PATH/README.MD if it does not exist
|
||||
static int console_write(int argc, char** argv) {
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_mount_point(storage_hdl, &mp));
|
||||
if (mp == TINYUSB_MSC_STORAGE_MOUNT_USB) {
|
||||
ESP_LOGE(TAG,
|
||||
"storage exposed over USB. Application can't write to storage.");
|
||||
return -1;
|
||||
}
|
||||
ESP_LOGD(TAG, "write to storage:");
|
||||
const char* filename = BASE_PATH "/README.MD";
|
||||
FILE* fd = fopen(filename, "r");
|
||||
if (!fd) {
|
||||
ESP_LOGW(TAG, "README.MD doesn't exist yet, creating");
|
||||
fd = fopen(filename, "w");
|
||||
fprintf(fd,
|
||||
"Mass Storage Devices are one of the most common USB devices. It "
|
||||
"use Mass Storage Class (MSC) that allow access to their internal "
|
||||
"data storage.\n");
|
||||
fprintf(fd,
|
||||
"In this example, ESP chip will be recognised by host (PC) as Mass "
|
||||
"Storage Device.\n");
|
||||
fprintf(fd,
|
||||
"Upon connection to USB host (PC), the example application will "
|
||||
"initialize the storage module and then the storage will be seen "
|
||||
"as removable device on PC.\n");
|
||||
fclose(fd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Show storage size and sector size
|
||||
static int console_size(int argc, char** argv) {
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_mount_point(storage_hdl, &mp));
|
||||
if (mp == TINYUSB_MSC_STORAGE_MOUNT_USB) {
|
||||
ESP_LOGE(TAG, "storage exposed over USB. Application can't access storage");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t sec_count;
|
||||
uint32_t sec_size;
|
||||
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_sector_size(storage_hdl, &sec_size));
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_capacity(storage_hdl, &sec_count));
|
||||
|
||||
// Calculate size in MB or KB
|
||||
uint64_t total_bytes = (uint64_t)sec_size * sec_count;
|
||||
if (total_bytes >= (1024 * 1024)) {
|
||||
uint64_t total_mb = total_bytes / (1024 * 1024);
|
||||
printf("Storage Capacity %lluMB\n", total_mb);
|
||||
} else {
|
||||
uint64_t total_kb = total_bytes / 1024;
|
||||
printf("Storage Capacity %lluKB\n", total_kb);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Show storage status
|
||||
static int console_status(int argc, char** argv) {
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_mount_point(storage_hdl, &mp));
|
||||
printf("storage exposed over USB: %s\n",
|
||||
(mp == TINYUSB_MSC_STORAGE_MOUNT_USB) ? "Yes" : "No");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Exit from application
|
||||
static int console_exit(int argc, char** argv) {
|
||||
ESP_ERROR_CHECK(tinyusb_msc_delete_storage(storage_hdl));
|
||||
ESP_ERROR_CHECK(tinyusb_driver_uninstall());
|
||||
|
||||
xSemaphoreGive(_wait_console_smp);
|
||||
|
||||
printf("Application Exit\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Storage mount changed callback
|
||||
*
|
||||
* @param handle Storage handle
|
||||
* @param event Event information
|
||||
* @param arg User argument, provided during callback registration
|
||||
*/
|
||||
static void storage_mount_changed_cb(tinyusb_msc_storage_handle_t handle,
|
||||
tinyusb_msc_event_t* event, void* arg) {
|
||||
switch (event->id) {
|
||||
case TINYUSB_MSC_EVENT_MOUNT_START:
|
||||
// Verify that all the files are closed before unmounting
|
||||
break;
|
||||
case TINYUSB_MSC_EVENT_MOUNT_COMPLETE:
|
||||
ESP_LOGI(
|
||||
TAG, "Storage mounted to application: %s",
|
||||
(event->mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP) ? "Yes" : "No");
|
||||
break;
|
||||
case TINYUSB_MSC_EVENT_MOUNT_FAILED:
|
||||
case TINYUSB_MSC_EVENT_FORMAT_REQUIRED:
|
||||
ESP_LOGE(TAG, "Storage mount failed or format required");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void init_msc(sdmmc_card_t* card) {
|
||||
ESP_LOGI(TAG, "Initializing storage...");
|
||||
|
||||
_wait_console_smp = xSemaphoreCreateBinary();
|
||||
if (_wait_console_smp == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create semaphore");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial mount point to APP
|
||||
tinyusb_msc_storage_config_t storage_cfg = {
|
||||
.mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB,
|
||||
.fat_fs =
|
||||
{
|
||||
.base_path = NULL, // Use default base path
|
||||
.config.max_files = 5, // Maximum number of files that can be
|
||||
// opened simultaneously
|
||||
.format_flags = 0, // No special format flags
|
||||
},
|
||||
};
|
||||
|
||||
storage_cfg.medium.card = card;
|
||||
ESP_ERROR_CHECK(tinyusb_msc_new_storage_sdmmc(&storage_cfg, &storage_hdl));
|
||||
|
||||
ESP_ERROR_CHECK(
|
||||
tinyusb_msc_set_storage_callback(storage_mount_changed_cb, NULL));
|
||||
// Re-mount to the APP
|
||||
mount();
|
||||
|
||||
ESP_LOGI(TAG, "USB MSC initialization");
|
||||
|
||||
tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG();
|
||||
|
||||
tusb_cfg.descriptor.device = &descriptor_config;
|
||||
tusb_cfg.descriptor.full_speed_config = msc_fs_configuration_desc;
|
||||
tusb_cfg.descriptor.string = string_desc_arr;
|
||||
tusb_cfg.descriptor.string_count =
|
||||
sizeof(string_desc_arr) / sizeof(string_desc_arr[0]);
|
||||
#if (TUD_OPT_HIGH_SPEED)
|
||||
tusb_cfg.descriptor.high_speed_config = msc_hs_configuration_desc;
|
||||
tusb_cfg.descriptor.qualifier = &device_qualifier;
|
||||
#endif // TUD_OPT_HIGH_SPEED
|
||||
|
||||
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
|
||||
|
||||
ESP_LOGI(TAG, "USB MSC initialization DONE");
|
||||
|
||||
ESP_ERROR_CHECK(tinyusb_msc_get_storage_mount_point(storage_hdl, &mp));
|
||||
if (mp == TINYUSB_MSC_STORAGE_MOUNT_USB) {
|
||||
ESP_LOGE(TAG, "storage exposed over USB. Application can't access.");
|
||||
}
|
||||
|
||||
#if 1
|
||||
// Init console based on menuconfig settings
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
/* Prompt to be printed before each line.
|
||||
* This can be customized, made dynamic, etc.
|
||||
*/
|
||||
repl_config.prompt = PROMPT_STR ">";
|
||||
repl_config.max_cmdline_length = 64;
|
||||
|
||||
esp_console_dev_uart_config_t hw_config =
|
||||
ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
|
||||
|
||||
for (int count = 0; count < sizeof(cmds) / sizeof(esp_console_cmd_t);
|
||||
count++) {
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmds[count]));
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
|
||||
xSemaphoreTake(_wait_console_smp, portMAX_DELAY);
|
||||
ESP_ERROR_CHECK(esp_console_stop_repl(repl));
|
||||
vSemaphoreDelete(_wait_console_smp);
|
||||
#endif
|
||||
}
|
||||
Loading…
Reference in New Issue