Skip to Content

Analisador de Espectro com ESP32 e MAX4466

Analisador de Espectro com ESP32 e MAX4466

Neste tutorial, vais aprender a construir um Analisador de Espectro de Frequência simples com um ESP32. Vamos usar o microfone MAX4466 e a biblioteca arduinoFFT para medir as frequências no sinal de áudio e mostrar o espectro de frequência num OLED.

Vamos começar com as peças que vais precisar para este projeto.

Peças Necessárias

Vais precisar de um ESP32, um ecrã OLED e o Módulo de Microfone MAX4466. Estou a usar o ESP32 lite como microprocessador, pois tem uma interface de carregamento de bateria e podes facilmente usar o Analisador de Espectro com bateria para o tornar portátil. Mas qualquer outro ESP32 também funciona.

ESP32 lite Lolin32

ESP32 lite

USB data cable

Cabo USB de Dados

Microfone MAX4466

OLED display

Ecrã OLED

Dupont wire set

Conjunto de Fios Dupont

Half_breadboard56a

Breadboard

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Analisador de Espectro de Frequência

Um Analisador de Espectro de Frequência é um dispositivo que visualiza e analisa o conteúdo espectral dos sinais e mostra a amplitude ou potência de um sinal ao longo da sua gama de frequências. Essencialmente, indica quanta potência do sinal está distribuída por frequências ou bandas de frequência específicas.

A imagem seguinte mostra um espectro de frequência. A gama de frequências está no eixo x e a magnitude do sinal é medida em Decibel como mostrado no eixo y:

Exemplo de um Espectro de Frequência (source)

Dependendo da precisão e da gama de frequências, os Analisadores de Espectro podem ser muito caros. Abaixo está uma imagem do Analisador de Espectro Siglent Technologies SSA3021X que pode analisar frequências de rádio de 9 kHz a 2,1 GHz.

Siglent Technologies SSA3021X Spectrum Analyzer
Analisador de Espectro Siglent Technologies SSA3021X (source)

Espectro de Frequência de Áudio

Uma aplicação comum dos Analisadores de Espectro é visualizar a distribuição de frequências dentro de umsinal de áudio. A gama de frequências audíveis de um sinal de áudio que os humanos conseguem ouvir varia de 20 a 20.000 Hz (=20KHz). No entanto, estes são os extremos e a gama auditiva diminui com a idade. Para adultos, o limite superior é tipicamente cerca de 15.000 Hz. Se quiseres saber o teu limite superior, podes experimentar este pequeno teste auditivo..

A imagem seguinte mostra o ecrã de um típico Analisador de Espectro de Áudio. Podes ver as diferentes bandas de frequência (barras verdes) que vão de 20Hz a 20KHz.

BDS PP-131P Digital Audio Spectrum Analyzer
Analisador Digital de Espectro de Áudio BDS PP-131P (source)

Neste tutorial, vamos construir um Analisador de Espectro de Áudio semelhante. No entanto, não esperes demasiado. Como estamos a usar um ESP32 barato, um OLED pequeno e um microfone simples MAX4466, o nosso Analisador de Espectro não será muito preciso.

A sua gama de frequências será apenas de 125Hz a 8000 Hz e teremos apenas 7 bandas de frequência, mas será suficiente para visualizar e animar o conteúdo de frequência da música.

Funcionamento de um Analisador de Espectro

Como funciona o nosso pequeno Analisador de Espectro de Áudio? O microfone converte ondas sonoras em impulsos elétricos. Estes impulsos fracos são amplificados pelo MAX4466 e enviados para o Conversor Analógico-Digital (ADC) do ESP32. Se leres a saída do ADC e a representares graficamente, o sinal de áudio detetado pode parecer assim:

Time domain signal composed of cosine waves
Sinal no domínio do tempo composto por ondas cosseno (source)

No eixo x está o tempo e no eixo y está a amplitude do sinal. Este sinal é chamado de sinal no domínio do tempo, pois é reportado ao longo do tempo, mas não mostra as frequências dentro do sinal.

O sinal acima é na verdade composto por sinais cosseno com frequências de 10, 20, 30, 40 e 50 Hz com amplitudes crescentes. Se aplicarmos um método matemático chamado Fast Fourier Transformation (FFT), podemos extrair estas frequências do sinal. O gráfico abaixo mostra o mesmo sinal após a aplicação da FFT:

Frequency domain signal composed of cosine waves
Sinal no domínio da frequência composto por ondas cosseno (source)

O sinal está agora no domínio da frequência, com a frequência no eixo x (em vez do tempo). Podes ver claramente os picos em 10, 20, 30, 40 e 50 Hz, que revelam as frequências que compõem o sinal de entrada. Para realizar a FFT, deixamos o ESP32 amostrar o sinal de áudio obtido do ADC e depois chamamos a arduinoFFT library para extrair as frequências. O espectro de frequência resultante é então mostrado no OLED.

No entanto, não vamos mostrar as frequências individuais no espectro, mas agrupá-las em bandas de frequência que são mais fáceis de ler e comumente usadas em Analisadores de Espectro de Áudio. As bandas de frequência serão aproximadamente 125, 250, 500, 1000, 2000, 4000 e 8000 Hz, como mostrado abaixo:

Frequency Spectrum on OLED
Espectro de Frequência no OLED

Módulo de Microfone MAX4466

O módulo de microfone MAX4466 é uma placa breakout com um microfone eletreto de 20-20KHz e o MAX4466 circuito pré-amplificador. Na parte de trás da placa há um pequeno trimmer que permite ajustar o ganho de 25x a 125x. A imagem abaixo mostra a frente e a parte de trás da placa:

Frente e Verso do Módulo de Microfone MAX4466

O módulo funciona com 2.4…5.5V para VCC com uma corrente de alimentação em repouso muito baixa de <24μA. A saída terá um viés de VCC/2. Portanto, quando estiver perfeitamente silencioso, a tensão de saída estará em VCC/2 V.

Se quiseres saber mais sobre o Módulo de Microfone MAX4466, dá uma vista de olhos ao Detect sound with MAX4466 and Arduino tutorial.

Ligação do MAX4466 e OLED ao ESP32

Nesta secção, primeiro ligamos o módulo de microfone MAX4466 ao ESP32 e testamos a sua função. Depois adicionamos o OLED ao circuito e escrevemos o código para o Analisador de Espectro:

Ligação do MAX4466 ao ESP32

Para ligar o MAX4466 a um ESP32, começa por ligar o terra (GND) do ESP32 e do módulo MAX4466 (fio azul). Depois liga o VCC do MAX4466 à saída de 3.3V do ESP32 (fio vermelho). Finalmente, liga o GPIO 4 do ESP32 ao pino OUT do MAX4466 (fio laranja):

Connecting MAX4466 to ESP32
Ligação do MAX4466 ao ESP32

Nota que, em vez do GPIO 4, podes usar qualquer outro pino desde que consiga ler um sinal analógico.

Código de teste para MAX4466

Depois de ligar o microfone MAX4466 ao ESP32, vamos executar um pequeno teste para ver se tudo está a funcionar como esperado. O código seguinte lê o sinal do microfone no pino 4 e imprime-o no Monitor Serial:

const byte micPin = 4;

void setup() {
  Serial.begin(115200);
  pinMode(micPin, INPUT);
}

void loop() {
  int value = analogRead(micPin);
  Serial.println(value);
  delay(100);  
}

Se carregares este código, abrires o Serial Plotter e fizeres algum barulho, deverás conseguir ver flutuações no sinal de áudio relacionadas com o volume do ruído:

Audio signal on Serial Plotter
Sinal de áudio no Serial Plotter

Se não vires qualquer alteração no sinal de áudio, então ou a tua ligação está errada, ou o pino GPIO para o microfone no código está incorreto.

Adicionar OLED ao ESP32

No passo seguinte, adicionamos o OLED ao ESP32. Primeiro, liga o GND e o VCC do OLED às linhas de alimentação existentes (fios azul e vermelho). Depois liga o SCL ao pino 23 e o SDA ao pino 18 do ESP32, como mostrado abaixo:

Connecting MAX4466 and OLED with ESP32
Ligação do MAX4466 e OLED ao ESP32

Os GPIO 23 e 18 são os pinos I2C hardware para o ESP32 lite. Se usares uma placa ESP32 diferente, os pinos para o I2C hardware podem ser diferentes. Se não tiveres a certeza de quais pinos usar, dá uma vista de olhos ao Find I2C and SPI default pins tutorial. A imagem abaixo mostra o circuito completo numa breadboard:

Audio Spectrum Analyzer circuit on breadboard
Circuito do Analisador de Espectro de Áudio na breadboard

Código para mostrar o Espectrograma de Frequência

Antes de podermos escrever o código para calcular e mostrar o Espectrograma de Frequência, precisamos de instalar a arduinoFFT library. Abre o LIBRARY MANAGER, escreve “arduinoFFT” na barra de pesquisa e pressiona oINSTALL botão. Após uma instalação bem-sucedida, deverás ver o seguinte:

arduinoFFT library installed in Library Manager
Biblioteca arduinoFFT instalada no Library Manager

Além disso, vamos precisar de instalar a Adafruit_SSD1306 Library para controlar o OLED. Escreve “Adafruit_SSD1306” na barra de pesquisa e, como antes, pressionaINSTALL para instalar a biblioteca:

Adafruit_SSD1306 library installed in Library Manager
Biblioteca Adafruit_SSD1306 instalada no Library Manager

Com isso resolvido, podemos agora começar a escrever o código. O código seguinte para um visualizador de espectro de áudio lê a entrada de áudio de um microfone, realiza uma Transformada Rápida de Fourier (FFT) para analisar o espectro de frequência e mostra os resultados num OLED. A imagem seguinte mostra a saída que cria e o significado dos elementos visuais:

Visual Elements of the Spectrum Analyzer
Elementos Visuais do Analisador de Espectro

Nas próximas secções vamos analisar este código função por função. O código baseia-se no exemplo ESP32_FFT_VU mas foi dividido em funções menores e ligeiramente modificado para melhor legibilidade.

#include "arduinoFFT.h"
#include "Adafruit_SSD1306.h"

const int micPin = 4;
const int volume = 300;        // Adjust depending on audio volume 
const int noise = 2000;        // Noise filter, smaller signal is ignored  
const int nSamples = 256;      // Must be a power of 2
const int sampleFreq = 40000;  // Hz, 40000 or less 

double vReal[nSamples];
double vImag[nSamples];
byte peaks[] = { 0, 0, 0, 0, 0, 0, 0 };

ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, nSamples, sampleFreq);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

void displayScale() {
  oled.clearDisplay();
  oled.setCursor(0, 0);
  oled.print(".1 .2 .5 1K 2K 4K 8K");
}

void displayBands() {
  for (int i = 2; i < (nSamples / 2); i++) {    
    if (vReal[i] > noise) {                               
      if (i <= 2) displayBand(0, vReal[i]);              // 125Hz
      if (i > 3 && i <= 5) displayBand(1, vReal[i]);     // 250Hz
      if (i > 5 && i <= 7) displayBand(2, vReal[i]);     // 500Hz
      if (i > 7 && i <= 15) displayBand(3, vReal[i]);    // 1000Hz
      if (i > 15 && i <= 30) displayBand(4, vReal[i]);   // 2000Hz
      if (i > 30 && i <= 53) displayBand(5, vReal[i]);   // 4000Hz
      if (i > 53 && i <= 200) displayBand(6, vReal[i]);  // 8000Hz
      if (i > 200) displayBand(7, vReal[i]);             // 16000Hz
    }
  }
}

void calcFFT() {
  FFT.windowing(vReal, nSamples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.compute(vReal, vImag, nSamples, FFT_FORWARD);
  FFT.complexToMagnitude(vReal, vImag, nSamples);
}

void sampleAudio() {
  static unsigned long newTime, oldTime;
  long period_us = round(1000000.0 / sampleFreq);

  for (int i = 0; i < nSamples; i++) {
    newTime = micros() - oldTime;
    oldTime = newTime;
    vReal[i] = analogRead(micPin);
    vImag[i] = 0;
    while (micros() < (newTime + period_us))
      ;
  }
}

void displayPeaks() {
  for (byte band = 0; band <= 6; band++) {
    oled.drawFastHLine(18 * band, 64 - peaks[band], 14, WHITE);
  }
}

void decayPeaks() {
  if (millis() % 4 == 0) {
    for (byte band = 0; band <= 6; band++) {
      peaks[band] = max(0, peaks[band] - 1);
    }
  }
}

void displayBand(int band, double magnitude) {
  int dmax = 50;
  int dsize = min((int)(magnitude / volume), dmax);
  if (band == 7) {
    oled.drawFastHLine(18 * 6, 0, 14, WHITE);
  }  
  for (int s = 0; s <= dsize; s += 2) {
    oled.drawFastHLine(18 * band, 64 - s, 14, WHITE);
  }
  if (dsize > peaks[band]) { 
    peaks[band] = dsize; 
  }
}

void setup() {
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  oled.setTextSize(1);
  oled.setTextColor(WHITE);

  pinMode(micPin, INPUT);
}

void loop() {
  displayScale();
  sampleAudio();
  calcFFT();
  displayBands();
  displayPeaks();
  decayPeaks();
  oled.display();
  delay(1);
}

Bibliotecas

O código começa por incluir as bibliotecas necessárias. Há a bibliotecaarduinoFFT para calcular a Transformada Rápida de Fourier (FFT) e a bibliotecaAdafruit_SSD1306, que é necessária para controlar o OLED:

#include "arduinoFFT.h"
#include "Adafruit_SSD1306.h"

Constantes

Depois definimos algumas constantes.micPin define o pino GPIO ao qual a saída do MAX4466 está ligada. A constantevolume permite ajustar a magnitude do espectro de frequência mostrado de acordo com o volume do áudio. Se não vires barras, diminui este valor e se as barras forem muito grandes, mesmo para sons fracos, aumenta este valor.

De forma semelhante, a constante de ruído permite filtrar ruído de baixo nível. Se vires demasiado ruído no espectro de frequência (flutuações aleatórias mesmo sem som), aumenta este valor.

nSamples define quantas amostras de áudio são tomadas esampleFreq é a frequência de amostragem. Para microcontroladores mais lentos que o ESP32, poderás ter de diminuir estes valores.

const int micPin = 4;
const int volume = 300;        // Adjust depending on audio volume 
const int noise = 2000;        // Noise filter, smaller signal is ignored  
const int nSamples = 256;      // Must be a power of 2
const int sampleFreq = 40000;  // Hz, 40000 or less 

Variáveis

Existem três variáveis.vReal evImag são arrays que armazenam a parte real e imaginária do sinal e o arraypeaks mantém o registo das magnitudes máximas para cada barra de frequência.

double vReal[nSamples];
double vImag[nSamples];
byte peaks[] = { 0, 0, 0, 0, 0, 0, 0 };

Objetos

Finalmente, temos os objetos. O objetoFFT contém as funções necessárias para calcular a Transformada Rápida de Fourier, e o objetooled contém as funções para controlar o ecrã OLED.

ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, nSamples, sampleFreq);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

displayScale

A função displayScale() prepara o ecrã OLED limpando qualquer conteúdo anterior e imprimindo etiquetas para as bandas de frequência. Isto ajuda o utilizador a entender qual banda corresponde a qual frequência. A função usa este código:

void displayScale() {
  oled.clearDisplay();
  oled.setCursor(0, 0);
  oled.print(".1 .2 .5 1K 2K 4K 8K");
}

A função displayBands() processa o resultado da FFT armazenado no arrayvReal e decide a que banda de frequência cada bin da FFT pertence. Filtra sinais abaixo de um certo limiar de ruído e depois chama a displayBand() com o índice da banda e a magnitude do sinal:

void displayBands() {
  for (int i = 2; i < (nSamples / 2); i++) {
    if (vReal[i] > noise) {
      if (i <= 2) displayBand(0, vReal[i]);
      if (i > 3 && i <= 5) displayBand(1, vReal[i]);
      if (i > 5 && i <= 7) displayBand(2, vReal[i]);
      if (i > 7 && i <= 15) displayBand(3, vReal[i]);
      if (i > 15 && i <= 30) displayBand(4, vReal[i]);
      if (i > 30 && i <= 53) displayBand(5, vReal[i]);
      if (i > 53 && i <= 200) displayBand(6, vReal[i]);
      if (i > 200) displayBand(7, vReal[i]);
    }
  }
}

A função calcFFT() realiza a análise FFT propriamente dita. Aplica uma janela de Hamming para suavizar o sinal e depois calcula a FFT, transformando as partes real e imaginária em magnitudes armazenadas de volta emvReal:

void calcFFT() {
  FFT.windowing(vReal, nSamples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.compute(vReal, vImag, nSamples, FFT_FORWARD);
  FFT.complexToMagnitude(vReal, vImag, nSamples);
}

A função sampleAudio() recolhe nSamples de dados analógicos do microfone. Lê o sinal em intervalos regulares, calculados usando sampleFreq, e armazena-o no arrayvReal. vImag é definido como zero, pois a entrada é puramente real:

void sampleAudio() {
  static unsigned long newTime, oldTime;
  long period_us = round(1000000.0 / sampleFreq);

  for (int i = 0; i < nSamples; i++) {
    newTime = micros() - oldTime;
    oldTime = newTime;
    vReal[i] = analogRead(micPin);
    vImag[i] = 0;
    while (micros() < (newTime + period_us))
      ;
  }
}

A função displayPeaks() mostra valores de pico persistentes de cada banda de frequência desenhando linhas horizontais curtas onde o pico ocorreu pela última vez. Estas linhas ajudam a visualizar os níveis máximos recentes de volume em cada banda:

void displayPeaks() {
  for (byte band = 0; band <= 6; band++) {
    oled.drawFastHLine(18 * band, 64 - peaks[band], 14, WHITE);
  }
}

A função decayPeaks() reduz lentamente o valor do pico ao longo do tempo, simulando um efeito de decaimento. Executa-se aproximadamente a cada 4 milissegundos e diminui o pico de cada banda em 1, até um mínimo de 0:

void decayPeaks() {
  if (millis() % 4 == 0) {
    for (byte band = 0; band <= 6; band++) {
      peaks[band] = max(0, peaks[band] - 1);
    }
  }
}

A função displayBand() desenha barras verticais para cada banda no OLED. Escala o tamanho da barra com base na magnitude do sinal dividida pelo parâmetro volume. Se o índice da banda for 7, uma barra especial é desenhada no topo do ecrã. A função também atualiza o arraypeaks se a barra atual exceder o pico anterior:

void displayBand(int band, double magnitude) {
  int dmax = 50;
  int dsize = min((int)(magnitude / volume), dmax);
  if (band == 7) {
    oled.drawFastHLine(18 * 6, 0, 14, WHITE);
  }
  for (int s = 0; s <= dsize; s += 2) {
    oled.drawFastHLine(18 * band, 64 - s, 14, WHITE);
  }
  if (dsize > peaks[band]) {
    peaks[band] = dsize;
  }
}

A função setup() inicializa o ecrã OLED e define o pino do microfone como entrada. Isto prepara o sistema para começar a amostrar e mostrar dados de áudio:

void setup() {
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  pinMode(micPin, INPUT);
}

Finalmente, a função loop() é o ciclo principal de execução. Executa repetidamente todos os passos necessários para capturar áudio, processá-lo através da FFT, visualizar os dados e atualizar o ecrã OLED:

void loop() {
  displayScale();
  sampleAudio();
  calcFFT();
  displayBands();
  displayPeaks();
  decayPeaks();
  oled.display();
  delay(1);
}

Juntas, estas funções formam um ciclo completo que analisa continuamente a entrada de áudio e mostra um espectro visual no ecrã OLED.

Tons de Teste

Podes testar o Analisador de Espectro tocando alguns tons de teste. Aqui estão links para vídeos do Youtube que tocam tons com frequências específicas:

O Analisador de Espectro deve mostrar uma barra maior/crescente para a banda de frequência em que o tom de teste se insere. Por exemplo, para um tom de 2000 Hz deverás ver uma barra maior na banda de 2KHz:

2KHz tone detected by Spectrum Analyzer
Tom de 2KHz detetado pelo Analisador de Espectro

A altura da barra dependerá do volume. Provavelmente terás de ajustar a constante volume ou ogain control para o MAX4466 para obter um sinal bom. Podes encontrar o potenciômetro para ajustar o ganho na parte de trás do módulo MAX4466:

Gain control of MAX4466 module
Controlo de ganho do módulo MAX4466

Conclusões

Neste tutorial aprendeste a construir um Analisador de Espectro de Frequência usando o módulo de microfone MAX4466, um OLED e um ESP32.

Se quiseres melhorar a precisão e a gama de frequências do Analisador de Espectro, poderias usar um Conversor Analógico-Digital externo para maior resolução e um microfone I2C para melhor alcance.

De forma semelhante, se quiseres um ecrã maior e a cores, poderias substituir o OLED por um ecrã TFT. Dá uma vista de olhos aos tutoriais Interface TFT ILI9341 Touch Display with ESP32 e talvez ao How to configure TFT_eSPI Library for TFT display.

A placa ESP32 lite tem uma interface para bateria LiPo, o que facilita usar o Analisador de Espectro com uma bateria LiPo de 4.2V. No entanto, a placa é um pouco grande, especialmente comparada com o pequeno OLED que usamos. Em vez do ESP32 lite, poderias usar um ESP32-C SuperMini para construir um Analisador de Espectro muito pequeno, bonito e portátil.

Boa diversão a criar ; )

Links

Aqui ficam alguns links que achei úteis ao escrever este tutorial: