インターフォンの製作(その5)

今回は、アンプの設定をもう少しいじってみます。

音量と音階

前回データとしてサイン波1波長分を64個持っていました。音の大きさはサイン波の振幅と関係が有ると容易に想像出来ます。先ずは、それを確認して見ましょう。前回のスケッチで、


#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
 
uint8_t recBuffer[I2S_BUFFER_SIZE]; // DMA Buffer
 
// 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);
}
  • 16行: define VOLUME ( (1UL << 28) – 1)

ここでサイン波の振幅を設定しています。アンプのデータは32ビットなので正負を考慮すると、(1UL << 31) – 1 が最大値となります。この値に修正して実行して下さい。音が大きくなった事が分かります。もちろんこの値を小さくすると音は小さくなります。このスケッチ実は、(1UL << 31) – 1 では音が大きすぎたので、(1UL << 28) – 1を使用しています。

音量をコントロールする関数が有るか探したのですが見つかりませんでした。単純に思いつく方法はデータの各要素に係数をかける事ですが、この方法ではちょっと悲しいです。

次は、音階です。音の高さです。サイン波1波長分64個だけでは音の高さは決まりません。音の高さはサンプリング周波数とこのサイン波のデータの数で決まる様です。現在の設定は、

  • サンプリング周波数: 32000Hz (スケッチの9行で設定)
  • サイン波の数: 64個(スケッチの19行で設定)

あるサイン波1波長分を32000Hzでサンプリングしてたら64回サンプリング出来たと考えると1波長の長さは、(1 / 32000) x 64 秒。この逆数が周波数となるので、周波数は 32000 / 64 = 500Hz。 多分今回のスケッチのサイン波は500Hz の音と思われます。とすると、

  • サンプリング周波数を固定した場合:
    • 音階を下げる: サイン波の数を増やす。
    • 音階を上げる: サイン波の数を減らす。
  • サイン波の数を固定した場合:
    • 音階を下げる: サンプリング周波数を下げる。
    • 音階を上げる: サンプリング周波数を上げる。

となるはずです。確かめて見ましょう。先ずは、サイン波の数を変えて見ます。サイン波の数は、

  • 18行: define BUFSIZE 64

で設定しています。例えばこの64を32に変更するれば音は高くなり、128に変更すれば低くなるはずです。値を変えて確かめて下さい。予想どおりになります。

次にサンプリング周波数ですが、スケッチでは

  • 9行: define I2S_SAMPLE_RATE 32000

で設定しています。勝手な値を設定出来る様ですが音の場合、16000、22050、32000、44100、48000 が良く使われる様です。今32000を使用しているので、16000に変更すれば音は低く、48000にすれば高くなるはずです。確認して下さい。

つまり、大事な点は同じデータでもサンプリング周波数の設定が違うと違う音として再生されるという事です。今後マイクを繋いで行くのですが、マイクのサンプリング周波数とアンプの周波数を合わせないと音が正しく再生されないということになります。

dma_buf_count/dma_buf_len

この2つのパラメータの指定方法が良く分かりません。単にESP32からアンプへデータを送る時のバッファー数とその長さの指定と思っているのですが、イマイチしっくり来ません。今回は、

  • dma_buf_count: 4
  • dma_buf_len : 512

と指定しています。これで32ビットの512個のバッファーが4個と理解しています。今回1回に送るデータは32ビットのデータ64個ですから、送るデータに対して十分に大きなバッファーとなります。

ちなみに、バッファーの数を1個。長さと512個と設定します。送るデータに対し十分に大きなバッファーですが、この設定では動きませんでした(音が再生されませんでした)。バッファーの数は最低でも2個必要な様です。

バッファーの数を2個に固定して、長さを変えてみました。長さを32個に設定すれば、送るデータの数と同じ64個になるので、32に設定して実行しましたが、問題無く再生されました。長さ256,128,64でも問題無く再生されました。16,8,4と減らして行くと4で再生しなくなりました。バッファーの総数(数 x 長さ)が送るデータより大きい必要は無い様です。また、小さ過ぎても機能しない様です。

バッファーの大きさは 数 x 長さ で決まります。大きく設定するとプログラムメモリーに影響しそうです。関数のソースが分からないので試行錯誤で値を決める事になりそうです。取り敢えず、dma_buf_count:4 / dma_buf_len :512 とします。

intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,

loop()関数に有る

i2s_write_bytes(I2S_NUM_AMP, sin_buf, sizeof(sin_buf), portMAX_DELAY);

を 削除して、setup()関数の最後に追加して下さい。


.
.
. 
 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;

  i2s_write_bytes(I2S_NUM_AMP, sin_buf, sizeof(sin_buf), portMAX_DELAY);
}
 
void loop() 
{
}

この設定では、サイン波のデータはsetup()での1回しかアンプに送られない事になります。これを実行して下さい。さっきまで綺麗に鳴っていた音がブツブツと途切れた音に変わったと思います。音が変わった事自体変ですが、ブツブツと鳴り続けるのも変です。どうも、intr_alloc_flags が関係している様です。

多分、”i2s_write_bytes()”は、データをバッファー(N=4: L=512のDMAバッファー)に送るだけで、DMAバッファーにデータ送られると定期的に割り込みを発生させてDMAバッファーからアンプに送られる仕組みになっていると思われます。だから、setup()関数で1回、”i2s_write_bytes()”を実行したのみなのに音が繰り返し再生されているのだと思われます。ではなぜブツブツと切れた音になるのか、多分こうだと思います、

  • DMA用設定したバッファーサイズは、4x512=2048個。
  • 一回で送るデータは64個
  • setup()でi2s_write_bytes()を実行してDMAバッファーの頭64個にデータが入る。残りは、多分”0”。
  • 定期的に割り込みによって、このDMAバッファーがアンプに送られれば、最初の64個でサイン波を再生。残りのは1984個は、”0”なので再生無し。
  • 結果的にブツブツと再生し続ける。

これを、確認する為に、dma_buf_countとdma_buf_lenを変えて見ます。DMAバッファーの数を1回に送るデータと同じ64個にします。dma_buf_countは最小でも2にする必要が有るので2とする。dma_buf_lenを32に設定すれば合計で64個になります。スケッチを修正して実行して下さい。綺麗なサイン波に戻ります。でもバッファーの数を変えるとそれに合わせて音が変わります。ここまでをまとめると、

  • i2s_write_bytes()は指定したデータをDMAバッファーにコピーするのみでアンプには送られない。
  • i2s_write_bytes()を実行すると割り込みが発生し、定期的にDMAバッファーのデータがアンプに送られる。
  • 次の割り込みが発生する前にDMAバッファーの値を書き換えないと前回のデータがアンプに送られる。
    • 今回のスケッチの様にDMAバッファーの値を書き換えなければ常に同じデータがアンプへ送られる

では何で、”i2s_write_bytes()が loop()内に有った場合、4x 512の2048個で綺麗なサイン波で聞こえたのか” と疑問に思います。多分、割り込みがかかる前にi2s_write_bytes()が繰り返し実行されDMAバッファーを全部埋めたのは無いかと推測します。それなら綺麗なサイン波として聞こえるはずです。

I2Sを正しく使用するには、dma_buf_countとdma_buf_lenの理解が必要です。こんな曖昧な状態で次に進むには不安が有りますが、これ以上やっても何か分かる感じがしません。そこで、次回はマイクを繋ぎたいと思います。