Teensy 4.0 + ADS1220

Lavanya rajan

Well-known member
Hi all,

Back again with ADC related question. I have Teensy 4.0 and ADS1220 ADC. I have an ADXL356 based Accelerometer sensor which captures vibration and its output is mapped to ADS1220. I need to continuously read 2k samples from ADC, calculate vibration in terms of Acceleration (either g or m/s2) and plot FFT and find peak frequency.

The input to the sensor is generated from frequency generator with frequency of 159.2 Hz and amplitude of 1V rms

The ADC is purchased from https://cyberblogspot.com/how-to-use-ads1220-adc-module-with-arduino/.

However when I check the output the peak frequency is not detecting properly.

I'm attaching the code I used. Can anyone check and let me know the issue
Code:
#include "Protocentral_ADS1220.h"
#include <SPI.h>
#include <arduinoFFT.h>


#define SPISPEED 2500000
#define SAMPLES 1024
#define SAMPLING_FREQUENCY 1000
#define PGA          1                 // Programmable Gain = 1
#define VREF         5.06           // Internal reference of 2.048V
#define VFSR         VREF/PGA
#define FULL_SCALE   (((long int)1<<23)-1)
 
#define ADS1220_CS_PIN    28
#define ADS1220_DRDY_PIN  32
 
Protocentral_ADS1220 pc_ads1220;
int32_t adc_data;
volatile bool drdyIntrFlag = false;
unsigned long microseconds;
arduinoFFT FFT = arduinoFFT();

unsigned int sampling_period_us;
int i;
double k[SAMPLES];
float adc_volt;
float adc_g;
double vReal[SAMPLES];
double vImag[SAMPLES];
void drdyInterruptHndlr(){
  drdyIntrFlag = true;
}
 
void enableInterruptPin(){
 
  attachInterrupt(digitalPinToInterrupt(ADS1220_DRDY_PIN), drdyInterruptHndlr, FALLING);
}
 
void setup()
{
    Serial.begin(9600);
    SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
    pinMode(ADS1220_CS_PIN, OUTPUT);
    digitalWrite(ADS1220_CS_PIN, LOW); // tied low is also OK.
    pinMode(ADS1220_DRDY_PIN, INPUT);
 
    pc_ads1220.begin(ADS1220_CS_PIN,ADS1220_DRDY_PIN);
 
    //pc_ads1220.set_data_rate(DR_1000SPS);
    pc_ads1220.set_pga_gain(PGA_GAIN_1);
    pc_ads1220.set_OperationMode(MODE_TURBO);
 
    pc_ads1220.set_conv_mode_continuous(); //Set Single shot mode
    pc_ads1220.Start_Conv();
    sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
}

void loop()
{
   Serial.println("Samples started");
  savesamples();
}

void test()
{
 
    adc_data=pc_ads1220.Read_SingleShot_SingleEnded_WaitForData(MUX_SE_CH0);
    adc_volt= convertToMilliV(adc_data)/1000;
    adc_g= adc_volt/0.02; //sensitivity of accelerometer
    //Serial.println(adc_g);
//    delay(100);
 }
 
float convertToMilliV(int32_t i32data)
{
    return (float)((i32data*VFSR*1000)/FULL_SCALE);
}

void savesamples()
{
  for( i=0; i<SAMPLES; i++)
  {
    microseconds = micros();
    test();
    k[i]=adc_g;
    //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_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
   double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
   int size1= sizeof(vReal);
   Serial.println(size1);
   Serial.print("Peak frequency is: ");
   Serial.println(peak);

  }
 
So, attaching the code without FFT , and screenshot serial plotter. Input is Sinusoidal signal with 1.0 Vpp and DC offset 0.5V and 60Hz frequency
Code:
#include "Protocentral_ADS1220.h"
#include <SPI.h>

#define PGA          1                 // Programmable Gain = 1
#define VREF         2.048             // Internal reference of 2.048V
#define VFSR         VREF / PGA
#define FULL_SCALE   (((long int)1 << 23) - 1)

#define ADS1220_CS_PIN    28
#define ADS1220_DRDY_PIN  32

Protocentral_ADS1220 pc_ads1220;
int32_t adc_data;
float dc_offset = 0.5; // Variable to store DC offset

void setup() {
    Serial.begin(9600);

    pc_ads1220.begin(ADS1220_CS_PIN, ADS1220_DRDY_PIN);

    pc_ads1220.set_data_rate(DR_1000SPS);
    pc_ads1220.set_pga_gain(PGA_GAIN_1);

    pc_ads1220.set_conv_mode_continuous(); // Set continuous conversion mode
    pc_ads1220.Start_Conv();
}

void loop() {
    // Read data from channel 0
     adc_data = pc_ads1220.Read_SingleShot_SingleEnded_WaitForData(MUX_SE_CH0);

 
    //Serial.print("A0 in (V): ");
    Serial.println(convertToV(adc_data));


    delay(100);
}


float convertToV(int32_t i32data) {
    return (float)((i32data * VFSR) / FULL_SCALE);
}
1706516518221.png
 
As you can see in the serial plotter , the signal reconstruction is not that correct. How can I properly reconstruct the signal.
 
Well, as you use SPI to an external ADC (according to OP) this is not an Teensy ADC problem. Also, if data are corrupt after ADC, as it seems, there is no way to reconstruct. In other words, this is no Teensy SW issue, but a ADC HW/SW issue.

Of course, you configure for continuous conversion and use singles-hot read. Not sure if Serial.print is fast enough for not missing samples.
 
Last edited:
Your code in msg #3 has a 100ms delay in loop(). Even if the rest of the code runs at infinite fast speed, your best case scenario would be 10 Hz sample rate. To get 2000 Hz sample rate, you need the ADC read to happen every 500us.

To get a more reliable speed, rather than a fixed delay where the time spent reading the ADC will cause the loop to run at a different speed, you could use an elapsedMicros variable to run the code every 500 us. It would probably look something like this:

Code:
void loop() {
  static elapsedMicros usec;

  if (usec >= 500) {
    usec = 0;  // or subtract 500...
 
    // Read data from channel 0
    adc_data = pc_ads1220.Read_SingleShot_SingleEnded_WaitForData(MUX_SE_CH0);
 
    //Serial.print("A0 in (V): ");
    Serial.println(convertToV(adc_data));
  }
}

Of course, this only works reliably if pc_ads1220.Read_SingleShot_SingleEnded_WaitForData() plus Serial.println() always complete faster than 500 us.

You could also try reading the elapsedMicros variable after both of those to check how much time they actually took. Maybe also print it to Serial, or if you use Dual Serial (Tools > USB Type menu) you could print it to SerialUSB1 to send to the other port. If you prefer using an oscilloscope (usually the way I do this sort of thing), you could also add digitalWriteFast() before and after, then watch the pulse width on your scope screen. While setting up an oscilloscope is extra work, it can really give excellent visibility of the timing details (and any problems) you might not anticipate, whereas printing to serial ports generally only gives you the info you anticipated you would want to see.
 
Last edited:
Hi Paul,

I modified the code, Now I'm able to detect the peak frequency, but the detected frequency is almost 2Hz more, for eg, if input frequency is 100 Hz, then detected is 102 Hz, it follows for all values. I hope the samples collected for FFT operation is not proper. can you help me to resolve this

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

#define PGA          1                 // Programmable Gain = 1
#define VREF         2.048             // Internal reference of 2.048V
#define VFSR         VREF / PGA
#define FULL_SCALE   (((long int)1 << 23) - 1)

#define ADS1220_CS_PIN    28
#define ADS1220_DRDY_PIN  32

Protocentral_ADS1220 pc_ads1220;

#define SAMPLES 512
#define SAMPLING_FREQUENCY 1000

double k[SAMPLES];
int32_t adc_data;
float dataWithoutOffset;
float dc_offset = 0.0; // Variable to store DC offset
float z_g;
unsigned long microseconds;

arduinoFFT FFT = arduinoFFT();
double vReal[SAMPLES];
double vImag[SAMPLES];
unsigned int sampling_period_us;

void setup()
{
    Serial.begin(9600);
    pc_ads1220.begin(ADS1220_CS_PIN, ADS1220_DRDY_PIN);
    pc_ads1220.set_data_rate(DR_1000SPS);
    pc_ads1220.set_pga_gain(PGA_GAIN_1);
    pc_ads1220.set_conv_mode_continuous(); // Set continuous conversion mode
    pc_ads1220.select_mux_channels(MUX_SE_CH0);
    pc_ads1220.Start_Conv();
    sampling_period_us = round(1000000*(1.0/SAMPLING_FREQUENCY));
}
void loop()
{
  savearray();
  delay(100);
}
void test() {
     adc_data=pc_ads1220.Read_WaitForData();
    float Vout = (float)((adc_data*VFSR)/FULL_SCALE);
    
    float alpha = 0.01; // EMA filter coefficient (adjust as needed)
    dc_offset = alpha *Vout + (1 - alpha) * dc_offset;
    float dataWithoutOffset = Vout - dc_offset;
    z_g = dataWithoutOffset/0.02; //accelerometer sensitivity

    //Serial.println(z_g);
 
    
    
}


void savearray()
{
  for( int i=0; i<SAMPLES; i++)
  {
    microseconds = micros();
    test();
 
     vReal[i] = z_g ;
    
    vImag[i] = 0.0;
    while(micros() < (microseconds + sampling_period_us)){
        }
           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);
}
 
can you help me to resolve this

Probably not. Best I can do is suggest a path you might take to resolve it yourself. I can try to help with issues specific to Teensy, but there's a limit to how deeply I can assist with the design of your project. Here's a couple suggestions, with that limit in mind...

First, I would collect the raw data on a PC and use well established software to analyze it. If you have access to any commercial data acquisition equipment, use it to also digitize the signal. Then you can compare the results. Or if you have no special equipment, at the very least you can capture the AC portion of the signal using a high quality USB audio sound card, or even just the sound input on your PC. If you have signal quality issues, perhaps inaccurate sample rate, jitter, non-linearity with whatever circuitry you've built, or issues with the ADS1220, best to discover those problems before you pour a lot of time into the analysis.

After you're sure you data collection is good, then you should consider the proper algorithm. Is FFT really the correct way? Have you looked at the algorithm arduinoFFT MajorPeak() really uses? I used a quick search and found this code. A quick read of the code look (at least to my eyes) to be finding the 2 bins with highest magnitude and then doing linear interpolation to guess the frequency between them. It doesn't seem to consider anyt of the other bins. Maybe, did I read it correctly? As understand this, your FFT bins will be spaced 3.9 Hz apart. Is this algorithm really up to your task? This question is mostly rhetorical... it's up to you to consider the answer or even whether the question is appropriate. Or maybe other people on this forum with knowledge of FFT analysis might have opinions?

I really can't get more deeply involved in the development of your project, but hopefully this message at least helps you to find a path to resolve the issues.
 
Back
Top