LC Meter
LC Meter
Introduction
Several DIY meters are available on the web. One of the most accurate and widely acclaimed was
designed and sold by Neil Hecht and his company AADE (Almost All Digital Electronics). Neil passed
away in 2015 but his design lives on.
An AADE-inspired design was recently implemented by “coreWeaver”, using an ATmega328 for the MCU
and a Nokia 5110 LCD for the display. The author designed the circuit, PCB, and 3D-printed case. And
he provides a four-part video describing his device. The documentation for his project, including links
to his videos, is found on his GitHub page at: https://github.com/coreWeaver/LC-Meter
The notes below describe my experience with the author’s project. The author wrote his firmware in
AVR Basic (BASCOM). He provides the source code and the .HEX file for anyone who would like to use
his software.
I used the author’s PCB but chose to write my own software. Why?
Hardware
The operation of this circuit is described elsewhere. Briefly, an LM311 comparator is configured as an
oscillator. The frequency-determining components of this oscillator are a parallel 82 uH coil L1 and
1000pF capacitor C12, which resonate at approximately 500 kHz. The component under test is added
to the LC tank -- either a coil in series with L1 or a capacitor in parallel with C12 -- and the new frequency
is measured. The change in frequency is mathematically related to the unknown component’s value.
The accuracy of the meter depends on an internal calibration capacitor, which is nominally a 1000pF
component with 1% (or better) tolerance.
The oscillator frequencies are measured by the MCU, which calculates the component value and
displays it on an LCD screen.
Power is supplied via a 9V battery. A 7805 voltage regulator is used to supply 5V to the MCU and
oscillator. The regulator is large for the modest current requirement of the board. An LM317 is used to
provide the 3.3V necessary for display. R1 and R3 control the voltage output of this regulator. R5 and
R6 form a resistive divider which is used for measuring the battery voltage, converting the 9V input into
a measurable 4.5V. (The MCU cannot measure voltages higher than 5V.)
The microcontroller is an ATmega328, the same device used in the Arduino UNO. Although this
component has been around many years, it has more than enough I/O pins and computing power for
the job. It uses an 8 MHz crystal clock, running half as fast as the 16 MHz UNO. The 100nF bypass caps
C5, C8, C9 are the usual MLCC type and the values are not critical. C7, C8 are loading caps for the crystal
oscillator; small ceramic discs of 20-27pF are OK for these. R4 is a 10K pullup resistor for the
MCU/display reset line.
The display is a Nokia 5110 monochrome 84 x 48 pixel LCD. This display is very readable in daylight and
has backlight LEDs for nighttime viewing. It requires very little power. R7-R14 are used to convert 5V
signals from the MCU into 3.3V signals for the display.
There are three display LEDs and two standard 6mm pushbutton switches. The switches do not have
external pullup resistors on them, so internal pullups should be enabled by software.
Construction
All components, except the battery, probes, and power switch, are mounted on a single PCB. The PCB
pads and holes are small, so a small-tipped soldering iron and small-gauge solder (0.015”) are
recommended. The resistor pads are only 7mm apart; you must bend the leads tightly against the ¼
watt resistor bodies in order to place these components.
I like to complete the build in stages, testing as I go. First, I fitted all the power components and
confirmed 5V and 3.3V output. Then I added the display and its support circuitry, creating the necessary
solder bridge for the backlight. Next, I added the MCU parts and ran a diagnostic sketch to confirm that
the MCU and display were working. Then I added the switches, LEDs, and current limiting resistors and
ran a second sketch to test those components. In the final step I added the LM311 and all the remaining
components.
Some of the components proved difficult to source. Here a partial list of the components and where I
obtained them. You will also need to have a PCB made from the author’s Gerber files.
Partial Parts List:
After building the board, it’s time to upload software. The author’s PCB does not have an ICSP (in circuit
serial programming) port, so you must remove the MCU chip from your meter and insert it into a
dedicated programmer. From the Tools menu of the Arduino IDE,
Next, install a library for the Nokia 5110 display. I am using the one from Adafruit called “PCD8544”.
Also install the Adafruit GFX library, if not already installed.
Finally, try uploading a simple sketch, and replace the programmed MCU into the LC meter.
This module was originally developed in 2001 for the Nokia 5510 mobile
phone. The module contains a monochrome 48 x 84 pixel LCD and a
Philips PCD8544 driver IC. In its current form, the module is contained
in a metal housing that clips to a PCB carrier.
This is a 3.3V device that uses SPI serial bus for communication. There
are 8 interface pins: reset, chip enable, data/cmd, data in, clock, Vcc,
backlight, and ground.
Back of the Nokia 5510 display
The PCB carrier contains 4 LEDs, which function as the display backlight.
On my unit, the anodes of LEDs connect directly to Vcc, and each cathode
is tied to the “backlight” pin via a 150-ohm resistor. As a result, grounding
the backlight pin causes all 4 LEDs to turn on. On the back of my display
carrier there is a jumper position marked “JP”. Create a solder bridge
across this jumper to turn the backlight on permanently. (Soldering across
this jumper is easier than soldering the small one found on the LC meter
PCB.) The current consumption of my module is 11 mA for the backlight
and less than 1 mA for the display. There are several varieties of this
PCB carrier with display removed display on the market, and not all of them have the same backlight
configuration.
Hello World
The first and simplest diagnostic sketch for any display is to print “Hello World”:
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
Adafruit_PCD8544 lcd =
Adafruit_PCD8544(13, 11, 12, -1, 3); // Pins for CLK,Din,DC,CS,RST
void setup() {
lcd.begin(); // initialize the display variable
lcd.setContrast(60); // good values are between 40 and 70
lcd.clearDisplay(); // start with blank screen
lcd.setTextSize(2); // use big and beautiful characters
lcd.println("Hello"); // say something profound
lcd.println("World");
lcd.display(); // update the screeen
}
void loop() {}
The first two lines, prefaced with “#include”, pull in two libraries needed by the Nokia 5510 display. You
will need to install both in your Arduino IDE first. The next line instantiates the lcd variable, indicating
which Arduino pins connect to the display Clock, Data, Data/Command, Chip Select, and Reset lines.
Notice that the Chip Select pin is specified as “-1”, which means that the display is selected all the time.
Referring to the schematic, you will notice that the CE (same thing as CS) line is tied to ground via R15,
thereby keeping the device always enabled.
In setup(), there are a few lines to specify the size of the text and
contrast level for the display, followed by printing the words Hello
and World. Notice the last line, lcd.display(). The Nokia display
does not automatically show all its updates. These are written into
memory but not shown until you explicitly say so. The display
remains blank until you call lcd.display().
The 2 buttons and 2 LEDs are each tied to a microcontroller pin. I like starting my sketches with a list of
the pins and the devices they are connected to. Compare these #defines with the schematic:
#define C_KEY 9 // PB1: "KEY1" pushbutton
#define L_KEY 2 // PD2: "KEY2" pushbutton
#define C_LED 5 // PD5: LED for C_MODE
#define L_LED 6 // PD6: LED for L_MODE
Now we can refer to these interface components by name, rather than the pin they are connected to.
Next, at the start of the sketch, declare each of these MCU pins as input or output:
void initPorts() { // INITIALIZE MCU PINS
pinMode(L_KEY,INPUT_PULLUP); // L pushbutton
pinMode(C_KEY,INPUT_PULLUP); // C pushbutton
pinMode(C_LED, OUTPUT); // C LED
pinMode(L_LED, OUTPUT); // L KED
}
The Keys are on input pins and the LEDs are on output pins.
INPUT_PULLUP means that the MCU internally applies a pullup
resistor to the pin, ensuring that its value is logic 1 in the absence
of any grounding input. Pressing a key pulls its corresponding pin
to ground. Here is the code:
bool LKeyPressed() { // is the L key pressed?
return !digitalRead(L_KEY); // pressed = pin is grounded
}
To control the LEDs, the MCU applies 5V to the corresponding pins. We can
control both LEDs with a single routine:
void setLEDs(int led1, int led2) { // control the LEDs
digitalWrite(L_LED,led1); // turn the L LED on/off
digitalWrite(C_LED,led2); // turn the C LED on/off
}
With these routines in place, we can write a full Arduino sketch to test the buttons
and LEDs. You can download the full sketch “LC_Blinker” on GitHub.
void setup() {
initPorts(); // initialize MCU ports
initDisplay(); // initialize NOKIA display
}
void loop() {
if (LKeyPressed() && CKeyPressed()) { // both keys pressed?
setLEDs(1,1); // ..light both LEDs
} else if (LKeyPressed()) { // only left key pressed?
setLEDs(1,0); // ..light left LED
} else if (CKeyPressed()) { // only right key pressed?
setLEDs(0,1); // .. light right LED
}
delay(700); // for a little bit
setLEDs(0,0); // then turn off the LEDs
delay(300); // repeat every second
}
Hardware Timers
Any discussion about the internals of an MCU gets complicated - fast. So, with that in mind, you may
keep your sanity and skip ahead to the next section. Or, grab a beverage and learn how to turn our
trusty ATmega328 into a frequency counter. The basic idea is simple: count the number of pulses in
exactly one second, and that number is the frequency in Hertz. 1000 pulses per second = 1000 Hz. We
need two routines: one to count pulses, and one measure exactly one second. Let’s consider the
second-timer first.
The ATmega328 MCU contains three internal timer/counters, which I abbreviate TC0, TC1, and TC2. TC0
and TC2 are 8-bit counters, meaning that they can count to 256. TC1 is 16 bits in size. What to these
devices count? Usually, they count the MCU’s own clock pulses or some fraction thereof. And each
timer/counter has several different modes of operation. We will only use the mode called “Clear Timer
on Compare Match” (CTC), in which the timer counts to a certain value and then resets to zero.
Back to the topic at hand: how to we measure out a second? Set TC1 to count clock cycles until exactly
one second has passed. If our clock is running at 8 MHz, then we will need to count to 8,000,000 to
mark out 1 second. This is number is too large for our 16-bit counter, which only has enough bits to
count to 65536. But, by using a prescalar, we can specify a much slower clock, and therefore won’t have
to count so high. For example, a prescalar of 256 will result in a clock of 8MHz/256 or 31.250 kHz. If we
count to 31250 with this lower frequency clock, exactly 1 second will have passed.
Complicated, but bite sized. It puts TC1 into CTC mode, using a prescalar to reduce the counter input
frequency to 31.25 kHz, and counts 31249 pulses. When the 31250th pulse comes in, an interrupt is
generated and the counter resets.
TC1 is our one second timer. Now we need something to count the incoming pulses. I wrote above that
Timer/Counters usually count MCU clock pulses. However, TC0 and TC1 have a special mode in which
they count external pulses instead. The schematic and PCB connect the external oscillator to the input
pin of Timer0.
The following code will put TC0 into CTC mode, counting external pulses on pin PD4:
TCNT0 = 0; // TIMER0 SETUP: count external pulses
TCCR0A = bit(WGM01); // CTC Mode; no external outputs
TCCR0B = bit(CS00)+bit(CS01)+bit(CS02); // use T0 (external) source = Digital 4
OCR0A = 256-1; // count to 256
TIMSK0 = bit(OCIE0A); // enable timer overflow interrupt
Notice that this 8-bit timer can’t count higher than 256. To get around this limitation, our Timer0
interrupt routine increments an overflow counter each time that we’ve counted to 256. And our 1-
second interrupt routine for TC1 will multiply that count by 256 to get the total number of counts per
second:
ISR(TIMER0_COMPA_vect) { // INTERRUPT SERVICE ROUTINE: Timer0 compare
ovfCounter++; // increment overflow counter
}
After setting up the timer registers and associated interrupt routines, the frequency counter is running
in the background, counting pulses on Arduino digital pin 4, and updating the measured frequency once
per second. We don’t have to tell it to start, or stop, or wait. It is always on.
As an aside, Timer/counter0 is used by the Arduino environment for several important timing functions:
delay(), millis() and micros(). If we use TC0, our own sketch - including any libraries that it uses - can no
longer call these important functions. So if we need a delay() routine, we must write our own. The
remaining hardware timer, TC2, can be used for this purpose. Consider the following code:
TCNT2 = 0; // TIMER2 SETUP: interrupts at 1000 Hz
TCCR2A = bit(WGM21); // CTC Mode; no external outputs
TCCR2B = bit(CS22); // prescalar /64
OCR2A = 125-1; // 8 Mhz/64/1000Hz = 125 (250 for 16MHz)
TIMSK2 = bit(OCIE2A); // enable timer compareA interrupt
The above lines will put TC2 into CTC mode and cause an interrupt to fire every millisecond. Our
corresponding interrupt routine will keep track of the number of milliseconds that have passed in a
global variable called ticks.
Here is a replacement routine for the Arduino delay() routine, called wait(). It just waits in an empty
while loop until the specified number of milliseconds have passed:
Neil Hecht is credited with the following method. I encourage you to watch coreWeaver’s YouTube
1
video, as he demonstrates the algebra of going from the resonant frequency equation (F = ) to
2𝜋√𝐿𝐶
equations that express the unknown value in terms of frequencies.
𝐹1 2
( ) −1
𝐹3
Cx = [ 𝐹1 2
]* Ccal
( ) −1
𝐹2
𝐹1 2 𝐹1 2 1 1 2
Lx = ⌊(𝐹3) − 1⌋ ∗ [(𝐹2) − 1] ∗ (𝐶 ) ∗ (2𝜋𝐹1)
𝑐𝑎𝑙
Where:
F1 = frequency with no calibration cap, no unknown
F2 = frequency with calibration cap in circuit, no unknown.
F3 = frequency with unknown in circuit, no calibration cap
The oscillator frequency is measured under three conditions. First, with an in-circuit inductor and
capacitor. This is called F1. Next, with a calibration capacitor added in parallel with the LC tank. This is
called F2. And finally, with the unknown component added to the LC tank. The resulting frequency is
called F3.
Unknown inductors are added in series with the in-circuit inductor. Unknown capacitors are added in
parallel to the in-circuit capacitor. The action of adding a series inductance or parallel capacitance is
controlled by a DPDT relay.
Note that Lx and Cx both depend on the calibration capacitor value. For accurate measurements, Ccal
should have a tolerance of 1% or less.
My meter consumes 40 mA of current in its idle state. Of this, 14 mA supplies the backlight, and 26 mA
supplies the PCB and display circuitry. Your results may vary, depending on the backlight module you
are using. When the DPDT relay is energized, which happens during calibration and capacitance
measurements, an additional 33 mA of current is required. When the SPST is energized during
calibration, an additional 11 mA is required.
In the name of science, I sacrificed a new Duracell 9V battery for a battery rundown test. The battery
powered the meter without difficulty for 6 hours. In the first hour of use, battery voltage dropped to 7-
8 volts, and remained there for most of the test. For this meter, a useful lower limit of battery voltage is
6.6 volts. Below that level, voltage dropped quickly, the regulator could not maintain 5V output, and the
meter stopped working. Without the backlight, battery runtime is increased from 6 hours to 10 hours.
We can check the battery voltage in software with the analogRead() function. Provide the function with
the pin number, and it will return the voltage on that pin expressed as a value from 0 to 1023, where 0 is
0 volts and 1023 is 5 volts. For example, for a voltage input on pin VBAT:
float getVoltage() { // get the battery voltage
int raw = analogRead(VBAT); // read voltage input as 0-1023
float voltage = raw*5.0/1024; // convert to a voltage, 0-5V
return voltage; // and return the result
}
The MCU and this function will only work for voltages less than or equal to 5 volts. If 9V from our
battery is applied to the MCU directly, it will ruin
the hardware. Notice that R5 and R6 are equal in
value and convert the 9V input “V_BAT” from the
battery into 4.5V “VBAT_ADC” which then
connects to the MCU.
Oscillator Stability and Warm-up
This meter requires 10-15 minutes warmup to achieve its most accurate and stable results.
Measurements taken immediately after startup are slightly (<1%) less than those obtained after warm-
up.
To measure the effects of warm-up, I started with a “cold” meter that had not been used for more than
an hour. I turned on the meter, calibrated, and measured an unknown component every 30 seconds,
recording the oscillator frequency and measured value. The data are shown below.
Capacitance Measurement:
oscillator frequency peaked Measured Capacitance (pF) vs. Time (s)
91 seconds after startup, and 1006
then exponentially decreased 1005
634 Hz over the next 15 1004
minutes to a resting
1003
frequency of 402390 Hz. The
1002
resulting capacitance
measurement changed from 1001
1026
61
121
181
242
303
364
426
486
546
606
666
726
786
846
906
966
1086
1148
1208
100% = +0.64%
Inductance Measurement:
the oscillator frequency Measured Inductance (pH) vs. Time (s)
peaked 100 seconds after 78250000
startup, and then decreased 78240000
94 Hz over then next 15 78230000
minutes to a resting
78220000
frequency of 399074 Hz. The
78210000
inductance value changed
from 78.16 uH to 78.24 uH 78200000
499
559
619
681
743
804
865
925
986
1047
1107