Go Down

Topic: An improvement to cap sensing (Read 2229 times) previous topic - next topic

lyrez

Hi,

I'm new there, and i would like to share one idea i got since i ordered my arduino mega. In playground, i found a very interesting  article about capacitor sensing. The author (Mario Becker, Fraunhofer IGD) measured the time to charge a capacitor through the pull-up resistor of a i/o pin of arduino, that gave him a rough measure of capacitor. (Time is somewhat proportional to capacitor, given the initial/final state, and given the value of the pull-up resistor; that are roughly constant)
He measures with a simple loop this time by incrementing a value as long as capacitor is not charged. I found this was very clever, and i found amazing you  just need an arduino and a capacitor to test; without any other component !
My improvement was to use timer1 as a counter (counting at 16 Mhz) to measure the time; so this gives a way higher sensitivity and i believe, accuracy.
Following is the code :

Code: [Select]
#include <avr/interrupt.h>
#include <avr/io.h>
#include <inttypes.h>

// sensor pin is PB6 on mcu (PWM 12 on my arduino mega)


static volatile int32_t time;

static volatile uint32_t count;

static volatile uint8_t notovf=1;

static volatile uint32_t remain;


int32_t read_cap()
{

 cli();
 
 DDRB &= ~_BV(PB6); //  PB6 as input

 notovf=1; // no overflow
 
 TCNT1 = 0 ; // reset counter
 
 PORTB |= _BV(PB6); // switching the pull-up resistor

 sei(); // enabling interupts

 TCCR1B = _BV(CS10); // prescaler x1 (no prescaler)  timer 1 starts    

 //  TIMER 1  is started          

 // cap is charging

 count=0UL;
 
 while (((PINB & _BV(PB6))==0) && notovf); // loop while cap not yet charged

 remain=TCNT1; // remainder time

 cli();
 
 time=(count << 16)+remain; // total time

 DDRB |= _BV(PB6); // switch pb6 (port pwm 12 of arduino mega) as output

 PORTB &= ~_BV(PB6); // we empty the cap (switch pb6 to low level)

 delay(5); // waiting a bit for discharge
 
 if (!notovf) return -1; // return in case of overflow
 
 return time; // no overflow, valid result
}


void setup()
{
 TIMSK1 = _BV(TOIE1);   // activate TIMER1_OVF_vect (when TCNT1=65536)

 Serial.begin(9600);      // connect to the serial port
}

void loop ()
{
 int32_t capval;
 delay(150);
 capval = read_cap();
 Serial.print("Capacity");
 Serial.print(" = ");
 Serial.print(capval,DEC);
 Serial.println("");
}

ISR(TIMER1_OVF_vect)
{
 count++;
 if (count>32766) notovf=0;
}



Thanks for you attention !

Jean-michel

retrolefty

#1
Nov 08, 2010, 01:13 am Last Edit: Nov 08, 2010, 01:16 am by retrolefty Reason: 1
I loaded the sketch and uploaded to my mega board. It does run and with no cap in pin 12 it reads a residual value of 194 and when I wire a .1ufd cap from pin 12 to ground it displays a value 4980930 with of course lots of changes in the last two digits of the displayed value. Now if I could figure you how to scale/convert that to an actual capacitance value it might be useful.  ;)
http://en.wikipedia.org/wiki/RC_time_constant shows the relationship but not sure how to make the conversion.

Thanks for passing along the code.

Lefty

lyrez

Hi retrolefty

I get the same offset of 194 ticks with no cap. I presume it's mostly due to the software delay between the setting of the  pull-up resistor and the actual starting of timer. The idea is to substract this offset, and then apply a scale to get pF for example. I figured out there's a ratio between the pF and ticks of about 4 (or 3) ticks per pF. Then you can get the value using : C(pF)=(ticks-194)/4;
That's rought, don't espect too much !
You can try to get the good ratio, at home the ratio is around 4.1
Thanks for your reply !  :)

lyrez

Hi,
Sorry for last reply : In fact, there's no linear relation between number of "ticks" and capacity value  :(
I told you this because I made the same (about) software before on an atmega 8, on a myAvr Board (http://www.myavr.fr/support/viewtopic.php?f=11&t=386) , and i found a very accurate such relation, that allowed a real capacity measurement. But there; on an Arduino Mega, there is a way  non-linear behaviour . Why ? i don't know. It remains a way to "sense" capacity, that, after all is not so bad !  :)

Jean-michel

MarkT

There is probably no way to have accurate measurement this way.   The "pull-up" is probably a depletion-mode FET (easier to fabricate, acts approximately as a constant current source).

Different pins on the same chip and different chips will all vary, so callibration would be necessary each time.

If the pull-up is a FET then it will be very temperature and supply-voltage dependent, and will drift with time on short and long term due to effects such as ion-migration.

Future revisions of the chip might have a very different pull-up behaviour - the current datasheet just says its between 20k and 50k, so a change by a factor of 2 is within spec!  The Mega is a totally different chip and I haven't read the datasheets for the ATmega1280, there is no a priori reason to expect the same behaviour.

The only way to get a reasonably robust measurement is ratiometric - use the same pin to measure a known capacitance then the DUT, then use the ratio to calculate the value.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

borref

Here's another approach to capacitance metering using the Arduino.

First the capacitor is discharged (shorted to ground) and then time is measured until it reaches 63.2% of the supply voltage (using analogRead). This equals one time constant and so Capacitance can be calculated as C = t / R. R is a fixed external resistor (of your choice) and t is the time measured.

http://arduino.cc/it/Tutorial/CapacitanceMeter

Another possibility is to use the AtMega's lesser known comparator. Connect capacitor plus (if it has polarity) to one comparator input and the same point to a resistor and then to a digital output. Capacitor minus to ground. That is the RC element connects between ground and a digital output and comparator sensing across the capacitor.

The other comparator input should go to a pot meter (voltage divider) calibrated to 3.16V (63.2% of 5V).

Then charge the capacitor (set digital out to high) and measure the time until the comparator trips. This could be done with comparator interrupts enabled and would allow for faster measurements as it will not be limited by ADC bandwidth.

lyrez

hi
Thanks for these ideas , i was just thinking of the first one, but as you said, it's not very sensitive, as the ADC works not very fast. The second idea look interesting, i did'nt thought of it. I tried yesterday to use an external resistor, but, unfortunately i got the same issues as with the pull-up.
I'll try to investigate for your second idea ! thanks !  :)

Jean-michel

lyrez

Hi,

Here we are !  :)
I finally got it : a good capacitor meter on the arduino with only an arduino and 2 wires (one on gnd the other on pin12 and the capacitor in between.
Note, at startup, or reset, there's an auto zero function, so do not plug the cap at reset/startup. I was trying to reinvent the wheel; for measuring short time delays accurately, and there was the micros() function that did it easily. Hence, code is much more on "arduino way" and it work way better (i mean more accurately)

Lyrez


Code: [Select]

#define RATIO (10.0/254.0)
#define MAXSAMPLES 8

int SENSE_PIN  = 12;

double zero = 0.0;

double read_cap()
{  
 
 long int time;
 
   time=micros();
   
  pinMode(SENSE_PIN,INPUT); // switching pull-up resistor : cap is charging

 digitalWrite(SENSE_PIN,HIGH);
 
 while ((digitalRead(SENSE_PIN))==LOW); // loop while cap not yet charged  
 
 time=micros()-time;
 
 pinMode(SENSE_PIN,OUTPUT);
 
 digitalWrite(SENSE_PIN,LOW);

 delay(4);
 
 return (RATIO*time)-zero;
}
double average_cap()
{
 double sum=0.0;
 for( int i=0; i<MAXSAMPLES;i++)
 sum+=read_cap();
 return sum/(1.0*MAXSAMPLES);
}

void setup()
{
 Serial.begin(9600);      // connect to terminal
 zero=average_cap(); // plug no cap at startup
}
void loop()
 {
   
   
 double capval;
 delay(10);
 capval = average_cap();
 if (capval<0.0) capval=0.0;
 Serial.print("Capacity");
 Serial.print(" = ");
 Serial.print(capval);
 Serial.println(" nF");
}



Go Up