Espectrómetro de Audio con Tiras LED RGB

ESP32 Audio Spectrum Analyzer
Proyecto DIY · ESP32 + FFT + WS2812B

Espectrómetro de Audio
con Tiras LED RGB

Visualizador de frecuencias en tiempo real usando transformada de Fourier (FFT) en un ESP32. El color de cada barra cambia de verde a rojo según la potencia de cada banda de frecuencia.

ESP32 FFT WS2812B I2S / Analógico FastLED 16 bandas
5
8
· TIRAS WS2812B · 16 BANDAS × 20 LEDS ·
20Hz80Hz250Hz1kHz 4kHz8kHz16kHz20kHz
PICO: — POT: — BANDA: — RMS: —
ComponenteCant.NotasPrecio aprox.
ESP32 DevKit v1138 pines, dual core 240MHz$4–6 USD
Micrófono INMP4411I2S digital, 24 bits, mejor calidad$3–5 USD
Micrófono MAX44661Analógico, más fácil, alternativa$1–2 USD
Tiras WS2812B1620 LEDs por tira = 320 LEDs total$1–2 USD/m
Fuente 5V1Mínimo 10A. 320 LEDs × 60mA$8–15 USD
Resistencias 330Ω16Una por pin de datos LED$0.50 USD
Capacitor 1000µF 10V1Elimina parpadeo en las tiras$0.50 USD
⚡ Alimentación crítica: Nunca alimentes las tiras desde el pin 5V del ESP32. Usa siempre una fuente externa dedicada de 5V/10A mínimo. El GND de la fuente debe estar conectado al GND del ESP32 (GND común).

INMP441 → ESP32

VCC
3.3V
GND
GND
SD
GPIO 32
SCK
GPIO 33
WS
GPIO 25
L/R
GND (mono)

MAX4466 → ESP32 (analógico)

VCC
3.3V
GND
GND
OUT
GPIO 34 (ADC)

ℹ Agrega divisor de tensión 2×100kΩ y capacitor 100nF para centrar la señal en 1.65V.

WS2812B → ESP32

DIN 0
GPIO 2 + 330Ω
DIN 1
GPIO 4 + 330Ω
DIN 2
GPIO 5 + 330Ω
DIN 3–15
GPIO 12–27 + 330Ω
5V
Fuente externa
GND
GND común

Condensador protector

Coloca el capacitor de 1000µF en paralelo entre el 5V y GND de la primera tira LED, lo más cerca posible al conector.

Elimina el pico de corriente que causa parpadeos al encender.

ℹ Versión con micrófono digital INMP441 (I2S). Mayor calidad de audio, 24 bits, hasta 20kHz estable.
esp32_spectrum_i2s.ino
// ═══════════════════════════════════════════════
// ESP32 Audio Spectrum Analyzer · I2S INMP441
// ═══════════════════════════════════════════════
#include <Arduino.h>
#include <driver/i2s.h>
#include <arduinoFFT.h>
#include <FastLED.h>

// ── I2S (INMP441) ─────────────────────────────
#define I2S_WS    25
#define I2S_SCK   33
#define I2S_SD    32
#define SAMPLE_RATE   44100
#define BUFFER_SIZE   1024

// ── LEDs ──────────────────────────────────────
#define NUM_BANDS     16
#define LEDS_PER_BAND 20
#define SENSITIVITY   2.5f
#define NOISE_FLOOR   500
#define SMOOTHING     0.25f

const uint8_t LED_PINS[NUM_BANDS] = {
  2,4,5,12,13,14,15,16,
  17,18,19,21,22,23,26,27
};

const float BAND_FREQ[NUM_BANDS+1] = {
  20,40,80,120,200,315,500,800,
  1250,2000,3150,5000,8000,12500,16000,20000,22050
};

CRGB leds[NUM_BANDS][LEDS_PER_BAND];
double vReal[BUFFER_SIZE], vImag[BUFFER_SIZE];
ArduinoFFT<double> FFT(vReal, vImag, BUFFER_SIZE, SAMPLE_RATE);
float bandValues[NUM_BANDS]={}, peakValues[NUM_BANDS]={};
int   peakTimer[NUM_BANDS]={};

void i2s_init() {
  i2s_config_t cfg = {
    .mode=(i2s_mode_t)(I2S_MODE_MASTER|I2S_MODE_RX),
    .sample_rate=SAMPLE_RATE,
    .bits_per_sample=I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format=I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format=I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags=ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count=4, .dma_buf_len=BUFFER_SIZE
  };
  i2s_pin_config_t pins={.bck_io_num=I2S_SCK,.ws_io_num=I2S_WS,
    .data_out_num=I2S_PIN_NO_CHANGE,.data_in_num=I2S_SD};
  i2s_driver_install(I2S_NUM_0,&cfg,0,NULL);
  i2s_set_pin(I2S_NUM_0,&pins);
}

CRGB powerToColor(float p) {
  p = constrain(p,0.0f,1.0f);
  if(p<0.4f) return CRGB(0, 50+(uint8_t)(p/0.4f*150), 0);
  if(p<0.7f){float t=(p-0.4f)/0.3f; return CRGB(t*255,200,0);}
  if(p<0.9f){float t=(p-0.7f)/0.2f; return CRGB(255,200-t*100,0);}
  float t=(p-0.9f)/0.1f;
  return CRGB(255,100-t*100,t*30);
}

void readMic() {
  int32_t raw[BUFFER_SIZE]; size_t b=0;
  i2s_read(I2S_NUM_0,raw,sizeof(raw),&b,portMAX_DELAY);
  for(int i=0;i<BUFFER_SIZE;i++){vReal[i]=(raw[i]>>8); vImag[i]=0;}
}

void computeFFT() {
  FFT.windowing(FFTWindow::Hamming,FFTDirection::Forward);
  FFT.compute(FFTDirection::Forward);
  FFT.complexToMagnitude();
  for(int b=0;b<NUM_BANDS;b++){
    int lo=BAND_FREQ[b]*BUFFER_SIZE/SAMPLE_RATE;
    int hi=min((int)(BAND_FREQ[b+1]*BUFFER_SIZE/SAMPLE_RATE),BUFFER_SIZE/2);
    float s=0; int n=0;
    for(int i=lo;i<hi;i++) if(vReal[i]>NOISE_FLOOR){s+=vReal[i];n++;}
    float v=n?constrain(s/n*SENSITIVITY/100000.f,0,1):0;
    float sp=v>bandValues[b]?SMOOTHING*2:SMOOTHING*0.5f;
    bandValues[b]+=(v-bandValues[b])*sp;
    if(bandValues[b]>peakValues[b]){peakValues[b]=bandValues[b];peakTimer[b]=30;}
    if(peakTimer[b]>0)peakTimer[b]--;
    else peakValues[b]=max(0.f,peakValues[b]-0.015f);
  }
}

void renderLEDs() {
  for(int b=0;b<NUM_BANDS;b++){
    int lit=bandValues[b]*LEDS_PER_BAND;
    int pk=peakValues[b]*LEDS_PER_BAND;
    for(int l=0;l<LEDS_PER_BAND;l++){
      if(l<lit) leds[b][l]=powerToColor((float)l/LEDS_PER_BAND+0.05f);
      else if(l==pk&&peakTimer[b]>0) leds[b][l]=CRGB::White;
      else leds[b][l]=CRGB::Black;
    }
  }
  FastLED.show();
}

void setup() {
  FastLED.addLeds<WS2812B,2,GRB>(leds[0],LEDS_PER_BAND);
  FastLED.addLeds<WS2812B,4,GRB>(leds[1],LEDS_PER_BAND);
  // ... repetir para las 16 tiras ...
  FastLED.setBrightness(180);
  FastLED.clear(); FastLED.show();
  i2s_init();
}

void loop() {
  readMic();
  computeFFT();
  renderLEDs();
}
ℹ Versión con micrófono analógico MAX4466 o KY-038. Más fácil y barato. Reemplaza los bloques I2S.
esp32_spectrum_analog.ino
// ═══════════════════════════════════════════════
// ESP32 Audio Spectrum · Micrófono Analógico
// MAX4466 o KY-038 en GPIO 34
// ═══════════════════════════════════════════════
#include <Arduino.h>
#include <arduinoFFT.h>
#include <FastLED.h>

#define MIC_PIN     34      // ADC1_CH6 — solo entrada
#define SAMPLE_RATE 10000   // Hz (max estable con ADC)
#define BUFFER_SIZE 512     // Potencia de 2
#define ADC_CENTER  2048    // Centro 12 bits (0–4095)
#define NUM_BANDS   16
#define LEDS_PER_BAND 20

CRGB leds[NUM_BANDS][LEDS_PER_BAND];
double vReal[BUFFER_SIZE], vImag[BUFFER_SIZE];
ArduinoFFT<double> FFT(vReal, vImag, BUFFER_SIZE, SAMPLE_RATE);
float bandValues[NUM_BANDS]={}, peakValues[NUM_BANDS]={};
int   peakTimer[NUM_BANDS]={};

const float BAND_FREQ[NUM_BANDS+1] = {
  20,40,80,120,200,315,500,800,
  1250,2000,3150,4500,5000,7000,9000,10000,10500
};

void readMicAnalog() {
  const uint32_t interval = 1000000 / SAMPLE_RATE;
  uint32_t t = micros();
  for(int i=0; i<BUFFER_SIZE; i++){
    while(micros() < t);
    t += interval;
    vReal[i] = analogRead(MIC_PIN) - ADC_CENTER;
    vImag[i] = 0;
  }
}

CRGB powerToColor(float p) {
  p = constrain(p,0,1);
  if(p<0.4f) return CRGB(0,50+p/0.4f*150,0);
  if(p<0.7f){float t=(p-0.4f)/0.3f; return CRGB(t*255,200,0);}
  float t=(p-0.7f)/0.3f;
  return CRGB(255,200-t*200,0);
}

void computeFFT() {
  FFT.windowing(FFTWindow::Hamming,FFTDirection::Forward);
  FFT.compute(FFTDirection::Forward);
  FFT.complexToMagnitude();
  for(int b=0;b<NUM_BANDS;b++){
    int lo=BAND_FREQ[b]*BUFFER_SIZE/SAMPLE_RATE;
    int hi=min((int)(BAND_FREQ[b+1]*BUFFER_SIZE/SAMPLE_RATE),BUFFER_SIZE/2);
    float s=0; int n=0;
    for(int i=lo;i<hi;i++) if(vReal[i]>300){s+=vReal[i];n++;}
    float v=n?constrain(s/n*3.0f/2048.f,0,1):0;
    float sp=v>bandValues[b]?0.5f:0.1f;
    bandValues[b]+=(v-bandValues[b])*sp;
    if(bandValues[b]>peakValues[b]){peakValues[b]=bandValues[b];peakTimer[b]=30;}
    if(peakTimer[b]>0)peakTimer[b]--;
    else peakValues[b]=max(0.f,peakValues[b]-0.02f);
  }
}

void renderLEDs() {
  for(int b=0;b<NUM_BANDS;b++){
    int lit=bandValues[b]*LEDS_PER_BAND;
    int pk=peakValues[b]*LEDS_PER_BAND;
    for(int l=0;l<LEDS_PER_BAND;l++){
      if(l<lit) leds[b][l]=powerToColor((float)l/LEDS_PER_BAND);
      else if(l==pk&&peakTimer[b]>0) leds[b][l]=CRGB::White;
      else leds[b][l]=CRGB::Black;
    }
  }
  FastLED.show();
}

void setup() {
  analogReadResolution(12);
  analogSetAttenuation(ADC_11db);
  analogSetPinAttenuation(MIC_PIN, ADC_11db);
  FastLED.addLeds<WS2812B,2,GRB>(leds[0],LEDS_PER_BAND);
  // ... agregar las 16 tiras ...
  FastLED.setBrightness(180);
  FastLED.clear(); FastLED.show();
}

void loop() {
  readMicAnalog();
  computeFFT();
  renderLEDs();
}
platformio.ini
; PlatformIO — pegar en platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
  fastled/FastLED @ ^3.6.0
  kosme/arduinoFFT @ ^2.0.2
monitor_speed = 115200
Arduino IDE — Library Manager
Herramientas → Administrar bibliotecas:

FastLED        by Daniel Garcia     v3.6+
arduinoFFT     by Enrique Condes    v2.0+

Placa: "ESP32 Dev Module"
Menú: Herramientas → Placa → esp32 → ESP32 Dev Module
Partición: "Default 4MB with spiffs"
MicTipoFreq maxResoluciónDificultad
INMP441Digital I2S20 kHz24 bitsMedia
MAX4466Analógico~10 kHz12 bits ADCFácil
KY-038Analógico~8 kHz12 bits ADCMuy fácil

 

Para el micrófono analógico

 

 

 

 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *