FFT not working properly

Lavanya rajan

Well-known member
Hello All,

I'm reading data from 24 bit ADC with 30kSps, and trying to remove offset and plot FFT. I'm attaching the code I have been testing with. But the serial monitor is not printing the peak frequency value. Also I want to remove DC offset in the signal, but I dont know how to do it. can anyone help me on this, please

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 1000
#define SAMPLING_FREQUENCY 1024
arduinoFFT FFT = arduinoFFT();

#define BUFFER_SIZE     30000
unsigned int sampling_period_us;
unsigned long microseconds = 0;
double vReal[SAMPLES];
double vImag[SAMPLES];

// 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[SAMPLES];

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 < SAMPLES; i++) {
      waitDRDY();
      adc_volt[i] = READ_ADC();
      delayMicroseconds(10);
      sampleCount++;
      if (sampleCount >= SAMPLES) {
        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 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); 
}

In serial monitor , it outputs only ads1256 test program, after that it stops
 
Thanks for pointing out my silly mistake. now the code outputs

Code:
ADS1256 test program

Peak frequency is: 17.68

Cycle completed

Peak frequency is: 7.89

Cycle completed

Peak frequency is: 3.97

Cycle completed

Peak frequency is: 15.96

Cycle completed

Peak frequency is: 7.94

Cycle completed

Peak frequency is: 18.35

Cycle completed

But i'm inputting known sine signal with amplitude of 1g and frequency of 159.2hz, but peak value detected is not correct here
 
The low frequencies will be due to the DC offset - the peak value is often bin 1 for a naive FFT with a big DC offset.

Removing DC offset is simple, subtract the mean from every sample.
 
attaching the code with offset removal, but still the program not detecting peak frequency, not sure whether the method i'm using for DC removal is good, it seems to ok when I plot time domain signals.
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[SAMPLES];

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 < SAMPLES; i++) {
      waitDRDY();
      adc_volt[i] = READ_ADC();
      delayMicroseconds(10);
      sampleCount++;
      if (sampleCount >= SAMPLES) {
        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 v = adc_raw * (float)(VREF / (1 << 23));
    float alpha = 0.01; // EMA filter coefficient (adjust as needed)
    dc_offset = alpha *v + (1 - alpha) * dc_offset;
    float dataWithoutOffset = v - dc_offset;
  return(dataWithoutOffset );
}

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


Please check
 
saved arduino time domain samples in excel and plotted this signal for 30k samples but x axis is not time value it only points

1707471661994.png


closer look of signal with 1500 samples

1707471703018.png
 
The code works OK when simulating a sin wave as input.

I see a problem in the way you attempt to do equidistant sampling. First you acquire a buffer with 1024 samples with a delay 10 us call in between. That does not a guarantee that there's 10 us in between each sample. The time for SPI transfers comes on top. But also interrupts for serving USB or any other background activity will eat CPU time.

Next, in your fft() function you attempt do redo the sampling (?) so that it becomes equidistant (??). But that will not work because the samples are already captured in the array - you cannot undo the fact that there was a specific time that had lapsed in between each sample being captured.

Also your first sample likely is a random number: the ADC may have been in ready state already...

If you'd really want to do it decently then have a timer generating interrupts, trigger ADC interrogation in the interrupt service routine and fill the 1024 samples buffer like that. If that's beyond capabilities, then consider changing your loop() into:
Code:
void loop()
{
    READ_ADC();
    microseconds = micros();
    waitDRDY();
    for (int i = 0; i < SAMPLES; i++) {
      adc_volt[i] = READ_ADC();


      
      while (micros() < (microseconds + sampling_period_us)) {
      }
      microseconds = micros();
      waitDRDY();
    }
 
    fft(adc_volt);  // Call fft function with the adc_volt array
    Serial.println("Cycle completed");
  }
}
Note that the waitDRDY now isn;t really needed anymore, becayse you can expect the ADC to be realdy after the sampling_period_us

This is the modified code where I replaced the input source to a pure sine wave instead of a stream from the SPI ADC.

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[SAMPLES];

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 < SAMPLES; i++) {
//      waitDRDY();
//      adc_volt[i] = READ_ADC();
      adc_volt[i] = sin(i/27);
      delayMicroseconds(10);
      sampleCount++;
      if (sampleCount >= SAMPLES) {
        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 v = adc_raw * (float)(VREF / (1 << 23));
    float alpha = 0.01; // EMA filter coefficient (adjust as needed)
    dc_offset = alpha *v + (1 - alpha) * dc_offset;
    float dataWithoutOffset = v - dc_offset;
  return(dataWithoutOffset );
}

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);
}
 
So thanks for the update, still program is not able to find the peak frequency and observed weird behaviour is that, my serial port keeps toggling, connecting and disconnecting, any idea why it is happening
 
code i uploaded to teensy
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 < SAMPLES; 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 v = adc_raw * (float)(VREF / (1 << 23));
    float alpha = 0.01; // EMA filter coefficient (adjust as needed)
    dc_offset = alpha *v + (1 - alpha) * dc_offset;
    float dataWithoutOffset = v - dc_offset;
  return(dataWithoutOffset );
}

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);
}
 
You're using interrupt on ADC DRDY. Quite likely your Teensy now hangs waiting for the interrupt to come. But it doesn't come because the ADC isn't triggered yet?
Why do you let the code wait for interrupts anyway? You're doing polling in your READ_ADC() are you not?
 
Your correction for offset is using an IIR filter to calculate the dc_offset. That will take several hundred samples to stabilize near the actual DC offset. (This actually shows up in your plot). If you include those samples in the FFT calculation, you will have a residual DC offset.

I think it might better to collect the offset values, compute the mean of the samples and subtract it just before doing the FFT. You can add each sample value to the sum of the samples as you collect the samples. Then divide the sum by the number of samples to get the mean, and subtract the mean from each sample. Yes, that's another 1024 subtractions to add to the time used to get the result---but you're eliminating the subtraction in the READ_ADC function.
 
The FFT is going to dominate compute time. Hamming isn't my choice of a window function. Hann window is widely used for basic use, if you want accurate peak values a flat-top window is required. Alas many systems have a low quality flat-top window with limited dynamic range, and don't let you provide your own window function. If you want to find out more about the gritty reality of spectrum measurement using the FFT there's a really thorough treatment here: https://holometer.fnal.gov/GH_FFT.pdf
 
Why go through the trouble to remove DC from signal? If it isn't eating up much of your dynamic range, just leave it and zero out bin 0.
 
The FFT is going to dominate compute time. Hamming isn't my choice of a window function. Hann window is widely used for basic use, if you want accurate peak values a flat-top window is required. Alas many systems have a low quality flat-top window with limited dynamic range, and don't let you provide your own window function. If you want to find out more about the gritty reality of spectrum measurement using the FFT there's a really thorough treatment here: https://holometer.fnal.gov/GH_FFT.pdf
The linked paper is not only thorough, but very well written. You don't need a math degree to understand the discussion and they provide pointers to real-world software and great figures showing the results of various approaches to spectral analysis. I wish I'd read that paper before I worked on an oceanographic software system that involved spectral analysis a decade ago.
 
I think it might better to collect the offset values, compute the mean of the samples and subtract it just before doing the FFT. You can add each sample value to the sum of the samples as you collect the samples. Then divide the sum by the number of samples to get the mean, and subtract the mean from each sample. Yes, that's another 1024 subtractions to add to the time used to get the result---but you're eliminating the subtraction in the READ_ADC function.
So I tried implementing your suggestion and modified the code, but still my FFT is not correct

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];
double xaxis[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[SAMPLES];

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();
    float sum = 0.0;

    for (int i = 0; i < SAMPLES; i++) {
      waitDRDY();
      adc_volt[i] = READ_ADC();
      sum += adc_volt[i];
        // Compute mean (DC offset)
    float mean = sum / SAMPLES;

    // Subtract mean from each sample to remove DC offset
    for (int i = 0; i < SAMPLES; i++) {
      adc_volt[i] -= mean;
    }
      delayMicroseconds(10);
      sampleCount++;
      if (sampleCount >= SAMPLES) {
        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_HANN, 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);

   for(int i=0; i<(SAMPLES/2); i++)
   {     
     Serial.print(vReal[i]);
     Serial.print("\t");
      xaxis[1]=0;
      vReal[1]=0;
      xaxis[i]=(i * 1.0 * SAMPLING_FREQUENCY) / SAMPLES;
    Serial.println(xaxis[i],2);
}}

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 v = adc_raw * (float)(VREF / (1 << 23));
  float alpha = 0.01; // EMA filter coefficient (adjust as needed)
  dc_offset = alpha * v + (1 - alpha) * dc_offset;
  float dataWithoutOffset = v - dc_offset;

  return dataWithoutOffset;
}

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

attaching the screen shot FFT plot reading from arduino
1707541749526.png


pls help me to sort this
 
Now, I have one more program which detects my input signal frequency properly, but there are few differences as compared to previously used program and to this


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

#define cs 14 // chip select
#define rdy 15 // data ready, input
#define rst 16 // may omit

#define SPISPEED 2500000   // Teensy 3.2 @120 mhz
#define SAMPLES 1024
#define SAMPLING_FREQUENCY 1000

float value1;
float value2;
float value3;
float x;
double VREF = 2.50;
#define DEBUG 1

arduinoFFT FFT = arduinoFFT();

unsigned int sampling_period_us;
unsigned long microseconds;

double vReal[SAMPLES];
double vImag[SAMPLES];
double k[SAMPLES];
double xaxis[SAMPLES];

void setup()
{
  Serial.begin(115200);
  pinMode(cs, OUTPUT);
  digitalWrite(cs, LOW); // tied low is also OK.
  pinMode(rdy, INPUT);
  pinMode(rst, OUTPUT);
  digitalWrite(rst, LOW);
  delay(1); // LOW at least 4 clock cycles of onboard clock. 100 microseconds is enough
  digitalWrite(rst, HIGH); // now reset to default values
  delay(100);
  SPI.begin(); //start the spi-bus
  delay(100);
  while (digitalRead(rdy)) {}  // wait for ready_line to go low
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(100);
  SPI.transfer(0xFE);
  delay(5);

  byte status_reg = 0x00 ;  // address (datasheet p. 30)
  byte status_data = 0x01; // 01h = 0000 0 0 0 1 => status: Most Significant Bit First, Auto-Calibration Disabled, Analog Input Buffer Disabled
  //byte status_data = 0x07; // 01h = 0000 0 1 1 1 => status: Most Significant Bit First, Auto-Calibration Enabled, Analog Input Buffer Enabled
  SPI.transfer(0x50 | status_reg);
  SPI.transfer(0x00);   // 2nd command byte, write one register only
  SPI.transfer(status_data);   // write the databyte to the register
  delayMicroseconds(100);
 
  byte adcon_reg = 0x00; //A/D Control Register (Address 02h)
  //byte adcon_data = 0x20; // 0 01 00 000 => Clock Out Frequency = fCLKIN, Sensor Detect OFF, gain 1
  byte adcon_data = 0x00; // 0 00 00 000 => Clock Out = Off, Sensor Detect OFF, gain 1
  //byte adcon_data = 0x01;   // 0 00 00 001 => Clock Out = Off, Sensor Detect OFF, gain 2
  SPI.transfer(0x50 | adcon_reg);  // 52h = 0101 0010
  SPI.transfer(0x00);              // 2nd command byte, write one register only
  SPI.transfer(adcon_data);        // write the databyte to the register
  delayMicroseconds(100);

  byte drate_reg = 0x03; //DRATE: A/D Data Rate (Address 03h)
  byte drate_data = 0xF0; // F0h = 11110000 = 30,000SPS
  SPI.transfer(0x50 | drate_reg);
  SPI.transfer(0x00);   // 2nd command byte, write one register only
  SPI.transfer(drate_data);   // write the databyte to the register
  delayMicroseconds(100);

  SPI.transfer(0xF0);     
  delay(100);
  digitalWrite(cs, HIGH);
  SPI.endTransaction();
sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
}

void loop()
{
unsigned long msec0 = millis();
saveinarray();
unsigned long msec1 = millis();
//Serial.print   ("capture interval ");
//Serial.println (msec1 - msec0);
//Serial.println("Iteration completed");
delay(100);
}


void test()
{
  unsigned long adc_val[1] = {0}; // store readings in array
  byte mux[1] = {0x08};
  int i = 0;
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(2);
 
  for (i=0; i <1; i++){         // read all 8 Single Ended Channels AINx-AINCOM
  byte channel = mux[i];             // analog in channels #
  while (digitalRead(rdy)) {} ;                         
  SPI.transfer(0x50 | 0x01); // 1st Command Byte: 0101 0001  0001 = MUX register address 01h
  SPI.transfer(0x00);     // 2nd Command Byte: 0000 0000  1-1=0 write one byte only
  SPI.transfer(channel);     // Data Byte(s): xxxx 1000  write the databyte to the register(s)
  delayMicroseconds(2);

  //SYNC command 1111 1100                               // ********** Step 2 **********
  SPI.transfer(0xFC);
  delayMicroseconds(2);

  SPI.transfer(0x00);
  delayMicroseconds(250);   // Allow settling time

  SPI.transfer(0x01); // Read Data 0000  0001 (01h)       // ********** Step 3 **********
  delayMicroseconds(5);
 
  adc_val[i] = SPI.transfer(0);
  adc_val[i] <<= 8; //shift to left
  adc_val[i] |= SPI.transfer(0);
  adc_val[i] <<= 8;
  adc_val[i] |= SPI.transfer(0);
  delayMicroseconds(2);
  }                                // Repeat for each channel ********** Step 4 **********
 
  digitalWrite(cs, HIGH);
  SPI.endTransaction();

  //The ADS1255/6 output 24 bits of data in Binary Two's
  //Complement format. The LSB has a weight of
  //2VREF/(PGA(223 − 1)). A positive full-scale input produces
  //an output code of 7FFFFFh and the negative full-scale
  //input produces an output code of 800000h.

  for (i=0; i <1; i++)
  {   // Single ended Measurements
  if(adc_val[i] > 0x7fffff)
  {
    adc_val[i] = adc_val[i]-16777216; //do 2's complement
  }
   float voltage = ((2*VREF) / 8388608)*adc_val[0];
 
float  Read_Data = adc_val[i] *0.0000002980232;
value1 = adc_val[0] *0.0000002980232;
value2 = adc_val[1] *0.0000002980232;
value3 = adc_val[2] *0.0000002980232;
x = (voltage - 0.5);

//  Serial.print(Read_Data);   // Raw ADC integer value +/- 23 bits
//  Serial.print("      ");
// Serial.println(x);
//Serial.println(x);
}}


void saveinarray()
{
  for( int i=0; i<SAMPLES; i++)
  {
    microseconds = micros();
    test();
    k[i]=x;
    //Serial.println(k[i]);

    vReal[i] = k[i] ;
    vImag[i] = 0.0;
    while(micros() < (microseconds + sampling_period_us)){
        }
  }
  FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HANN, 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);
  
    
}

here the data rate is set to 30k, but in real time 30k samples are not acquired, its only 1024 samples. Is this major difference affecting the finding peak of signal in my previous program. Please enlighten me
 
looking at your FFT output; you are sampling much too fast for the signals you are interested in or, the signals ac levels are very small compared to their dc offset or, you have a significant low frequency trend such as you are sampling on a rising or falling large signal edge. Or a combination of all of those….

Post the waveform or part thereof also if you can.
 
Oh and if you are not sampling on a trigger/timer (so your sampling interval is not stricktly equidistant), you are introducing a low frequency ghost. Depending on how how severe and how wobbly your actual sampling rate is, this would show as a distribution of frequencies hugging dc.
 
looking at your FFT output; you are sampling much too fast for the signals you are interested in or, the signals ac levels are very small compared to their dc offset or, you have a significant low frequency trend such as you are sampling on a rising or falling large signal edge. Or a combination of all of those….

Post the waveform or part thereof also if you can.
as of now the frequency of interest to be determined by the code is 159.2 Hz, I hope sampling the signal at 1000Hz is reasonable, further on my signal ac level is 40mV without offset

attaching the signal plot(5000 samples), readings saved from arduino

1707547459740.png
 
In post #15 for function loop() you've got braces in the wrong place (doesn't match indentation).
 
In post #15 for function loop() you've got braces in the wrong place (doesn't match indentation).
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 << 23));
  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()
{
  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);
}
 
What do you think is the time in between adc samples??

I would say it is around 15 us. (10 + 5 + some cpu time).

You‘ve not been clear on how you get ‘signal plot(5000 samples)’ data and how that relates to the 1024 samples in the code that you did share… The spectrum with two peaks (post #15) does not tell us what the units are on x and y axis.
But with a peak still at f=0, it is clear that the mean subtraction does not work as intended… I think you set that mean to zero each time you read a sample?
 
You‘ve not been clear on how you get ‘signal plot(5000 samples)’ data and how that relates to the 1024 samples in the code that you did share… The spectrum with two peaks (post #15) does not tell us what the units are on x and y axis.
But with a peak still at f=0, it is clear that the mean subtraction does not work as intended… I think you set that mean to zero each time you read a sample?
the signal plot is the adc reading which i got from loop section. I saved 30000samples and plotted the samples in excel

but 1024 samples belongs to FFT section, where it performs 1024 point FFT, which is how I understood
related to peaks the x axis are frequency values and y axis are vReal values from the program
this part of code in fft() section does the fft related outputs
Code:
for(int i=0; i<(SAMPLES/2); i++)
   {     
     Serial.print(vReal[i]);
     Serial.print("\t");
      xaxis[1]=0;
      vReal[1]=0;
      xaxis[i]=(i * 1.0 * SAMPLING_FREQUENCY) / SAMPLES;
    Serial.println(xaxis[i],2);
}

yes, you are right mean subtraction is not happening as it is supposed to be, I moved that part to READADC() part but still no success, it is same as old.

literally blank on where the problem. FYI, the program in reply#16 works well, it detects my input frequency but it is not good at low frequency such as from 10 to 100hz, but it detects frequencies from 159.2 to 800Hz, no idea what is wrong in two codes to behaviour in this way.
 
Back
Top