Capacitance Meter Temperature Sensitivity

One of the comments was from Jim, who was interested in the temperature sensitivity of the capacatiance meter. So I’ve tested it. I took some measurements of a selection of capacitors. I then put the Arduino board in the fridge at 4C for an hour, and repeated the test. I tried to do this as quickly as possible, so the board didn’t warm up too much. The results were as follows:

Continue reading

Capacitance Meter Mk II

I was blown away with the interest in the Capacitance Meter for the Arduino Uno, and that encouraged me to think about how I could improve it.

CapMeterMkII

Scott made a comment on the Hackaday post, and suggested using the internal pullup resistor to cover higher capacitances. I had initially ruled that out because it wouldn’t work for low value capacitors (less than about 1nF). But thinking about it again, I realized that this method could be used to extend the range up to uF values. By combining this with the method in the Mk I Capacitance meter, I could make a capacitance meter that could measure from 1pF to over 1000uF – with no external components!

Actually getting this to work proved slightly more complicated than I first thought. The basic idea was going to be:

  1. Test the capacitor using the stray capacitance method. If the capacitor is less than 1nF then we are done.
  2. Otherwise, we start charging the capacitor using the internal pullup.
  3. After a certain amount of time (maybe the RC time constant), the capacitor will have reached a certain voltage.
  4. Using the charge time and voltage (or more specifically  the ratio of the capacitor voltage to the charging voltage) we can work out the capacitance.

The time to charge larger capacitors would be in the order of milliseconds. This would be quite easy to measure with a reasonable degree of accuracy. But the RC time constant of a 1nF capacitor with a 30k pullup is about 30us. Which is more difficult to measure accurately. I thought about using interrupts, and using a hand-crafted ADC conversion method (analogRead takes about 100us – and you can do the conversion much faster than this), but I didn’t want the code to get too complicated, and I wanted to stick to the standard Arduino routines. So I came up the the following code:

View Code

const int OUT_PIN = A2;
const int IN_PIN = A0;

//Capacitance between IN_PIN and Ground
//Stray capacitance value will vary from board to board.
//Calibrate this value using known capacitor.
const float IN_STRAY_CAP_TO_GND = 24.48;
const float IN_CAP_TO_GND  = IN_STRAY_CAP_TO_GND;
//Pullup resistance will vary depending on board.
//Calibrate this with known capacitor.
const float R_PULLUP = 34.8;  //in k ohms
const int MAX_ADC_VALUE = 1023;

void setup()
{
  pinMode(OUT_PIN, OUTPUT);
  //digitalWrite(OUT_PIN, LOW);  //This is the default state for outputs
  pinMode(IN_PIN, OUTPUT);
  //digitalWrite(IN_PIN, LOW);

  Serial.begin(115200);
}

void loop()
{
    //Capacitor under test between OUT_PIN and IN_PIN
    //Rising high edge on OUT_PIN
    pinMode(IN_PIN, INPUT);
    digitalWrite(OUT_PIN, HIGH);
    int val = analogRead(IN_PIN);
    digitalWrite(OUT_PIN, LOW);

    if (val < 1000)
    {
      //Low value capacitor
      //Clear everything for next measurement
      pinMode(IN_PIN, OUTPUT);

      //Calculate and print result

      float capacitance = (float)val * IN_CAP_TO_GND / (float)(MAX_ADC_VALUE - val);

      Serial.print(F("Capacitance Value = "));
      Serial.print(capacitance, 3);
      Serial.print(F(" pF ("));
      Serial.print(val);
      Serial.println(F(") "));
    }
    else
    {
      //Big capacitor - so use RC charging method

      //discharge the capacitor (from low capacitance test)
      pinMode(IN_PIN, OUTPUT);
      delay(1);

      //Start charging the capacitor with the internal pullup
      pinMode(OUT_PIN, INPUT_PULLUP);
      unsigned long u1 = micros();
      unsigned long t;
      int digVal;

      //Charge to a fairly arbitrary level mid way between 0 and 5V
      //Best not to use analogRead() here because it's not really quick enough
      do
      {
        digVal = digitalRead(OUT_PIN);
        unsigned long u2 = micros();
        t = u2 > u1 ? u2 - u1 : u1 - u2;
      } while ((digVal < 1) && (t < 400000L));

      pinMode(OUT_PIN, INPUT);  //Stop charging
      //Now we can read the level the capacitor has charged up to
      val = analogRead(OUT_PIN);

      //Discharge capacitor for next measurement
      digitalWrite(IN_PIN, HIGH);
      int dischargeTime = (int)(t / 1000L) * 5;
      delay(dischargeTime);    //discharge slowly to start with
      pinMode(OUT_PIN, OUTPUT);  //discharge remainder quickly
      digitalWrite(OUT_PIN, LOW);
      digitalWrite(IN_PIN, LOW);

      //Calculate and print result
      float capacitance = -(float)t / R_PULLUP
                              / log(1.0 - (float)val / (float)MAX_ADC_VALUE);

      Serial.print(F("Capacitance Value = "));
      if (capacitance > 1000.0)
      {
        Serial.print(capacitance / 1000.0, 2);
        Serial.print(F(" uF"));
      }
      else
      {
        Serial.print(capacitance, 2);
        Serial.print(F(" nF"));
      }

      Serial.print(F(" ("));
      Serial.print(digVal == 1 ? F("Normal") : F("HighVal"));
      Serial.print(F(", t= "));
      Serial.print(t);
      Serial.print(F(" us, ADC= "));
      Serial.print(val);
      Serial.println(F(")"));
    }
    while (millis() % 1000 != 0)
      ;    
}

I’ll try to explain why I coded it this way… I tried using analogRead() to determine when the capacitor had charged up to a certain value. This could work, but analogRead() is very slow so I decided to use the input as a digital input to start with. When the input gets to about 2.5V the input will change from 0 to 1 and we stop charging the capacitor. At this point we really can’t be sure what the the voltage is across the capacitor, so we use analogRead to get an accurate value. We have taken timestamps at the start and end of the charging process, so now we have everything we need to determine the capacitance. The maths behind this is as follows:

RC_Charging1

For the circuit above, the formulas that we need are:

RCFormula1

Initially I assumed that the pullup resistor had a value of 30k. This won’t be correct and should be calibrated using a capacitor of a known value. Something around about 100nF – 1uF should give a good measurement (don’t use an electrolytic – these tend to have very poor tolerance, typically -20%, +80%!). By putting these numbers into the last formula, we can determine the actual pullup resistance.

I used a 1uF capacitor and got the following result:

Capacitance Value = 1.00 uF (Normal, t= 24704 us, ADC= 520)

[Note that at this point the measured capacitance probably won’t be 1.00uF]

Putting these numbers (C = 1e-6F, t = 0.024704s, Vc=520, Vin=1023) into the last formula gives us the result: R = 34.80 kΩ (34800Ω). For C we should use the expected value (1.00uF) rather than the measured value which, as previously stated, is likely to be different until we have done the calibration.

I then tested this with various capacitors.

dec9caps

These were my results:

Results

Capacitor Reading
5pF +-.5pF 5.404 pF (185)
101J 102.000 pF (825)
471K 457.117 pF (971)
102K 0.95 nF (Normal, t= 32 us, ADC= 636)
222K 2.07 nF (Normal, t= 64 us, ADC= 602)
103K 10.50 nF (Normal, t= 272 us, ADC= 537)
104K 112.39 nF (Normal, t= 2800 us, ADC= 523)
105K 986.04 nF (Normal, t= 24428 us, ADC= 521)
106K (tant.) 11.75 uF (Normal, t= 291124 us, ADC= 521)
4.7u (elec.) 5.02 uF (Normal, t= 124108 us, ADC= 520)
10u (elec.) 12.12 uF (Normal, t= 299352 us, ADC= 520)
220u (elec.) 224.77 uF (HighVal, t= 400004 us, ADC= 51)
1000u (elec.) 1170.11 uF (HighVal, t= 400000 us, ADC= 10)

When testing electrolytics make sure that you connect the +ve end to A2 and the negative end to A0.

I’m not sure how accurate this is, but I will look into this (and try to improve it if necessary) when I get a chance.

Note:
I've turned this into a library for the Arduino - so that's the easiest way to measure capacitance.
Search for "capacitor" in the Arduino IDE library manager (Tools menu, Manage Libraries...).

Capacitance measurement with the Arduino Uno

You can buy meters, like this one for instance:

Peak_LCR40

that measure capacitance, but where’s the fun in that?!

If you’ve got an Arduino, why not build one instead?

Note:
I've turned this into a library for the Arduino - so that's the easiest way to measure capacitance.
Search for "capacitor" in the Arduino IDE library manager (Tools menu, Manage Libraries...).

Update: Thank you for all the interest in this article! I have managed to increase the range, so it can measure capacitances of less than 1pF to over 1000uF , still with no external components! I’ve called this the Capacitance Meter Mk II.

There are lots of examples of how to do this on the internet, but I’m going to suggest an incredibly simple way to it. Let’s start with the theory.

Consider the following circuit:

SimpleCapacitanceTester

CT is the capacitor under test. We start with both capacitors discharged and A2 at 0 volts. When we raise A2 to 5 volts a current will flow through both capacitors. The voltage on A0 will settle to within 1% of it’s final value within 30ns. The value that A0 will settle to is proportional to the ratio of CT divided by the total capacitance, C1 + CT. The formulas that we are going to need are:

CapTestFormulas3

We will use the ADC to measure VA0. VA2 is actually 5 volts,  so VA0 will vary from 0 to 5 volts, but we can use the ADC value instead, to make the calculations easier. Obviously we should then use the maximum ADC value (1023) for VA2. The ADC readings we can expect will range from about 33 for CT = 1pF to about 993 for CT = 1nF (1000pF).

So now we are ready to build the circuit and write the code. Right? Well, not quite. In the circuit above we have specified C1 as 30pF. But the Arduino will have some stray capacitance on the board and in the micro-controller. About 30pF of stray capacitance. We’re going to use this to make our circuit simpler – we are going to remove C1. But we need to work out how much stray capacitance there is, so we can plug it into the formulas above. But first, we’ll build the circuit and write the code.

The circuit is surprisingly easy. In fact there isn’t a circuit now. We’re going to use the Arduino as follows:

CapTestBoard1

The capacitor is the capacitor under test between A0 and A2. The capacitors on the right are the capacitors that I am going to test a bit later on.

The code looks like this:

const int OUT_PIN = A2;
const int IN_PIN = A0;

//Capacitance between IN_PIN and Ground
//Stray capacitance is always present. Extra capacitance can be added to
//allow higher capacitance to be measured.
const float IN_STRAY_CAP_TO_GND = 24.48; //initially this was 30.00
const float IN_EXTRA_CAP_TO_GND = 0.0;
const float IN_CAP_TO_GND  = IN_STRAY_CAP_TO_GND + IN_EXTRA_CAP_TO_GND;
const int MAX_ADC_VALUE = 1023;

void setup()
{
  pinMode(OUT_PIN, OUTPUT);
  //digitalWrite(OUT_PIN, LOW);  //This is the default state for outputs
  pinMode(IN_PIN, OUTPUT);
  //digitalWrite(IN_PIN, LOW);

  Serial.begin(115200);
}

void loop()
{
  //Capacitor under test between OUT_PIN and IN_PIN
  //Rising high edge on OUT_PIN
  pinMode(IN_PIN, INPUT);
  digitalWrite(OUT_PIN, HIGH);
  int val = analogRead(IN_PIN);

  //Clear everything for next measurement
  digitalWrite(OUT_PIN, LOW);
  pinMode(IN_PIN, OUTPUT);

  //Calculate and print result

  float capacitance = (float)val * IN_CAP_TO_GND / (float)(MAX_ADC_VALUE - val);

  Serial.print(F("Capacitance Value = "));
  Serial.print(capacitance, 3);
  Serial.print(F(" pF ("));
  Serial.print(val);
  Serial.println(F(") "));

  while (millis() % 500 != 0)
    ;    
}

The code above loops round every half second, applying a 5V pulse to the capacitor, and measuring the voltage the other side. It then prints out the calculated capacitance (and raw ADC value).

If we try this out it won’t be very accurate. That is because the stray capacitance isn’t exactly 30pF. So we need to calibrate it. I did this with a 100pF capacitor. My multimeter reckons that it actually has a value of 102pF. The reading I get on my Arduino board is 125pF (raw ADC value 825). So if we put VA0 = 825, VA2 = 1023 and CT = 102 into the second equation, this tells us that C1 is 24.48pF. So I changed IN_STRAY_CAP_TO_GND to 24.48 and uploaded this to the Arduino. This time the value displayed on the serial monitor is 102pF (most of the time!).

CapTestSerialMon1

I tried it out with the following capacitors:

MiscCap1

This was the results of my tests:

Capacitor Reading
0.072 pF (3)
15J 15.461 pF (396)
22J 23.403 pF (500)
47J 48.532 pF (680)
101J 102.000 pF (825)
471K 448.030 pF (970)
102K 1064.348 pF (1000)
392K 3553.097 pF (1016)
472K 4984.128 pF (1018)

I was curious to know how accurate this might be, so I worked it out. You can see the results of my analysis here. My conclusion was that you could expect a resolution of about 1% between 3.5pF and 225pF, and about 5% between 0.5pF and 1300pF. I haven’t worked the accuracy and linearity yet – I need to get hold of some very accurate reference capacitors (or a very accurate meter) for that.

You can build your own capacitor, and do experiments, which I have demonstrated here.

If you want to build on this or make a complete LCR meter, I have some ideas here.