This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

ADS1220: ADS1220 issue: PGA is dead?

Part Number: ADS1220

Hello!

Tell me, please, what could be the problem.
I have a issue getting data if the PGA is activated.
Register configuration by default, I only change the gain.

To registers 0-3, values are written and read correctly.
I connect the voltage from the battery to the input through a divider, about
20.6 mV, but I get this value only if the PGA bypass is on.
Values 20.65335, 20.65421, 20.65333, 20.65112, etc.

If the PGA bypass is turned off and set PGA to anything gain, I get very small floating values (0.00012, 0.00015, -0.00010, -0.00013, etc)
Maybe I'm doing something wrong?

Below I attached schematic, logic analizer screenshot, arduino code and lib
what I use.

Code

#include "ADS1220.h"
ADS1220 adc;
#define PGA          1
#define VREF         2.048
#define VFSR         VREF/PGA
#define FULL_SCALE   (((long int)1<<23)-1)


void setup() {
  Serial.begin(9600);
  adc.begin(9, 8);
  adc.setGain(PGA);
  adc.setConversionMode(1); //continious mode
  adc.setMultiplexer(0X00);
  adc.setPGAbypass(true); //PGA OFF

  uint8_t Buff = 0;
  Buff = adc.readRegister(0);
  Serial.print("REG0 = ");
  Serial.println(Buff, BIN);
  Buff = adc.readRegister(1);
  Serial.print("REG1 = ");
  Serial.println(Buff, BIN);
  Buff = adc.readRegister(2);
  Serial.print("REG2 = ");
  Serial.println(Buff, BIN);
  Buff = adc.readRegister(3);
  Serial.print("REG3 = ");
  Serial.println(Buff, BIN);
}

void loop() {
  char outstr[10];

  if (adc.isDataReady()) {
    long test = adc.readADC();
    float a = convertToMilliV(test);
    dtostrf(a, 10, 5, outstr);
    Serial.println(outstr);
  }
  delay(300);
}

float convertToMilliV(int32_t i32data)
{
  return (float)((i32data * VFSR * 1000) / FULL_SCALE);
}

CPP file

#include "ADS1220.h" #include "Arduino.h" #include "SPI.h" ADS1220::ADS1220() { } void ADS1220::writeRegister(uint8_t address, uint8_t value) { digitalWrite(ADS1220_CS_PIN, LOW); SPI.transfer(CMD_WREG|(address<<2)); // What not setting num bytes? SPI.transfer(value); startSync(); // Send start/sync for continuous conversion mode delayMicroseconds(1); // Delay a minimum of td(SCCS) digitalWrite(ADS1220_CS_PIN, HIGH); } uint8_t ADS1220::readRegister(uint8_t address) { digitalWrite(ADS1220_CS_PIN,LOW); SPI.transfer(CMD_RREG|(address<<2)); // What not setting num bytes? uint8_t data = SPI.transfer(SPI_MASTER_DUMMY); digitalWrite(ADS1220_CS_PIN,HIGH); return data; } void ADS1220::begin(uint8_t cs_pin, uint8_t drdy_pin) { // Set pins up ADS1220_CS_PIN = cs_pin; ADS1220_DRDY_PIN = drdy_pin; // Configure the SPI interface (CPOL=0, CPHA=1) SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE1); // Configure chip select as an output pinMode(ADS1220_CS_PIN, OUTPUT); // Configure DRDY as as input (mfg wants us to use interrupts) pinMode(ADS1220_DRDY_PIN, INPUT); digitalWrite(ADS1220_CS_PIN, LOW); // Set CS Low delayMicroseconds(100); // Wait a minimum of td(CSSC) reset(); // Send reset command delayMicroseconds(100);; // Delay a minimum of 50 us + 32 * tclk // Sanity check read back (optional) startSync(); // Send start/sync for continuous conversion mode delayMicroseconds(1); // Delay a minimum of td(SCCS) digitalWrite(ADS1220_CS_PIN, HIGH); // Clear CS to high } bool ADS1220::isDataReady() { if (digitalRead(ADS1220_DRDY_PIN) == HIGH) { return false; } return true; } long ADS1220::readADC() { digitalWrite(ADS1220_CS_PIN, LOW); // Take CS low delayMicroseconds(1); // Minimum of td(CSSC) long adcVal = SPI.transfer(SPI_MASTER_DUMMY); adcVal = (adcVal << 8) | SPI.transfer(SPI_MASTER_DUMMY); adcVal = (adcVal << 8) | SPI.transfer(SPI_MASTER_DUMMY); adcVal = ( adcVal << 8 ); adcVal = ( adcVal >> 8 ); delayMicroseconds(1); // Minimum of td(CSSC) digitalWrite(ADS1220_CS_PIN, HIGH); return adcVal; } byte * ADS1220::readADC_Array() { digitalWrite(ADS1220_CS_PIN, LOW); // Take CS low delayMicroseconds(1); // Minimum of td(CSSC) static byte dataarray[3]; for (int x = 0; x < 3 ; x++) { dataarray[x] = SPI.transfer(SPI_MASTER_DUMMY); } delayMicroseconds(1); // Minimum of td(CSSC) digitalWrite(ADS1220_CS_PIN, HIGH); return dataarray; } //Single Conversion read modes long ADS1220::readADC_Single() { digitalWrite(ADS1220_CS_PIN, LOW); // Take CS low delayMicroseconds(1); // Minimum of td(CSSC) SPI.transfer(0x08); while(digitalRead(ADS1220_DRDY_PIN) == HIGH) { //Wait to DRDY goes down //Not a good thing //Code could be stuck here //Need a improve later } long adcVal = SPI.transfer(SPI_MASTER_DUMMY); adcVal = (adcVal << 8) | SPI.transfer(SPI_MASTER_DUMMY); adcVal = (adcVal << 8) | SPI.transfer(SPI_MASTER_DUMMY); adcVal = ( adcVal << 8 ); adcVal = ( adcVal >> 8 ); delayMicroseconds(1); // Minimum of td(CSSC) digitalWrite(ADS1220_CS_PIN, HIGH); return adcVal; } byte * ADS1220::readADC_SingleArray() { digitalWrite(ADS1220_CS_PIN, LOW); // Take CS low delayMicroseconds(1); // Minimum of td(CSSC) SPI.transfer(0x08); while(digitalRead(ADS1220_DRDY_PIN) == HIGH) { //Wait to DRDY goes down //Not a good thing //Code could be stuck here //Need a improve later } static byte dataarray[3]; for (int x = 0; x < 3 ; x++) { dataarray[x] = SPI.transfer(SPI_MASTER_DUMMY); } delayMicroseconds(1); // Minimum of td(CSSC) digitalWrite(ADS1220_CS_PIN, HIGH); return dataarray; } void ADS1220::sendCommand(uint8_t command) { // Following Protocentral's code, not sure exactly what's going on here. digitalWrite(ADS1220_CS_PIN, LOW); delay(2); digitalWrite(ADS1220_CS_PIN, HIGH); delay(2); digitalWrite(ADS1220_CS_PIN, LOW); delay(2); SPI.transfer(command); delay(2); digitalWrite(ADS1220_CS_PIN, HIGH); } void ADS1220::writeRegisterMasked(uint8_t value, uint8_t mask, uint8_t address) { // Write the value to a register using the mask to leave the rest of the // register untouched. This does not shift the value, so it shoudl be provided // shifted to the appropriate positions. // Read what's in the register now uint8_t register_contents = readRegister(address); // Flip the mask so that it's zeros where we'll put data and zero out that // part of the register's contents register_contents = register_contents & ~mask; // OR in the value to be written register_contents = register_contents | value; // Write it back out writeRegister(address, register_contents); } void ADS1220::setMultiplexer(uint8_t value) { /* Set multiplexer | Value | AINp | AINn | | ----- | ---- | ---- | | 0x00 | AIN0 | AIN1 | | 0X01 | AIN0 | AIN2 | | 0X02 | AIN0 | AIN3 | | 0X03 | AIN1 | AIN2 | | 0X04 | AIN1 | AIN3 | | 0X05 | AIN2 | AIN3 | | 0X06 | AIN1 | AIN0 | | 0X07 | AIN3 | AIN2 | | 0X08 | AIN0 | AVSS | | 0X09 | AIN1 | AVSS | | 0X0A | AIN2 | AVSS | | 0X0B | AIN3 | AVSS | | 0X0C | REF/4 MON | | 0X0D | APWR/4 MON | | 0X0E | SHORTED | */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x0E) { value = 0x00; } value = value << 4; // Shift to match with mask writeRegisterMasked(value, REG_MASK_MUX, CONFIG_REG0_ADDRESS); } void ADS1220::setGain(uint8_t gain) { /* Sets ADC gain. Possible values are 1, 2, 4, 8, 16, 32, 64, 128. */ uint8_t value = 0x00; switch(gain) { case 1: value = 0x00; break; case 2: value = 0x01; break; case 4: value = 0x02; break; case 8: value = 0x03; break; case 16: value = 0x04; break; case 32: value = 0x05; break; case 64: value = 0x06; break; case 128: value = 0x07; break; default: value = 0x00; break; } value = value << 1; // Shift to match with mask writeRegisterMasked(value, REG_MASK_GAIN, CONFIG_REG0_ADDRESS); } void ADS1220::setPGAbypass(bool value) { /* Bypasses the PGA if true. PGA can only be disabled for gains 1, 2, 4. */ writeRegisterMasked(value, REG_MASK_PGA_BYPASS, CONFIG_REG0_ADDRESS); } void ADS1220::setDataRate(uint8_t value) { /* Sets the data rate for the ADC. See table 18 in datasheet for datarates in various operating modes. */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x07) { value = 0x00; } value = value << 5; // Shift to match with mask writeRegisterMasked(value, REG_MASK_DATARATE, CONFIG_REG1_ADDRESS); } void ADS1220::setOpMode(uint8_t value) { /* Sets the ADC operating mode: 0 - Normal mode 1 - Duty-cycle mode 2 - Turbo mode */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x02) { value = 0x00; } value = value << 3; // Shift to match with mask writeRegisterMasked(value, REG_MASK_OP_MODE, CONFIG_REG1_ADDRESS); } void ADS1220::setConversionMode(uint8_t value) { /* Sets the ADC conversion mode. 0 - Single shot mode 1 - continuous conversion mode */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x01) { value = 0x00; } value = value << 2; // Shift to match with mask writeRegisterMasked(value, REG_MASK_CONV_MODE, CONFIG_REG1_ADDRESS); } void ADS1220::setTemperatureMode(uint8_t value) { /* Controls the state of the internal temperature sensor. 0 - Disables temperature sensor 1 - Enables temperature sensor */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x01) { value = 0x00; } value = value << 1; // Shift to match with mask writeRegisterMasked(value, REG_MASK_TEMP_MODE, CONFIG_REG1_ADDRESS); } void ADS1220::setBurnoutCurrentSources(bool value) { /* Turns the 10uA burn-out current sources on or off. */ writeRegisterMasked(value, REG_MASK_BURNOUT_SOURCES, CONFIG_REG1_ADDRESS); } void ADS1220::setVoltageRef(uint8_t value) { /* Sets the voltage reference used by the ADC. 0 - Internal 2.048 V 1 - External on REFP0 and REFN0 inputs 2 - External on AIN0/REFP1 and AIN3/REFN1 inputs 3 - Use analog supply as reference */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x03) { value = 0x00; } value = value << 6; // Shift to match with mask writeRegisterMasked(value, REG_MASK_VOLTAGE_REF, CONFIG_REG2_ADDRESS); } void ADS1220::setFIR(uint8_t value) { /* Controls the FIR filter on the ADC. 0 - No 50 or 60 Hz rejection 1 - Both 50 and 60 Hz rejection 2 - 50 Hz rejection 3 - 60 Hz rejection */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x03) { value = 0x00; } value = value << 4; // Shift to match with mask writeRegisterMasked(value, REG_MASK_FIR_CONF, CONFIG_REG2_ADDRESS); } void ADS1220::setPowerSwitch(uint8_t value) { /* Configures behavior of low-side switch between AIN3/REFN1 and AVSS. 0 - Always open 1 - Automatically closes when START/SYNC command is sent and opens when POWERDOWN command is issues. */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x01) { value = 0x00; } value = value << 3; // Shift to match with mask writeRegisterMasked(value, REG_MASK_PWR_SWITCH, CONFIG_REG2_ADDRESS); } void ADS1220::setIDACcurrent(uint8_t value) { /* Set current for both IDAC1 and IDAC2 excitation sources. 0 - Off 1 - 10 uA 2 - 50 uA 3 - 100 uA 4 - 250 uA 5 - 500 uA 6 - 1000 uA 7 - 1500 uA */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x07) { value = 0x00; } writeRegisterMasked(value, REG_MASK_IDAC_CURRENT, CONFIG_REG2_ADDRESS); } void ADS1220::setIDAC1routing(uint8_t value) { /* Selects where IDAC1 is routed to. 0 - Disabled 1 - AIN0/REFP1 2 - AIN1 3 - AIN2 4 - AIN3/REFN1 5 - REFP0 6 - REFN0 */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x06) { value = 0x00; } value = value << 5; // Shift to match with mask writeRegisterMasked(value, REG_MASK_IDAC1_ROUTING, CONFIG_REG3_ADDRESS); } void ADS1220::setIDAC2routing(uint8_t value) { /* Selects where IDAC2 is routed to. 0 - Disabled 1 - AIN0/REFP1 2 - AIN1 3 - AIN2 4 - AIN3/REFN1 5 - REFP0 6 - REFN0 */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x06) { value = 0x00; } value = value << 2; // Shift to match with mask writeRegisterMasked(value, REG_MASK_IDAC2_ROUTING, CONFIG_REG3_ADDRESS); } void ADS1220::setDRDYmode(uint8_t value) { /* Controls the behavior of the DOUT/DRDY pin when new data are ready. 0 - Only the dedicated DRDY pin is used 1 - Data ready indicated on DOUT/DRDY and DRDY */ // Make sure the value is in the valid range. Otherwise set to 0x00 if (value > 0x01) { value = 0x00; } value = value << 1; // Shift to match with mask writeRegisterMasked(value, REG_MASK_DRDY_MODE, CONFIG_REG3_ADDRESS); } void ADS1220::reset() { sendCommand(CMD_RESET); } void ADS1220::startSync() { sendCommand(CMD_START_SYNC); } void ADS1220::powerDown() { sendCommand(CMD_PWRDWN); } void ADS1220::rdata() { sendCommand(CMD_RDATA); } void ADS1220::ReadRegister(int StartAddress, int NumRegs, unsigned * pData) { int i; digitalWrite(ADS1220_CS_PIN, LOW); // send the command byte SendByte(CMD_RREG | (((StartAddress<<2) & 0x0c) |((NumRegs-1)&0x03))); // get the register content for (i=0; i< NumRegs; i++) { *pData++ = ReceiveByte(); } digitalWrite(ADS1220_CS_PIN, HIGH); return; } void ADS1220::SendByte(unsigned char Value) { SPI.transfer(Value); } unsigned int ADS1220::ReceiveByte(void) { unsigned int readvalue; readvalue = SPI.transfer(0x00); return readvalue; }

header file

#ifndef ADS1220_h
#define ADS1220_h

#include "Arduino.h"

#define SPI_MASTER_DUMMY   0xFF
// Commands for the ADC
#define CMD_RESET 0x07
#define CMD_START_SYNC 0x08
#define CMD_PWRDWN 0x03
#define CMD_RDATA 0x1f
#define CMD_RREG 0x20
#define CMD_WREG 0x40

// Configuration registers
#define CONFIG_REG0_ADDRESS 0x00
#define CONFIG_REG1_ADDRESS 0x01
#define CONFIG_REG2_ADDRESS 0x02
#define CONFIG_REG3_ADDRESS 0x03

// Register masks for setings
// Register 0
#define REG_MASK_MUX 0xF0
#define REG_MASK_GAIN 0x0E
#define REG_MASK_PGA_BYPASS 0x01

// Register 1
#define REG_MASK_DATARATE 0xE0
#define REG_MASK_OP_MODE 0x18
#define REG_MASK_CONV_MODE 0x04
#define REG_MASK_TEMP_MODE 0x02
#define REG_MASK_BURNOUT_SOURCES 0x01

// Register 2
#define REG_MASK_VOLTAGE_REF 0xC0
#define REG_MASK_FIR_CONF 0x30
#define REG_MASK_PWR_SWITCH 0x08
#define REG_MASK_IDAC_CURRENT 0x07

// Register 3
#define REG_MASK_IDAC1_ROUTING 0xE0
#define REG_MASK_IDAC2_ROUTING 0x1C
#define REG_MASK_DRDY_MODE 0x02
#define REG_MASK_RESERVED 0x01

class ADS1220 {
  public:
    ADS1220();
    uint8_t ADS1220_CS_PIN;
    uint8_t ADS1220_DRDY_PIN;
    void writeRegister(uint8_t address, uint8_t value);
    uint8_t readRegister(uint8_t address);
    void begin(uint8_t cs_pin, uint8_t drdy_pin);
    bool isDataReady(void);
    long readADC(void);
    byte * readADC_Array(void);
    long readADC_Single(void);
    byte * readADC_SingleArray(void);
    void sendCommand(uint8_t command);
    void reset(void);
    void startSync(void);
    void powerDown(void);
    void rdata(void);
    void writeRegisterMasked(uint8_t value, uint8_t mask, uint8_t address);
    void setMultiplexer(uint8_t value);
    void setGain(uint8_t gain);
    void setPGAbypass(bool value);
    void setDataRate(uint8_t value);
    void setOpMode(uint8_t value);
    void setConversionMode(uint8_t value);
    void setTemperatureMode(uint8_t value);
    void setBurnoutCurrentSources(bool value);
    void setVoltageRef(uint8_t value);
    void setFIR(uint8_t value);
    void setPowerSwitch(uint8_t value);
    void setIDACcurrent(uint8_t value);
    void setIDAC1routing(uint8_t value);
    void setIDAC2routing(uint8_t value);
    void setDRDYmode(uint8_t value);
	
	void SendByte(unsigned char cData );     // Send byte to the ADS1220
    unsigned int ReceiveByte(void);          // Receive byte from the ADS1220
	int GetChannel(void);
	void ReadRegister(int StartAddress, int NumRegs, unsigned * pData);
  };
#endif

  • Hi Oleg,

    Welcome to the E2E forum! The issue you are seeing relates to the common-mode input range for the PGA when it is enabled. The PGA is similar to an instrumentation amplifier where the output of the amplifier is unable to drive the output all the way to the supply rails. This is discussed in the datasheet starting on page 21 in section 8.3.2. You will want to pay particular attention to 8.3.2.1 where the common-mode voltage requirements are detailed and where equations 13 through 15 must be satisfied.

    When using a unipolar analog supply (+5V/GND) the only way you can make a ground referenced measurement is to disable and bypass the PGA. If you want to make a ground referenced measurement while enabling the PGA, you would need to use bipolar supplies (+/-2.5V).

    Best regards,
    Bob B
  • Hi Bob,

    Thanks for the explanation. Now it's clear, that my main mistake is that I didn't carefully read the datasheet  ))

    If I right understand it, with bipolar power supply, I need to connect the negative input to the ground.

    Is it so? Or is it optional?

    For example:

  • Hi Oleg,

    If you use bipolar supplies for the the analog supply (AVDD=+2.5V, AVSS=-2.5V) you will need to connect one of your inputs to analog ground similar to as you have shown. The single-ended mux configuration is only useful with a unipolar analog supply as the measurement is take with respect to AVSS (for example, AINp = AIN0, AINn = AVSS). So you would instead need to make a differential measurement using 2 inputs (for example AINp = AIN0, AINn = AIN1) where one of the inputs connects to analog ground.

    Best regards,
    Bob B
  • Bob, many thanks!