TI ADS1256 8 CH, 24 bit A/D

Wozzy

Well-known member
I recently purchased an ADS1256 Module for $17.
The TI ADS1256 is a 24 Bit 8 Channel ADC with SPI communication. It offers a PGA with up to 64x gain and acquisition rates up to 30kHz.
It does not support negative voltages relative to ground for analog input as I learned (disappointingly).

Here is an image of the module:
IMG_20180119_173046.jpg

I started working with the code developed by dariosalvi on the arduino forums here
I modified that code to get it working properly and added lots of comments and info from the datasheet.

I'm using it with a Teensy 3.2 running at 120 MHz and 2.5 MHz SPI
Currently compiling under Arduino 1.8.5 with Teensyduino 1.40

Here is the working code for reading all 8 single ended channels (AINx-AINCOM)
Code:
/* ADS1256, datasheet: http://www.ti.com/lit/ds/sbas288j/sbas288j.pdf
  compare: https://github.com/Flydroid/ADS12xx-Library/blob/master/ads12xx.cpp

  Reads all 8 Single Ended Channels AINx-AINCOM
  Arduino 1.8.5, Teensyduino 1.40
  
    Connections to Teensy 3.2

    ADS1256       Teensy 3.2
    DVDD (5V)   - 5V
    DGND (GND}  - GND
    SCLK        - pin 13 (SCK)
    DIN         - pin 11 (MOSI)
    DOUT        - pin 12 (MISO)
    DRDY        - pin 9
    CS          - pin 10 (CS)
    RESET       - pin 8 (or tie HIGH?)
*/

#define cs 10 // chip select
#define rdy 9 // data ready, input
#define rst 8 // may omit

#define SPISPEED 2500000   // Teensy 3.2 @120 mhz

#include <SPI.h>

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(500);
  SPI.begin(); //start the spi-bus
  delay(500);

  //init
  while (digitalRead(rdy)) {}  // wait for ready_line to go low
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(100);

  //Reset to Power-Up Values (FEh)
  SPI.transfer(0xFE);
  delay(5);

/******************************************************************************************************************
STATUS : STATUS REGISTER (ADDRESS 00h)
Reset Value = x1h
  BIT 7    BIT 6    BIT 5    BIT 4    BIT 3    BIT 2    BIT 1    BIT 0
  ID       ID       ID       ID       ORDER    ACAL     BUFEN    DRDY
Bits 7-4 ID3, ID2, ID1, ID0 Factory Programmed Identification Bits (Read Only)
Bit 3 ORDER: Data Output Bit Order
0 = Most Significant Bit First (default)
1 = 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.
Bit 2 ACAL: Auto-Calibration
0 = Auto-Calibration Disabled (default)
1 = 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.
Bit 1 BUFEN: Analog Input Buffer Enable
0 = Buffer Disabled (default)
1 = Buffer Enabled
Bit 0 DRDY: Data Ready (Read Only)
This bit duplicates the state of the DRDY pin.
**************************************************************************************************************/
  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);
  
/***************************************************************************************************************
ADCON: A/D Control Register (Address 02h)
Reset Value = 20h
BIT 7   BIT 6   BIT 5   BIT 4   BIT 3   BIT 2   BIT 1   BIT 0
0       CLK1    CLK0    SDCS1   SDCS0   PGA2    PGA1    PGA0
Bit 7 Reserved, always 0 (Read Only)
Bits 6-5 CLK1, CLK0: D0/CLKOUT Clock Out Rate Setting
00 = Clock Out OFF
01 = Clock Out Frequency = fCLKIN (default)
10 = Clock Out Frequency = fCLKIN/2
11 = 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.
Bits 4-2 SDCS1, SCDS0: Sensor Detect Current Sources
00 = Sensor Detect OFF (default)
01 = Sensor Detect Current = 0.5μA
10 = Sensor Detect Current = 2μA
11 = 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.
Bits 2-0 PGA2, PGA1, PGA0: Programmable Gain Amplifier Setting

PGA SETTING
00h = 000 = 1   ±5V(default)
01h = 001 = 2   ±2.5V
02h = 010 = 4   ±1.25V
03h = 011 = 8   ±0.625V
04h = 100 = 16  ±312.5mV
05h = 101 = 32  ±156.25mV
06h = 110 = 64  ±78.125mV
07h = 111 = 64  ±78.125mV
**********************************************************************************************************************/
  byte adcon_reg = 0x02; //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);

/*********************************************************************************************************
DRATE: A/D Data Rate (Address 03h)
Reset Value = F0h
BIT 7    BIT 6    BIT 5    BIT 4    BIT 3    BIT 2    BIT 1    BIT 0
DR7     DR6       DR5      DR4      DR3      DR2      DR1      DR0
The 16 valid Data Rate settings are shown below. Make sure to select a valid setting as the invalid settings may produce
unpredictable results.
Bits 7-0 DR[7: 0]: Data Rate Setting(1)
F0h = 11110000 = 30,000SPS (default)
E0h = 11100000 = 15,000SPS
D0h = 11010000 = 7,500SPS
C0h = 11000000 = 3,750SPS
B0h = 10110000 = 2,000SPS
A1h = 10100001 = 1,000SPS
92h = 10010010 = 500SPS
82h = 10000010 = 100SPS
72h = 01110010 = 60SPS
63h = 01100011 = 50SPS
53h = 01010011 = 30SPS
43h = 01000011 = 25SPS
33h = 00110011 = 15SPS
23h = 00100011 = 10SPS
13h = 00010011 = 5SPS
03h = 00000011 = 2.5SPS
(1) for fCLKIN = 7.68MHz. Data rates scale linearly with fCLKIN. 
 ***********************************************************************************************/
  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);

  // Perform Offset and Gain Self-Calibration (F0h)
  SPI.transfer(0xF0);     
  delay(400);
  digitalWrite(cs, HIGH);
  SPI.endTransaction();
  
  while (!Serial && (millis ()  <=  5000));  // WAIT UP TO 5000 MILLISECONDS FOR SERIAL OUTPUT CONSOLE
  Serial.println("configured, starting");
  Serial.println("");
  Serial.println("AIN0-AINCOM  AIN1-AINCOM  AIN2-AINCOM  AIN3-AINCOM  AIN4-AINCOM  AIN5-AINCOM  AIN6-AINCOM  AIN7-AINCOM");
}


void loop()
{

  //Single ended Measurements
  unsigned long adc_val[8] = {0,0,0,0,0,0,0,0}; // store readings in array
  byte mux[8] = {0x08,0x18,0x28,0x38,0x48,0x58,0x68,0x78};
  
  int i = 0;

  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(2);
  
 /***************************************************************************
 Settling Time Using the Input Multiplexer
The most efficient way to cycle through the inputs is to
change the multiplexer setting (using a WREG command
to the multiplexer register MUX) immediately after DRDY
goes low. Then, after changing the multiplexer, restart the
conversion process by issuing the SYNC and WAKEUP
commands, and retrieve the data with the RDATA
command. Changing the multiplexer before reading the
data allows the ADS1256 to start measuring the new input
channel sooner. Figure 19 demonstrates efficient input
cycling. There is no need to ignore or discard data while
cycling through the channels of the input multiplexer
because the ADS1256 fully settles before DRDY goes low,
indicating data is ready.

Step 1: When DRDY goes low, indicating that data is ready for retrieval,
update the multiplexer register MUX using the WREG command. For example,
setting MUX to 23h gives AINP = AIN2, AINN = AIN3.

Step 2: Restart the conversion process by issuing a SYNC command
immediately followed by a WAKEUP command.
Make sure to follow timing specification t11 between commands.

Step 3: Read the data from the previous conversion using the RDATA command.

Step 4: When DRDY goes low again, repeat the cycle by first
updating the multiplexer register, then reading the previous data.
***************************************************************************************/
  for (i=0; i <= 7; i++){         // read all 8 Single Ended Channels AINx-AINCOM
  byte channel = mux[i];             // analog in channels # 
  
  while (digitalRead(rdy)) {} ;                          

/*
 WREG: Write to Register
Description: Write to the registers starting with the register specified as part of the command. The number of registers that
will be written is one plus the value of the second byte in the command.
1st Command Byte: 0101 rrrr where rrrr is the address to the first register to be written.
2nd Command Byte: 0000 nnnn where nnnn is the number of bytes to be written – 1.
Data Byte(s): data to be written to the registers. 
 */
  //byte data = (channel << 4) | (1 << 3); //AIN-channel and AINCOM   // ********** Step 1 **********
  //byte data = (channel << 4) | (1 << 1)| (1); //AIN-channel and AINCOM   // ********** Step 1 **********
  //Serial.println(channel,HEX);
  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);

  //while (!digitalRead(rdy)) {} ;
  //WAKEUP 0000 0000
  SPI.transfer(0x00);
  delayMicroseconds(250);   // Allow settling time

/*
MUX : Input Multiplexer Control Register (Address 01h)
Reset Value = 01h
BIT 7    BIT 6    BIT 5    BIT 4    BIT 3    BIT 2    BIT 1    BIT 0
PSEL3    PSEL2    PSEL1    PSEL0    NSEL3    NSEL2    NSEL1    NSEL0
Bits 7-4 PSEL3, PSEL2, PSEL1, PSEL0: Positive Input Channel (AINP) Select
0000 = AIN0 (default)
0001 = AIN1
0010 = AIN2 (ADS1256 only)
0011 = AIN3 (ADS1256 only)
0100 = AIN4 (ADS1256 only)
0101 = AIN5 (ADS1256 only)
0110 = AIN6 (ADS1256 only)
0111 = AIN7 (ADS1256 only)
1xxx = AINCOM (when PSEL3 = 1, PSEL2, PSEL1, PSEL0 are “don’t care”)
NOTE: When using an ADS1255 make sure to only select the available inputs.

Bits 3-0 NSEL3, NSEL2, NSEL1, NSEL0: Negative Input Channel (AINN)Select
0000 = AIN0
0001 = AIN1 (default)
0010 = AIN2 (ADS1256 only)
0011 = AIN3 (ADS1256 only)
0100 = AIN4 (ADS1256 only)
0101 = AIN5 (ADS1256 only)
0110 = AIN6 (ADS1256 only)
0111 = AIN7 (ADS1256 only)
1xxx = AINCOM (when NSEL3 = 1, NSEL2, NSEL1, NSEL0 are “don’t care”)
NOTE: When using an ADS1255 make sure to only select the available inputs.
 */

  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 <= 7; i++){   // Single ended Measurements 
  if(adc_val[i] > 0x7fffff){   //if MSB == 1
    adc_val[i] = adc_val[i]-16777216; //do 2's complement
  }

  Serial.print(adc_val[i]);   // Raw ADC integer value +/- 23 bits
  Serial.print("      ");
  }
  Serial.println();
  delay(250);

}

And here is the working code for reading all 4 differential channel pairs (AIN0-AIN1, AIN2-AIN3, AIN4-AIN5, AIN6-AIN7)
Code:
/* ADS1256, datasheet: http://www.ti.com/lit/ds/sbas288j/sbas288j.pdf
  compare: https://github.com/Flydroid/ADS12xx-Library/blob/master/ads12xx.cpp

Reads all 4 differential channel pairs AIN0-AIN1, AIN2-AIN3, AIN4-AIN5, AIN6-AIN7
Arduino 1.8.5, Teensyduino 1.40

    Connections to Teensy 3.2

    ADS1256       Teensy 3.2
    DVDD (5V)   - 5V
    DGND (GND}  - GND
    SCLK        - pin 13 (SCK)
    DIN         - pin 11 (MOSI)
    DOUT        - pin 12 (MISO)
    DRDY        - pin 9
    CS          - pin 10 (CS)
    RESET       - pin 8 (or tie HIGH?)
*/

#define cs 10 // chip select
#define rdy 9 // data ready, input
#define rst 8 // may omit

#define SPISPEED 2500000   // Teensy 3.2 @120 mhz

#include <SPI.h>

void setup()
{
  Serial.begin(9600);
  
  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(500);
  SPI.begin(); //start the spi-bus
  delay(500);

  //init
  while (digitalRead(rdy)) {}  // wait for ready_line to go low
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(100);

  //Reset to Power-Up Values (FEh)
  SPI.transfer(0xFE);
  delay(5);

/******************************************************************************************************************
STATUS : STATUS REGISTER (ADDRESS 00h)
Reset Value = x1h
  BIT 7    BIT 6    BIT 5    BIT 4    BIT 3    BIT 2    BIT 1    BIT 0
  ID       ID       ID       ID       ORDER    ACAL     BUFEN    DRDY
Bits 7-4 ID3, ID2, ID1, ID0 Factory Programmed Identification Bits (Read Only)
Bit 3 ORDER: Data Output Bit Order
0 = Most Significant Bit First (default)
1 = 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.
Bit 2 ACAL: Auto-Calibration
0 = Auto-Calibration Disabled (default)
1 = 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.
Bit 1 BUFEN: Analog Input Buffer Enable
0 = Buffer Disabled (default)
1 = Buffer Enabled
Bit 0 DRDY: Data Ready (Read Only)
This bit duplicates the state of the DRDY pin.
**************************************************************************************************************/
  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);
  
/***************************************************************************************************************
ADCON: A/D Control Register (Address 02h)
Reset Value = 20h
BIT 7   BIT 6   BIT 5   BIT 4   BIT 3   BIT 2   BIT 1   BIT 0
0       CLK1    CLK0    SDCS1   SDCS0   PGA2    PGA1    PGA0
Bit 7 Reserved, always 0 (Read Only)
Bits 6-5 CLK1, CLK0: D0/CLKOUT Clock Out Rate Setting
00 = Clock Out OFF
01 = Clock Out Frequency = fCLKIN (default)
10 = Clock Out Frequency = fCLKIN/2
11 = 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.
Bits 4-2 SDCS1, SCDS0: Sensor Detect Current Sources
00 = Sensor Detect OFF (default)
01 = Sensor Detect Current = 0.5μA
10 = Sensor Detect Current = 2μA
11 = 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.
Bits 2-0 PGA2, PGA1, PGA0: Programmable Gain Amplifier Setting

PGA SETTING
00h = 000 = 1   ±5V(default)
01h = 001 = 2   ±2.5V
02h = 010 = 4   ±1.25V
03h = 011 = 8   ±0.625V
04h = 100 = 16  ±312.5mV
05h = 101 = 32  ±156.25mV
06h = 110 = 64  ±78.125mV
07h = 111 = 64  ±78.125mV
**********************************************************************************************************************/
  byte adcon_reg = 0x02; //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);

/*********************************************************************************************************
DRATE: A/D Data Rate (Address 03h)
Reset Value = F0h
BIT 7    BIT 6    BIT 5    BIT 4    BIT 3    BIT 2    BIT 1    BIT 0
DR7     DR6       DR5      DR4      DR3      DR2      DR1      DR0
The 16 valid Data Rate settings are shown below. Make sure to select a valid setting as the invalid settings may produce
unpredictable results.
Bits 7-0 DR[7: 0]: Data Rate Setting(1)
F0h = 11110000 = 30,000SPS (default)
E0h = 11100000 = 15,000SPS
D0h = 11010000 = 7,500SPS
C0h = 11000000 = 3,750SPS
B0h = 10110000 = 2,000SPS
A1h = 10100001 = 1,000SPS
92h = 10010010 = 500SPS
82h = 10000010 = 100SPS
72h = 01110010 = 60SPS
63h = 01100011 = 50SPS
53h = 01010011 = 30SPS
43h = 01000011 = 25SPS
33h = 00110011 = 15SPS
23h = 00100011 = 10SPS
13h = 00010011 = 5SPS
03h = 00000011 = 2.5SPS
(1) for fCLKIN = 7.68MHz. Data rates scale linearly with fCLKIN. 
 ***********************************************************************************************/
  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);

  // Perform Offset and Gain Self-Calibration (F0h)
  SPI.transfer(0xF0);     
  delay(400);
  digitalWrite(cs, HIGH);
  SPI.endTransaction();

  while (!Serial && (millis ()  <=  5000));  // WAIT UP TO 5000 MILLISECONDS FOR SERIAL OUTPUT CONSOLE
  Serial.println("configured, starting");
  Serial.println("");
  Serial.println("AIN0-AIN1    AIN2-AIN3    AIN4-AIN5    AIN6-AIN7");
}

void loop(){

  // Differential Measurements
  long adc_val[4] = {0,0,0,0}; // store readings in array
  byte mux[4] = {0x01,0x23,0x45,0x67};
  
  int i = 0;

  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(2);
  
 /***************************************************************************
 Settling Time Using the Input Multiplexer
The most efficient way to cycle through the inputs is to
change the multiplexer setting (using a WREG command
to the multiplexer register MUX) immediately after DRDY
goes low. Then, after changing the multiplexer, restart the
conversion process by issuing the SYNC and WAKEUP
commands, and retrieve the data with the RDATA
command. Changing the multiplexer before reading the
data allows the ADS1256 to start measuring the new input
channel sooner. Figure 19 demonstrates efficient input
cycling. There is no need to ignore or discard data while
cycling through the channels of the input multiplexer
because the ADS1256 fully settles before DRDY goes low,
indicating data is ready.

Step 1: When DRDY goes low, indicating that data is ready for retrieval,
update the multiplexer register MUX using the WREG command. For example,
setting MUX to 23h gives AINP = AIN2, AINN = AIN3.

Step 2: Restart the conversion process by issuing a SYNC command
immediately followed by a WAKEUP command.
Make sure to follow timing specification t11 between commands.

Step 3: Read the data from the previous conversion using the RDATA command.

Step 4: When DRDY goes low again, repeat the cycle by first
updating the multiplexer register, then reading the previous data.
***************************************************************************************/
  for (i=0; i <= 3; i++){            // read all 4 differential channel pairs AIN0-AIN1, AIN2-AIN3, AIN4-AIN5, AIN6-AIN7
  byte channel = mux[i];             // analog in channels # 
  
  while (digitalRead(rdy)) {} ;                          

/*
 WREG: Write to Register
Description: Write to the registers starting with the register specified as part of the command. The number of registers that
will be written is one plus the value of the second byte in the command.
1st Command Byte: 0101 rrrr where rrrr is the address to the first register to be written.
2nd Command Byte: 0000 nnnn where nnnn is the number of bytes to be written – 1.
Data Byte(s): data to be written to the registers. 
 */
  //byte data = (channel << 4) | (1 << 3); //AIN-channel and AINCOM   // ********** Step 1 **********
  //byte data = (channel << 4) | (1 << 1)| (1); //AIN-channel and AINCOM   // ********** Step 1 **********
  //Serial.println(channel,HEX);
  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);

  //while (!digitalRead(rdy)) {} ;
  //WAKEUP 0000 0000
  SPI.transfer(0x00);
  delayMicroseconds(250);   // Allow settling time

/*
MUX : Input Multiplexer Control Register (Address 01h)
Reset Value = 01h
BIT 7    BIT 6    BIT 5    BIT 4    BIT 3    BIT 2    BIT 1    BIT 0
PSEL3    PSEL2    PSEL1    PSEL0    NSEL3    NSEL2    NSEL1    NSEL0
Bits 7-4 PSEL3, PSEL2, PSEL1, PSEL0: Positive Input Channel (AINP) Select
0000 = AIN0 (default)
0001 = AIN1
0010 = AIN2 (ADS1256 only)
0011 = AIN3 (ADS1256 only)
0100 = AIN4 (ADS1256 only)
0101 = AIN5 (ADS1256 only)
0110 = AIN6 (ADS1256 only)
0111 = AIN7 (ADS1256 only)
1xxx = AINCOM (when PSEL3 = 1, PSEL2, PSEL1, PSEL0 are “don’t care”)
NOTE: When using an ADS1255 make sure to only select the available inputs.

Bits 3-0 NSEL3, NSEL2, NSEL1, NSEL0: Negative Input Channel (AINN)Select
0000 = AIN0
0001 = AIN1 (default)
0010 = AIN2 (ADS1256 only)
0011 = AIN3 (ADS1256 only)
0100 = AIN4 (ADS1256 only)
0101 = AIN5 (ADS1256 only)
0110 = AIN6 (ADS1256 only)
0111 = AIN7 (ADS1256 only)
1xxx = AINCOM (when NSEL3 = 1, NSEL2, NSEL1, NSEL0 are “don’t care”)
NOTE: When using an ADS1255 make sure to only select the available inputs.
 */

    
  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 <= 3; i++){
  if(adc_val[i] > 0x7fffff){   //if MSB == 1
    adc_val[i] = adc_val[i]-16777216; //do 2's complement
  }

  Serial.print(adc_val[i]);   // Raw ADC integer value +/- 23 bits
  Serial.print("      ");
  }
  Serial.println();
  delay(250);

}

Next I want to try to get the Data Ready signal working correctly using interrupts on the Teensy.
All in all it's not a bad ADC module. The AINCOM on the module is tied to ground so it can't be used as a floating differential channel even though it's independently muxed to the ADC.
Also you can't use all 24 bits if you have a +/-5V signal without shifting and attenuating the signals. otherwise this module works very well and is well designed with seemingly high quality components.

There's also a library by Flydroid with many features for the ADS1256
 
Last edited:
Updated Code for ADS1256 and Teensy

Hi Wozzy (and all)!

I have been working a lot with the Teensy and ADS1256 lately and have compiled my own library/code using the work of the others listed in your post plus some interpretation of the datasheet myself. It implements interrupt-based data ready and seems to get the maximum speed available out of the chip.

Videos on how it works and the code are available on my website here: https://mattbilsky.com/mediawiki/index.php?title=Teensy_and_the_ADS1256

Hopefully this helps!
 
Curious if you find this ADS1256 chip occasionally resets when you didn't want it to. Among other things, the reset means it returns to its default of 30 kHz sampling rate, so that can be a problem if you were using a different rate. It can be reset by the hardware RESET/ pin and by the software reset command and by a special pattern on the SPI clock line, so that's three different ways. In my circuit with my code that is maybe one too many :)
 
Thoughts...

Curious if you find this ADS1256 chip occasionally resets when you didn't want it to. Among other things, the reset means it returns to its default of 30 kHz sampling rate, so that can be a problem if you were using a different rate. It can be reset by the hardware RESET/ pin and by the software reset command and by a special pattern on the SPI clock line, so that's three different ways. In my circuit with my code that is maybe one too many :)

I personally haven't had the issue, but I also haven't been collecting data for more than a few minutes at a time. My colleagues who have started to implement the ADS in their data systems said they had it reset. Their solution was to just eliminate using the gain.

I would be curious if you could add a line of code that would detect the data doing something weird then would poll the register values to see if they changed. Then maybe put a system that changed the register values back to the proper gain.

We have a grant to investigate using these tools more (i.e. we're hiring a student to do this for us) and we can see in the next few months if we can generate a solution ourselves.

Thanks! Matt
 
I'm using the ADS1256 code from Matt Bilsky at https://github.com/mbilsky/TeensyADS1256 on a Teensy 4 and it is working well at the moment. In the past I used a different hardware setup, using T3.2 that sometimes had a reset issue with many-hour-long acquisition sessions. With the new setup I have not (yet) seen that. It may have just been some wiring problem with the old configuration, or something that happens only with long term acquisition.

A question: I want to acquire two input channels with very nearly simultaneous sampling. This chip has 8 inputs, but only one ADC and multiplexing takes time, so instead of just switching between inputs, I want to use two separate ADS1256 boards. I don't see any hardware reason why not, but as it is, I think the Bilsky code is not setup to address more than one hardware chip. Has anyone tried to use two ADS1256 boards, and if so what kind of code did you use?
 
Just in case of interest, I did modify the Flydroid library to allow using two different ADS1256 devices on one Teensy. It "usually" works, not yet 100% confident it always does.

Separate from the dual-device change, I discovered that I needed a significantly longer delay between dropping _CS low, and sending the read command in ads12xx::GetConversion(). I am using 50 microseconds delay now and SPI clock = 960kHz. So far OK after 3M readings (1.7 hours at 500 Hz). The Flydroid library used 10 usec delay, which after a while (maybe thousands of readings) may cause a strange mode where the output word is left-shifted 1 or 2 bytes. The TI data sheet for ADS1256 on p.6, figure 1 claims that there is no setup time, specifically that the minimum interval "t3" from CS low to first SCLK is 0 nsec, but that is not my observation.

2019-09-14_ADS1256-setup.png

EDIT: Nope. Flaked out again after 3.8 million reads. Sigh.
 
Last edited:
Here is my code (modified library + sample dual-ADC application) which has now run 8 hours sampling two ADC chips, each one at 500 Hz, without any obvious error: https://github.com/jbeale1/ADS1256_DUAL

This 8-hour run (over 14 million reads) was using a regular Teensy 4 board and Teensyduino 1.47.
The previous fault after 3.8 million reads was using a T4-Beta1 board and Teensyduino 1.46-beta9.

They ran the same code, although with a fault that rare I'm still unsure of the root cause of the problem.
You could also describe my breadboard wiring setup as "haphazard".

20190915_DualADC.jpg
 
Last edited:
Hi All,

I'm glad to see this thread is alive and well.

For the past couple of months I've had a grad student, Tyler, at Lehigh working on making the ADS1256 library more robust (and an actual object-based library). He's nearly finished then we will be releasing it.

I have also experimented in the past with running 2 ADS on the same SPI bus on a teensy 3.2 getting about 20KSPS from each board. We hope to include this in the new library.

We are also running robustness tests (like your 8 hour tests).

I hope to release the code in the next week or two along with a video discussing the progress.

Then we are moving on to other ADS boards. This is all part of a grant I wrote to support the development of open source engineering tools. Lots of exciting things to come!
 
...Then we are moving on to other ADS boards. This is all part of a grant I wrote to support the development of open source engineering tools. Lots of exciting things to come!

That's a terrific project that can greatly benefit the open source community.

I'd really love to see an open source hardware & software solution for an instrument grade DAC with voltage and 4-20ma output capability like the Analog Devices AD5755
 
Hi All,

I purchased ADS1256 board and trying to interface with ESP32 controller. I used the code shared by Wozzy for single ended measurements. Im using 4 channel for measurements and remaining channels are grounded. My goal is to plot FFT peak for the given input signal.

As a testing phase I'm inputting a known sinusoidal signal from function generator of 1.0Vp-p with 0.5Vdc offset with varying frequency in the range of 1 to 200Hz.

As I input the signal, above 40 Hz, the analog signal read by ESP32 controller through SPI protocol seems to be distorted when the signal is reconstructed using data samples.

Please help me out , how I can rectify this problem.

Im attaching the code

Code:
#include <SPI.h>

#define cs 5 // chip select
#define rdy 25 // data ready, input
#define rst 26 // may omit

#define SPISPEED 2500000   // Teensy 3.2 @120 mhz
#define SAMPLES 2048

float value1;
float value2;
float value3;
float value4;

float  Read_Data;

float x;
float y;
float z;
float j;

double k[SAMPLES];
double a[SAMPLES];
double b[SAMPLES];
double c[SAMPLES];

int i;
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(500);
  SPI.begin(); //start the spi-bus
  delay(500);

  //init
  while (digitalRead(rdy)) {}  // wait for ready_line to go low
  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(100);

  //Reset to Power-Up Values (FEh)
  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);

  // Perform Offset and Gain Self-Calibration (F0h)
  SPI.transfer(0xF0);     
  delay(400);
  digitalWrite(cs, HIGH);
  SPI.endTransaction();
  
 // while (!Serial && (millis ()  <=  5000));  // WAIT UP TO 5000 MILLISECONDS FOR SERIAL OUTPUT CONSOLE
//  Serial.println("configured, starting");
//  Serial.println("");
//  Serial.println("AIN0-AINCOM  AIN1-AINCOM  AIN2-AINCOM  AIN3-AINCOM  AIN4-AINCOM  AIN5-AINCOM  AIN6-AINCOM  AIN7-AINCOM");
}

void loop()
{

  saveinarray();
  Serial.println("Iteration completed");
  delay(10);
}

void test()
{

  //Single ended Measurements
  unsigned long adc_val[4] = {0,0,0,0}; // store readings in array
  byte mux[4] = {0x08,0x18,0x28,0x38};
  int i = 0;

  SPI.beginTransaction(SPISettings(SPISPEED, MSBFIRST, SPI_MODE1)); // start SPI
  digitalWrite(cs, LOW);
  delayMicroseconds(2);
  

  for (i=0; i <=3; i++){         // read all 8 Single Ended Channels AINx-AINCOM
  byte channel = mux[i];             // analog in channels # 
  
  while (digitalRead(rdy)) {} ;                          


  //byte data = (channel << 4) | (1 << 3); //AIN-channel and AINCOM   // ********** Step 1 **********
  //byte data = (channel << 4) | (1 << 1)| (1); //AIN-channel and AINCOM   // ********** Step 1 **********
  //Serial.println(channel,HEX);
  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);

  //while (!digitalRead(rdy)) {} ;
  //WAKEUP 0000 0000
  SPI.transfer(0x00);
  delayMicroseconds(210);   // 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();

  for (i=0; i <=3; i++){   // Single ended Measurements 
  if(adc_val[i] > 0x7fffff){   //if MSB == 1
    adc_val[i] = adc_val[i]-16777216; //do 2's complement
   
  }
  
 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;
 value4 = adc_val[3] *0.0000002980232;

x = (value1-0.5) * 5;
y = (value2-0.5) * 5;
z = (value3-0.5) * 5;
j = (value4-0.5) * 5;

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

void saveinarray()
{
  for( i=0; i<SAMPLES; i++)
  {
    test();
    k[i]=x;
    a[i]=y;
    b[i]=z;
    c[i]=j;
    Serial.print(k[i]);
    Serial.print("\t");
    Serial.print(a[i]);
    Serial.print("\t");
    Serial.print(b[i]);
    Serial.print("\t");
    Serial.println(c[i]);
    
  }
  
}

Screenshot of reconstructed signal with a frequency of 50Hz. dt used is 0.0025
50hz.jpg
 
Back
Top