Frequency Measurement FFT, DFT

Dear all;

I would like to know what is an efficient and accurate method to measure frequency with noises in real time. I know of 2 methods such as FFT and DFT that are widely used. However, I am not sure if they would be sufficient and accurate using arduino.

How do I implement FFT or DFT on arduino?

Thank you

There is also this... http://en.wikipedia.org/wiki/Goertzel_algorithm

I can't tell Fourier from formica, but I was considering hacking a Korg chromatic guitar tuner to do the frequency analysis for a vocal pitch controlled game I'm planning. Anybody ever try something similar?

http://www.korg.com/Product.aspx?pd=99

Look at this link:

http://elm-chan.org/works/akilcd/report_e.html

I have succesfully implemented this on an arduino ;)

Hi I would like to know if you use the same code provided on the website.

What compiler do you use?

Thank you

@Peeter123:

Dang! Did you really solder tiny little wires to that processor?

Nooo ofcourse not, I used an Arduino! :stuck_out_tongue: As you can read on that page the AVR used is an ATmega8 so the software is compatible with an arduino. Used avrstudio to write the code and uploaded the .hex to the arduino. :wink:

Quick and Dirty Code

// AVR FFT
// based on code from ELM-chan & Michael Spiceland
// Designed for atmega328 @ 16MHz
// LCD Pins for Arduino Data(DB0-Pin2|DB1-Pin3|DB2-Pin4|DB3-Pin5) Funtions(RS-Pin6|RW-Pin7|E-Pin8)
// Samplerate = 9.6kHz Max Freq ~5kHz
// (c) Peeter123
// F_CPU is defined already in project definition
// #define F_CPU 16000000

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/sleep.h>
#include <stdio.h>
#include <inttypes.h>
#include <ctype.h>
#include <util/delay.h>
#include <math.h>
#include "fft.h"
#include "lcd.h"


// choose 9600 if you have issues, but you shouldn't
#define BAUD 38400
#define MYUBRR F_CPU/16/BAUD-1

/* defines */
#define NUM_SAMPLES  FFT_N
#define FFT_SIZE FFT_N / 2

int16_t capture[FFT_N];                  /* Wave capturing buffer */
complex_t bfly_buff[FFT_N];            /* FFT buffer */
uint16_t spectrum[FFT_N/2];            /* Spectrum output buffer */

// UART functions
static int uart_putchar(char c, FILE *stream);
uint8_t uart_getchar(void);
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

// make blinkin' e-z on port C
void BlinkMS(int time);
int set_PORTB_bit(int position, int value);
void lcd_spectrum_data(unsigned char fft_hight, unsigned char position);

// to print FFT data to UART
void print_FFT(uint16_t *buffer, uint8_t points);
void print_wave(int16_t *buffer, uint8_t points);


static const PROGMEM unsigned char BarGraphChar[] = //Bargraph custom chars
{
      0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
      0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
      0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1f,
      0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x1f, 0x1f,
      0x0, 0x0, 0x0, 0x0, 0x1f, 0x1f, 0x1f, 0x1f,
      0x0, 0x0, 0x0, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
      0x0, 0x0, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
      0x0, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f
};

unsigned char LcdDataBuffer1[20]; //LCD Line data buffers
unsigned char LcdDataBuffer2[20];
unsigned char LcdDataBuffer3[20];
unsigned char LcdDataBuffer4[20];


/************************************************************************
 * ISR TIMER0 Overflow
 *
 * This routine is run evertime the timer counter overflows.  Since the
 * timer counter is 8 bits, it can count to 255 before overflowing and
 * going back to 0.  The frequency this is executed in is determined by
 * the clock speed / clk_divide (TCCR0) / 256.
 ***********************************************************************/
ISR(TIMER0_OVF_vect){
      static unsigned char count = 0;
      static unsigned char count_clock = 0;
      static uint16_t offset = 0;
      uint8_t adc_val;

      //printf("%s \n ","inside interrupt");

      // we only want to run every clk/4 times  8mhz
      count_clock++;
      if ((count_clock % 4)){ // 2 = 16mhz, 4 = 8mhz, 8 = 4mhz
            return;}

      ADCSRA |= (1 << ADSC); // start ADC conversion
      while (ADCSRA&(1<<ADSC)); // wait for the result to be available
      adc_val = ADCH;

      capture[offset] = ((int16_t)(adc_val)-127)*4; // convert to signed linear and multiplied by 4
      offset++;

      if (offset == NUM_SAMPLES) // buffer is full
      {
      count = 0;
            //FFT
            fft_input(capture, bfly_buff);
            fft_execute(bfly_buff);
            fft_output(bfly_buff, spectrum);
            print_FFT(spectrum, NUM_SAMPLES/2);
            offset = 0;
           //LCD
            lcd_clrscr();
        for(unsigned char p=0;p<60;p = p + 3){
                  lcd_spectrum_data(spectrum[p+1],count);      
                  count++;
            }
            for(unsigned char l1=0;l1<20;l1++){
                  lcd_putc(LcdDataBuffer1[l1]);
            }
            lcd_gotoxy(0,1);      
            for(unsigned char l2=0;l2<20;l2++){
                  lcd_putc(LcdDataBuffer2[l2]);
            }
            lcd_gotoxy(0,2);
            for(unsigned char l3=0;l3<20;l3++){
                  lcd_putc(LcdDataBuffer3[l3]);
            }
            lcd_gotoxy(0,3);      
            for(unsigned char l4=0;l4<20;l4++){
                  lcd_putc(LcdDataBuffer4[l4]);
            }
      }
}


int main(void){

      // STARTUP SEQUENCE

      /* initialize port data directions */
      PORTB = 0xFF;

      /* initialize timers */
      TCCR0B |=  _BV(CS00);       // clk prescale (Fcpu/1)
      TIMSK0 |= _BV(TOIE0);       // enable timer/counter0 overflow interrupt
      TCNT0 = 0;                  // Reset timer 0
    //TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode
    //TCCR1A |= (1 << COM1A0);// Enable timer 1 Compare Output channel A in toggle mode
    //OCR1A   = 31;                   // Set CTC compare value to 500kHz at 16MHz AVR clock, with a prescaler of 1
    //TCCR1B |= (1 << CS00);       // Start timer at Fcpu/1
      
      /* initialize the ADC */
      ADMUX |= _BV(REFS0)| _BV(ADLAR);  // we only want 8-bit on ADC 0 VCC reference
      ADCSRA |= _BV(ADEN);  // for now we don't do this in the ISR | _BV(ADIE);
      ADCSRA |= _BV(ADPS2)| _BV(ADPS0);//_BV(ADPS2);//| _BV(ADPS1)| _BV(ADPS0); // clk is / 32

      /* initialize the UART */
      DDRD = 0b11111110;         //PORTD (RX on PD0)
      // load baud rate registers
      UBRR0H = (MYUBRR) >> 8;
    UBRR0L = MYUBRR;
    // enable transmitter + receiver
    UCSR0B = (1<<RXEN0)|(1<<TXEN0);
    // asynchronous, no parity, 1 stop bit, 8 bit character size
    UCSR0C = (0<<UMSEL01)|(0<<UMSEL00)|(0<<UPM01)|(0<<UPM00)|(0<<USBS0)|(0<<UCSZ02)|(1<<UCSZ01)|(1<<UCSZ00);
      stdout = &mystdout;
      
      /* initialize the LCD Display*/
      lcd_init(LCD_DISP_ON);
      
      //Load two userdefined characters from program memory into LCD controller CG RAM
       lcd_command(_BV(LCD_CGRAM));  /* set CG RAM start address 0 */
       for(unsigned char i=0; i<64; i++)
       {
           lcd_data(pgm_read_byte_near(&BarGraphChar[i]));
       }
      
      // let ya know startup's finished
      BlinkMS(500);
      sei(); //enable global interrupts

      /* AVR's like to have a loop */
      while(1) {
      }
}

void lcd_spectrum_data(unsigned char fft_hight, unsigned char position){
      if(fft_hight < 32){
            LcdDataBuffer1[position] = 0;
            LcdDataBuffer2[position] = 0;
            LcdDataBuffer3[position] = 0;
            LcdDataBuffer4[position] = 1 + fft_hight / 4;
      }else if(fft_hight < 64){
            LcdDataBuffer1[position] = 0;
            LcdDataBuffer2[position] = 0;
            LcdDataBuffer3[position] = fft_hight / 4;
            LcdDataBuffer4[position] = 0xFF;
      }else if(fft_hight < 96){
            LcdDataBuffer1[position] = fft_hight / 4;
            LcdDataBuffer2[position] = 0xFF;
            LcdDataBuffer3[position] = 0xFF;
            LcdDataBuffer4[position] = 0xFF;
      }else if(fft_hight < 128){
            LcdDataBuffer1[position] = fft_hight / 4;
            LcdDataBuffer2[position] = 0xFF;
            LcdDataBuffer3[position] = 0xFF;
            LcdDataBuffer4[position] = 0xFF;
      }else{
            LcdDataBuffer1[position] = 0xFF;
            LcdDataBuffer2[position] = 0xFF;
            LcdDataBuffer3[position] = 0xFF;
            LcdDataBuffer4[position] = 0xFF;
      }
}

static int uart_putchar(char c, FILE *stream)
{
    if (c == '\n') uart_putchar('\r', stream);

    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = c;

    return 0;
}
/*
uint8_t uart_getchar(void)
{
    while( !(UCSR0A & (1<<RXC0)) );
    return(UDR0);
}*/

// Blink an LED on PB5 Pin 13 Arduino
void BlinkMS(int time)
{
      set_PORTB_bit(5, 1);
      _delay_ms(time);
      set_PORTB_bit(5, 0);
      _delay_ms(time);
}

int set_PORTB_bit(int position, int value)
{
        // Sets or clears the bit in position 'position'
        // either high or low (1 or 0) to match 'value'.
        // Leaves all other bits in PORTB unchanged.

      if (value == 0){
            PORTB &= ~(1 << position);      // Set bit # 'position' low
      }
      else{
            PORTB |= (1 << position);       // Set bit # 'position' high
      }
return 1;
}

#define GAIN 1


// prints comma separated values of sample[0]...sample[N/2] to UART
void print_FFT(uint16_t *buffer, uint8_t points)
{
      uint8_t i = 0;
      uint16_t value;
      
      for(i = 0; i < points; i++)
      {
            value = (unsigned char)(buffer[i]) * GAIN;

            // I print the value of the FFT sample
            printf("%d,",buffer[i]);
      }
      // end
      printf("\n");
}

Hello

How do you get the fundamental frequency from this FFT code?

Thank you

How do you get the fundamental frequency from this FFT code?

Normally, the fundamental is the zeroth component.

thank you. how does it look like in the actual code. I am not familiar with the code posted above.

Again, what ADC channel does the code refer to on the arduino?

Thank you

I'm guessing the spectrum data is in "spectrum", so the power of the fundamental should be in "spectrum [0]"

[edit]Sorry, missed the ADC question - it's ADC channel zero (the comment is in the code)[/edit]

How do I display the output of frequency on avrstudio without having an lcd display?

I know for arduino regular compiler I can use serial monitor. Is it something along that line?

Thank you

I'm guessing the spectrum data is in "spectrum", so the power of the fundamental should be in "spectrum [0]"

spectrum[0] is the DC component of the input (ie. zero frequency).

If you want the "fundamental frequency", that's the lowest frequency in a harmonic series, which can likely only be found by statistical analysis.

It might be safe to take the "modal" frequency, ie. the frequency bin with the highest value, and call that the fundamental.

Hi,

In reply to displaying the output without using an LCD, you could use 'Processing'. I'm going to do that once I get this up and running and I'll post back.

Just in case: In case using another IDE seems a drag it is just like the Arduino IDE (same framework I think).

Hi,

http://processing.org/

This is the link to Processing.

fft_input(capture, bfly_buff); ** fft_execute(bfly_buff);** ** fft_output(bfly_buff, spectrum);**

I tried to compiled the code in winstudio 4 but I have a compile error for these 3 lines. They are defined in "ffft.h" but not in the main.c itself.

Please explain. Thank you

spectrum[0] is the DC component of the input (ie. zero frequency).

Whoops! Of course - brain-fart. Apologies. :-[

Hi,

i should say first that i'm new to all of this.

what driver did you use for the LCD functions in the fft code you pasted? I tried importing the LCD functions from arduino into avr studio and had a horrible time...

also, the data output to the uart, how do you monitor it with a computer? i used processing to 'listen' over the USB COM port and got only "-1" sent repeatedly.

thank you!