ESP32のI2Sを使ったDAC出力研究

概要

前回いろいろ検証しましたが、ちょっと気になったところを深堀りしてみたいと思います。

スケッチ

#include <driver/i2s.h>
#include "ESP32LiteDebugGPIO.h"
 
#define SAMPLING_RATE (440*256)     // サンプリングレート
#define BUFFER_LEN    (1024)        // バッファサイズ
uint8_t soundBuffer[BUFFER_LEN];    // DMA転送バッファ
 
void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.printf("f= %d\n", SAMPLING_RATE);
 
  // 再生設定
  i2s_config_t i2s_config = {
    .mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
    .sample_rate          = SAMPLING_RATE,
    .bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format       = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S_MSB,
    .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count        = 2,
    .dma_buf_len          = BUFFER_LEN,
    .use_apll             = false,
    .tx_desc_auto_clear   = true,
    .fixed_mclk           = 0,
  };
 
  int step = 256;
  int val = 0;
  for (int i = 0; i < BUFFER_LEN; i += 4) {
    soundBuffer[i] = 0;
    soundBuffer[i + 1] = 256 * val / step;
    soundBuffer[i + 2] = 0;
    soundBuffer[i + 3] = 127 + 127 * sin(2 * PI / 256 * val);
    Serial.printf(" %d\n", soundBuffer[i + 1]);
    val += (256 / step);
    if (256 <= val) {
      val = 0;
    }
  }
 
  // 再生設定実施
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM_0, NULL);               // 25, 26
  //i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);  // 25, 26
  //i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); // 25
  //i2s_set_dac_mode(I2S_DAC_CHANNEL_LEFT_EN ); // 26
  i2s_zero_dma_buffer(I2S_NUM_0);

  delay(1000);
  dispGpio();
}
 
void loop() {
  size_t transBytes;
  i2s_write(I2S_NUM_0, (char*)soundBuffer, BUFFER_LEN, &transBytes, portMAX_DELAY);
}

前回検証したコードを少し手を加えて、ステレオ出力にしてみました。また、GPIOの状態をみるためにESP32LitePackライブラリのESP32LiteDebugGPIO.hを読み込んでいます。

GPIO LEVEL MODE   ADC PULLUP PULLDOWN OPEN_DRAIN I/O NOTE 
-----------------------------------------------------------------------------
   0 HIGH  INPUT      PULLUP                     I/O 
   1 LOW   OUTPUT                                I/O [O]U0TXD_OUT_IDX
   2 LOW   INPUT             PULLDOWN            I/O 
   3 HIGH  INPUT                                 I/O [I]U0RXD_IN_IDX
   4 LOW   INPUT             PULLDOWN            I/O 
   5 HIGH  INPUT      PULLUP                     I/O 
   6 LOW   OUTPUT     PULLUP                     I/O [O]SPICLK_OUT_IDX
   7 HIGH  INPUT      PULLUP                     I/O 
   8 LOW   OUTPUT     PULLUP                     I/O [O]SPID_OUT_IDX
   9 HIGH  INPUT      PULLUP                     I/O 
  10 HIGH  INPUT      PULLUP                     I/O 
  11 HIGH  INPUT      PULLUP                     I/O 
  12 LOW   INPUT             PULLDOWN            I/O 
  13 LOW   INPUT             PULLDOWN            I/O 
  14 HIGH  INPUT      PULLUP                     I/O 
  15 HIGH  INPUT      PULLUP                     I/O 
  16 HIGH  OUTPUT     PULLUP                     I/O [O]SPICS0_OUT_IDX
  17 HIGH  OUTPUT     PULLUP                     I/O [O]SPIQ_OUT_IDX
  18 HIGH  INPUT                                 I/O 
  19 LOW   INPUT                                 I/O 
  20 LOW   INPUT                                 I/O 
  21 HIGH  INPUT                                 I/O 
  22 HIGH  INPUT                                 I/O 
  23 LOW   INPUT                                 I/O 
  25 LOW   INPUT  ADC                            I/O 
  26 LOW   INPUT  ADC                            I/O 
  27 LOW   INPUT                                 I/O 
  32 LOW   INPUT                                 I/O 
  33 LOW   INPUT                                 I/O 
  34 LOW   INPUT                                 I   
  35 LOW   INPUT                                 I   
  36 LOW   INPUT                                 I   
  37 LOW   INPUT                                 I   
  38 LOW   INPUT                                 I   
  39 LOW   INPUT                                 I   

んー、現状のdispGpio()だとRTC経由でADCやDACに接続していることは、わかりますがDACかADCかはもう少し深いレジスタを見ないとわからないみたいです。出力もデジタル出力は使っていないのでINPUTのままですね。

データ出力です。

GPIOデータ波形
25データ2正弦波
26データ1ノコギリ波

データは最初にノコギリ波、次に正弦波を入れてあります。

I2S_CHANNEL_FMT_ALL_RIGHT

両方とも正弦波になりました。つまり右チャンネルは正弦波。

I2S_CHANNEL_FMT_ALL_LEFT

当たり前ですがノコギリ波になりました。

I2S_CHANNEL_FMT_ONLY_RIGHT

ステレオデータではなく、モノラルデータなので両方ノコギリ波にしてみました。あれ、、、ステレオで出力されている、、、

I2S_CHANNEL_FMT_ALL_RIGHTとI2S_CHANNEL_FMT_ONLY_RIGHTはデータ形式の差であって、両方ともステレオ出力されちゃうのね。。。

I2S_CHANNEL_FMT_ONLY_LEFT

I2S_CHANNEL_FMT_ONLY_RIGHTとI2S_CHANNEL_FMT_ONLY_LEFTの差がないように思えるけれど、、、

まとめ

GPIOデータステレオ
25データ2右(RIGHT)
26データ1左(LEFT)

GPIO的には25が右、26が左ですがデータは左、右の順番で設定しないとだけみたいです。そしてI2SでのDAC出力は常にステレオになります。片チャンネルだけ出力することはできませんでした!

そして、モノラル転送をすると波形がおかしくなるので、常にステレオモードで転送をおすすめします。つまりI2S_CHANNEL_FMT_RIGHT_LEFTで、どちらのチャンネルにどのデータを出力するのかを明示的に指定しないといけません。

ATOMなどでGROVE端子に出ているGPIO26に、I2S経由のDAC出力をすると、I2Cで利用している25にも出力が出ちゃうのでI2Cが使えなくなります!

モノラル設定でも両チャンネルに出力されちゃうのはバグな気もしますが、現状はこの動作なので気をつけて使う必要がありますね。。。Arduinoで使われているESP-IDFのバージョンが古すぎるので、現行バージョンのESP-IDFだと修正されているかもしれません。。。早く4系に更新されないかな、、、

追記(2020/09/25)

コメント欄でi2s_set_dac_mode()関数を教えていただきました!

初期化方法出力GPIO
i2s_set_pin(I2S_NUM_0, NULL)25, 26
i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN)25, 26
i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN)25
i2s_set_dac_mode(I2S_DAC_CHANNEL_LEFT_EN )26

上記の初期化方法があるとのことです。おそらくDACのときにはi2s_set_pin()関数ではなく、i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN)で初期化したほうが良さそうですね。

2件のコメント

  1. ESP-IDFのi2s_set_pin関数のドキュメントによると

    if *pin is set as NULL, this function will initialize both of the built-in DAC channels by default. if you don’t want this to happen and you want to initialize only one of the DAC channels, you can call i2s_set_dac_mode instead.

    と言うことなのでi2s_set_dac_mode関数を使えば良いのではないでしょうか?

    https://docs.espressif.com/projects/esp-idf/en/v3.2.3/api-reference/peripherals/i2s.html#_CPPv411i2s_set_pin10i2s_port_tPK16i2s_pin_config_t

コメントする

メールアドレスが公開されることはありません。

管理者承認後にページに追加されます。公開されたくない相談はその旨本文に記載するかTwitterなどでDM投げてください。またスパム対策として、日本語が含まれない投稿は無視されますのでご注意ください。