Arduino Nano 33 BLE サウンドスペクトラムビジュアライザー:リアルタイムオーディオ表示
部品と材料
| Arduino Nano 33 BLE | ×1 | |
| エレクトレットマイクアンプ MAX9814 | ×1 | |
| 0.91インチ OLED I2Cディスプレイ 128×32ピクセル | ×1 | |
| コンデンサ 4.7nF | RCローパスフィルタのカットオフ周波数による | ×1 |
| コンデンサ 470μF | ×1 | |
| 抵抗 2kΩ | RCローパスフィルタのカットオフ周波数による | ×1 |
アプリとオンラインサービス
| Arduino IDE |
このプロジェクトについて
はじめに
小さなOLEDディスプレイ上で、音楽や音に反応するバーグラフを表示します。左端が低周波、右端が高周波です。
構築
接続

- マイクのGAINピンで感度設定:VCC 接続で40dB(低感度)、GND 接続で50dB(中感度)、フロート で60dB(高感度)
- アナログピン0 A0 をマイクアンプ OUT に接続
- 3.3V、GND、SDA(またはアナログ4 A4)、SCL(アナログ5 A5)をI2C SSD1306に接続
- 電源バスのVCC-GND間に470μFコンデンサを接続
低周波ノイズが気になる場合?
近くで動作している電気機器からのノイズです。すべての電子機器は固定周波数・電圧の交流(AC)で動作し、お住まいの国の商用電源周波数(50Hzまたは60Hz)がノイズ源になります。ファラデーケージで簡単に除去できます。回路を箱に入れ、箱をアースに接続し、回路もよくアースしてください。

Arduino Nano 33 BLEのピン配置

Arduino標準の analogRead() と Arduino Nano 33 BLE を使うと、約35,000サンプル/秒しか取得できません。FFTを使う場合、ナイキストの定理(サンプリング周波数の半分まで)により17.5kHz以上の周波数は扱えません。さらにクロック読み取りやビジーウェイトループでの比較処理があるため、安全を見て上限8kHzとしています。この周波数以上をカットするシンプルなRCローパスフィルタを使用できます。


R=180Ω、C=0.1μF でカットオフ周波数 fc=8842Hz
今回は2kΩ抵抗と4.7nFコンデンサで fc=17,000Hz としています

最大サンプリングレートの概算
通常の analogRead() はオーディオサンプリングには遅すぎるため、最大サンプリングレートを概算します。
Arduino Nano 33 BLE で Arduinoコアの analogRead() を使ってどれだけサンプリングできるか測定するスケッチ:
/* Rough calculation of maximum sample rate with AnalogRead */
#define ANALOG_PIN A0
#define SAMPLES 1000000.0
int newTime;
int analogValue;
void setup() {
Serial.begin(9600);
Serial.println("Sampling... please, wait. ");
}
void loop() {
newTime = micros();
for (int i = 0; i < (int)SAMPLES; i++) {
analogValue = analogRead(ANALOG_PIN);
}
float elapsedTimePerSampleUs = (micros() - newTime) / SAMPLES;
Serial.print("Time per sample (us): ");
Serial.println(elapsedTimePerSampleUs);
Serial.print("Max sampling freq(hz): ");
Serial.println(pow(10,6) / elapsedTimePerSampleUs);
}

Arduino Nano 33 BLEで約35,000サンプル/秒。ナイキストの約2倍の要件により、スペクトラムビジュアライザーは17kHz以下に制限されます。この上限以上をカットするローパスフィルタを使用します。
クロック読み取りとビジーウェイトループでの比較があるため、安全マージンを取って8kHzを上限としています。
// サンプリング
for (int i = 0; i < SAMPLES; i++) {
unsigned long newTime = micros();
int value = analogRead(ANALOG_PIN);
vReal[i] = value;
vImag[i] = 0;
while (micros() < (newTime + sampling_period_us)) {
yield();
}
}
ユーザーインターフェース

画面を11の周波数ゾーンに分割し、人間の聴覚に近い対数スケールで周波数をグループ化しています。
人間の可聴域は約20Hz〜20kHz(乳児は20kHz以上も聴取可能だが、成長とともに高周波感度は低下し、平均的な成人の上限は15〜17kHz程度)。
ゾーン3〜13(11ゾーン)を表示に使用。

各バーは、帯域内の最大値を点線で、平均値を塗りつぶし長方形で表示。値はヒューリスティックに決定した基準値を用いてデシベル(dB)で表示。
仕組み
オーディオサンプルを高速フーリエ変換(FFT)で周波数スペクトラムに変換。スペクトラムを14ゾーンに分割・グループ化し、11ゾーンをdBで表示。
マイクモジュールについて



エレクトレットマイクロフォン
エレクトレットは準永久的に帯電した誘電体。セラミック材料を加熱し、磁場中で冷却することで作られます。永久磁石の静電気版のようなものです。エレクトレットマイクでは、この材料の薄片をコンデンサの誘電体の一部として使用し、マイクの振動板が一方の極板になります。音圧で振動板が動くと容量が変化し、誘電体の固定電荷により極板間電圧も変化。このコンデンサを内蔵FETアンプの入力に接続。エレクトレットマイクは小型で高感度、広い周波数特性、低コストが特徴です。
MAX9814 マイクアンプ
MAX9814は自動利得制御(AGC)と低ノイズマイクバイアスを備えたマイクアンプで、ブレークアウト基板でいくつかの設定が可能。
- 3段階の増幅設定(40dB、50dB、60dB)。デフォルト60dB、GainピンをVCCまたはGNDにジャンパーで40dB/50dBに変更可能
- 内蔵低ノイズマイクバイアス 1.25V、2Vpp。アンプ出力は1.25V DCバイアス上に最大約2Vppなので、3.3V入力までのADCで容易に使用可能
- アタック/リリース比もデフォルト1:4000から1:2000や1:500に変更可能
- ライン入力に直接接続する場合、DC成分をブロックするためのブロッキングコンデンサ/カップリングコンデンサが必要。1μF以上を直列に挿入(システムによる。迷えば33μF〜100μFを試す)。カップリング回路はAC信号を通しDC成分を遮断し、オーディオ出力の歪みを防ぐ。有効性は挿入損失、等価直列抵抗、直列共振周波数など周波数依存パラメータに依存
- 電源電圧:2.7V〜5.5V
感度
感度は、アナログ出力電圧またはデジタル出力値と入力音圧の比率で、マイクの主要スペックです。音響領域の単位を電気領域の単位にマッピングすることで、既知の入力に対する出力信号の大きさが決まります。
マイク感度の標準リファレンス入力は、1kHz正弦波、94dB SPL(音圧レベル)、または1Pa(圧力単位)。感度値が高いマイクは、固定音響入力に対してより高い出力レベルを示します。デシベル(dB)で表されるマイク感度は通常負の値なので、絶対値が小さいほど高感度。
アナログマイクの感度は通常 dBV(1.0V rms基準のデシベル)で指定されます。
リファレンスレベルと周波数特性
周波数特性は、周波数スペクトラム全体での出力レベルを示します。高域・低域の限界は、1kHzでの基準出力レベルから3dB下がった点で定義。1kHzの基準レベルは慣例的に0dBで正規化されます。
0.91インチ OLED I2Cディスプレイ 128×32ピクセル
対角約1インチの小型ディスプレイ。128×32個の白色OLEDピクセルで構成され、コントローラーチップで各ピクセルを個別にON/OFF制御。バックライト不要で消費電力が低い。
ここでは4ピンのI2C通信モデルを使用(RESETピンなし)。Arduino UnoのI2Cピンに接続:
- Arduino Nano: SDA (A4)、SCL (A5)
OLED制御には adafruit_SSD1306.h と adafruit_GFX.h ライブラリが必要。インストール手順に従ってください。Adafruitライブラリを使用しています。Adafruitから何か購入して支援することもできます。



次のステップ
Arduino Nano 33 BLEはアナログ読み取りをさらに最適化可能。次期バージョンではnrf52840向けに最適化されたFFTとアナログ入力を使用予定。
コード
Arduino Nano 33 BLE用 サウンドスペクトラムビジュアライザー Arduino
Sound Spectrum Visualizer for Arduino Nano 33 BLE
uses arduinoFFT https://github.com/kosme/arduinoFFT
uses Adafruit_GFX https://github.com/adafruit/Adafruit-GFX-Library
uses Adafruit_SSD1306 https://github.com/adafruit/Adafruit_SSD1306
/* Sound Spectrum Visualizer for Arduino Nano 33 BLE
Arduino based sound visualizer
@author Enrique Albertos
Hardware requirements:
- Arduino or Arduino-compatible boards.
- ssd1306 oled I2C 128x32 display
- Electret Microphone Amplifier with max9814
- Optional: battery for portable use (else power through USB)
Software requirements:
- arduinoFFT https://github.com/kosme/arduinoFFT
- Adafruit_GFX https://github.com/adafruit/Adafruit-GFX-Library
- Adafruit_SSD1306 https://github.com/adafruit/Adafruit_SSD1306
Connections:
- Mic GAIN: * VCC to mic GAIN for 40dB
* GND to mic GAIN for 50dB
* floating mic GAIN for 60dB
- Analog pin 0 to mic amp output
- +3.3V, GND, SDA (or analog 4) and SCL (analog 5) to I2C SSD1306
@uses arduinoFFT https://github.com/kosme/arduinoFFT
@uses Adafruit_GFX https://github.com/adafruit/Adafruit-GFX-Library
@uses Adafruit_SSD1306 https://github.com/adafruit/Adafruit_SSD1306
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "arduinoFFT.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SAMPLES 1024
#define SAMPLING_FREQ 24000
#define AMPLITUDE 100
#define FREQUENCY_BANDS 14
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define BARWIDTH 11
#define BARS 11
#define ANALOG_PIN A0
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
double vImag[SAMPLES];
double vReal[SAMPLES];
unsigned long sampling_period_us;
arduinoFFT fft = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
float reference = log10(50.0);
double coutoffFrequencies[FREQUENCY_BANDS];
void setup() {
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for (;;);
}
display.clearDisplay();
display.display();
display.setRotation(0);
display.invertDisplay(false);
sampling_period_us = (1.0 / SAMPLING_FREQ) * pow(10.0, 6);
double basePot = pow(SAMPLING_FREQ / 2.0, 1.0 / FREQUENCY_BANDS);
coutoffFrequencies[0] = basePot;
for (int i = 1; i < FREQUENCY_BANDS; i++) {
coutoffFrequencies[i] = basePot * coutoffFrequencies[i - 1];
}
for (int i = 0; i < BARS - 1; i++) {
for (int j = 0; j < SCREEN_HEIGHT; j += 4) {
display.writePixel((i + 1) * BARWIDTH + 2, j, SSD1306_WHITE);
}
}
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
}
int oldHeight[20];
int oldMax[20];
double maxInFreq;
void loop() {
for (int i = 0; i < SAMPLES; i++) {
unsigned long newTime = micros();
int value = analogRead(ANALOG_PIN);
vReal[i] = value;
vImag[i] = 0;
while (micros() < (newTime + sampling_period_us)) {
yield();
}
}
fft.DCRemoval();
fft.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fft.Compute(FFT_FORWARD);
fft.ComplexToMagnitude();
double median[20];
double max[20];
int index = 0;
double hzPerSample = (1.0 * SAMPLING_FREQ) / SAMPLES;
double hz = 0;
double maxinband = 0;
double sum = 0;
int count = 0;
for (int i = 2; i < (SAMPLES / 2); i++) {
count++;
sum += vReal[i];
if (vReal[i] > max[index]) {
max[index] = vReal[i];
}
if (hz > coutoffFrequencies[index]) {
median[index] = sum / count;
sum = 0.0;
count = 0;
index++;
max[index] = 0;
median[index] = 0;
}
hz += hzPerSample;
}
if (sum > 0.0) {
median[index] = sum / count;
if (median[index] > maxinband) {
maxinband = median[index];
}
}
int bar = 0;
for (int i = FREQUENCY_BANDS - 1; i >= 3; i--) {
int newHeight = 0;
int newMax = 0;
if (median[i] > 0 && max[i] > 0) {
newHeight = 20.0 * (log10(median[i]) - reference);
newMax = 20.0 * (log10(max[i]) - reference);
}
if (newHeight < 0 || newMax < 0) {
newHeight = 1;
newMax = 1;
}
if (newHeight >= SCREEN_HEIGHT - 2) {
newHeight = SCREEN_HEIGHT - 3;
}
if (newMax >= SCREEN_HEIGHT - 2) {
newMax = SCREEN_HEIGHT - 3;
}
int barX = bar * BARWIDTH + 5;
if (oldHeight[i] > newHeight) {
display.fillRect(barX, newHeight + 1, 7, oldHeight[i], SSD1306_BLACK);
}
if (oldMax[i] > newHeight) {
for (int j = oldMax[i]; j > newHeight; j -= 2) {
display.drawFastHLine(barX, j, 7, SSD1306_BLACK);
}
}
for (int j = newMax; j > newHeight; j -= 2) {
display.drawFastHLine(barX, j, 7, SSD1306_WHITE);
}
display.fillRect(barX, 1, 7, newHeight, SSD1306_WHITE);
oldMax[i] = newMax;
oldHeight[i] = newHeight;
bar++;
}
display.drawFastHLine(0, SCREEN_HEIGHT - 1, SCREEN_WIDTH, SSD1306_WHITE);
display.display();
}
回路図
soundspectrumvisualizer_qLzRsVPVlc.fzz
製造プロセス
- Arduino UNOとOLEDディスプレイでクラシックPongゲームを自作:ステップバイステップチュートリアル
- K30 CO2センサで室内空気質をリアルタイム監視する
- 1SheeldとArduinoで盲ろう者のコミュニケーションを支援する
- スマート貯金箱を作ろう:Arduino Nanoでコインアクセプターを制御
- ArduinoでBluetooth経由でLEDを制御 – シンプルDIYガイド
- Arduino Nanoとジョイスティックで2軸ステッピングモーターを制御:Visuinoで簡単プログラミング
- Arduino Nano 33 BLE Senseで音声データ伝送を実現する方法
- Arduino Nanoで作る携帯型ガイガーカウンター
- Arduino UNOとEthernetシールドで太陽放射を測定する自作ピラノメーター
- Arduino Nanoで作る「食べさせロボット」:超音波センサーで食べ物検知し口を開閉
- Arduinoリアルタイム気象時計 - OLEDに時刻/日付/温度を表示