AD7190 Ultra-low noise 24-bit Sigma-Delta ADC

Introduction

Analog to digital converters are used to measure analog sensor values and convert them to digital representations so they can be processed by computer systems. Sensors with built-in ADCs and digital outputs are replacing many low-to-medium resolution analog sensors and ADC combinations, but these integrated digital sensors do not currently approach the performance of high resolution discrete components.

The AD7190 is a complete analog front end for high precision measurement applications. It contains an ultra low noise, 24-bit sigma-delta (Σ-Δ) analog-to-digital converter (ADC). The on-chip low noise gain stage means that signals of small amplitude can be interfaced directly to the ADC. This feature and a buffer stage greatly reduce the need for external components. There are too many features to describe here - see the manufacturer's web site and data sheet.

It's not difficult to interface with the mbed but it will take some effort. You will, however, be rewarded with one of the very best converter for digitizing low-to-medium bandwidth analog signals that have a high dynamic range. Some uses might include interfacing with analog sensors such as homebuilt seismometers, accelerometers, load cells and pressure sensors.

The AD7190 has a great number of capabilities and it is essential that you thoroughly read the data sheet and understand all the configuration settings and registers. You can download PC-based software from web site link above, called the "AD7190 Active Functional ADC Model", to help you understand how it works.

A couple of people have asked me for example code, so I'm posting what I have. It's just basic working code (not a complete library) and this brief description, but it should be enough to get started. You might want to read my notebook page on the BMP085 Pressure Sensor where I run some comparison tests against an AD7190 based solution.

Note that I am not affiliated in any way with Analog Devices, but I have had very good experience with their products and prefer to use them when possible.

What's Good

  • Incredibly high performing ADC for low to mid-speed, high-resolution measurements.
  • Low power consumption, especially compared to the TI ADS1256, which I previously used.
  • It's a mature product so the data sheet is complete, and Analog Devices has very good ones!
  • Although mature, it's definitely not obsolete - I haven't seen any better devices for this type of measurement and I have looked at many specs and tried several.
  • Highly integrated, flexible through software configuration, inexpensive for such a complete ADC solution.
  • Very low overhead - sigma-delta ADCs offload signal processing from the microcontroller.
  • Minimal external components required - no need for external buffer amp. With the internal amp you can load down the input with a low impedance sensor and/or filter caps.

Take Care With...

  • If you don't need solid 16 bits of data (or stretching for 17 or 18 bits) this will be overkill. Note that there are no inexpensive 24-bit ADCs that will give you 24 bits of usable data, and most signals are too noisy anyway. Make sure you read up on ENOB.
  • It's not a plug-and-play device, so if you want a simple solution that requires minimal programming, look elsewhere!
  • Smaller ADCs with fewer pins are available, so if minimizing space is a requirement this may not be the best solution, especially if you only need one channel.
  • The device can sequentially read multiple channels but doesn't do simultaneously sampling. However if you must do simultaneous sampling, it's possible to wire up multiple devices and synchronize them with a common clock and sync pins.
  • I wish chip designers wouldn't mux the data ready output pin with some other pin but I know they are trying to save pins. It's not a huge issue - just means you have to be a little careful.
  • As with any high resolution device, you must be careful to keep noise out of the circuit. It's a good practice to use differential input and a filter right in front of the ADC input.

Hardware

I bought an evaluation board some time ago (part number AD7190EBZ), which included a PC-based control program via USB. This made it easy to quickly get the device up and running, and was very useful for learning how the registers and bit settings worked. The eval board is currently in stock on the Analog Devices web site but I don't see it at distributors such as Digikey. Of course, as with most eval boards, many experimenters may consider the cost too high.

Fortunately the TSSOP package can be hand soldered with a little care and there are surfboards or similar prototype boards available for this package.

Wiring

The AD7190 uses a standard bi-directional 3-wire SPI interface. Data should be read from the device after it asserts an interrupt, which requires one additional pin on the mbed. You can simply hard wire the chip select - it's not used for framing or some other proprietary purpose.

You can run the AD7190 off of 5 volts from the mbed. Make sure you provide filter caps.

Here's the I/O wiring, I'll see about putting up a real diagram:

  • ADC pin 23 (RDY) -> mbed pin 8 (interrupt in)
  • ADC pin 4 (CS) <- can hardwire to ground in this configuration
  • ADC pin 3 (SCk) <- mbed pin 7 (SPI SCK)
  • ADC pin 23 (DOUT) -> mbed pin 6 (SPI MISO)
  • ADC pin 24 (DIN) <- mbed pin 5 (SPI MOSI)

Notes: ADC pin 23 (DOUT/RDY) is dual purpose.It's used for both data output and also outputs a separate RDY signal. Since these are never used at the same time, there is no conflict but if you use RDY to trigger an interrup, the interrupt must be disabled while data is being transferred to prevent false interrupts.

You don't absolutely need to use RDY to generate an interrupt on the mbed for processing the ADC data, but it is an efficient technique - a good practice. The alternative is to use use a polling mechanism, which can be justified to use in some circumstances.

Voltage Reference

ADCs need a reference voltage, which can be handled in many ways. It's a huge topic and I'm no expert - just a dedicated experimenter. ADC manufacturers provide recommedations via data sheets and tech notes. In many cases, it's easiest and best to use your sensor input voltage as a reference so the system is ratiometric. Even if the reference voltage isn't in exactly the right range to get optimum scaling, the huge dynamic range of the ADC allows you to throw away bits to scale the output to where you need it. Make certain the reference is filtered.

Be very careful of adding temperature sensitive devices that might independently affect the reference voltage. You could breathe on the circuit and have the output change radically. Adjustable resistors should be avoided! I always test my ADC circuits by hitting them with a heat gun to see what happens. Some change is to be expected...just make sure you know how much! If necessary you can use a software calibration routine with the internal temperature sensor. If you have a sensor driven by contant current rather than voltage, referencing can get a little tricky.

Notes on Example Code

This code only demonstrates basic operation, and there is no error handling. I wrote it some time ago for a quick demo project and it worked reliably. Now that I'm finally posting it, I don't remember all the details. I don't plan to create a library for the AD7190 (unless somebody wants to pay me ;-)

I wasn't able to get continuous read mode to work for reasons that are unclear, but I didn't try very hard to troubleshoot the problem.

This code also returns the status register when reading data, and it's probably a good idea to use during development work especially if you are using multiple channels. This makes it easy to check that that data is aligned with the expected channel.

You can increase resolution a bit further if you follow the data register output code with a simple averaging or EWMA filter.

Troubleshooting

The AD7190 is a complex chip to program - I spent a lot of time reading the datasheet and figuring out the chip's behavior and how to program the registers. If you are not able to invest much time, you should consider using a simpler ADC.

If you're having trouble getting things to work, start with simplest operation and get that working before moving on to more complex processing. First try reading the ID or the configuration register - you can do this before you setting up interrupts or a processing loop. Once that's working, try writing to a register such as the 8-bit GPOCON resister, then read it back and compare results.

Of cousre before you can read/write to the ADC, the mbed SPI port and the wiring must be set up correctly. It's easy to make a wiring mistake, so double check and ask someone else to verify it. If you are still having trouble, you can use an oscilloscope or logic analyser to check for signals on the pins.

Once you get basic register read-write working, then you can move on to building a continuous loop for data processing.

////////////////////////////////////////////////////////////////////////////////////
// Basic, minimal demo code for interfacing the AD7190 to the mbed
// by Tom Kreyche tkreyche@well.com
// for anyone to use or redistribute - use at your own risk!
////////////////////////////////////////////////////////////////////////////////////

#include "mbed.h"

// for ADC
#define STATUS_REG      0x00
#define MODE_REG        0x08
#define CONFIG_REG      0x10
#define DATA_REG        0x18
#define ID_REG          0x20
#define GPOCON_REG      0x28
#define OFFSET_REG      0x30
#define FULLSCALE_REG   0x38

void drSub();
void ADC_Reset();
uint32_t ADC_ReadReg( uint32_t reg );
void ADC_WriteReg( uint32_t reg, uint32_t val );
uint32_t ADC_ReadData( void );

SPI spi(p5, p6, p7); // mosi, miso, sclk
Serial pc(USBTX, USBRX); // tx, rx
InterruptIn dr(p8);
uint32_t drFlag;


int main() {

    uint32_t reg = 0;
    uint32_t sta = 0;

    // set up interrupts
    __disable_irq();

    // set up ADC data ready
    drFlag = 0;
    dr.mode(PullUp);
    dr.fall(&drSub);

    // set up SPI interface
    spi.format(8,3);
    spi.frequency(500000);

    // reset the ADC and read the status registers
    ADC_Reset();

    reg = ADC_ReadReg(STATUS_REG);
    pc.printf("Status: 0x%X\n",reg);

    reg = ADC_ReadReg(MODE_REG);
    pc.printf("Mode: 0x%X\n",reg);

    reg = ADC_ReadReg(CONFIG_REG);
    pc.printf("Config: 0x%X\n",reg);

    reg = ADC_ReadReg(ID_REG);
    pc.printf("ID: 0x%X\n",reg);

    reg = ADC_ReadReg(GPOCON_REG);
    pc.printf("GPOCON: 0x%X\n",reg);

    reg = ADC_ReadReg(OFFSET_REG);
    pc.printf("Offset: 0x%X\n",reg);

    reg = ADC_ReadReg(FULLSCALE_REG);
    pc.printf("Fullscale: 0x%X\n",reg);

    // modify default configuration
    // Some config options, incomplete list, see data sheet for details
    //ADC_WriteReg(CONFIG_REG, 0x80C008);  //ain3, ain4 to aincom, no buf
    //ADC_WriteReg(CONFIG_REG, 0x804008);  //ain3 only no buf
    ADC_WriteReg(CONFIG_REG, 0x804018);    //ain3 only yes buf, use for big input caps, no single
    //ADC_WriteReg(CONFIG_REG, 0x804818);  //ain3 only yes buf, use for big input caps, single    
    reg = ADC_ReadReg(CONFIG_REG);
    pc.printf("Config: 0x%X\n",reg);


    // More incomplete config options
    // ADC_WriteReg(MODE_REG, 0x188050);   //no single,10 Hz /2 = 5 Hz/ch, SINC3
    // single channel, no status
    //ADC_WriteReg(MODE_REG, 0x0880A0);   //no single,10 Hz SINC3
    ADC_WriteReg(MODE_REG, 0x088050);     //no single,20 Hz SINC3

    reg = ADC_ReadReg(MODE_REG);
    pc.printf("Mode: 0x%X\n",reg);


    // Put ADC in continuous read mode
    // can't get to work properly, see p. 32 data sheet
    //spi.write(0x5C);


    // ready for ADC data processing
    __enable_irq();

    // mbed runs continuous loop, services interrupt as required
    while (1) {

        // handle ADC data ready flag
        if (drFlag) {
            // disable interrupts so read data doesn't trigger more interrupts, pins are muxed
            __disable_irq();
            reg = ADC_ReadData();
            __enable_irq();
            sta = 0x0000000F & reg;
            reg >>= 15;

            pc.printf("0x%X %d ",sta, reg);

            }
            drFlag = 0;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////
// handle data ready interrupt, ust sets data ready flag
////////////////////////////////////////////////////////////////////////////////////

void drSub() {
    drFlag = 1;
}

////////////////////////////////////////////////////////////////////////////////////
// reset the AD7190
////////////////////////////////////////////////////////////////////////////////////

void ADC_Reset() {
    for (int i = 0; i < 10; i++) {
        spi.write(0xFF);
    }
    wait_ms(100);
}

////////////////////////////////////////////////////////////////////////////////////
// read an AD7190 register
// reads 8 or 24 bit registers only, not 32
// won't work for data reads
////////////////////////////////////////////////////////////////////////////////////

uint32_t ADC_ReadReg( uint32_t reg ) {
    uint32_t data_length = 0;
    uint32_t register_value = 0;
    uint32_t rx_data[3] = {0, 0, 0};

    // determine data length
    switch (reg) {
        // 8 bit result
        case STATUS_REG:
        case ID_REG:
        case GPOCON_REG:
            data_length = 1;
            break;

        // 24 bit result
        case MODE_REG:
        case CONFIG_REG:
        case DATA_REG:
        case OFFSET_REG:
        case FULLSCALE_REG:
            data_length = 3;
            break;
    }

    // convert write address to read address
    reg |= 0x40;

    // send location to comm register
    spi.write(reg);

    // get the data
    if (data_length == 3) {
        rx_data[0] = spi.write(0xFF);
        rx_data[0] <<= 16;
        rx_data[1] = spi.write(0xFF);
        rx_data[1] <<= 8;
        rx_data[2] = spi.write(0xFF);
        register_value = rx_data[2] | rx_data[1] | rx_data[0];
    } else {
        register_value = spi.write(0xFF);
    }
    return (register_value);
}

////////////////////////////////////////////////////////////////////////////////////
// read  AD7190 data, 32 bits
// just streamlined version of read register routine
// use this becuase I can't get continuous read to work
// also returns status register for channel information
////////////////////////////////////////////////////////////////////////////////////

uint32_t ADC_ReadData( void ) {

    uint32_t rx_data[4] = {0, 0, 0, 0};

    // send location to comm register
    // shouldn't have to use this for continuous read
    spi.write(DATA_REG | 0x40);

    // get the data
    rx_data[0] = spi.write(0xFF);   // data
    rx_data[1] = spi.write(0xFF);   // data
    rx_data[2] = spi.write(0xFF);   // data
    rx_data[3] = spi.write(0xFF);   // status

    rx_data[0] <<= 24;
    rx_data[1] <<= 16;
    rx_data[2] <<= 8;

    return (rx_data[3] | rx_data[2] | rx_data[1] | rx_data[0]);
}

////////////////////////////////////////////////////////////////////////////////////
// write to AD7190 register
// handles 8 and 24 bit registers
////////////////////////////////////////////////////////////////////////////////////

void ADC_WriteReg( uint32_t reg, uint32_t val ) {

    uint32_t data_length = 0;

    // determine data length
    switch (reg) {
        // 8 bit
        case ID_REG:
        case GPOCON_REG:
            data_length = 1;
            break;

        // 24 bit
        case MODE_REG:
        case CONFIG_REG:
        case OFFSET_REG:
        case FULLSCALE_REG:
            data_length = 3;
            break;
    }

    // send location to comm register
    spi.write(reg);

    // send the data
    if (data_length == 3) {
        spi.write ((val & 0x00FF0000) >> 16);
        spi.write((val & 0x0000FF00) >> 8);
    }
    spi.write(val & 0x000000FF);

    return;
}


11 comments on AD7190 Ultra-low noise 24-bit Sigma-Delta ADC:

28 Feb 2012

Hi Tom, Your work insterests me a lot. I would be very glad if you take me informed in your progress. I have to read 4 chanels * 24 bits, but no time to really start the project. Thanks in advance. Henri

29 Mar 2013

Hello Tom, I need to make vibration measurements by using accelerometer/s (one ore more piezo-electric) located inside a platform remotely connected to a PC by using a wireless link (aerial for example). For this purpose, I need to make a signal conditioning and conversion to send digital data over the link. For this purpose I'm thinking to use one (or more) mbed devices and my question is, based on your experience, if this component could be used for this purpose or if you know if a similar one can be located to make expected function (by using mbed and SPI of course). Thanks to share your experience with us.

21 May 2013

Julian,

I just wanted to emphasize that the AD7190 is a very high resolution, ultra-low noise device...probably one of the best on the market. However there is a tradeoff - it's somewhat complex to configure and use, a little expensive and requires care to layout properly to minimze external noise.

That said, the AD7190 would work very well for that type of application...vibration measurements, seismometers and similar equipment typically has need for very high dynamic range.

Of course you should consider how many bits of resolution you really need, and how much noise your sensor outputs. With this device, you can get 16 bits (17 bits or possibly 18 with care). Many applications do not need this high resolution.

...Tom

30 May 2013

I understand your code pretty much, but what's the interrupt on pin 8 doing?

30 May 2013

Jay - Sorry I didn't include a wiring diagram to make things clear...will find some time to fix.

I have the ADC set up to generate an interrupt when the data conversion is complete - the signal is called "Data Ready." The mbed then responds to the interrupt to read and process the data.

Generally I prefer to use an interrupt for peripherals rather than polling them since it's more efficient. But in some cases I get lazy or it doesn't matter so I using polling.

31 May 2013

I'd be very curious what goes into p8, is it the DOUT from the ADC?

Jay

31 May 2013

Here's the I/O wiring, I'll see about putting up a real diagram.

ADC pin 23 (DOUT/RDY) -> mbed pin 8 (interrupt in)

ADC pin 4 (CS) <- can hardwire to ground in this configuration

ADC pin 3 (SCk) <- mbed pin 7 (SPI SCK)

ADC pin 23 (DOUT) -> mbed pin 6 (SPI MISO)

ADC pin 24 (DIN) <- mbed pin 5 (SPI MOSI)

Notes: ADC generates RDY, triggering interrupt on the mbed when conversion is complete. ADC pin 23 is dual purpose DOUT/RDY, there is a line in the code to disable the interrupt while data is being transferred to prevent false interrupts.

31 May 2013

I guess you mean:

ADC pin 23 (DOUT) -> mbed pin 6 (SPI MISO)

?

05 Jul 2013

Hi Tom

Thanks for the C code example. I am using the 7190 in an instrument that uses VB6 for control, and I managed to get the continuous read to work (I noticed the comment in your code). The way to do it is to write the Comm register first to set it up, then to simply read the data register continuously - with appropriate settle time in between. Drop me a line if you need details.

Tony

02 Aug 2014

Hi Tom,

Thanks for uploading your code. It's extremely helpful.

Could you possibly upload a picture of how you wired to the evaluation board? I'm running the same setup with a slightly different code and I can read all the registers correctly except the data register. I can't imagine what the problem could be in the software so I assume it has to be the hardware.

Thanks, Brad

06 Aug 2014

Brad,

Oh man, I haven't looked at the board in while and don't currently have it hooked up to my mbed.

I would have to start all over again, but it would only be a couple hours work. I had the board wired up to a pressure sensor but am thinking of trying it will an accelerometer, or hooking it up to my surplus seismometer.

If I get around to doing that, I'll post a new project with details on how to set up the eval board.

Since you are reading the other registers you have it 90% solved. Of course that last 10% can be a killer!

...Tom

Please log in to post comments.