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

Adafruit Ethernet, Motor, Proto, Wave, Datalogger, GPS Shields - etc!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
wbp
 
Posts: 260
Joined: Mon Mar 07, 2011 1:18 pm

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

Post by wbp »

Here is an improved version of the mcp342xRead function:

Code: Select all

// read mcp342x data - updated 10mar11/wbp
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();
  if ((adcConfig & MCP342X_RES_FIELD) == MCP342X_18_BIT)  // in 18 bit mode?
  {
    do {   // 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_BUSY) == 0) return true;
    } while (millis() - start < 500); //allows rollover of millis()
  }
  else
  {
    do {  // 12-bit to 16-bit mode
      Wire.requestFrom(MCP3422_ADDRESS, 3);
      if (Wire.available() != 3) {
        Serial.println("read failed");
        return false;
      }
      p[1] = Wire.receive();
      p[0] = Wire.receive();
      // extend sign bits
      p[2] = p[1] & 0X80 ? 0XFF : 0;
      p[3] = p[2];
      // read config/status byte
      uint8_t s = Wire.receive();
      if ((s & MCP342X_BUSY) == 0) return true;
    } while (millis() - start < 500); //allows rollover of millis()
  }
  Serial.println("read timeout");
  return false;
}

User avatar
queglay
 
Posts: 7
Joined: Mon Apr 11, 2011 3:09 am

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

Post by queglay »

i managed to get this working, thanks- its amazing.

something that isn't clear to me is the gain control though.

the manual on the MCP3424 states higher gain values (2,4,8) will allow measurement of finer or smaller input voltages.
but testing this with gain set to 1x, i might get a measurement of say 1600mv for an input.
with gain set to 8, the measurement becomes 200mv for the same 1600mv i put in.

this doesn't really make sense to me. is perhaps the math for the different gain settings incorrect?

unless the gain is supposed to enable higher voltages on the input, eg gain of 8x mean the max voltage in becomes 8x 2.048v = 16.384v
but the manual doesn't state anything about being able to put in voltages that high.

Is anyone able to throw me a bone on how the gain is supposed to work and what its for?

Thanks for any help.

User avatar
fat16lib
 
Posts: 595
Joined: Wed Dec 24, 2008 1:54 pm

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

Post by fat16lib »

The MCP3424 has an amplifier before its ADC. If you set gain to eight then the max input voltage will be the max ADC voltage, 2048 mv, divided by the gain or 256 mv.

Gain of eight allows measurements in the uv range.

To measure higher voltages use gain one and a resistor voltage divider. I find a divider with a resistor of R and 10R very useful. This gives a range of about 0 - 22 V.

I use low temperature coefficient 1% resistors and calibrate the divider by measuring a stable 1.5 V battery with and without the divider to get better than 1% accuracy.

User avatar
queglay
 
Posts: 7
Joined: Mon Apr 11, 2011 3:09 am

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

Post by queglay »

thanks for the reply man. I had a similar idea using silver oxide batteries to calibrate too.

When you say you calibrate the divider with your 1.5 volt battery, how do you actually vary the calibration if its out? by replacing resistors or by altering the numeric output with a linear equation or something? not to sure about what your doing there.

on another note-
Having a voltage divider of total 11 ohms would draw 1 amp of current of my 12v lead acid battery. in my case i'd like to keep that current low.
if i use 1k and 7k ohm resitors that gives me a perfect range with a draw of 1.5ma, which is what im using now. is there any reason why i should consider using a lower resistance divider like yours?
my total resistance is 8k, which is a small ratio compared to the internal resistance of the soic which is around the 1.4 megaohm range - this is another factor i discovered thats important when measuring off a voltage divider, or the measurement tool itself affects the voltage and current throughout the divider.
thanks again for the tips.

User avatar
fat16lib
 
Posts: 595
Joined: Wed Dec 24, 2008 1:54 pm

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

Post by fat16lib »

I didn't mean 1 ohm for R. If R is 10k ohms 10R is 100k. Then the scale factor is eleven.

If you read 1.16 volts the battery voltage is 12.76 = 11*1.16

To calibrate read the voltage of a 1.5V battery with and wihout the divider.

If the readings are 1.515 V and 0.1372 V then use

1.515/0.1372 = 11.04

As the scale factor.

Your divider values seem good for lead acid batteries.

User avatar
queglay
 
Posts: 7
Joined: Mon Apr 11, 2011 3:09 am

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

Post by queglay »

ahh ok, that makes more sense to me now.

do you think using a calibration voltage (1.5v silver oxide battery) that is much smaller than the voltage range im actually measuring (12-14v) would result in innacuracy?
i would have also thought that measuring both the resistances with a meter and using those ratios as your multiplier would be just as accurate, or is it bad practice?

User avatar
fat16lib
 
Posts: 595
Joined: Wed Dec 24, 2008 1:54 pm

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

Post by fat16lib »

I get very good results by measuring voltages and calculating the scale factor as the rario. The results are good to better than 1% when compared with a 10V voltage standard.

I actually put the divider on the battery and measure at the battery and at the divider so the load on the battery is constant.

You must use high quality resistors or temperature changes will cause large errors. Use 1% resistors with the best temp coef.

You need a very high quality meter to measure resistances to better than 1%. I have a bench meter and measuring resistance with it gives good results.

If you have a meter that measures voltage to a fraction of a percent you can use it to calculate the scale factor.

In short there are a lot of ways to calibrate. The first method I described requires no special equipment.

gpf
 
Posts: 19
Joined: Wed Apr 06, 2011 9:22 am

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

Post by gpf »

Doe this adc settle any quicker than the one on the arduino?

i.e. do you still need to do 2 analog reads with a 10 millis delay between them to get a good reading?

User avatar
fat16lib
 
Posts: 595
Joined: Wed Dec 24, 2008 1:54 pm

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

Post by fat16lib »

gpf,

The "settling problem" for the Arduino built-in ADC only occurs for higher impedance input sources and mainly when switching channels. The delay can be avoided for high impedance sources by using an op-amp before the ADC.

The built-in ADC is a 10-bit successive approximation ADC. The settling time issue is due to 14 pf of capacitance in the ADC's sample and hold circuit.

The MPC342X is delta-sigma ADC with an On-Board Programmable Gain Amplifier so it doesn't have the settling problem.

It is fairly slow compared to the Arduino ADC. The sample rate is 3.75, 15, 60, or 240 samples per second for 18, 16, 14, or 12 bit results.

gpf
 
Posts: 19
Joined: Wed Apr 06, 2011 9:22 am

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

Post by gpf »

OK - Thanks for that.

So at it's fastest the 342x can sample ~ every 4 millis @ 12 bit which may, or may not, be better depending on the input source?

Is there a natural way to determine if sources will have high enough impedance to worry about?

I guess I could just sample them every millisecond for 10 millis and see what happens - may be difficult if they are constantly changing though......

Edit - Actually, having just googled op-amp (yep, complete newbie here....), I presume that if I am only dealing with 0-5v sources such as MAP sensors, temp sensors etc on a car then they are probably not going to be "high impedance" anyway?

User avatar
fat16lib
 
Posts: 595
Joined: Wed Dec 24, 2008 1:54 pm

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

Post by fat16lib »

The MPC342X is not right for your application. It is good for high precision differential measurements.

Your sensors may be high enough impedance to cause problems with the raw Arduino ADC. Most articles on testing MAP sensors say use a high impedance meter. Other auto sensors can be high impedance also.

I know op-amp buffers are often used in the MAP/ECU circuit.

Once again the big problem with the Arduino is switching ADC channels. The analogRead() function switches the ADC mux and starts the ADC immediately so you get crosstalk between channels with high impedance sensors.

gpf
 
Posts: 19
Joined: Wed Apr 06, 2011 9:22 am

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

Post by gpf »

OK, thanks again.

I'll try and do some testing of the sensors.

User avatar
queglay
 
Posts: 7
Joined: Mon Apr 11, 2011 3:09 am

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

Post by queglay »

ok, so i reused the code first posted that allows 18 bit reading of the adc. i just cut out all the menus basicly.

Now I want to alternate between channel 0 and channel 1 with each sample taken (see the very end of my code).

all im doing is alternating the BANNED var between 0 and 1.

For some reason it doesn't work though. It's fine if instead of the var "chanAlternate" I put in an int (as was originally the way it works). but i want that number to change between 0 and 1 with each sample. the result is it just appears to keep a value of 1, despite my "if" condition trying to flip flop between 0 and 1.

can anyone point out what i'm doing wrong here?
also, does this chip allow you to have a different ground for each channel? or must the ground be shared?

Thanks heaps for any help guys.

Code: Select all

#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

int chanAlternate = 0; //BANNED alternation per read

//------------------------------------------------------------------------
// 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;
  chan = 0;
  gain = 0;
  res = 3;
  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.  
  uint8_t chan = 0XFF, gain = 0XFF, res = 0XFF;
  chan = chanAlternate;
  gain = 0;
  res = 3;
  adcConfig |= chan << 5 | res << 2 | gain;
  mcp342xWrite(adcConfig);
  int32_t data;
  if (!mcp342xRead(data)) halt();
  // voltage in millivolts
  double mv = (double)data/mvDivisor;
  Serial.print(mv);Serial.print(" mv");Serial.println(chanAlternate);
  if(chanAlternate == 0) {
    chanAlternate = 1;
  } else {
    chanAlternate = 0;
  }
  delay(1000/3.75);
}

User avatar
fat16lib
 
Posts: 595
Joined: Wed Dec 24, 2008 1:54 pm

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

Post by fat16lib »

This line in loop() is the problem

Code: Select all

      adcConfig |= chan << 5 | res << 2 | gain;
Once the channel 1 bit is set it is not cleared by the '|' operator.

I think you want one-shot mode, not continuous mode since you alternate channels.

Try this:

Code: Select all

      adcConfig = MCP342X_START | chan << 5 | res << 2 | gain;

User avatar
queglay
 
Posts: 7
Joined: Mon Apr 11, 2011 3:09 am

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

Post by queglay »

great, that works. thankyou...
what happens when you have an or | not in an if condition? i haven't seen that before.

some funny observation here - the channels seem to go high (2V) when no external voltage which is strange.
if i touch ch1+ the other channels go low.
wait, now its stoped for some reason.
all channels are high, unles i touch ch1-, then ch1 goes low.

another thing is that when these channels are read, it delays the ability to write to digital pins, which occurs in the same loop. are there any tricks i can use to avoid this delay?

Locked
Please be positive and constructive with your questions and comments.

Return to “Arduino Shields from Adafruit”