Teensy 4.1 + ADS1256

frohr

Well-known member
Hi all,
I have Teensy 4.1 and ADS1256. I am not strong in ADS1256 so I have questions for more experinced colleagues what I need:
1) Reading 30kSPS
2) Timing is very important = 1 sample every 1/30000 seconds (one sample every33.333 microseconds)
3) Differential input
4) Continuous reading
5) Save 30k samples to array
I have to read sensor and make FFT and calculate velocity. So timing is very important to have correct result and 30kSPS to have FFT 0-15000Hz.
Thank you for any advice and maybe example code which can help me.
 
You say that you are not strong in ADS1256. Have you read the data sheet? Have you searched for existing ADS1256 libraries? If you do, you will find some. Choose one, install it, and run the example sketches. When you run into a problem, or you need help getting to the speed required, you'll be able to ask specific questions. This is a forum to get help, not to have others do the work for you!
 
To get precision timing the #SYNC line probably should be driven in hardware from, say, a PWM output set to the right speed.
Then the code to read the data can be interrupt driven from #DRDY going low.

Issuing conversion starts using SPI commands may introduce timing jitter that will reduce the quality of the data - for an FFT
timing is crucial to avoid spurs. For 24 bit data you'll need the q31 or f32 CMSIS FFT routines for best accuracy. Even better you
might find an FFT library using doubles.

Do you know which FFT window you want to use? I'm not sure what you are doing, ie how does your signal encode "velocity"?
 
It also bothers me that the ADS1256 data sheet says that the maximum sample rate is 30KHz. Running any ADC right at the limits on the data sheet bothers me. Do You really need a 24-bit ADC for your application? Is it possible that a 16-bit ADC would do what you need? If so, you would have to receive 1/3 less data via the SPI link.

I used a similar product, the ADS1254 many years ago in an oceanographic data logger. It turned out that the application didn't really need the 24-bit data to do the calculations required and solid 16-bit data was sufficient. This was an application that depended on FFT calculations and the detection of spectral peaks. It turned out that timing accuracy in collection was more important that the increase in resolution and dynamic range provided by 24-bit data.
 
On Teensy I just collect data and main program is in Python. When I press button then I read samples from teensy via serial (block of for example 20k samples and reading time I have about 0,08s what is perfect for me). And then calculate g from V, then low and high cut filters and then plot wave and FFT (hanning window) for 10-1000Hz and 500-12000Hz. Mostly the sampling rate 25kSPS is ok for me but Im not sure how to do it on Teensy. Now I can read 15kSPS and it works fine but FFT max 7500Hz is too low. I have single read and 15kSPS - if I change to 30kSPS (write 11100000 for 15kSPS / 11110000 for 30kSPS ) then everithing is wrong. For vibrations and bearings failures is important high resolution. Standard is 24bit.

My code now:

Code:
#include <SPI.h>
// Define PIN
#define cs 21
#define rdy 22 
#define rst 8 
// SPI
#define SPISPEED 1950000               
#define WREG 0x50
double mV;
double v;
int32_t adc_raw = 0;
long minus;
volatile int DRDY_state = HIGH;
double VREF = 2.50;
#define BUFFER_SIZE 15000
float adc_volt[BUFFER_SIZE];
float adc_g[BUFFER_SIZE];
double g;
char inChar;

void setup()
{ 
  Serial.begin(2000000); 
  START_SPI();  
}

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

void loop()
{  
  SAMPLE_ADC();
  SAMPLE();  

    if (Serial.available() > 0) {
      inChar = Serial.read();
      for(int i=0; i<BUFFER_SIZE; i++) {
        byte *b = (byte *)&adc_volt[i];
        Serial.write(b[0]);
        Serial.write(b[1]);
        Serial.write(b[2]);
        Serial.write(b[3]);
      }
  }
}

void SAMPLE_ADC()
{        
   for(int i=0;i <(BUFFER_SIZE); i++)              
  {    
    READ_ADC();
    adc_volt[i] = v; 
   }
 }

void SAMPLE()
{ 
  for(int i=0;i <(BUFFER_SIZE); i++)              
  {      
    adc_volt[i] = adc_volt[i]; 
  }
} 

void READ_ADC()
{   
   adc_raw= 0;    
   waitforDRDY();
   SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); 
    //SYNC command 1111 1100                             
   SPI.transfer(0xFC);
   delayMicroseconds(10);
    //WAKEUP 0000 0000
   SPI.transfer(0x00);
   delayMicroseconds(20);   // Allow settling time  
   digitalWriteFast(cs, LOW);    
   SPI.transfer(B0000001); 
   delayMicroseconds(6);      
   adc_raw |= SPI.transfer(0); 
   adc_raw <<= 8;                 
   adc_raw |= SPI.transfer(0); 
   adc_raw <<= 8;                
   adc_raw |= SPI.transfer(0);    
   digitalWriteFast(cs, HIGH);     
   SPI.endTransaction();  
  if (( minus = adc_raw >> 23 == 1))
  {
     adc_raw = adc_raw- 16777216; 
  } 
       v = (2.5 / 8388608)*adc_raw;   
       adc_raw= 0;        
  }  
 
void waitforDRDY() 
{
  while (DRDY_state) 
 {
  continue;
 }
  noInterrupts();
  DRDY_state = HIGH;
  interrupts();
}
//Interrupt function
void DRDY_Interuppt()
 {
  DRDY_state = LOW;
 }


void START_SPI ()
{

  pinMode(cs, OUTPUT);
  digitalWrite(cs, LOW); 
  pinMode(rdy, INPUT);
  pinMode(rst, OUTPUT);
  digitalWrite(rst, LOW);
  // 
  delay(1); 
  digitalWrite(rst, HIGH); 
  delay(500);
   //start the spi-bus
  SPI.begin(); 
  delay(500);
  // nastaveni preruseni
  while (digitalRead(rdy)) {} 
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(100);   
  attachInterrupt(rdy, DRDY_Interuppt, FALLING);
  //Reset to Power-Up Values (FEh)
  SPI.transfer(0xFE);
  delay(5);
  // nastaveni ADS1256 
  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 = 0x03; // 01h = 0000 0 0 1 1 => Most Significant Bit First,  Auto-Calibration Disabled, Anlog Input Buffer Enabled
 //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 = 0x02; //A/D Control Register (Address 02h)
  byte adcon_data = 0x21; // 0 01 00 001 => Clock Out Frequency = fCLKIN, Sensor Detect OFF, gain 2 7.68MHz 
  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 = 0xE0 ; //E0 15khz // F0 30khz
  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);

  //First we need to write some registers to select an input:  
  writeRegister(0x01, B00001000); 
  delayMicroseconds(100); 

  SPI.transfer(0xF0);     
  delay(1000);
  
  SPI.endTransaction(); 
}
 
Last edited:
For each sample, you have 5 calls to transfer() (8 bits each) for a total of 40 bits. With the SPI clock rate 1.95 MHz, the bit transfer time is 20.5 us, and that does not include any of the overhead for begin/endTransaction(), manipulating the chip select, or calling transfer(). In addition, you have 10+20+6 = 36 us in calls to delayMicroseconds(), for a total of at least 56.5 us per sample, not including all of the overhead. To reach 30kSPS, you need to be under 1/30k = 33 us per sample. @MarkT mentioned that the chip has a SYNC input. If you want precise sampling at 30kSPS, you probably will need to use that signal rather than sending SYNC over SPI. Before you do that, though, you can work on reducing the time for each SPI read. First, if the ADS1256 is the only SPI device, you can call beginTransaction() once before the 30,000 reads, and call endTransaction() once at the end. You don't need to call them for every sample. Second, you can reduce or eliminate the calls to delayMicroseconds(). If you need them at all, they should be no more than 1 or 2 us. Third, you can replace the 5 calls to transfer() with a single call to the function below.

Code:
void SPIClass::transfer(const void * buf, void * retbuf, size_t count);

I would try those things first, and see how far that gets you. To get the very precise timing of samples, you will have to generate a signal to the SYNC pin at 30 kHz and then do the read on the DRDY interrupt. I also don't think you need to disable/enable interrupts.
 
Have you looked at this page? https://mattbilsky.com/mediawiki/index.php?title=Teensy_and_the_ADS1256

And this github repository? https://github.com/mbilsky/TeensyADS1256

There is a video that shows how to achieve over 30kSPS, without using the SYNC pin or DRDY interrupts, with a T3.2. The author uses 2.5 MHz for SPI clock rate, and there is a very good video explaining his process in achieving the full data rate. It should be possible to adapt this to T4.1 pretty easily. I have ordered the ADS1256 breakout board used for this project, and I should have it on Friday and be able to try it out over the weekend.
 
Have you looked at this page? https://mattbilsky.com/mediawiki/index.php?title=Teensy_and_the_ADS1256

And this github repository? https://github.com/mbilsky/TeensyADS1256

There is a video that shows how to achieve over 30kSPS, without using the SYNC pin or DRDY interrupts, with a T3.2. The author uses 2.5 MHz for SPI clock rate, and there is a very good video explaining his process in achieving the full data rate. It should be possible to adapt this to T4.1 pretty easily. I have ordered the ADS1256 breakout board used for this project, and I should have it on Friday and be able to try it out over the weekend.


Great, I will check this page! Sounds great if you will be able to try it during weekend. Me also.
Just for information, screenshots of my program in python - sampling 15kSPS is OK.

time-wave.jpg
fft1500.jpg
fft7500.jpg
 
I am testing https://github.com/mbilsky/TeensyADS1256
I have connected function generator (JDS6600). SPS30000, PGA64, 30k samples, + to AIN0 - to AIN1.
It seems to be a bit noisy. For example for 14kHz I can see in FFT lot of noise from 0-6000Hz, see pictures.
Any sugestions how to improve it?


FFT500-15000-14kHz.jpg

Time signal:
wave-sine14000Hz.jpg

Zoomed:
wave-sine14000Hz-zoom.jpg

This is 50Hz FFT:
FFT10-1500-50Hz.jpg

An time signal all and zoomed:

wave-sine50Hz.jpg

wave-sine50Hz-zoomed.jpg

The main code:
Code:
#include <SPI.h>

#define BUFFER_SIZE 30000
float adc_volt[BUFFER_SIZE];
double value = 0;
char inChar;
int32_t adc_raw = 0;

//(other stuff for getting the ADS1526 to work is in the next tab
#define ADS_RST_PIN    8 //ADS1256 reset pin
#define ADS_RDY_PIN    22 //ADS1256 data ready
#define ADS_CS_PIN    21 //ADS1256 chip select

/* 
    CLK  - pin 13
    DIN  - pin 11 (MOSI)
    DOUT - pin 12 (MISO)
*/


//put the ADC constants here

double resolution = 8388608.; //2^23-1

//this needs to match the setting in the ADC init function in the library tab
double Gain = 64.; //be sure to have a period 

double vRef = 5; //reference voltage

//we'll calculate this in setup
double bitToVolt = 0.;

void setup() {
  delay(1000);
  Serial.begin(2000000);
  Serial.println("booting");
  //initialize the ADS
  pinMode(ADS_CS_PIN, OUTPUT);

  pinMode(ADS_RDY_PIN, INPUT);
  pinMode(ADS_RST_PIN, OUTPUT);

  SPI.begin();

  initADS();
  Serial.println("done init");

  //determine the conversion factor
    //do some calculations for the constants
  bitToVolt = resolution*Gain/vRef;
  Serial.println(bitToVolt);
}

int32_t val1;
int32_t val2;
int32_t val3;

void loop() {
  
  SAMPLE_ADC();
 
if (Serial.available() > 0) {
      inChar = Serial.read();// get incoming byte:
      for(int i=0; i<BUFFER_SIZE; i++) {
        byte *b = (byte *)&adc_volt[i];
        Serial.write(b[0]);
        Serial.write(b[1]);
        Serial.write(b[2]);
        Serial.write(b[3]);
      }
  }


}

void SAMPLE_ADC()
{        
   for(int i=0;i <(BUFFER_SIZE); i++)              
  {    
    
    adc_raw = read_Value();
    //adc_raw = adc_raw / 10;
    adc_volt[i] = adc_raw / bitToVolt; 
    
 
   }
 }


ADS setup:
Code:
//my code
//note...this is written for TEENSY meaning I am using DigitalWriteFast to speed things up.
//thus the CS pin must be hard coded. in my case, this is pin 21 but you will have to change it for yourself if needed
//see this post about how to use: https://forum.pjrc.com/threads/24573-Speed-of-digitalRead-and-digitalWrite-with-Teensy3-0

//built up on the work of:
//https://github.com/Flydroid/ADS12xx-Library
//https://gist.github.com/dariosalvi78/f2e990b4317199d235bbf5963c3486ae
//https://github.com/adienakhmad/ADS1256


void initADS() {
  attachInterrupt(ADS_RDY_PIN, DRDY_Interuppt, FALLING);

  digitalWrite(ADS_RST_PIN, LOW);
  delay(10); // LOW at least 4 clock cycles of onboard clock. 100 microsecons is enough
  digitalWrite(ADS_RST_PIN, HIGH); // now reset to deafult values

  delay(1000);

  //now reset the ADS
  Reset();

  //let the system power up and stabilize (datasheet pg 24)
  delay(2000);
  //this enables the buffer which gets us more accurate voltage readings
 // SetRegisterValue(STATUS,B00110010);

  Serial.println(GetRegisterValue(STATUS));

  //next set the mux register
  //we are only trying to read differential values from pins 0 and 1. your needs may vary.
  //this is the default setting so we can just reset it
  SetRegisterValue(MUX,MUX_RESET); //set the mux register
   //B00001000 for single ended measurement

  //now set the ADCON register
  //set the PGA to 64x
  //you need to adjust the constants for the other ones according to datasheet pg 31 if you need other values
  SetRegisterValue(ADCON, PGA_64); //set the adcon register

  //next set the data rate
  SetRegisterValue(DRATE, DR_30000); //set the drate register

  //we're going to ignore the GPIO for now...

  //lastly, we need to calibrate the system

  //let it settle
  delay(2000);

  //then do calibration
  SendCMD(SELFCAL); //send the calibration command

  //then print out the values
  delay(5);

  Serial.print("OFC0: ");
  Serial.println(GetRegisterValue(OFC0));
  Serial.print("OFC1: ");
  Serial.println(GetRegisterValue(OFC1));
  Serial.print("OFC2: ");
  Serial.println(GetRegisterValue(OFC2));
  Serial.print("FSC0: ");
  Serial.println(GetRegisterValue(FSC0));
  Serial.print("FSC1: ");
  Serial.println(GetRegisterValue(FSC1));
  Serial.print("FSC2: ");
  Serial.println(GetRegisterValue(FSC2));
}

//function to read a value
//this assumes that we are not changing the mux action
int32_t read_Value() {
  int32_t adc_val;
  waitforDRDY(); // Wait until DRDY is LOW
  SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE1));
  digitalWriteFast(21, LOW); //Pull SS Low to Enable Communications with ADS1247
  //delayMicroseconds(5); // RD: Wait 25ns for ADC12xx to get ready
  SPI.transfer(RDATA); //Issue RDATA
  delayMicroseconds(7);
  adc_val |= SPI.transfer(NOP);
  //delayMicroseconds(10);
  adc_val <<= 8;
  adc_val |= SPI.transfer(NOP);
  //delayMicroseconds(10);
  adc_val <<= 8;
  adc_val |= SPI.transfer(NOP);
  //delayMicroseconds(5);
  digitalWriteFast(21, HIGH);
  SPI.endTransaction();

  if (adc_val > 0x7fffff) { //if MSB == 1
    adc_val = adc_val - 16777216; //do 2's complement, keep the sign this time!
  }

  return adc_val;
}


//library files

volatile int DRDY_state = HIGH;

void waitforDRDY() {
  while (DRDY_state) {
    continue;
  }
  noInterrupts();
  DRDY_state = HIGH;
  interrupts();
}

//Interrupt function
void DRDY_Interuppt() {
  DRDY_state = LOW;
}

long GetRegisterValue(uint8_t regAdress) {
  uint8_t bufr;
  digitalWriteFast(21, LOW);
  delayMicroseconds(10);
  SPI.transfer(RREG | regAdress); // send 1st command byte, address of the register
  SPI.transfer(0x00);     // send 2nd command byte, read only one register
  delayMicroseconds(10);
  bufr = SPI.transfer(NOP); // read data of the register
  delayMicroseconds(10);
  digitalWriteFast(21, HIGH);
  //digitalWrite(_START, LOW);
  SPI.endTransaction();
  return bufr;

}

void SendCMD(uint8_t cmd) {
  waitforDRDY();
  SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE1)); // initialize SPI with 4Mhz clock, MSB first, SPI Mode0
  digitalWriteFast(21, LOW);
  delayMicroseconds(10);
  SPI.transfer(cmd);
  delayMicroseconds(10);
  digitalWriteFast(21, HIGH);
  SPI.endTransaction();
}

void Reset() {
  SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE1)); // initialize SPI with  clock, MSB first, SPI Mode1
  digitalWriteFast(21, LOW);
  delayMicroseconds(10);
  SPI.transfer(RESET); //Reset
  delay(2); //Minimum 0.6ms required for Reset to finish.
  SPI.transfer(SDATAC); //Issue SDATAC
  delayMicroseconds(100);
  digitalWriteFast(21, HIGH);
  SPI.endTransaction();
}

void SetRegisterValue(uint8_t regAdress, uint8_t regValue) {

  uint8_t regValuePre = GetRegisterValue(regAdress);
  if (regValue != regValuePre) {
    //digitalWrite(_START, HIGH);
    delayMicroseconds(10);
    waitforDRDY();
    SPI.beginTransaction(SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE1)); // initialize SPI with SPI_SPEED, MSB first, SPI Mode1
    digitalWriteFast(21, LOW);
    delayMicroseconds(10);
    SPI.transfer(WREG | regAdress); // send 1st command byte, address of the register
    SPI.transfer(0x00);   // send 2nd command byte, write only one register
    SPI.transfer(regValue);         // write data (1 Byte) for the register
    delayMicroseconds(10);
    digitalWriteFast(21, HIGH);
    //digitalWrite(_START, LOW);
    if (regValue != GetRegisterValue(regAdress)) {   //Check if write was succesfull
      Serial.print("Write to Register 0x");
      Serial.print(regAdress, HEX);
      Serial.println(" failed!");
    }
    else {
      Serial.println("success");
    }
    SPI.endTransaction();

  }

}


Constants:
Code:
//using the definitions in this library: https://github.com/Flydroid/ADS12xx-Library

#define SPI_SPEED 1950000


/* For information to the register and settings see manual page (p..) */

/* ADS1248 Register (see p42 for Register Map) */

#define    STATUS    0x00 //Status Control Register 0
#define   MUX     0x01 //Multiplexer Control Register 0
#define   ADCON     0x02 //A/D Control Register 0
#define   DRATE   0x03 //A/D Data Rate Control Register 0
#define   IO        0X04 //GPIO Control Register 0
#define   OFC0    0x05 //Offset Calibration Coefficient Register 1
#define   OFC1    0x06 //Offset Calibration Coefficient Register 2
#define   OFC2    0x07 //Offset Calibration Coefficient Register 2
#define   FSC0    0x08 //Full scale Callibration Coefficient Register 0
#define   FSC1    0x09 //Full scale Callibration Coefficient Register 1
#define   FSC2    0x0A //Full scale Callibration Coefficient REgister 2

/*STATUS - Status Control Register 0 ( see p30)*/
/* BIT7 - BIT6 -  BIT5   -  BIT4   -  BIT3   -  BIT2   -  BIT1   -  BIT0 */
/* ID   - ID   -  ID     -  ID     -  ORDER  -  ACAL   -  BUFEN  -  DRDY */
#define STATUS_RESET 0x01 // Reset STATUS Register
/*Bits 7 - 4 ID3, ID2, ID1, ID0 Factory Programmed Identification Bits(Read Only)*/
/*ORDER1:0  Data Output Bit Order*/
#define ORDER_MSB B00000000 // Most significant Bit first (default)
#define ORDER_LSB B00001000//Least significant Bit first
/*Input data is always shifted in most significant byte and bit first. Output data is always shifted out most significant
byte first. The ORDER bit only controls the bit order of the output data within the byte.*/
/*ACAL1:0 Auto Calibration*/
#define ACAL_OFF B00000000 // Auto Calibration Disabled (default)
#define ACAL_ON  B00000100 // Auto Calibration Enabled
/*When Auto-Calibration is enabled, self-calibration begins at the completion of the WREG command that changes
the PGA (bits 0-2 of ADCON register), DR (bits 7-0 in the DRATE register) or BUFEN (bit 1 in the STATUS register)
values.*/
/*BUFEN1:0 Analog Input Buffer Enable*/
#define BUFEN_OFF B00000000 //Buffer Disabled (default)
#define BUFEN_ON  B00000010 //BUffer Enabled
/*DRDY1:0 Data Ready (Read Only) Duplicates the state of the DRDY pin*/

/* MUX - Multiplexer Control Register 0 (see p31 - bring together with bitwise OR | */
/* BIT7  - BIT6  -  BIT5   -  BIT4   -  BIT3   -  BIT2   -  BIT1   -  BIT0 */
/* PSEL3 - PSEL2 -  PSEL1  -  PSEL0  -  NSEL3  -  NSEL2   - NSEL1   - NSEL0 */
#define MUX_RESET 0x01      // Reset MUX0 Register
/* PSEL3:0 Positive input channel selection bits */
#define P_AIN0 B00000000 //(default)
#define P_AIN1 B00010000
#define P_AIN2 B00100000
#define P_AIN3 B00110000
#define P_AIN4 B01000000
#define P_AIN5 B01010000
#define P_AIN6 B01100000
#define P_AIN7 B01110000
#define P_AINCOM B10000000
/* NSEL3:0 Negativ input channel selection bits */
#define N_AIN0 B00000000
#define N_AIN1 B00000001 //(default)
#define N_AIN2 B00000010
#define N_AIN3 B00000011
#define N_AIN4 B00000100
#define N_AIN5 B00000101
#define N_AIN6 B00000110
#define N_AIN7 B00000111
#define N_AINCOM B00001000

/*ADCON - A/D Control Register 0 ( see p31)*/
/* BIT7 - BIT6   -  BIT5   -  BIT4   -  BIT3   -  BIT2   -  BIT1   -  BIT0 */
/* 0    - CLK1   -  CLK0   -  SDCS1  -  SDCS0  -  PGA2   -  PGA1   -  PAG0 */
#define ADCON_RESET 0x20 // Reset ADCON Register
/*CLK2:0 D0/CLKOUT Clock Out Rate Setting*/
#define CLK_OFF B00000000 //Clock Out off
#define CLK_1   B00100000 //Clock Out Frequency = fCLKIN (default)
#define CLK_2   B01000000 //Clock Out Frequency = fCLKIN/2
#define CLK_4   B01100000 //Clock Out Frequency = fCLKIN/4
/*When not using CLKOUT, it is recommended that it be turned off. These bits can only be reset using the RESET pin.*/
/*SDCS2:0 Sensor Detection Current Sources*/
#define SDCS_OFF B00000000//Sensor Detect Off (default)
#define SDCS_05  B00001000//Sensor Detect Current 0.5?A
#define SDCS_2   B00010000//Sensor Detect Current 2?A
#define SDCS_10  B00011000//Sensor Detect Current 10?A
/*The Sensor Detect Current Sources can be activated to verify the integrity of an external sensor supplying a signal to the
ADS1255/6. A shorted sensor produces a very small signal while an open-circuit sensor produces a very large signal.*/
/*PGA3:0 Programmable Gain Amplifier Setting*/
#define PGA_1 B00000000 //(default)
#define PGA_2
#define PGA_4
#define PGA_8 B00000011
#define PGA_16
#define PGA_32
#define PGA_64 B00100111

/*DRATE - A/D Data Rate Register 0 ( see p32)*/
/* BIT7 - BIT6   -  BIT5   -  BIT4   -  BIT3   -  BIT2   -  BIT1   -  BIT0 */
/* DR7  - DR6    -  DR5    -  DR4    -  DR3    -  DR2    -  DR1    -  DR0 */
#define DRATE_RESET 0xF0 // Reset DRATE Register
/*DR7:0 Data Rate Setting*/
#define DR_30000 B11110000 //30.000 SPS (default)
#define DR_15000 B11100000 //15.000 SPS
#define DR_7500  B11010000 //7.500 SPS
#define DR_3750  B11000000 //3.750 SPS
#define DR_2000  B10110000 //2.000 SPS
#define DR_1000  B10100001 //1.000 SPS
#define DR_500   B10010010 //500 SPS
#define DR_100   B10000010 //100 SPS
#define DR_60    B01110010 //60 SPS
#define DR_50    B01100011 //50 SPS
#define DR_30    B01010011 //30 SPS
#define DR_25    B01000011 //25 SPS
#define DR_15    B00110011 //15 SPS
#define DR_10    B00100011 //10 SPS
#define DR_5     B00010011 //5 SPS
#define DR2_5    B00000011 //2,5 SPS

/*IO - GPIO Control Register 0 ( see p32)*/
/* BIT7 - BIT6   -  BIT5   -  BIT4   -  BIT3   -  BIT2   -  BIT1   -  BIT0 */
/* DIR3 - DIR2   -  DIR1   -  DIR0   -  DIO3   -  DIO2   -  DIO1   -  DIO0 */
#define IO_RESET 0xE0 // Reset IO Register
/*DIR3 - Digital I/O Direction for Pin D3*/
#define DIR3_OUT B00000000 //D3 is an output
#define DIR_IN   B10000000 //D3 is an input (default)
/*DIR2 - Digital I/O Direction for Pin D3*/
#define DIR2_OUT B00000000 //D2 is an output
#define DIR2_IN   B01000000 //D2 is an input (default)
/*DIR1 - Digital I/O Direction for Pin D3*/
#define DIR1_OUT B00000000 //D1 is an output
#define DIR1_IN   B00100000 //D1 is an input (default)
/*DIR0 - Digital I/O Direction for Pin D3*/
#define DIR0_OUT B00000000 //D0/CLKOUT is an output
#define DIR0_IN   B00010000 //D0/CLKOUT is an input (default)
/*DIO3:0 Status of Digital I/O, Read Only*/

/* SPI COMMAND DEFINITIONS (p34) */
/*SYSTEM CONTROL */
#define   WAKEUP    0x00  //Exit Sleep Mode
#define   STANDBY   0xFD  //Enter Sleep Mode
#define   SYNC    0xFC    //Synchornize the A/D Conversion
#define   RESET   0xFE  //Reset To Power UP values
#define   NOP     0xFF  //No operation
/*DATA READ*/
#define   RDATA   0x01  //Read data once
#define   RDATAC    0x03  //Read data continously
#define   SDATAC    0x0F  //Stop reading data continously
/*READ REGISTER */
#define   RREG    0x10  //Read From Register
#define   WREG    0x50  //Write To Register
/*Calibration */
#define   SYSOCAL   0xF3  //System Offset Calibration
#define   SYSGCAL   0xF2  //System Gain Calibration
#define   SELFCAL     0xF0  //Self Offset Calibration
 
@frohr, can you please summarize the changes you made to the sketch after downloading it from github/mbilsky/TeensyADS1256?

I changed nothing for now - just SPI speed from 2500000 to 1950000 but the result seems the same.
Then I use function read_Value()

then I use:
Code:
void SAMPLE_ADC()
{        
   for(int i=0;i <(BUFFER_SIZE); i++)              
  {    
    adc_raw = read_Value();
    //adc_raw = adc_raw / 10;
    adc_volt[i] = adc_raw / bitToVolt; 
  }

and then loop:

Code:
void loop() {
  
  SAMPLE_ADC();
 
if (Serial.available() > 0) {
      inChar = Serial.read();
      for(int i=0; i<BUFFER_SIZE; i++) {
        byte *b = (byte *)&adc_volt[i];
        Serial.write(b[0]);
        Serial.write(b[1]);
        Serial.write(b[2]);
        Serial.write(b[3]);
      }
  }
}
 
Do you mean the result seems the same with both SPI rates? Are you reaching 30 kSPS? I think of that as step one.

Yes, seems the same - time signal is visually the same and fft is the same.
I measured time to read 30k samples = 999731 micros - the same for both SPI speeds. I think I need 1M us.

Code:
void SAMPLE_ADC()
{  
   int cas = 0;
   int cas2 = 0;
   cas = micros();      
   for(int i=0;i <(BUFFER_SIZE); i++)              
  {    
    adc_raw = read_Value();
    //adc_raw = adc_raw / 10;
    adc_volt[i] = adc_raw / bitToVolt; 
   }
   cas2 = micros();
   Serial.println(cas2-cas);
 }
 
Yes, seems the same - time signal is visually the same and fft is the same.
I measured time to read 30k samples = 999731 micros - the same for both SPI speeds. I think I need 1M us.

I don't know how the A/D is clocked, or the accuracy of either the Teensy clock or the A/D clock, but you probably shouldn't expect to do any better unless you switch to clocking the samples with a signal from Teensy. That might get your total sample time closer to exactly 1 second because you'll be using the same clock to both measure time and to clock the samples. You will not be able to see any difference in a 30k-sample FFT sampled over 0.999731 seconds versus 1.000000 seconds.
 
I second that - its essential for a sigma-delta chip like this has a good quartz-controlled clock - is it actually running at 7.68MHz?
 
Well you have the circuit... Can you post the schematic? Is this a module? Does it have a xtal on it?
 
This is an interesting question. I haven't spent much time studying the ADS1256 data sheet, but the (settable) data rate, in this case 30kSPS must be derived from the on-board clock. 7.68M / 30000 = 256, so if you have a very, very accurate and calibrated frequency counter, you could measure the frequency of the DRDY signal and the nominal 7.68 MHz. I think it's important to understand that when you use the Teensy to make your measurement, your sample rate is 99.97% accurate. Do you really think that isn't good enough? I recently worked on a project where we wanted to measure vibration at specific frequencies, and we had two ways of clocking the samples. One clocking method was more precise than the other. We collected data both ways and compared the results and we couldn't tell them apart. I was a little surprised, but very pleased to know that we could capture the great majority of the frequency information we wanted even if we couldn't control the sample rate as precisely as we would like. Our two samples rates were much further apart than 30000 versus 30008, so I'm pretty sure that any data you want that requires a sample rate of 30kSPS will be pretty much indistinguishable from what you get by sampling at 30008, or whatever it really is.
 
Yes, seems ok, but I am not sure if the wave / time signal is ok.
I want to see nice sine wave but I have:

wave-sine14000Hz-zoom.jpg


And another question - is possible make differential reading from ADXL1005? (I have eval board)

https://www.analog.com/media/en/technical-documentation/data-sheets/adxl1005.pdf

https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/eval-adxl1005.html#eb-overview

I wired 3,3V from Teensy to VDD, and AIN0 to Vout and AIN1 to GND - but no readings, just noise.
 
Just for comparison:

"Matt Bilski" code, 30k SPS:
new-code-time1.jpg
new-code-time-zoom.jpg

My code, 15kSPS (but does not work 30kSPS):
my-code-time1.jpg
my-code-time-zoom.jpg
 
@frohr, I can only guess that your configuration of the ADS1256 is different from the program @mbilsky. Can you please tell us how you produce the sine wave for test, and what are the settings (frequency, amplitude, offset). Also how is the signal source connected to the ADS1256?
 
@frohr, I can only guess that your configuration of the ADS1256 is different from the program @mbilsky. Can you please tell us how you produce the sine wave for test, and what are the settings (frequency, amplitude, offset). Also how is the signal source connected to the ADS1256?

I have signal generator JDS6600. I have sine wave, testing from 10-7400Hz for 15kSPS and from 10-14900Hz for 30kSPS. Connected to ADS by crocodile clips.
15kSPS - signal OK in all range.
30kSPS - signal "shifted" in all range - I think something with bad timing. For example FFT for 14000Hz is moved to 13996Hz. For 6000Hz I have FFT peak on 5998Hz. You can see time wave pictures above.

Problem is that I have no oscilloscope to check correct timing.


See pictures:
JDS6600.jpg
custom board1.jpg
custom board2.jpg
 
Since you are getting a good sine wave with your own sketch, I decided to start from there. The code below has my changes to your sketch. There are only 3 functional changes, which are listed below. There are some other cosmetic changes, such as defining more constants at the top of the program. You can switch between 15 kSPS and 30 kSPS by just changing your BUFFER_SIZE macro to 15000 or 30000, whichever you want. There are a few additional print statements. Please give this a try and see if it works. I did not do any testing with a function generator.

In START_SPI()
(1) the CS pin is initialized HIGH (disabled) instead of LOW (enabled)
(2) all commands are sent using the existing writeRegister() function
In READ_ADC()
(3) commented out the sending of SYNC and WAKEUP commands

Code:
#include <SPI.h>

// Define PIN
#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 STANDBY_CMD  0xFD  // begin standby mode

double v;
double g;
char inChar;

int32_t adc_raw = 0;
long minus;
volatile int DRDY_state = HIGH;

double VREF = 2.50;

#define BUFFER_SIZE 30000
float adc_volt[BUFFER_SIZE];
float adc_g[BUFFER_SIZE];

void setup()
{ 
  Serial.begin(2000000);
  while (!Serial) {} // JWP
  Serial.println( "ADS1256 test program" ); // JWP
  START_SPI();
  delay( 100 ); // JWP  
}

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

void loop()
{
  uint32_t t0 = micros();  
  SAMPLE_ADC();
  Serial.printf( "Read %1d samples in %1lu us\n", BUFFER_SIZE, micros()-t0 ); // JWP
  SAMPLE();  

  if (Serial.available() > 0) {
    inChar = Serial.read();
    for(int i=0; i<BUFFER_SIZE; i++) {
      byte *b = (byte *)&adc_volt[i];
      Serial.write(b[0]);
      Serial.write(b[1]);
      Serial.write(b[2]);
      Serial.write(b[3]);
    }
  }
}

void SAMPLE_ADC()
{        
   for(int i=0;i <(BUFFER_SIZE); i++)              
  {    
    READ_ADC();
    adc_volt[i] = v; 
   }
 }

void SAMPLE()
{ 
  for(int i=0;i <(BUFFER_SIZE); i++)              
  {      
    adc_volt[i] = adc_volt[i]; 
  }
} 

void READ_ADC()
{   
  waitforDRDY();
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1));                             
/*     
  SPI.transfer( SYNC_CMD );
  delayMicroseconds(3); // JWP (10);
   
  SPI.transfer( WAKEUP_CMD );
  delayMicroseconds(3); // JWP (20);   // Allow settling time  
*/   
  digitalWriteFast(CS_PIN, LOW);    
   
  SPI.transfer( RDATA_CMD ); 
  delayMicroseconds(6); // JWP (6);      
  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 ((minus = adc_raw >> 23 == 1))
  {
    adc_raw = adc_raw - 16777216; 
  } 
  v = (2.5 / 8388608) * adc_raw;   
}  
 
void waitforDRDY() 
{
  while (DRDY_state) 
 {
  continue;
 }
  noInterrupts();
  DRDY_state = HIGH;
  interrupts();
}

//Interrupt function
void DRDY_Interuppt()
{
  DRDY_state = LOW;
}

void START_SPI ()
{
  // JWP set up digital I/O pins
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH); // (CS_PIN, LOW); // JWP 
  pinMode(DRDY_PIN, INPUT);
  pinMode(RST_PIN, OUTPUT);
  
  // hardware reset 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 HIGH    nastaveni preruseni
  while (digitalRead(DRDY_PIN)) {}
   
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  
  digitalWrite(CS_PIN, LOW);
  delayMicroseconds(100);   
  
  attachInterrupt(DRDY_PIN, DRDY_Interuppt, FALLING);

  // Reset to Power-Up Values (FEh)
  SPI.transfer( RESET_CMD );
  delay(5);
  
  // nastaveni ADS1256 
  // 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 = 0x03; // 01h = 0000 0 0 1 1 => Most Significant Bit First,  Auto-Calibration Disabled, Anlog Input Buffer Enabled
  // byte status_data = 0x07; // 01h = 0000 0 1 1 1 => status: Most Significant Bit First, Auto-Calibration Enabled, Analog Input Buffer Enabled
  writeRegister( STATUS_REG, status_data ); // JWP
  delayMicroseconds(100);
  
  byte adcon_data = 0x21; // 0 01 00 001 => Clock Out Frequency = fCLKIN, Sensor Detect OFF, gain 2 7.68MHz  
  writeRegister( ADCON_REG, adcon_data ); // JWP
  delayMicroseconds(100);
  
  #if (BUFFER_SIZE == 15000)
  writeRegister( DRATE_REG, 0xE0 ); // 15 kHz  
  #elif (BUFFER_SIZE == 30000)
  writeRegister( DRATE_REG, 0xF0 ); // 30 kHz
  #endif
  delayMicroseconds(100);
  
  // First we need to write some registers to select an input:  
  writeRegister( MUX_REG, B00001000 ); 
  delayMicroseconds(100); 

  // JWP do auto-calibration of offset and gain
  SPI.transfer( SELFCAL_CMD );     
  delay(1000);
  
  SPI.endTransaction(); 
}
 
Back
Top