从0学起ESP-IDF之旅 —— I2S总线理解与运用

一、I2S总线基础概念

1. I2S概念

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

2. PCM音频数据

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

  • 转换成PCM格式的三个参数

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

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

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

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

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

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

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

3. I2S总线通讯方式

  • 支持全双工和半双工通信
  • 支持主/从模式

4. I2S总线引脚

(1)SCK

continuous serial clock,串行时钟,I2S的心跳。

串行时钟SCK也叫位时钟BCLK,也有的称为BCK、SCLK等。对应数字音频的每一位数据,SCK都有1个脉冲。

SCK的频率 = 声道数 * 采样频率 * 采样位数。

(2)WS

word select,字(声道)选择。

字选择信号WS也叫LRCLK(LRCK),用于切换左右声道的数据。

WS的频率 = 采样频率

(3)SDATA

串行数据。

在全双工的模式下分为Data In和Data Out,主从之间的In/Out要交叉接线

(4)MCLK

主时钟。

主时钟频率一般为采样频率的256倍。

二、ESP32——从ES8311分析I2S驱动如何去写

1. 原理

原理如下图所示:

注意:

  • SDOUT和SDIN要交叉接线。
  • 除了I2S给数据流之外,还需要一个I2C用来初始化codec芯片。

2. 例程代码分析

  • 主函数
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并且配置ES8311,然后就是通过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;
}

这部分详见代码中注释,很细致。

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

这部分同样参加代码中注释,很细致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值