Arduino Code for Spectrum Analyzer
As the DigiSpark has no (working!) serial communications and the ADC for the Arduino UNO is the same for the ATTiny85 (I think), I have prototyped the code for an Arduino UNO. So all done except for the graphics!
Here is the code:
// Audio Spectrum Analyser
#define N 100 // Number of samples
#define SampleFreq 50000 // Average free running speed
byte samples[N]; // Sample array
byte window[N]; // Window array
// Define various ADC prescaler
const unsigned char PS_16 = (1 << ADPS2);
const unsigned char PS_32 = (1 << ADPS2) | (1 << ADPS0);
const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
// Setup the serial port and pin 2
void setup() {
int i;
pinMode(LED_BUILTIN,OUTPUT);
digitalWrite(LED_BUILTIN,LOW);
// Setup the ADC
pinMode(A2, INPUT);
ADCSRA &= ~PS_128; // remove bits set by Arduino library
// Set prescaler
// ADCSRA |= PS_64; // 64 prescaler (250 kHz assuming a 16MHz clock)
// ADCSRA |= PS_32; // 32 prescaler (500 kHz assuming a 16MHz clock)
ADCSRA |= PS_16; // 16 prescaler (1 MHz assuming a 16MHz clock)
// Hamming Window
for (i=0;i<N;i++) window[i]=(byte)((0.54-0.46*cos(2*M_PI*i/(N-1)))*255);
// Generate test samples
for (i=0;i<N;i++) samples[i]=(byte)(50*sin(2*M_PI*i*4000/SampleFreq)+50*sin(2*M_PI*i*7000/SampleFreq)+127);
Serial.begin(9600);
Serial.println();
}
void loop() {
int i;
int freq;
float s;
float s_prev;
float s_prev2;
float coeff;
float magn;
if (false) {
// Capture the values to memory
for (i=0;i<100;i++) samples[i]=(byte)(analogRead(A2)>>2);
} else {
// Pretend to be working
delay(N*1000/SampleFreq);
}
// Scan frequencies
for (freq=0;freq<=20000;freq+=1000) {
coeff=2*cos(2*M_PI*freq/SampleFreq);
s_prev=0;
s_prev2=0;
for (i=0;i<N;i++) {
// Goertzel
s=0.0000768935*window[i]*samples[i]+s_prev*coeff-s_prev2;
s_prev2=s_prev;
s_prev=s;
}
// Get magnitude
magn=2*sqrt(s_prev2*s_prev2+s_prev*s_prev-coeff*s_prev*s_prev2)/N;
Serial.print("Freq: ");
Serial.print(freq);
Serial.print(" Magn: ");
Serial.println(magn);
}
digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
delay(1000);
}
Here is the output:
- Freq: 0 Magn: 2.67
- Freq: 1000 Magn: 0.00
- Freq: 2000 Magn: 0.00
- Freq: 3000 Magn: 0.00
- Freq: 4000 Magn: 0.53
- Freq: 5000 Magn: 0.00
- Freq: 6000 Magn: 0.00
- Freq: 7000 Magn: 0.53
- Freq: 8000 Magn: 0.00
- Freq: 9000 Magn: 0.00
- Freq: 10000 Magn: 0.00
- Freq: 11000 Magn: 0.00
- Freq: 12000 Magn: 0.00
- Freq: 13000 Magn: 0.00
- Freq: 14000 Magn: 0.00
- Freq: 15000 Magn: 0.00
- Freq: 16000 Magn: 0.00
- Freq: 17000 Magn: 0.00
- Freq: 18000 Magn: 0.00
- Freq: 19000 Magn: 0.00
- Freq: 20000 Magn: 0.00
Which is correct!
Note that the magnitude calculation is not 100% accurate.
The DC component should be 2.49v rms (not 2.67) and the two frequencies (4000 Hz and 7000 Hz) should be 0.5v rms (not 0.53). The error issue is understood to be due to "phase" errors and "Window" errors.
Execution time:
- sampling 2.0 ms
- calculation per frequency 1.2 ms
- Total time (21 frequencies) 27 ms
So all good!
AlanX
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
Hi Eric,
Thanks. I find the art of coding is making it and keeping it simple.
Regards AlanX
Are you sure? yes | no
Hah, I see it now, I think I went a bit overboard in my integerification, by removing *all* floating-point math, including sqrt and the initial calculations with sin(), etc. Yeah, that might've been a bit overboard.
My intention was to measure an unknown impedance via magnitude/phase... so the input-frequency would be known. I have no idea why I was so dead-set on speeding the thing up so much.
The algorithm you've got here is much more intuitive than I remember ;)
Are you sure? yes | no