February 21, 2015

DFT on an Arduino - Part 2

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 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]);
        }
    }