0

Great 18-bit ADC for GPS and Proto Shield sensor logging
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Great 18-bit ADC for GPS and Proto Shield sensor logging

by fat16lib on Sat Aug 15, 2009 12:55 pm

The Arduino's ADC is not suitable for many sensors like thermocouples or bridge sensing for pressure, strain, and force. Also it has fairly low dynamic impedance since it has no buffer amplifier.

I have been looking for a simple solution to this problem for about a year. I have found a part that is accurate and very simple to use. The part is a Delta-Sigma ADC system from Microchip. I have used it on an Adafruit Proto Shield and have connected it directly to a GPS Shield to log sensor data. See below for directions for using it.

I have compared the accuracy of several of these parts with a Fluke (0.09% DC V) meter. The results agree to about 1 part in a 1000 with no calibration of the ADC for measurements in the one to two volt range.

Here are the main features of this part:

High accuracy 18-bit Delta-Sigma ADC with differential inputs.
MCP3424 14-pin SOIC with 4 channel or MCP3422 8-pin SOIC with 2 channel MUX.
Digi-Key prices MCP3422 $3.14, MCP3424 $4.26.

On-board Voltage Reference (VREF) 2.048V ± 0.05%
On-board Programmable Gain Amplifier (PGA) with gains of 1, 2, 4 or 8.
Differential input full-scale range: -VREF to +VREF
Self calibration of internal offset and gain for each conversion.
Conversion bit resolution options: 12, 14, 16, or 18 bits.
Conversion rate:3.75 SPS (18 bits), 15 SPS (16 bits), 60 SPS (14 bits), 240 SPS (12 bits).
Detectable input signal level is 2 micro-volts for LSB in 18-bit mode with PGA = 8.
High input impedance, 2.25 M ohm at gain of 1.
One-shot or continuous conversion option.
On-board oscillator.
2-wire Interface allows up to eight chips with 32 channels to be connected to one Arduino using just the two I2C pins.

How to make it:
Solder the part on to the SOIC area of an Adafruit Proto Shield or SOIC breakout board.
Connect VSS pin to ground.
Connect VDD pin to +5V.
Connect SDA pin to Arduino SDA (Analog pin 4)
Connect SCL pin to Arduino SCL (Analog pin 5)
Connect input channels to your sensor.

That’s it, no other parts required.

Here is a photo of the 14-pin part soldered on a Proto Shield (upper left SOIC area) for experiments and logging of sensor data. And a break-out board (above Proto Shield) that shows how the 8-pin part can be used as an 8-pin DIP or if the pins are replaced by wires it can be directly connected to a GPS Shield or bare Arduino.
mcp3424adc.jpg
mcp3424adc.jpg (103.6 KiB) Viewed 30757 times


Simple test program that reads channel one in 12-bit mode (default mode at power on). Output is the voltage in mv, no calibration required.

Code: Select all | TOGGLE FULL SIZE
#include <Wire.h>
#define MCP3422_ADDRESS 0X68

void setup()
{
  Serial.begin(9600); 
  Wire.begin();
}

void loop()
{
  Wire.requestFrom(MCP3422_ADDRESS, 3);
  if (Wire.available() != 3) {
    Serial.println("Wire.available failed");
    while(1);
  }
  int16_t v = (Wire.receive() << 8);
  v |= Wire.receive();
  // read but ignore status
  uint8_t s = Wire.receive();
  //print voltage from channel one in millivolts
  Serial.print(v);Serial.println(" mv");
  delay(500);
}
Last edited by fat16lib on Mon Aug 17, 2009 1:19 pm, edited 1 time in total.
fat16lib
 
Posts: 593
Joined: Wed Dec 24, 2008 1:54 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by adafruit on Sat Aug 15, 2009 11:28 pm

wow beautiful! we'll post this up!

adafruit
 
Posts: 12141
Joined: Thu Apr 06, 2006 4:21 pm
Location: nyc

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by fat16lib on Thu Sep 10, 2009 9:37 am

Here is a demo sketch you can use to explore the MCP342X ADCs.

Code: Select all | TOGGLE FULL SIZE
#include <Wire.h>

// I2C address for MCP3422 - base address for MCP3424
#define MCP3422_ADDRESS 0X68

// fields in configuration register
#define MCP342X_GAIN_FIELD 0X03 // PGA field
#define MCP342X_GAIN_X1    0X00 // PGA gain X1
#define MCP342X_GAIN_X2    0X01 // PGA gain X2
#define MCP342X_GAIN_X4    0X02 // PGA gain X4
#define MCP342X_GAIN_X8    0X03 // PGA gain X8

#define MCP342X_RES_FIELD  0X0C // resolution/rate field
#define MCP342X_RES_SHIFT  2    // shift to low bits
#define MCP342X_12_BIT     0X00 // 12-bit 240 SPS
#define MCP342X_14_BIT     0X04 // 14-bit 60 SPS
#define MCP342X_16_BIT     0X08 // 16-bit 15 SPS
#define MCP342X_18_BIT     0X0C // 18-bit 3.75 SPS

#define MCP342X_CONTINUOUS 0X10 // 1 = continuous, 0 = one-shot

#define MCP342X_CHAN_FIELD 0X60 // channel field
#define MCP342X_CHANNEL_1  0X00 // select MUX channel 1
#define MCP342X_CHANNEL_2  0X20 // select MUX channel 2
#define MCP342X_CHANNEL_3  0X40 // select MUX channel 3
#define MCP342X_CHANNEL_4  0X60 // select MUX channel 4

#define MCP342X_START      0X80 // write: start a conversion
#define MCP342X_BUSY       0X80 // read: output not ready

//------------------------------------------------------------------------
// default adc congifuration register - resolution and gain added in setup()
uint8_t adcConfig = MCP342X_START | MCP342X_CHANNEL_1 | MCP342X_CONTINUOUS;
// divisor to convert ADC reading to milivolts
uint16_t mvDivisor;
//------------------------------------------------------------------------------
void halt(void)
{
  Serial.println("Halted");
  while(1);
}
//------------------------------------------------------------------------------
// read mcp342x data
uint8_t mcp342xRead(int32_t &data)
{
  // pointer used to form int32 data
  uint8_t *p = (uint8_t *)&data;
  // timeout - not really needed?
  uint32_t start = millis();
  do {
    // assume 18-bit mode
    Wire.requestFrom(MCP3422_ADDRESS, 4);
    if (Wire.available() != 4) {
      Serial.println("read failed");
      return false;
    }
    for (int8_t i = 2; i >= 0; i--) {
      p[i] = Wire.receive();
    }
    // extend sign bits
    p[3] = p[2] & 0X80 ? 0XFF : 0;
    // read config/status byte
    uint8_t s = Wire.receive();
    if ((s & MCP342X_RES_FIELD) != MCP342X_18_BIT) {
      // not 18 bits - shift bytes for 12, 14, or 16 bits
      p[0] = p[1];
      p[1] = p[2];
      p[2] = p[3];
    }
    if ((s & MCP342X_BUSY) == 0) return true;
  } while (millis() - start < 500); //allows rollover of millis()
  Serial.println("read timeout");
  return false;
}
//------------------------------------------------------------------------------
// write mcp342x configuration byte
uint8_t mcp342xWrite(uint8_t config)
{
  Wire.beginTransmission(MCP3422_ADDRESS);
  Wire.send(config);
  Wire.endTransmission();
}

//------------------------------------------------------------------------------
void setup()
{
  Serial.begin(9600); 
  Wire.begin();
  uint8_t chan = 0XFF, gain = 0XFF, res = 0XFF;
  do {
    Serial.flush();
    Serial.print("\nEnter channel (1, 2, 3, 4): ");
    while (Serial.available() == 0);
    chan = Serial.read() - '1';
  } while(chan > 3);
  Serial.println(chan + 1, DEC);
  do {
    Serial.println();
    Serial.flush();
    Serial.print("Enter gain (1, 2, 4, or 8): ");
    while(Serial.available() < 1);
    switch (Serial.read()) {
      case '1': gain = 0; break;
      case '2': gain = 1; break;
      case '4': gain = 2; break;
      case '8': gain = 3; break;
    }
  } while (gain > 3);
  Serial.println(1 << gain, DEC);
  do {
    Serial.flush();
    Serial.println();
    Serial.print("Enter resolution (12, 14, 16, or 18): ");
    while (Serial.available() < 2);
    if (Serial.read() != '1') continue;
    switch (Serial.read()) {
      case '2': res = 0; break;
      case '4': res = 1; break;
      case '6': res = 2; break;
      case '8': res = 3; break;
    }
  } while (res > 3);
  Serial.println(12 + 2*res, DEC);

  adcConfig |= chan << 5 | res << 2 | gain;
  // divisor to convert ADC reading to millivolts
  mvDivisor = 1 << (gain + 2*res);
}
//------------------------------------------------------------------------------
void loop()
{
  // Really only needed write once in contiguous mode. Could move to setup.         
  mcp342xWrite(adcConfig);
  int32_t data;
  if (!mcp342xRead(data)) halt();
  // voltage in millivolts
  double mv = (double)data/mvDivisor;
  // convert reading to microvolts
  int32_t uv = (data*1000L)/mvDivisor;
  Serial.print(mv);Serial.print(" mv, ");
  Serial.print(uv);Serial.println(" uv");
  delay(1000);
}
fat16lib
 
Posts: 593
Joined: Wed Dec 24, 2008 1:54 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by fat16lib on Sat Sep 12, 2009 12:11 pm

Here is a sketch to verify that a MCP342x ADC responds on the I2C bus. Run this as a first step in debugging your setup.

Code: Select all | TOGGLE FULL SIZE
#include <Wire.h>
void setup(void)
{
  Serial.begin(9600);
  Wire.begin();
  for (uint8_t add = 0X0; add < 0X80; add++) {
    Wire.requestFrom(add, (uint8_t)1);
    if (Wire.available()) {
      Serial.print("Add: ");
      Serial.println(add, HEX);
    }
  }
  Serial.println("Done");
}
void loop(void){}

It should print this:
Add: 68
Done
fat16lib
 
Posts: 593
Joined: Wed Dec 24, 2008 1:54 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by chocjulio on Sat Oct 31, 2009 7:04 pm

Hey great post i followed your advice and im now testing these adc. my first try was bad i fried my adc. Second attempt was great i was able to measure my device and it was accurate except when no signal was there it jumped to 10v. which did not effect adc just maxed it out. when i came back for day two progrraming i was no longer able to read any signals the only thing the code returned was read timeout. so i uploaded to debug code and it returns 68 done! in the docs it mentions sending a por(power on reset) command how do you do this in the arduino wire code? Or is there a way to reset this chip or have i fried another one? how do you know?

Thanks Chocjulio
chocjulio
 
Posts: 1
Joined: Sat Oct 31, 2009 6:54 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by TomasReabe on Fri Nov 13, 2009 11:25 pm

Ok So I like how the first example works as far as simple code but I can't figure out how to put in the inputs to set up channel 1, 1x gain, and 18bit encoding. and still output mv like it did before. Can you walk me thru how your code works so I can edit it to what I need. Sorry if my thoughts are not nice and inline a newborn has a way of draining your brain :) Thanks
TomasReabe
 
Posts: 48
Joined: Tue Jul 14, 2009 10:14 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by fat16lib on Fri Nov 13, 2009 11:44 pm

TomasReabe,
Look at the second example. it lets you select any channel, gain, and resolution.
fat16lib
 
Posts: 593
Joined: Wed Dec 24, 2008 1:54 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by njtoll on Tue Jan 26, 2010 5:41 pm

Can someone post an example for a single ended sensor connected or a 4-20ma example? I've got the ADC soldered and working correctly, but I am having trouble getting meaningful results from sensors.
njtoll
 
Posts: 1
Joined: Tue Jan 26, 2010 5:38 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by adafruit on Tue Jan 26, 2010 11:59 pm

put a resistor across the sensor pins, then measure the voltage

adafruit
 
Posts: 12141
Joined: Thu Apr 06, 2006 4:21 pm
Location: nyc

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by TomasReabe on Mon Apr 19, 2010 1:47 pm

I have tried to take your code and turn it into a function so I can feed in the: Gain, Resolution, and Channel into the function and the function would change, and re-write the adcConfig to the ADC, it would also update the mvDivisor, and return the read data. I was not looking to add an address field or a continuous/ one-shot field but this would be fairly straight forward.

I have a few questions, first what does the "|" operator do?
you use it to set the adcConfig byte, is it an add, flip or set function? I am trying to see if I need to zero the adcConfig byte every time I call the function or what?

How is the data returned from the mcp342xRead Function, it looks as if you are only returning a uint_8 variable but for an 18 bit reading you would need the a int32, or are you sending back the bytes and in the *p somehow?

If I decided to use only 12, 14, or 16 bit than I could use an "int"(int_16) to save the reading from the ADC, right? and then I would use the format seen in your first set of code.

Would you look through it to check for any errors, I do not fully understand what is happening in your code but I think I know enough to be dangerous.

Thanks
Code: Select all | TOGGLE FULL SIZE
#include <Wire.h>

// I2C address for MCP3422 - base address for MCP3424
#define MCP3422_ADDRESS 0X68

// fields in configuration register
#define MCP342X_GAIN_FIELD 0X03 // PGA field
#define MCP342X_GAIN_X1    0X00 // PGA gain X1
#define MCP342X_GAIN_X2    0X01 // PGA gain X2
#define MCP342X_GAIN_X4    0X02 // PGA gain X4
#define MCP342X_GAIN_X8    0X03 // PGA gain X8

#define MCP342X_RES_FIELD  0X0C // resolution/rate field
#define MCP342X_RES_SHIFT  2    // shift to low bits
#define MCP342X_12_BIT     0X00 // 12-bit 240 SPS
#define MCP342X_14_BIT     0X04 // 14-bit 60 SPS
#define MCP342X_16_BIT     0X08 // 16-bit 15 SPS
#define MCP342X_18_BIT     0X0C // 18-bit 3.75 SPS

#define MCP342X_CONTINUOUS 0X10 // 1 = continuous, 0 = one-shot

#define MCP342X_CHAN_FIELD 0X60 // channel field
#define MCP342X_CHANNEL_1  0X00 // select MUX channel 1
#define MCP342X_CHANNEL_2  0X20 // select MUX channel 2
#define MCP342X_CHANNEL_3  0X40 // select MUX channel 3
#define MCP342X_CHANNEL_4  0X60 // select MUX channel 4

#define MCP342X_START      0X80 // write: start a conversion
#define MCP342X_BUSY       0X80 // read: output not ready

//------------------------------------------------------------------------
// default adc congifuration register - resolution and gain added in setup()
uint8_t adcConfig = MCP342X_START | MCP342X_CHANNEL_1 | MCP342X_CONTINUOUS;
// divisor to convert ADC reading to milivolts
uint16_t mvDivisor;
//------------------------------------------------------------------------------
void halt(void)
{
  Serial.println("Halted");
  while(1);
}
//------------------------------------------------------------------------------
// read mcp342x data
uint8_t mcp342xRead(int32_t &data, uint8_t chan, uint8_t gain, uint8_t res)
{

  adcConfig |= chan << 5 | res << 2 | gain;
  // divisor to convert ADC reading to millivolts
  mvDivisor = 1 << (gain + 2*res);     
  mcp342xWrite(adcConfig);
  // pointer used to form int32 data
  uint8_t *p = (uint8_t *)&data;
  // timeout - not really needed?
  uint32_t start = millis();
  do {
    // assume 18-bit mode
    Wire.requestFrom(MCP3422_ADDRESS, 4);
    if (Wire.available() != 4) {
      Serial.println("read failed");
      return false;
    }
    for (int8_t i = 2; i >= 0; i--) {
      p[i] = Wire.receive();
    }
    // extend sign bits
    p[3] = p[2] & 0X80 ? 0XFF : 0;
    // read config/status byte
    uint8_t s = Wire.receive();
    if ((s & MCP342X_RES_FIELD) != MCP342X_18_BIT) {
      // not 18 bits - shift bytes for 12, 14, or 16 bits
      p[0] = p[1];
      p[1] = p[2];
      p[2] = p[3];
    }
    if ((s & MCP342X_BUSY) == 0) return true;
  } while (millis() - start < 500); //allows rollover of millis()
  Serial.println("read timeout");
  return false;
}
//------------------------------------------------------------------------------
// write mcp342x configuration byte
uint8_t mcp342xWrite(uint8_t config)
{
  Wire.beginTransmission(MCP3422_ADDRESS);
  Wire.send(config);
  Wire.endTransmission();
}

//------------------------------------------------------------------------------
void setup()
{
  Serial.begin(9600); 
  Wire.begin();
}
//------------------------------------------------------------------------------
void loop()
{
  int32_t data;
  uint8_t chan=0;
  // banned=chan+1
  uint8_t gain=0;
  // Gain=2^gain
  uint8_t res=0;
  // Res= 12+res*2
  if (!mcp342xRead(data, chan, gain, res)) halt();
  // voltage in millivolts
  double mv = (double)data/mvDivisor;
  // convert reading to microvolts
  int32_t uv = (data*1000L)/mvDivisor;
  Serial.print(mv);Serial.print(" mv, ");
  Serial.print(uv);Serial.println(" uv");
  delay(1000);
}
TomasReabe
 
Posts: 48
Joined: Tue Jul 14, 2009 10:14 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by fat16lib on Mon Apr 19, 2010 5:23 pm

I don't have time to look for errors in your changes to the program. Thousands of people use and modify programs I have written so I can't debug modifications like this.
fat16lib
 
Posts: 593
Joined: Wed Dec 24, 2008 1:54 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by TomasReabe on Mon Apr 19, 2010 10:11 pm

Thank you for your responce, So can you just say yes or no to if the statement is right.

uint8_t adcConfig = MCP342X_START | MCP342X_CHANNEL_1 | MCP342X_CONTINUOUS;
would be the same as
uint8_t adcConfig = MCP342X_START + MCP342X_CHANNEL_1 + MCP342X_CONTINUOUS;
TomasReabe
 
Posts: 48
Joined: Tue Jul 14, 2009 10:14 pm

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by adafruit_support_bill on Tue Apr 20, 2010 7:44 am

The values 0x80, ox00 and 0x10 do not have any bits in common, so the result of the additions will be the same as the result of the bitwise-ORs.
Code: Select all | TOGGLE FULL SIZE
#define MCP342X_START      0X80 // write: start a conversion
#define MCP342X_CHANNEL_1  0X00 // select MUX channel 1
#define MCP342X_CONTINUOUS 0X10 // 1 = continuous, 0 = one-shot

adafruit_support_bill
 
Posts: 63486
Joined: Sat Feb 07, 2009 10:11 am

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by netmage on Mon Oct 25, 2010 7:11 pm

How would one integrate the Wire libraries for communicating w/ the Sigma ADC via I2C using Firmata....?

I just stumbled onto this thread today and am looking to interface 3 x R22D Oygen cells that should kick out ~8-250mV when exposed to varying partial pressures of O2.

Plan was to use the FirmataVB libraries to code a front end in Visual Basic....

-Tim
User avatar
netmage
 
Posts: 7
Joined: Tue Sep 21, 2010 3:03 pm
Location: Coral Springs, FL

Re: Great 18-bit ADC for GPS and Proto Shield sensor logging

by wbp on Thu Mar 10, 2011 2:15 am

I realize this thread is a bit old, but has anyone else noticed that there is an error in the mcp342xRead function of the "explore" demo sketch? If you add timing to the main loop, you will discover that 18 bit mode is *far* faster than 12, 14, or 16. That's because the mcp342xRead function is waiting for 4 bytes, and for 12 thru 16 bit conversions, the chip only sends 3. For the others, the read routine sits waiting for a second conversion to complete, and the byte it thinks is the config data is really part of the next conversion data...

Even with this fixed, it appears the chip is optimized for the 18 bit mode. I'm seeing about 8 ms per sample for 18 bits, and 31 or so for 16 bits!

William

wbp
 
Posts: 239
Joined: Mon Mar 07, 2011 1:18 pm

Please be positive and constructive with your questions and comments.