前言
声音在我们的生活中有这重要的意义,那音频数据是如何在嵌入式设备中传输的呢,在实时音频系统中如会议室的麦克风和音响,麦克风输入的音频数据传到嵌入式设备,嵌入式设备再将数据传给音响设备,在这个数据的传输链路中使用的是I²S协议。
目标:学习并掌握I²S协议和ESP32C3 I²S的开发。
一、I²S协议
1.概述
I²S(Inter-IC Sound)是一种串行同步通信协议,通常用于在两个数字音频设备之间传输音频数据。I²S是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专门用于音频设备之间的数据传输,广泛应用于各种多媒体系统。
2.接口定义
I²S 标准总线定义了三种信号:串行时钟信号 SCK、字选择信号 WS 和串行数据信号 SD。一个基本的 I2S 数据总线有一个主机和一个从机如下图。主机和从机的角色在通信过程中保持不变。
1.SCK
串行时钟,主模式下作为时钟信号输出,从模式下作为输入。也称为BCLK(位时钟线),串行时钟频率=每个声道道比特数 x 声道数 x 音频采样频率。
在某些外部音频设备需要主时钟时,可以另有一个附加引脚输出时钟:MCK:
主时钟(独立映射),在I2S配置为主模式,作为输出额外的时钟信号引脚使用。输出时钟信号的频率预先设置为256 × Fs,其中Fs是音频信号的采样频率。
2.WS
字选,主模式下作为数据控制信号输出,从模式下作为输入;WS是分隔左右声道的线:
WS = 0,表示左声道;
WS = 1,表示右声道。
3.SD
串行数据,用来发送和接收2路时分复用通道的数据线。
发送器可以在时钟脉冲的前沿或后沿发送数据,这可以在相应的控制寄存器中配置。但接收器仅在时钟脉冲的前沿锁存串行数据和WS。发送器仅在WS改变后的一个时钟脉冲后发送数据。接收器使用WS信号同步串行数据。
3.功能特点
1.有四种可用的数据和包帧组合。可以通过以下四种数据格式发送数据:
● 16位数据打包进16位帧
● 16位数据打包进32位帧
● 24位数据打包进32位帧
● 32位数据打包进32位帧
对于所有的数据格式和通讯标准,总是先发送最高位(MSB)。
2.I²S接口支持主流的音频标准
3.支持主从模式
4.支持全双工和半双工通信,全双工通信需要两条串行数据线。
5.音频采样频率可配置
6.TX 和 RX 可独立工作或同时工作
7.交错式立体声FIFO或独立的左右声道FIFO
8.支持DMA传输和I²S接口中断
4.时序特点
I²S接口在不同的音频协议标准中的时序是不同的,以飞利浦和PCM的标准来对比一下:
I²S飞利浦协议:
在此标准下,引脚WS用来指示正在发送的数据属于哪个声道。在发送第一位数据(MSB)前1个时钟周期,该引脚即为有效。
发送方在时钟信号(CK)的下降沿改变数据,接收方在上升沿读取数据。 WS信号也在时钟信号的下降沿变化。
此外,可以注意到WS线在传输MSB之前改变一个CLK周期,这为接收器提供了时间来存储较早的字并清除下一个字的输入寄存器。当WS改变后SCK改变时发送MSB。
PCM标准:
在PCM标准下,不存在声道选择的信息。 PCM标准有2种可用的帧结构,短帧或者长帧,可以通过设置相应寄存器的PCMSYNC位来选择。对于长帧,主模式下,用来同步的WS信号有效的时间固定为13位。对于短帧,用来同步的WS信号长度只有1位。
注意: 无论哪种模式(主或从)、哪种同步方式(短帧或长帧),连续的2帧数据之间和2个同步信号之间的时间差, (即使是从模式)需要通过设置相应寄存器的DATLEN位和CHLEN位来确定。
二、ESP32C3 I²S
1.ESP32C3 I²S介绍
简介
ESP32-C3 内置一个 I2S 接口,为多媒体应用,尤其是为数字音频应用提供了灵活的数据通信接口。
定义了三种信号:串行时钟信号 BCK、字选择信号 WS 和串行数据信号 SD。
特性
• 支持主机模式和从机模式
• 支持全双工和半双工通信
• TX 模块和 RX 模块相互独立
• TX 模块和 RX 模块可独立工作或同时工作
• 支持多种音频标准:
– TDM Philips 标准
– TDM MSB 对齐标准
– TDM PCM 标准
– PDM 标准
• 高精度采样时钟可配置
• 支持如下采样频率: 8 kHz、 16 kHz、 32 kHz、 44.1 kHz、 48 kHz、 88.2 kHz、 96 kHz、 128 kHz 和 192
kHz。注意:不支持从机 192 kHz 32 位模式。
• 支持 8/16/24/32 位数据通信
• 支持 DMA
• 支持 I2S 接口中断
系统架构
• 独立的发送单元 (TX control)
• 独立的接收单元 (RX control)
• 输入输出时序调节单元 (I/O sync)
• 时钟分频器 (Clock Generator)
• 64 x 32-bit TX FIFO
• 64 x 32-bit RX FIFO
• 压缩/解压缩模块 (Compress/Decompress)
ESP32-C3 I²S 模块支持 DMA,可访问内部存储器。
发送单元和接收单元各自有一组三线接口,分别为串行时钟线 BCK,字选择线 WS 和串行数据线 SD。其中,发送单元的 SD 线固定为输出,接收单元的 SD 线固定为接收。发送单元和接收单元的 BCK 和 WS 信号线均可配置为主机输出模式或从机输入模式。
I²S 的所有信号均需要经过 GPIO 交换矩阵映射到芯片的管脚,可以根据需要配置引脚,全双工(发送单元与接收单元共同工作)与半双工需要的引脚数量是不同的。
支持的音频协议
ESP32-C3 I²S 模块支持多种音频标准,包括 TDM Philips 标准、 TDM MSB 对齐标准、 TDM PCM 标准以及 PDM标准。
通过配置以下寄存器,选择所需的音频标准:
• I2S_TX/RX_TDM_EN
– 0:禁用 TDM 模式
– 1:选择 TDM 模式
• I2S_TX/RX_PDM_EN
– 0:禁用 PDM 模式
– 1:选择 PDM 模式
I2S_TX/RX_MSB_SHIFT
– 0:配置 WS 信号和 SD 信号同时开始变化,即选择 MSB 对齐标准
– 1:配置 WS 信号先于 SD 信号一个 BCK 时钟周期开始变化,即选择 Philips 标准或 PCM 标准
• I2S_TX/RX_PCM_BYPASS
– 0:选择 PCM 标准
– 1:禁用 PCM 标准
2.ESP32C3 I²S开发
ESP32C3用I²S驱动ES8311音频编码器,按照之前的方法去创建I²S ES8311的工程例程。
1.配置ESP32C3 I²S参数
ES8311音频编码器是可接麦克风和喇叭的音频编码芯片,所以驱动ES8311是要用上发送单元与接收单元,ESP32C3为主设备。
代码如下:
#define EXAMPLE_RECV_BUF_SIZE (2048)
#define EXAMPLE_SAMPLE_RATE (16000)
#define EXAMPLE_MCLK_MULTIPLE I2S_MCLK_MULTIPLE_256
#define EXAMPLE_VOICE_VOLUME CONFIG_EXAMPLE_VOICE_VOLUME
#if CONFIG_EXAMPLE_MODE_ECHO
#define EXAMPLE_MIC_GAIN CONFIG_EXAMPLE_MIC_GAIN
#endif
i2s_config_t i2s_cfg = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX,
.sample_rate = EXAMPLE_SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.tx_desc_auto_clear = true,
#if SOC_I2S_SUPPORTS_TDM
.total_chan = 2,
.chan_mask = I2S_TDM_ACTIVE_CH0 | I2S_TDM_ACTIVE_CH1,
.left_align = false,
.big_edin = false,
.bit_order_msb = false,
.skip_msk = false,
#endif
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false,
.mclk_multiple = EXAMPLE_MCLK_MULTIPLE,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_RETURN_ON_ERROR(i2s_driver_install(I2S_NUM, &i2s_cfg, 0, NULL), TAG, "install i2s failed");
定义i2s_cfg 变量,然后配置I²S参数,调用i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config, int queue_size, void *i2s_queue)函数安装并启动I2S驱动程序。在进行任何I2S驱动器读/写操作之前,必须调用此函数(i2s_driver_install())。
2.配置ESP32C3 I²S接口引脚
驱动ES8311是要用上发送单元与接收单元,而且还需要配置MCK时钟,总共要5个GPIO口
/* I2S port and GPIOs */
#define I2S_NUM (0)
#define I2S_MCK_IO (GPIO_NUM_0)
#define I2S_BCK_IO (GPIO_NUM_4)
#define I2S_WS_IO (GPIO_NUM_5)
#define I2S_DO_IO (GPIO_NUM_18)
#define I2S_DI_IO (GPIO_NUM_19)
i2s_pin_config_t i2s_pin_cfg = {
.mck_io_num = I2S_MCK_IO,
.bck_io_num = I2S_BCK_IO,
.ws_io_num = I2S_WS_IO,
.data_out_num = I2S_DO_IO,
.data_in_num = I2S_DI_IO
};
ESP_RETURN_ON_ERROR(i2s_set_pin(I2S_NUM, &i2s_pin_cfg), TAG, "set i2s pins failed");
调用i2s_set_pin(i2s_port_t i2s_num, const i2s_pin_config_t *pin)函数配置相应的GPIO口。
3.初始化I²S
static esp_err_t i2s_driver_init(void)
{
i2s_config_t i2s_cfg = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX,
.sample_rate = EXAMPLE_SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.tx_desc_auto_clear = true,
#if SOC_I2S_SUPPORTS_TDM
.total_chan = 2,
.chan_mask = I2S_TDM_ACTIVE_CH0 | I2S_TDM_ACTIVE_CH1,
.left_align = false,
.big_edin = false,
.bit_order_msb = false,
.skip_msk = false,
#endif
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false,
.mclk_multiple = EXAMPLE_MCLK_MULTIPLE,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_RETURN_ON_ERROR(i2s_driver_install(I2S_NUM, &i2s_cfg, 0, NULL), TAG, "install i2s failed");
i2s_pin_config_t i2s_pin_cfg = {
.mck_io_num = I2S_MCK_IO,
.bck_io_num = I2S_BCK_IO,
.ws_io_num = I2S_WS_IO,
.data_out_num = I2S_DO_IO,
.data_in_num = I2S_DI_IO
};
ESP_RETURN_ON_ERROR(i2s_set_pin(I2S_NUM, &i2s_pin_cfg), TAG, "set i2s pins failed");
return ESP_OK;
}
4.ES8311驱动
在工程的外部组件部分提供了ES8311的底层驱动库,如下图:
在es8311.c文件实现了一下驱动函数:
5.初始化ES8311
根据数据手册,操作ES8311需要通过的I²C接口来配置其I²S的采样频率等参数,故初始化ES8311还要使用到I²C。在工程例程中提供了ES8311的驱动库,可以直接调用相关的函数进行初始化,代码如下:
/* I2C port and GPIOs */
#define I2C_NUM (0)
#ifdef CONFIG_IDF_TARGET_ESP32C3
#define I2C_SDA_IO (GPIO_NUM_17)
#define I2C_SCL_IO (GPIO_NUM_16)
#else
#define I2C_SDA_IO (GPIO_NUM_15)
#define I2C_SCL_IO (GPIO_NUM_14)
#endif
static esp_err_t es8311_codec_init(void)
{
/* Initialize I2C peripheral */
i2c_config_t es_i2c_cfg = {
.sda_io_num = I2C_SDA_IO,
.scl_io_num = I2C_SCL_IO,
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
};
ESP_RETURN_ON_ERROR(i2c_param_config(I2C_NUM, &es_i2c_cfg), TAG, "config i2c failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0), TAG, "install i2c driver failed");
/* Initialize es8311 codec */
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_clock_config_t es_clk = {
.mclk_from_mclk_pin = true,
.sample_frequency = EXAMPLE_SAMPLE_RATE
};
es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16);
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");
ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, EXAMPLE_VOICE_VOLUME, NULL), TAG, "set es8311 volume failed");
ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), TAG, "set es8311 microphone failed");
#if CONFIG_EXAMPLE_MODE_ECHO
ESP_RETURN_ON_ERROR(es8311_microphone_gain_set(es_handle, EXAMPLE_MIC_GAIN), TAG, "set es8311 microphone gain faield");
#endif
return ESP_OK;
}
ESP32C3的I²C配置成主机,时钟频率是100KHz。
6.ES8311实现回声
ES8311回声功能是指:讲麦克风录的音通过耳机(喇叭)播放出来,功能实现任务函数如下:
static void app_i2s_echo(void *args)
{
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);
/* Read sample data from mic */
ret = i2s_read(I2S_NUM, mic_data, EXAMPLE_RECV_BUF_SIZE, &bytes_read, 100);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "[echo] i2s read failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
abort();
}
/* Write sample data to earphone */
ret = i2s_write(I2S_NUM, mic_data, EXAMPLE_RECV_BUF_SIZE, &bytes_write, 100);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "[echo] i2s write failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
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);
}
主要是ESP32C3从ES8311读取麦克风数据和将数据写入回ES8311通过耳机播放出来,调用i2s_read()、i2s_write()函数实现。
7.主函数
初始化、创建任务。
void app_main(void)
{
/* Initialize i2s peripheral */
if (i2s_driver_init() != ESP_OK) {
ESP_LOGE(TAG, "i2s driver init failed");
abort();
}
/* Initialize i2c peripheral and config es8311 codec by i2c */
if (es8311_codec_init() != ESP_OK) {
ESP_LOGE(TAG, "es8311 codec init failed");
abort();
}
/* Echo the sound from MIC in echo mode */
xTaskCreate(app_i2s_echo, "app_i2s_echo", 8192, NULL, 5, NULL);
}
总结
本次主要是学习I²S协议和了解ESP32C3 I²S的特点,再通过官方的工程示例来掌握I²S接口的开发和使用。ESP32C3 I²S更详细的内容可以去仔细地阅读《esp32-c3_technical_reference_manual_cn》手册,如果对ES8311像了解更多的话可以去查看它的数据手册(我有上传到下载,虽然是英文的)。