Last Modified: 2014-07-10
Every conductor tends to accumulate a small charge when a potential is applied to it. For instance, two metal plates facing each other will accumulate electrons on one side and an electron deficit on the other side when a potential difference is set between them. But even wires or PCB tracks will behave that way. The tendency of a conductor to accumulate more or less charge is given by its capacitance value, written in Farad units and its derivates (milli-farad, micro-farad... and so on). Conventional capacitances values ranges from a few pico-farads to a few milli-farads. Higher and lower values are possible but not often met as electrical components.
Some chemicals placed between the conducting surfaces can greatly increase the capacitance of the couple. As an example, our two facing metal plates will not accumulate the same charge if immersed in water or alcohol. Concerning the capacitance of wires, even your body will modify it as we are mainly composed of water which is a highly polar chemical. This phenomenon is at the basis of some of the touch-screen technologies, but that will be for a later article if we have some time left.
The trick is: how to measure capacitance values? Like always, several technologies exist but I will focus here on one based on oscillatory circuits. More specifically, I will focus on measurement of small capacitance values, down to the femto-farad level (that's right, down to 0.000000000000001 farads!) and up to a few nano-farads. Higher values ranges are also possible but will be met by most multimeter available at any DIY store.
Oscillatory circuits work by charging and discharging capacitance through resistors in loop patterns. Each charge/discharge period is a value that is fixed by both the capacitance and resistor values used and can be extremely reproducible. However, due to the uncertainty of the values of components, the period of oscillation is therefore not accurate and will require some calibration to make the system precise. Still, once the circuit is assembled it is generally stable unless the component values change with time (a classical issue we face is the small change of resistance with temperature). For that reason, we recommend proceeding to a calibration before any measurements.
I will be using here a square wave oscillator based on the NE555 chip that is popular on the Internet and relatively easy to find. Have a look at your local reseller; he surely has some for a few dollars! As explained previously, this oscillator will work by charging/discharging a capacitor through a resistor. More precisely, the classic [∞] NE555 astable square wave oscillator uses two resistor R1 and R2 and a capacitance C. The period of oscillation being τc=ln(2)*C*(R1+2*R2).
At first sight, it would be tempting to plug our unknown capacitance directly into the oscillator in order to produce periods directly proportional to the unknown capacitance value C. But because of R1 and R2 being limited to a few hundred kΩ by the NE555 chip, we would get frequencies up to hundreds of GHz which is clearly out of conventional circuitry specifications!
A slightly smarter approach is to use a capacitor with known values (say, 1 nF) to produce a stable oscillation frequency and to plug our unknown capacitance in parallel to the known capacitor to act as a perturbation on the system. Because we have plugged our unknown capacitance Cunknown in parallel, it will accumulate a small additive charge ∆Q that will slightly increase the response of the charge/discharge circuitry by a period ∆τc:
with τc,base the frequency of the system in the absence of the unknown capacitance.
For my tests, I have used 1 kΩ for R1, 220 kΩ for R2 and 2.2 nF for the base capacitor. This gives a base frequency of about 1500 Hz. I am using a [∞] PIC16F688 microcontroller from Microchip clocked at 20 MHz. To have the best accuracy, I am using the Interrupt 0 pin interruption system which activates the Timer 1 timer to monitor the probe oscillation period τc and transmit it to a nearby computer through the RS232 protocol. Check at the end of the post for the complete source code!
Using a 20 MHz quartz crystal, we get a precision of about 200 ns. We may infer the resolution of our probe by equating this to ∆τc:
which gives, using our resistor values, about 0.65 pF.
To increase the precision even more, we use an artifice which allows us dividing by 100 the effective period reaching the microcontroller. The idea is to use a 74HC390 counter chip to perform two successive divide-by-ten operations to give a minimum capacitance measurement of 6.5 fF. The complete circuit is given on Figure 1.
I have mentioned earlier that calibration is required before every experiment. The procedure is relatively simple:
1. First measure the period in the absence of any unknown capacitance to have a recording of τc,base. Subtract it from all the following measurements such that we can now focus on ∆τc.
2. The link between ∆τc and Cunknown is linear and depend on the exact values of R1 and R2 which may change in time. As we do not need to know the exact contribution of these resistors, we are only interested in finding the linear dependency between ∆τc and Cunknown which can be written as an equation ∆τc=k*Cunknown with k an unknown constant. The best way to find the value of k is to use a known capacitor value and to measure ∆τc.
However, capacitors usually have high tolerance values and uncertainties of 10-15% are fairly common. As a consequence, the value of k will reflect the same uncertainty. If this is a problem for your applicator, don't worry there is a trick. We may assume that the capacitor values of a vendor batch have a Gaussian distribution centred on the targeted component value. This is a fairly acceptable hypothesis since the reasons we get scattered values components are so numerous that the Central Limit theorem should apply. As a consequence, the more capacitors from the batch we get, the more probable we are getting closer to the value printed on the label. Mathematically speaking, the accuracy increases by the square root of the number of components used for the test. All we have to do is to plug a dozen of capacitors from the same batch in parallel on a rail to cumulate their effect. With a labelled value Clabel, you have ∆τc=n*k*Clabel which is well enough to compute k. To have some accuracy figures, simply divide the batch tolerance given by the vendor by √n. For example, using 16 capacitors with an original tolerance of 20% allows you to have a 5% (20%/√16) accuracy on the estimation of k.
During test, I faced an interesting phenomenon given on Figure 2. It seems that there is a strong modification of the recorded capacitance in the first hour of probe usage which I decided to call capacitance heat-up. It is still not clear why this is happening but it may be due to component reaching a thermal steady-state after some delay. Anyway, as a consequence, I recommend turning the probe up at least one hour before making precise measurements (pF levels) and not trying to measure slight changes (less than 0.5 pF) over extended periods of time as the fluctuation seems to be too high for more precise studies.
Still, the probe works quite well and, given its low price, I will surely be using it for experiments in future posts!
The source code for the microcontroller is given below:
#include <pic.h>
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
/* CAP1 */
#define VERSION_IDENT 0x43415031
struct data_s
{
dword dwIdent;
dword dwTime;
dword dwValue;
word cksum;
};
byte g_bClock = 0;
dword g_dwTimeIntegral = 0, g_dwCurrentValue = 0;
void io_init(void)
{
SYNC = 0;
BRGH = 1;
TX9D = 0;
SPEN = 1;
/* 19200 baunds */
SPBRG = 64;
TX9 = 0;
RX9 = 0;
TXEN = 0;
CREN = 0;
TXIE = 0;
RCIE = 0;
}
void io_putc(byte c)
{
TXEN = 1;
TXREG = c;
while(!TXIF);
}
void io_write(byte *p, byte n)
{
while(n--)
io_putc(*(p++));
}
word checksum(byte *p, byte len)
{
word sum = 0;
word *pw = (word*)p;
while(len > 0)
{
sum += ~(*pw);
pw ++;
p += 2;
len -= 2;
}
if(len)
sum += ~((word)(*p));
return ~sum;
}
void send(dword dwTime)
{
struct data_s data;
data.dwIdent = VERSION_IDENT;
data.dwTime = g_dwTimeIntegral;
data.dwValue = dwTime;
data.cksum = 0;
data.cksum = checksum((byte*)&data, sizeof(data));
io_write((byte*)&data, sizeof(data));
}
void init(void)
{
/* port config */
TRISA = 0b00000100;
TRISC = 0b00110000;
/* all pins as I/O */
CMCON0 = 0b00000111;
ANSEL = 0b00000000;
/* timer 0 */
T0CS = 1;
PSA = 0;
PS0 = 0;
PS1 = 0;
PS2 = 0;
T0IE = 0;
/* timer 1 */
TMR1IE = 1;
T1OSCEN = 0;
TMR1CS = 0;
T1CKPS0 = 0;
T1CKPS1 = 0;
TMR1ON = 0;
TMR1L = 0;
TMR1H = 0;
/* Int0 */
INTEDG = 1;
INTE = 1;
}
void interrupt ctrl(void)
{
if(TMR1IF)
{
if(g_bClock < 0xff)
g_bClock ++;
TMR1IF = 0;
}
if(INTF)
{
INTF = 0;
g_dwCurrentValue = 0;
byte old_state = TMR1ON;
TMR0 = 0;
TMR1ON = 0;
T0CS = 0;
if(old_state)
{
g_dwCurrentValue = (dword)g_bClock;
g_dwCurrentValue <<= 8;
g_dwCurrentValue |= (dword)TMR1H;
g_dwCurrentValue <<= 8;
g_dwCurrentValue |= (dword)TMR1L;
}
g_bClock = 0;
TMR1L = 0;
TMR1H = 0;
T0CS = 1;
TMR1ON = 1;
g_dwCurrentValue += TMR0;
if(TMR1IF)
{
g_dwCurrentValue += 0xffff;
TMR1IF = 0;
}
if(!old_state)
g_dwCurrentValue = 0;
}
}
void main(void)
{
init();
io_init();
GIE = 1;
PEIE = 1;
while(1)
{
if(g_dwCurrentValue != 0)
{
send(g_dwCurrentValue);
g_dwTimeIntegral += g_dwCurrentValue;
g_dwCurrentValue = 0;
}
}
}
You may also like:
[»] Measuring Liquids Levels by Capacitance
[»] Split Power Supply with the TC962
[»] Sine Wave Oscillator with Fewer Op-Amp