ESP32 を I2S経由で外部DAC とつなぎ、Arduino IDEで書いたコードで思い通りの音が出ない(汚い音が出る)状況だったので調べてみた。
Arudino IDE上で ESP32を使う
Arduino IDE : 1.8.15
ESP32 platform: 1.0.6
と、現時点では最新のはず。
ESP32 をI2S経由で外部DACとつなぐ
ESP32 からI2Sインターフェース (BCLK, DOUT, LRCK またの名を SCLK, SD, WS)を 外付けDAC (PCM5102) につなぐ。16bit以上の解像度で鳴らしたかったのだ。ESP32内蔵DACは解像度8bit しかないからである。
フルスイングの正弦波の音がひずむ
16bit分、すなわち-32768~+32767 (0x08000~0x07fff; I2Sはsigned intで表現する) 目いっぱい振っている 正弦波を生成させたのだが、音が汚い。PCM5102なのに?
出力レベルが高くて、頭と足が切れたのかもしれなかったので、簡易オシロで見てみると、下のようになっていた。
この波形はそういう歪み方ではない。unsigned/signed を間違えたか?とも思ったがそうでもなかった。左右のチャネルの音が違っていて、他方からは別の高調波みたいなのが聞こえている。これは伝送データを取りこぼしているのかもしれない。
I2Sの信号がどうなってるかロジックアナライザで確認する。
確認のために左チャンネルに1 (0x0001)、右チャンネルに2 (0x0002)を出力させてみた。
使ったコードは下記。GPIO 25, 26, 27 番ピンを外付けDACのDIN, LRCK, BCLKにつなぐ。
# include <driver/i2s.h>
# define I2S_BCLK 27
# define I2S_LRCK 26
# define I2S_DOUT 25
# define I2S_SAMPLE_RATE 44100
uint16_t wave[2] ;
static const i2s_port_t i2s_num = I2S_NUM_0 ; // i2s port number
static const i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCLK,
.ws_io_num = I2S_LRCK,
.data_out_num = I2S_DOUT,
.data_in_num = I2S_PIN_NO_CHANGE
};
static const i2s_config_t i2s_config = {
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = I2S_SAMPLE_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 = 4,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = true
};
void setup() {
i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
i2s_set_pin(i2s_num, &pin_config);
i2s_zero_dma_buffer(i2s_num);
wave[0] = 0x0001 ; // Lch
wave[1] = 0x0002 ; // Rch
}
void loop() {
size_t written ;
i2s_write(I2S_NUM_0, wave, sizeof(wave), &written, portMAX_DELAY) ;
}
wave[]が出力する値だ。この場合、定数値を出し続けるので「波」にはならず音は鳴らないが I2S上はどこでキャプチャしても同じ値が取れるはず。
左チャンネル、右チャンネルとも値が2倍になっている。LSBの位置がおかしいように見える。
本来の I2Sの規格ならもう1クロック右にLSB が来るはず。例えば、左チャンネルのLSBはLRCKの立ち上がりエッジの次のBCLKで出力されなければならない。
By wdwd - <span class="int-own-work" lang="en">Own work</span>, CC BY 3.0, Link
結論
MSBも本来の位置より1クロック左に出力されていた。DOUT (SD) は全体的に 1clock 早いのだ。言い換えると LRCK (WS) が 1 clock 遅いのだ。
バグ?
ESP32 の Errata にそれらしいことは書いてないので、ハードウェアではなくライブラリに問題があるのかもしれない。
ESP32-IDF は使わないのでわからないが、Arduino IDE の i2s.h ("C:\Users%USER%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\tools\sdk\include\driver\driver\i2s.h") を見ると2016年とかになっているので、もしかすると Arduino の ESP32 SDK環境だけは古いのかもしれない。