簡介
在這個系列的上一篇文章中,我們介紹了ESP32 I2S音頻總線的相關知識,簡要了解了什么是I2S總線、它的通信格式,以及相關的底層API函數。沒有看過上篇文章的可以點擊文章進行回顧:
ESP32 I2S音頻總線學習筆記(一):初識I2S通信與配置基礎
這篇文章將介紹一個小案例——ESP32驅動INMP441讀取音頻數據,它是關于如何使用I2S讀取數據的一個應用,主要是將ESP32讀取到的音頻數據發送到串口上并實時顯示波形,這個我們可以通過串口繪圖儀來實現。在這之前先來看一下INMP441這個模塊吧
INMP441全向麥克風模塊
NMP441是一款高性能、低功耗、數字輸出,帶底部端口的全向MEMS麥克風。該完整的INMP441解決方案由一個MEMS傳感器、信號調節模塊、數字混合濾波器、電源管理和行業標準的24位I2S接口組成。I2S接口允許INMP441直接連接到數字處理器,如DSP和微控制器,無需額外的音頻解碼器。NMP441具有高信噪比,是一種適用于近場應用的理想選擇。
產品特性:
具有高精度24位數據的數字I2S接口
高信噪比為61 dBA
高靈敏度-26 dBFS
從60 Hz到15 kHz的穩定頻率響應
低功耗:低電流消耗1.4 mA
高PSR:-75 dBFS
功能框圖
INMP441模塊使用到的芯片內置ADC,用于將采集到的模擬信號轉換成數字信號,上面還有濾波器和硬件控制、電源相關的引腳。
引腳定義
VDD | 輸入電源,1.8V至3.3V |
---|---|
GND | 電源地 |
SCK | I2S接口的串行數據時鐘 |
WS | 用于I2S接口的串行數據字選擇 |
SD | I2S接口的串行數據輸出 |
L/R | 左/右聲道選擇 |
其中L/R是 左/右聲道選擇。設置為低電平時,麥克風在I2S幀的左聲道輸出信號。設置為高電平時,麥克風在右聲道輸出信號。
安裝I2S驅動
上篇文章我們在介紹I2S底層API函數提到,在使用I2S通信時需要加載I2S驅動,不知道小伙伴們還記不記得。這個加載I2S驅動的函數就是:esp_err_t i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config, int queue_size, void *i2s_queue)
,里面有4個參數需要配置,在上次都有提到每個參數的意義。 其中比較復雜的是i2s_config這個結構體變量,我們需要對結構體的每個參數進行配置,如下:
typedef struct {i2s_mode_t mode; /*< 設置 I2S 的工作模式 */uint32_t sample_rate; /*!< 設置音頻采樣率 */i2s_bits_per_sample_t bits_per_sample; /*!< 設置采樣位數 */i2s_channel_fmt_t channel_format; /*!< 設置數據通道格式.*/i2s_comm_format_t communication_format; /*!< 設置I2C數據傳輸格式 */int intr_alloc_flags; /*!< 設置中斷相關標志位*/int dma_buf_count; dma緩存個數, int dma_buf_len;
} i2s_driver_config_t;typedef i2s_driver_config_t i2s_config_t;
除了后面幾個整型變量,每個結構體成員其實是一個枚舉類型
I2S工作模式mode
typedef enum {I2S_MODE_MASTER = (0x1 << 0), /*!< Master mode*/I2S_MODE_SLAVE = (0x1 << 1), /*!< Slave mode*/I2S_MODE_TX = (0x1 << 2), /*!< TX mode*/I2S_MODE_RX = (0x1 << 3), /*!< RX mode*/
#if SOC_I2S_SUPPORTS_DAC//built-in DAC functions are only supported on I2S0 for ESP32 chip.I2S_MODE_DAC_BUILT_IN = (0x1 << 4), /*!< Output I2S data to built-in DAC, no matter the data format is 16bit or 32 bit, the DAC module will only take the 8bits from MSB*/
#endif // SOC_I2S_SUPPORTS_DAC
#if SOC_I2S_SUPPORTS_ADCI2S_MODE_ADC_BUILT_IN = (0x1 << 5), /*!< Input I2S data from built-in ADC, each data can be 12-bit width at most*/
#endif // SOC_I2S_SUPPORTS_ADC// PDM functions are only supported on I2S0 (all chips).I2S_MODE_PDM = (0x1 << 6), /*!< I2S PDM mode*/
} i2s_mode_t;
音頻采樣率bits_per_sample
typedef enum {I2S_BITS_PER_SAMPLE_8BIT = 8, /*!< data bit-width: 8 */I2S_BITS_PER_SAMPLE_16BIT = 16, /*!< data bit-width: 16 */I2S_BITS_PER_SAMPLE_24BIT = 24, /*!< data bit-width: 24 */I2S_BITS_PER_SAMPLE_32BIT = 32, /*!< data bit-width: 32 */
} i2s_bits_per_sample_t;
通道格式channel_format
typedef enum {I2S_CHANNEL_FMT_RIGHT_LEFT, /*!< Separated left and right channel */I2S_CHANNEL_FMT_ALL_RIGHT, /*!< Load right channel data in both two channels */I2S_CHANNEL_FMT_ALL_LEFT, /*!< Load left channel data in both two channels */I2S_CHANNEL_FMT_ONLY_RIGHT, /*!< Only load data in right channel (mono mode) */I2S_CHANNEL_FMT_ONLY_LEFT, /*!< Only load data in left channel (mono mode) */
#if SOC_I2S_SUPPORTS_TDM// Multiple channels are available with TDM featureI2S_CHANNEL_FMT_MULTIPLE, /*!< More than two channels are used */
#endif
} i2s_channel_fmt_t;
通信傳輸格式communication_format
typedef enum {I2S_COMM_FORMAT_STAND_I2S = 0X01, /*!< I2S communication I2S Philips standard, data launch at second BCK*/I2S_COMM_FORMAT_STAND_MSB = 0X02, /*!< I2S communication MSB alignment standard, data launch at first BCK*/I2S_COMM_FORMAT_STAND_PCM_SHORT = 0x04, /*!< PCM Short standard, also known as DSP mode. The period of synchronization signal (WS) is 1 bck cycle.*/I2S_COMM_FORMAT_STAND_PCM_LONG = 0x0C, /*!< PCM Long standard. The period of synchronization signal (WS) is channel_bit*bck cycles.*/I2S_COMM_FORMAT_STAND_MAX, /*!< standard max*///old definition will be removed in the future.I2S_COMM_FORMAT_I2S __attribute__((deprecated)) = 0x01, /*!< I2S communication format I2S, correspond to `I2S_COMM_FORMAT_STAND_I2S`*/I2S_COMM_FORMAT_I2S_MSB __attribute__((deprecated)) = 0x01, /*!< I2S format MSB, (I2S_COMM_FORMAT_I2S |I2S_COMM_FORMAT_I2S_MSB) correspond to `I2S_COMM_FORMAT_STAND_I2S`*/I2S_COMM_FORMAT_I2S_LSB __attribute__((deprecated)) = 0x02, /*!< I2S format LSB, (I2S_COMM_FORMAT_I2S |I2S_COMM_FORMAT_I2S_LSB) correspond to `I2S_COMM_FORMAT_STAND_MSB`*/I2S_COMM_FORMAT_PCM __attribute__((deprecated)) = 0x04, /*!< I2S communication format PCM, correspond to `I2S_COMM_FORMAT_STAND_PCM_SHORT`*/I2S_COMM_FORMAT_PCM_SHORT __attribute__((deprecated)) = 0x04, /*!< PCM Short, (I2S_COMM_FORMAT_PCM | I2S_COMM_FORMAT_PCM_SHORT) correspond to `I2S_COMM_FORMAT_STAND_PCM_SHORT`*/I2S_COMM_FORMAT_PCM_LONG __attribute__((deprecated)) = 0x08, /*!< PCM Long, (I2S_COMM_FORMAT_PCM | I2S_COMM_FORMAT_PCM_LONG) correspond to `I2S_COMM_FORMAT_STAND_PCM_LONG`*/
} i2s_comm_format_t;
知道了每個參數的含義以及知道它可以配置哪些參數,就可以調用i2s_driver_install這個函數了。
這里我們舉一個安裝I2S驅動的例子,就比較容易理解了。同時配置的時候,我們把它放在一個函數里面,起名為i2s_install( )。
// 安裝I2S驅動void i2s_install(){// 配置I2S接收i2s_config_t i2s_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),.intr_alloc_flags = 0,.dma_buf_count = 16,.dma_buf_len = bufferLen,.use_apll = false };if (ESP_OK != i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL)) {Serial.println("Install I2S driver failed");return;}
}
配置I2S引腳
I2S通信最重要的三個信號是位時鐘BCK、字時鐘WS、數據引腳SD,因此我們需要對其引腳進行配置,設置I2S引腳的函數是
esp_err_t i2s_set_pin(i2s_port_t i2s_num, const i2s_pin_config_t *pin)
,第一個參數傳入I2S端口,填I2S_NUM_0或I2S_NUM_1, 第二個參數是結構體如下:
typedef struct {int mck_io_num; /*!< MCK in out pin. Note that ESP32 supports setting MCK on GPIO0/GPIO1/GPIO3 only*/int bck_io_num; /*!< BCK in out pin*/int ws_io_num; /*!< WS in out pin*/int data_out_num; /*!< DATA out pin*/int data_in_num; /*!< DATA in pin*/
} i2s_pin_config_t;
其中mck_io_num; bck_io_num,ws_io_num等都是整型變量,data_out_num如果我們沒有用到就傳入-1,在driver/i2s.h頭文件定義了
#define I2S_PIN_NO_CHANGE (-1) /*!< Use in i2s_pin_config_t for pins which should not be changed */
假設我們要配置的引腳位時鐘BCK、字時鐘WS、數據引腳SD分別是D13, D12, D14,同樣把要配置的參數寫入一個函數i2s_setpin()里面,配置I2S引腳示例如下:
// 配置I2S引腳
#define I2S_MIC_WS 12
#define I2S_MIC_SD 14
#define I2S_MIC_BCK 13void i2s_setpin(){i2s_pin_config_t pin_config = {};pin_config.bck_io_num = I2S_MIC_BCK;pin_config.ws_io_num = I2S_MIC_WS;pin_config.data_out_num = I2S_PIN_NO_CHANGE;pin_config.data_in_num = I2S_MIC_SD;if (ESP_OK != i2s_set_pin(I2S_PORT, &pin_config)) {Serial.println("I2S set pin failed");return;}}
安裝完I2S驅動和配置好I2S引腳后,我們只要在setup()函數里面調用這兩個函數就可以了。
void setup() {Serial.begin(115200);Serial.println("Setup I2S ...");delay(1000);i2s_install();i2s_setpin();delay(500);
}
讀取I2S數據
上面只是對I2S進行了一下初始化,要通過INMP441讀取i2s數據,我們只需要調用esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait);
這個函數。因為前面在初始化I2S時的量化位數是16位,所以每個采樣點的數據大小為2字節,我們將讀取到的數據放入一個緩存區數組sBuffer,將數組長度bufferLen定義為64,確保每次從I2S接口讀取時能獲得足夠的音頻數據。 如果讀取成功2s_read這個函數會返回一個ESP_OK,成功后就進入數據處理部分。這里我們將讀取到的音頻數據求均值然后可以用串口繪圖儀觀察它的數據波形,代碼如下:
void loop() {size_t bytesIn = 0;esp_err_t result = i2s_read(I2S_PORT, &sBuffer, bufferLen, &bytesIn, portMAX_DELAY);if (result == ESP_OK){int samples_read = bytesIn / 2;if (samples_read > 0) {float mean = 0;for (int i = 0; i < samples_read; ++i) {mean += (sBuffer[i]);}mean /= samples_read;Serial.println(mean);delay(50);}}
}
完整代碼
#include "driver/i2s.h"
#define SAMPLE_RATE (44100)#define I2S_MIC_WS 12
#define I2S_MIC_SD 14
#define I2S_MIC_BCK 13
#define I2S_PORT I2S_NUM_0
#define bufferLen 64int16_t sBuffer[bufferLen];// 安裝I2S驅動void i2s_install(){// 配置I2S接收i2s_config_t i2s_config = {.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),.sample_rate = SAMPLE_RATE,.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),.intr_alloc_flags = 0,.dma_buf_count = 16,.dma_buf_len = bufferLen,.use_apll = false };if (ESP_OK != i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL)) {Serial.println("Install I2S driver failed");return;}
}// 配置I2S引腳void i2s_setpin(){i2s_pin_config_t pin_config = {};pin_config.bck_io_num = I2S_MIC_BCK;pin_config.ws_io_num = I2S_MIC_WS;pin_config.data_out_num = I2S_PIN_NO_CHANGE;pin_config.data_in_num = I2S_MIC_SD;if (ESP_OK != i2s_set_pin(I2S_PORT, &pin_config)) {Serial.println("I2S set pin failed");return;}}void setup() {Serial.begin(115200);Serial.println("Setup I2S ...");delay(1000);i2s_install();i2s_setpin();i2s_start(I2S_PORT);delay(500);
}void loop() {size_t bytesIn = 0;esp_err_t result = i2s_read(I2S_PORT, &sBuffer, bufferLen, &bytesIn, portMAX_DELAY);if (result == ESP_OK){int samples_read = bytesIn / 2;if (samples_read > 0) {float mean = 0;for (int i = 0; i < samples_read; ++i) {mean += (sBuffer[i]);}mean /= samples_read;Serial.println(mean);delay(50);}}}
接線圖
寫完代碼后就可以開始接線了,接線圖如下圖所示:供電這里接3.3V,L/R接地。
實驗效果
一開始接收到的是外界的聲音,波形是雜亂無章的。后面用嘴吹氣,波形會跟著吹氣變化,不吹氣波形是平緩不變的,后面大概吹了幾次,可以看到波形變化,如下圖:
總結
本篇文章介紹了通過ESP32的I2S通信實時讀取INMP441麥克風模塊的音頻數據,并通過串口繪圖儀顯示音頻數據波形。后面我們還會介紹使用INMP441采集音頻并實時播放音頻,感興趣的可以先關注收藏一下!