Sample code for 22-bit ADC: Microchip MCP3550

In case of interest. This is the highest-resolution delta-sigma ADC which I have found relatively easy to use (although you do need to handle the SO-8 package). I was pleased to get slightly better performance than what the data sheet spec promised (2.5 uV RMS noise). Make sure your Vdd and Vref is clean, though (that is, don't just use a USB +5V rail without RC filtering it!). Device is very low-power, so 500 ohms in series with Vdd, and 1 uF cap works OK.

// Arduino program to read Microchip MCP3550-60 using SPI bus 
// by John Beale www.bealecorner.com Feb. 9 2012

/* =======================================================
 MCP3550 is a differential input 22-bit ADC (21 bits + sign)  
 +Fullscale = (+Vref -1 LSB) = 0x1fffff
 -Fullscale = (-Vref) = 0x200000
 1 LSB => Vref / 2^21    for Vref=2 V, 1 LSB = 0.95 uV
 Datasheet Spec: noise = 2.5 uV RMS with Vref = 2.5 V
 measured results: 
 Noise Test setup: Vdd = 5V, +Vin = -Vin = Vref/2, Zin = 500 ohms (2x 1k divider)
 with Vref = 0.500 V, RMS noise = 8.60 LSB or 2.05 uV  (1 LSB = 0.24 uV)
 with Vref = 1.000 V, RMS noise = 4.33 LSB or 2.06 uV  (1 LSB = 0.48 uV)
 with Vref = 2.048 V, RMS noise = 2.16 LSB or 2.12 uV  (1 LSB = 0.98 uV)
 with Vref = 4.00 V,  RMS noise = 1.24 LSB or 2.37 uV  (1 LSB = 1.91 uV)
 ==========================================================
 */

#include <SPI.h> // use Arduino SPI library

#define CS 8     // bring CS high for ADC sleep mode, low to start new conversion
#define MOSI 11  // MOSI is not used for this device
#define MISO 12  // status and data bits from ADC
#define SCK 13   // SPI clock from Arduino to ADC

int samples = 100;    // how many samples to group together for avg. and std.dev
                    // MCP3550-60 does 15 samples per second (max) or 66.7 msec per sample

void setup() {
  Serial.begin(9600);
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV32);	// SPI clock rate < 5 MHz per MCP3550 spec
  SPI.setBitOrder(MSBFIRST);		// MSB or LSB first
  SPI.setDataMode(SPI_MODE3);		// rising/falling edge of clock

  digitalWrite(CS,HIGH);
  pinMode(CS, OUTPUT);  // CS (out from Arduino)
  pinMode(MOSI, OUTPUT);  //MOSI (data out from Arduino)
  pinMode(MISO, INPUT);  // MISO (data in to Arduino)
  pinMode(SCK, OUTPUT); // SCK  (serial clock)
  Serial.print("# MCP3550-60 ADC-Read v0.2 Feb.9 2012 sample size: ");
  Serial.println(samples);
  Serial.println("hours,value,stdev,min,max");
} // end setup()

void loop() {
  byte r1, r2, r3, r4;
  byte OVL, OVH;      // overload condition HIGH and LOW, respectively
  unsigned int i;              // loop counter
  unsigned long w;
  long x;
  long minval, maxval;
  double sum, mean, m2, delta, sumsq, variance,stdev;
  double hours;
  
  sum = 0;
  sumsq = 0;
  m2 = 0;
  mean = 0;
  minval=1E9;
  maxval=-1E9;
  
  for (int n=0;n<samples;) {
    digitalWrite(CS,HIGH); 
    delayMicroseconds(100);
    digitalWrite(CS,LOW);   // start next conversion
    delay(50);            // delay in milliseconds (nominal MCP3550-60 rate: 66.7 msec => 15 Hz)
    i=0;                // use i as loop counter
    do {
      i++;
      delayMicroseconds(50);                            // loop keeps trying for up to 1 second
    } while ((digitalRead(MISO)==HIGH) && (i < 20000));   // wait for bit to drop low (ready)
    
    w = readword();    // data in:  32-bit word gets 24 bits via SPI port
    OVL = ((w & 0x80000000) != 0x00000000);  // ADC negative overflow bit (input > +Vref)
    OVH = ((w & 0x40000000) != 0x00000000);  // ADC positive overflow bit (input < -Vref)

    if ((i < 10000)) {
      n++;
      x = w <<2;  // to use the sign bit
      x = x/1024; // to move the LSB to bit 0 position
      if (x>maxval) maxval = x;
      if (x<minval) minval = x;
//      Serial.println(x);
      delta = x - mean;
      mean += delta/n;
      if (n > 1) {        // from http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
        m2 += delta * (x - mean); 
      }
    }
  } // end for i..

  variance = m2/samples;
  stdev = sqrt(variance);
  hours = millis()/3600000.0;  // convert milliseconds to hours
  Serial.print(hours,4);
  Serial.print(",");
  Serial.print(mean);
  Serial.print(",");
  Serial.print(stdev);
  Serial.print(",");
  Serial.print(minval);
  Serial.print(",");
  Serial.print(maxval);
  Serial.println();

} // end main loop()


// ===================================================
// read one word from 22-bit ADC device  MCP3550

unsigned long readword() {
   union {
    unsigned long svar;
    byte c[4];
  } w;        // allow access to 4-byte word, or each byte separately
  
  w.c[3] = SPI.transfer(0x00);  // fill 3 bytes with data: 22 bit signed int + 2 overflow bits
  w.c[2] = SPI.transfer(0x00);
  w.c[1] = SPI.transfer(0x00);
  w.c[0]=0x00;                  // low-order byte set to zero
  
  return(w.svar);    // return unsigned long word
} // end readword()

Nice specs. Its about 12 samples a second though, just so people realize. In fact its so accurate you should choose the correct version for your mains frequency(!) - the MCP3550-50 for 50Hz and MCP3550-60 for 60Hz, each has mains frequency nulling of some sort.

yes, there are several versions of the chip. the -60 with a null at 60 Hz has a conversion rate of 15 Hz. There is also the MCP3553 which has a 60 Hz conversion rate, of course with higher noise as a result- as usual, a tradeoff between speed and resolution.

Got your code up and running, thanks!

I'm a bit new so i am still trying to wrap my head around all the math in your code.

Was wondering if you could help me modify it a little to be able to display an actual voltage?

I am figuring that dividing the "mean" value by V per bit might do it, but I tried that and am getting strange data.

I am ultimately wanting to use this chip for an ultra high resolution volt meter, with about a 30v upper limit, but with xx.xxxxx5 resolution.

Your code seems like a great place to start since it averages the data coming in.

Thanks, Joe

Hey John,

i do not know if you had ever tested your code. Right now I'm doing also a MCP3551 implementation and it looks like there are several issues that I do not understand:

 OVL = ((w & 0x80000000) != 0x00000000);  // ADC negative overflow bit (input > +Vref)

OVH = ((w & 0x40000000) != 0x00000000);  // ADC positive overflow bit (input < -Vref)

This bit operation will probably never work. The highest bit count is 23 read from ADC. You do an and with the highest byte which supposed to be always zero or always one depending on the signed and overflow bits.
It would be more easier to manipulate only w.c[2]

Next you dont evaluate OVL or signed for negative values:

w.c[0]=0x00;                  // low-order byte set to zero

This line is also strange with that remark. You probably want to set the highest byte (MSB) to zero. This is okay whenever Vin > V-ref.
But it should be evaluated if OVL or SIGNED is set and then write FF.

hi,

can someone help me with this code to run on an esp8266? it works well on Arduino Uno, but not on esp8266 :frowning: