前回の記事に書いたものは回路図も書かずI2Sの動作の確認のためだけに適当につくったものだったが,きちんと情報をまとめておくためにもあらためて詳しく記事にしていく.
これをもとに何をしたいかというと,SDR(趣味)と楽器用のエフェクタ(仕事).どちらもやることはよく似ている.さすがにエフェクタの方に複素係数フィルタの出番はないが.
まずは回路図を作成した.
(見づらい場合は,「名前を付けて画像を保存」してからViewerで拡大して閲覧ください)

CODECは前回の記事から変更して,DACにはPCM5102A,ADCにはPCM1808を使用した.限られた手持ちのデバイス中ではこの組み合わせが一番ノイズが少なかった.
PCM5102Aは単体のデバイスではなくモジュールを使った(紫色の基板).アマゾンで検索するとたくさんでてくる.

PCM1808のI2S制御信号にはダンピング抵抗を入れている.これがないと出力にプチプチとノイズが入る.抵抗値は適当なので最適値があるかもしれない.
LCD用のポートも確保しておいた.一応8bitパラレルを想定しているが今後ポートが足りなくなるようならSPIにする.ESP32よりポート数は増えているが十分ではないかもしれない.
以下にこの回路で動作するコードを示す.
環境は Arduino IDE + ESP32ボードマネージャ(2.0.5)
(折り返し等で見づらい場合は全コピーしてテキストエディタに貼り付けてください)
// I2S on ESP32-S3 // T.Uebo October 4 , 2022 // I2S Master MODE 48kHz/32bit // // Mclk GPIO 0 // Bclk GPIO 42 // LRclk GPIO 2 // Dout GPIO 41 // Din GPIO 1 #include <driver/i2s.h> #define fsample 48000 #define BLOCK_SAMPLES 64 //buffers int rxbuf[BLOCK_SAMPLES*2], txbuf[BLOCK_SAMPLES*2]; float Lch_in[BLOCK_SAMPLES], Rch_in[BLOCK_SAMPLES]; float Lch_out[BLOCK_SAMPLES], Rch_out[BLOCK_SAMPLES]; #define MUTE 40 // MUTE control (LOW: Mute) /*----------------------------------------------------------------------------------------------- Setup -------------------------------------------------------------------------------------------------*/ void setup(void) { Serial.begin(115200); delay(50); pinMode(MUTE, OUTPUT); digitalWrite(MUTE, HIGH); // unmute // I2S setup ------------------------------------------------------------ i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX), .sample_rate = fsample, .bits_per_sample = (i2s_bits_per_sample_t)32, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), .intr_alloc_flags = 0, .dma_buf_count = 6, .dma_buf_len = BLOCK_SAMPLES*4, .use_apll = false, .tx_desc_auto_clear = true, .fixed_mclk = 0, }; i2s_driver_install( I2S_NUM_0, &i2s_config, 0, NULL); i2s_pin_config_t pin_config = { .bck_io_num = 42, .ws_io_num = 2, .data_out_num = 41, .data_in_num = 1 }; i2s_set_pin( I2S_NUM_0, &pin_config); } /*----------------------------------------------------------------------------------------------- Signal Process Loop -------------------------------------------------------------------------------------------------*/ void loop(void) { size_t readsize = 0; //Input esp_err_t res = i2s_read(I2S_NUM_0, &rxbuf[0], BLOCK_SAMPLES*2*4, &readsize, portMAX_DELAY); if (res == ESP_OK && readsize==BLOCK_SAMPLES*2*4) { int j=0; for (int i=0; i<BLOCK_SAMPLES; i++) { Lch_in[i] = (float) rxbuf[j]; Rch_in[i] = (float) rxbuf[j+1]; j+=2; } //-------Signal process ------------------------------- for (int i=0; i<BLOCK_SAMPLES; i++) { Lch_out[i] = Lch_in[i]; Rch_out[i] = Rch_in[i]; } //------------------------------------------------------ //Output j=0; for (int i=0; i<BLOCK_SAMPLES; i++) { txbuf[j] = (int) Lch_out[i]; txbuf[j+1] = (int) Rch_out[i]; j+=2; } i2s_write( I2S_NUM_0, &txbuf[0], BLOCK_SAMPLES*2*4, &readsize, portMAX_DELAY); } }
動作は,入力された信号をそのまま出力する.
GPIO 40をミュート制御用の信号としているが,回路は対応していないので今のところ意味はない.
今後,//——-Signal process —————- の部分に処理を追加していく.
ESP32ではバックグラウンドでfreeRTOSが動作しているそうでなかなか理解が難しい(Teensy もそうだったか?).i2s.h ( I2S.h ではない)に書かれてある内容を頼りに用意されている関数を使ってとりあえず動作はしたが,自分自身はよく理解しているとは言い難い.とはいえやりたいことは信号処理なので,それ以外は先人の成果をありがたく使わせていただくことにしたいと思う.
参考にさせていただいたサイト