Capacitance meter with Microchip's PIC 16F628A and DEM 16216-SGH
Some time ago I found a nice blog about creating a capacitance meter with the PIC16F628A by

Roman Black . I liked the simplicity, but unfortunately I did not have the 16 Mhz crystal and only the hex file was available for download. I couldn't see whethere that would be a problem or not. Therefore I decided to build my own capacitance meter based on the same idea: use a known RC-network (270pF 5% capacitor and 10k 1% resistor) as a reference for measuring capacitance. At start up, the reference value for dT is measured, consecutive measurements are calculated by comparing dT measurement with dT reference.

capacitancemeter schema
However there are some design differences with Roman's approach:

The first capture is skipped because it is inaccurate. 256 consecutive measurements are taken to calculate an accurate average (instead of waiting until more than 0.5 second passed) 20 Mhz crystal is used instead of 16 Mhz. Therefore timer1's frequency is 5 Mhz (timer1 is configured as fosc /4) Bigger capacitors only have between 1 and 256 captures
In a nutshell:

At start up, measure dT of reference capacitor dTref Compare the measured amount of time dTref with the expected amount of time Texp. Texp can be calculated by: 2 x R x C x ln(Vth/Vtl) where R=10000 ohm, C=270pF, Vth = 3.33V and Vtl = 1.67V => 0.0000037429947750 seconds. 256 measurements at clock speed 5Mhz, every 16th rising edge, no prescalar makes 76656.5 ticks. Measure reference value of capacitor Cref. Because I use a 0.1% 10000oh resistor, and a 1% 270pF the difference between measured and expected, mainly is caused by the capacitor. The real value of Cref = 270pF x dTmeasured / dTexpected Consecutive measurements first need to determine the range for Cx, this is performed my detecting timer 1 overflows. Measuring starts with settings pF. When timer 1 overflow, the settings for nF When timer 1 overflows again, settings for uF are used. When this still results in timer 1 overflows, the program starts capturing in an alternative mode: each timer 1 overflow results in adding 65536 to the measured value. When a capture event occurs, then the value of timer 1 is added too. If timer 1 has more then 256 overflows when the capture event occurs, the measurings stops and the capacitance value is calculated. In the first 3 modes, 256 consecutive measurements are taken, in the latter between 1 and 256 measurements are taken.
The table below shows the settings for each mode:

Range Nth rising edge Prescalar Timer1 overflows Number of measurements 1 (pF) 16 th 1 No 256 2 (nF) 4 th 2 No 256 3 (uF) 1 st 4 No 256
4 (uF2) 1 st 8 Yes 1...256

In order to calculate Cx, the following formula is applied: Cx = Cref * ((Mx * (16 / Nth rising edge) * prescalar - Mref) / Mref
Example measurements for all 4 modes. The reference capacitor Cref = 270 pF (5%), the reference measurement Mref = 78306
Which means that the Cref is about 275.8 pF (that is within the tolerance)
All measurements resulted in value within tolerance of capacitor. Only values below 10pF are tricky (the measeringwires add some capacitance too. This can be more than 2pF (!), the shorter the wires, the better the result. N.B. keep your fingers/hands away while measuring small caps in order to decrease the measurement error.

Cin (pF) Range Mx Nmeas. Nth rising edge Prescalar Exact Percentage(1) Ccalc (pF) Error Percentage(2) Cdisp Unit 22 pF 84811 256 16 1 22.91194151 4.1 22.911 0.00411 4.1 22.911 pF 47 pF 92889 256 16 1 51.36431099 9.3 51.362 0.00450 9.3 51.362 pF 220 pF 145077 256 16 1 235.1811293 6.9 234.43 0.31938 6.6 234.430 pF 470 pF 212826 256 16 1 473.8069749 0.8 471.618 0.46200 0.3 471.618 pF 2200 pF 677080 256 16 1 2109.004591 -4.1 2107.112 0.08974 -4.2 2107.112 pF 4700 pF 1475776 256 16 1 4922.175388 4.7 4920.272 0.03867 4.7 4920.272 pF 22000 pF 5883013 256 16 1 20445.36622 -7.1 20442.296 0.01502 -7.1 20442.296 pF 47000 pF 13157091 256 16 1 46066.15786 -2.0 46064.116 0.00443 -2.0 46064.116 pF 100000 nF 3570992 256 4 2 100346.3084 0.3 100115.4 0.23011 0.1 100.1154 nF 220000 nF 7282642 256 4 2 204931.8367 -6.8 204919.4 0.00607 -6.9 204.9194 nF 1000000 uF 4397551 256 1 4 991024.9746 -0.9 990949.4 0.00763 -0.9 0.9909494 uF 10000000 uF2 20724615 256 1 8 9343255.217 -6.6 9294460 0.52225 -7.1 9.29446 uF 220000000 uF2 17717125 9 1 8 227203354.2 3.3 227121300 0.03611 3.2 227.12130 uF 2200000000 uF2 23371351 1 1 8 2697418299 22.6 2697241260 0.00656 22.6 2697.24126 uF

Cin = indicated value of the capacitor being measured. Range see table above Mx = the cumulative timer1 ticks. Nmeas = the number of measurements taken. Nth rising edge, see table above Prescalar, see table above 'Exact' = the value calculated by spreadsheet (no integer roundoffs). Percentage(1) = the difference between indicated value and measured value. Ccalc is the calculated value with int32 calculations Error = 100 * (Ccalc - Exact) /Exact Percentage(2) = the difference between indicated value and int32 calculated value. Cdisp = the dispayed value Unit = the displayed unit
The outcome is pretty good. Most capacitors have a tolerance of more than 10% (!) and as you can see the calculated values all within 10% except for the 2200 uF capacitor.

You can download both the ASM file and the HEX file (last update: 2013-06-09):

16f628a_cap_v5.asm
16f628a_cap_v5.hex (rename 'txt' to 'hex')
Below you can see a high-level program overview.

program overview
In the config section the oscillator mode is set to High Speed, no watch dog timer is used. The interrupt vector is set to memory locaton 'MY_INTERRUPT_HANDLER'.

__CONFIG(_HS_OSC & _WDT_OFF & _MCLRE_OFF & _BOREN_OFF & _LVP_OFF & _CP_OFF);
org 0x0000
goto MAIN_INIT
org 0x0004
goto MY_INTERRUPT_HANDLER

The section flag bits defines bitnumber values for specific flags. I hope the comment behind the names explains their purpose. For example the byte value MY_FLAGS has 8 bits, 5 bits are used. When bit 5 (SKC) is set, this indicates that the first capture must be skipped. Bit 4 (CAP) indicates whether caputure mode is on. Etc.

;--- MY_FLAGS ---
#define SKC 5 ;Skip capture bit
#define CAP 4 ;Capture mode on = 1, off = 0
#define U4B 3 ;Write upper four bits (high nibble)
#define SKF 2 ;Skip frame bit
#define WRT 1 ;Ready for writing bit
#define LCDE 0 ;LCD Enable bit
;--- MY_MEASUREMENT ---
#define AL_T1OF 4 ;allow timer 1 overflow
#define DISA 3 ;display all digits
#define FSTM 2 ;First measurement
#define MR_0 1 ;MR[1:0] measurement range, 00 = pF, 01 = nF, 10 = uF, 11 = uF2 accepts timer1 overflows
#define MR_1 0 ;First digit
;-- MY_ERRORS bits --
#define OVERFLOW 0 ;Math overflow
#define UNDERFLOW 1 ;Math negative value (underflow)
#define DIV0 2 ;Division by zero
#define BIGCAP 3 ;Capacitance overflow

The section binary constants is used for defining constants that look like a bit-representation of a hex value. For example 'b10101010' makes more sense to me when you are interessed in which bits of port A are set then the hex value 0xAA

#define b00000000 0x00
#define b00000001 0x01
#define b00000011 0x03
...
#define b11110101 0xF5
#define b11111111 0xFF

The constants section defines other constants. In this case only one. TMR_DELTA. This value is used to initialize timer 0. Timer 0 starts at 0xF0 and counts from 0xF0...0xFF, overflows and is reset to 0xF0

#define TMR_DELTA 0xF0 ;start at: 255 - x, overflow on 255+1

The section 'bank 0, general purpose registers' defines the variables I use. They're al located in 'bank 0', hence the name. For all calculations I use 32-bits intergers (MATH_A, MATH_B, MATH_RES, MATH_TMPA, MATH_TMPB). Writing mathmetical operations like 'a divide by b', 'a times b' in assembly is a 'nice exercise'. Because the integers are actually just bytes that have to be addressed per byte. So variable MATH_A exists of 4 bytes: MATH_A3...MATH_A0. So I had to take care of checking the 'C', 'Z' flags in calculations and detect overflows, underflows, division by zero, etc. Using the simulation option in MPLAB-X was quite a relief for me in detecting errors in my algorithm. Below is a list of the variables and their purpose.

;RAM data variables bank 0
cblock 0x20 ;bank 0x20, 80 bytes general purpose register
MATH_RES3 ;MATH_R3...0 32 bits unsigned int 'RESULT'
MATH_RES2
MATH_RES1
MATH_RES0
MATH_TMPA3 ;MATH_TMPA3...0 32 bits unsigned int 'TMPA'
MATH_TMPA2
MATH_TMPA1
MATH_TMPA0
MATH_TMPB3 ;MATH_TMPB3...0 32 bits unsigned int 'TMPB'
MATH_TMPB2
MATH_TMPB1
MATH_TMPB0
MATH_A3 ;MATH_A3...0 32 bits unsigned int 'A'
MATH_A2
MATH_A1
MATH_A0
MATH_B3 ;MATH_B3...0 32 bits unsigned int 'B'
MATH_B2
MATH_B1
MATH_B0
MATH_C3 ;MATH_C3...0 32 bits unsigned int 'C'
MATH_C2
MATH_C1
MATH_C0
MATH_D3 ;MATH_D3...0 32 bits unsigned int 'D'
MATH_D2
MATH_D1
MATH_D0
MATH_REM3 ;MATH_REM3...0 32 bits unsigned int 'MATH_REM'
MATH_REM2
MATH_REM1
MATH_REM0
Mx_Cap3 ;Mx_Cap3...0 32 bits unsigned int 'Mx_Cap'
Mx_Cap2
Mx_Cap1
Mx_Cap0
Mx_REF3 ;Mx_REF3...0 32 bits unsigned int 'Mx_REF'
Mx_REF2
Mx_REF1
Mx_REF0
C_REF3 ;C_REF3...0 32 bits unsigned int 'C_REF'
C_REF2
C_REF1
C_REF0
LCD_DIV3 ;LCD_DIV3..0 32 bits unsigned int 'LCD_DIV'
LCD_DIV2
LCD_DIV1
LCD_DIV0
W_TEMP
STATUS_TEMP
MY_ERRORS
MY_FLAGS
MY_MEASUREMENT
LCD_BUF_00
LCD_BUF_01
LCD_BUF_02
LCD_BUF_03
LCD_BUF_04
LCD_BUF_05
LCD_BUF_06
LCD_BUF_07
CNT_VAR
DLY_VAR
CNT0
CNT1
CNT2
CNT3
TMR1IF_C1 ;TMR1IF_C1...0 16 bits unsigend int: number of timer1 time-outs
TMR1IF_C0
CNT_RW ;number of items in LCD_BUF
LCD_TMP ;buffer for tmp lcd data
CAP_NR1 ;CAP_NR1...0 16 bit capture number
CAP_NR0
MATH_BS ;number of bits shifted
LCD_C0 ;LCD_C0...LCD_C5 6 lcd characters
LCD_C1
LCD_C2
LCD_C3
LCD_C4
LCD_C5
endc

The main init section is responsible for initializing the PIC and the LCD. But I already discussed this in my previous blog

PIC16F628A LCD driver (assembly)
In the capture init, the capture and compare module is initialized depending on the capture mode (pF, nF, uF, uF2). See also code comments.

init_capture_compare_module:
BSF MY_FLAGS, CAP ;set capture mode flag
BSF MY_FLAGS, SKC ;set skip first capture flag
BCF MY_ERRORS, OVERFLOW ;clear overflow flag
BCF MY_ERRORS, BIGCAP ;clear BIGCAP flag
;--- select bank 1 ---
BSF STATUS, RP0
BCF INTCON, GIE ;disable all interrupt
BCF INTCON, T0IE ;disable Timer 0 overflow interrupt
CLRF PIE1
MOVLW b00000101 ;RA0 and RA2 as analog inputs
MOVWF TRISA
;--- select bank 0 ---
BCF STATUS, RP0
MOVLW b00000000
MOVWF T1CON ; - - T1CKPS1 T1CKPS0 T1OSCEN T1SYNC TMR1CS TMR1ON
;prescale 1:1, T1SYNC ignored, clock source: Internal clock (FOSC/4), tmr1 off
MOVWF TMR1H
MOVWF TMR1L
MOVLW b00000110
MOVWF CMCON ;C2OUT=x, C1OUT=x, C2INV = 1, C1INV = 1, CIS = 0, CM<2:0> = 100
;C2OUT: Comparator 2 Output bit
;C1OUT: Comparator 1 Output bit
;C2INV: Comparator 2 Output Inversion bit
;C1INV: Comparator 1 Output Inversion bit
;CIS
;Two Independent Comparators CM<2:0> = 100
;Two Common Reference Comparators CM<2:0> = 011
;Two Common Reference Comparators with Outputs CM<2:0> = 110
init_capture_compare_pF:
MOVLW b00000111 ;Capture mode, every 16th rising edge
MOVWF CCP1CON ;CCP1M<3:0>: CCPx Mode Select bits
BTFSS MY_MEASUREMENT, MR_1 ; uF or F?
GOTO init_capture_compare_pF_nF
init_capture_compare_uF_uF2:
MOVLW b00000101 ;Capture mode, every rising edge (factor 16)
MOVWF CCP1CON ;CCP1M<3:0>: CCPx Mode Select bits
BTFSS MY_MEASUREMENT, MR_0 ; F?
GOTO init_capture_compare_uF
init_capture_compare_uF2:
MOVLW b00110000 ;prescale 1:8 (factor 8)
MOVWF T1CON
GOTO init_capture_compare_done
init_capture_compare_uF:
MOVLW b00100000 ;prescale 1:4 (factor 4)
MOVWF T1CON
GOTO init_capture_compare_done
init_capture_compare_pF_nF:
BTFSS MY_MEASUREMENT, MR_0 ; nF?
GOTO init_capture_compare_done
init_capture_compare_nF:
MOVLW b00000110 ;Capture mode, every 4th rising edge (factor 4)
MOVWF CCP1CON ;CCP1M<3:0>: CCPx Mode Select bits
MOVLW b00010000 ;prescale 1:2 (factor 2)
MOVWF T1CON
init_capture_compare_done:
;0000 = Capture/Compare/PWM off (resets CCP1 module)
;0100 = Capture mode, every falling edge
;0101 = Capture mode, every rising edge
;0110 = Capture mode, every 4th rising edge
;0111 = Capture mode, every 16th rising edge
;1000 = Compare mode, set output on match (CCP1IF bit is set)
;1001 = Compare mode, clear output on match (CCP1IF bit is set)
;1010 = Compare mode, generate software interrupt on match (CCP1IF bit is set, CCP1 pin is unaffected)
;1011 = Compare mode, trigger special event (CCP1IF bit is set; CCP1 resets TMR1
;11xx = PWM mode
;--- select bank 1 ---
BSF STATUS, RP0
MOVLW b00000100 ;RB0...RB1, RB3...RB7 as outputs, RB2 as input
MOVWF TRISB ;TRISB, bank 1
BCF PIE1, CMIE ;bank 1, disable comparator interrupts
BSF PIE1, CCP1IE ;bank 1, enable CCP1 Interrupt Enable
BSF PIE1, TMR1IE ;bank 1, enable timer 1 interrupts
;--- select bank 0 ---
BCF STATUS, RP0
BSF T1CON, TMR1ON ;start timer 1
BSF INTCON, PEIE ;enable periperal interrupts
BSF INTCON, GIE ;enable interrupts
RETURN

In the capture interrupt routine is responsible for:

skipping the first measurement taking n timer1 measurements optionally act on the number of timer1 overflows if timer1 overflow are allowed.
capture_interrupt:
;--- BANK 0 ---
BCF PIR1, CCP1IF ;bank 0, reset interrupt CCP1IF
btfss MY_FLAGS, CAP ;if not captureing, stop adding captured T1
GOTO capture_stop
BCF STATUS, RP0
BCF STATUS, RP1
BTFSS MY_FLAGS, SKC ;skip first capture
GOTO capture_ccpr1
MOVLW 0x00
MOVWF CAP_NR1 ;bank 0, Capturenummer
MOVWF CAP_NR0 ;bank 0, Capturenummer
BCF MY_FLAGS, SKC ;stop skipping captures
GOTO capture_end
capture_ccpr1:
MOVF Mx_Cap3, 0 ;copy 'Mx_Cap' to 'MATH_A'
MOVWF MATH_A3
MOVF Mx_Cap2, 0;
MOVWF MATH_A2
MOVF Mx_Cap1, 0;
MOVWF MATH_A1
MOVF Mx_Cap0, 0;
MOVWF MATH_A0
MOVLW 0x00 ;copy '0x00 0x00 CCPR1H CCPR1L' to 'MATH_B'
MOVWF MATH_B3
MOVWF MATH_B2
MOVF CCPR1H, 0 ;bank 0, CCPR1H
MOVWF MATH_B1
MOVF CCPR1L, 0 ;bank 0, CCPR1L
MOVWF MATH_B0
CALL calc_a_plus_b
MOVF MATH_RES3, 0 ;copy 'RES' to 'Mx_Cap'
MOVWF Mx_Cap3
MOVF MATH_RES2, 0
MOVWF Mx_Cap2
MOVF MATH_RES1, 0
MOVWF Mx_Cap1
MOVF MATH_RES0, 0
MOVWF Mx_Cap0
BTFSS MY_MEASUREMENT, AL_T1OF ;allow TMR1IE?
GOTO capture_timer1_no_overflow
INCFSZ CAP_NR0, 1
GOTO capture_check_c1 ;max 256 measurements
INCF CAP_NR1, 1
GOTO capture_stop
capture_check_c1:
MOVF TMR1IF_C1, 0
ADDLW 0xFF
BTFSS STATUS, C
GOTO capture_end
caputure_timer1_overflow_end:
call capture_stop
RETURN
capture_timer1_no_overflow:
INCFSZ CAP_NR0, 1 ;do 256 measurements
GOTO capture_end
INCF CAP_NR1, 1
capture_stop:
BCF T1CON, TMR1ON ;bank 0, stop timer 1
MOVLW b00000000 ;set timer 1 to b0000000000000000
MOVWF TMR1H
MOVWF TMR1L
BCF INTCON, PEIE ;disable periperal interrupts
BCF INTCON, GIE ;disable interrupts
;--- select bank 1 ---
BSF STATUS, RP0
BCF PIE1, CCP1IE ;bank 1, disable CCP1 Interrupt Enable
BCF PIE1, TMR1IE ;bank 1, disable timer 1 interrupts
;--- select bank 0 ---
BCF STATUS, RP0
bcf MY_FLAGS, CAP ;end of caputure
RETURN
capture_end:
BCF T1CON, TMR1ON ;bank 0, stop timer 1
restore_prescaler:
MOVLW b00000000 ;set timer 1 to b0000000000000000
MOVWF T1CON ; - - T1CKPS1 T1CKPS0 T1OSCEN T1SYNC TMR1CS TMR1ON
;prescale 1:1, T1SYNC ignored, clock source: Internal clock (FOSC/4), tmr1 off
MOVWF TMR1H ;setting TMR1H or L resets prescaler!
MOVWF TMR1L
BTFSS MY_MEASUREMENT, MR_1 ; uF or F?
GOTO restore_prescaler_pF_nF
restore_prescaler_uF_uF2:
MOVLW b00000101 ;Capture mode, every rising edge (factor 16)
MOVWF CCP1CON ;CCP1M<3:0>: CCPx Mode Select bits
BTFSS MY_MEASUREMENT, MR_0 ; F?
GOTO restore_prescaler_uF
restore_prescaler_uF2:
MOVLW b00110000 ;prescale 1:8 (factor 8)
MOVWF T1CON
GOTO restore_prescaler_done
restore_prescaler_uF:
MOVLW b00100000 ;prescale 1:4 (factor 4)
MOVWF T1CON
GOTO restore_prescaler_done
restore_prescaler_pF_nF:
BTFSS MY_MEASUREMENT, MR_0 ; nF?
GOTO restore_prescaler_done
restore_prescaler_nF:
MOVLW b00010000 ;prescale 1:2 (factor 2)
MOVWF T1CON
restore_prescaler_done:
BSF T1CON, TMR1ON ;bank 0, start timer 1
BCF PIR1, CCP1IF ;bank 0, reset interrupt CCP1IF
RETURN