A few weeks ago I was looking for an ESR meter to build and ran across this thread. Thank you szmeu! I have built this up and it works very well.
I just wanted to share some observations. First, the hardware is a great circuit, simple and effective. Thanks again. And the circuit is very tolerant of the non-polarized capacitor value (47microFarad in the original). I used a 10micro I had and it works fine.
Second, you are correct that the pulse is not from a current source and what you want is to calculate as a voltage divider. (BTW, the original code will get you close enough to serve the purpose, especially at low values of ESR.)
Let's say that Vin is your source voltage (near 5V here), Vout is the value between the two resistors (you measure that at AIN0), Rs is the upper resistance (the 100 Ohm resister) and the lower resistance, the ESR, we'll call Rm. The formula is given as follows.
Rm = Rs / ((Vin/Vout) - 1)
It is important to not mix units, so here is an example. Say your AIN0 measurement, 'milliVolts' variable in the code, is 20. So, Vout is 20mV. Using the value of 5V for the source voltage, Vin, we need to express that in millVolts because that's the unit we're using for Vout. So, Vin is 5000mV. Rs is 100 Ohms so the result will be in Ohms.
Rm (in Ohms) = 100 / ((5000/20)-1) = 100 / 249 = 0.402 Ohms
To get milliOhms, multiply times 1000 or 402 milliOhms!
Here is my re-write of the ESR calc routine. This combines the oversample routine with some things were outside in the original code. A couple of lines are re-ordered. The big calculation difference is at the end.
// ESR calculation routine
// discharge, start a current pulse, measure the voltage at the ESR_PIN, stop the pulse
// repeat 4096 times and average the results ('oversampling and decimation')
// refer to Atmel Application Note AVR121: 'Enhancing ADC resolution by oversampling'
unsigned long accumulator = 0;
unsigned int sample = 0;
int i = 0;
// oversampling 4096 times (for 16 bit is 4^(desiredResolution - ADCresolution))
while ( i++ < 4096 )
digitalWrite( DISCHARGE_PIN, HIGH ); // discharge the capacitors
delayMicroseconds( 600 ); // discharge wait time
digitalWrite( DISCHARGE_PIN, LOW ); // disable discharging
cli(); // disable interrupts for the pulse measure
digitalWrite( PulsePin, PulseActive ); // start current pulse
delayMicroseconds( 1 );
// on the scope it appears that after enabling the pulse
// a small delay is needed for oscillations to fade away
sample = analogRead( ESR_PIN ); // read ADC value
sei(); // measured, enable interrupts
digitalWrite( PulsePin, ! PulseActive ); // stop current pulse
accumulator += sample; // accumulate
// sampling is done
esrSamples = accumulator >> 6; // decimate the accumulated result
// calculate voltage on AIN0 pin...
milliVolts = (esrSamples * vRef) / 65.536;
// calculate ESR in milliOhms...
double Rm = Rs / ((Vin / milliVolts) - 1); // Rm is in Ohms
return Rm * 1000; // milliOhms
This now consistently measures my 0.1 Ohm and 1 Ohm precision resistors within about 2%. And a 4.7 Ohm resistor reads 4.68. Super!
BTW, I used all those extra pins on the Arduino to drive a 3-digit, 7-segment display. (That's why the code above has interrupt disable/enable calls to protect the pulse measure from the display multiplex interrupts.)