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:
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.
/* 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++;
}