FB pixel

Making a Simple Capacitance Meter | Embedded C Programming - Part 24


Hi again! This tutorial will be devoted to measuring the capacitance using the charge-discharge method. In this method, a capacitor is fully charged and then discharged through a resistor with a known value. The discharge time is defined only by the known resistor and an unknown capacitor value (or vice versa if we want to measure the resistance), so calculating the unknown value will be quite simple.

In this tutorial, we will measure the capacitance and display it on the 1602-character LCD we discussed about which we talked in tutorial 18. Apart from the LCD, we will need just several resistors and the capacitor, which value we will measure.

As usual, let’s first get acquainted with the new MCU modules used in this device. There will be two of them: Analog comparator (as follows from the tutorial’s title) and Timer3, the last timer of PIC18F14K50 MCU that we haven’t considered yet. Let’s start with the latter, as it’s quite similar to Timer1 which we already know from Tutorial 14.

Timer3 Module

As I already said in the previous tutorials, there are four timers in the PIC18F14K50 MCU, which are all different. So let’s now consider the last one we haven’t used yet. As I mentioned, Timer3 is very similar to Timer1, except that Timer3 can’t be used as a clock source for the MCU. Other than that, it has the same features:

  • Software selectable operation as a 16-bit timer or counter.
  • Readable and writable 8-bit counting registers (TMR3H and TMR3L).
  • Selectable internal or external clock source or Timer1 (yes, Timer1, not a typo, as only Timer1 can be used to clock the whole MCU) oscillator internal option.
  • Interrupt-on-overflow.
  • Reset on CCP Special Event Trigger.

Timer3 is configured with the T3CON (Timer3 CONtrol) register, which also is similar to T1CON register (see Tutorial 14):

  • bit #7 - RD16 (16-bit Read/Write Mode enabled). Setting this bit to 1 allows reading/writing of the Timer3 register in one 16-bit operation; resetting it to 0 enables reading/writing of the Timer3 register in two 8-bit operations.
  • bit #6 - Unimplemented, read as 0.
  • bits #5-4 - T3CKPS1 and T3CKPS0 (Timer3 input Clock PreScaler)



Prescaler value













  • bit #3 - T3CCP1 (Timer3 and Timer1 to CCP1 Enable). If this bit is 1, then Timer3 is used as the clock source for compare/capture of ECCP1. If this bit is 0, then Timer1 is used as the clock source for compare/capture of ECCP1.
  • bit #2 - T3SYNC (Timer3 External Clock Input Synchronization Select). This bit makes sense only when bit #1 (TMR3CS) is set to 1. It allows one to synchronize the external clock with the main CPU clock if reset to 0 or not synchronize it when it is set to 1. When the TMR3CS bit is reset to 0, this bit is ignored.
  • bit #1 - TMR3CS (Timer3 Clock Source Select). When this bit is 1, the external clock applied to pin T13CKI or from Timer1 is used for clocking the Timer3. When this bit is 0, the internal source Fosc/4 is used as a Timer3 clock source.
  • bit #0 - TMR3ON (Timer3 On). When set to 1, this bit enables Timer3; reset to 0 disables Timer3.

As most of these bits are the same in Timer1, I will not discuss them in detail. You can read the brief information about them in Tutorial 14. If you want to know more about Timer3, though, please feel free to read chapter 13 of the PIC18F14K50 datasheet.

Analog Comparator Module

The analog comparator included in the PIC18F14K50 MCU is a regular comparator but integrated with the microcontroller. If you are unfamiliar with comparators, you can refer to this tutorial. Being brief, the comparator is a device with two inputs: the analog input itself and the reference input. If the input voltage is higher than the reference voltage, the comparator’s output becomes high; otherwise, it’s low.

PIC18F14K50 MCU has two comparators that are almost identical and share the same input and output pins. Because of this, you can’t use the same comparators at the same time with the same pins, though if one uses the external pins, the other can use internal sources. But if you connect them to the internal voltage sources, there is no problem. But let’s be consistent and consider first the main features of the comparator module:

  • Independent comparator control means that each of the two comparators has its own configuration register and can be configured individually.
  • Programmable input selection. The positive input of each comparator can be connected either to the dedicated pin C12IN+ (RC0) or to a fixed or programmable internal reference voltage. The negative input of each comparator can be connected either to the analog ground or one of three dedicated pins: C12IN1- (RC1), C12IN2- (RC2), or C12IN3- (RC3).
  • The comparator output is available internally and/or externally. There is a dedicated pin C12OUT (RC4) to which the output of one of the comparators can be connected and thus accessed externally. And in any case, the comparator output is available by the firmware as a dedicated bit of the comparator’s control register.
  • Programmable output polarity means that the comparator output can be high if the positive input is greater than the negative one (straight polarity) or low (inverted polarity).
  • Interrupt-on-change. Comparators can generate an interrupt when their output changes, so you don’t need to poll them permanently.
  • Wake up from sleep. Change inthe comparator output can also wake the MCU from sleep mode.
  • Programmable speed/power optimization. You can select one of two modes: High-speed or Low-speed. In the first mode, the power consumption is higher than in the second one, but the propagation delay is lower.

Comparators are configured with the identical registers CMxCON0, where x is the comparator number 1 or 2. The bits of these registers also have the same numbers, so in the later description, I will put ‘x’ in their names, meaning that it is 1 or 2. So, CMxCON0 registers have the following bits:

  • bit #7 - CxON (Comparator ON). If this bit is 1, the comparator is enabled, and if it is 0 then it is disabled.
  • bit #6 - CxOUT (Comparator OUT). This bit represents the output of the corresponding comparator. Its value depends on both input states and the CxPOL bit, which sets the output polarity. So, if CxPOL = 0 (straight polarity), then CxOUT is 0 when the positive input voltage is lower than the negative input voltage and is 1 otherwise. And if CxPOL = 1 (inverted polarity), then CxOUT is 0 when the positive input voltage is greater than the negative input voltage and is 1 otherwise.
  • bit #5 - CxOE (Comparator Output Enable). This bit connects the output of the comparator to the C12OUT pin. The behavior of this bit is a bit different for comparator 1 and comparator 2. It’s better to indicate it in the next table






Regular I/O










As you can see, comparator 2 has a priority over comparator 1. If the C2OE bit is 1 the C12OUT pin is connected to the comparator 2 output regardless of the value of the C1OE bit.

  • bit #4 - CxPOL (Comparator output POLarity). This bit (as I said when describing the CxOUT bit) sets the output polarity of the comparator. If it is 0, then the polarity is straight, and if it is 1, it is inverted.
  • bit #3 - CxSP (Comparator Speed/Power). When this bit is 0, the comparator operates in low-power and low-speed mode, and when it is 1, the comparator operates in normal-power and high-speed mode.
  • bit #2 - CxR (Comparator Reference). This bit defines if the positive input of the comparator will be connected to the external pin C12IN+ (when CxR = 0) or the internal reference voltage source (when CxR = 1).
  • bits #1 and #0 - CxCH1 and CxCH0 (Comparator CHannel). These bits define the source of the negative voltage of the comparator:



Negative voltage source






C12IN1- pin



C12IN2- pin



C12IN3- pin

That’s all about the basic configuration of the comparators. There is one note, though. If you want to use the C12OUT pin as the comparator output, manually configure it as an output by setting the corresponding bit of the TRIS register as 0.

Comparator 2 has an additional control register, CM2CON1, which nevertheless sets the advanced features of both comparators. Let’s also consider it in detail.

  • bit #7 - MC1OUT (Mirror Copy of comparator 1 OUTput). This bit has the same value as the C1OUT bit of the CM1CON0 register.
  • bit #6 - MC2OUT (Mirror Copy of comparator 2 OUTput). This bit has the same value as the C2OUT bit of the CM2CON0 register.

The main idea of using these two bits is that you can read both simultaneously from the same register and do not need to read the two registers.

  • bit #5 - C1RSEL (Comparator 1 Reference SELect). If this bit is 0, the fixed internal reference source FRV with a voltage of 1.024V is used as the comparator reference. If this bit is 1, the programmable reference source CCVREF is used as the comparator reference.
  • bit #4 - CR2SEL (Comparator 2 Reference SELect). The same as C1RSEL but for comparator 2.
  • bit #3 - C1HYS (Comparator 1 HYSteresis enable). If this bit is 1, the input hysteresis of 65mV is enabled at comparator 1. If this bit is 0 then the hysteresis is disabled.
  • bit #2 - C2HYS (Comparator 2 HYSteresis enable). The same as C1HYS but for comparator 2.
  • bit #1 - C1SYNC (C1 output SYNChronous mode). If this bit is 1 then the comparator 1 output is synchronous to the rising edge of the Timer1 clock. If this bit is 0, then the C1 output is asynchronous.
  • bit #0 - C2SYNC (C1 output SYNChronous mode). The same as C1SYNC but for comparator 2.

And this is everything I wanted to share about the comparator module. Again, for more information, please refer to chapter 11 of the PIC18F14K50 data sheet.

Timer3 and analog comparators interrupts handling

In the current program, we will not use these interrupts, but I decided to provide the information about them here so I don’t have to return to them later. I intentionally joined the interrupt handling of both these modules into the same chapter because the bits that control them are located in the same registers.

Bits that enable the interrupts are located in the register PIE2:

  • bit #6 - C1IE (Comparator 1 Interrupt Enable). When this bit is 1, comparator 1 interrupt is enabled, and when it is 0 - it is disabled.
  • bit #5 - C2IE (Comparator 2 Interrupt Enable). When this bit is 1, comparator 2 interrupt is enabled, and when it is 0 - it is disabled.
  • bit #1 - TMR3IE (Timer3 overflow Interrupt Enable). When this bit is 1, the Timer3 overflow interrupt is enabled, and when it is 0 - it is disabled.

Interrupt flags are located in the register PIR2:

  • bit #6 - C1IF (Comparator 1 Interrupt Flag). This bit is set to 1 when the comparator 1 output has changed (it must be cleared by software). If comparator 1 output has not changed, this bit remains 0.
  • bit #5 - C2IF (Comparator 2 Interrupt Flag). This bit is set to 1 when the comparator 2 output has changed (it must be cleared by software). If comparator 2 output has not changed, this bit remains 0.
  • bit #1 - TMR3IF (Timer3 overflow Interrupt Flag). This bit is set to 1 when the Timer3 counting register has overflowed (it must be cleared by software). If the Timer3 counting register has not overflowed, this bit remains 0.

And finally, the interrupt priorities are located in the register IPR2:

  • bit #6 - C1IP (Comparator 1 Interrupt Priority). If this bit is 1, comparator 1 interrupt is high priority, and otherwise it’s low priority.
  • bit #5 - C2IP (Comparator 2 Interrupt Priority). If this bit is ,1 then comparator 2 interrupt is high priority; otherwise, it’s low priority.
  • bit #1 - TMR3IP (Timer3 overflow Interrupt Priority). If this bit is 1 then the Timer3 overflow interrupt is high priority; otherwise, it’s low priority.

And that is all (and more!) that we need to know about the new modules that we will use in the current project. Now, consider the schematic diagram and see how the capacitance is measured.

Schematics diagram and theoretical background

The schematics diagram is shown in Fig. 1.

Figure 1 - Schematics diagram with the PIC18F14K50 with 1602 character LCD and the capacitance measurement circuit
Figure 1 - Schematics diagram with the PIC18F14K50 with 1602 character LCD and the capacitance measurement circuit

This schematic diagram is quite similar to the one presented in the first part of tutorial 20: it also consists of the PIC18F14K50 MCU (DD1) and 1602-character LCD (X2). The only difference in the LCD connection is that the RS pin of the LCD is connected to pin RC3 instead of RC4 because the latter will be used for another purpose.

The resistors R3 and R4 form a voltage divider, which is supplied from the same voltage as the MCU.

The voltage from the divider goes to pin RC1, which also is the negative voltage input of the analog comparator C12IN1-. The values of R3 and R4 are not random and were selected with a certain purpose, which I will discuss later. And now, let’s just calculate the reference voltage from this divider.

For now, we don’t need the real value in volts; this expression is quite enough.

Now, let’s consider the most important thing - the measurement circuit, which consists of the known resistor R5 of 10 kOhm and unknown capacitor Cx. This circuit is supplied from pin RA4 (actually, any pin can be used for this purpose; I just selected this one because I noticed it first). In this case, it’s used as a regular digital output, which can be either high or low. The voltage from the capacitor Cx goes to pin RC0, which is merged with the positive input of the comparator C12IN+.

Also, pay attention that pins RC4 and RC5 are tied together. It is very important not to forget this link. Pin RC4 is merged with the output of the comparator C12OUT, and pin RC5 is merged with the capture input CCP1 of the ECCP module.

The algorithm of the operation is the following:

  1. Pin RA4 goes high, and the capacitor Cx is charged through resistor R5 for at least 1 second to the VCC voltage.
  2. After charging the capacitor, the pin RA4 goes low, and simultaneously, Timer3 is reset to start the counting from 0.
  3. The capacitor discharges through the resistor R5, and its voltage drops by the known law where is the voltage on the capacitor Cx, and t is the time.
  4. When the voltage on the capacitor Cx reaches the negative voltage from the R3-R4 divider, the comparator’s output becomes low. At this moment, the capture event of the ECCP module is happening because the capture input CCP1 is connected with the comparator output C12OUT, and the Timer3 counting register is copied into the ECCP register.
  5. The time t from the previous equation is already measured by Timer3 and captured by the ECCP module. The value at the capture time is also known and is and R5 value is known as well, so we can now calculate the capacitance:

    Let’s shorten both parts by VCC and take the natural logarithm:

    Now, the value of 2.7 should be understood. It’s very close to the e, which is 2.7172. In this case, we can consider that and thus:

    And finally:

As you can see, the final equation is very simple and doesn’t depend on the supply voltage, only on the resistance R5 and the discharge time t.

That’s actually everything about the device schematics diagram so we can proceed to the program code consideration.

Program Code Description

Let’s create a new project now. I’ve called it “Capacitance_meter,” but you can give it any name you want. Then create the new “main.c” file in it, as we are used to doing.

The same as in tutorial 20, we will now and next use the separate “config.h” file in which the configuration bits are defined. I will not provide its text here; it’s the same as in tutorial 20, so you can just copy it there and paste it into the current project. Also, you will need to copy and paste the “lcd_1602.h” and “lcd_1602.c” files from that tutorial (fig. 2-5).

Figure 2 - Copying the header files from the “Control_system” project
Figure 2 - Copying the header files from the “Control_system” project
Figure 3 - Pasting the header files into the “Capacitance_meter” project
Figure 3 - Pasting the header files into the “Capacitance_meter” project
Figure 4 - Copying the  “lcd_1602.c” file from the “Control_system” project
Figure 4 - Copying the “lcd_1602.c” file from the “Control_system” project
Figure 5 - Pasting the  “lcd_1602.c” file into the “Capacitance_meter” project
Figure 5 - Pasting the “lcd_1602.c” file into the “Capacitance_meter” project

After all these manipulations your project should look like in fig. 6

Figure 6 - Project structure with all required files
Figure 6 - Project structure with all required files

Before we start with the main file, let’s first make changes in the “lcd_1602.h” file. As I said in tutorial 20, now that this file is more universal, you only slightly change it when you change the display connection. According to fig. 1, we have moved the RS pin from RC4 to RC3. Let’s reflect these changes in the “lcd_1602.h” file.

#include <xc.h> // include processor files - each processor file is guarded.

#define TRIS_Ebits TRISCbits //TRIS register of the E pin

#define TRIS_E_pin TRISC6 //E pin of the TRIS register

#define LAT_Ebits LATCbits //LAT register of the E pin

#define LAT_E_pin LATC6 //E pin of the LAT register

#define TRIS_RSbits TRISCbits //TRIS register of the RS pin

#define TRIS_RS_pin TRISC3 //RS pin of the TRIS register

#define LAT_RSbits LATCbits //LAT register of the RS pin

#define LAT_RS_pin LATC3 //RS pin of the LAT register

As you can see we only need to change the TRIS_RS_pin from TRISC4 to TRISC3 (line 9) and LAT_RS_pin from LATC4 to LATC3 (line 11), and everything will work fine.

Now let’s consider the “main.c” file in which the most interesting things happen

#include <xc.h>

#include <stdio.h>

#include "config.h"

#include "lcd_1602.h"

volatile uint8_t measurement_done; //Flag that indicates that the time measurement is complete

volatile uint16_t value; //Time value received from the ECCP module

void putch(char data) //Function to put the character to LCD (needed by printf)


lcd_data(data); //Send character to LCD


void __interrupt() InterruptManager (void) //Interrupt vector


if(INTCONbits.PEIE == 1) //If peripheral interrupts are enabled


if(PIE1bits.CCP1IE == 1 && PIR1bits.CCP1IF == 1) //It ECCP1 interrupt is enabled and ECCP1 interrupt flag is set


PIR1bits.CCP1IF = 0; // Clear the ECCP1 interrupt flag

value = CCPR1H * 256 + CCPR1L; //Save the discharge time

measurement_done = 1; //Indicate that the timer capture event has happened




void main(void)


//GPIO configure

TRISAbits.RA4 = 0; //Configure RA4 (Charge control pin) as output

TRISCbits.RC4 = 0; //Configure RC4 (Comparator output) as output

//Oscillator module configuration

OSCCONbits.IRCF = 6; //Set CPU frequency as 8 MHz

OSCTUNEbits.SPLLEN = 1; //Enable PLL

//Timer 3 configuration

T3CONbits.RD16 = 1; //Enable read/write in one 16-bit operation

T3CONbits.T3CKPS = 3; //Prescaler 1:8

T3CONbits.T3CCP1 = 1; //Timer3 is the clock source of the ECCP module

T3CONbits.TMR3CS = 0; //Internal clock FOSC/4

T3CONbits.TMR3ON = 1; //Timer3 is enabled

//ECCP module configuration

CCP1CONbits.CCP1M = 4; //Capture mode, every falling edge

PIR1bits.CCP1IF = 0; //Clear the ECCP interrupt flag

PIE1bits.CCP1IE = 1; //Enable the ECCP interrupts

//Comparator C1 configuration

CM1CON0bits.C1ON = 1; //Enable comparator C1

CM1CON0bits.C1OE = 1; //C1OUT is present on the C12OUT pin

CM1CON0bits.C1POL = 0; //C1OUT logic is not inverted

CM1CON0bits.C1SP = 1; //High-speed mode

CM1CON0bits.C1R = 0; //C1Vin+ connects to C12IN+ pin

CM1CON0bits.C1CH = 1; //C1Vin- connects to C12IN1- pin

CM2CON1 = 0; //No hysteresis, no C1 output synchronization with Timer1

//Interrupts configuration

INTCONbits.GIE = 1; //Enable global interrupts

INTCONbits.PEIE = 1; //Enable peripheral interrupts

lcd_init(0, 0); //Initialize the LCD without cursor and blinking

while (1)


LATAbits.LATA4 = 1; //Set the RA4 pin high to charge the capacitor

__delay_ms(1000); //Wait for 1 second while the capacitor is being charged

measurement_done = 0;//Reset the measurement_done flag

LATAbits.LATA4 = 0; //Set the RA4 pin low to start the capacitor discharge

TMR3H = 0; //Reset Timer3

TMR3L = 0;

while (!measurement_done); //Wait while the capture event happens

lcd_clear(); //Clear the LCD

printf ("C=%u.%unF", (value / 10), (value % 10));//Display the capacitance value



In line 1, we, as usual, include the “xc.h” file to use the PIC MCU-related variables, functions, and macros. In line 2, we include the “stdio.h” file, which should be familiar to those who wrote the code in the regular C for command-line desktop applications. In this file, there are declarations of the functions of the standard input-output. Why do we need it here, in the MCU application? Besides the new MCU modules I showed you how to use the printf function to display the information on the 1602 LCD (and any other output device).

In line 3, we include the “config.h” file, in which now the configuration bits are defined.

And finally, in line 4, we include the “lcd1602.h” file, which consists of the 1602 LCD-related functions.

In lines 6 and 7, we declare two global variables: measurement_done is the flag that indicates that the capacitor has been discharged to the level defined by the negative reference voltage set by the R3-R4 divider, and the comparator’s output switched from high to low, which, in its turn causes the Timer3 capture interrupt. The value variable is the captured time of the capacitor discharge in 1 us unit (I will explain later how this happens).

In lines 9-12, we define the putch function. This is the special function the printf function uses to send the data anywhere. In this exact function, you determine where the output character stream will be directed. The putch function has a single parameter - data of the char type. The application of it is to send this character somewhere. In our case, we send it to the 1602 LCD using the lcd_dat function, which has been described in detail in Tutorial 18 (line 11). And that’s it! When you invoke the printf function, it will display the formed string in the LCD. For sure, we already have the functions lcd_write_string and lcd_write_number, which allow us to do the same, and actually, they require less Flash and RAM, but printf is quite convenient and familiar. So it’s up to you what to use - it’s the usual choice between convenience and simplicity on the one hand and the memory economy and productivity on the other hand.

In lines 14-25, there is the InterruptManager function, which we met several times in the previous tutorials. This time, we will use the ECCP capture event interrup,t the same as in Tutorial 14. In line 16, we check if the peripheral interrupts are enabled because the ECCP interrupt, as I mentioned before, belongs to the peripherals. In line 18, we check if the ECCP interrupt is enabled (PIE1bits.CCP1IE == 1) and if the ECCP interrupt flag is set (PIR1bits.CCP1IF == 1). In this case, we consider that the interrupt has occurred. After that, we clear the interrupt flag (line 20) because, as I said before, we must clear it with the firmware.

When the capture event happens, the value of the Timer3 register (in the current case) is automatically copied to the CCPR1 register and can now be saved into some variable. So, in line 21, we copy the CCPR1 value into the value variable. To get the 16-bit value of the CCPR1 register, we need to multiply the CCPR1H value by 256 (which shifts the value 8 bits to the left) and add the CCPR1L value to the result.

Finally, we set the measurement_done flag as 1 to indicate that we have captured the capacitor discharge time and saved it into the value variable.

That’s all we do inside the interrupt subroutine function, sticking to the principle of the shortest possible code.

In lines 27-76, there is the main function of the program. As you can see, it’s quite short, and the majority of it is occupied by the initialization part (lines 29-62). Let’s consider it in detail.

In lines 30-31, we configure RA4 and RC4 pins as outputs. RA4 pin charges and discharges the capacitor according to the algorithm described above. RC4 pin is merged with the C12OUT pin, which is the output of the comparator, and, as I said earlier, we need to take care of configuring it as output in the program code.

In lines 34-35, we set the CPU frequency as 32 MHz.

In lines 38-42, we configure Timer3:

  • enable 16-bit read/write operation (line 38)
  • set the timer prescaler as 1:8 (line 39)
  • assign the ECCP module to Timer3 (line 40)
  • set the Timer3 clock source as Fosc/4 (line 41)
  • Finally enable the Timer3 (line 67).

When we set the Timer3 clock source as Fosc/4, the frequency of this source is 32 / 4 = 8 MHz. After the prescaler, the input timer frequency is 8 x 1:8 = 1 MHz. So, each tick of the timer corresponds to 1 / 1MHz = 1us. That’s why I said the value variable contains the capacitor discharge time in 1us units.

In lines 45-47, we configure the ECCP module:

  • set the capture mode, every falling edge (line 45, see Table 1 of Tutorial 14 for more information about the CCP1CON register)
  • clear the interrupt flag (line 46)
  • the ECCP interrupt (line 47).

In line 59, we enable all unmasked interrupts;in line 60 we enable the peripheral interrupts.

Finally, in line 62, we initialize the 1602 LCD without the cursor and blinking.

That’s all about the initialization. As you can see, you already met either the same code or very similar code (like with Timer3) in previous tutorials.

Now, let’s consider the main loop of the program. It’s quite short and occupies lines 64-75. In line 66, we set the RA4 pin high. After that, the capacitor Cx starts charging through the resistor R5 (see Fig. 1). This process takes some time, so we need to wait while it completely charges. Thus, in line 67, we implement the delay of 1 second. Actually, the charging time is less than half of a second and depends on the capacitor and resistor R5 values, so you can probably adjust this value.

In lines 68-71, we prepare everything for the discharge time measurement:

  • reset the measurement_done flag (line 68)
  • set the RA4 pin low to start the discharge (line 69)
  • simultaneously reset the value of the Timer3 counting register (lines 70, 71).

The same as with Timer1, Timer3’s 16-bit counting register is split into two 8-bit registers: TMR3H - for higher 8 bits and TMR3L - for lower 8 bits.

Now, the only thing to do is wait while the capacitor’s voltage becomes 1/2.7 of the initial value. When this happens, the comparator’s output goes low, which causes the ECCP capture event (as we configured it to be triggered by the falling edge in line 45). This event will trigger the capture interrupt, and inside its handler (lines 14-25), we get the discharge time and set the flag measurement_done. So, in line 72, we just wait while the measurement_done flag becomes 1.

Before proceeding further, we need to figure out how to convert the discharge time into the capacitance. We already have the formula for it:

But let’s now set the real values

So, to get the capacitance in uF, we need to divide the value by 10,000, which doesn’t seem reasonable. It would be better to calculate the capacitance in nF, then the formula will be the following:

Now it looks good. Let’s see the capacitance boundaries we can obtain with the current setup. The value is a 16-bit variable, so it changes from 0 to 65535, so the least possible capacitance is Cmin = 1/10 = 0.1 nF, and the greatest possible capacitance is Cmax = 65535/10 = 6553.5 nF = 6.55 uF. If you want to change these boundaries, you can use another value of the resistor R5.

Now, let’s return to the program consideration. We stopped at line 72, where we waited for the capacitor discharge. Now we have the value value (sorry for the tautology; I should probably name things more carefully) and can convert it into the capacitance and display it on the LCD.

In line 73, we clear the LCD and set the cursor on the first position of the first row. Then, we call the printf function in line 74. For more information about its use in the PIC MCUs please refer to this guide. As you can see there, it almost doesn’t differ from the regular printf function for desktop applications and even supports the floating point values (which is not supported in a lot of MCU’s implementations of this function). But as I already mentioned in some of the tutorials, the floating point operations drastically increase the spending of the Flash and RAM of the MCU, so whenever possible, they should be avoided. In our case, it is possible, by the way, and I will show how.

So, we call the printf function as

printf ("C=%u.%unF", (value / 10), (value % 10))

So first, it will display the text “C=” then the unsigned number ( %u), then the decimal point as plain text, then another unsigned number, and finally the text “nF.”As the first shown number, we display the value divided by 10; as the second number, we display the modulo of dividing the value by 10. You can calculate by yourself that this is exactly what we need.

And that’s it! Now let’s assemble the circuit according to Fig. 1, connect the debugger to the USB port of the PC and compile and download the program code.

Testing of the Capacitance Meter

First, I connected the ceramic capacitor of 1 uF as the Cx, and the result of the measurement was the following (fig. 7).

Figure 7 - Result of the measurement of the 1uF ceramic capacitor
Figure 7 - Result of the measurement of the 1uF ceramic capacitor

If the tolerance is ±20% then this result is fine. Actually I don’t know what is the real tolerance as I bought these capacitors several years ago and they could simply degrade.

Then I connected several other capacitors, and the result of their measurement is gathered into table 1.

Table 1 - Results of the capacitance measurements


Figure 8 - Oscillogram of the capacitor discharging curve
Figure 8 - Oscillogram of the capacitor discharging curve

Here is the measurement of the capacitor with a nominal 1.5 uF. As you can see, initially, the capacitor is charged to 3.04V (value Vpp in the legend). The moment marked with the first vertical white line starts discharging and discharges till the value of 3.04/2.7 = 1.13 V. This moment is marked with the second vertical white line. The time between these two events is 14.5 ms (𝞓T in the legend). If we divide this time by 10 kOhm, we will get 1.45 uF, corresponding to the value from Table 1.

And that’s all about this tutorial. As you can see, the capacitance metering method is not difficult and doesn’t require any active external components, just three resistors. We have learned two new modules - Timer3 and Comparator. Also, we have discovered how to use the printf function to show the information on the LCD.

As for homework, I suggest you do the opposite - using a known capacitor (you can select it using the current circuit) and measure an unknown resistance with the exact schematics. The resistance is taken from the same equation: R5Cx=t, where now Cx is known, and R5 is unknown.

Make Bread with our CircuitBread Toaster!

Get the latest tools and tutorials, fresh from the toaster.

What are you looking for?