2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ESP-IDF v5 で I2S 信号を出力 (ESP32)

Last updated at Posted at 2022-10-01

ESP32 で ESP-IDF v5 を使用して I2S 信号を出力する方法です。

前提環境

  • ESP32-DevKitC
  • ESP-IDF master 版 (2022/09/19)

ESP32 の I2S について

ここで使用する ESP-IDF は v5 系です。I2S を出力するためには ESP-IDF の I2S driver を使用しますが、v4 までの API は deprecated となっています。せっかくなので新しい方を使用する事にしました。

公式ドキュメントの I2S について: Inter-IC Sound (I2S)

  • Master / Slave どちらも使用可。
  • クロックは PLL 等から生成。
  • 左詰め、I2S フォーマットどちらも対応。
  • ビット幅は 8,16,24,32bit を設定可。
  • MCLK 出力可能(倍率設定可)。
  • 出力信号の反転機能。
  • DMA 転送。
  • ESP32 では I2S が 2 つ(I2S0 と I2S1)。

これだけ多機能だと使い方が難しそうですが、API は分かりやすくシンプルです。今回の様に、単純に出力する程度であれば簡単にできました。

ESP ではオーディオ用の Framework として ESP-ADF があります。現在のバージョンは ESP-IDF v4 をベースとしてるため、ESP-IDF v5 の API は使用できません。

今回の使い方

  • Master で使用
  • サンプリング周波数は 44.1KHz
  • I2S フォーマット出力
  • ビット幅 16bit
  • MCLK x256 を出力する

ソースコード

I2S の初期化を行ってから、信号出力(無限ループ)までのコードです。
「信号を出力する」のが目的のため、生成波形に意味はありません。

全体

main.c
#include <stdio.h>
#include <driver/i2s_std.h>

void app_main(void)
{
    i2s_chan_config_t channel_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    i2s_chan_handle_t tx_channel_handle;
    ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &tx_channel_handle, NULL /* rx_handle */));

    i2s_std_config_t std_config = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100),
        .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
        .gpio_cfg = {
            .mclk = GPIO_NUM_0,
            .bclk = GPIO_NUM_25,
            .ws = GPIO_NUM_26,
            .dout = GPIO_NUM_27,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false
            }
        }
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_channel_handle, &std_config));
    ESP_ERROR_CHECK(i2s_channel_enable(tx_channel_handle));

    const int SAMPLE_LENGTH = 4;
    int16_t* buffer = (int16_t*)calloc(sizeof(int16_t), SAMPLE_LENGTH);
    for (int i = 0; i < SAMPLE_LENGTH; ++i) {
        buffer[i] = (i & 1) ? 0x8000 + i : i;
    }
    for (;;) {
        size_t written_bytes = 0;
        const uint32_t TIMEOUT_MS = 1000; 
        ESP_ERROR_CHECK(i2s_channel_write(tx_channel_handle, buffer,
            SAMPLE_LENGTH * sizeof(int16_t), &written_bytes, TIMEOUT_MS));
    }
}

説明

i2s channel 初期化

i2s_channel の初期化を行っています。

i2s_chan_config_t channel_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_chan_handle_t tx_channel_handle;
ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &tx_channel_handle, NULL /* rx_handle */));
  • i2s_new_channel 関数は i2s_chan_config_t の内容に従い、送信(tx_handle)と受信(rx_handle)ハンドルを生成します。
  • I2S_CHANNEL_DEFAULT_CONFIG は、I2S番号と Master / Slave のみの指定で i2s_chan_config_t 値を生成するマクロです。
  • 受信は使用しないため i2s_new_channel の rx_handle は NULL を指定します。

標準モード設定

I2S を標準モードに設定しています。標準モードはデータとしてリニア PCM 値を扱います。他に PDM と TDM モードがあります。

i2s_std_config_t std_config = {
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100),
    .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
                                                   I2S_SLOT_MODE_STEREO),
    .gpio_cfg = {
        .mclk = GPIO_NUM_0,
        .bclk = GPIO_NUM_25,
        .ws = GPIO_NUM_26,
        .dout = GPIO_NUM_27,
        .din = I2S_GPIO_UNUSED,
        .invert_flags = {
             .mclk_inv = false,
             .bclk_inv = false,
             .ws_inv = false
         }
     }
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_channel_handle, &std_config));
ESP_ERROR_CHECK(i2s_channel_enable(tx_channel_handle));
  1. i2s_std_config_t の clk_cfg は i2s_std_clk_config_t 型です。サンプリング周波数と MCLK 倍率等のメンバがあります。
    • I2S_STD_CLK_DEFAULT_CONFIG はサンプリング周波数を指定して i2s_std_clk_config_t を生成します。MCLK の倍率はデフォルト値の 256fs (44100Hz * 256 = 11.2896MHz)になります。
  2. slot_cfg は、i2s_std_slot_config_t 型です。I2S_STD_PHILP_SLOT_DEFAULT_CONFIG はデータ幅とモノ/ステレオの指定で i2s_std_slot_config_t を生成します。
    • I2S_STD_PHILP_SLOT_DEFAULT_CONFIG (PHILIPS ではない)は通常の I2S フォーマット用です。左詰めフォーマットの場合は代わりに I2S_STD_MSB_SLOT_DEFAULT_CONFIG を使用します。
  3. gpio_cfg は、i2s_std_gpio_config_t 型です。信号へ割り当てる GPIO ピン等を指定します。
    • 使用しないピンは I2S_GPIO_UNUSED を指定できます。今回は受信を使用しないため din へ I2S_GPIO_UNUSED を指定します。
    • ESP32 では MCLK を出力できる GPIO に制限があり、GPIO0/1/3 しか指定できない様です(参考: i2s_check_set_mclk 関数の実装)。通常、GPIO1/3 は USB シリアル変換チップの TX/RX と接続されているため、実質指定できるのは GPIO0 だけです。未確認ですが、ESP32-S 等では任意の GPIO へ割り当て可能な様です。
    • invert_flags は信号を反転するかどうかの指定です。true の指定で反転します。
  4. i2s_channel_init_std_mode 関数で channel handle を標準モードへ設定します。
  5. i2s_channel_enable 関数で channel を有効化します。

サンプル生成、出力

const int SAMPLE_LENGTH = 4;
int16_t* buffer = (int16_t*)calloc(sizeof(int16_t), SAMPLE_LENGTH);
for (int i = 0; i < SAMPLE_LENGTH; ++i) {
    buffer[i] = (i & 1) ? 0x8000 + i : i;
}
for (;;) {
    size_t written_bytes = 0;
    const uint32_t TIMEOUT_MS = 1000; 
    ESP_ERROR_CHECK(i2s_channel_write(tx_channel_handle, buffer,
        SAMPLE_LENGTH * sizeof(int16_t), &written_bytes, TIMEOUT_MS));
}
  1. 4サンプル分のバッファを準備しています。
    • 1サンプルが 16bit のため、バッファのサイズは sizeof(int16_t) * 4 です。
    • バッファを適当な値で初期化します。中身は 0x0000, 0x8001, 0x0002, 0x8003 になります。
  2. i2s_channel_write 関数でバッファの内容を出力します。

出力信号の確認

ロジックアナライザで GPIO0 (MCLK)、GPIO25 (BCLK)、GPIO26 (WS)、GPIO27 (DOUT) を確認しました。上から順に D3=MCLK / D0=BCLK / D1=WS(LRCLK) / D2=DOUT です。
image.png
意図通りに出力されています。

以下は、周波数カウンタで周波数を測定した結果です。

信号 規定値 測定結果 偏差
MCLK 11.2896MHz 11.28964MHz 30ppm
BCLK 1.4112MHz 1.4112085MHz 60ppm
WS 44100Hz 44100Hz 0ppm

安価な周波数カウンタなので、値をどこまで信頼できるかの判断は難しいところですが…。
S/PDIF では許容偏差が 1000ppm ですので、通常の音声出力用には十分そうです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?