/*
CapMeter V2.1 - capacity meter for Arduino.
Capacity meter with automatic selection of the measurement range.
It measures the charging and discharging times of the capacitor,
and converts the measured values into capacitance.
The measured capacity is from 10pF to 10mF (10000uF).
A one-time meter calibration is required. For calibration, you need to turn on
the meter with the calibration button pressed. Then connect the reference
capacitors, the value of which is indicated on the display. The connection
of each capacitor must be confirmed by pressing the calibration button.
Information is output to an external display and to a serial port. Customize
the code for the display you are using (U8X8_ON and I2C_LCD_ON macros).
Warning! The capacitor must be completely discharged before measurement.
Otherwise, the device may be damaged.
Version 2.1 - added support for various displays.
Version 2.0 - added calibration.
Version 1.0 - first release.
(c) NaLex Software
Blog: http://nalexsoft.blogspot.com
Contact: nalexsoft@gmail.com
*/
#define U8X8_ON 0 // U8x8lib (1) or LiquidCrystal (0)
#define I2C_LCD_ON 1 // Serial (1) or Parallel (0) LiquidCrystal
#if U8X8_ON
// library for working with graphic displays
#include <U8x8lib.h>
// create a display object
// clock, data, cs, dc, reset
//U8X8_ST7565_KS0713_4W_SW_SPI lcd(11, 12, 9, 10, U8X8_PIN_NONE);
U8X8_ST7565_64128N_4W_SW_SPI lcd(11, 12, 9, 10, U8X8_PIN_NONE);
// set display contrast
#define CONTRAST 60
#else
#if I2C_LCD_ON
// library for working with LCD
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// create a display object
LiquidCrystal_I2C lcd(0x27, 16, 2);
#else
// library for working with LCD
#include <LiquidCrystal.h>
// create a display object
// RS, E, DB4, DB5, DB6, DB7
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
#endif
#endif
#include <EEPROM.h>
#define ADC_input A0
#define ADC_max 255 // ADCH 8-bit
#define numOuts 3
#define AUTORANGE_ON 1
enum controls
{
kSelectUp = 0,
kSelectDown,
kNum
};
//bool swState[kNum];
bool swOn[kNum];
uint8_t swPin[kNum];
int input, lowLevel, highLevel, calibrationCap[numOuts];
long int counter, counterLimit[numOuts], corrCounter, resultCounter[2];
bool charge, measurementBreak, calibrationOn;
uint8_t out[numOuts];
int8_t select_old = -1, select = 0; // start range, 0 for autorange
float divider[numOuts], cap[2];
char* nominal[numOuts];
// the setup routine runs once when you press reset
void setup()
{
// initialize serial communication at 9600 bits per second
Serial.begin(9600);
// set control pins
swPin[kSelectUp] = 6;
swPin[kSelectDown] = 5;
// set out pins
out[0] = 2; // 180 Ohms, uF
out[1] = 3; // 50k Ohms, nF
out[2] = 4; // 1M Ohms, pF
// set calibration caps
calibrationCap[0] = 47; // uF
calibrationCap[1] = 220; // nF
calibrationCap[2] = 1000; // pF
// set nominals
nominal[0] = "uF";
nominal[1] = "nF";
nominal[2] = "pF";
// switches setup
for (int i = 0; i < kNum; i++)
{
//swState[i] = false;
swOn[i] = false;
pinMode(swPin[i], INPUT_PULLUP);
}
// ADC setup
ADMUX = 0x60 - A0 + ADC_input; // ADC input, 0x60 is A0, 0x61 is A1, ...
ADCSRA = 0xe2;
// clock divider, 0b010 -> 4, 0b011 -> 8, 0b100 -> 16
bitWrite(ADCSRA, 0, 0);
bitWrite(ADCSRA, 1, 0);
bitWrite(ADCSRA, 2, 1);
// thresholds setup
lowLevel = ADC_max / 3.; // 1/3 VCC like NE555
highLevel = ADC_max - lowLevel; // 2/3 VCC like NE555
// display setup
#if U8X8_ON
lcd.begin();
lcd.setContrast (CONTRAST);
//lcd.setFlipMode (0); // mirror
lcd.setFlipMode (1); // rotation 180 deg
// set font
lcd.setFont(u8x8_font_px437wyse700b_2x2_r);
//lcd.setFont(u8x8_font_amstrad_cpc_extended_f);
#else
#if I2C_LCD_ON
lcd.init();
lcd.backlight();
#else
lcd.begin(16, 2);
#endif
#endif
// clear memory
lcd.clear();
setDividers();
// if calibration
// check dividers to zero
float minDiv = divider[0];
for (int i = 0; i < numOuts; i++) minDiv = min(minDiv, divider[i]);
//if (digitalRead(swPin[kSelectUp]) == 0 && digitalRead(swPin[kSelectDown]) == 0) calibration();
if (digitalRead(swPin[kSelectUp]) == 0 || minDiv <= 0) calibration();
calibrationOn = false;
}
// the loop routine runs over and over again forever
void loop()
{
// switches
for (int i = 0; i < kNum; i++)
{
bool butt = digitalRead (swPin[i]);
// trigger
if (butt == 0 && !swOn[i])
{
//swState[i] = !swState[i];
swOn[i] = true;
// buttons action
if (i == kSelectUp) select++;
if (i == kSelectDown) select--;
// loop limits
if (select >= numOuts) select = 0;
if (select < 0) select = numOuts - 1;
}
if (butt == 1 && swOn[i]) swOn[i] = false;
}
selector();
draw();
delay (500);
}
void setDividers()
{
// set dividers
for (int i = 0; i < numOuts; i++)
EEPROM.get(i * sizeof(float), divider[i]);
// set counter limits
counterLimit[0] = divider[0] * 11e3;
counterLimit[1] = divider[1] * 5e3;
counterLimit[2] = divider[2] * 5e3;
}
void selector()
{
// pins setup
if (select_old != select)
{
select_old = select;
for (int i = 0; i < numOuts; i++)
if (i == select) pinMode(out[i], OUTPUT);
else pinMode(out[i], INPUT);
digitalWrite(out[select], LOW);
for (int i = 0; i < 2; i++) cap[i] = 0;
delay(100);
}
// read the input on analog pin, in range 0..1023 (10-bit)
input = ADCH; //analogRead(ADC_input); // A0 is pin 14, A1 is pin 15
// charging
if (input < lowLevel)
{
charge = true;
digitalWrite(out[select], HIGH);
measurement(0);
}
// discharging
else if (input > highLevel)
{
charge = false;
digitalWrite(out[select], LOW);
measurement(1);
}
else if (!calibrationOn && AUTORANGE_ON)
{
select--;
if (select < 0) select = 0;
}
}
void measurement(uint8_t i)
{
corrCounter = counter = 0;
measurementBreak = false;
if (charge)
while (ADCH < highLevel)
{
counter++;
if (ADCH < lowLevel) corrCounter++;
if (counter > counterLimit[select])
{
measurementBreak = true;
break;
}
}
else
while (ADCH > lowLevel)
{
counter++;
if (ADCH > highLevel) corrCounter++;
if (counter > counterLimit[select])
{
measurementBreak = true;
break;
}
}
resultCounter[i] = counter - corrCounter;
cap[i] = double(resultCounter[i]) / divider[select];
if (!calibrationOn && AUTORANGE_ON)
{
if (measurementBreak) select = 0; //--;
else
{
// range auto detect
if (cap[i] < 2) select++; // limit setup
if (cap[i] > 2500) select--; // limit setup
//if (select >= numOuts || select < 0 || counter == 0 || cap[i] < 0) select = 0;
if (select >= numOuts || select < 0 || cap[i] < 0) select = 0;
}
}
}
void draw()
{
float capAverage = (cap[0] + cap[1]) / 2.;
lcd.clear();
lcd.setCursor(0, 0);
/*char txt[20];
sprintf(txt, "Cap,%s", nominal[select]);*/
lcd.print("Cap," + String(nominal[select])); //txt);
#if U8X8_ON
lcd.setCursor(0, 3);
#else
lcd.setCursor(0, 1);
#endif
lcd.print(!measurementBreak ? String(capAverage) : "OVER");
// print to serial port
#if 0
// debug
for (int i = 0; i < numOuts; i++)
Serial.println(divider[i]);
Serial.println("---");
Serial.println(counterLimit[select]);
Serial.println(corrCounter);
Serial.println(String(counter));
Serial.println("---");
Serial.println(String(input));
Serial.println(String(cap[0]) + " + " + String(cap[1]));
Serial.println("---");
#endif
Serial.println(!measurementBreak ? String(capAverage) + " " + String(nominal[select]) : "OVER");
Serial.println();
}
void dualPrint(String str)
{
lcd.print(str);
Serial.println(str);
}
void calibration()
{
calibrationOn = true;
lcd.setCursor(0, 0);
lcd.clear();
dualPrint("Release");
while (digitalRead(swPin[kSelectUp]) != 1 || digitalRead(swPin[kSelectDown]) != 1) delay (100);
for (int i = 0; i < numOuts; i++)
{
select = i;
counterLimit[i] = 1e5;
while (digitalRead(swPin[kSelectUp]) != 0 && digitalRead(swPin[kSelectDown]) != 0)
{
// measurement
selector();
divider[i] = double(resultCounter[0] + resultCounter[1]) / calibrationCap[i] / 2.; // average
// draw
lcd.clear();
lcd.setCursor(0, 0);
dualPrint(String(calibrationCap[i]) + String(nominal[select]));
#if U8X8_ON
lcd.setCursor(0, 3);
#else
lcd.setCursor(0, 1);
#endif
dualPrint(String((cap[0] + cap[1]) / 2.)); // average
delay (500);
}
if (digitalRead(swPin[kSelectDown]) != 0) // else skip EEPROM write
EEPROM.put(i * sizeof(float), divider[i]);
lcd.clear();
dualPrint("Release");
while (digitalRead(swPin[kSelectUp]) != 1 || digitalRead(swPin[kSelectDown]) != 1) delay (100);
}
setDividers();
}
Comments