i2s总线理解与运用

  • I2S总线基础概念
  • I2S概念
  • PCM音频数据
  • 转换成PCM格式的三个参数
  • 采样频率(声音周期量化)
  • 采样位数(声音的幅度量化)
  • 声道数(单声道,立体声)
  • I2S总线通讯方式
  • I2S总线引脚
  • esp32从ES8311分析i2s驱动如何去写
  • 原理图
  • 例程代码分析
  • 主函数
  • i2s初始化
  • es8311初始化
  • 播放音频


I2S总线基础概念

I2S概念

I2S(Inter-IC Sound)总线, 又称集成电路内置音频总线,最早是由现在的恩智浦半导体公司针对数字音频设备之间的音频数据传输而制定的总线标准。该总线专门用于音频设备间的传输,广泛用于各种多媒体系统。它传输的是PCM格式数据。

PCM音频数据

PCM (脉冲编码调制),由A.里弗斯于1937年提出的记录音频的格式。PCM是模拟信号经过采样、量化、编码转换成标准数字音频的原始数据格式。

esp32 I2S 外置 esp32的i2s_esp32 I2S 外置

转换成PCM格式的三个参数

音频数据量=采样频率×量化位数×声道数/8(字节/秒)

采样频率(声音周期量化)

1秒同时对多个声道完成adc采样的次数。采样频率越高,声音质量越好,还原越真实,但同时它占的资源比较多。

采样位数(声音的幅度量化)

每个采样点用多少二进制位表示数据范围。量化位数越多,音质越好,数据量也越大

声道数(单声道,立体声)

使用声音通道的个数,有单声道和立体声之分,立体声比单声道数据量翻倍。

I2S总线通讯方式

  1. 支持全双工和半双工通信
  2. 支持主/从模式

I2S总线引脚

  1. SCK:(continuous serial clock) 串行时钟,I2S的心跳。
    串行时钟SCK,也叫位时钟BCLK,也有的称为BCK、SCLK等。对应数字音频的每一位数据,SCK都有1个脉冲。
    SCK的频率 = 声道数 * 采样频率 * 采样位数。
  2. WS: (word select) 字段(声道)选择
    字段选择信号WS,也叫LRCLK(LRCK),用于切换左右声道的数据。
    WS的频率 = 采样频率
    WS为0,表示正在传输的是左声道的数据;
    WS为1,表示正在传输的是右声道的数据。
  3. SDATA:串行数据,在全双工的模式下分为DATAin和DATAout,主从之间的in/out要交叉接线
  4. MCLK:主时钟频率。一般为采样频率的256倍

esp32从ES8311分析i2s驱动如何去写

原理图

┌─────────────────┐           ┌──────────────────────────┐
│       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输入音频数据。

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相关的这里有比较详细的说明,大致就是多声道输出。本人理解有限 ,这里不做过多解释。

es8311初始化

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);
}