今回は、ESP32とI2Sマイク、アンプの接続です。I2Sとの接続は
- BCLK: ビットクロック 。サンプリング信号
- LRCLK: 左右を区別する信号
- DIN・DOUT: 音信号(入力)/(出力)
- Vdd: 電源
- GND: グランド
上記の5本です。音信号は、マイクはDIN、アンプはDOUTを使います。
アンプ端子 | ESP32端子 |
BCLK | GPIO27 |
LRLCK | GIPO25 |
DOUT | GPIO26 |
マイク端子 | ESP32端子 |
BCLK | GPIO16 |
LRLCK | GPIO4 |
DIN | GPIO17 |
これらのアンプとマイクですが、ステレオ仕様になっています。今回はインターフォンなので片側(左)のみ使用する事にしました。左のみ使用する為に下記の配線が必要です。
秋月(下図左)とBanggood(下図右)の配線図は以下
実際にはこんな感じです。左が秋月。右がBanggoodです。秋月側には他の部品も載っていますが使用したポートは上記の回路だけです。
先ずはアンプ側から
本来はマイクから音を取ってスピーカーに出力。でも今回の様に入力も出力も初めての場合、問題が有るとが原因か分からず途方に暮れる事を避ける事が多いです。そこでどちらか一方を先に確認する事にしました。仕様を見ると、アンプ側はデジタルデータを送れば再生する様なので先ずはアンプの動作確認から初めます。
送るデータは32,16ビットの様です。先ずは32ビットとします。データはサイン波として下記の様に準備します。
// volume:32 bit (MAX)
#define VOLUME ( (1UL << 30) - 1)
#define buf_size 256
int sin_buf[buf_size];
void setup()
{
Serial.begin(115200);
for(int i=0; i<buf_size; i++)
{
sin_buf[i] = sin( (2*PI / (buf_size) ) * i) * VOLUME;
Serial.println(sin_buf[i]);
}
}
void loop()
{
}
- 2行 これが音の大きさになります。データは32ビットですので31左にシフトしたい所ですが、最大の音量になってしまうので30としています。
- 4,5行 サンプルの数と保存場所。今回のサンプル数は256個です。
- 10行 ここからサイン波の計算の始まり。
- 12行 サイン波の計算。振幅に2行目の値を使っています。
- 13行 計算結果の表示。
このスケッチをコンパイル後、モニタを上げて実行して下さい。モニタには計算結果が表示されたと思います。いつもはこの様に動作を確認していたのですが、IDEにはツールメニューに、”シリアルプロッタ”(シリアルモニタの下)が有り、結果をプロットしてくれる事が分かりました。ただこのプロッターを使用するには、
- シリアルモニタとは同時に使用出来ない
- 以前シリアルモニタを上げた状態でこのメニューを選んで、”プロッターは使用出来ません”とエラーを受けていました。(これでこの機能は諦めれいた)
- Serial.println()を使う
- Serial.print()ではプロットしてくれません。改行有りを使用する必要が有ります。
縦軸と横軸の指定の方法は分かりません。自動で割り振られたものを使用しています。このプロッターを使って今回のスケッチを実行すると
こんなふうに綺麗にサイン波がプロットされました。この機能とても有り難いです。
関数自体は簡単ですが
I2Sのアンプに値を書き込む関数は、i2s_write_bytes(i2s_port_t i2s_num, const void *src, size_t size, TickType_t ticks_to_wait);なのですがその前にI2Sのセットアップが必要です。これがちょっと厄介。設定が必要なのは、i2s_config_tとi2s_pin_config_tの2つです。
先ずは、i2s_config_t
#define I2S_SAMPLE_RATE 32000
#define I2S_BUFFER_COUNT 4
#define I2S_BUFFER_SIZE 512
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_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = I2S_BUFFER_COUNT,
.dma_buf_len = I2S_BUFFER_SIZE,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
- mode モードの指定
- 入出力の指定。接続されている機器の指定。等
- 今回はI2Sのアンプなので、I2S_MODE_MASTERとI2S_MODE_TXを指定
- マイクの時は、I2S_MODE_RXを指定する。
- sample_rate サンプルレートの指定
- 今回は32000を指定します。
- bits_per_sample サンプルデータのビット数
- 32または16ビットを指定する様です。今回は32ビットとしました。
- channel_format ステレオかモノかの指定
- 右と左の2つのチャンネルが有りそれらの割当を指定する。
- I2S_CHANNEL_FMT_RIGHT_LEFT: 左右のデータ
- I2S_CHANNEL_FMT_ALL_RIGHT: 右右のデータ
- I2S_CHANNEL_FMT_ALL_LEFT: 左左のデータ
- I2S_CHANNEL_FMT_ONLY_RIGHT: 右のデータ
- I2S_CHANNEL_FMT_ONLY_LEFT: 左のデータ
- communication_format
- I2S_COMM_FORMAT_I2Sが基本の様です。
- intr_alloc_flags
- 割り込みレベル。
- 今回は、ESP_INTR_FLAG_LEVEL1
- dma_buf_count/dma_buf_len
- バッファーの数とバッファーの長さ指定
- サンプルデータのビット数で指定したデータで長さ、dma_buf_lenのバッファーが、dma_buf_count設定される様です。
- 指定する値によっては関数が機能しなくなります。(dma_buf_countに1を指定すると機能しませんでした)
- DMA用のバッファーと思うのですが、この2つの関係が良く分かりません。
- use_apll
- クロックソースをAPLLを使うかの選択。
- tx_desc_auto_clear
- アンダーバファーになった場合の送信データの指定。
- fixed_mclk
- APLLを有効にした場合のクロック指定
続いて、i2s_pin_config_t。こちらは、ピンの指定のみなので簡単です。今回はアンプなので出力用ピンを使用しますが、入力用ピンは使用しません。使用しない時は、”I2S_PIN_NO_CHANGE”と指定する様です。
#define I2S_PIN_WS 4
#define I2S_PIN_CLK 16
#define I2S_PIN_DOUT 17
#define I2S_PIN_DIN I2S_PIN_NO_CHANGE
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_PIN_CLK,
.ws_io_num = I2S_PIN_WS,
.data_out_num = I2S_PIN_DOUT,
.data_in_num = I2S_PIN_DIN,
};
これらパラメータの指定に付いて、”ESP32のI2Sマイク研究 その1 基礎”を参考にしています。
サイン波を出力するスケッチ
これらをまとめてサイン波を出力するスケッチを書いてみました。
#include <driver/i2s.h>
#define I2S_NUM_AMP I2S_NUM_0
#define I2S_PIN_WS 4
#define I2S_PIN_CLK 16
#define I2S_PIN_DOUT 17
#define I2S_PIN_DIN I2S_PIN_NO_CHANGE
#define I2S_SAMPLE_RATE 32000
#define I2S_BUFFER_COUNT 4
#define I2S_BUFFER_SIZE 512
// volume:32 bit (MAX)
#define VOLUME ( (1UL << 28) - 1)
#define BUFSIZE 64
int sin_buf[BUFSIZE];
void setup()
{
Serial.begin(115200);
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_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = I2S_BUFFER_COUNT,
.dma_buf_len = I2S_BUFFER_SIZE,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_PIN_CLK,
.ws_io_num = I2S_PIN_WS,
.data_out_num = I2S_PIN_DOUT,
.data_in_num = I2S_PIN_DIN,
};
i2s_driver_install(I2S_NUM_AMP, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM_AMP, &pin_config);
for(int i=0; i<BUFSIZE; i++)
sin_buf[i] = sin( (2*PI / (BUFSIZE) ) * i) * VOLUME;
}
void loop()
{
i2s_write_bytes(I2S_NUM_AMP, sin_buf, sizeof(sin_buf), portMAX_DELAY);
}
- 23から42行: i2s_config_tとi2s_pin_config_tを設定
- 44行: i2s_driver_install(I2S_NUM_AMP, &i2s_config, 0, NULL);
- i2s_config_tの設定を有効にします。
- 第一引数: i2s_port_t
i2s_num
- 使用するI2Sの番号。
- ESP32のI2Sは2つある。I2S_NUM_0, I2S_NUM_1
- 第二引数: i2s_config_tconst *
i2s_config
*i2s_config
- Configを設定した、i2s_config_tconst型のポインター
- 第三引数: int
queue_size
- I2S event queue size/depth.
- 第四引数: void *
i2s_queue
- I2S event queue handle
- NULLを指定するとqueueを使用しない。
- 45行: i2s_set_pin(I2S_NUM_AMP, &pin_config);
- ピン指定を有効にします。
- 第一引数: i2s_port_t
i2s_num
- 使用するI2Sの番号。
- 第二引数: i2s_pin_config_tconst *
pin
- ピンを指定したi2s_pin_config_t型のポインター
- 47,48行: ここでサイン波を計算しています。
- 53行: i2s_write_bytes(I2S_NUM_AMP, sin_buf, sizeof(sin_buf), portMAX_DELAY);
- Loop関数内でサイン波のデータを繰り返しアンプに送信しています。
- i2s_write_bytes(I2S_NUM_AMP, sin_buf, sizeof(sin_buf), portMAX_DELAY);
- 第一引数: i2s_port_t
i2s_num
- 使用するI2Sの番号。
- 第二引数: const void *
src
- 送信するデータが保存されているバッファーのポインター
- 第三引数: size_t
size
- 送信するバッファーのサイズ
- 第四引数: TickType_t
ticks_to_wait
- 送信時のタイムアウト時間を指定するようです。
- portMAX_DELAYを指定すると、タイムアウト無しとなります。
コンパイル、実行して下さい。綺麗な音が聞こえると思います。とりあえずこれで動いている様です。
次回は、このアンプに付いてもう少しいじってみます。