Interfacing with a Microchip MCP3421 / MCP3422 / MCP3424 18-bit Sigma Delta A/Ds

copyright, Peter H Anderson, Baltimore, MD, Sept 5, '11


This note first illustrates how to interface an MCP3421 single 18-bit A/D with a PICAXE-20X2 and with an Arduino. I quickly moved to the MCP3422 dual A/D and illustrate how to measure voltage and temperature using an LM34 for the PICAXE. With the Arduino, I illustrate how to interface with a PT1000 RTD device using a bridge arrangement. This was repeated for a PT100 device and a FET was used to power the external circuitry only during the measurments.

MCP3421 - Single A/D

I have avoided tinkering with this device as it is only available in a tiny SOT-23-6 package. But, I did find some SOT-23-6 to DIP adapators on eBay, purchased some MCP3421A3T-E/CHCT from Digikey and tested my patience and eyesight in mounting a few on the adaptors. I found it was a indeed a test requring magnification and a good deal of light. I came to the conclusion that this would pose a difficulty with no obvious payoff for most hobbyists and resolved to move to the MCP3422 dual A/D in an SOIC package which is actually much larger.

The MCP3241 is an I2C device. The address is 0110 1xxx where the 0110 1 the family code set by the factory for all MCP3421 devices. I ordered a device with the lowest three bit of the address set to 011 or 3 as in "A3T". Thus, the full I2C address for my device is 0110 1011.

Data Sheet

Program MCP3421_1.Bas

Figure #1

Operation is a matter of setting the configuration register;

     Hi2cout [SlaveAdr_2], ($88) ' 1000 1000

In my case, I opted for a gain setting of times 1, 15 samples per second or 16 bits, and single shot conversion. The conversion is initiated by writing a one to the /RDY bit.

The result is read;

     Hi2cin [SlaveAdr_2], (Hi, Lo, Config)

As I was using the 16-bit mode, only two bytes were required to fetch the result. (If 18 bits were used, three bytes, Hi, Mi and Lo, would be fetched). Note that I opted to fetch and display the content of the configuration register.

In the following code, I began to develop code to display the number of micro volts, but only reached the point of multiplying ADVal by 62 uV and my attention was turned in another direction.

I was surprised and at first discouraged to see ADVal sag from 25300 to 25250 over the course of an hour. However, the ADVal from one reading to the next spaced by 30 seconds was always in close agreement. There was a trend toward sagging but ony 1 or 2 bands. I concluded that what I was seeing was battery discharge. 50 bands corresponds to 3 mV, which is probably realistic. Recall, the reference for this A/D is an internal 2.048 reference, and not the nominal 5V supply.

This routine could be improved by taking perhaps 16 measurements in sequence and averaging them. Hopefully, I can return to do this.


' MCP3421_1.Bas
'
' Continually measures and displays an A/D Value.
'
' PICAXE-20X2               MCP3421
'
' Term 11 SCL --------------- SCL
' Term 13 SDA --------------- SDA
'
' 4.7K resistors to +5 VDC on SDA and SCL.   
'
' copyright, Peter H Anderson, Baltimore, MD, May 14, '11

#picaxe 20x2
#Terminal 9600
#No_Table
#No_Data
#freq m4

Symbol Lo = B0
Symbol Hi = B1
Symbol Config = B2
Symbol SlaveAdr_2 = B3
Symbol ADVal = W2
Symbol Volts_uV_Hi = W3
Symbol Volts_uV_Lo = W4

Top:

    SlaveAdr_2 = $68 + $03	' set adr to 0110 1011,     
    SlaveAdr_2 = SlaveAdr_2 * 2

    Hi2cSetup I2CMaster, SlaveAdr_2, I2CSlow, I2CByte 

    Do

       Hi2cout [SlaveAdr_2], ($88)	' config register %1000 1000
                                    '/RDY = 1, One Conversion, 15 samples per, PGA = X1

       Pause 1000
       Hi2cin [SlaveAdr_2], (Hi, Lo, Config)
       ADVal = Hi
       ADVal = ADVal * 256 + Lo

       If ADVal > 32767 Then ' its negative
          ADVal = -ADVal
          SerTxD ("-")
       EndIf

       SerTxD (#ADVal, "  ", #Config, CR, LF)

       
       Volts_uV_Hi = ADVal ** 62
       Volts_uV_Lo = ADVal * 62
       SerTxD (#Volts_uV_Hi, "  ", #Volts_uV_Lo, CR, LF)
       Pause 30000
    Loop

Arduino

The following performs the same function using the Arduino.

Note the I2C commands.

      Wire.begin();   // configures pins 17 and 18 as I2C SDA and SCL.

To send data to the MCP3421.

      Wire.beginTransmission(address);
      Wire.send(0x88);   // config register %1000 1000
                         // /RDY = 1, One Conversion, 15 samples per, PGA = X1

      Wire.endTransmission();

To then receive data from the device.

      Wire.requestFrom((int)address, (int) 3);
      Hi = Wire.receive();
      Lo = Wire.receive();
      Config = Wire.receive();
      Wire.endTransmission();

// MCP3421_1.PDE - Arduino
// 
// Continually measures and displays an A/D Value.
//
// Arduino                   MCP3421
//
// 17, AN4 SDA --------------- SDA
// 18, AN5 SCL --------------- SCL
//
// 4.7K resistors to +5 VDC on SDA and SCL
//
// copyright, Peter H Anderson, Baltimore, MD, May 25, '11
// Observe the copyright.  Use it, copy it, but leave the copyright.

#include  //I2C library

#define TRUE 1
#define FALSE 0

void setup(void)
{
   Serial.begin(9600);
   Wire.begin();
   delay(5000);
   Serial.println(">>>>>>>>>>>>>>>>>>>>>>>>");  // just to be sure things are working
}
    
void loop(void)
{

   byte address, Hi, Lo, Config;
   int ADVal;
  
   while(1)
   {
      address = 0x68 + 0x03;
      Wire.beginTransmission(address);
      Wire.send(0x88);   // config register %1000 1000
                         // /RDY = 1, One Conversion, 15 samples per, PGA = X1

      Wire.endTransmission();
   
      delay(1000);
      Wire.requestFrom((int)address, (int) 3);
      Hi = Wire.receive();
      Lo = Wire.receive();
      Config = Wire.receive();
      Wire.endTransmission();
   
      ADVal = Hi;
      ADVal = ADVal * 256 + Lo;
   
      Serial.print(ADVal, DEC);
      Serial.print("  ");
      Serial.println(Config, DEC);
      
      delay(25000);
   }
}


MCP3422, Dual 18-bit A/D

Data Sheet

Operation of the MCP3422 is quite similar to the MCP3421, write to the configuration register and then read the result. However, the channel is selected by the state of bit 5 of the Config Register

PICAXE. Measuring temperature using an LM34.

Fig 2, PICAXE - MCP3422 Dual A/D

The following routine measures the voltage across the 4.7 K resistor in a voltage divider configuration. Note that each analog band is 2.048 VDC / 32767 or 62.5 uV. Thus, the voltage in uV can be calculated as ADVal * 62.5 or in mV as ADVal * 0.0625 or simply ADVal/16.

For the LM34, the temperature in degrees F is 10 mV per degree F. Thus, quite like the above, Tf = ADVal * 0.625 or Tf_10 = ADVal * 0.0625 or ADVal / 16.

I considered trying to get degrees F in hundreths of degrees. TF_100 = ADVal / 1.6. However, I could not see how to easily evaluate this.


' MCP3422_1.Bas
'
' Continually measures and displays an A/D Values on both channels.
'
' Displays the voltage appearing on Ch 1 - resistive voltage divider.
'
' Measures voltage at output of an LM34 and calculates and displays the 
' temperature.
'
' PICAXE-20X2               MCP3422
'
' Term 11 SCL --------------- SCL
' Term 13 SDA --------------- SDA
'
' 4.7K resistors to +5 VDC on SDA and SCL  
'
' copyright, Peter H Anderson, Baltimore, MD, Jun 3, '11

#picaxe 20x2
#Terminal 9600
#No_Table
#No_Data
#freq m4

Symbol Lo = B0
Symbol Hi = B1
Symbol Channel = B2
Symbol ConfigReg = B3
Symbol SlaveAdr_2 = B4
Symbol ADVal = W3
Symbol ADVal_1 = W4
Symbol ADVal_2 = W5
Symbol Vmv = W6
Symbol Tf_10 = W7
Symbol Whole = B0
Symbol Fract = B1

Top:

    SlaveAdr_2 = $68 + $00	' set adr to 0110 1011,     
    SlaveAdr_2 = SlaveAdr_2 * 2

    Hi2cSetup I2CMaster, SlaveAdr_2, I2CSlow, I2CByte 

    Do
        Channel = 1
        GoSub ADConv
        ADVal_1 = ADVal

        Channel = 2
        GoSub ADConv
        ADVal_2 = ADVal
   
        SerTxD (#ADVal_1, "  ", #ADVal_2, "  ", #ConfigReg, CR, LF)

        ' calculate Vmv on Channel 1
        
        Vmv = ADVal_1 / 16

        ' calculate Tf at output of the LM34 on Channel 2
        Tf_10 = ADVal_2 / 16
        Whole = Tf_10 / 10
        Fract = Tf_10 % 10

        SerTxD (#Vmv, "  ", #Whole, ".", #Fract, CR, LF)
        Pause 30000
    Loop

ADConv:

       If Channel = 1 Then 
           ConfigReg = $88
       Else
           ConfigReg = $88 | $20 ' 1 in bit 5 position
       EndIf

       Hi2cout [SlaveAdr_2], (ConfigReg)	

       Pause 1000
       Hi2cin [SlaveAdr_2], (Hi, Lo, ConfigReg)
       ADVal = Hi
       ADVal = ADVal * 256 + Lo

       Return


Arduino. Measuring Temperature using a PT1000 RTD.

Fig 3, Arduino - MCP3422 Dual A/D

I was able to find both PT1000 and PT100 devices on eBay at very reasonable prices, typically less than $5.00.

Wikipedia Discussion.

My intent is developing this routines was to measure temperature using a PT1000 positive temperature coefficient RTD. To a first approxiamtion, these devices are linear, with the resistance increasing 3.84 Ohms / degree C. Temperature measurment is than a matter of determining the R_therm and then calculating the temperature. Determining the resistance is not a simple matter as 3.84 is quite small in relation to 1000.

I used a bridge circuit as illustrated in Figure #3. Voltage Vab is calculated by subtracting the voltages of the two voltage dividers.

       Vab = (909 / (909 + 10K) - Rtherm / (Rtherm + 10K)) * Vs
where Vs is nominally 5 VDC.

Solving for Rtherm;

    Rtherm = 10K * ((1.0 / (0.91667 - Vab / Vs) - 1.0)
Voltage Vs (nominally 5.0 VDC) is determined by measuring Vch1 across the 4.7K resistor. Vs is then calculated as;
    Vch1 = 4.7K / (10K + 4.7K + 10K) * Vs 
    Vs = 5.255 * Vch1
Thus, the multistep process. Measure Vch1 and calculate Vs. Measure Vch2 (or Vab). Calculate Vab / Vs. Calculate Rtherm. Calculate Tc;
    Tc = (Rtherm - 1000.0) / 3.84;
Indeed, this did give the expected results at room temperature. However, use some caution. Use more precise resistors than my ten percent and do some kind of sensitivity analysis to assure that if everything tolerance is the wrong way, the result is reasonable.


// MCP3422_2.PDE - Arduino
// 
// Measures temperature using a PT1000 thermistor in a bridge arrangement.
//
// Arduino                   MCP3422
//
// 17, AN4 SDA --------------- SDA
// 18, AN5 SCL --------------- SCL
//
// 4.7K resistors to +5 VDC on SDA and SCL
//
// copyright, Peter H Anderson, Baltimore, MD, June, '11
// Observe the copyright.  Use it, copy it, but leave the copyright.

#include <Wire.h> //I2C library

#define TRUE 1
#define FALSE 0

int ad_convert(byte channel);
void print_float(float f, int num_digits);

void setup(void)
{
   Serial.begin(9600);
   Wire.begin();
   delay(5000);
   Serial.println(">>>>>>>>>>>>>>>>>>>>>>>>");  // just to be sure things are working
}
    
void loop(void)
{

   int ADVal_1, ADVal_2; 
   float Vs, Vch1, Vch2, X, denom, Rtherm, Tc;
  
   while(1)
   {
      ADVal_1 = ad_convert(0x68, 1); // 16-bit measurments on both channels
      ADVal_2 = ad_convert(0x68, 2);
      
      Vch1 = 62.5 * (float) ADVal_1 / 1.0e6; // calcualte Vs (nominal +5 VDC)
      Vs = Vch1 * 5.255;
      print_float(Vs, 3);
      Serial.print("  ");
      
      Vch2 = 62.5 * (float) ADVal_2 / 1.0e6; // Now calculate Rthem and Tc
      
      X = Vch2 / Vs;
      
      denom = 0.91667 - X;
      Rtherm = 10.0e3 * (1.0 / denom - 1.0);
      Tc = (Rtherm - 1000.0) / 3.84;
      
      Serial.print((int)Tc, DEC);
      Serial.println("");
     
      delay(25000);
   }
}
   
int ad_convert(byte adr, byte channel)
{
     int ADVal;
     byte Hi, Lo, Config;
     
     Wire.beginTransmission(adr);
     if (channel == 1)
     {    
         Config = 0x88;
        // config register %1010 1000
        // /RDY = 1, One Conversion, 15 samples per, PGA = X1
     }
     else
     {
         Config = 0x88 | 0x20; // 1 in bit 5 position
     }
     Wire.send(Config);
     Wire.endTransmission();
   
     delay(1000);
     
     Wire.requestFrom((int)adr, (int) 3);
     Hi = Wire.receive();
     Lo = Wire.receive();
     Config = Wire.receive();  // for possible debugging
     Wire.endTransmission();
   
     ADVal = Hi;
     ADVal = ADVal * 256 + Lo;
     return(ADVal);
}

void print_float(float f, int num_digits)
{
    int f_int;
    int pows_of_ten[4] = {1, 10, 100, 1000};
    int multiplier, whole, fract, d, n;

    multiplier = pows_of_ten[num_digits];
    if (f < 0.0)
    {
        f = -f;
        Serial.print("-");
    }
    whole = (int) f;
    fract = (int) (multiplier * (f - (float)whole));

    Serial.print(whole);
    Serial.print(".");

    for (n=num_digits-1; n>=0; n--) // print each digit with no leading zero suppression
    {
         d = fract / pows_of_ten[n];
         Serial.print(d);
         fract = fract % pows_of_ten[n];
    }
}
Arduino Measuring tempearture using a PT100 RTD.

Fig #4

Measuring temperature using a PT100 (vs PT1000) was implemented by scaling all resistances in the bridge down by a factor of ten and simple modifications of the expressions.

I also added a FET to turn on this external circuitry only when performing measurments, thus greatly reducing the the current drain. I used an NXP PSMN4R3-30PL, but this is a bit of an overkill in this application.

My results at first seemed disappointing as I was measuring 31 degees C or about five degrees high. However, I then paused to consider that five degrees is an inaccuracy about 2 Ohms (0.384 Ohms / degree C), which seems pretty good. If I were to persue this I would initially correct my result by simply subtracting the 5 degrees, but as I acquired more experience at other temperatures, I would strive for some kind of straight line correction.


// MCP3422_3.PDE - Arduino
// 
// Measures temperature using a PT100 thermistor in a bridge arrangement.
//
// This is similar to routine MCP3422_2 except it uses a PT100 RTD and
// the external circuitry is turned on during measurement using an FET
// on Pin 2.
//
// Arduino                   MCP3422
//
// 17, AN4 SDA --------------- SDA
// 18, AN5 SCL --------------- SCL
//
// 4.7K resistors to +5 VDC on SDA and SCL
//
// copyright, Peter H Anderson, Baltimore, MD, June, '11
// Observe the copyright.  Use it, copy it, but leave the copyright.

#include  //I2C library

#define TRUE 1
#define FALSE 0

int ad_convert(byte channel);
void print_float(float f, int num_digits);

void setup(void)
{
   Serial.begin(9600);
   Wire.begin();
   digitalWrite(2, LOW);
   pinMode(2, OUTPUT);
   delay(5000);
   Serial.println(">>>>>>>>>>>>>>>>>>>>>>>>");  // just to be sure things are working
}
    
void loop(void)
{
   int ADVal_1, ADVal_2; 
   float Vs, Vch1, Vch2, X, denom, Rtherm, Tc;
  
   while(1)
   {

      digitalWrite(2, HIGH);  // turn on FET
      delay(1000);
      
      ADVal_1 = ad_convert(0x68, 1); // 16-bit measurments on both channels
      ADVal_2 = ad_convert(0x68, 2);
      
      Vch1 = 62.5 * (float) ADVal_1 / 1.0e6; // calcualte Vs (nominal +5 VDC)
      Vs = Vch1 * 5.255;
      print_float(Vs, 3);
      Serial.print("  ");
      
      Vch2 = 62.5 * (float) ADVal_2 / 1.0e6; // Now calculate Rthem and Tc
      
      X = Vch2 / Vs;
      
      denom = 0.91667 - X;
      Rtherm = 1.0e3 * (1.0 / denom - 1.0);
      Tc = (Rtherm - 100.0) / 0.384;
      
      Serial.print((int)Tc, DEC);
      Serial.println("");
      
      digitalWrite(2, LOW); // turn FET off to save power
     
      delay(25000);
   }
}

Implementations of 

int ad_convert(byte channel) 
void print_float(float f, int num_digits)

are the same as in MCP3422_2.PDE


MCP3424, Quad 18-bit A/D

Data Sheet

Operation of the MCP3424 is similar to the MCP3421, write to the configuration register and then read the result. However, the channel is selected by the states of bits 6 and 5 of the Config Register

PICAXE. Measuring temperature using an LM34. To within 1/25 of a degree F.

Fig 5, PICAXE - MCP3424 Quad A/D

' MCP3424_1.Bas
'
' Measures voltage at output of an LM34 and calculates and displays the 
' temperature in degrees F to 1/25 of a degree.  Pretty amazing.
'
' This is repeated for Channels 1, 2, 3 and 4.
'
'
' PICAXE-20X2               MCP3424
'
' Term 11 SCL --------------- SCL (term 8)
' Term 13 SDA --------------- SDA (term 7)
'
' 4.7K resistors to +5 VDC on SDA and SCL  
'
' copyright, Peter H Anderson, Baltimore, MD, Sept 3, '11

#picaxe 20x2
#Terminal 9600
#No_Table
#No_Data
#freq m4

Symbol Lo = B0
Symbol ChBits = B0
Symbol Hi = B1
Symbol Channel = B2
Symbol ConfigReg = B3
Symbol SlaveAdr_2 = B4
Symbol ADVal = W3
Symbol LowWord = W4
Symbol Tf_100 = W5
Symbol HighWord = W6
Symbol Whole = B0
Symbol Fract = B1

Top:

    SlaveAdr_2 = $68 	' set adr to 0110 1000,     
    SlaveAdr_2 = SlaveAdr_2 * 2

    Hi2cSetup I2CMaster, SlaveAdr_2, I2CSlow, I2CByte 

    Do
        For Channel = 1 to 4
           GoSub MeasTemperature
        Next
        Pause 10000 
    Loop
    
MeasTemperature:
    
    GoSub ADConv	' result in ADVal
    HighWord = 5 ** ADVal
    LowWord = 5 * ADval
    
    LowWord = LowWord / 8
    HighWord = HighWord * 256 * 32
    LowWord = HighWord + LowWord
    TF_100 = LowWord
    
    Whole = Tf_100 / 100
    Fract = Tf_100 % 100
    Fract = Fract / 4 * 4
    
    SerTxD (#Channel, "  ",#Whole, ".")
    If Fract > 9 Then
       SerTxD (#Fract, CR, LF)
    Else
       SerTxD ("0", #Fract, CR, LF)
    End If        
    Return
        
ADConv:
       Lookup Channel, ($ff, $00, $20, $40, $60), ChBits
       
       ConfigReg = $88 + ChBits
       

       Hi2cout [SlaveAdr_2], (ConfigReg)	

       Pause 1000
       Hi2cin [SlaveAdr_2], (Hi, Lo, ConfigReg)
       ADVal = Hi
       ADVal = ADVal * 256 + Lo

       Return