Спектральный анализатор звука на Arduino Nano 33 BLE с OLED-дисплеем
Компоненты и материалы
| Arduino Nano 33 BLE | ×1 |
| Электретный микрофон с усилителем MAX9814 | ×1 |
| OLED I2C 0.91" 128×32 (SSD1306) | ×1 |
| Конденсатор 4.7 нФ (для RC ФНЧ, зависит от частоты среза) | ×1 |
| Конденсатор 470 мкФ (стабилизация питания) | ×1 |
| Резистор 2 кОм (для RC ФНЧ) | ×1 |
Приложения
- Arduino IDE
О проекте
Введение
Визуализация звука на маленьком OLED: столбиковая диаграмма спектра в реальном времени. Низкие частоты — слева, высокие — справа.
Сборка и подключение
- GAIN микрофона: VCC→GAIN = 40dB (низкая чувствительность), GND→GAIN = 50dB (средняя), GAIN висит = 60dB (высокая).
- Микрофон OUT → A0 (аналоговый вход)
- OLED I2C: 3.3V, GND, SDA (A4), SCL (A5)
- 470 мкФ между VCC и GND на шине питания
Проблема низкочастотного гудения (50/60 Гц): от сетевых наводок. Решение — фарадеевская клетка: схему в коробке, корпус на землю.
Arduino Nano 33 BLE + analogRead() дает ~35 000 сэмплов/с. Найквист → макс. частота 17.5 кГц. С учетом накладных расходов берем 8 кГц верхнюю границу. RC ФНЧ отсекает выше.
Использую R=2кОм, C=4.7нФ → fc≈17 кГц.
Оценка максимальной частоты дискретизации
Тестовый скетч для замера скорости analogRead():
#define ANALOG_PIN A0
#define SAMPLES 1000000.0
int newTime, 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);
}
~35 ксэмпл/с → Найквист 17.5 кГц. В коде busy-wait с micros() для точного периода дискретизации:
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 частотных зон по логарифмической шкале (как восприятие уха). Человек слышит 20 Гц–20 кГц (взрослые ~15-17 кГц). Используем зоны 3–13.
Каждый столбик: пунктирная линия — максимум в полосе, закрашенный прямоугольник — среднее. Значения в децибелах относительно эмпирического референса.
Как это работает
- Аудиосэмплы → FFT (быстрое преобразование Фурье) → частотный спектр.
- Спектр делится и группируется в 14 зон, 11 отображаются в дБ.
О микрофонном модуле MAX9814
Электретный микрофон: квази-постоянно заряженный диэлектрик. Диафрагма меняет емкость → напряжение на конденсаторе. Встроенный FET-усилитель. Малый размер, высокая чувствительность, широкий АЧХ, низкая цена.
MAX9814: усилитель с АРУ (авт. регулировкой усиления), низкий шум, bias 1.25В. 3 настройки усиления: 40/50/60dB (джампер GAIN к VCC/GND/висячий). Выход ~2В пик-пик на bias 1.25В — идеально для 3.3В АЦП. Attack/Release настраиваем: 1:4000 (умолч.), 1:2000, 1:500.
Для Line In — разделительный конденсатор >1мкФ (33-100мкФ) для блокировки DC.
Питание: 2.7–5.5В.
Чувствительность: отношение выходного сигнала к звуковому давлению. Эталон: 1 кГц, 94 дБ SPL (1 Па). Выше чувствительность (менее отрицательное dBV) → больше выходной сигнал.
АЧХ: границы ±3 дБ от уровня на 1 кГц (референс 0 дБ).
OLED 0.91" 128×32 I2C
Маленький дисплей ~1", 128×32 белых пикселей, без подсветки — мало энергии. 4 пина, I2C. Подключение к Nano: SDA→A4, SCL→A5.
Библиотеки: Adafruit_SSD1306.h, Adafruit_GFX.h (установка через Library Manager).
Дальнейшие планы: Nano 33 BLE умеет быстрее читать АЦП (nrf52840). Следующая версия — оптимизированный FFT и ADC.
Код
Полный скетч использует arduinoFFT, Adafruit_GFX, Adafruit_SSD1306:
/* Sound Spectrum Visualizer for Arduino Nano 33 BLE
@author Enrique Albertos
Hardware: Arduino Nano 33 BLE, SSD1306 128x32 I2C, MAX9814, опционально батарея
Libraries: arduinoFFT, Adafruit_GFX, Adafruit_SSD1306
Connections: MIC GAIN (VCC/GND/float), A0←MIC OUT, 3.3V/GND/SDA/SCL→OLED
License: GPL v3+ */
#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], 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) * 1e6;
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], 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] = {0}, max[20] = {0};
int index = 0;
double hzPerSample = (1.0 * SAMPLING_FREQ) / SAMPLES;
double hz = 0, 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; count = 0;
index++; max[index] = 0; median[index] = 0;
}
hz += hzPerSample;
}
if (sum > 0) {
median[index] = sum / count;
if (median[index] > maxInFreq) maxInFreq = median[index];
}
int bar = 0;
for (int i = FREQUENCY_BANDS-1; i >= 3; i--) {
int newHeight = 0, 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

Производственный процесс
- Создание классической игры Pong на Arduino UNO с OLED-дисплеем: пошаговый урок
- Датчик CO2 K30: мониторинг качества воздуха в реальном времени на Arduino
- Устройство для общения глухосліпых людей с помощью 1Sheeld и Arduino
- Умная копилка: управление монетоприёмником на Arduino Nano
- Управление светодиодом по Bluetooth с Arduino: простой DIY-гид
- Управление двумя шаговыми двигателями через джойстик на Arduino Nano — простой урок (Visuino)
- Передача аудиоданных с Arduino Nano 33 BLE Sense: Руководство по Chirp SDK
- Ручной дозиметр (Гейгер-счетчик) на Arduino Nano: сборка в 3D-корпусе
- Измерение солнечной радиации на Arduino UNO с Ethernet Shield
- Робот-«едобот» на Arduino Nano: открывает рот при подаче еды (HC-SR04 + сервопривод)