Teensy 4.1 + ADS1256

With the program above, I tested an input sine wave with frequency = 60 Hz, amplitude = 0.2 Vpp, and offset = 0 V. The data from the ADS1256 looks good. At 30 kSPS, the period of the sine wave is 500 samples, max = 0.1, min = -0.1

The total time for 30000 samples is 0.999825 us, very similar to what you measured with the mbilsky sketch.

One important note. In READ_ADC(), you had delayMicroseconds(6) between sending the RDATA command and reading the 3-byte value. The delay must be >= 5 us to get good data. With a delay < 5 us, the data is garbage.

The main reason you could not reach 30 kSPS was the unnecessary SYNC and WAKEUP commands and the large delays after those commands in READ_ADC(). Because CS was HIGH until after those commands were sent, the ADS1256 was ignoring them, so I commented them out.

I have not looked at the mbilsky sketch to see why it wasn't working. I think one problem was that it sets the PGA to a high gain (64), and that was probably causing the A/D converter to saturate.
 
Last edited:
Seems OK, but what do you mean about signal 12kHz and FFT 14kHz?

Sine 50Hz 0.1V
sine50Hz-0.1V.jpg

Sine 12000Hz 0.1V
sine12000Hz-0.1V zoom.jpg

FFT 14000Hz
FFT14000Hz.jpg
 
With 30-kHz sampling of a 12-kHz sine wave, your raw data won't look like a sine wave (only 2.5 samples per period), but theoretically the FFT is okay up to sample freq / 2 = 15 kHz.

Here is another update, with a lot more cleanup. For example

- more constants and less magic numbers in code
- move code from SAMPLE_ADC() to loop()
- in READ_ADC() simplify conversion from raw data to volts and return value
- in START_SPI(), after SELFCAL command, wait for DRDY low, then DRDY high, per datasheet
- in waitDRDY(), remove disable/enable interrupts (not necessary)

Code:
#include <SPI.h>

// PIN assignements
#define CS_PIN    21
#define DRDY_PIN  22 
#define RST_PIN   8 

// SPI
#define SPISPEED 1950000               

// 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 BUFFER_SIZE     (30000) // samples

#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 (BUFFER_SIZE==15000 ? DRATE_15K : 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( 9600 ); // 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()
{
  uint32_t t0 = micros();  
  for (int i=0; i < BUFFER_SIZE; i++) {    
    waitDRDY();
    adc_volt[i] = READ_ADC(); 
  }
  Serial.printf( "Read %1d samples in %1lu us\n", BUFFER_SIZE, micros()-t0 );

  if (Serial.available() > 0) {
    char inChar = Serial.read();
    for (int i=0; i<BUFFER_SIZE; i++) {
      #if (1)
      Serial.printf( "%5d  %8.3f\n", i, adc_volt[i] );
      #else
      byte *b = (byte *)&adc_volt[i];
      Serial.write(b[0]);
      Serial.write(b[1]);
      Serial.write(b[2]);
      Serial.write(b[3]);
      #endif
    }
  }
}

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
  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(); 
}
 
Seems OK, but what do you mean about signal 12kHz and FFT 14kHz?

On T4.1, I get total time of 999825 us for 30000 samples. That means sample rate = 30000/0.999825 = 30005.25

When you do your FFT, instead of specifying your sample rate as 30000 Hz, specify it as 30005.25 or whatever you compute. By specifying the true sample rate, I think the FFT will report the peak at 14000, which seems to be what you want.
 
Great. I wil test it more.

Just one question - any idea why is there peak at 0Hz? It is there for all frequencies.

For 12kHz:

noise1.jpg


Zoomed:

noise2.jpg
 
Can you show the time series chart? The 0th element of the FFT is simply the mean value. If you have your function generator set up to produce a sine wave with 0 offset, the mean of your signal should be 0. If your mean is not 0, you can either ignore it, or start plotting from element 1 (instead of 0), or make sure the mean of your signal is 0 by subtracting the mean from all values in the time domain. That means:

1) collect data
2) compute mean
3) subtract mean from each data value
4) compute the FFT on the modified values

The FFT should look exactly the same except that value at 0 Hz will be 0.
 
Here is time wave for 12kHz:

time1.jpg

Zoomed:

time1-zoom.jpg


If I understand well, I have to calculate average value of all samples and then deduct this average from every sample, right?
 
Yes, that's correct.

Can you please show a time series chart and FFT chart for a lower frequency, such as 50 or 100 Hz? I think you have a bias in the time series of the higher frequencies because the sample rate is not very high compared to the frequency of the sine wave.
 
Okay, good. Your 0 value is closer to 0 for the lower frequency sine wave because your time series offset is close to 0. You will ALWAYS have a non-zero value at 0 Hz if you have ANY offset, and there is almost always some offset. If you want the FFT value at 0 Hz to be exactly 0, you will have to subtract the mean from all values. You can also just ignore it!
 
Removed mean - FFT seem ok:

removed-mean.jpg

About frequency shift:
"On T4.1, I get total time of 999825 us for 30000 samples. That means sample rate = 30000/0.999825 = 30005.25"

Where I have to change sample rate to 30005.25?

My FFT Python:
Code:
        yf = scipy.fftpack.fft(self.ydata5)
        xf = np.linspace(0.0, 1.0/(2.0*T), SPS//2)
        self.canvas.axes.cla()
        self.canvas.axes.set_ylabel("Amplitude in g")
        self.canvas.axes.set_xlabel("Frequency in Hz")
        self.canvas.axes.plot(xf, 1.0/SPS * np.abs(yf[:SPS//2]), 'b')

Because there is FFT from self.ydata5 = array from Teensy.
SPS = 30000
T = 1.0 / SPS

Anyway, thanks a lot for help.
 
You're welcome. "SPS" is probably 30000, and this value must already be part of "ydata5". When the FFT is computed, you need to tell it the actual samples per second, which is 30005.25. Pseudo-code would look something like this:

SPS = 30000
ActualSPS = 30005.25
ydata.SPS = ActualSPS (so it uses 30005.25)
FFT(ydata5)
plot(SPS) (so it uses 30000)

Remember that 30005.25 is based on MY measurement of 999825 us, and 30000/0.999825 = 30005.25

You should compute the value based on your own measurement.
 
But ActualSPS (30005) I need from Teensy, right? And tehre I have buffer 30000. I can not use 30005 in Python because I receive only 30k samples. Or I can add to array in Python 5 items with zero value...
 
When you run the sketch, it tell you the time for 30000 samples. Yes, you have 30000 samples, but these are read in slightly less than 1 second, so your SPS (samples PER SECOND) is not 30000

SPS = 30000 samples / 0.999825 seconds = 30005.25 samples/second

Your FFT needs to know (a) how many samples, and (b) total time (seconds) or frequency (Hz) or time for one sample (seconds). Look for references to ydata5 before the call to fft()
 
Hi,
I am testing it with my app in Python.
But I can see lot of noise:

DATA ADXL NOISE.jpg

If I have connected signal generator, I have no problem with noise and no problem witth all range of frequencies from 0-15000Hz:

generator-off-noise.jpg

If I connect ADXL1005 eval board I measure quite OK values from lower frequencies (a bit noisy but OK)

motor34Hz-OK with another analyzer.jpg

But for example if I start motor for 78Hz, I can see nonsenses - values are two times lower than on profi analyzer.

motor-78Hz-PROBLEM.jpg

Question is - is possible to lower noise?
Can it be there problem with "anti-aliasing"?

Thanks for any advice!
 
Back
Top