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:
Please log in to post comments.
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