当前位置: 首页 > article >正文

SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动

飞书文档https://x509p6c8to.feishu.cn/wiki/SKZzwIRH3i7lsckUOlzcuJsdnVf

I2S简介

I2S(Inter-Integrated Circuit Sound)是一种用于传输数字音频数据的通信协议,广泛应用于音频设备中。

ESP32-S3 包含 2 个 I2S 外设,通过配置这些外设,可以借助 I2S 驱动来输入和输出采样数据。

SP32-S3 的 I2S 外设支持多种模式,适用于不同的音频应用场景:

  • 标准 I2S 模式:适用于大多数立体声音频设备。
  • PDM 模式:适用于数字麦克风等低功耗设备。
  • TDM 模式:适用于多声道音频系统。
标准 I2S 模式
作用:标准 I2S 模式是最常用的音频传输模式,支持立体声数据传输。数据帧包括左右声道信息,适用于大多数音频设备。
应用场景:
音频 DAC/ADC 接口(如连接扬声器或麦克风)。
与音频编解码器通信(如 ESP32-S3 与外部音频芯片的通信)。
立体声音频播放或录制。PDM(Pulse Density Modulation)模式
作用:PDM 是一种单比特数据流格式,通过高采样率表示音频信号的幅度。PDM 数据需要转换为 PCM 格式后才能使用。
应用场景:
数字麦克风接口(如 MEMS 麦克风)。
低功耗音频采集设备。
需要高采样率但数据量较小的场景。TDM(Time Division Multiplexing)模式
作用:TDM 模式通过时分复用技术支持多声道音频传输。数据帧被划分为多个时隙,每个时隙对应一个声道。
应用场景:
多声道音频系统(如家庭影院、环绕声系统)。
需要同时传输多个音频信号的场景(如多麦克风阵列)。
与多声道音频编解码器通信。

线序说明:

标准或 TDM 通信模式下的 I2S 总线包含以下几条线路:

  • MCLK:主时钟线。该信号线可选,具体取决于从机,主要用于向 I2S 从机提供参考时钟。
  • BCLK:位时钟线。用于数据线的位时钟。
  • WS/LRCLK:字(声道)选择线。通常用于识别声道(除 PDM 模式外)。
  • DIN/DOUT:串行数据输入/输出线。如果 DIN 和 DOUT 被配置到相同的 GPIO,数据将在内部回环。

PDM 通信模式下的 I2S 总线包含以下几条线路:

  • CLK:PDM 时钟线。
  • DIN/DOUT:串行数据输入/输出线。

如何选择合适的I2S模式?

对于ESP32S3来说,很多时候都是作为主机端,采集麦克风的声音,或者通过功放播放声音,此时应该如何选择模式呢?这就要看我们的外设电路了,拿麦克风举例,

如果我们直接用PDM MEMS数字麦克风,例如ST的MP34DT05,这颗数字麦支持PDM协议,那我们就选择PDM模式,MIC使用PDM RX

如果我们接的是ES8311这颗Codec芯片,这颗芯片支持一路ADC模拟MIC输入,一路DAC,可以理解为把上方的NS4168集成一起了,这颗芯片是支持I2S模式的,这时候我们就选择标准I2S模式。

如果我们需要多通道音频采集,例如使用ES7210芯片,这颗芯片支持TDM协议,那我们就选择TDM模式

ESP32S3有两个I2SI2S0/1各支持什么模式?

芯片I2S 标准PDM TXPDM RXTDM
ESP32I2S 0/1I2S 0I2S 0
ESP32-S2I2S 0
ESP32-C3I2S 0I2S 0I2S 0
ESP32-C6I2S 0I2S 0I2S 0
ESP32-S3I2S 0/1I2S 0I2S 0I2S 0/1
ESP32-H2I2S 0I2S 0I2S 0
ESP32-P4I2S 0~2I2S 0I2S 0I2S 0~2
ESP32-C5I2S 0I2S 0I2S 0I2S 0
ESP32-C61I2S 0I2S 0I2S 0I2S 0

前面我们根据外设电路确定了I2S的模式,例如我们外设是ES8311,我们选择TDM(标准模式)

I2S标准模式

ESP32S3标准模式下支持多种数据格式:

标准模式中有且仅有左右两个声道,驱动中将声道称为 slot。这些声道可以支持 8/16/24/32 位宽的采样数据,声道的通信格式主要包括以下几种:

  • Philips 格式(常用)
  • MSB 格式
  • PCM 帧同步格式

如何选择这些格式呢?其实主要是根据外设支持的模式来选用的,我们逐个模式了解下”

Philips 格式(常用)

  • Philips 格式(常用):数据信号与 WS 信号相比有一个位的位移。WS 信号的占空比为 50%。

data_bit_width:数据位宽

slot_bit_width:声道位宽

在 I2S 时序图中:

  • data_bit_width 决定了有效数据的长度。
  • slot_bit_width 决定了每个声道数据占用的总位数(包括填充位)。

例如:

  • 如果 data_bit_width = 24slot_bit_width = 32,时序图可能如下:
WS:   0         1         0         1
SCK: _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
SD:  00000000XXXXXXXX|00000000YYYYYYYY填充位  左声道   填充位  右声道

MSB 格式

与 Philips 格式基本相同,但其数据没有位移。

PCM 帧同步

数据有一个位的位移,同时 WS 信号变成脉冲,持续一个 BCLK 周期。

例如:

ES8311支持以下四种数据格式, 标准I2S(Philips格式),左对齐,右对齐,PCM格式,这些模式可以通过芯片的I2C接口进行配置,如果我们配置为 标准I2S(Philips格式),那ESP32端也配置为对应角色。

那如何通过代码配置呢?我们可以参考下方图:

I2S 通道有三种状态,分别为 registered(已注册)ready(准备就绪)running(运行中),它们的关系如下图所示:

I2S标准模式-Philips格式初始化代码如下:

static esp_err_t i2s_driver_init(void)
{// 配置I2S通道// 使用默认配置,指定I2S编号和主模式i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);// 启用自动清除DMA缓冲区中的遗留数据chan_cfg.auto_clear = true;// 创建一个新的I2S通道,并将返回的发送和接收通道句柄分别存储在tx_handle和rx_handle中ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));// 配置标准I2S模式i2s_std_config_t std_cfg = {// 设置时钟配置,使用默认的标准I2S时钟配置,并根据EXAMPLE_SAMPLE_RATE配置采样率.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),// 设置槽位配置,使用默认的Philips标准槽位配置,16位数据宽度和立体声模式.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),// 设置GPIO配置,指定各个I2S信号的GPIO引脚,并配置信号不反转.gpio_cfg = {.mclk = I2S_MCK_IO,  // 主时钟引脚.bclk = I2S_BCK_IO,  // 位时钟引脚.ws = I2S_WS_IO,     // 左右声道选择引脚.dout = I2S_DO_IO,   // 数据输出引脚.din = I2S_DI_IO,    // 数据输入引脚.invert_flags = {.mclk_inv = false, // 主时钟不反转.bclk_inv = false, // 位时钟不反转.ws_inv = false,   // 左右声道选择信号不反转},},};// 设置主时钟的倍数std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE;// 初始化发送通道为标准I2S模式ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));// 初始化接收通道为标准I2S模式ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));// 启用发送通道ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));// 启用接收通道ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}

配置完I2S模式后,我们还需要通过IIC初始化ES8311,对于这个音频芯片,ESP32官方封装成组件形式,我们是可以直接使用的。直接在工程目录下执行

idf.py add-dependency "espressif/es8311^1.0.0"

就会生成idf_component.yml文件

## IDF Component Manager Manifest File
dependencies:## Required IDF versionidf:version: '>=4.1.0'# # Put list of dependencies here# # For components maintained by Espressif:# component: "~1.0.0"# # For 3rd party components:# username/component: ">=1.0.0,<2.0.0"# username2/component2:#   version: "~1.0.0"#   # For transient dependencies `public` flag can be set.#   # `public` flag doesn't have an effect dependencies of the `main` component.#   # All dependencies of `main` are public by default.#   public: trueespressif/es8311: ^1.0.0

此时执行下方指令,就会自动下载该库到工程demo/managed_components文件夹中,这里面就是这个器件驱动的源码。

idf.py reconfigure

组件库文档说明:

https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4/esp32/api-guides/tools/idf-component-manager.html

更多组件库参考:

https://components.espressif.com/

在这里,我们也可以看到ESP32生态的优势,

  • 对于外设传感器、芯片厂家,我们可以提交自己芯片的驱动组件到官方组件库。
  • 对于开发者,可以快速使用对应组件完成项目开发,当然你也可以制作组件提交或者自定义本地组件。

组件的架构,也可以解耦不同的芯片平台,解耦不同的项目,把业务层和驱动层完全分离开来,加速开发。

添加组件到本地后,我们就可以进行初始化配置了,下面来看看如何实现一个本地音频播放功能

demo/main/CMakeLists.txt添加音频文件,音频文件位于esp-idf/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/canon.pcm

idf_component_register(SRCS "main.c"INCLUDE_DIRS "."EMBED_FILES "canon.pcm")

实现音频播放代码如下:

#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "esp_system.h"
#include "esp_check.h"
#include "es8311.h"/* Example configurations */
#define EXAMPLE_SAMPLE_RATE (16000)                                        // 音频采样率,采样率被设置为16000 Hz,即每秒采样16000次。
#define EXAMPLE_MCLK_MULTIPLE (384)                                        // 主时钟频率是采样率的倍数,用于驱动I2S接口。MCLK的倍数被设置为384。这意味着主时钟频率将是采样率的384倍。如果数据宽度不是24位,256倍数可能已经足够。
#define EXAMPLE_MCLK_FREQ_HZ (EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE) // 主时钟的频率
#define EXAMPLE_VOICE_VOLUME 90                                            // 音量,控制输出音量的大小。
#define EXAMPLE_MIC_GAIN ES8311_MIC_GAIN_0DB                               // 麦克风增益/* I2C port and GPIOs */
#define I2C_NUM (0)
#define I2C_SCL_IO (GPIO_NUM_5)
#define I2C_SDA_IO (GPIO_NUM_7)
/* I2S port and GPIOs */
#define I2S_NUM (0)
#define I2S_MCK_IO (GPIO_NUM_6)
#define I2S_BCK_IO (GPIO_NUM_14)
#define I2S_WS_IO (GPIO_NUM_12)
#define I2S_DO_IO (GPIO_NUM_11)
#define I2S_DI_IO (GPIO_NUM_13)#define SPKER_CTRL_PIN    GPIO_NUM_10
#define SPKER_CTRL_PIN_SEL  (1ULL<<SPKER_CTRL_PIN)static const char *TAG = "i2s_es8311";
static i2s_chan_handle_t tx_handle = NULL;
static i2s_chan_handle_t rx_handle = NULL;extern const uint8_t music_pcm_start[] asm("_binary_canon_pcm_start");
extern const uint8_t music_pcm_end[] asm("_binary_canon_pcm_end");static esp_err_t es8311_codec_init(void)
{/* 初始化I2C外设 */const i2c_config_t es_i2c_cfg = {.sda_io_num = I2C_SDA_IO,            // SDA引脚编号.scl_io_num = I2C_SCL_IO,            // SCL引脚编号.mode = I2C_MODE_MASTER,             // I2C模式为主模式.sda_pullup_en = GPIO_PULLUP_ENABLE, // 启用SDA引脚的上拉电阻.scl_pullup_en = GPIO_PULLUP_ENABLE, // 启用SCL引脚的上拉电阻.master.clk_speed = 100000,          // I2C主时钟速度为100 kHz};// 配置I2C参数ESP_RETURN_ON_ERROR(i2c_param_config(I2C_NUM, &es_i2c_cfg), TAG, "config i2c failed");// 安装I2C驱动ESP_RETURN_ON_ERROR(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0), TAG, "install i2c driver failed");// 初始化ES8311编解码器 创建ES8311句柄,使用I2C_NUM和ES8311的地址es8311_handle_t es_handle = es8311_create(I2C_NUM, ES8311_ADDRRES_0);ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, TAG, "es8311 create failed");// 配置ES8311的时钟const es8311_clock_config_t es_clk = {.mclk_inverted = false,                 // 主时钟不反转.sclk_inverted = false,                 // 位时钟不反转.mclk_from_mclk_pin = true,             // 主时钟从MCLK引脚获取.mclk_frequency = EXAMPLE_MCLK_FREQ_HZ, // 主时钟频率.sample_frequency = EXAMPLE_SAMPLE_RATE // 采样频率};// 初始化ES8311编解码器ESP_ERROR_CHECK(es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));// 配置ES8311的采样频率ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(es_handle, EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE, EXAMPLE_SAMPLE_RATE), TAG, "set es8311 sample frequency failed");// 设置ES8311的音量ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, EXAMPLE_VOICE_VOLUME, NULL), TAG, "set es8311 volume failed");// 配置ES8311的麦克风ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), TAG, "set es8311 microphone failed");// 设置ES8311的麦克风增益ESP_RETURN_ON_ERROR(es8311_microphone_gain_set(es_handle, EXAMPLE_MIC_GAIN), TAG, "set es8311 microphone gain failed");return ESP_OK;
}static esp_err_t i2s_driver_init(void)
{// 指定I2S编号和主模式i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);// 启用自动清除DMA缓冲区中的遗留数据chan_cfg.auto_clear = true;// 创建一个新的I2S通道,并将返回的发送和接收通道句柄分别存储在tx_handle和rx_handle中ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));// 配置标准I2S模式i2s_std_config_t std_cfg = {// 设置时钟配置,使用默认的标准I2S时钟配置,并根据EXAMPLE_SAMPLE_RATE配置采样率.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),// 设置槽位配置,使用默认的Philips标准槽位配置,16位数据宽度和立体声模式.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),// 设置GPIO配置,指定各个I2S信号的GPIO引脚,并配置信号不反转.gpio_cfg = {.mclk = I2S_MCK_IO, // 主时钟引脚.bclk = I2S_BCK_IO, // 位时钟引脚.ws = I2S_WS_IO,    // 左右声道选择引脚.dout = I2S_DO_IO,  // 数据输出引脚.din = I2S_DI_IO,   // 数据输入引脚.invert_flags = {.mclk_inv = false, // 主时钟不反转.bclk_inv = false, // 位时钟不反转.ws_inv = false,   // 左右声道选择信号不反转},},};// 设置主时钟的倍数std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE;// 初始化发送通道为标准I2S模式ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));// 初始化接收通道为标准I2S模式ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));// 启用发送通道ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));// 启用接收通道ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));return ESP_OK;
}static void i2s_music(void *args)
{esp_err_t ret = ESP_OK;size_t bytes_write = 0;uint8_t *data_ptr = (uint8_t *)music_pcm_start;// 禁用发送通道ESP_ERROR_CHECK(i2s_channel_disable(tx_handle));// 预加载数据到发送通道ESP_ERROR_CHECK(i2s_channel_preload_data(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write));// 移动数据指针data_ptr += bytes_write;// 启用发送通道ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));while (1){// 将音乐数据写入发送通道ret = i2s_channel_write(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write, portMAX_DELAY);if (ret != ESP_OK){ESP_LOGE(TAG, "[music] i2s write failed");abort(); // 终止程序}if (bytes_write > 0){ESP_LOGI(TAG, "[music] i2s music played, %d bytes are written.", bytes_write);}else{// 记录播放失败的日志ESP_LOGE(TAG, "[music] i2s music play failed.");abort(); // 终止程序}data_ptr = (uint8_t *)music_pcm_start; // 重置数据指针到音乐数据的起始位置vTaskDelay(1000 / portTICK_PERIOD_MS); // 延迟1秒}vTaskDelete(NULL); // 删除任务
}void app_main(void)
{gpio_config_t io_conf = {};io_conf.intr_type = GPIO_INTR_DISABLE;io_conf.mode = GPIO_MODE_OUTPUT;io_conf.pin_bit_mask = SPKER_CTRL_PIN_SEL;io_conf.pull_down_en = 0;io_conf.pull_up_en = 0;gpio_config(&io_conf);gpio_set_level(SPKER_CTRL_PIN, 1);printf("i2s es8311 codec example start\n-----------------------------\n");/* 初始化I2S外设 */if (i2s_driver_init() != ESP_OK){ESP_LOGE(TAG, "i2s driver init failed");abort(); // 终止程序}else{ESP_LOGI(TAG, "i2s driver init success");}/* 初始化I2C外设并配置ES8311编解码器 */if (es8311_codec_init() != ESP_OK){ESP_LOGE(TAG, "es8311 codec init failed");abort(); // 终止程序}else{ESP_LOGI(TAG, "es8311 codec init success");}/* 音乐播放任务 */xTaskCreate(i2s_music, "i2s_music", 4096, NULL, 5, NULL);
}

这个代码运行时,需要我们接上喇叭!这部分的功能不需要底板了,我们可以把核心板拆下来,接上喇叭,仅用USB供电即可。

然后烧录运行,就会重复播放音乐。

实现边录音边播放功能

上方的程序仅用到播放功能,如果我们需要使用麦克风功能呢?那我们就可以使用下方的程序,实现边录音边播放功能。

#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "esp_system.h"
#include "esp_check.h"
#include "es8311.h"/* Example configurations */
#define EXAMPLE_SAMPLE_RATE (16000)                                        // 音频采样率,采样率被设置为16000 Hz,即每秒采样16000次。
#define EXAMPLE_MCLK_MULTIPLE (384)                                        // 主时钟频率是采样率的倍数,用于驱动I2S接口。MCLK的倍数被设置为384。这意味着主时钟频率将是采样率的384倍。如果数据宽度不是24位,256倍数可能已经足够。
#define EXAMPLE_MCLK_FREQ_HZ (EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE) // 主时钟的频率
#define EXAMPLE_VOICE_VOLUME 90                                            // 音量,控制输出音量的大小。
#define EXAMPLE_MIC_GAIN ES8311_MIC_GAIN_0DB                               // 麦克风增益
#define EXAMPLE_RECV_BUF_SIZE   (2400)                                     // MIC接收缓冲区大小
/* I2C port and GPIOs */
/* I2C port and GPIOs */
#define I2C_NUM (0)
#define I2C_SCL_IO (GPIO_NUM_5)
#define I2C_SDA_IO (GPIO_NUM_7)
/* I2S port and GPIOs */
#define I2S_NUM (0)
#define I2S_MCK_IO (GPIO_NUM_6)
#define I2S_BCK_IO (GPIO_NUM_14)
#define I2S_WS_IO (GPIO_NUM_12)
#define I2S_DO_IO (GPIO_NUM_11)
#define I2S_DI_IO (GPIO_NUM_13)
#define SPKER_CTRL_PIN    GPIO_NUM_10
#define SPKER_CTRL_PIN_SEL  (1ULL<<SPKER_CTRL_PIN)static const char *TAG = "i2s_es8311";
static i2s_chan_handle_t tx_handle = NULL;
static i2s_chan_handle_t rx_handle = NULL;extern const uint8_t music_pcm_start[] asm("_binary_canon_pcm_start");
extern const uint8_t music_pcm_end[] asm("_binary_canon_pcm_end");static esp_err_t es8311_codec_init(void)
{/* 初始化I2C外设 */const i2c_config_t es_i2c_cfg = {.sda_io_num = I2C_SDA_IO,            // SDA引脚编号.scl_io_num = I2C_SCL_IO,            // SCL引脚编号.mode = I2C_MODE_MASTER,             // I2C模式为主模式.sda_pullup_en = GPIO_PULLUP_ENABLE, // 启用SDA引脚的上拉电阻.scl_pullup_en = GPIO_PULLUP_ENABLE, // 启用SCL引脚的上拉电阻.master.clk_speed = 100000,          // I2C主时钟速度为100 kHz};// 配置I2C参数ESP_RETURN_ON_ERROR(i2c_param_config(I2C_NUM, &es_i2c_cfg), TAG, "config i2c failed");// 安装I2C驱动ESP_RETURN_ON_ERROR(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0), TAG, "install i2c driver failed");// 初始化ES8311编解码器 创建ES8311句柄,使用I2C_NUM和ES8311的地址es8311_handle_t es_handle = es8311_create(I2C_NUM, ES8311_ADDRRES_0);ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, TAG, "es8311 create failed");// 配置ES8311的时钟const es8311_clock_config_t es_clk = {.mclk_inverted = false,                 // 主时钟不反转.sclk_inverted = false,                 // 位时钟不反转.mclk_from_mclk_pin = true,             // 主时钟从MCLK引脚获取.mclk_frequency = EXAMPLE_MCLK_FREQ_HZ, // 主时钟频率.sample_frequency = EXAMPLE_SAMPLE_RATE // 采样频率};// 初始化ES8311编解码器ESP_ERROR_CHECK(es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));// 配置ES8311的采样频率ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(es_handle, EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE, EXAMPLE_SAMPLE_RATE), TAG, "set es8311 sample frequency failed");// 设置ES8311的音量ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, EXAMPLE_VOICE_VOLUME, NULL), TAG, "set es8311 volume failed");// 配置ES8311的麦克风ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), TAG, "set es8311 microphone failed");// 设置ES8311的麦克风增益ESP_RETURN_ON_ERROR(es8311_microphone_gain_set(es_handle, EXAMPLE_MIC_GAIN), TAG, "set es8311 microphone gain failed");return ESP_OK;
}static esp_err_t i2s_driver_init(void)
{// 指定I2S编号和主模式i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);// 启用自动清除DMA缓冲区中的遗留数据chan_cfg.auto_clear = true;// 创建一个新的I2S通道,并将返回的发送和接收通道句柄分别存储在tx_handle和rx_handle中ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));// 配置标准I2S模式i2s_std_config_t std_cfg = {// 设置时钟配置,使用默认的标准I2S时钟配置,并根据EXAMPLE_SAMPLE_RATE配置采样率.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),// 设置槽位配置,使用默认的Philips标准槽位配置,16位数据宽度和立体声模式.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),// 设置GPIO配置,指定各个I2S信号的GPIO引脚,并配置信号不反转.gpio_cfg = {.mclk = I2S_MCK_IO, // 主时钟引脚.bclk = I2S_BCK_IO, // 位时钟引脚.ws = I2S_WS_IO,    // 左右声道选择引脚.dout = I2S_DO_IO,  // 数据输出引脚.din = I2S_DI_IO,   // 数据输入引脚.invert_flags = {.mclk_inv = false, // 主时钟不反转.bclk_inv = false, // 位时钟不反转.ws_inv = false,   // 左右声道选择信号不反转},},};// 设置主时钟的倍数std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE;// 初始化发送通道为标准I2S模式ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));// 初始化接收通道为标准I2S模式ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));// 启用发送通道ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));// 启用接收通道ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));return ESP_OK;
}static void i2s_echo(void *args)
{// 分配接收缓冲区,大小为2400字节int *mic_data = malloc(EXAMPLE_RECV_BUF_SIZE);if (!mic_data) {ESP_LOGE(TAG, "[echo] No memory for read data buffer");abort(); // 终止程序}esp_err_t ret = ESP_OK;size_t bytes_read = 0;size_t bytes_write = 0;ESP_LOGI(TAG, "[echo] Echo start");while (1) {// 清空接收缓冲区memset(mic_data, 0, EXAMPLE_RECV_BUF_SIZE);/* 从麦克风读取样本数据 */ret = i2s_channel_read(rx_handle, mic_data, EXAMPLE_RECV_BUF_SIZE, &bytes_read, 1000);if (ret != ESP_OK) {ESP_LOGE(TAG, "[echo] i2s read failed");abort(); // 终止程序}/* 将样本数据写入播放器 */ret = i2s_channel_write(tx_handle, mic_data, EXAMPLE_RECV_BUF_SIZE, &bytes_write, 1000);if (ret != ESP_OK) {ESP_LOGE(TAG, "[echo] i2s write failed");abort(); // 终止程序}// 检查读取和写入的字节数是否一致if (bytes_read != bytes_write) {ESP_LOGW(TAG, "[echo] %d bytes read but only %d bytes are written", bytes_read, bytes_write);}}vTaskDelete(NULL); // 删除任务
}void app_main(void)
{printf("i2s es8311 codec example start\n-----------------------------\n");gpio_config_t io_conf = {};io_conf.intr_type = GPIO_INTR_DISABLE;io_conf.mode = GPIO_MODE_OUTPUT;io_conf.pin_bit_mask = SPKER_CTRL_PIN_SEL;io_conf.pull_down_en = 0;io_conf.pull_up_en = 0;gpio_config(&io_conf);gpio_set_level(SPKER_CTRL_PIN, 1);/* 初始化I2S外设 */if (i2s_driver_init() != ESP_OK){ESP_LOGE(TAG, "i2s driver init failed");abort(); // 终止程序}else{ESP_LOGI(TAG, "i2s driver init success");}/* 初始化I2C外设并配置ES8311编解码器 */if (es8311_codec_init() != ESP_OK){ESP_LOGE(TAG, "es8311 codec init failed");abort(); // 终止程序}else{ESP_LOGI(TAG, "es8311 codec init success");}/* 创建任务以回声模式从麦克风播放声音 */xTaskCreate(i2s_echo, "i2s_echo", 8192, NULL, 5, NULL);
}

编译烧录后,轻轻敲下桌面或USB头,就可以听到扬声器播放出敲击的声音。

I2S PDM模式

目前板卡是没有接支持PDM协议的麦克风的,所以这个实验在板卡上是做不了的,大家了解下即可。

在 PDM(Pulse-density Modulation,脉冲密度调制)模式下

TX 通道可以将 PCM 数据转换为 PDM 格式,该格式始终有左右两个声道。

RX 通道可以接收 PDM 格式的数据并将数据转换成 PCM 格式。

PDM TX RX 只在 I2S0 中受支持,且只支持 16 位宽的采样数据。。

PDM 至少需要一个 CLK 管脚用于时钟信号,一个 DOUT 管脚用于数据信号,即下图中的 WS 和 SD 信号。

PDM RX

i2s_chan_handle_t rx_handle = NULL;void init_microphone(void)
{// 使用默认配置,自动选择I2S编号,并设置为主模式i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);// 创建一个新的I2S通道,并将返回的通道句柄存储在rx_handle中ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));// 配置PDM接收模式i2s_pdm_rx_config_t pdm_rx_cfg = {// 设置时钟配置,使用默认的PDM接收时钟配置,配置采样率44100.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(44100),// 设置槽位配置,使用默认的16位数据宽度和单声道模式.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),// 设置GPIO配置,指定时钟引脚和数据输入引脚,并配置时钟不反转.gpio_cfg = {.clk = 18,  // 时钟引脚.din = 19, // 数据输入引脚.invert_flags = {.clk_inv = false, // 时钟不反转},},};// 初始化I2S通道为PDM接收模式ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg));// 启用I2S通道ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}

PDM TX

#include "driver/i2s_pdm.h"
#include "driver/gpio.h"i2s_chan_handle_t tx_handle = NULL;void init_microphone(void)
{// 自动选择I2S编号,并设置为主模式i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));/* 初始化通道为 PDM TX 模式 */i2s_pdm_tx_config_t pdm_tx_cfg = {// 设置时钟配置,使用默认的PDM接收时钟配置,配置采样率44100.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(44100),// 设置槽位配置,使用默认的16位数据宽度和单声道模式.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),// 设置GPIO配置,指定时钟引脚和数据输入引脚,并配置时钟不反转.gpio_cfg = {.clk = GPIO_NUM_5,.dout = GPIO_NUM_18,.invert_flags = {.clk_inv = false,},},};// 初始化I2S通道为PDM发送模式i2s_channel_init_pdm_tx_mode(tx_handle, &pdm_tx_cfg);// 启用I2S通道ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}
  • peripherals/i2s/i2s_recorder 演示了如何通过 I2S 外设以 PDM 数据格式用数字 MEMS 麦克风录制音频,并将其以 .wav 文件格式保存到 ESP32-S3 开发板上的 SD 卡中。
  • peripherals/i2s/i2s_basic/i2s_pdm 演示了如何在 ESP32-S3 上使用 PDM TX 模式,包括必要的硬件设置和配置。

相关文章:

SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动

飞书文档https://x509p6c8to.feishu.cn/wiki/SKZzwIRH3i7lsckUOlzcuJsdnVf I2S简介 I2S&#xff08;Inter-Integrated Circuit Sound&#xff09;是一种用于传输数字音频数据的通信协议&#xff0c;广泛应用于音频设备中。 ESP32-S3 包含 2 个 I2S 外设&#xff0c;通过配置…...

比较数据迁移后MySQL数据库和ClickHouse数据仓库中的表

设计一个MySQL数据库和Clickhouse数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

break 语句和 continue 语句

break语句和continue语句都具有跳转作用&#xff0c;可以让代码不按既有的顺序执行 break break语句用于跳出代码块或循环 1 2 3 4 5 6 for (var i 0; i < 5; i) { if (i 3){ break; } console.log(i); } continue continue语句用于立即终…...

使用 uv 工具快速部署并管理 vLLM 推理环境

uv&#xff1a;现代 Python 项目管理的高效助手 uv&#xff1a;Rust 驱动的 Python 包管理新时代 在部署大语言模型&#xff08;LLM&#xff09;推理服务时&#xff0c;vLLM 是一个备受关注的方案&#xff0c;具备高吞吐、低延迟和对 OpenAI API 的良好兼容性。为了提高部署效…...

更新 Docker 容器中的某一个文件

&#x1f504; 如何更新 Docker 容器中的某一个文件 以下是几种在 Docker 中更新单个文件的常用方法&#xff0c;适用于不同场景。 ✅ 方法一&#xff1a;使用 docker cp 拷贝文件到容器中&#xff08;最简单&#xff09; &#x1f9f0; 命令格式&#xff1a; docker cp <…...

【Linux】使用1Panel 面板让服务器定时自动执行任务

服务器就是一台24小时开机的主机&#xff0c;相比自己家中不定时开关机的主机更适合完成定时任务&#xff0c;例如下载资源、备份上传&#xff0c;或者登录某个网站执行一些操作&#xff0c;只需要编写 脚本&#xff0c;然后让服务器定时来执行这个脚本就可以。 有很多方法实现…...

Python爬虫(四):PyQuery 框架

PyQuery 框架详解与对比 BeautifulSoup 第一部分&#xff1a;PyQuery 框架介绍 1. PyQuery 是什么&#xff1f; PyQuery 是一个 Python 的 HTML/XML 解析库&#xff0c;它采用了 jQuery 的语法风格&#xff0c;让开发者能够用类似前端 jQuery 的方式处理文档解析。它的核心特…...

Excel 怎么让透视表以正常Excel表格形式显示

目录 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总...

LINUX编译vlc

下载 VideoLAN / VLC GitLab 选择最新的发布版本 准备 sudo apt install -y xcb bison sudo apt install -y autopoint sudo apt install -y autoconf automake libtool编译ffmpeg LINUX FFMPEG编译汇总&#xff08;最简化&#xff09;_底部的附件列表中】: ffmpeg - lzip…...

初级程序员入门指南

初级程序员入门指南 在数字化浪潮中&#xff0c;编程已然成为极具价值的技能。对于渴望踏入程序员行列的新手而言&#xff0c;明晰入门路径与必备知识是开启征程的关键。本文将为初级程序员提供全面的入门指引。 一、明确学习方向 &#xff08;一&#xff09;编程语言抉择 编…...

WinUI3开发_使用mica效果

简介 Mica(云母)是Windows10/11上的一种现代化效果&#xff0c;是Windows10/11上所使用的Fluent Design(设计语言)里的一个效果&#xff0c;Windows10/11上所使用的Fluent Design皆旨在于打造一个人类、通用和真正感觉与 Windows 一样的设计。 WinUI3就是Windows10/11上的一个…...

Python爬虫(52)Scrapy-Redis分布式爬虫架构实战:IP代理池深度集成与跨地域数据采集

目录 一、引言&#xff1a;当爬虫遭遇"地域封锁"二、背景解析&#xff1a;分布式爬虫的两大技术挑战1. 传统Scrapy架构的局限性2. 地域限制的三种典型表现 三、架构设计&#xff1a;Scrapy-Redis 代理池的协同机制1. 分布式架构拓扑图2. 核心组件协同流程 四、技术实…...

MyBatis-Plus 常用条件构造方法

1.常用条件方法 方法 说明eq等于 ne不等于 <>gt大于 >ge大于等于 >lt小于 <le小于等于 <betweenBETWEEN 值1 AND 值2notBetweenNOT BETWEEN 值1 AND 值2likeLIKE %值%notLikeNOT LIKE %值%likeLeftLIKE %值likeRightLIKE 值%isNull字段 IS NULLisNotNull字段…...

华为OD机考- 简单的自动曝光/平均像素

import java.util.Arrays; import java.util.Scanner;public class DemoTest4 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint[] arr Array…...

C/Python/Go示例 | Socket Programing与RPC

Socket Programming介绍 Computer networking这个领域围绕着两台电脑或者同一台电脑内的不同进程之间的数据传输和信息交流&#xff0c;会涉及到许多有意思的话题&#xff0c;诸如怎么确保对方能收到信息&#xff0c;怎么应对数据丢失、被污染或者顺序混乱&#xff0c;怎么提高…...

Spring是如何实现无代理对象的循环依赖

无代理对象的循环依赖 什么是循环依赖解决方案实现方式测试验证 引入代理对象的影响创建代理对象问题分析 源码见&#xff1a;mini-spring 什么是循环依赖 循环依赖是指在对象创建过程中&#xff0c;两个或多个对象相互依赖&#xff0c;导致创建过程陷入死循环。以下通过一个简…...

C++ Saucer 编写Windows桌面应用

文章目录 一、背景二、Saucer 简介核心特性典型应用场景 三、生成自己的项目四、以Win32项目方式构建Win32项目禁用最大化按钮 五、总结 一、背景 使用Saucer框架&#xff0c;开发Windows桌面应用&#xff0c;把一个html页面作为GUI设计放到Saucer里&#xff0c;隐藏掉运行时弹…...

中国政务数据安全建设细化及市场需求分析

(基于新《政务数据共享条例》及相关法规) 一、引言 近年来,中国政府高度重视数字政府建设和数据要素市场化配置改革。《政务数据共享条例》(以下简称“《共享条例》”)的发布,与《中华人民共和国数据安全法》(以下简称“《数据安全法》”)、《中华人民共和国个人信息…...

【AI News | 20250609】每日AI进展

AI Repos 1、OpenHands-Versa OpenHands-Versa 是一个通用型 AI 智能体&#xff0c;通过结合代码编辑与执行、网络搜索、多模态网络浏览和文件访问等通用工具&#xff0c;在软件工程、网络导航和工作流自动化等多个领域展现出卓越性能。它在 SWE-Bench Multimodal、GAIA 和 Th…...

轻量安全的密码管理工具Vaultwarden

一、Vaultwarden概述 Vaultwarden主要作用是提供一个自托管的密码管理器服务。它是Bitwarden密码管理器的第三方轻量版&#xff0c;由国外开发者在Bitwarden的基础上&#xff0c;采用Rust语言重写而成。 &#xff08;一&#xff09;Vaultwarden镜像的作用及特点 轻量级与高性…...

SQLSERVER-DB操作记录

在SQL Server中&#xff0c;将查询结果放入一张新表可以通过几种方法实现。 方法1&#xff1a;使用SELECT INTO语句 SELECT INTO 语句可以直接将查询结果作为一个新表创建出来。这个新表的结构&#xff08;包括列名和数据类型&#xff09;将与查询结果匹配。 SELECT * INTO 新…...

开疆智能Ethernet/IP转Modbus网关连接鸣志步进电机驱动器配置案例

在工业自动化控制系统中&#xff0c;常常会遇到不同品牌和通信协议的设备需要协同工作的情况。本案例中&#xff0c;客户现场采用了 罗克韦尔PLC&#xff0c;但需要控制的变频器仅支持 ModbusRTU 协议。为了实现PLC 对变频器的有效控制与监控&#xff0c;引入了开疆智能Etherne…...

NineData数据库DevOps功能全面支持百度智能云向量数据库 VectorDB,助力企业 AI 应用高效落地

NineData 的数据库 DevOps 解决方案已完成对百度智能云向量数据库 VectorDB 的全链路适配&#xff0c;成为国内首批提供 VectorDB 原生操作能力的服务商。此次合作聚焦 AI 开发核心场景&#xff0c;通过标准化 SQL 工作台与细粒度权限管控两大能力&#xff0c;助力企业安全高效…...

代理服务器-LVS的3种模式与调度算法

作者介绍&#xff1a;简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们上一章介绍了Web服务器&#xff0c;其中以Nginx为主&#xff0c;本章我们来讲解几个代理软件&#xff1a…...

ubuntu清理垃圾

windows和ubuntu 双系统&#xff0c;ubuntu 150GB&#xff0c;开发用&#xff0c;基本不装太多软件。但是磁盘基本用完。 1、查看home目录 sudo du -h -d 1 $HOME | grep -v K 上面的命令查看$HOME一级目录大小&#xff0c;发现 .cache 有26GB&#xff0c;.local 有几个GB&am…...

学习 Hooks【Plan - June - Week 2】

一、React API React 提供了丰富的核心 API&#xff0c;用于创建组件、管理状态、处理副作用、优化性能等。本文档总结 React 常用的 API 方法和组件。 1. React 核心 API React.createElement(type, props, …children) 用于创建 React 元素&#xff0c;JSX 会被编译成该函数…...

宠物车载安全座椅市场报告:解读行业趋势与投资前景

一、什么是宠物车载安全座椅&#xff1f; 宠物车载安全座椅是一种专为宠物设计的车内固定装置&#xff0c;旨在保障宠物在乘车过程中的安全性与舒适性。它通常由高强度材料制成&#xff0c;具备良好的缓冲性能&#xff0c;并可通过安全带或ISOFIX接口固定于车内。 近年来&…...

解决MybatisPlus使用Druid1.2.11连接池查询PG数据库报Merge sql error的一种办法

目录 前言 一、问题重现 1、环境说明 2、重现步骤 3、错误信息 二、关于LATERAL 1、Lateral作用场景 2、在四至场景中使用 三、问题解决之道 1、源码追踪 2、关闭sql合并 3、改写处理SQL 四、总结 前言 在博客&#xff1a;【写在创作纪念日】基于SpringBoot和PostG…...

Neo4j 完全指南:从入门到精通

第1章&#xff1a;Neo4j简介与图数据库基础 1.1 图数据库概述 传统关系型数据库与图数据库的对比图数据库的核心优势图数据库的应用场景 1.2 Neo4j的发展历史 Neo4j的起源与演进Neo4j的版本迭代Neo4j在图数据库领域的地位 1.3 图数据库的基本概念 节点(Node)与关系(Relat…...

day51 python CBAM注意力

目录 一、CBAM 模块简介 二、CBAM 模块的实现 &#xff08;一&#xff09;通道注意力模块 &#xff08;二&#xff09;空间注意力模块 &#xff08;三&#xff09;CBAM 模块的组合 三、CBAM 模块的特性 四、CBAM 模块在 CNN 中的应用 一、CBAM 模块简介 在之前的探索中…...