Maker Pro
Arduino

DIY Arduino Audio Spectrum Analyzer on VFD Display

May 21, 2022 by Mirko Pavleski
Share
banner

A spectrum analyzer is a measurement tool that displays real-time frequency analysis of incoming audio signals.

Hardware

Software

1 Arduino IDE

Tools

1 Soldering iron (generic)
1 Solder Wire, Lead Free

A spectrum analyzer is a measurement tool that displays real-time frequency analysis of incoming audio signals.

It is usually an integral part of equalizers and audio signal processing devices. The vertical axis shows the amplitude of certain frequencies measured in decibels.

This time I will show you how to make such a device on a 20x2 VFD display. In one of my previous videos you could see the making of a VU meter on such a display. The advantage of these displays is that they emit a very bright light with high contrast, and the beautiful retro look. In the video I mentioned earlier (https://www.youtube.com/watch?v=gotUokTuP9U) you can see how to modify such a display, and then you can use the libraries intended for 16x2 character LCD with HD44780 driver chip. Specifically in this case, to control this VFD display is used "LiquidCrystal" library.

The presented device is very simple to build, and consists of several components:

- Arduino nano microcontroller

- VFM202MDA1-1 type VFD Display wich is slightly modified

- Stereo potentiometer to control the intensity of the input signal

- and two capacitors

For analysis and processing of the audio signal is used "fix_fft" library. As for the code, the base is taken from Ray Burnette with some minor tweaks. New code is adapted for 20x2 characters, increased noise sensitivity threshold and some minor visual corrections. The device is built into a suitable box made of PVC material with thicknesses of 3 and 5 mm, coated with self-adhesive wallpaper. A thin transparent filter foil is placed on the front of the display.

IMG_20220223_194449.jpg
Schematic diagram.png
/* FFT_TEST4
  Ray Burnette 20130810 function clean-up & 1284 port (328 verified)
  Uses 2x16 Parallel LCD in 4-bit mode, see LiquidCrystal lib call for details
  http://forum.arduino.cc/index.php?PHPSESSID=4karr49jlndufvtlqs9pdd4g96&topic=38153.15
  Modified by varind in 2013: this code is public domain, enjoy!
  http://www.variableindustries.com/audio-spectrum-analyzer/
  328P = Binary sketch size: 5,708 bytes (of a 32,256 byte maximum)
  1284P= Binary sketch size: 5,792 bytes (of a 130,048 byte maximum) Free RAM = 15456
  Binary sketch size: 8,088 bytes (of a 130,048 byte maximum) (Debug)
*/

#include <Wire.h>
#include <LiquidCrystal.h>
#include <fix_fft.h>

#define DEBUG 0
#define L_IN 3 // Audio input A0 Arduino
#define R_IN 2 // Audio input A1 Arduino

const int Yres = 8;
const int gain = 3;
float peaks[64];
char im[64], data[64];
char Rim[64], Rdata[64];
char data_avgs[64];
int debugLoop;
int i;
int load;

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);// RS,E,D4,D5,D6,D7

// Custom CHARACTERS
byte v1[8] = {
  B00000, B00000, B00000, B00000, B00000, B00000, B00000, B11111
};
byte v2[8] = {
  B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111
};
byte v3[8] = {
  B00000, B00000, B00000, B00000, B00000, B11111, B11111, B11111
};
byte v4[8] = {
  B00000, B00000, B00000, B00000, B11111, B11111, B11111, B11111
};
byte v5[8] = {
  B00000, B00000, B00000, B11111, B11111, B11111, B11111, B11111
};
byte v6[8] = {
  B00000, B00000, B11111, B11111, B11111, B11111, B11111, B11111
};
byte v7[8] = {
  B00000, B11111, B11111, B11111, B11111, B11111, B11111, B11111
};
byte v8[8] = {
  B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111
};

void setup() {

  if (DEBUG) {
    Serial.begin(9600); // hardware serial
    Serial.print("Debug ON");
    Serial.println("");
  }

  lcd.begin(16,2);
  lcd.clear();
  lcd.createChar(1, v1);
  lcd.createChar(2, v2);
  lcd.createChar(3, v3);
  lcd.createChar(4, v4);
  lcd.createChar(5, v5);
  lcd.createChar(6, v6);
  lcd.createChar(7, v7);
  lcd.createChar(8, v8);

  for (i=0;i<100;i++)
  {
    for (load = 0; load < i / 5; load++)
    {
      lcd.setCursor(load, 1);
      lcd.write(5);
    }
    if (load < 1)
    {
      lcd.setCursor(0, 1);
      lcd.write(5);
    }

    lcd.setCursor(load + 1, 1);
    lcd.write((i - i / 5 * 5) + 1);
    for (load = load + 2; load < 20; load++)
    {
      lcd.setCursor(load, 1);
      lcd.write(9);
    }
    lcd.setCursor(0, 0);
    lcd.print("LOADING.............");
    delay(10);
  }
  lcd.clear();
  delay(10);
}

void loop() {

  for (int i = 0; i < 64; i++) {    // 64 bins = 32 bins of usable spectrum data
    data[i]  = ((analogRead(L_IN) / 8 ) - 256);  // chose how to interpret the data from analog in
    im[i]  = 0;   // imaginary component
    Rdata[i] = ((analogRead(R_IN) / 8 ) - 256);  // chose how to interpret the data from analog in
    Rim[i] = 0;   // imaginary component
  }

  fix_fft(data, im, 6, 0);   // Send Left channel normalized analog values through fft
  fix_fft(Rdata, Rim, 6, 0); // Send Right channel normalized analog values through fft

  // At this stage, we have two arrays of [0-31] frequency bins deep [32-63] duplicate

  // calculate the absolute values of bins in the array - only want positive values
  for (int i = 0; i < 40; i++) {
    data[i] = sqrt(data[i]  *  data[i] +  im[i] *  im[i]);
    Rdata[i] = sqrt(Rdata[i] * Rdata[i] + Rim[i] * Rim[i]);

    // COPY the Right low-band (0-15) into the Left high-band (16-31) for display ease
    if (i < 20) {
      data_avgs[i] = data[i];
    }
    else {
      data_avgs[i] = Rdata[i - 20];
    }

    // Remap values to physical display constraints... that is, 8 display custom character indexes + "_"
    data_avgs[i] = constrain(data_avgs[i], 0, 9 - gain);     //data samples * range (0-9) = 9
    data_avgs[i] = map(data_avgs[i], 0, 9 - gain, 0, Yres);  // remap averaged values
  }

  Two16_LCD();
  decay(1);
}

void Two16_LCD() {
  lcd.setCursor(0, 0);
  lcd.print("L"); // Channel ID replaces bin #0 due to hum & noise
  lcd.setCursor(0, 1);
  lcd.print("R"); // ditto

  for (int x = 1; x < 20; x++) {  // init 0 to show lowest band overloaded with hum
    int y = x + 20; // second display line
    if (data_avgs[x] > peaks[x]) peaks[x] = data_avgs[x];
    if (data_avgs[y] > peaks[y]) peaks[y] = data_avgs[y];

    lcd.setCursor(x, 0); // draw first (top) row Left
    if (peaks[x] == 0) {
      lcd.print("_");  // less LCD artifacts than " "
    }
    else {
      lcd.write(peaks[x]);
    }

    lcd.setCursor(x, 1); // draw second (bottom) row Right
    if (peaks[y] == 0) {
      lcd.print("_");
    }
    else {
      lcd.write(peaks[y]);
    }
  }

  debugLoop++;
  if (DEBUG && (debugLoop > 99)) {
    Serial.print( "Free RAM = " );
    Serial.println( freeRam(), DEC);
    Serial.println( millis(), DEC);
    debugLoop = 0;
  }
}


int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}


void decay(int decayrate) {
  int DecayTest = 1;
  // reduce the values of the last peaks by 1
  if (DecayTest == decayrate) {
    for (int x = 0; x < 40; x++) {
      peaks[x] = peaks[x] - 1;  // subtract 1 from each column peaks
      DecayTest = 0;
    }
  }

  DecayTest++;
}

Related Content

Comments


You May Also Like