4-channel, 24-bit ADC ADS1220

About this post

Many applications require precise measurement of voltages. With the internal A/D converters (ADCs) of the Arduino, ESP8266 or ESP32 boards, you will quickly reach the limits. You can achieve good results with the 16-bit A/D converter ADS1115, which I reported about here. But even 16 bits are sometimes not enough, e.g. when controlling load cells (see my posts about HX711 based scales or strain gauges). In this article, I will introduce the (comparatively) easy-to-use 24-bit ADC ADS1220.

As part of my research, I tried the Protocentral_ADS1220 library. It is slim and basically it works well, except for one of the two example ketches. However, when testing the various settings, I often had to look into the ADS1220 data sheet or find the parameters for the functions from the header file. Therefore, I have written the library ADS1220_WE. It is an attempt to make the operation of the ADS1220 comfortable. You can find my library here on GitHub or install it using the Arduino library manager. Just try both libraries and take what you like better.

Technical data of the ADS1220

IC vs. module

For the connection of the ADS1220 to a microcontroller, the data sheet in section 9.1.1 recommends the use of some capacitors and resistors. If you want it simple, like I do, use a module that already has these additional components implemented. You can get such a module in online stores for less than 10 Euros, mostly directly from China.

ADS1220 module
ADS1220 module

Essential data at a glance

Key technical data are:

  • Analog power supply: 2.3 to 5.5 volts at AVDD / AVSS (AGND).
  • Digital power supply: 2.3 to 5.5 volts at DVDD / DGND.
  • Bipolar power supply option: e.g. AVSS to DGND = -2.5 volts, AVDD to DGND = +2.5 volts.
  • Power consumption: 120 microamps in duty cycle mode.
  • Communication: via SPI.
  • Reference voltages:
    • Internal: 2.048 volts.
    • External: VREFPx – VREFNx = min. 0.75 volts, max. AVDD.
  • Gain: 1 – 128 (with PGA = Programmable Gain Amplifier), 1 – 4 (without PGA).
  • Data rate: 5 – 2000 SPS (Samples per Second)
  • Conversion modes: Continuous and “Single-Shot” mode.
  • Voltage inputs: 4 single-ended (against AVSS) or 2 differential inputs.
  • Measurement in multiplex mode.
  • Two programmable IDACs (digital-to-analog converter for current): 10 microamps to 1.5 milliamperes (also called “excitation current source” in the data sheet).
  • Digital filter: 50 and/or 60 Hz.
  • Integrated temperature sensor.

Pinout of the ADS1220 (module)

The labels of the inputs and outputs on the module partly differ from the data sheet (in brackets, if different):

  • MISO (DOUT/DRDY), MOSI (DIN), SCLK, CS: SPI – connectors
  • AVDD, AGND (AVSS): Analog voltage supply
  • DVDD, DGND: Digital voltage
  • CLK: external clock (optional)
  • DRDY: Data Ready Pin, is LOW when data is available.
  • REFP0, REFN0: external reference voltage; “P” = positive, “N” = negative.
  • AIN0 (AIN0/REFP1), AIN1, AIN2, AIN3 (AIN3/REFP1): Inputs for the voltages to be measured.
    • AIN0/AIN3 can alternatively be used as input for an additional external reference voltage.

Simple measurements

Wiring

I will explain how to use the ADS1220 with example sketches. First, I want to show you how to change input channels and query data using the default settings. For my example sketch, the following circuit was used:

Example circuit: ADS1220 on Arduino UNO
Example circuit: ADS1220 on Arduino UNO

A few notes beforehand

In this example, the internal voltage reference of 2.048 volts is used. This means that you can only measure voltages in the range of -2.048 to +2.048 volts.

The output voltage of the PGA must be greater than AVSS plus 200 millivolts and lower than AVDD minus 200 millivolts. Outside these limits, the ADS1220 no longer operates linearly. Since the default gain is 1, these limits also apply to the input voltage in the first example. To be on the safe side, we turn off the PGA with bypassPGA(true).

The measured voltage is calculated from the raw value as follows:

\text{Spannung} = \frac{\text{Rohwert}\cdot \text{Referenzspannung}}{(2^{23}-1)\cdot \text{Gainfaktor}}\;\text{[V]}

The sketch

Here’s the sketch:

#include <ADS1220_WE.h>
#include <SPI.h>

#define ADS1220_CS_PIN    7 // chip select pin
#define ADS1220_DRDY_PIN  6 // data ready pin 

/* Create your ADS1220 object */
ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN);
/* Alternatively you can also pass the SPI object as reference */
// ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN, &SPI);

void setup(){
  Serial.begin(9600);
  if(!ads.init()){
    Serial.println("ADS1220 is not connected!");
    while(1);
  }
/* The voltages to be measured need to be between negative VREF + 0.2 V and positive
 * VREF -0.2 V if PGA is enabled. For this basic example I disable PGA, to be on the 
 * safe side.
 */
  ads.bypassPGA(true); 
}

/* 
 *  You set the channels to be measured with setCompareChannels(); You
 * can choose the following parameters:
 * Parameter                  Pos. Input     Neg. Input         Comment
 * ADS1220_MUX_0_1              AIN0           AIN1
 * ADS1220_MUX_0_2              AIN0           AIN2
 * ADS1220_MUX_0_3              AIN0           AIN3
 * ADS1220_MUX_1_2              AIN1           AIN2
 * ADS1220_MUX_1_3              AIN1           AIN3
 * ADS1220_MUX_2_3              AIN2           AIN2
 * ADS1220_MUX_1_0              AIN1           AIN0
 * ADS1220_MUX_3_2              AIN3           AIN2
 * ADS1220_MUX_0_AVSS           AIN0           AVSS           single-ended
 * ADS1220_MUX_1_AVSS           AIN1           AVSS           single-ended  
 * ADS1220_MUX_2_AVSS           AIN2           AVSS           single-ended
 * ADS1220_MUX_3_AVSS           AIN3           AVSS           single-ended
 * ADS1220_MUX_REFPX_REFNX_4   REFP0/REFP1   REFN0/REFN1     (REFPX-REFNX)/4; PGA bypassed
 * ADS1220_MUX_AVDD_M_AVSS_4    AVDD           AVSS           (AVDD-AVSS)/4; PGA bypassed
 * ADS1220_MUX_AVDD_P_AVSS_2    AVDD           AVSS           (AVDD+AVSS)/2
 * 
 * The last three modes use the internal reference (2.048 V) and gain = 1, independent of 
 * your settings.
 */


void loop(){
  float result = 0.0;
  long longResult = 0;
    
  ads.setCompareChannels(ADS1220_MUX_0_1);
  result = ads.getVoltage_mV();
  longResult = ads.getRawData();
  Serial.print("AIN0 vs. AIN1  [mV]: ");
  Serial.println(result);
  Serial.print("AIN0 vs. AIN1 (raw): ");  // raw data
  Serial.println(longResult);
  
  ads.setCompareChannels(ADS1220_MUX_0_AVSS);
  result = ads.getVoltage_mV();
  Serial.print("AIN0 vs. AVSS  [mV]: ");
  Serial.println(result);

  ads.setCompareChannels(ADS1220_MUX_1_AVSS);
  result = ads.getVoltage_mV();
  Serial.print("AIN1 vs. AVSS  [mV]: ");
  Serial.println(result);
  
  ads.setCompareChannels(ADS1220_MUX_2_AVSS);
  result = ads.getVoltage_muV();    // request result in microvolts
  Serial.print("AIN2 vs. AVSS  [µV]: ");
  Serial.println(result);
  
  ads.setCompareChannels(ADS1220_MUX_3_AVSS);
  result = ads.getVoltage_mV();
  Serial.print("AIN3 vs. AVSS  [mV]: ");
  Serial.println(result);
  
  ads.setCompareChannels(ADS1220_MUX_AVDD_M_AVSS_4); // voltage supply / 4
  result = ads.getVoltage_mV() * 4.0;
  Serial.print("Supply Voltage [mV]: ");
  Serial.println(result);

  result = ads.getVRef_V();                 // get the reference voltage
  Serial.print("Reference       [V]: ");
  Serial.println(result,3); 
  
  Serial.print("Temperature    [°C]: ");    // get the temperature
  result = ads.getTemperature();
  Serial.println(result);
  
  Serial.println();
 
  delay(2000);
}

 

Explanations to the sketch

I only go into the parts that, I think, need explanation (otherwise, ask!):

  • With init() you initialize the ADS1220. Among other things, the function performs a reset.
  • setCompareChannels() defines which inputs are used for the measurement. 
  • The parameter ADS1220_MUX_REFPX_REFNX_4 causes the reference voltage REFP0/REFN0 or REFP1/REFN1 to be measured. Regardless of your settings, this bypasses the PGA, the gain is 1, and the internal reference is used. To ensure that this also works with reference voltages greater than 2.048 volts, the voltage is reduced to a quarter before the measurement. 
  • When using the parameter ADS1220_MUX_AVDD_M_AVSS_4 you measure a quarter of the supply voltage.
  • ADS1220_MUX_AVDD_P_AVSS_2 returns the result (VAVDDVAVSS) / 2. This determines the offset when using bipolar voltage supply.
  • The functions getVoltage_mV() / getVoltage_muV() provide the result in millivolts and microvolts. It is always waited until DRDY goes LOW. This ensures that you get a “fresh” result. In single-shot mode, the measurements must be initiated. The getVoltage functions take care of that as well.
  • Alternatively, query the raw value of the A/D conversion with getRawData().
  • With getVRef_V() you get the reference voltage in volts. This is not the result of a measurement, but the function returns what you have set. The default setting is 2.048 volts.
  • getTemperature() provides the temperature in degrees Celsius.

Output

This is how the output of the sketch may look like:

Output of ADS1220_basic_example.ino
Output of ADS1220_basic_example.ino

As you can see,VAIN0/AIN1 =VAIN0/AVSS  -VAIN1/AVSS, which must be the case if you think about it.

Change of the reference voltage of the ADS1220

As mentioned before, you have four options for the reference voltage:

  1. Internal reference voltage (2.048 volts)
  2. External reference voltage at REFP0/REFN0
  3. External reference voltage at REFP1/REFN1 (at the expense of two voltage inputs)
  4. Supply voltage (AVDD/AVSS) as voltage reference

The following circuit diagram is intended to illustrate this:

Circuit example: Using external reference voltages with the ADS1220
Circuit example: Using external reference voltages with the ADS1220

An example sketch

I used the circuit shown above and applied different reference voltages to REFP0/REFN0 and REFP1/REFN1. A test voltage to be measured was connected to AIN2.

The following sketch shows how to set or change reference voltages for the ADS1220:

#include <ADS1220_WE.h>
#include <SPI.h>

#define ADS1220_CS_PIN    7 // chip select pin
#define ADS1220_DRDY_PIN  6 // data ready pin 

/* Create your ADS1220 object */
ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN);
/* Alternatively you can also pass the SPI object as reference */
// ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN, &SPI);

void setup(){
  Serial.begin(9600);
  if(!ads.init()){
    Serial.println("ADS1220 is not connected!");
    while(1);
  }
}

/* For better readability of the code the setting options are explained here and not
 * in loop() where the functions are used.
 * 
 * You set the channels to be measured with setCompareChannels(); You
 * can choose the following parameters:
 * Parameter                  Pos. Input     Neg. Input         Comment
 * ADS1220_MUX_0_1              AIN0           AIN1
 * ADS1220_MUX_0_2              AIN0           AIN2
 * ADS1220_MUX_0_3              AIN0           AIN3
 * ADS1220_MUX_1_2              AIN1           AIN2
 * ADS1220_MUX_1_3              AIN1           AIN3
 * ADS1220_MUX_2_3              AIN2           AIN2
 * ADS1220_MUX_1_0              AIN1           AIN0
 * ADS1220_MUX_3_2              AIN3           AIN2
 * ADS1220_MUX_0_AVSS           AIN0           AVSS (=AGND)   single-ended
 * ADS1220_MUX_1_AVSS           AIN1           AVSS           single-ended  
 * ADS1220_MUX_2_AVSS           AIN2           AVSS           single-ended
 * ADS1220_MUX_3_AVSS           AIN3           AVSS           single-ended
 * ADS1220_MUX_REFPX_REFNX_4   REFP0/REFP1   REFN0/REFN1     (REFPX-REFNX)/4; PGA bypassed
 * ADS1220_MUX_AVDD_M_AVSS_4    AVDD           AVSS          (AVDD-AVSS)/4; PGA bypassed
 * ADS1220_MUX_AVDD_P_AVSS_2    AVDD           AVSS           AVDD+AVSS)/2
 * The last three modes use the internal reference (2.048 V) and gain = 1, independent of 
 * your settings.
 * 
 * setVRefSource() sets the the reference voltage source. Parameters are:
 * ADS1220_VREF_INT          int. reference 2.048 V (default)
 * ADS1220_VREF_REFP0_REFN0  ext. reference = Vrefp0 - Vrefn0
 * ADS1220_VREF_REFP1_REFN1  ext. reference = Vrefp1 - Vrefn1 (REFP1=AIN0, REFN1=AIN3)
 * ADS1220_VREF_AVDD_AVSS    ext. reference = supply voltage
 * 
 * If you use the above options you also have to set the value of vRef "manually":
 * setVRefValue_V(vRef in volts);
 * 
 * Alternatively, you can set the reference voltage source and let the ADS1220 measure 
 * the reference. Be aware that this is not a measurement with highest precision. "Calibration" 
 * might be a bit misleading. You should take the lowest data rate (default) for most accurate 
 * results. You can use the following functions:
 * setRefp0Refn0AsVefAndCalibrate();
 * setRefp1Refn1AsVefAndCalibrate();
 * setAvddAvssAsVrefAndCalibrate();
 * setIntVRef();
 * The latter function sets the default settings. 
 */

void loop(){
  float result = 0.0;
    
  Serial.println("1. Standard internal voltage reference");
  result = ads.getVRef_V();                 // get the reference voltage 
  Serial.print("Reference, default      [V]: ");
  Serial.println(result,3); 
  
  Serial.println("2. REFP0/REFN0 as voltage reference, vRef manually set");
  ads.setVRefSource(ADS1220_VREF_REFP0_REFN0);
  ads.setVRefValue_V(3.295);    // set reference voltage in volts
  result = ads.getVRef_V();
  Serial.print("Reference, manually set [V]: ");
  Serial.println(result,3); 

  Serial.println("3. REFP0/REFN0 as voltage reference, vRef measured by ADS1220");
  ads.setRefp0Refn0AsVefAndCalibrate();
  result = ads.getVRef_V();
  Serial.print("Reference, measured     [V]: ");
  Serial.println(result,3); 

  Serial.println("4. REFP1/REFN1 as voltage reference, vRef measured by ADS1220");
  ads.setRefp1Refn1AsVefAndCalibrate();
  result = ads.getVRef_V();
  Serial.print("Reference, measured     [V]: ");
  Serial.println(result,3);
  
  Serial.println("5. AVDD/AVSS as voltage reference, vRef measured by ADS1220");
  ads.setAvddAvssAsVrefAndCalibrate();
  result = ads.getVRef_V();
  Serial.print("Reference, measured     [V]: ");
  Serial.println(result,3); 
 
  Serial.println("With 5. you can measure up to AVDD, just try.");
  ads.setCompareChannels(ADS1220_MUX_2_AVSS);
  result = ads.getVoltage_mV();
  Serial.print("AIN2 vs. AVSS          [mV]: ");
  Serial.println(result);

  ads.setIntVRef(); // switch back to standard vRef
  Serial.println();
 
  delay(2000);
}

 

The output

This is what the output looked like for me:

Output of ADS1220_set_voltage_references.ino
Output of ADS1220_set_voltage_references.ino

Explanations to the sketch

By default, the ADS1220 uses the internal reference voltage. If you want to switch to an external reference voltage, you have two options:

  1. You define the reference voltage source with setVRefSource() and specify its value with setVRefValue().
  2. With a single instruction you set the reference voltage source and let the ADS1220 measure the reference voltage value. The following functions are available for this purpose:
    1. setRefp0Refn0AsVefAndCalibrate(): REFP0/REFN0
    2. setRefp1Refn1AsVefAndCalibrate(): REFP1/REFN1
    3. setAvddAvssAsVrefAndCalibrate(): AVDD/AVSS

The ADS1220 data sheet states that the external reference measurement is less accurate than a regular measurement. One should not overestimate this error, because the internal reference voltage also has a certain error (up to 3 millivolts). However, I would not call the functions 2a-2c too often, because the reference value will then always fluctuate a little.

I applied a supply voltage (AVDD/AVSS) of about 5 volts and checked the voltage with my multimeter and via setAvddAvssAsVrefAndCalibrate() and getVRef_V(). The deviation was a maximum of one millivolt.

What’s the point of all these references?

In many applications, such as Wheatstone bridges or resistance thermometers, one is not interested in absolute voltage values, but rather in resistances determined via voltage ratios and reference resistances. By applying a current across the sought resistor and across the reference resistor, and using the voltage drop across the reference resistor as the reference voltage, the sought resistor can be determined independently of current variations. This will be the subject of my next post.

Setting PGA and Gain

The voltage to be converted can be amplified with the PGA (Programmable Gain Amplifier). Gain factors between 1 and 128 are available. As described earlier, you leave the linear range when the output voltage of the PGA approaches AVDD or AVSS to less than 200 millivolts. You can bypass this restriction by bypassing the PGA. Confusingly, you can still apply gains of 1, 2 or 4.

These are the functions:

  • setGain(): sets the gain factor (1, 2, 4, 8, 16, 32, 64 or 128).
  • getGainFactor(): returns the actual gain factor as a byte value. The actual gain factor may differ from the value you set before (see below).
  • bypassPGA(true/false): bypasses the PGA or switches it on again.
  • isPGABypassed(): returns true if the PGA is bypassed, false otherwise. Again, the value may differ from your preset.

There are still a few things to keep in mind:

  • If you set a gain factor >= 8, the PGA will not be bypassed, and this is independent of your settings made with bypassPGA().
  • If you perform a single-ended measurement (AINx = AVSS), then the PGA must be bypassed and only gains of 1, 2 and 4 are possible. The library will ensure that these rules are followed. You don’t have to worry about it, but you should be aware of the limitation.
  • The PGA is also forcibly bypassed when measuring external reference voltages or the supply voltage. The gain factor is reduced to 1.

Example sketch

To illustrate how the PGA state and gain changes, I wrote the following sketch. As you can see, forced changes are made to the settings under certain conditions, but they are also undone when the conditions are changed back.

#include <ADS1220_WE.h>
#include <SPI.h>

#define ADS1220_CS_PIN    7
#define ADS1220_DRDY_PIN  6

ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN);

void setup(){
  Serial.begin(9600);
  ads.init();
  ads.setGain(ADS1220_GAIN_8); // set gain to 8
  ads.bypassPGA(false);  // redundant, since this is default, but I wanted to highlight this setting. 
}

void loop(){
  ads.setCompareChannels(ADS1220_MUX_0_1);
  float result = ads.getVoltage_mV();
  byte gain = ads.getGainFactor();  // queries the (effective) gain
  Serial.print("AIN0/AIN1 [mV]: ");
  Serial.println(result);
  Serial.print("PGA bypassed (0: no, 1: yes): ");  
  Serial.println(ads.isPGABypassed()); // queries if PGA is bypassed, here the result will be 0
  Serial.print("Current gain: ");  // 
  
  Serial.println(gain);
  Serial.println();
   
  ads.setCompareChannels(ADS1220_MUX_2_AVSS);
  result = ads.getVoltage_mV();
  gain = ads.getGainFactor();
  Serial.print("AIN2/AVSS [mV]: ");
  Serial.println(result);
  Serial.print("PGA bypassed (0: no, 1: yes): "); // the PGA will be bypassed
  Serial.println(ads.isPGABypassed());
  Serial.print("Current gain: ");  // gain will be reduced to 4. 
  Serial.println(gain);
  Serial.println();

  ads.setCompareChannels(ADS1220_MUX_AVDD_M_AVSS_4);
  result = ads.getVoltage_mV();
  gain = ads.getGainFactor();
  Serial.print("AVDD/AVSS [mV]: ");
  Serial.println(result*4.0);
  Serial.print("PGA bypassed (0: no, 1: yes): "); // the PGA will be bypassed
  Serial.println(ads.isPGABypassed());
  Serial.print("Current gain: ");  // gain will be reduced to 1. 
  Serial.println(gain);
  Serial.println();
 
  delay(2000);
}

 

Here is the output:

Output of ADS1220_WE_pga_settings.ino
Output of ADS1220_WE_pga_settings.ino

IDAC settings

The ADS1220 allows you to set up constant currents between 10 microamps and 1.5 milliamps on two pins. These currents are referred to as IDAC1 and IDAC2. Since the current is known and the ADS1220 measures the voltage needed to generate the current, you can conveniently measure resistances this way. A typical application is temperature measurement with resistance thermometers.

I would like to demonstrate the function with a simple example. To do this, place a 10 kΩ resistor between AIN0 and AIN1 or between AIN2 and  AIN1, respectively. Connect AIN1 to AVSS.

ADS1220 IDAC test - schematic illustration
IDAC test – schematic illustration

Then upload the following sketch:

#include <ADS1220_WE.h>
#include <SPI.h>

#define ADS1220_CS_PIN    7 // chip select pin
#define ADS1220_DRDY_PIN  6 // data ready pin 

ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN);

void setup() {
  Serial.begin(9600);
  if (!ads.init()) {
    Serial.println("ADS1220 is not connected!");
    while (1);
  }
  //  ads.setCompareChannels(ADS1220_MUX_0_1); default seeting for this test
  ads.setGain(ADS1220_GAIN_1);
  ads.bypassPGA(true); // since the negative voltage is AVSS, we have to bypass PGA 

  /* The ADS1220 can provide two excitation currents, IDAC1 and IDAC2. It takes up to 200µs
     until the current is set up. The library includes a delay, so you don't have to add one.
     You can switch IDAC1 and IDAC2 on and off individually but the current is the same for 
     both.
     The ADS1220 will try to provide the voltage needed for the current you have chosen. This 
     voltage shall not exceed AVDD - 0.9V.

     ADS1220_IDAC_OFF         // default
     ADS1220_IDAC_10_MU_A     // set IDAC1/IDAC2 to 10 µA
     ADS1220_IDAC_50_MU_A     // 50 µA
     ADS1220_IDAC_100_MU_A    // 100 µA
     ADS1220_IDAC_250_MU_A    // 250 µA
     ADS1220_IDAC_500_MU_A    // 500 µA
     ADS1220_IDAC_1000_MU_A   // 1000 µA
     ADS1220_IDAC_1500_MU_A   // 1500 µA
  */
 ads.setIdacCurrent(ADS1220_IDAC_100_MU_A);

  /* You can choose to which pin IDAC1 and IDAC2 are directed. The parameters are self-explaining.
     ADS1220_IDAC_NONE
     ADS1220_IDAC_AIN0_REFP1
     ADS1220_IDAC_AIN1
     ADS1220_IDAC_AIN2
     ADS1220_IDAC_AIN3_REFN1
     ADS1220_IDAC_REFP0
     ADS1220_IDAC_REFN0
  */
  //  ads.setIdac1Routing(ADS1220_IDAC_AIN0_REFP1);
  //  ads.setIdac2Routing(ADS1220_IDAC_AIN2);

} // end of setup()

void loop() {
  float result = 0.0;

  ads.setIdac1Routing(ADS1220_IDAC_AIN0_REFP1);
  result = ads.getVoltage_mV(); 
  Serial.print("Voltage, caused by IDAC1 [mV]:           ");
  Serial.println(result, 3);  // will be roughly 1000 mA, according to Ohm's law

  ads.setIdac2Routing(ADS1220_IDAC_AIN2);
  result = ads.getVoltage_mV(); 
  Serial.print("Voltage, caused by IDAC1 and IDAC2 [mV]: ");
  Serial.println(result, 3);  // will be roughly 2000 mA, according to Ohm's law

  ads.setIdac1Routing(ADS1220_IDAC_NONE);
  ads.setIdac2Routing(ADS1220_IDAC_NONE);
  Serial.println();
  
  delay(2000);
}

 

Output

Your output should look like this or similar:

Output of ADS1220_IDAC_test.ino
Output of ADS1220_IDAC_test.ino

Explanations

The handling of IDACs is simple:

  • setIdacCurrent(ADS1220_IDAC_100_MU_A); sets the current to the desired value, here it is 100 microamps. IDAC1 and IDAC2 are identical in terms of current level.
  • setIdac1Routing(ADS1220_IDAC_AIN0_REFP1); sets pin AIN0/REFP1 as current source for IDAC1. For IDAC2 you proceed accordingly, if you want to apply two currents. Use the parameter ADS1220_IDAC_NONE to turn off the power.

Then you simply measure the voltage at the current output pin and calculate the resistance. As you can see, you can also merge IDAC1 and IDAC2.

You have to take care that the current is only high enough for the ADS1220 to generate the necessary voltage. According to the data sheet, the IDAC voltage should not exceed AVDD minus 0.9 volts.

The IDACs have a typical deviation of one percent. So, this is not a precision measurement. It is better to use reference resistors (see next article).

Other settings of the ADS1220

The other settings of the ADS1220 have fewer “side effects”. I have therefore summarized them in a sketch and also included the functions already covered once again. This should be quite a convenient template for your own sketches. You just have to uncomment the corresponding functions and apply your parameters. The sketch is called ADS1220_WE_all_settings.ino and is, like the previous examples, part of the library.

The sketch

Here is the sketch:

#include <ADS1220_WE.h>
#include <SPI.h>

#define ADS1220_CS_PIN    7 // chip select pin
#define ADS1220_DRDY_PIN  6 // data ready pin 

/* Create your ADS1220 object */
ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN);
/* Alternatively you can also pass the SPI object as reference */
// ADS1220_WE ads = ADS1220_WE(ADS1220_CS_PIN, ADS1220_DRDY_PIN, &SPI);

void setup() {
  Serial.begin(9600);
  if (!ads.init()) {
    Serial.println("ADS1220 is not connected!");
    while (1);
  }

  /* General settings / commands */
  //  ads.start(); // wake up from power down and start measurement
  //  ads.reset(); // resets the ADS1220; all settings will change to default
  //  ads.powerDown(); // send the ADS1220 to sleep
  //  ads.setSPIClockSpeed(8000000); // set SPI clock speed, default is 4 MHz

  /* You set the channels to be measured with setCompareChannels(); You
     can choose the following parameters:
     Parameter                  Pos. Input     Neg. Input         Comment
     ADS1220_MUX_0_1              AIN0           AIN1             default
     ADS1220_MUX_0_2              AIN0           AIN2
     ADS1220_MUX_0_3              AIN0           AIN3
     ADS1220_MUX_1_2              AIN1           AIN2
     ADS1220_MUX_1_3              AIN1           AIN3
     ADS1220_MUX_2_3              AIN2           AIN2
     ADS1220_MUX_1_0              AIN1           AIN0
     ADS1220_MUX_3_2              AIN3           AIN2
     ADS1220_MUX_0_AVSS           AIN0           AVSS (=AGND)   single-ended
     ADS1220_MUX_1_AVSS           AIN1           AVSS           single-ended
     ADS1220_MUX_2_AVSS           AIN2           AVSS           single-ended
     ADS1220_MUX_3_AVSS           AIN3           AVSS           single-ended
     ADS1220_MUX_REFPX_REFNX_4   REFP0/REFP1   REFN0/REFN1     (REFPX-REFNX)/4; PGA bypassed
     ADS1220_MUX_AVDD_M_AVSS_4    AVDD           AVSS          (AVDD-AVSS)/4; PGA bypassed
     ADS1220_MUX_AVDD_P_AVSS_2    AVDD           AVSS           AVDD+AVSS)/2
     The last three modes use the internal reference (2.048 V) and gain = 1, independent of
     your settings.
  */
  //  ads.setCompareChannels(ADS1220_MUX_0_3);

  /* You can choose a gain between 1 (default) and 128 using setGain() if PGA is enabled
     (default). If PGA is disabled you can still choose a gain factor up to 4. If PGA is
     enabled, the amplified voltage shall be between AVSS + 200mV and AVDD - 200mV. Outside
     this range linearity drops. For details check the data sheet, section 8.3.2.1.

     If you apply a single-ended mode (negative AINx = AVSS), PGA must be bypassed. Accordingly,
     the maximum gain is 4. The library does these settings automatically.

     For the measurement of reference voltages / supply voltage PGA will also be bypassed. In
     this case gain is 1.

     The parameters you can choose for setGain() are:
     ADS1220_GAIN_X with X = 1,2,4,8,16,32,64 or 128

     With getGainFactor() you can query the gain. The function returns the effective gain and
     not the gain set in the register. Under certian conditions thes are are different. For
     example, the effective gain is set to 1 when external references are measured.
  */
  ads.setGain(ADS1220_GAIN_1);
  //  ads.getGainFactor(); // returns the effective gain as a byte value
  //  ads.bypassPGA(true); // true disables PGA, false enables PGA
  //  ads.isPGABypassed(); // returns true, if PGA is bypassed

  /* The data rate level with setDataRate(). The data rate itself also depends on the operating
     mode and the clock. If the internal clock is used or an external clock with 4.096 MHz the data
     rates are as follows (per second):

      Level               Normal Mode      Duty-Cycle      Turbo Mode
     ADS1220_DR_LVL_0          20               5               40         (default)
     ADS1220_DR_LVL_1          45              11.25            90
     ADS1220_DR_LVL_2          90              22.5            180
     ADS1220_DR_LVL_3         175              44              350
     ADS1220_DR_LVL_4         330              82.5            660
     ADS1220_DR_LVL_5         600             150             1200
     ADS1220_DR_LVL_6        1000             250             2000

     The higher the data rate, the higher the noise (tables are provided in section 7.1 in the
     data sheet). In single-shot mode the conversion times equal the times in Normal Mode.
  */
  //  ads.setDataRate(ADS1220_DR_LVL_2);

  /* Using setOperatingMode() you choose the operating mode. Possible parameters are:
     ADS1220_NORMAL_MODE      ->  Normal Mode
     ADS1220_DUTY_CYCLE_MODE  ->  Duty cycle mode. Saves power, but noise is higher.
     ADS1220_TURBO_MODE       ->  Turbo Mode for fast measurements
  */
  //  ads.setOperatingMode(ADS1220_DUTY_CYCLE_MODE);

  /*  You can choose between a continuous and a single-shot (on demand) mode with
      setConversionMode(). Parameters are:
      ADS1220_SINGLE_SHOT (default)
      ADS1220_CONTINUOUS
  */
  // ads.setConversionMode(ADS1220_CONTINUOUS);

  /* In order to obtain temperature values, choose enableTemperatureSensor(true); false will
     disable the temperature sensor. As long as the temperature sensor is enabled the ADS1220
     is blocked for this task. To obtain voltage values, you have to switch the sensor off. The
     temperature is queried with getTemperature();
  */
  //  ads.enableTemperatureSensor(true);
  //  ads.getTemperature(); // returns temperature as float

  /*
     setVRefSource() sets the the reference voltage source. Parameters are:
     ADS1220_VREF_INT          int. reference 2.048 V (default)
     ADS1220_VREF_REFP0_REFN0  ext. reference = Vrefp0 - Vrefn0
     ADS1220_VREF_REFP1_REFN1  ext. reference = Vrefp1 - Vrefn1 (REFP1=AIN0, REFN1=AIN3)
     ADS1220_VREF_AVDD_AVSS    ext. reference = supply voltage

     If you use the above options you also have to set the value of vRef "manually":
     setVRefValue_V(vRef in volts);

     Alternatively, you can set the reference voltage source and let the ADS1220 measure
     the reference. Be aware that this is not a measurement with highest precision.
     "Calibration" might be a bit misleading. You should take the lowest data rate (default)
     for most accurate results. You can use the following functions:
     setRefp0Refn0AsVefAndCalibrate();
     setRefp1Refn1AsVefAndCalibrate();
     setAvddAvssAsVrefAndCalibrate();
     setIntVRef();
     The latter function sets the default settings.

     Be aware that VREFPx must be >= VREFNx + 0.75V.
  */
  //  ads.setVRefSource(ADS1220_VREF_REFP0_REFN0);
  //  ads.setVRefValue_V(3.312);  // just an example
  //  or:
  //  ads.setRefp0Refn0AsVefAndCalibrate(); //or:
  //  ads.setRefp1Refn1AsVefAndCalibrate(); //or:
  //  ads.setAvddAvssAsVrefAndCalibrate(); //or:
  //  ads.setIntVRef();
  //  to query VRef:
  //  ads.getVRef_V(); // returns VRef as float

  /* You can set a filter to reduce 50 and or 60 Hz noise with setFIRFilter(); Parameters:
     ADS1220_NONE       no filter (default)
     ADS1220_50HZ_60HZ  50Hz and 60Hz filter
     ADS1220_50HZ       50Hz filter
     ADS1220_60HZ       60Hz filter
  */
  //  ads.setFIRFilter(ADS1220_50HZ_60HZ);

  /* When data is ready the DRDY pin will turn from HIGH to LOW. In addition, also the DOUT pin
     can be set as a data ready pin. The function is setDrdyMode(), parameters are:
     ADS1220_DRDY        only DRDY pin is indicating data readiness  (default);
     ADS1220_DOUT_DRDY   DRDY and DOUT pin indicate data readiness
  */
  //  ads.setDrdyMode(ADS1220_DOUT_DRDY);


  /* There is a switch between AIN3/REFN1 and AVSS. You can use this option to save power in
     bridge sensor applications.
     ADS1220_ALWAYS_OPEN    The switch is always open.
     ADS1220_SWITCH         Switch automatically closes when the START/SYNC command is sent
                            and opens when the POWERDOWN command is issued.
  */
  // ads.setLowSidePowerSwitch(ADS1220_SWITCH);

  /* The ADS1220 can provide two excitation currents, IDAC1 and IDAC2. It takes up to 200µs
     until the current is set up. The library includes a delay, so you don't have to add one.
     You can switch IDAC1 and IDAC2 on and off individually but the current is the same for 
     both.
     The ADS1220 will try to provide the voltage needed for the current you have chosen. This 
     voltage shall not exceed AVDD - 0.9V.

     ADS1220_IDAC_OFF         // default
     ADS1220_IDAC_10_MU_A     // set IDAC1/IDAC2 to 10 µA
     ADS1220_IDAC_50_MU_A     // 50 µA
     ADS1220_IDAC_100_MU_A    // 100 µA
     ADS1220_IDAC_250_MU_A    // 250 µA
     ADS1220_IDAC_500_MU_A    // 500 µA
     ADS1220_IDAC_1000_MU_A   // 1000 µA
     ADS1220_IDAC_1500_MU_A   // 1500 µA
  */
  // ads.setIdacCurrent(ADS1220_IDAC_50_MU_A);

  /* You can choose to which pin IDAC1 and IDAC2 are directed. The parameters are self-explaining.
     ADS1220_IDAC_NONE
     ADS1220_IDAC_AIN0_REFP1
     ADS1220_IDAC_AIN1
     ADS1220_IDAC_AIN2
     ADS1220_IDAC_AIN3_REFN1
     ADS1220_IDAC_REFP0
     ADS1220_IDAC_REFN0
  */
  //  ads.setIdac1Routing(ADS1220_IDAC_AIN0_REFP1);
  //  ads.setIdac2Routing(ads1220IdacRouting route);

} // end of setup()

void loop() {
  float result = 0.0;

  /* The following functions query measured values from the ADS1220. If you request values in
     single-shot mode, the conversion will be initiated automatically. The value will be delivered
     once DRDY goes LOW. This ensures that you will receive "fresh" data. This is particularly
     important when you change channels.
  */
  result = ads.getVoltage_mV(); // get result in millivolts
  //  ads.getVoltage_muV(); // get result in microvolts
  //  ads.getRawData();  // get raw result (signed 24 bit as long int)
  //  ads.getTemperature();  // get temperature (you need to enable the T-sensor before);
  Serial.println(result, 3);
  
  delay(2000);
}

 

Explanations to the sketch and settings

General settings / functions

  • start(): wakes up the ADS1220 from power down mode. Moreover, the function starts single or continuous measurements. However, you don’t need it for this purpose, since start() is called automatically when you request readings.
  • reset(): performs a reset of the ADS1220.
  • powerDown(): sends the ADS1220 into power down mode. In this state, the current consumption drops to typically 0.1 microampere.
  • setSPIClockSpeed(): sets the SPI clock frequency. Default setting is 4 MHz.

Setting the operating mode

You select the operating mode with setOperatingMode(). The following parameters are available:

  • ADS1220_NORMAL_MODE
  • ADS1220_DUTY_CYCLE_MODE
  • ADS1220_TURBO_MODE

The ADS1220 works according to the oversampling principle. In simple terms, the voltages are converted in high frequency (modulator frequency) and the result is averaged. The more measurements there are, the lower the noise.

The default setting is “Normal Mode”. The modulator frequency in normal mode is one 16th of the frequency of the ADS1220. When using the internal 4.096 MHz oscillator, the modulator frequency is 256 kHz accordingly.

In duty cycle mode, the ADS1220 operates as in normal mode, but with a duty cycle of 25%. The remaining 75% of the time, the ADS1220 is in power down mode. This saves considerable power, but increases the noise. At a data rate of x in duty cycle mode, the noise is comparable to the noise at a data rate of 4x in normal mode.

In Turbo Mode the modulator frequency is doubled. As a result, more transformations are performed in the same time. Accordingly, the noise decreases compared to Normal Mode, as long as you apply comparable data rates. You can also achieve the highest data rates in Turbo Mode.

Set the data rate

You can set the data rate with setDataRate().  The level ADS1220_DR_LVL_x with x = 0 to 6 are available as parameters. The actual data rate in PLC depends on the level and the operating mode. You can find a table in the sketch (or in the datasheet).

The maximum data rate is 2000 SPS. I checked this by determining the time required for 2000 measurements in continuous mode. The result was 1031 milliseconds, which is only a bit slower than theoretically expected. I chose the continuous mode because in this mode the time needed to request the next reading is irrelevant. In single-shot mode, the time for 2000 measurements was 1327 milliseconds.

In general, the higher the data rate, the lower the oversampling and correspondingly higher the noise. The data sheet contains a series of tables listing the noise level depending on the settings. I find the information in ENOB (=effective number of bits), i.e. the actual noise-free bits, particularly informative. At the slowest data rate in normal mode, the resolution is approx. 20 bits.

Setting the conversion mode

You can set the conversion mode with setConversionMode(). The two possible parameters are:

  • ADS1220_SINGLE_SHOT: Single-Shot = “On request”
  • ADS1220_CONTINUOUS: continuous

In general, the single-shot mode is preferable, as it is saves power. This is because the ADS1220 goes into power down mode after the respective measurement.

In both modes, the microcontroller waits after the request until the measured value is available. In a future version of the library, I will add an interrupt-based approach.

50 and 60 Hertz filter

You can apply a 50 Hz filter, a 60 Hz filter, or both filters at the same time. The function is setFIRFilter(). FIR stands for “finite impulse response”. You can find the parameters in the sketch above. For more details, please have a look at the datasheet.

DRDY settings

By default, the DRDY pin goes LOW when a measured value is ready. Additionally, the DOUT pin (=MISO) can be configured to go LOW as well. The function for this is called setDrdyMode(), the parameters available for selection are:

  • ADS1220_DRDY: only the DRDY pin shows a new measured value.
  • ADS1220_DOUT_DRDYDRDY and DOUT show a new measured value.

The ADS1220 – conclusion and outlook

The ADS1220 is a high-performance ADC with high resolution, high gain, high data rate, low noise and low power consumption. However, to fully utilize the ADS1220 and not to measure “nonsense”, one has to deal with its technical details more deeply than is the case, for example, with the 16-bit ADC ADS1115

The ADS1220 shows its advantages in applications where small voltage differences are to be measured, such as Wheatstone bridges or thermocouples. In the next post, I will deep-dive into these applications.

Compared to the 24-bit ADC HX711, the ADS1220 is more universally applicable. The HX711 should be familiar to those of you who have ever dealt with load cells. Its measuring range is limited to 20 or 40 millivolts, depending on the setting. 

Leave a Reply

Your email address will not be published.