Capacitance meter (Part 3)

Part 1234

Let’s talk about the code for a while. Here is the sequence of commands that we will execute:

  • Charge capacitor
  • Start discharge cycle
  • Acquire scanned data
  • Compute time constant
  • Compute Capacitance
  • Format and print result
  • Wait for completion of discharge cycle
  • Loop back

The tirikiest part of the code is probably the calculation part for the time constant equation. How does it work? As seen before, the transient curve can be described with an exponential function. If you reached this point you may wonder:

  • Expo… what function?
  • Ouch, that is reminding me of my younger times
  • So what, a kid could do it
  • Gosh, I should have listened better my maths class
  • Ah come on, tell us now

Ok, I will tell you how. In fact, all we have to do is to linearize the raw data using a Log function. So that we can now apply a simple linear model (y=b.x+a) on the linearized data. What about the tangeant now? The equation of the tangent comes the first derivative from the exponential curve. Once we find this function, we solve it for y = 0 and get x, the time constant. Well, at this point, I will save your nerves and brains. Luckily, thanks to the simplicity of the exponential derivatives and to some fancy simplifications, the time constant is equal to the slope from the linearized raw data!

Computing this slope comes easy now, and here is its equation (no need for matrices this time)

Just keep in mind that this slope routine integrates the linearization of the ordinates.

double slope(void) 
{
	/* Compute mean of xs */
	double meanX = 0;
	for (uint16_t i = 0; i < samples; i++) {
		meanX += (double(i) / samplingFrequency);
	}
	meanX /= samples;
	/* Compute mean of ys */
	double meanY = 0;
	for (uint16_t i = 0; i < samples; i++) {
		meanY += log((PADC.ReadData(i) * adcReference) / 1024.0);
	}
	meanY /= samples;	
	/* Compute sums */
	double sumXYDiff = 0;
	double sumXXDiff = 0;
	for (uint16_t i = 0; i < samples; i++) {
		double XDiff = ((double(i) / samplingFrequency) - meanX);
		double YDiff = (log((PADC.ReadData(i) * adcReference) / 1024.0) - meanY);
		sumXYDiff += (XDiff * YDiff);
		sumXXDiff += (XDiff * XDiff);
	}	
	/* Compute and return result */
	double result = (sumXYDiff / sumXXDiff);
	return(result);
}

Note: PADC.ReadData() randomly reads the ordinates values acquired with the PlainADC functions.

Starting from there, computing the capacitance is almost trivial

	/* Compute capacitor value */
	double timeConstant = (-1.0 / slope()); /* In seconds */
	double capacitorValue = (timeConstant / resistorValue) * 1E+12; /* In farads */

Finally, the main loop looks like

void loop() 
{
	upperLimit = uint16_t((4.5 * 1024.0) / adcReference);
	/* Start capacitor charge */
	PORTB |= (1 << CAP_DRV_PIN); 
	/* Wait until the capacitor is almost fully charged */
	while(PADC.GetSingleData() < upperLimit){};
	/* Turn led on */
	PORTB |= (1 << LED_PIN); 
	/* Mark event */
	PORTB &= ~(1 << SYN_PIN); 
	/* Start capacitor discharge */
	PORTB &= ~(1 << CAP_DRV_PIN); 
	/* Acquire data */
	PADC.GetDataScan();
	/* Mark event */
	PORTB |= (1 << SYN_PIN);  
	/* Turn led off */
	PORTB &= ~(1 << LED_PIN); 
	/* Compute capacitor value */
	double timeConstant = (-1.0 / slope()); /* In seconds */
	double capacitorValue = (timeConstant / resistorValue) * 1E+12; /* In pF */
	Serial.print("C = ");		
	Serial.print(capacitorValue, 0);
	Serial.print(" pF");		
	Serial.println();			
	/* Wait until the capacitor is almost fully discharged */
	lowerLimit = uint16_t((0.5 * 1024.0) / adcReference);
	while(PADC.GetSingleData() > lowerLimit){};
	/* Loop delay */
	delay(1000);
};

Note: PADC.GetSingleData() performs A/D conversion in less than 8 µs, and PADC.GetDataScan() acquires vectors at programmable rates ranging from .1 Hz to 128 kHz. Both belong to the PlainADC library.

Reading capcitance in pF may not be very appropriate for bigger capacitors, so that the print routine may be optimised in this way:

void printCapacitorValue(double capacitorValue) 
/* Print capacitor value in the most appropriate unit */
{
	Serial.print("Capacitance = ");
	if (capacitorValue < 1.0E-9) {
		Serial.print(capacitorValue * 1.0E+12, 2);
		Serial.print(" pF");
	}
	else if (capacitorValue < 1.0E-6) {
		Serial.print(capacitorValue * 1.0E+9, 2);
		Serial.print(" nF");
	}
	else if (capacitorValue < 1.0E-3) {
		Serial.print(capacitorValue * 1.0E+6, 2);
		Serial.print(" uF");
	}
	else if (capacitorValue < 1.0) {
		Serial.print(capacitorValue * 1.0E+3, 2);
		Serial.print(" mF");
	} 
	else {
		Serial.print(capacitorValue, 1);
		Serial.print(" F");	
	}	
	Serial.println();	
};

Next post on same subject

3 Comments

  1. Pintokitkat says:

    I’ve tried sending you an email using the code request form, but it doesn’t get delivered. I’ve tried again a minute ago, but just in case it doesn’t make it to you I thought I’d drop a note here just to start contact.

  2. Pintokitkat says:

    Failed again. Every time I try to send you a code request (and I understand your reasons for doing it this way, but it seems to bring this drawback) it fails to be delivered with the following reason:

    Delivery to the following recipient failed permanently:

    didier.longueville@arduinoos.com

    Technical details of permanent failure:
    DNS Error: DNS server returned general failure

    do you have any suggestions as to how I might contact you other than abusing this forum?

Leave a Reply

You must be logged in to post a comment.