简介
本文将会介绍如何使用ESP32-S3使用I2S音频协议读取麦克风INMP441并且通过MAX98357模块将麦克风读取到的数据进行播放。在这篇文章中你将会学习到如何使用ESP-IDF的旧版API driver/i2s.h 库来实现上述功能。(新版本库的老版本的库功能上实际上没有什么不同,新版本的库只不过是将driver/i2s.h库按照了其中不同格式的I2S进行了拆分)
模块简介
1-ESP32S3开发板
这块开发板已经被我开源到了立创开源广场,开源链接为:https://oshwhub.com/chongwang/christmas-tree-lights。 模块采用的是ESP32-S3-N4, 上图为旧版本的照片。 功能已经经过测试,目前的开源链接全部功能无误。 之后的系列教程中如果没有其他的需求等,会继续使用这个开发板进行测试。实际上使用其他版本的ESP32-S3模组也是可以的(个人没有经过测试)。在乐鑫官方的https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_cn.pdf数据手册中我们可以找到这块开发板的详细说明和介绍。但是由于我们这次使用的是它的I2S功能因此我们仅仅关注其I2S部分。
2-INMP441
INMP441 是一款高性能、低功耗的数字 MEMS 麦克风,采用 PDM 接口输出数字音频信号,信噪比高达 61 dB,频率响应范围为 60 Hz 至 15 kHz,能够提供清晰的音频捕捉。其工作电流低至 650 µA,封装尺寸仅为 3.76 mm x 4.72 mm x 1.0 mm,非常适合智能手机、平板电脑、耳机等对功耗和空间要求严格的消费电子设备。
如果我们购买的成品模块如上图所示的话,那么它一共具备6个外部引脚。他们分别为
L/R : 左右声道切换,悬空或者接GND则为左声道, 在左声道的模式下其I2S数据的右声道数据都为0, 如果为右声道的话则左声道数据全为0
WS:WS引脚为Word select,在I2S通讯中负责区分左右声道和数据帧的开始
SCK:时钟信号
SD:I2S输出信号
VDD:电源
GND:GND
其中L/R 可以进行悬空,其他的线为必选。
3-MAX98357模块
MAX98357是一款高品质、低功耗的立体声音频放大器芯片,由Maxim Integrated生产。 该芯片具有内置的数字音频接口(I2S),可与各种数字音频设备配合使用。 MAX98357能够提供高达3.2W的输出功率,并具有优秀的失真和噪声性能。 此外,该芯片还支持多种电源电压,包括3.3V、5V和+/-5V等。 (数据介绍来自互联网)
如果是在某宝进行购买的话成品将和上述非常相似,大概可以驱动4欧到8欧的喇叭进行音频播放。它一共具备七个引脚。分别是
LRC : 接WS 引脚,进行左右声道识别
BCLK:时钟信号,可以和INMP441的连接在一起。
DIN:为I2S数据输入信号,接ESP32的I2S数据输出引脚。
GAIN:为输出增益引脚。这里不涉及使用
SO:为关断输出引脚,这里不涉及使用
GND: 电源地
VIN:3v3 或 5v5
代码部分及其讲解说明
在我们正式开始写代码之前最好来查阅ESP-IDF的官方文档https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4/esp32s3/api-reference/peripherals/i2s.html
上图介绍了ESP32-S3的通讯模式和标准模式下的不同方式的通信时序等。
接下来我们快速使用ESP-IDF来新建一个工程。
在上述的选择模板处选择使用I2S的模板进行新建,此处仅仅是为了让其自动包含I2S相关的库。得益于ESP32的IO交换矩阵,我们可以使用任意的引脚来初始化I2S相关的PIN。因此首先删除原本的代码, 在程序中定义需要使用的引脚信息和我们需要的采样率信息等等
#define I2S_WS_GPIO 6 // LRCLK #define I2S_SCK_GPIO 7 // BCLK #define I2S_SD_GPIO 4 // INMP441 的 DOUT (数据输入) #define I2S_DOUT_GPIO 5 // MAX98357 的 DIN (数据输出) #define I2S_SAMPLE_RATE 44100 // 采样率 #define I2S_BUFFER_SIZE 512 // 缓冲区大小
接下来我们需要初始化I2S信息, 由于我们想要实现的效果为从INMP441接收数据,然后再将数据发送到MAX98357因此I2S的模式需要配置为双向的数据,且作为主机还需要将其配置为master模式。同时由于INMP441的声道选择引脚悬空,则为左声道因此。还需要配置为左声道模式。
i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX), // 主模式,接收和发送 .sample_rate = I2S_SAMPLE_RATE, // 采样率 .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // 32 位数据 .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // 单声道(左声道) .communication_format = I2S_COMM_FORMAT_STAND_I2S, // 标准 I2S 格式 .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // 中断优先级 .dma_buf_count = 8, // DMA 缓冲区数量 .dma_buf_len = I2S_BUFFER_SIZE, // DMA 缓冲区长度 .use_apll = false // 不使用 APLL }; i2s_pin_config_t pin_config = { .bck_io_num = I2S_SCK_GPIO, // 位时钟 .ws_io_num = I2S_WS_GPIO, // 左右声道时钟 .data_out_num = I2S_DOUT_GPIO, // 数据输出(连接到 MAX98357 的 DIN) .data_in_num = I2S_SD_GPIO // 数据输入(连接到 INMP441 的 DOUT) };
之后便是初始化I2S驱动
// 初始化 I2S i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); i2s_set_pin(I2S_NUM_0, &pin_config);
对于数据的读取和写入我们则可以调用I2s_read() 来读取来自INMP441的数据。它一共接收五个数据, 分别是I2S端口(第几个I2S)、读取数据的缓冲区, 缓冲区的大小, 读取的数据大小和读取数据的延时。其I2s_wirte()方法也是相似只不过read方法是从INMP441读取数据到缓冲区,而wirte方法在当前的程序代码中是将数据从缓冲区进行读取然后写入到I2S总线上到MAX98357.
int32_t sample = 0; size_t bytes_read = 0; size_t bytes_written = 0;
定义上述全局变量用于数据的保存和写入。
while (1) { // 从 INMP441 读取音频数据 i2s_read(I2S_NUM_0, &sample, sizeof(sample), &bytes_read, portMAX_DELAY); // 将音频数据发送到 MAX98357 if (bytes_read > 0) { i2s_write(I2S_NUM_0, &sample, sizeof(sample), &bytes_written, portMAX_DELAY); } }
之后便可以完成上述所有的功能, 读取INMP441的数据到MAX98357.
完整接线图如下(并没有参考意义,因为得益于IO交换矩阵, 可以使用任意的PIN来初始化I2S)
完整项目工程如下
视频链接如下
https://www.bilibili.com/video/BV1tRwUe7Eo9/