Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Enrique Albertos
Published © GPL3+

Sound Spectrum Visualizer with Arduino Nano 33 BLE

Visualize sound frequencies spectrum with an OLED 128x32 display, Arduino Nano 33 BLE and an electret microphone amplifier (MAX9814).

BeginnerFull instructions provided1 hour23,519

Things used in this project

Hardware components

Arduino Nano 33 BLE
Arduino Nano 33 BLE
×1
Electret Microphone Amplifier Max9814
×1
0.91 inch OLED I2C Display 128 x 32 Pixels
×1
Capacitor 4.7 nF
Depending on your RC Low pass filter and your selected cutoff frequency
×1
Capacitor 470 µF
Capacitor 470 µF
×1
Resistor 2K
Depending on your RC Low pass filter and your selected cutoff frequency
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Schematics fritzing

Schematics fritzing

Code

Sound Spectrum Visualizer for Arduino Nano 33 BLE

Arduino
Sound Spectrum Visualizer for Arduino Nano 33 BLE
uses arduinoFFT https://github.com/kosme/arduinoFFT
uses Adafruit_GFX https://github.com/adafruit/Adafruit-GFX-Library
uses Adafruit_SSD1306 https://github.com/adafruit/Adafruit_SSD1306
/*

  Sound Spectrum Visualizer for Arduino Nano 33 BLE
  Arduino based sound visualizer
  @author Enrique Albertos

  Hardware requirements:
   - Arduino or Arduino-compatible boards.
   - ssd1306 oled I2C 128x32 display
   - Electret Microphone Amplifier with max9814
   - Optional: battery for portable use (else power through USB)

  Software requirements:
  - arduinoFFT https://github.com/kosme/arduinoFFT
  - Adafruit_GFX https://github.com/adafruit/Adafruit-GFX-Library
  - Adafruit_SSD1306 https://github.com/adafruit/Adafruit_SSD1306
  
  Connections:
  - Mic GAIN:
       * VCC to mic GAIN for 40dB 
       * GND to mic GAIN for 50dB 
       * floating mic GAIN for 60dB 
  - Analog pin 0 to mic amp output
  - +3.3V, GND, SDA (or analog 4) and SCL (analog 5) to I2C SSD1306

  @uses arduinoFFT https://github.com/kosme/arduinoFFT
  @uses Adafruit_GFX https://github.com/adafruit/Adafruit-GFX-Library
  @uses Adafruit_SSD1306 https://github.com/adafruit/Adafruit_SSD1306

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.


*/

#include "arduinoFFT.h"

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SAMPLES 1024 // power of 2
#define SAMPLING_FREQ 24000 // 12 kHz Fmax = sampleF /2 
#define AMPLITUDE 100 // sensitivity
#define FREQUENCY_BANDS 14


#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

#define BARWIDTH 11
#define BARS 11

#define ANALOG_PIN A0

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

double vImag[SAMPLES];
double vReal[SAMPLES];
unsigned long sampling_period_us;
arduinoFFT fft = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);

// adjust reference to get remove background noise noise
float reference = log10(50.0);

double coutoffFrequencies[FREQUENCY_BANDS];


void setup() {
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    for (;;); // Don't proceed, loop forever
  }

  // Setup display
  display.clearDisplay();
  display.display();
  display.setRotation(0);
  display.invertDisplay(false);

  sampling_period_us = (1.0 / SAMPLING_FREQ ) * pow(10.0, 6);

  // Calculate cuttoff frequencies,meake a logarithmic scale base basePOt
  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];
  }

  // draw dashed lines to sperate frequency bands
  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];
int oldMax[20];
double maxInFreq;

void loop() {

  // take samples
  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();
    }
  }

  // compute FFT
  fft.DCRemoval();
  fft.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  fft.Compute(FFT_FORWARD);
  fft.ComplexToMagnitude();

  double median[20];
  double max[20];
  int index = 0;
  double hzPerSample = (1.0 * SAMPLING_FREQ) / SAMPLES; //
  double hz = 0;
  double maxinband = 0;
  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.0;
      count = 0;
      index++;
      max[index] = 0;
      median[index]  = 0;
    }
    hz += hzPerSample;
  }
  // calculate median and maximum per frequency band
  if ( sum > 0.0) {
    median[index] =  sum / count;
    if (median[index] > maxinband) {
      maxinband = median[index];
    }
  }

  int bar = 0;

  for (int i = FREQUENCY_BANDS - 1; i >= 3; i--) {
    int newHeight = 0;
    int newMax = 0;
    // calculate actual decibels
    if (median[i] > 0 && max[i] > 0 ) {
      newHeight = 20.0 * (log10(median[i] ) - reference);
      newMax = 20.0 * (log10(max[i] ) - reference);
    }

    // adjust minimum and maximum levels
    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;
    // remove old level median
    if (oldHeight[i] > newHeight) {
      display.fillRect(barX, newHeight + 1, 7, oldHeight[i], SSD1306_BLACK);
    }
    // remove old max level
    if ( oldMax[i] > newHeight) {
      for (int j = oldMax[i]; j > newHeight; j -= 2) {
        display.drawFastHLine(barX , j, 7, SSD1306_BLACK);
      }
    }
    // paint new max level
    for (int j = newMax; j > newHeight; j -= 2) {
      display.drawFastHLine(barX , j, 7,  SSD1306_WHITE);
    }
    // paint new level median
    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();
}

Credits

Enrique Albertos

Enrique Albertos

12 projects • 38 followers
Pedestrian.
Thanks to Adafruit, Adafruit, and Kosme.

Comments