Turn your Arduino into a capacitor tester ...

As for the hardware, I first tested with the earlier sketch (the one that used Timer 1). That worked fine, gave expected results.

Capacitance = 47.66 nF
Capacitance = 47.29 nF
Capacitance = 48.71 nF
Capacitance = 47.30 nF
Capacitance = 47.35 nF
Capacitance = 47.30 nF
Capacitance = 47.28 nF

Then without touching the hardware I used the code above, and there was no response.

Can you post your code?

Yes. There are a number of differences between this code and yours:

  • It charges from pin 9
  • Analog input pins are reversed, so it looks for a falling edge
  • It writes directly to the port
  • It expects a low-value resistor to discharge the capacitor, and it discharges through the charging resistor and the discharge resistor in parallel. In the code, it's 550. It calculates the time constant of the discharge resistor-capacitor combination from the measured capacitance, and discharges for at least ten of those. You can make it work without the discharge resistor by setting Rd equal to the charging resistor - it'll just discharge through that, but it'll take a while for big capacitors. [Edit: add this] It discharges on pin 8.
  • Pullup resistors are globally disabled

Note that I've never tested it with a capacitor bigger than 10 uF, though I don't know why it wouldn't work.

Here's the code:

/*
  Capacitance Meter
  Resistor, value = Rc, beween pin 7 and pin 9
  Resistor, value < Rc/5 & > 200, between pin 8 and pin 7
  Capacitor to be measured between pin 7 and GND
  Voltage reference connected to pin 6
*/

#define Rc 10000.0
#define Rd 550.0
#define R1 40100.0
#define R2 21000.0
#define clockRate_us 16.0
const float k = 1/(clockRate_us*Rc*log((R1+R2)/R2));

volatile uint8_t TIFR1_Copy;
volatile uint16_t ICR_Copy;
volatile uint8_t ICR_Flag = 0;
volatile uint16_t TOV1_Ctr;

void setup() {
  Serial.begin(115200);
  Serial.println("OK");
  
  MCUCR |= (1 << PUD);  // Globally disable pullup resistors

  TCCR1B = 0;  // Noise Canceler off; Normal mode; falling edge; stopped
  TCCR1A = 0;  // Normal mode; 
  TIMSK1 = (1 << ICIE1) | (1 << TOIE1); // Input Capture and Overflow interrupts

  DIDR1 |= (1<<AIN1D)|(1<<AIN0D); // Disable digital buffer on comp inputs
  ACSR = (1 <<  ACIC); // Enable comparator capture

  pinMode(8,OUTPUT); 
  pinMode(9,OUTPUT);
  digitalWrite(8,LOW);
  digitalWrite(9,LOW); // Both pins low - discharge

  delay(1000); // Wait plenty of time for discharge
}

void loop() {
  float C;
  uint16_t dischargeTime;
  
  ICR_Flag = 0; // Initilize counters and flag
  TOV1_Ctr = 0;
  TCNT1 = 0;

  pinMode(8,INPUT); // Pin 8 set to input - high impedance

  cli(); // No interrupts between starting charge and starting timer
  PORTB |= (1 << 1);  // Pin 9 set high; start charging
  TCCR1B = (1<<CS10); // Start Timer1
  sei(); // Let interrutps happen now

  while (!ICR_Flag) {} // Wait for charging to finish

  pinMode(8,OUTPUT); // Pin 8 set to output
  digitalWrite(8,LOW);
  digitalWrite(9,LOW); // Pins 8,9 low - start discharge

  // Calculate capacitance
  // Adjust overflow counter - if TOV1 is on, and ICR is low -
  if ((!(ICR_Copy & (1 << 15))) && (TIFR1_Copy & (1 << TOV1))) {
    TOV1_Ctr++;
  }
  C = k * (float)(((uint32_t)TOV1_Ctr << 16) | ICR_Copy);
  
  Serial.print(C,6); Serial.print(" uF"); Serial.println();

  dischargeTime = 1 + (uint16_t)(10.0*Rd*C/1000.0);  
  delay(dischargeTime); // Wait at least 10 time constants
  if (dischargeTime < 250) {
    delay(250-dischargeTime); // Wait some more to limit printing
  }
}

ISR(TIMER1_CAPT_vect) {
  TCCR1B = 0;          // Stop Timer1 - CS12:CS10 = 0
  TIFR1_Copy = TIFR1;  // Get overflow interrupt status
  TIFR1 = (1 << TOV1); // Clear overflow
  ICR_Copy = ICR1;     // Get the ICR
  ICR_Flag = 1;        // Set the flag
}

ISR(TIMER1_OVF_vect) {
  TOV1_Ctr++;  // Bump the overflow counter
}

Hmmm, powering my board off and on again seems to have cleared that, which is a bit worrying. There must be a register that isn't being set up properly.

Anyway, using my code above with slight amendments, I now get what looks like a "faster" and more consistent reading:

Capacitance = 47.33 nF
Capacitance = 47.33 nF
Capacitance = 47.33 nF
Capacitance = 47.33 nF
Capacitance = 47.34 nF
Capacitance = 47.34 nF
Capacitance = 47.33 nF
Capacitance = 47.33 nF
Capacitance = 47.33 nF
Capacitance = 47.31 nF
Capacitance = 47.34 nF
Capacitance = 47.36 nF
Capacitance = 47.33 nF

Previously I was getting something like 47.9 nF.

Unless my calculations are out, that would be accounted for by the time taken to service the interrupt (say, 2 or 3 uS). Since we are dividing by 10000, that accounts for the decimal place being out by around 5 (well, almost). Maybe my reference voltage was slightly different.

Re-testing with the older sketch, and not touching the reference voltage, I get:

Capacitance = 47.51 nF
Capacitance = 47.51 nF
Capacitance = 47.56 nF
Capacitance = 47.56 nF
Capacitance = 47.57 nF
Capacitance = 47.56 nF
Capacitance = 47.58 nF

So the improved one is about 0.2 nF lower, which is about right for servicing an interrupt (2 uS).


Now incorporating the suggested voltage divider with fixed value resistors, and allowing for that in the sketch, I have this:

/*
Capacitance meter

Author: Nick Gammon
Date:   2 July 2013

Pulse pin (D2): Connect to capacitor via 10K resistor (Rc)

Reference voltage connected to D7 (AIN1) as per below.

Measure pin (D6 - AIN0) connected to first leg of capacitor, other leg connected to Gnd.

Like this:

Capacitor to test:

D2  ----> Rc ----> D6 ----> capacitor_under_test ----> Gnd

Reference voltage:

+5V ----> R2 ---> D7 ---> R1 ----> Gnd

*/

const byte pulsePin = 2;   // the pin used to pulse the capacitor
const float Rc = 10000.0;  // charging resistor
const float R1 = 1000;     // between ground and D7
const float R2 = 1800;     // between +5V and D7
const float clockRate_us = 16;  // 16 MHz
const float k = 1000 / (clockRate_us * Rc * log ((R1 + R2) / R2));

volatile boolean triggered;
volatile boolean active;

volatile unsigned long timerCounts;
volatile unsigned long overflowCount;

ISR (TIMER1_OVF_vect)
{
  ++overflowCount;               // count number of Counter1 overflows  
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
  {
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;
  
  if (active)
    {
    // if just missed an overflow
    if ((TIFR1 & _BV (TOV1)) && timer1CounterValue < 0x7FFF)
      overflowCopy++;
    // calculate total count
    timerCounts = (overflowCopy << 16) + timer1CounterValue;  // each overflow is 65536 more
    triggered = true;
    digitalWrite (pulsePin, LOW);  // start discharging capacitor
    TCCR1B = 0;    // stop the timer
    }  // end if active
  }  // end of TIMER1_CAPT_vect

void setup ()
  {
  pinMode (pulsePin, OUTPUT);
  digitalWrite (pulsePin, LOW);
  
  Serial.begin (115200);
  Serial.println ("Started.");
  ADCSRB = 0;           // (Disable) ACME: Analog Comparator Multiplexer Enable
  ACSR = _BV (ACIC);    // Analog Comparator Input Capture Enable
  DIDR1 |= _BV (AIN1D) | _BV (AIN0D); // Disable digital buffer on comparator inputs
  PRR = 0;
  }  // end of setup

void startTiming ()
  {
  active = true;
  triggered = false;
  noInterrupts ();

  // prepare timer
  overflowCount = 0;            // no overflows yet
  // reset Timer 1
  TCCR1A = 0;             
  TCCR1B = 0;   
  TCNT1 = 0;      // Counter to zero
  // Timer 1 - counts clock pulses
  TIMSK1 = _BV (TOIE1) | _BV (ICIE1);   // interrupt on Timer 1 overflow and input capture

  // get on with it
  digitalWrite (pulsePin, HIGH);  // start charging capacitor
  // start Timer 1, no prescaler
  TCCR1B =  _BV (CS10) | _BV (ICES1);  // plus Input Capture Edge Select
  interrupts ();
    
  } // end of startTiming

void finishTiming ()
  {
  active = false;
  Serial.print ("Capacitance = ");
  float capacitance = (float) timerCounts * k;
  Serial.print (capacitance);
  Serial.println (" nF");
  triggered = false;
  delay (3000);
  }  // end of finishTiming
  
void loop ()
  {
  // start another test?
  if (!active)
    startTiming ();
    
  // if the ISR noted the time interval is up, display results
  if (active && triggered)
    finishTiming ();
  
  }  // end of loop

I am now getting this output:

Capacitance = 46.04 nF
Capacitance = 46.05 nF
Capacitance = 46.01 nF
Capacitance = 46.01 nF
Capacitance = 45.98 nF
Capacitance = 46.01 nF
Capacitance = 45.99 nF

I'm happy to hear that it works. To an extent, I'm also happy to hear that it was, "one of those things," because that explains why I found your code working out of the box. But, I'm less happy to hear that "one of those things" exists. I remember the annoyance of the issue of R1 not being zeroed on reset with my first Arduio, but I had hoped that went away with Optiboot 4.4.

The symptoms are odd - worked with one sketch, wouldn't work with another, then worked again with the first sketch, and finally worked with both after a POR.

Do you want to chase this further, have you had enough of it?

Personally I like things to be reliable, so I wouldn't mind finding out what caused the problem.

My tentative guess is that switching from the earlier sketch (which doesn't use the ICR register) to the later one, which does, has something to do with it.

I'm game. Do we still have both sketches - the one that worked and the one that didn't? If I can get those, I'll try to reproduce the problem here. It might take a bit of back and forth.

They are in the posts above, but yes I have them.

No luck. Several sessions of alternating between two sketches, with a sprinkling of resets, yielded no failures. I'm using an Uno rev. 2, Optiboot 4.4, IDE 1.04, Ubuntu 12.04 LTS. I used the sketch from the original post, modified as indicated in post #11, and the sketch from reply #18. I alternated between skethes a total of 250 times, and did many more resets than that. Still no joy.

An earlier version of Optiboot, maybe more than one version, failed to set R1 to zero before handing control to the sketch. If a sketch was reset while R1 wasn't zero - apparently, that happens in division routines - unexpected behavior ensued. I know it was true of Optiboot 3.3, since my first Arduino had that one, and it annoyed me no end. A description appears here: Problem with modulo (%) and reset - #40 by westfw - Bugs & Suggestions - Arduino Forum. The observed symptoms for that quirk were that the sketch appeared to reset over and over, and that reloading the sketch would temporarily restore normal operation. In this thread, we saw the sketch start and fail, and stay in that state through reloads, until power was cycled. Not particularly similar; I don't think this is it.

A possible explanation: Neither sketch disables the digital input buffers on pins 6 and 7, the analog comparator inputs. The ATmega??8P datasheet warns that, "An analog signal level close to VCC/2 on an input pin can cause significant current ..."[02/2013 datasheet, 10.10.6] No word on what "significant" means in this context. The capacitor voltage spends quite a few machine cycles the digital no-man's land between VIL and VIH, even for small values. It's possible that the digital input buffer drew it's significant current, and something inside the IC got into an improper state that it couldn't leave until power went away. I have nothing to support that theory but conjecture, though.

So, other than a reference to an out-of-date problem with Optiboot, and an unsupported theory, I got nothing.

Thanks for the extensive research. I might move on from this, particularly as neither of use seem to be able to reproduce it. The whole thread is more of a proof of concept than the design for an actual meter. That would require more work with outputting displays, etc.

Still it was an interesting exercise.

Yes! Very interesting exercise. The programs worked well.

You explain things just ideally for me in this thread and on your pages, e.g. the Arduino power_down thread there is my constant reading and has lead to several experiments.

The results of this capacitance measurement seem to be very consistent. Would there be still a tiny improvement: the internal resistance of the pin, which charges the capacitor. On AVR datasheet page 411 there are curves how the voltage of the output pin drops when the current increases. A linear curve seems to show, that the voltage drops from 5 V to about 4.5 V, when the current increases from
0 mA to 20 mA (in a certain temperature) . Thus the internal resistance is about 0.5 V / 20 mA = 25 ohms. That value could/should be added to the resistance value of the charging resistor?
(25 ohms is also an interesting number, if connecting a capacitor between an output pin and ground without any external series resistor:
the charging current could be momentarily 5V/25ohms = 200 mA, way above the absolute max 40 mA per pin)

I think you meant meter, not tester.

Are you taking into account stray capacitance of the circuit? As optimistx says, the AVR output pin impedance also needs to be taken into account.

optimistx, yes, you can easily go way over the maximum rated current of an AVR output. 40mA is the maximum that you -should- draw, not the maximum you -can- draw.

I have built uncounted numbers of capacitance meters using 555 timers. The key to compensating for the problem of output impedance and the fact that no output pin really goes to Vcc is to connect the resistor directly to Vcc, and use the output to keep it pulled low. A saturated BJT can go down to 0.2V reliably, and of course a MOSFET can go to what is effectively zero.

This wasn't the first time I built one, only the first time I drew a complete schematic to save:

I'm going to update and build this as an Arduino cap meter, probably using P-channel MOSFETs to switch the appropriate resistor into the circuit, and N-channel MOSFET to pull low where the 555's pin 7 Discharge pin is pulling low in the 2nd 555 timer. I'm about to put in a big order of parts including some P and N channel SMD MOSFETs with about 20mOhm ON resistance logic level that should work great for this.

Of course, rather than the smoothed DC output sent to a 200mV meter, the Arduino timer will check how long it takes to charge the capacitor. I still have to decide if the AVR's internal comparator is good enough for this. My 555 design can stably measure down to 0.1pF (with a meter that can measure 200.0mV) and probably lower with a better meter.

I haven't looked real close at this yet, but it seems like a great project. Has any consideration been made toward safely draining the cap before testing, and again after testing?

No, I didn't want to over-complicate it. I put a 3 second delay into the sketch before repeated tests. For small capacitors at least that should be plenty of time to discharge.

If you were going to turn this into a proper benchtop device (eg. with a readout) then that would be one of the things to take into consideration.

Absolutely. Bookmarking this one... I'm involved in getting a makerspace off the ground here in my town, and this would be a cool project. (Assuming you're OK with that?)

Sure.

I've seen plenty of simple resistance/voltage testers but a capacitance tester is a bit more of a novelty.

polymorph:
The key to compensating for the problem of output impedance and the fact that no output pin really goes to Vcc is to connect the resistor directly to Vcc, and use ...
...

Nice to see handwritten notes of yours from 1985! (?). I like the personal touch.

Avr datasheet has the attached graphic, which shows that the output voltage of an output pin with current 0 mA should be the same as Vcc, (5V). I measured the voltage between an unconnected pin A1 and power pin of an Arduino nano after issuing

pinMode(A1, OUTPUT); 
digitalWrite(HIGH);

My voltage meter M890G showed 1.0 millivolt on the scale of 200 mV. I wonder what is wrong? What should the difference be theoretically, if not 0 mV, and why? (might be useful to pick several readings from other Arduinos).


To be sure that the discharging operation really succeeds I connected the not-grounded leg of the capacitor to pin A0, and added this line to the end of the loop() code:

while (analogRead(A0) >0){};

The existing program lines of tmd3 about waiting the capacitor to become empty could be removed, simplifying the code. (tmd3: thanks for the idea of using log() !)

optimistx:
Avr datasheet has the attached graphic, which shows that the output voltage of an output pin with current 0 mA should be the same as Vcc, (5V). I measured the voltage between an unconnected pin A1 and power pin of an Arduino nano after issuing
...
My voltage meter M890G showed 1.0 millivolt on the scale of 200 mV. I wonder what is wrong? What should the difference be theoretically, if not 0 mV, and why?

Page 313 of the datasheet states that the output high voltage for pins except reset where Vcc is 5V is a minimum of 4.2V, so that is well within spec.

Yes, page 313 has this text:

Output High Voltage
(4)
except Reset pin
I
OH= -20 mA, VCC= 5V
I
OH= -10 mA, VCC= 3V
4.2
2.3
V

This rating is consistent with the graphic on page 411 , where the high voltage is about 4.5 V, when the current is -20 mA.
If I understand right, the original capacitance meter method and software assume that the charging pin supplies current at the voltage VCC, not somewhere near 4.2 V. The voltage divider (R1, R2) is between the ground and VCC, not between ground and charging pin. The formula
V = V0 *(1- e to the power of (-t/RC))
assumes that V0 is constant during the whole charging, and the reference target voltage V is a known fraction of V0, fraction R1 /(R1+R2).
Adding the internal resistance of 25 ohms to the charging resistor value takes care of the pin voltage drop from VCC (from 5V) due to charging current, but not about the possible lower,wrong value of output pin high voltage at current == 0 mA.


Stray capacitance?

Could one take care of the stray capacitance this way:

  • there is a very small capacitor A with known capacitance at the measurement location always (e.g. 22 pF)
  • the software measures the capacitor A and gives a result e.g. 50 pF, due to the capacitances of the capacitor A, wires leading to the measurement position etc.
  • any capacitor B to be measured is put parallel with A and very near it (e.g. on a breadboard within distance of 0.1 inches).
  • measurement with A and B in their positions is done. Assume we get 101.620 nanoFarad.
  • because the capacitors are parallel, their capacitances add. Therefore, the program assumes that the capacitance of B is 101.620 nanoFarad - 50 picoFarad = 101.570 nanoFarad. (the leads from A to B are so short that their stray capacitance is assumed neglible).

optimistx:
My voltage meter M890G showed 1.0 millivolt on the scale of 200 mV. I wonder what is wrong? What should the difference be theoretically, if not 0 mV, and why? (might be useful to pick several readings from other Arduinos).

I measured my Uno, both 5V pin and a digital pin set to output and high. I got:

5V:  4.839V
pin: 4.829V

So it appears that we have lost 10 mV (this was under no load).

Under load (a 10K resistor) it dropped to 4.818V. That would be about 0.5 mA.

Still, the difference of 10 mV under no load is only 10/4839 which is about 0.2%. Thus the calculated capacitance might be out by a small percentage.

optimistx:
I measured the voltage between an unconnected pin A1 and power pin ... My voltage meter M890G showed 1.0 millivolt ... I wonder what is wrong?

Speculation: the difference is due to voltage drop along the internal VCC bus inside the ATmega328P. The IC draws current internally through that bus, and we should expect some voltage drop. If the IC were drawing 10 mA, a 1mV difference would correspond to 0.01 ohms. This project uses a digital output as an analog voltage source. That's essentially an off-label use of the pin, and we can't expect high-grade analog performance.