It’s been a year! A year since I was supposed to document my code. Please, hold the applause while I accept the award for
DFT on an Arduino - Part 1
I am going to continue exactly where I left off.
SOURCE CODE
The
The primary display is a 16x2 generic LCD panel. Here’s the Arduino
I got myself a simple electret mic. and strapped it to the integrated 10-bit ADC on the atmega328. Since, the inbuilt
There are some fancy mic options nowadays : link1, link2
The ADC is set to 5V and LAR (Left Adjust Read) - ie: the higher bits are stored in the ADCH and the remaining lower bits are in the ADCL. Since, I need only 8 bits, hence I am going to grab the ADCH and move on.
Ah, finally, the main course…
Sample in the higher 8 bits (the ADCH) to collect 64 values. This part takes the most amount of time.
Convolute the samples with the DFT constants and accumulate the raw spectral results. You need to keep shifting it because there really isn’t much space in 16bits. At the end, get the magnitude as that’s what you’d finally display as bands.
Laziest SlacKing
for 2014. Sigh … alright, let’s get on with this. I hope I can understand what I had written…DFT on an Arduino - Part 1
I am going to continue exactly where I left off.
SOURCE CODE
The
dft_C.ino
is the main arduino sketch.LCD Setup
The primary display is a 16x2 generic LCD panel. Here’s the Arduino
LiquidCrystal
library that I’d use to manipulate it. Since, I would require bands to show the strength of the spectrum, I created some custom characters for the LCD controller using createChar. Here are some neat LCD chara creators - link, link2.#include <LiquidCrystal.h>
// LCD object - and pins it hogs
LiquidCrystal lcd(8,9,10,11,12,13);
// LCD Levels - store in the progmem
prog_uint8_t lcd_char[8][8] PROGMEM = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F},
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F},
{0x00,0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F},
{0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F,0x1F},
{0x00,0x00,0x00,0x1F,0x1F,0x1F,0x1F,0x1F},
{0x00,0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F},
{0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F},
{0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F}
};
// temp var
uint8_t tlcd_char[8];
void setup() {
for(i=0;i<8;i++){
for(j=0;j<8;j++){
// Extract the chara info from the progmem
tlcd_char[j]=pgm_read_byte(&lcd_char[i][j]);
}
// Shove each custom chara to the LCD controller
lcd.createChar(i, tlcd_char);
}
}
Mic Setup
I got myself a simple electret mic. and strapped it to the integrated 10-bit ADC on the atmega328. Since, the inbuilt
analogRead
takes a while, I decided to take direct control of the ADC. Here, read this to know how to do this on your own.There are some fancy mic options nowadays : link1, link2
// On the Induino, I used the A2
byte micPort = 0x02;
void setup() {
// Set up ADC
// Vref = 5V (REFS1:REFS0 = bit8:7 = 01)
// Left Adjust read (ADLAR = bit6 = 1)
// Select analog channel (MUX3:0 = bit3:0)
ADMUX = 0b01100000;
ADMUX |= micPort;
ADCSRB = 0;
ADCSRA = 0;
ADCSRA |= 1<<ADEN; // Enable ADC
ADCSRA |= 1<<ADSC; // Start ADC
ADCSRA |= 1<<ADPS2; // ADPS2:0 = 3bit prescaler set to 64
ADCSRA |= 1<<ADPS1;
}
The ADC is set to 5V and LAR (Left Adjust Read) - ie: the higher bits are stored in the ADCH and the remaining lower bits are in the ADCL. Since, I need only 8 bits, hence I am going to grab the ADCH and move on.
The Loop
Ah, finally, the main course…
Sampling
Sample in the higher 8 bits (the ADCH) to collect 64 values. This part takes the most amount of time.
// Select Analog Channel
ADMUX |= micPort;
for(i = 0;i<DFT_N;i++){
// Start conversion
ADCSRA |= 1<<ADSC;
// Wait till conversion is done
while(ADCSRA & 1<<ADSC){};
// Read ADCH.
x[i] = (ADCH);
}
// Scale from 0-255 to -128 to +127
// Apply window to reduce spectral leakage
for(i = 0;i<DFT_N;i++){
x[i]-=128;
x[i]=(x[i]*(int8_t)pgm_read_byte(&window[i]))>>7;
}
DFT
Convolute the samples with the DFT constants and accumulate the raw spectral results. You need to keep shifting it because there really isn’t much space in 16bits. At the end, get the magnitude as that’s what you’d finally display as bands.
for(k=1;k<DFT_Nb2;k++) {
dft_ReX[k]=0;
dft_ImX[k]=0;
for(i =0;i<DFT_N;i++) {
dft_ReX[k] += ((int8_t)pgm_read_byte(&REx_cons[k][i]) * x[i])>>2;
if(k<DFT_Nb2-1){
dft_ImX[k] += -1 * ((int8_t)pgm_read_byte(&IMx_cons[k][i]) * x[i])>>2;
}
}
dft_ReX[k] = dft_ReX[k] >> (5+LOG2_Nb2);
dft_ImX[k] = dft_ImX[k] >> (5+LOG2_Nb2);
}
//dft_ReX[0] = dft_ReX[0] >> 1;
dft_ReX[DFT_Nb2-1] = dft_ReX[DFT_Nb2-1] >> 1;
//Magnitude Calculation
for(k =1;k<DFT_Nb2;k++) {
dftX[k] = sqrt(dft_ReX[k]*dft_ReX[k] + dft_ImX[k]*dft_ImX[k]);
}
Render
lcd.clear();
lcd.setCursor(0,1);
for(k =0;k<16;k++) {
//LINEAR
mag=dftX[Bands[k]];
// LOG: 18*log2
mag = pgm_read_byte(&logMag[mag]);
// Noise adjust
if(k==0 && mag<80){mag=0;}
else if (k==1 && mag<42){mag=0;}
mag = mag>>3;
// chaser - slowly falling bands (instead of flickering silliness)
if (mag<lcd_lvl[k]){lcd_lvl[k]--;}
else {lcd_lvl[k] = mag;}
// Print the bands!
if(lcd_lvl[k]>7){
lcd.setCursor(k,0);
lcd.write(lcd_lvl[k]-8);
lcd.setCursor(k,1);
lcd.write(7);
} else {
lcd.write(lcd_lvl[k]);
}
}
No comments:
Post a Comment
You got something to say? Wait! I need to get my microphone array online.