Arduino Nano 33 BLE 声频频谱可视化:实时 OLED 声纹显示
组件与物料
| 组件 | 数量 | 备注 |
|---|---|---|
| Arduino Nano 33 BLE | 1 | nRF52840 内核 |
| MAX9814 电容麦克风模块 | 1 | 自动增益控制 (AGC) |
| 0.91" OLED I2C 128×32 | 1 | SSD1306 驱动 |
| 4.7nF 电容 | 1 | RC 低通滤波截止频率用 |
| 470μF 电容 | 1 | 电源总线滤波 |
| 2kΩ 电阻 | 1 | RC 低通滤波截止频率用 |
应用与服务
- Arduino IDE
项目简介
功能演示
OLED 上实时显示 11 段对数分布频谱柱状图,左低频右高频,随音乐/声音动态跳动。
硬件连接

- MAX9814 GAIN 脚:接 VCC=40dB(低灵敏)、接 GND=50dB(中)、悬空=60dB(高)
- 麦克风 OUT → Arduino A0
- OLED:3.3V、GND、SDA(A4)、SCL(A5)
- 电源总线并联 470μF 电容
低频工频噪声抑制
市电 50/60Hz 耦合噪声可用法拉第笼屏蔽:电路装金属盒接地。

采样率与抗混叠滤波

Arduino 核心 analogRead() 在 Nano 33 BLE 上约 35 kSPS,奈奎斯特极限 17.5kHz。考虑 busy-wait 采样开销,保守上限 8kHz。需 RC 低通滤波器抗混叠:
- R=180Ω, C=0.1μF → fc≈8.8kHz
- 本例用 R=2kΩ, C=4.7nF → fc≈17kHz (更宽频响)



最大采样率实测
#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(1e6/elapsedTimePerSampleUs);
}

实测 ~35 kSPS。采样代码用 micros() busy-wait 定时:
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();
}
显示界面设计

128×32 像素分 11 个频带,对数刻度模拟人耳感知。人耳 20Hz-20kHz,婴儿略高,成人上限 15-17kHz。使用第 3-13 带 (共 11 带)。

每柱:虚线=频带峰值,实心矩形=频带均值,单位 dB (经启发式基准归一化)。
工作流程
- 定时采样 1024 点 (24kHz 采样率)
- FFT 变频域 (去直流、汉宁窗、幅度谱)
- 按对数截止频率分 14 带,取 3-13 带
- 计算各带均值与峰值,换算 dB 绘制
麦克风模块详解 (MAX9814)

- 驻极体电容咪头 + FET 前置放大
- MAX9814 集成 AGC,三档增益 (40/50/60dB),低噪偏置 1.25V,输出 2Vpp 适配 3.3V ADC
- Attack/Release 比可选 1:4000/1:2000/1:500
- 直通 Line-in 需串联阻容耦合电容 (≥1μF,建议 33-100μF) 隔直
OLED 显示屏
128×32 白色 OLED,I2C 四线 (SDA/SCL/3.3V/GND),无 RESET 脚。需库:Adafruit_SSD1306、Adafruit_GFX。
后续优化
Nano 33 BLE 的 nRF52840 可用 PDM/更高速 ADC,后续版本将优化采样与 FFT。
代码
完整草图 (依赖 arduinoFFT、Adafruit_GFX、Adafruit_SSD1306):
/* Sound Spectrum Visualizer for Arduino Nano 33 BLE
* @author Enrique Albertos
* 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];
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 hz=0, hzPerSample = (1.0*SAMPLING_FREQ)/SAMPLES;
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; count=0; index++; max[index]=0; median[index]=0;
}
hz += hzPerSample;
}
if (sum>0) { median[index]=sum/count; }
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();
}
原理图
Fritzing: soundspectrumvisualizer_qLzRsVPVlc.fzz

制造工艺
- 用 Arduino UNO 和 OLED 显示屏制作经典 Pong 游戏:分步教程
- K30 CO₂ 传感器:室内空气质量实时监测
- 基于 1Sheeld 与 Arduino 的盲聋人士沟通辅助系统
- 智能存钱罐:Arduino Nano 控制硬币识别器实现余额显示
- Arduino + HC-05 蓝牙控制 LED:MIT App Inventor 手机 App 开发全攻略
- Arduino Nano + 摇杆控制双步进电机:Visuino 图形化教程
- 使用 Arduino Nano 33 BLE Sense 实现音频数据传输
- Arduino Nano 手持盖革计数器:DIY 核辐射检测仪
- Arduino UNO + 以太网盾板实现低成本太阳辐射测量仪(热电堆原理)
- 手把手打造Arduino Nano喂食机器人
- Arduino 实时天气时钟:OLED 显示时间、日期与温度
- 便携可编程口袋电源:Arduino 兼容、OLED 显示、电压电流功率三表合一