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 *