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
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:

RangeNth rising edgePrescalarTimer1 overflowsNumber of measurements
1 (pF)16 th1No256
2 (nF)4 th2No256
3 (uF)1 st4No256
4 (uF2)1 st8Yes1...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)RangeMxNmeas.Nth rising edgePrescalarExactPercentage(1)Ccalc (pF)ErrorPercentage(2)CdispUnit

  • 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.hex (rename 'txt' to 'hex')

Below you can see a high-level program overview.
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'.


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
On 2013-06-09, Eeuwe Vandyke wrote:


On 2013-06-14, Eeuwe Vandyke wrote:


On 2013-12-10, Joris wrote:


On 2013-12-11, Eeuwe Vandyke wrote:


On 2014-05-23, Robert Marko wrote:


On 2014-05-24, Eeuwe Vandyke wrote:


On 2014-05-25, Robert Marko wrote:


On 2014-05-25, Eeuwe Vandyke wrote:


On 2014-05-25, Robert Marko wrote:


On 2014-05-25, Eeuwe Vandyke wrote:


On 2014-05-25, Robert Marko wrote:


On 2014-11-12, Victor wrote:


On 2014-11-16, Eeuwe Vandyke wrote:


Back to List

All form fields are required.
A confirmation mail for the comments will be send to you.