nalex_software
Published © GPL3+

CapMeter - capacity meter from 10pF to 10mF

Capacity meter with automatic selection of the measurement range.

IntermediateWork in progress427
CapMeter - capacity meter from 10pF to 10mF

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
Through Hole Resistor, 180 ohm
Through Hole Resistor, 180 ohm
×1
Through Hole Resistor, 50 kohm
Through Hole Resistor, 50 kohm
×1
Resistor 1M ohm
Resistor 1M ohm
×1
Test Probe Connector, Hook
Test Probe Connector, Hook
×2
OLED Display, Blue on Black
OLED Display, Blue on Black
Or any other display: 16x2, 8x2, graphic.
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

CapMeter V2 Circuit

Code

CapMeter_V2-1.ino

C/C++
/*
  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();
}

Credits

nalex_software

nalex_software

1 project • 1 follower

Comments