I2S(Inter-IC Sound)总线, 又称集成电路内置音频总线,最早是由现在的恩智浦半导体公司针对数字音频设备之间的音频数据传输而制定的总线标准。该总线专门用于音频设备间的传输,广泛用于各种多媒体系统。它传输的是PCM格式数据。
PCM (脉冲编码调制),由A.里弗斯于1937年提出的记录音频的格式。PCM是模拟信号经过采样、量化、编码转换成标准数字音频的原始数据格式。
音频数据量=采样频率×量化位数×声道数/8(字节/秒)
1秒同时对多个声道完成adc采样的次数。采样频率越高,声音质量越好,还原越真实,但同时它占的资源比较多。
每个采样点用多少二进制位表示数据范围。量化位数越多,音质越好,数据量也越大
使用声音通道的个数,有单声道和立体声之分,立体声比单声道数据量翻倍。
┌─────────────────┐ ┌──────────────────────────┐
│ ESP │ │ ES8311 │
│ │ │ │
│ MCLK-GPIO 0 ├──────────►│PIN2-MCLK │
│ │ │ │ ┌─────────┐
│ BCLK-GPIO 4 ├──────────►│PIN6-BCLK PIN12-OUTP├───────────┤ │
│ │ │ │ │ EARPHONE│
│ WS-GPIO 5 ├──────────►│PIN8-LRCK PIN13-OUTN├───────────┤ │
│ │ │ │ └─────────┘
│ SDOUT-GPIO 18├──────────►│PIN9-SDIN │
│ │ │ │
│ SDIN-GPIO 19│◄──────────┤PIN7-SDOUT │
│ │ │ │ ┌─────────┐
│ │ │ PIN18-MIC1P├───────────┤ │
│ SCL-GPIO 16├──────────►│PIN1 -CCLK │ │ MIC │
│ (GPIO 7)│ │ PIN17-MIC1N├───────────┤ │
│ SDA-GPIO 17│◄─────────►│PIN19-CDATA │ └─────────┘
│ (GPIO 8)│ │ │
│ VCC 3.3├───────────┤VCC │
│ │ │ │
│ GND├───────────┤GND │
└─────────────────┘ └──────────────────────────┘
注意:这里的SDOUT和SDIN要交叉接线。而且除了i2s给数据流之外,我们还需要一个i2c用来初始化codec芯片
void app_main(void)
{
/* 初始化i2s驱动 */
if (i2s_driver_init() != ESP_OK) {
ESP_LOGE(TAG, "i2s driver init failed");
abort();
}
/* 初始化 i2c 外围设备并通过 i2c 配置 es8311 编解码器 */
if (es8311_codec_init() != ESP_OK) {
ESP_LOGE(TAG, "es8311 codec init failed");
abort();
}
#if CONFIG_EXAMPLE_MODE_MUSIC
/* 在音乐模式下播放一段音乐 */
xTaskCreate(i2s_music, "i2s_music", 4096, NULL, 5, NULL);
#else
/* 以回声模式回声来自 MIC 的声音 */
xTaskCreate(i2s_echo, "i2s_echo", 8192, NULL, 5, NULL);
#endif
}
主函数整体比较简单初始化i2s后初始化i2c并且给i2c初始化,然后就是通过i2s输入音频数据。
static esp_err_t i2s_driver_init(void)
{
i2s_config_t i2s_cfg = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX, //设置i2s工作模式,根据需求设置
.sample_rate = EXAMPLE_SAMPLE_RATE,//设置I2S 采样率,根据音频确定采样率
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,//设置采样位数
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,//设置I2S 通道格式(分离左右声道)
.communication_format = I2S_COMM_FORMAT_STAND_I2S,//设置I2S 通讯格式
.tx_desc_auto_clear = true,//I2S 自动清除tx描述符
#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_desc_num = 8, // I2S DMA 用于接收/发送数据的描述符总数
.dma_frame_num = 64, // 一次性采样的帧数。这里的 frame 表示一个 WS 周期内所有通道的总数据
.use_apll = false, // I2S 使用 APLL 作为主要 I2S 时钟,使其能够获得准确的时钟
.mclk_multiple = EXAMPLE_MCLK_MULTIPLE, // I2S 主时钟(MCLK)与采样率的倍数,有256(默认) 128 384倍
.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;
}
关于TDM相关的这里有比较详细的说明,大致就是多声道输出。本人理解有限 ,这里不做过多解释。
static esp_err_t es8311_codec_init(void)
{
/* i2c初始化 */
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");
/* 初始化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_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;
}
前面部分是在初始化i2c,后面部分主要是在调用es8311库里的东西,主要是调用i2c去配置一些es8311的寄存器。
static void i2s_music(void *args)
{
esp_err_t ret = ESP_OK;
size_t bytes_write = 0;
while (1) {
/* 将音频数据通过i2s写入es8311 */
ret = i2s_write(I2S_NUM, music_pcm_start, music_pcm_end - music_pcm_start, &bytes_write, portMAX_DELAY);
if (ret != ESP_OK) {
/* 由于我们在 'i2s_write' 中将超时设置为 'portMAX_DELAY',
所以除非设置其他超时值,否则您将无法到达此处,如果检测到超时,则表示写入操作失败。*/
ESP_LOGE(TAG, "[music] i2s read failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
abort();
}
/* 清除 DMA 缓冲区以避免缓冲区中的旧数据产生噪声 */
i2s_zero_dma_buffer(I2S_NUM);
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 falied.");
abort();
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}