FFT not working properly

Lavanya, what do you think is the time in between adc samples??

Try to print/plot the time domain samples before you compute the FFT. Then you will see what’s wrong I think…
 
so updated with your suggestion and changed little bit program but still no success

Code:
#include <SPI.h>
#include <arduinoFFT.h>

// PIN assignments
#define CS_PIN    14
#define DRDY_PIN  15
#define RST_PIN   16

// SPI
#define SPISPEED 2500000
#define SAMPLES 1024
#define SAMPLING_FREQUENCY 1000
arduinoFFT FFT = arduinoFFT();

#define BUFFER_SIZE     30000
unsigned int sampling_period_us;
unsigned long microseconds = 0;
double vReal[SAMPLES];
double vImag[SAMPLES];
float dataWithoutOffset;
float dc_offset = 0.0;
// ADS1256 registers
#define STATUS_REG   0x00
#define MUX_REG      0x01
#define ADCON_REG    0x02
#define DRATE_REG    0x03

// ADS1256 commands
#define WAKEUP_CMD   0x00  // completes SYNC and exits standby mode
#define RDATA_CMD    0x01  // read data
#define RREG_CMD     0x10  // read register (register ID in low nibble)
#define WREG_CMD     0x50  // write register (register ID in low nibble)
#define SELFCAL_CMD  0xF0  // offset and gain self-calibration
#define SYNC_CMD     0xFC  // synchronize the A/D conversion
#define STANDBY_CMD  0xFD  // begin standby mode
#define RESET_CMD    0xFE  // reset to power-up values

#define VREF            (2.5)   // for conversion of raw ADC data to Volts

#define DRATE_30K       (0xF0)  // 30 kSPS
#define STATUS_REG_0x03 (0x03)  // MSB first, Auto-Cal Dsbl, Input Buffer Enbl
#define ADCON_REG_VALUE (0x21)  // 0 01 00 001 => Clock Out Freq = fCLKIN, Sensor Detect OFF, gain 2 7.68MHz
#define DRATE_REG_VALUE (DRATE_30K)
#define MUX_REG_VALUE   (B00001000) // AINCOM

volatile int DRDY_state = HIGH;

float adc_volt[BUFFER_SIZE];

void writeRegister(uint8_t address, uint8_t value)
{
  SPI.transfer(WREG_CMD | address);
  SPI.transfer(0x00);
  SPI.transfer(value);
  delayMicroseconds(100);
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) {}
  Serial.println("ADS1256 test program");
  START_SPI();
  attachInterrupt(DRDY_PIN, DRDY_Interrupt, FALLING);
}

void loop()
{
  int sampleCount = 0;
  while (true) {
    uint32_t t0 = micros();
    for (int i = 0; i < BUFFER_SIZE; i++) {
      waitDRDY();
      adc_volt[i] = READ_ADC();
      delayMicroseconds(10);
      sampleCount++;
      if (sampleCount >= BUFFER_SIZE) {
        sampleCount = 0;
        break;  // Exit the loop after completing the required samples
      }
    }
 
    fft(adc_volt);  // Call fft function with the adc_volt array
    Serial.println("Cycle completed");
  }
}

void fft(float* samples)
{
  for (int i = 0; i < SAMPLES; i++)
  {
    microseconds = micros();
    vReal[i] = samples[i];
    vImag[i] = 0.0;
    while (micros() < (microseconds + sampling_period_us)) {
    }
  }
  FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
  double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);

  Serial.print("Peak frequency is: ");
  Serial.println(peak);
}

float READ_ADC()
{
  int32_t adc_raw;
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1));                          
  digitalWriteFast(CS_PIN, LOW);
 
  SPI.transfer(RDATA_CMD);
  delayMicroseconds(5); // delay MUST be >= 5 us  
  adc_raw = 0;
  adc_raw |= SPI.transfer(0);
  adc_raw <<= 8;              
  adc_raw |= SPI.transfer(0);
  adc_raw <<= 8;            
  adc_raw |= SPI.transfer(0);
 
  digitalWriteFast(CS_PIN, HIGH);  
  SPI.endTransaction();

  if (adc_raw & (1 << 23)) {
    adc_raw |= 0xFF000000;
  }
   float sum = 0.0;
  float v = adc_raw * (float)(VREF / (1 << 2
[/QUOTE]
[QUOTE="Lavanya rajan, post: 338851, member: 91221"]

  sum += v;
        // Compute mean (DC offset)     
    float mean = sum / BUFFER_SIZE;

    // Subtract mean from each sample to remove DC offset
    for (int i = 0; i < BUFFER_SIZE; i++) {
      v -= mean;
  return(v );
}
}
void DRDY_Interrupt()
{
  DRDY_state = LOW;
}

void waitDRDY()
{
  while (DRDY_state == HIGH) {
    continue;
  }
  DRDY_state = HIGH;
}

void START_SPI()
{
/// removed as not necessary for understanding offset correction
}
Your algorithm for correcting the DC Offset is the problem here. You should NOT calculate the sum and correct the offset inside READ_ADC()!

sum should be a local variable in loop.. Set it to zero just before
for (int i = 0; i < SAMPLES; i++)
Use READ_ADC() to collect raw data.
add the raw value to sum inside the for loop.
temporarily save the raw value in adc_volt

AFTER that for loop, calculate the mean, and use another for loop to subtract the mean from adc_volt.
You will now have the values corrected for the offset in adc_volt.
 
Lavanya, what do you think is the time in between adc samples??

Try to print/plot the time domain samples before you compute the FFT. Then you will see what’s wrong I think…
Sry for the late reply, held up with some work.

So I used this code to read 30000 samples and saved those samples and plotted the signal and my serial output prints like this
Code:
#include <SPI.h>
#include <arduinoFFT.h>

// PIN assignements
#define CS_PIN    14
#define DRDY_PIN  15
#define RST_PIN   16

// SPI
#define SPISPEED 2500000             
#define SAMPLING_FREQUENCY 1000
#define SAMPLES 1024
arduinoFFT FFT = arduinoFFT();
#define BUFFER_SIZE     (30000)
unsigned int sampling_period_us;
double vReal[BUFFER_SIZE];
double vImag[BUFFER_SIZE];

// ADS1256 registers
#define STATUS_REG   0x00
#define MUX_REG      0x01
#define ADCON_REG    0x02
#define DRATE_REG    0x03

// ADS1256 commands
#define WAKEUP_CMD   0x00  // completes SYNC and exits standby mode
#define RDATA_CMD    0x01  // read data
#define RREG_CMD     0x10  // read register (register ID in low nibble)
#define WREG_CMD     0x50  // write register (register ID in low nibble)
#define SELFCAL_CMD  0xF0  // offset and gain self-calibration
#define SYNC_CMD     0xFC  // synchronize the A/D conversion
#define STANDBY_CMD  0xFD  // begin standby mode
#define RESET_CMD    0xFE  // reset to power-up values

#define VREF            (2.5)   // for conversion of raw ADC data to Volts


//#define DRATE_15K       (0xE0)  // 15 kSPS
#define DRATE_30K       (0xF0)  // 30 kSPS

#define STATUS_REG_0x01 (0x01)  // MSB first, Auto-Cal Dsbl, Input Buffer Dsbl
#define STATUS_REG_0x03 (0x03)  // MSB first, Auto-Cal Dsbl, Input Buffer Enbl
#define STATUS_REG_0x07 (0x07)  // MSB first, Auto-Cal Enbl, Input Buffer Enbl
#define ADCON_REG_VALUE (0x21)  // 0 01 00 001 => Clock Out Freq = fCLKIN,
                                //             Sensor Detect OFF, gain 2 7.68MHz 
#define DRATE_REG_VALUE (DRATE_30K) 
#define MUX_REG_VALUE   (B00001000) // AINCOM

volatile int DRDY_state = HIGH;

float adc_volt[BUFFER_SIZE];
float adc_g[BUFFER_SIZE];

void writeRegister(uint8_t address, uint8_t value)
{
  SPI.transfer( WREG_CMD | address );
  SPI.transfer( 0x00 );
  SPI.transfer( value );
  delayMicroseconds( 100 );
}

void setup()
{
  Serial.begin(115200); // USB data rate is always max
  while (!Serial) {} // wait for USB serial ready
 Serial.println( "ADS1256 test program" );
  START_SPI();
  attachInterrupt( DRDY_PIN, DRDY_Interrupt, FALLING );
}

void loop()
{
  int sampleCount = 0;
  while (true) {
    uint32_t t0 = micros();
    for (int i = 0; i < BUFFER_SIZE; i++) {
      waitDRDY();
      adc_volt[i] = READ_ADC();
      delayMicroseconds(10);  // Adjust the delay as needed
      sampleCount++;
      if (sampleCount >= BUFFER_SIZE) {
        sampleCount = 0;
        Serial.println("Cycle completed");
      }
    }
    Serial.printf("Read %1d samples in %1lu us\n", BUFFER_SIZE, micros() - t0);

    for (int i = 0; i < BUFFER_SIZE; i++) {
      Serial.println(adc_volt[i], 4);  // Print with four decimal places
    }
  }
}




float READ_ADC()
{
  int32_t adc_raw;   
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1));                             
  digitalWriteFast(CS_PIN, LOW);   
  
  SPI.transfer( RDATA_CMD );
  delayMicroseconds(5); // delay MUST be >= 5 us     
  adc_raw = 0;   
  adc_raw |= SPI.transfer(0);
  adc_raw <<= 8;                 
  adc_raw |= SPI.transfer(0);
  adc_raw <<= 8;               
  adc_raw |= SPI.transfer(0);   
  
  digitalWriteFast(CS_PIN, HIGH);     
  SPI.endTransaction(); 

  if (adc_raw & (1<<23)) {    // if 24-bit MSB == 1
    adc_raw |= 0xFF000000;    //   value is negative, sign-extend to 32 bits
  }
  float v = adc_raw * (float)(VREF / (1<<23));
  return( v );
} 

// DRDY falling-edge interrupt function
void DRDY_Interrupt()
{
  DRDY_state = LOW;
}
 
void waitDRDY()
{
  // wait for DRDY_state to be LOW
  while (DRDY_state == HIGH) {
    continue;
  }
  // then set it back to HIGH
  DRDY_state = HIGH;
}

void START_SPI ()
{
  // configure I/O pins
  sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH); // init CS high (disable)
  pinMode(DRDY_PIN, INPUT);
  pinMode(RST_PIN, OUTPUT);
 
  // hardware reset ADS1256 by toggling pin LOW then HIGH
  digitalWrite(RST_PIN, LOW);
  delay(1);
  digitalWrite(RST_PIN, HIGH);
  delay(500);
 
  // start the spi-bus
  SPI.begin();
  delay(500);
 
  // wait for DRDY signal LOW
  while (digitalRead(DRDY_PIN)) {}
  
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1));
  digitalWriteFast(CS_PIN, LOW);
  delayMicroseconds(100);   

  // reset to power-up state
  SPI.transfer( RESET_CMD );
  delay(5);

  // configure registers
  writeRegister( STATUS_REG, STATUS_REG_0x03 );
  writeRegister( ADCON_REG, ADCON_REG_VALUE );
  writeRegister( DRATE_REG, DRATE_REG_VALUE );
  writeRegister( MUX_REG, MUX_REG_VALUE );

  // auto-calibrate (send command, wait DRDY = LOW, then wait DRDY = HIGH)
  SPI.transfer( SELFCAL_CMD );
  uint32_t t0 = micros();
  while (digitalReadFast(DRDY_PIN)!=LOW && micros()-t0 < 10000) {}
  while (digitalReadFast(DRDY_PIN)!=HIGH && micros()-t0 < 10000) {}
 
  digitalWriteFast(CS_PIN, HIGH);
  SPI.endTransaction();
}



ADS1256 test program
Cycle completed
Read 30000 samples in 999884 us
I hope the time between ADC samples are 33.33 micro seconds.

but signal appears to be not referenced to zero axis. I hope due to this only FFT is not working correctly

1707990490953.png
 
Last edited:
I think your original plan was to sample with 1000 Hz, and then do a FFT over 1024 samples, so that you get a frequency spectrum with 512 lines. Those lines correspond to 0 to 500 Hz, in steps of 1000/1024 Hz. The line that you wanted to see was i think ~155 Hz or so.

What you have is something that samples at 33 kHz. If you take 1024 of those samples and then do fft then you will get 512 lines, but now from 0 to 16.5 kHz, in steps of 32 Hz. The frequency that you wanted to see would be line #4 or 5.
This is not the resolution you want is it?

If you want to sample with 1 ms, then sample with 1000 us…

The way you had it was first sample at 33 kHz, and then copy the array of 1024 samples with a carefully managed 1000 us delay in between copying each sample from the original to the new array. That does not change the rate at which the ADC sampled before I’m aftraid…
 
I think your original plan was to sample with 1000 Hz, and then do a FFT over 1024 samples, so that you get a frequency spectrum with 512 lines. Those lines correspond to 0 to 500 Hz, in steps of 1000/1024 Hz. The line that you wanted to see was i think ~155 Hz or so.

What you have is something that samples at 33 kHz. If you take 1024 of those samples and then do fft then you will get 512 lines, but now from 0 to 16.5 kHz, in steps of 32 Hz. The frequency that you wanted to see would be line #4 or 5.
This is not the resolution you want is it?

If you want to sample with 1 ms, then sample with 1000 us…

The way you had it was first sample at 33 kHz, and then copy the array of 1024 samples with a carefully managed 1000 us delay in between copying each sample from the original to the new array. That does not change the rate at which the ADC sampled before I’m aftrai
actually i just want to collect 30k samples , remove dc offset (if any) and plot fft for the same..
 
So these data points collected from Arduino and saved in excel. The arduino seems to remove offset
meanwhile I used other software to check on what's really going wrong in FFT part.

time domain and frequency domain graphs, and FFT detects my first peak properly.

So I reconstructed the signal 30k sampling rate, FFT computed with hanning window and amplitude is rms value

1708080296449.png


closer look

1708080398913.png


I'm not sure why arduino is not detecting fft peak properly.
 
I hope now this code is computing mean, subtracting mean from each data value , please let me know is this correct or not

Code:
#include <SPI.h>
#include <arduinoFFT.h>

#define CS_PIN    14
#define DRDY_PIN  15
#define RST_PIN   16

#define SPISPEED 2500000             
#define SAMPLING_FREQUENCY 4000
#define SAMPLES 4096
arduinoFFT FFT = arduinoFFT();
unsigned long microseconds = 0;
#define BUFFER_SIZE     (30000)
unsigned int sampling_period_us;
double vReal[SAMPLES];
double vImag[SAMPLES];

#define STATUS_REG   0x00
#define MUX_REG      0x01
#define ADCON_REG    0x02
#define DRATE_REG    0x03

#define WAKEUP_CMD   0x00
#define RDATA_CMD    0x01
#define RREG_CMD     0x10
#define WREG_CMD     0x50
#define SELFCAL_CMD  0xF0
#define SYNC_CMD     0xFC
#define STANDBY_CMD  0xFD
#define RESET_CMD    0xFE

#define VREF            (2.5)   

#define DRATE_30K       (0xF0) 
#define STATUS_REG_0x03 (0x03) 
#define ADCON_REG_VALUE (0x21) 
#define DRATE_REG_VALUE (DRATE_30K) 
#define MUX_REG_VALUE   (B00001000)

volatile int DRDY_state = HIGH;

float adc_volt[BUFFER_SIZE];

void writeRegister(uint8_t address, uint8_t value)
{
  SPI.transfer( WREG_CMD | address );
  SPI.transfer( 0x00 );
  SPI.transfer( value );
  delayMicroseconds( 100 );
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) {}
  Serial.println( "ADS1256 test program" );
  START_SPI();
  attachInterrupt( DRDY_PIN, DRDY_Interrupt, FALLING );
}

void loop()
{
  int sampleCount = 0;
  float meanValue = 0.0;

  // Collect data and compute mean
  while (true) {
    uint32_t t0 = micros();
    for (int i = 0; i < BUFFER_SIZE; i++) {
      waitDRDY();
      adc_volt[i] = READ_ADC();
      meanValue += adc_volt[i];
      delayMicroseconds(10);
      sampleCount++;

      if (sampleCount >= BUFFER_SIZE) {
        meanValue /= BUFFER_SIZE;
        sampleCount = 0;
        //Serial.println("Cycle completed");
      }
    }
    //Serial.printf("Read %1d samples in %1lu us\n", BUFFER_SIZE, micros() - t0);

    // Subtract mean from each data value
    for (int i = 0; i < BUFFER_SIZE; i++) {
      adc_volt[i] -= meanValue;
      fft(adc_volt);
    }

    // Output adjusted values (optional)
    //Serial.println("Adjusted Values:");
    for (int i = 0; i < BUFFER_SIZE; i++) {
      Serial.println(adc_volt[i], 3);
    }
  }
}
void fft(float* samples)
{
  for (int i = 0; i < SAMPLES; i++)
  {
    microseconds = micros();
    vReal[i] = adc_volt[i] ;
    vImag[i] = 0.0;
    while (micros() < (microseconds + sampling_period_us)) {
    }
  }
  FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
  double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);

  Serial.print("Peak frequency is: ");
  Serial.println(peak);
}

float READ_ADC()
{
  int32_t adc_raw;   
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1));                             
  digitalWriteFast(CS_PIN, LOW);   
  
  SPI.transfer( RDATA_CMD );
  delayMicroseconds(5);     
  adc_raw = 0;   
  adc_raw |= SPI.transfer(0);
  adc_raw <<= 8;                 
  adc_raw |= SPI.transfer(0);
  adc_raw <<= 8;               
  adc_raw |= SPI.transfer(0);   
  
  digitalWriteFast(CS_PIN, HIGH);     
  SPI.endTransaction(); 

  if (adc_raw & (1<<23)) {   
    adc_raw |= 0xFF000000;   
  }
  float v = adc_raw * (float)(VREF / (1<<23));
  return( v );
} 

void DRDY_Interrupt()
{
  DRDY_state = LOW;
}
 
void waitDRDY()
{
  while (DRDY_state == HIGH) {
    continue;
  }
  DRDY_state = HIGH;
}

void START_SPI ()
{
  sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);
  pinMode(DRDY_PIN, INPUT);
  pinMode(RST_PIN, OUTPUT);
 
  digitalWrite(RST_PIN, LOW);
  delay(1);
  digitalWrite(RST_PIN, HIGH);
  delay(500);
 
  SPI.begin();
  delay(500);
 
  while (digitalRead(DRDY_PIN)) {}
  
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1));
  digitalWriteFast(CS_PIN, LOW);
  delayMicroseconds(100);   

  SPI.transfer( RESET_CMD );
  delay(5);

  writeRegister( STATUS_REG, STATUS_REG_0x03 );
  writeRegister( ADCON_REG, ADCON_REG_VALUE );
  writeRegister( DRATE_REG, DRATE_REG_VALUE );
  writeRegister( MUX_REG, MUX_REG_VALUE );

  SPI.transfer( SELFCAL_CMD );
  uint32_t t0 = micros();
  while (digitalReadFast(DRDY_PIN)!=LOW && micros()-t0 < 10000) {}
  while (digitalReadFast(DRDY_PIN)!=HIGH && micros()-t0 < 10000) {}
 
  digitalWriteFast(CS_PIN, HIGH);
  SPI.endTransaction();
}
 
It looks like you are calling your fft() function 30,000 times, but you only use the values from 0 to SAMPLES each time! You don't seem to ever do the FFT over the rest of the sample.

1. Subtract the mean as you are doing in your loop. DO NOT call your fft() function at that time.

2. In a new loop compute the FFT and advance by SAMPLES/2 each time;
Code:
uint32_t start = 0;
for( i = start;  i<(BUFFER_SIZE-SAMPLES/2); i+= SAMPLES/2){
   // now put data into vReal and vImag
   for(j= 0; j<SAMPLES; j++){ // select 4K samples in sliding window
      vReal[j+start] = adc_volt[i] ;
      vImag[j+start] = 0.0; 
      //no reason to delay here---the samples are already collected!
     } //  end of for(j= 0 ...  4K samples ready----each set overlaps previous by 2048 samples
  // now you're ready to compute the FFT
      FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
      FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
       FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
       double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
      Serial.print("Peak frequency is: ");
      Serial.println(peak);
  }  // end of for(i= start....


You should probably increase BUFFER_SIZE to 32768 so that it is a multiple of your sample size.
 
It looks like you are calling your fft() function 30,000 times, but you only use the values from 0 to SAMPLES each time! You don't seem to ever do the FFT over the rest of the sample.

1. Subtract the mean as you are doing in your loop. DO NOT call your fft() function at that time.

2. In a new loop compute the FFT and advance by SAMPLES/2 each time;
Code:
uint32_t start = 0;
for( i = start;  i<(BUFFER_SIZE-SAMPLES/2); i+= SAMPLES/2){
   // now put data into vReal and vImag
   for(j= 0; j<SAMPLES; j++){ // select 4K samples in sliding window
      vReal[j+start] = adc_volt[i] ;
      vImag[j+start] = 0.0;
      //no reason to delay here---the samples are already collected!
     } //  end of for(j= 0 ...  4K samples ready----each set overlaps previous by 2048 samples
  // now you're ready to compute the FFT
      FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
      FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
       FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
       double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
      Serial.print("Peak frequency is: ");
      Serial.println(peak);
  }  // end of for(i= start....


You should probably increase BUFFER_SIZE to 32768 so that it is a multiple of your sample size.
I shouldn't do these things late at night!

There's a bug in the code to extract the fft samples from the buffer. It should be:

vReal[ j ] = adc_volt[start+j ];
vImage[ j ] = 0; // still not sure if this needs to be done more than once!
 
Lavanya, what is SAMPLING_FREQUENCY?

You’ve shared code where it was defined at 1024, 1000 and 4000. And you mentioned ‘hope’ as justifier for correct results eventually.
But you let other code measure the time it takes to grab 30000 samples and found that to be roughly 1 second. So 33 us in between each sample. Irrespective if the vReal array is 1024 or whatever length. So when you call

FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);

would it not make more sense to have a value of 30000 in SAMPLING_FREQUENCY?
 
Back
Top