It rained (again) in Georgia, so my wife left me alone in my lab to play yesterday XD
I have been playing around with an Atmega1284P-PU on one of CrossRoad's boards; for some reason, I decided to try the FFT algorithm. Searching the Forum and Google was interesting (not in an overly positive way), so when I finally got something to work based on an old forum post: Modified 8bit FFT in c - Development - Arduino Forum I decided to clean-up the code a bit, add some small comments, and double-check the performance with my Rigol oscilloscope. The results of that is in this post.
The last entry by Magician back in January of '11 was
Unfortunately, there is a flow in the code, no FFT performed
if you copy/paste as it is.
:-[
My goal for this post was to fix this cut & paste issue.
I have checked the following test code on UNO and Bobuino_1284 and it works fine using Arduino 1.0.5. To avoid having to set up the .h and the .cpp files as libraries, just put them into your Arduino sketch folder with the .INO file. I do this to allow me to make minor changes (ONLY IF Necessary) before migrating same to the standard \Arduino\Libraries folder. It is just a preference of mine and certainly is not necessary, but if you use the traditional approach, be sure to change #include "fix_fft.h"
to #include <fix_fft.h>
so that the GUI can find the files at compile time.
Based on my testing, (I do not use bin #0, rather using the display space for the "L" and "R" indicating the Left and Right channels) bin#1 is tuned to 85Hz and the following 14 bins are: 120, 190, 240, 305, 365, 430, 480, 550, 625, 660, 735, 780, 850, and 910Hz. You will note that it is not exactly linear because we are not using an interrupt-driven paradigm for accurate timing; that is, the Analog inputs are just read in a loop. Accurate timing examples are given in some of the music note implementations of FFT use.
Audio signal is approximately 1V P-P from the audio generator, fed through a 0.5uF (0.5MFD value not critical) to the junction of two (2) 10K resistors with the other ends of the resistors going to +5V and Gnd. This voltage-divider junction establishes the 2.5V balance to the AD port. Note, you must use a capacitor and two resistors for each of the two analog inputs. In my breadboard design, I used a 680 Ohm resistor between the voltage divider junction and the input to the uC analog port as a kind of safety to limit any mishap to around 7mA of current BUT this is not necessary in the final layout unless you just have a bunch of 680-1000 Ohm resistors laying around (I do.)
Main code:
/* 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 <LiquidCrystal.h>
#include "fix_fft.h" // fix_fft.ccp & fix_fft.h in same directory as sketch
#define DEBUG 0
#define LCHAN 1
#define RCHAN 0
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;
//LiquidCrystal(rs, enable, d4, d5, d6, d7) 1284P Physical: 6, 5, 4, 3, 2, 1
LiquidCrystal lcd(11, 10, 7, 6, 5, 4); // saves all analog pins port PA
// 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);
}
void loop() {
for (int i = 0; i < 64; i++) { // 64 bins = 32 bins of usable spectrum data
data[i] = ((analogRead(LCHAN) / 4 ) - 128); // chose how to interpret the data from analog in
im[i] = 0; // imaginary component
Rdata[i] = ((analogRead(RCHAN) / 4 ) - 128); // 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 < 32; 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 < 16) {
data_avgs[i] = data[i];
}
else {
data_avgs[i] = Rdata[i - 16];
}
// 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 < 16; x++) { // init 0 to show lowest band overloaded with hum
int y = x + 16; // 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 < 32; x++) {
peaks[x] = peaks[x] - 1; // subtract 1 from each column peaks
DecayTest = 0;
}
}
DecayTest++;
}
Continued in next post...
Ray