Download as pdf or txt
Download as pdf or txt
You are on page 1of 41

© Gooligum Electronics 2015 www.gooligum.com.

au

Introduction to PIC Programming


Enhanced Mid-Range Architecture and Assembly Language

by David Meiklejohn, Gooligum Electronics

Lesson 17: CCP, part 1 - Capture and Compare

We’ve seen in previous lessons that timers are very useful for timing and scheduling events. For example, in
lesson 5, we used Timer0 to measure the elapsed time between an LED being lit (an internally driven event)
and a button being pressed (an external signal). In lesson 15, we used Timer1’s gate control facility to
measure the pulse width of a signal. In lesson 13, we used a Timer0 interrupt to initiate regular analog-to-
digital conversions. And in every timer lesson, we’ve used the timer to flash LEDs.
This lesson introduces the Capture/Compare/PWM (CCP) and Enhanced CCP (ECCP) modules, beginning
with their capture and compare modes, which work with Timer1 to make it easy (in capture mode) to
accurately time external signals or (in compare mode) to automatically schedule an event, such as toggling a
pin or initiating an analog-to-digital conversion.
We’ll look at the pulse-width modulation (PWM) mode in the next lesson.
In summary, this lesson covers:
 Introduction to the CCP module and its capture and compare modes
 Using capture mode to measure signal period and pulse width
 Using compare mode to trigger accurately-timed external events (pin changes)
 Using compare mode to initiate regular analog-to-digital conversions

PIC16F1824 (E)CCP Modules


The PIC16F1824 includes two CCP modules and two ECCP modules.
The “enhanced” functionality in the ECCP modules relates only to their PWM modes; the capture and
compare functions are identical in all four modules (ECCP1, ECCP2, CCP3 and CCP4), which we will refer
to generically as “CCP” modules in this lesson.
Each CCP module is controlled by a CCPxCON register (where ‘x’ is ‘1’, ‘2’, ‘3’ or ‘4’):
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
CCPxCON PxM<1:0> DCxB<1:0> CCPxM<3:0>

Bits 4 to 7 are only used in PWM mode, and will be described in the next lesson.
The CCPxM<3:0> bits select the operating mode.
Clearing these mode select bits turns off and resets the CCP module.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 1
© Gooligum Electronics 2015 www.gooligum.com.au

We’ll look at the bit settings relevant to the capture and compare modes, below.

Capture Mode
Capture mode allows us to measure the duration of an external signal on one of the CCPx pins1.
A capture event is a defined change, or some number of changes, in the signal on CCPx.
When this occurs, the current value of TMR1 is copied into a pair of registers: CCPRxH and CCPRxL
(where ‘x’ is ‘1’, ‘2’, ‘3’ or ‘4’), forming the upper and lower 8 bits of the 16-bit captured timer value.
An interrupt request flag is also set, to indicate that an event has been captured.
Each CCP module has a separate interrupt flag, CCPxIF, located in one of the peripheral interrupt registers:
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
PIR1 TMR1GIF ADIF RCIF TXIF SSP1IF CCP1IF TMR2IF TMR1IF

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0


PIR2 OSFIF C2IF C1IF EEIF BCL1IF – – CCP2IF

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0


PIR3 – – CCP4IF CCP3IF TMR6IF – TMR4IF –

As always, an interrupt can only be triggered if the corresponding enable bit (CCPxIE) is set. These are
located in the peripheral interrupt enable registers:

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0


PIE1 TMR1GIE ADIE RCIE TXIE SSP1IE CCP1IE TMR2IE TMR1IE

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0


PIE2 OSFIE C2IE C1IE EEIE BCL1IE – – CCP2IE

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0


PIE3 – – CCP4IE CCP3IE TMR6IE – TMR4IE –

The available capture events, and the


corresponding mode selection bit settings, are CCPxM<3:0> mode capture event
shown in the table on the right. 0000 off
0100 capture every falling edge
If TMR1 is incrementing at a known rate, this
allows us to measure the time between events, 0101 capture every rising edge
by subtracting the captured timer values. 0110 capture every 4th rising edge
Hopefully some examples will make this 0111 capture every 16th rising edge
clearer!

1
on the PIC16F1824, CCP1, CCP2, CCP3 and CCP4 are shared by default with RC5, RC3, RA2, and RC1
respectively, although CCP2 can alternatively be assigned to RA5, using the APFCON1 register (see the data sheet)

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 2
© Gooligum Electronics 2015 www.gooligum.com.au

Example 1: Period measurement using capture mode


To show how capture mode can be used to measure the period of a digital signal, we’ll use the circuit
(similar to those used in the Timer1 gate control examples in lesson 15) shown below:

Once again, the 555 timer generates a train of digital pulses (square waves), with the potentiometer adjusting
the frequency between approximately 150 and 10000 Hz (depending on component values, which will vary
due to tolerances), generating active-high pulses ranging from less than 100 µs to more than 3000 µs.
This variable-frequency oscillator is included on the Gooligum training board, with the frequency controlled
by trimpot RP1. If you have the Gooligum board, you can implement this circuit by:
 placing shunts (six of them) across every position in jumper block JP4, connecting segments A-D, F
and G to pins RA0-1 and RC1-4
 placing a single shunt in position 2 (“RA/RB2”) of JP5, connecting segment E to pin RA2
 placing a shunt across pins 1 and 2 (“GND”) of JP6, connecting digit 1 to ground
 placing a shunt in position 1 (“CCP1”) of JP26, connecting the variable frequency digital output to
the CCP1 pin.
All other shunts should be removed.
If you are using the Microchip Low Pin Count Demo Board, you will need to build the 555-based oscillator
circuit separately and connect it to the 14-pin header on the demo board (RC5/CCP1 is available on pin 4 of
the header, while power and ground are pins 13 and 14).

We will display the measured period as a single hexadecimal digit on the 7-segment LED display – after
appropriate scaling, of course!

To measure the period of a signal, we need to record the time at the start of the signal (t1), and at the end of
the signal (t2). The period (T) is then the difference between the two: T = t2 – t1.
Our signal is a square wave, so the start of the signal is the rising edge of each pulse, and the end of the
signal is the rising edge of the next pulse (the end of one period is the start of the next).
Or, you could say that the period is the time between successive falling edges. With a simple square wave, it
doesn’t matter if we measure the time between rising or falling edges – we’ll get the same period, either way.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 3
© Gooligum Electronics 2015 www.gooligum.com.au

So, to measure the signal’s period, we need to record (or capture) the time of every rising or falling edge.
That’s a good task for the ECCP module, in capture mode!

To measure time, we need a timer, incrementing at a steady, known rate.


Since the ECCP module’s capture mode uses Timer1, we don’t have any choices here; we have to configure
Timer1 to provide the time base.
If you have an accurate external clock source available, you could use it to drive Timer1 in counter mode
(see example 3 in lesson 15). Or you could use Timer1’s LP oscillator with a watch crystal, as we did in
examples 4 and 5 in that lesson. But if you do use Timer1 in counter mode, you must configure it as a
synchronous counter, to ensure that it works correctly with the CCP modules.
In this example, we’ll configure Timer1 in timer mode, where the timing is derived from the processor clock.
We’re not aiming for high accuracy, so the internal RC oscillator is good enough to use as the time base.

It is easiest to calculate the period if the timer can run for the whole period without overflowing.
Given that Timer1 is a 16-bit timer, it will overflow after 65,536 increments, and ideally the period should be
less than this.
For the best time resolution, we should run Timer1 as quickly as possible, while keeping the period to less
than 65,536 increments.
Our signal has a minimum frequency of around 150 Hz, so the maximum period will be around 7 ms.

Given a 32 MHz processor clock, if we select the instruction clock (FOSC/4) with no prescaler as the Timer1
clock source, TMR1 will increment every 0.125 µs.
Timer1 will then overflow every 65,536 × 0.125 µs = 8.192 ms, allowing us to measure periods up to 8 ms or
so. That’s a little more than we need, but there’s not much margin for error – what if the maximum period is
a little longer than expected?
It would be better to use a 16 MHz processor clock, with FOSC/4 as the Timer1 clock source and no
prescaler, giving a maximum Timer1 period of 65,536 × 0.25 µs ≈ 16 ms.

So first, we configure the device to use the internal RC oscillator, as usual:


;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code or data protect,
; no failsafe clock monitor, two-speed start-up disabled
; no write protection, 4xPLL off, stack resets on,
; low BOR threshold, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF & _CPD_OFF & _FCMEN_OFF & _IESO_OFF
__CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF

We then select the 16 MHz clock (see lesson 9):


movlw b'01111010' ; configure internal oscillator:
; -1111--- 16 MHz (IRCF = 1111)
; ------1- select internal clock (SCS = 1x)
banksel OSCCON ; -> 0.25 us / instruction cycle
movwf OSCCON

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 4
© Gooligum Electronics 2015 www.gooligum.com.au

We must configure the CCP1 pin (shared with RC5) as a digital input2:
; configure ports
banksel LATA ; start with all output pins low
clrf LATA ; (all LED segments off)
clrf LATC
banksel TRISA ; configure PORTA and PORTC as all outputs
clrf TRISA
movlw 1<<5 ; except RC5 (CCP1 input)
movwf TRISC

Timer1 is configured with the instruction clock source and no prescaler (see lesson 15):
movlw b'00000001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --00---- no prescaler (T1CKPS = 00)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 0.25 us
movwf T1CON

Now we can configure the CCP module to capture every falling or rising edge, with:
movlw b'00000100' ; configure ECCP1:
; ----0100 capture mode, falling edge (CCP1M = 0100)
banksel CCP1CON ; -> capture every falling edge
movwf CCP1CON

or:
movlw b'00000101' ; configure ECCP1:
; ----0101 capture mode, rising edge (CCP1M = 0101)
banksel CCP1CON ; -> capture every rising edge
movwf CCP1CON

Note that, since the variable signal is connected to the CCP1 (RA5) pin, we are using the ECCP1 module to
capture it.
If the signal was, for example, connected to the CCP3 (RA2) pin, we’d use the CCP3 module in exactly the
same way, but using the CCP3CON register instead. Once again, the four “CCP” modules are completely
interchangeable, as far as the capture mode is concerned.

With the CCP module and timer configured and running, we can process each capture event.
In pseudo code, the main loop will look like:
repeat
wait for a capture event
period = current capture time – previous (saved) capture time
save current capture time
scale and display period
forever

2
a pin cannot be used as a CCP input if analog input mode is selected for that pin. For example, to use CCP3 as a
CCP input, you must configure RA2 (shared with CCP3) as a digital input by clearing the ANSELA<2> bit.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 5
© Gooligum Electronics 2015 www.gooligum.com.au

The first step, waiting for a capture event (such as a falling or rising edge on CCP1) is easy – simply poll the
CCP1IF flag:
; wait for capture event
banksel PIR1
w_cap btfss PIR1,CCP1IF ; wait for CCP1 interrupt flag to go high
goto w_cap
bcf PIR1,CCP1IF ; clear flag for next event

Note that the flag has to be cleared after the polling loop, ready to be set by the next capture event.

At this point, the CCPR1 registers hold the value of TMR1 from when the event occurred.
We already have the “current capture time” – it is simply the current value of CCPR1H:CCPR1L.
But it’s clear that we will need to save this current value somewhere, so that it can become the “previous
capture time”, when the process the next event. This means that we will need a 16-bit variable to hold our
“saved” (or “previous”) capture time.
We will also need a 16-bit variable to store the calculated period.
We can define these with:
UDATA_SHR
ccpr1_s res 2 ; saved value of CCPR1 (previous capture)
period res 2 ; signal period (raw)

(using shared registers because we don’t need many variables in this example, avoiding the need for
banksel directives when accessing them)

Now we need to calculate the period.


This could be expressed in pseudo-code as:
period = current capture – previous capture
(= CCPR1 – ccpr1_s)

We learned to perform 16-bit subtraction in lesson 14, using a routine which implemented “b = b – a”.
But, we have three variables here (period, CCPR1 and ccpr1_s), so we need a routine which implements
“c = b – a”, don’t we?
Actually, no. We can re-order the calculation as an assignment followed by as subtraction:
period = CCPR1 (= current capture)

period = period – ccpr1_s


(= CCPR1 – ccpr1_s)
(= current capture – previous capture)

Now this is in the form “b = b – a”, which we know how to do, with:
; subtract previous capture time
banksel CCPR1L ; period = CCPR1
movf CCPR1L,w
movwf period
movf CCPR1H,w
movwf period+1
movf ccpr1_s,w ; period = period - ccpr1_s
subwf period,f ; (= CCPR1 - ccpr1_s)
movf ccpr1_s+1,w ; (= current capture - previous capture)
subwfb period+1,f

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 6
© Gooligum Electronics 2015 www.gooligum.com.au

You may be thinking “hold on – what if ccpr1_s is greater than CCPR1?”


How could this work? Wouldn’t it mean that the period could be negative? How is that possible?
This would happen if TMR1 overflows during the capture period.
Suppose that we’re capturing falling edges, and that when one falling edge is detected, the value of TMR1 is
FFF0h. After 16 clock periods (8 µs in this example), TMR1 will overflow, and roll over to 0000h.
Suppose also that, after another 32 clocks, when TMR1 = 0020h, the next falling edge is detected.
We would then have:
CCPR1 = 0020h (= 32)
ccpr1_s = FFF0h (= 65,520)
period = CCPR1 – ccpr1_s
= 0020h – FFF0h

If we were working with ordinary, unbounded, decimal numbers, the calculation would be:
period = 0020h – FFF0h
= 32 – 65,520
= –65,488

That’s clearly impossible.


But we’re not working with ordinary, unbounded numbers. We’re working with 16-bit quantities.
Recall, from lesson 14, that in fixed-length (e.g. 16-bit) binary arithmetic, subtracting a number is the same
as adding the two’s complement of that number, and that the two’s complement of a binary number can be
obtained by inverting it (flipping all the bits) then adding one.
So, subtracting FFF0h is the same is adding the two’s complement of FFF0h.
To obtain the two’s complement of FFF0h, we invert it (giving 000Fh), then add one, giving 0010h.
Thus, when working with 16-bit quantities, we have:
period = 0020h – FFF0h
= 0020h + (-FFF0h)
= 0020h + 0010h
= 0030h (= 48)

And that (0030h, or 48 in decimal) is the correct answer, because 48 clock periods have elapsed from one
falling edge to the next.
Like magic, we get the correct period if we subtract the previous capture value from the current capture
value, even if the current value is less than the previous value.
But this only works if we use fixed length (e.g. 16-bit) binary arithmetic, which is what we’re doing here.
And, crucially, it doesn’t work if TMR1 overflows more than once between capture events, which is why the
timer’s period (i.e. 65,536 counts for Timer1) should be longer than the maximum period we’re measuring3.

Having calculated the signal’s period, we can save the current capture value for next time:
; save current capture time for next period
movf CCPR1L,w
movwf ccpr1_s
movf CCPR1H,w
movwf ccpr1_s+1

3
If this isn’t possible, you can use a Timer1 interrupt to count the number of TMR1 overflows between capture events.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 7
© Gooligum Electronics 2015 www.gooligum.com.au

Finally, we need to display the period as a single hex digit, which means that we must scale it to the range 0
to 15 (four bits).
Our maximum period will be around 7 ms. At 0.25 µs per clock, that’s a maximum of 28,000 clock periods.
To avoid complicated (and slow) arithmetic, it’s best to scale using simple binary operations, such as shifts,
if possible. So, it’s easiest to work in binary if we can.
28,000 is close to 32,727 = 215-1 = 7FFFh.
This means that we can consider the period to be a 15-bit quantity.
To convert it to a 4-bit quantity for display, we can simply drop the lower byte (effectively dividing the
period by 256), and right-shift the upper byte by three (dividing it by 8, so that we’re dividing by 2048
overall), as follows:
; display scaled period (divide by 2048)
lsrf period+1,f ; divide MSB of period by 8
lsrf period+1,f
lsrf period+1,f

The upper byte (period+1) should now hold a 4-bit value that we can display, but that will only be true if
we really are certain that the period is no more than 32,767 clocks (around 8 ms). If it’s ever more than that,
we’ll end up with a value greater than 15, which we can’t display. So, to be safe, we should mask off (clear)
the upper nybble (4 bits) before displaying it:
movlw b'00001111' ; mask off upper nybble
andwf period+1,w ; to ensure value <= 0Fh
movwf digit ; copy result to digit variable
pagesel set7seg ; then display it
call set7seg

(using the set7seg subroutine developed in lesson 12)

Now that we’ve finished calculating and displaying the current period, we can go back and repeat for the
next period.

Complete program
This is how it all fits together (using rising edges):
;************************************************************************
; Description: Lesson 17, example 1a *
; *
; Demonstrates use of CCP capture mode *
; to measure the period of a digital signal on CCP1, *
; scaled and displayed as a single hex digit *
; *
; Period (in 0.25 us) between rising edges on CCP1 is captured *
; Result is divided by 2048 and displayed in hex on a single-digit *
; 7-segment LED display. *
; Time base is internal RC oscillator at 16 MHz. *
; *
;************************************************************************
; *
; Pin assignments: *
; RA0-2, RC1-4 = 7-segment display bus (common cathode) *
; CCP1 = signal to measure period of (8 ms max) *
; *
;************************************************************************

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 8
© Gooligum Electronics 2015 www.gooligum.com.au

#include "p16f1824.inc"

errorlevel -302 ; no warnings about registers not in bank 0


errorlevel -303 ; no warnings about program word too large

radix dec

;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code or data protect,
; no failsafe clock monitor, two-speed start-up disabled
; no write protection, 4xPLL off, stack resets on,
; low BOR threshold, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF & _CPD_OFF & _FCMEN_OFF & _IESO_OFF
__CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF

;***** VARIABLE DEFINITIONS


UDATA_SHR
ccpr1_s res 2 ; saved value of CCPR1 (previous capture)
period res 2 ; signal period (raw)
digit res 1 ; digit to be displayed

;***** RESET VECTOR ***************************************************


RES_VECT CODE 0x0000 ; processor reset vector

;***** MAIN PROGRAM ***************************************************

;***** Initialisation
start
; configure ports
banksel LATA ; start with all output pins low
clrf LATA ; (all LED segments off)
clrf LATC
banksel TRISA ; configure PORTA and PORTC as all outputs
clrf TRISA
movlw 1<<5 ; except RC5 (CCP1 input)
movwf TRISC

; configure oscillator
movlw b'01111010' ; configure internal oscillator:
; -1111--- 16 MHz (IRCF = 1111)
; ------1- select internal clock (SCS = 1x)
banksel OSCCON ; -> 0.25 us / instruction cycle
movwf OSCCON

; configure timers
movlw b'00000001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --00---- no prescaler (T1CKPS = 00)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 0.25 us
movwf T1CON

; configure CCP modules


movlw b'00000101' ; configure ECCP1:
; ----0101 capture mode, rising edge (CCP1M = 0101)

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 9
© Gooligum Electronics 2015 www.gooligum.com.au

banksel CCP1CON ; -> capture every rising edge


movwf CCP1CON

;***** Main loop


main_loop
; Measure period of pulses on CCP1 input

; wait for capture event


banksel PIR1
w_cap btfss PIR1,CCP1IF ; wait for CCP1 interrupt flag to go high
goto w_cap
bcf PIR1,CCP1IF ; clear flag for next event

; subtract previous capture time


banksel CCPR1L ; period = CCPR1
movf CCPR1L,w
movwf period
movf CCPR1H,w
movwf period+1
movf ccpr1_s,w ; period = period - ccpr1_s
subwf period,f ; (= CCPR1 - ccpr1_s)
movf ccpr1_s+1,w ; (= current capture - previous capture)
subwfb period+1,f

; save current capture time for next period


movf CCPR1L,w
movwf ccpr1_s
movf CCPR1H,w
movwf ccpr1_s+1

; display scaled period (divide by 2048)


lsrf period+1,f ; divide MSB of period by 8
lsrf period+1,f
lsrf period+1,f
movlw b'00001111' ; mask off upper nybble
andwf period+1,w ; to ensure value <= 0Fh
movwf digit ; copy result to digit variable
pagesel set7seg ; then display it
call set7seg

; repeat forever
pagesel main_loop
goto main_loop

;***** LOOKUP TABLES ****************************************************


TABLES CODE

; pattern table for 7 segment display on port A


; RA2:0 = EFG
get7sA brw
retlw b'000110' ; 0
retlw b'000000' ; 1
retlw b'000101' ; 2
retlw b'000001' ; 3
retlw b'000011' ; 4
retlw b'000011' ; 5
retlw b'000111' ; 6
retlw b'000000' ; 7

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 10
© Gooligum Electronics 2015 www.gooligum.com.au

retlw b'000111' ; 8
retlw b'000011' ; 9
retlw b'000111' ; A
retlw b'000111' ; b
retlw b'000110' ; C
retlw b'000101' ; d
retlw b'000111' ; E
retlw b'000111' ; F

; pattern table for 7 segment display on port C


; RC4:1 = CDBA
get7sC brw
retlw b'011110' ; 0
retlw b'010100' ; 1
retlw b'001110' ; 2
retlw b'011110' ; 3
retlw b'010100' ; 4
retlw b'011010' ; 5
retlw b'011010' ; 6
retlw b'010110' ; 7
retlw b'011110' ; 8
retlw b'011110' ; 9
retlw b'010110' ; A
retlw b'011000' ; b
retlw b'001010' ; C
retlw b'011100' ; d
retlw b'001010' ; E
retlw b'000010' ; F

; Display digit passed in 'digit' variable (common) on 7-segment display


set7seg
banksel LATA
movf digit,w ; get digit to display
call get7sA ; lookup pattern for port A
movwf LATA ; then output it
movf digit,w ; repeat for port C
call get7sC
movwf LATC
return

END

Example 2: Pulse width measurement using capture mode


Capture mode can also be used to measure the width of a pulse, which we can demonstrate using the circuit
from the last example.

Given that the pulse width is the time between each rising edge and the next falling edge, we need to capture
the rising and falling edges.
Then, for each pulse, we subtract the rising edge time from the falling edge time, to get the pulse width.

This means that, instead of configuring the CCP module in our initialisation code, we need to configure it at
the start of the main loop to capture rising edges, wait for the rising edge, and then reconfigure it to capture
falling edges, wait for the falling edge, and finally subtract the two capture times to calculate the pulse width.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 11
© Gooligum Electronics 2015 www.gooligum.com.au

So we begin the main loop by waiting for the rising edge at the start of the pulse:
main_loop
; Measure with of pulses on CCP1 input

; wait for rising edge


movlw b'00000101' ; configure ECCP1 to capture rising edge:
; ----0101 capture mode, rising edge (CCP1M = 0101)
banksel CCP1CON
movwf CCP1CON
banksel PIR1
w_rise btfss PIR1,CCP1IF ; wait for CCP1 interrupt flag to go high
goto w_rise
bcf PIR1,CCP1IF ; clear flag for next event

If we now save the captured time, we have a record of when the pulse started:
; save capture time at pulse start
banksel CCPR1L
movf CCPR1L,w
movwf ccpr1_s
movf CCPR1H,w
movwf ccpr1_s+1

So far, this is the same as the period measurement technique. The difference with pulse width measurement
is that we now reconfigure the CCP module to capture falling edges, and wait for the end of the pulse:
; wait for falling edge
movlw b'00000100' ; configure ECCP1 to capture falling edge:
; ----0100 capture mode, falling edge (CCP1M = 0100)
banksel CCP1CON
movwf CCP1CON
banksel PIR1
w_fall btfss PIR1,CCP1IF ; wait for CCP1 interrupt flag to go high
goto w_fall
bcf PIR1,CCP1IF ; clear flag for next event

We can now subtract the two captured times, to give the width:
; subtract start time
banksel CCPR1L ; width = CCPR1
movf CCPR1L,w
movwf width
movf CCPR1H,w
movwf width+1
movf ccpr1_s,w ; width = width - ccpr1_s
subwf width,f ; (= CCPR1 - ccpr1_s)
movf ccpr1_s+1,w ; (= falling edge - rising edge)
subwfb width+1,f

This is the same as before, except that we’ve named the variable ‘width’, instead of ‘period’.
It holds the number of 0.25 µs periods (assuming that Timer1 has been configured to increment every 0.25
µs, as before) between the pulse’s rising and falling edges.
We have to scale this value for display as a single hex digit.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 12
© Gooligum Electronics 2015 www.gooligum.com.au

Although we could divide by 2048, as we did for the period measurement, we can get better resolution by
dividing by only 1024 – because although the signal’s period could be up to 8 ms, its pulse width should be
no more than 4 ms, with the component values shown.
So to scale and display the pulse width, we have:
; display scaled pulse width (divide by 1024)
lsrf width+1,f ; divide MSB of width by 4
lsrf width+1,f
movlw b'00001111' ; mask off upper nybble
andwf width+1,w ; to ensure value <= 0Fh
movwf digit ; copy result to digit variable
pagesel set7seg ; then display it
call set7seg

Finally, we can repeat the process – going back to wait for the next rising edge.

The code is otherwise the same as it was in the first example, so there is no need to repeat it here.

As an exercise, you may wish to calculate the signal’s period, as well as its pulse width, by combining these
first two examples. The period calculation would be done immediately after each rising edge is captured,
before the falling edge is captured. By adding display multiplexing (see lesson 12), you could output the
period and pulse width on separate digits. And if you want a real challenge, you could divide the pulse width
by the period, to get the duty cycle, and display that (perhaps as a value from 00h to FFh, instead of as a
percentage) using a couple of 7-segment digits.

It’s interesting to compare the capture mode approach to the one we took in lesson 15, where we used
Timer1 gate control for pulse width measurement. Both methods use Timer1, and have the same time
resolution (dependent on how quickly Timer1 is incremented).
The Timer1 gate control method is simpler in some ways (the example code is only 85 lines long, compared
with 106 lines for the equivalent capture mode example) – so why would we choose to use capture mode?
Perhaps the most significant reason is that devices such as the PIC16F1824 commonly have several CCP
inputs, making it possible to capture multiple inputs (comparing the pulse widths of a couple of inputs, for
example), but they usually only have a single timer gate input.

Example 3: Period measurement using capture mode interrupts


Instead of polling the CCP interrupt flag, you may prefer to use an interrupt handler to process each capture
event – especially if your code needs to be doing other things “at the same time”.
In this example, we’ll continue to use the circuit from the first two examples, to show how CCP interrupts
can be used to measure the period of a square wave.

If enabled, the CCP interrupt is triggered on every capture event. The interrupt handler would then calculate
the signal’s period, in much the same way as we did in example 1. The ISR would typically store the period
in a variable, which would be processed and/or displayed by another routine, perhaps within the main loop or
in another interrupt handler – similar to what we did to display the ADC output in the ADC interrupt
example in lesson 13.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 13
© Gooligum Electronics 2015 www.gooligum.com.au

However, you probably don’t want the interrupt handler to run too often, or else you won’t have enough time
between interrupts to do much else.
With the oscillator running at up to 10 kHz, the CCP interrupt could be run as often as every 100 µs – if it is
triggered on every pulse.
Luckily, the CCP modules provide a way to limit how often each CCP interrupt is triggered: instead of
capturing every falling or rising edge, as we did in example 1, we can capture every 4th or 16th rising edge.
We can configure the ECCP1 module to capture every 16th rising edge, with:
movlw b'00000111' ; configure ECCP1:
; ----0111 capture mode, 16th rising edge (CCP1M = 0111)
banksel CCP1CON ; -> capture every 16th rising edge
movwf CCP1CON

With the oscillator running at 10 kHz, the CCP interrupt would now be triggered every 16 × 100 µs = 1.6 ms,
which is much more reasonable.
Another advantage of capturing every 16th rising edge is that we’re effectively measuring the average period
of a series of 16 pulses – which is useful if there is some jitter in the signal which we’d like to smooth out,
without having to implement an averaging filter, like the one in lesson 14.
However, if our oscillator runs at its minimum 150 Hz, giving a period of around 7 ms, the interrupt would
be triggered every 16 × 7 ms = 112 ms. If we continue to run the device at 16 MHz (0.25 µs per instruction
clock), that’s a period between capture events of up to 448,000 instruction cycles.
Our 16-bit timer, TMR1, can only count up to 65,535, so, if we don’t make any other changes, the timer will
overflow between capture events. We could run the PIC at a lower clock rate (see lesson 9), but that defeats
the purpose of reducing the number of interrupts (if we run the PIC more slowly, we can’t do as much
processing between interrupts).
The solution is simple – use a 1:8 prescaler with Timer1:
movlw b'00110001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --11---- prescale = 8 (T1CKPS = 11)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 2 us
movwf T1CON

With TMR1 incrementing every 2 µs, it will overflow every ~131 ms – long enough to measure our signal’s
period, even at the slowest oscillator speed.
Previously, when measuring a single pulse, our “full scale” of ~8 ms was 32,767 (215-1) × 0.25 µs counts.
Now that we’re capturing every 16th rising edge, and counting every 2 µs, our full scale of ~131 ms will be
65,535 (216-1) counts.
To convert this to a 4-bit quantity, for display as a single hex digit, we can once again ignore the least
significant byte (effectively dividing it by 256), and then right-shift the upper byte by four (dividing it by 16,
so that we’re dividing the period by 4096 overall). But instead of doing four right shifts, it’s easier to use the
swap instruction (moving the upper nybble, which we want to keep, into the lower nybble) and then clear the
upper nybble. After doing this, we can display the scaled result, as before:
; display scaled period (divide by 4096)
swapf period+1,f ; scaled result is high nybble of period
movlw b'00001111' ; mask off upper nybble
andwf period+1,w ; to ensure value <= 0Fh
movwf digit ; copy result to digit variable
pagesel set7seg ; then display it
call set7seg

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 14
© Gooligum Electronics 2015 www.gooligum.com.au

If you make these changes to the code from example 1, the resulting program should work the same way as
before, displaying a ‘0’ when (if you are using the Gooligum training board) trimpot RP1 is turned all the
way clockwise, and ‘d’ (or maybe ‘c’ or ‘E’, depending on component values) when it is at the other
extreme.

But of course, we want to implement this using interrupts!


First, we need to enable the CCP interrupt:
banksel PIE1
bsf PIE1,CCP1IE ; enable CCP1 interrupt

and of course peripheral and global interrupts, as usual:


bsf INTCON,PEIE ; enable peripheral
bsf INTCON,GIE ; and global interrupts

Our interrupt handler should begin, as always, by clearing the CCP interrupt flag:
; *** Service CCP1 interrupt
;
; Triggered on capture event,
; every 16th rising edge on CCP1 input
;
; Measures average period of pulses on CCP1 input
;
; (only CCP1 interrupts are enabled)
;
banksel PIR1
bcf PIR1,CCP1IF ; clear interrupt flag

Then, within the interrupt handler, we can calculate the signal’s period, in the same way as in example 1:
; subtract previous capture time
banksel CCPR1L ; period = CCPR1
movf CCPR1L,w
movwf period
movf CCPR1H,w
movwf period+1
movf ccpr1_s,w ; period = period - ccpr1_s
subwf period,f ; (= CCPR1 - ccpr1_s)
movf ccpr1_s+1,w ; (= current capture - previous capture)
subwfb period+1,f

; save current capture time for next period


movf CCPR1L,w
movwf ccpr1_s
movf CCPR1H,w
movwf ccpr1_s+1

With this CCP interrupt handler being triggered on every capture event, we can safely access the 16-bit
‘period’ variable within the main loop, knowing that it will always hold the period calculated for the most
recent capture event. That is, the value of ‘period’ is always current, and we can display it. There’s no
need to wait for each capture event – that’s what the interrupt handler is doing. All the main loop has to do
is to continually scale and display the current period.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 15
© Gooligum Electronics 2015 www.gooligum.com.au

However, we cannot do it this way:


; display scaled period (divide by 4096)
swapf period+1,f ; scaled result is high nybble of period
movlw b'00001111' ; mask off upper nybble
andwf period+1,w ; to ensure value <= 0Fh
movwf digit ; copy result to digit variable
pagesel set7seg ; then display it
call set7seg

Remember that period is being updated by the interrupt handler. If an interrupt was triggered in the middle
of this routine, the value stored in period may change between the two instructions which access it,
generating an incorrect result.
In general, your main code should never attempt to modify a variable which is updated within an active
interrupt service routine – it can lead to confusion, incorrect results, and haywire programs. Your main code
should only read such variables once, and not modify them.
The solution in this case is to write the scaled period directly to the digit display variable:
; display scaled period (divide by 4096)
swapf period+1,w ; scaled result is high nybble of period
andlw b'00001111' ; mask result
movwf digit ; to ensure value <= 0Fh
pagesel set7seg ; then display it
call set7seg

In this way, we’re only reading the period variable once, and we’re not modifying it.

Complete program
Here is how these fragments fit together:
;************************************************************************
; *
; Description: Lesson 17, example 3b *
; *
; Demonstrates use of CCP capture mode interrupts *
; to measure the period of a digital signal on CCP1, *
; scaled and displayed as a single hex digit *
; *
; Period between every 16th rising edge on CCP1 is captured *
; using CCP1 interrupt handler *
; Result is scaled and displayed in hex on a single-digit *
; 7-segment LED display. *
; Time base is internal RC oscillator at 16 MHz. *
; *
;************************************************************************
; *
; Pin assignments: *
; RA0-2, RC1-4 = 7-segment display bus (common cathode) *
; CCP1 = signal to measure period of (8 ms max) *
; *
;************************************************************************

#include "p16f1824.inc"

errorlevel -302 ; no warnings about registers not in bank 0


errorlevel -303 ; no warnings about program word too large

radix dec

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 16
© Gooligum Electronics 2015 www.gooligum.com.au

;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code or data protect,
; no failsafe clock monitor, two-speed start-up disabled
; no write protection, 4xPLL off, stack resets on,
; low BOR threshold, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF & _CPD_OFF & _FCMEN_OFF & _IESO_OFF
__CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF

;***** VARIABLE DEFINITIONS


UDATA_SHR
ccpr1_s res 2 ; saved value of CCPR1 (previous capture)
period res 2 ; signal period (raw)
digit res 1 ; digit to be displayed

;***** RESET VECTOR *****************************************************


RES_VECT CODE 0x0000 ; processor reset vector
pagesel start
goto start

;***** MAIN PROGRAM *****************************************************


MAIN CODE

;***** Initialisation
start
; configure ports
banksel LATA ; start with all output pins low
clrf LATA ; (all LED segments off)
clrf LATC
banksel TRISA ; configure PORTA and PORTC as all outputs
clrf TRISA
movlw 1<<5 ; except RC5 (CCP1 input)
movwf TRISC

; configure oscillator
movlw b'01111010' ; configure internal oscillator:
; -1111--- 16 MHz (IRCF = 1111)
; ------1- select internal clock (SCS = 1x)
banksel OSCCON ; -> 0.25 us / instruction cycle
movwf OSCCON

; configure timers
movlw b'00110001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --11---- prescale = 8 (T1CKPS = 11)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 2 us
movwf T1CON

; configure CCP modules


movlw b'00000111' ; configure ECCP1:
; ----0111 capture mode, 16th rising edge (CCP1M = 0111)
banksel CCP1CON ; -> capture every 16th rising edge
movwf CCP1CON
banksel PIE1
bsf PIE1,CCP1IE ; enable CCP1 interrupt

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 17
© Gooligum Electronics 2015 www.gooligum.com.au

; enable interrupts
bsf INTCON,PEIE ; enable peripheral
bsf INTCON,GIE ; and global interrupts

;***** Main loop


main_loop
; display scaled period (divide by 4096)
swapf period+1,w ; scaled result is high nybble of period
andlw b'00001111' ; mask result
movwf digit ; to ensure value <= 0Fh
pagesel set7seg ; then display it
call set7seg

; repeat forever
pagesel main_loop
goto main_loop

;***** INTERRUPT SERVICE ROUTINE


ISR CODE 0x0004

; *** Service CCP1 interrupt


;
; Triggered on capture event,
; every 16th rising edge on CCP1 input
;
; Measures average period of pulses on CCP1 input
;
; (only CCP1 interrupts are enabled)
;
banksel PIR1
bcf PIR1,CCP1IF ; clear interrupt flag

; subtract previous capture time


banksel CCPR1L ; period = CCPR1
movf CCPR1L,w
movwf period
movf CCPR1H,w
movwf period+1
movf ccpr1_s,w ; period = period - ccpr1_s
subwf period,f ; (= CCPR1 - ccpr1_s)
movf ccpr1_s+1,w ; (= current capture - previous
capture)
subwfb period+1,f

; save current capture time for next period


movf CCPR1L,w
movwf ccpr1_s
movf CCPR1H,w
movwf ccpr1_s+1

isr_end ; *** Exit interrupt


retfie

;***** LOOKUP TABLES ****************************************************


TABLES CODE

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 18
© Gooligum Electronics 2015 www.gooligum.com.au

; pattern table for 7 segment display on port A


; RA2:0 = EFG
get7sA brw
retlw b'000110' ; 0
retlw b'000000' ; 1
retlw b'000101' ; 2
retlw b'000001' ; 3
retlw b'000011' ; 4
retlw b'000011' ; 5
retlw b'000111' ; 6
retlw b'000000' ; 7
retlw b'000111' ; 8
retlw b'000011' ; 9
retlw b'000111' ; A
retlw b'000111' ; b
retlw b'000110' ; C
retlw b'000101' ; d
retlw b'000111' ; E
retlw b'000111' ; F

; pattern table for 7 segment display on port C


; RC4:1 = CDBA
get7sC brw
retlw b'011110' ; 0
retlw b'010100' ; 1
retlw b'001110' ; 2
retlw b'011110' ; 3
retlw b'010100' ; 4
retlw b'011010' ; 5
retlw b'011010' ; 6
retlw b'010110' ; 7
retlw b'011110' ; 8
retlw b'011110' ; 9
retlw b'010110' ; A
retlw b'011000' ; b
retlw b'001010' ; C
retlw b'011100' ; d
retlw b'001010' ; E
retlw b'000010' ; F

; Display digit passed in 'digit' variable (common) on 7-segment display


set7seg
banksel LATA
movf digit,w ; get digit to display
call get7sA ; lookup pattern for port A
movwf LATA ; then output it
movf digit,w ; repeat for port C
call get7sC
movwf LATC
return

END

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 19
© Gooligum Electronics 2015 www.gooligum.com.au

Compare Mode
Compare mode automatically performs an action, such as changing the state of one of the CCPx pins,
whenever TMR1 matches the contents of the CCPRx registers. If TMR1 is incrementing at a known rate,
this allows us to schedule a particular action to be performed at a predetermined time.
The active CCP module’s CCPxIF interrupt request flag is also set, and an interrupt will be triggered if the
corresponding CCPxIE enable bit is set, and peripheral interrupts are enabled.
Compare mode is often used to generate precisely-timed pulses, such as those used in some serial modulation
formats. It is also used to schedule periodic interrupts, and to automatically sample the ADC at a steady rate,
as we shall see.

CCPxM<3:0> mode compare action


The available compare actions, and their
corresponding mode selection bit settings, 0000 off
are shown in the table on the right. 0010 compare toggle CCPx output
1000 compare set CCPx output
1001 compare clear CCPx output
Hopefully some more examples will make 1010 compare none (interrupt only)
this clearer!
1011 compare clear TMR1,
initiate ADC if enabled

Example 4: Using compare mode to flash an LED


Although compare mode is most commonly used to generate short pulses, such as those used in serial
communications, we can make the compare mode actions visible (literally!) by, yet again, flashing an LED.

To make the CCP2 output visible, we can connect an LED to


the CCP2 pin (shared with RC3) of a PIC16F1824, as shown
on the right.

If you have the Gooligum training board, close jumper JP19


to enable the LED on RC3. All other jumpers should be
removed.

Or, if you are using Microchip’s Low Pin Count Demo Board,
you only need to plug in your PIC16F1824; the LED on RC3
is labelled DS4.

To flash the LED at 1 Hz, with a 50% duty cycle (as we’ve
done numerous times, going back to lesson 2), we need to
toggle the CCP2 output every 500 ms.
Therefore, we should select CCP mode ‘0010’: “Compare mode, toggle output on match”.
We then start Timer1 counting at some known, steady rate, and load a value into the CCPR2 registers such
that 500 ms will have passed when TMR1 reaches that value.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 20
© Gooligum Electronics 2015 www.gooligum.com.au

So, Timer1 has to be able to count for at least 500 ms without overflowing.
In lesson 15 we saw that, in instruction clock timer mode, with the default 500 kHz processor clock and no
prescaler, Timer1 will overflow every 524 ms. That’s more than the 500 ms we need, so we can configure
the PIC to use the internal RC oscillator, running at the default 500 kHz, and initialise Timer1 with:
; initialise timers
banksel TMR1L ; clear TMR1
clrf TMR1L
clrf TMR1H
; configure timers
movlw b'00000001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --00---- no prescaler (T1CKPS = 00)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 8 us
movwf T1CON

You’ll see that TMR1 is being cleared here, to make it easy to load the appropriate initial value into
CCPR2, so that a match will occur after 500 ms:
banksel CCPR2L ; load initial compare time to CCPR2
movlw LOW (500000/8) ; = 0.5 sec / 8 us/count
movwf CCPR2L
movlw HIGH (500000/8)
movwf CCPR2H

This makes use of the LOW and HIGH assembler directives, introduced in lesson 14.

We can then configure the ECCP2 module to toggle the CCP2 pin when TMR1 reaches the value stored in
the CCPR2 registers:
movlw b'00000010' ; configure ECCP2:
; ----0010 compare mode, toggle output (CCP2M = 0010)
movwf CCP2CON ; -> toggle CCP2 on match

Note that, for CCP2 to be used as an output, the pin it shares (RC3) must also be configured as an output:
banksel TRISC ; configure PORTC as all inputs,
movlw ~(1<<3) ; except RC3 (CCP2 output)
movwf TRISC

Following a power-on reset, each CCPx output is initially low.


Therefore, after completing the previous initialisation instructions, the LED will be initially off. Meanwhile,
TMR1 will be counting, incrementing every 8 µs. After 500 ms, TMR1 will reach 62,500 (= 500,000 ÷ 8),
which is the value stored in CCPR2, and the CCP2 output will toggle – changing to high.
Thus, if we do nothing else, the LED will turn on, automatically, after 500 ms.

Since we want the LED to continually flash, we need to wait for CCP2 to toggle, then add the appropriate
value to CCPR2 (such that CCP2 will toggle again, after another 500 ms), and then repeat.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 21
© Gooligum Electronics 2015 www.gooligum.com.au

CCP2 will toggle when a CCP match occurs, and we can detect by clearing the CCP2IF flag, and then
waiting until it goes high:
; wait for CCP match
banksel PIR2 ; clear CCP2 interrupt flag
bcf PIR2,CCP2IF
w_ccp2 btfss PIR2,CCP2IF ; wait for flag to go high
goto w_ccp2

At this point, we know that the CCP2 output has toggled.


We can now add another 500 ms to the value in CCPR2:
; add 0.5 sec to last compare time
banksel CCPR2L
movlw LOW (500000/8) ; add 0.5 sec / 8 us/count
addwf CCPR2L,f ; to CCPR2
movlw HIGH (500000/8)
addwfc CCPR2H,f

TMR1 will continue to increment (we don’t care about TMR1 overflows), and when it reaches this new
compare value, CCP2 (and our LED) will toggle again.
We then wait for this match to happen, add another 500 ms, wait again, add 500 ms, and so on.
Our main loop is then simply:
main_loop
; Toggle CCP2 output every 0.5 sec

; wait for CCP match


banksel PIR2 ; clear CCP2 interrupt flag
bcf PIR2,CCP2IF
w_ccp2 btfss PIR2,CCP2IF ; wait for flag to go high
goto w_ccp2

; add 0.5 sec to last compare time


banksel CCPR2L
movlw LOW (500000/8) ; add 0.5 sec / 8 us/count
addwf CCPR2L,f ; to CCPR2
movlw HIGH (500000/8)
addwfc CCPR2H,f

; repeat forever
goto main_loop

Suppose, though, that instead of simply toggling the output at a steady rate, flashing the LED with a 50%
duty cycle, we wanted to flash the LED asymmetrically, with (say) a 20% duty cycle, as we did in lesson 3?
We would need to change our approach – clearing the CCP2 output (turning off the LED), waiting 800 ms,
setting the CCP2 output (lighting the LED), then waiting another 200 ms, and then repeating.
We could do that with the “toggle” action, as above, but it would be hard to keep track of what we were
doing. It’s easier to understand if we explicitly use the “set” and “clear” actions to turn the LED on and off.
This means that, instead of configuring the CCP module once, in the initialisation routine, we need to re-
configure it, to alternately set and clear CCP2, within the main loop.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 22
© Gooligum Electronics 2015 www.gooligum.com.au

But first, we have to address a small problem. Our LED has to stay off for 800 ms, which means that TMR1
has to be able to count for at least 800 ms without overflowing. But, as mentioned above, with the default
500 kHz processor clock and no prescaler, Timer1 will overflow every 524 ms.
In this case, the solution is easy – select a 1:2 prescale ratio!
The Timer1 configuration then becomes:
movlw b'00010001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --01---- prescale = 2 (T1CKPS = 01)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 16 us
movwf T1CON

TMR1 will now increment every 16 µs and will overflow every 1049 ms, which is long enough.

Recall that, following a power-on reset, the CCP2 output will initially be low.
That means that, when we start the main loop, we should assume that CCP2 is low, and the LED is off.
We want the LED to stay off for 800 ms, and then turn on, so we should configure the ECCP2 module to set
the CCP2 output (lighting the LED), 800 ms from now:
; add 0.8 sec to previous compare time
banksel CCPR2L
movlw LOW (800000/16) ; add 0.8 sec / 16 us/count
addwf CCPR2L,f ; to CCPR2
movlw HIGH (800000/16)
addwfc CCPR2H,f

; configure ECCP2 to set CCP2 when compare time reached


movlw b'00001000' ; configure ECCP2:
; ----1000 compare mode, set output (CCP2M = 1000)
movwf CCP2CON ; -> set CCP2 on match

There is nothing to do now but wait for the match to happen, which we can do by monitoring the CCP2IF
flag, as we did before:
; wait for CCP match
banksel PIR2 ; clear CCP2 interrupt flag
bcf PIR2,CCP2IF
w_ccp1 btfss PIR2,CCP2IF ; wait for flag to go high
goto w_ccp1

At this point, we know that the CCP2 output has just gone high. We want the LED to stay on for 200 ms, so
we should re-configure the ECCP2 module to clear CCP2 (turning off the LED), after another 200 ms:
; add 0.2 sec to previous compare time
banksel CCPR2L
movlw LOW (200000/16) ; add 0.2 sec / 16 us/count
addwf CCPR2L,f ; to CCPR2
movlw HIGH (200000/16)
addwfc CCPR2H,f

; configure ECCP2 to clear CCP2 when compare time reached


movlw b'00001001' ; configure ECCP2:
; ----1001 compare mode, clear output (CCP2M = 1001)
movwf CCP2CON ; -> clear CCP2 on match

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 23
© Gooligum Electronics 2015 www.gooligum.com.au

Now we have to wait once more for TMR1 to match CCPR2:


; wait for CCP match
banksel PIR2 ; clear CCP2 interrupt flag
bcf PIR2,CCP2IF
w_ccp2 btfss PIR2,CCP2IF ; wait for flag to go high
goto w_ccp2

When the CCP2IF flag goes high, we know that the CCP action (clearing CCP2) has occurred.
The LED has been turned off, we’re back to where we started, and we can restart the main loop.

Once we’re into the main loop, accurate pulse timing is maintained by adding fixed quantities to the compare
value stored in CCPR2, as TMR1 continues to steadily count. The timing of each transition is relative to the
last – the LED turns off 200 ms after it turns on, and we don’t care about the actual values are, at the start of
the loop. As long as we add the values corresponding to 200 ms and 800 ms, the correct timing will be
maintained, each time we go around the loop.
However, we need to start somewhere. If we want the LED to stay off for a full 800 ms, the first time the
main loop is entered, we need to clear TMR1 and CCPR2 before the main loop starts. But – and this is the
important point for understanding compare mode – having cleared CCPR2 once, we never have to load a
value into it again. It’s all done by adding, because the timing of each pulse is always relative.

Complete program
Here is how these fragments fit together, to form the CCP version of the “flash an LED with 20% duty
cycle” program:
;************************************************************************
; *
; Description: Lesson 17, example 4b *
; *
; Demonstrates use of CCP compare mode *
; to generate high and low pulses on the CCP2 pin *
; *
; Flashes an LED on CCP2 at 1 Hz, with 20% duty cycle *
; Time base is internal RC oscillator at 500 kHz. *
; *
;************************************************************************
; *
; Pin assignments: *
; CCP2 = indicator LED *
; *
;************************************************************************

#include "p16f1824.inc"

errorlevel -302 ; no warnings about registers not in bank 0


errorlevel -303 ; no warnings about program word too large

radix dec

;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code or data protect,
; no failsafe clock monitor, two-speed start-up disabled

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 24
© Gooligum Electronics 2015 www.gooligum.com.au

; no write protection, 4xPLL off, stack resets on,


; low BOR threshold, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF & _CPD_OFF & _FCMEN_OFF & _IESO_OFF
__CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF

;***** RESET VECTOR ***************************************************


RES_VECT CODE 0x0000 ; processor reset vector

;***** MAIN PROGRAM ***************************************************

;***** Initialisation
start
; configure ports
banksel TRISC ; configure PORTC as all inputs,
movlw ~(1<<3) ; except RC3 (CCP2 output)
movwf TRISC

; configure oscillator
movlw b'00111010' ; configure internal oscillator:
; -0111--- 500 kHz (IRCF = 0111)
; ------1- select internal clock (SCS = 1x)
banksel OSCCON ; -> 8 us / instruction cycle
movwf OSCCON

; initialise timers
banksel TMR1L ; clear TMR1
clrf TMR1L
clrf TMR1H
; configure timers
movlw b'00010001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --01---- prescale = 2 (T1CKPS = 01)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 16 us
movwf T1CON

; initialise CCP modules


banksel CCPR2L ; clear CCPR2 (compare time)
clrf CCPR2L
clrf CCPR2H ; (note: CCP initially off, CCP2 output low)

;***** Main loop


main_loop
; Output low for 0.8 sec

; add 0.8 sec to previous compare time


banksel CCPR2L
movlw LOW (800000/16) ; add 0.8 sec / 16 us/count
addwf CCPR2L,f ; to CCPR2
movlw HIGH (800000/16)
addwfc CCPR2H,f

; configure ECCP2 to set CCP2 when compare time reached


movlw b'00001000' ; configure ECCP2:
; ----1000 compare mode, set output (CCP2M = 1000)
movwf CCP2CON ; -> set CCP2 on match

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 25
© Gooligum Electronics 2015 www.gooligum.com.au

; wait for CCP match


banksel PIR2 ; clear CCP2 interrupt flag
bcf PIR2,CCP2IF
w_ccp1 btfss PIR2,CCP2IF ; wait for flag to go high
goto w_ccp1

; Output high for 0.2 sec

; add 0.2 sec to previous compare time


banksel CCPR2L
movlw LOW (200000/16) ; add 0.2 sec / 16 us/count
addwf CCPR2L,f ; to CCPR2
movlw HIGH (200000/16)
addwfc CCPR2H,f

; configure ECCP2 to clear CCP2 when compare time reached


movlw b'00001001' ; configure ECCP2:
; ----1001 compare mode, clear output (CCP2M = 1001)
movwf CCP2CON ; -> clear CCP2 on match

; wait for CCP match


banksel PIR2 ; clear CCP2 interrupt flag
bcf PIR2,CCP2IF
w_ccp2 btfss PIR2,CCP2IF ; wait for flag to go high
goto w_ccp2

; repeat forever
goto main_loop

END

Example 5: Compare mode interrupts


Compare mode can also be used to generate precise intervals, without affecting any of the CCPx output pins.
We can illustrate that by using CCP interrupts to flash an
LED connected to RC0 (which is not shared with any of
the CCPx outputs), as shown in the circuit on the right, at
exactly 1 Hz.

If you have the Gooligum training board, close the jumper


JP16 to enable the LED on RC0.
Or, if you are using Microchip’s Low Pin Count Demo
Board, note that the LED on RC0 is labelled DS1.

In lesson 7 we used Timer0 interrupts to flash an LED at


exactly 1 Hz (given an accurate 500 kHz processor clock),
by adding an offset to TMR0 within the ISR.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 26
© Gooligum Electronics 2015 www.gooligum.com.au

We also saw, in lesson 15, that it’s messy to try to take the same approach with Timer14.
A more elegant way to do this is to use compare mode to generate a periodic interrupt.
If we select CCP mode ‘1010’, the CCPxIF interrupt flag will be set when TMR1 matches CCPRx, but no
other action will occur. This means that, in CCP mode ‘1010’, a CCP interrupt will be triggered when
TMR1 matches CCPRx, if the corresponding CCP interrupt is are enabled.
This is also true in the other compare modes, such as those used in example 4, above. The difference with
mode ‘1010’ is that no other action occurs. A CCP interrupt will be triggered (if enabled), but the CCPx
output will not be affected.

We’ll flash the LED with a simple 50% duty cycle, as we did in the timer interrupt examples in lessons 7 and
15. Our code will be quite similar to those examples, except that we’re now using a CCP interrupt. That is,
the ISR will toggle the LED output every 500 ms, leaving the main loop with nothing to do.
And, to illustrate the point that the CCP modules are interchangeable, we’ll use CCP3 in this example.

The ISR will be triggered when TMR1 matches CCPR3, and we want this to occur every 500 ms. We’ve
seen that Timer1 can be configured to count for up to 524 ms without overflowing, given a 500 kHz
processor clock and no prescaler, so 500 ms is ok.

To make the code more maintainable, we’ll define this toggle period as a constant:
constant FlashMS=500 ; LED flash toggle time in milliseconds
; (max 524 ms)

Since TMR1 will be incrementing every 8 µs, we can generate each new 500 ms interval by adding
FlashMS*1000/8 to CCPR3, in much the same way as in example 4.
Instead of repeating this expression multiple times, we can make the code a little easier to read by defining it
as a constant5:
constant IncCCPR=FlashMS*1000/8 ; Amount to incr CCPRx by to generate
; FlashMS delay (assuming 8 us/tick)

Timer1 and the CCP module are configured as before, except that we will now use CCP mode ‘1010’:
; initialise timers
banksel TMR1L ; clear TMR1
clrf TMR1L
clrf TMR1H
; configure timers
movlw b'00000001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --00---- no prescaler (T1CKPS = 00)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 8 us
movwf T1CON

4
when adding an offset to a 16-bit timer that is actively counting, the timer may overflow during the two-part add
operation, and this possibility has to be handled carefully, leading to increased code complexity
5
whether this is really easier to read is, of course, a question of personal preference…

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 27
© Gooligum Electronics 2015 www.gooligum.com.au

; initialise CCP modules


banksel CCPR3L ; load initial compare time to CCPR3
movlw LOW IncCCPR ; to match after 500 ms
movwf CCPR3L
movlw HIGH IncCCPR
movwf CCPR3H
movlw b'00001010' ; configure CCP3:
; ----1010 compare mode, interrupt only (CCP3M = 1010)
movwf CCP3CON ; -> (only) set CCP3IF on match

We also need to enable the interrupt for the CCP3 module:


banksel PIE3
bsf PIE3,CCP3IE ; enable CCP3 interrupt

and the peripheral and global interrupts:


bsf INTCON,PEIE ; enable peripheral
bsf INTCON,GIE ; and global interrupts

Note that, since we cleared TMR1 and loaded CCPR3 with the value that TMR1 will reach in 500 ms, the
first interrupt will occur in 500 ms.

Within the interrupt service routine, we begin by clearing the interrupt flag, as usual:
banksel PIR3 ; clear interrupt flag
bcf PIR3,CCP3IF

If we then add 500 ms to the value in CCPR3, the next interrupt will be triggered in another 500 ms:
; add offset to CCPR3 for next match
banksel CCPR3H
movlw HIGH IncCCPR
addwf CCPR3H,f
movlw LOW IncCCPR
addwfc CCPR3L,f

It’s important to note that this timing will be exact. It doesn’t matter that TMR1 continues to increment
while we do this addition. By adding a value representing 500 ms to CCPR3, we can be sure that the next
interrupt will be triggered exactly 500 ms after this one.

We can then toggle the LED, as we’ve done before:


banksel LATC ; toggle flashing LED
movlw 1<<nF_LED
xorwf LATC,f

Finally, the main loop is left with nothing to do:


main_loop
; (do nothing)

; repeat forever
goto main_loop

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 28
© Gooligum Electronics 2015 www.gooligum.com.au

Complete program
Here is how all these fragments fit together, in a similar way to our earlier interrupt-based LED flashers:
;************************************************************************
; *
; Description: Lesson 17, example 5 *
; *
; Demonstrates use of CCP compare mode interrupts *
; *
; Flashes an LED at 1 Hz, with 50% duty cycle *
; Time base is internal RC oscillator at 500 kHz. *
; *
;************************************************************************
; *
; Pin assignments: *
; RC0 = flashing LED *
; *
;************************************************************************

#include "p16f1824.inc"

errorlevel -302 ; no warnings about registers not in bank 0


errorlevel -303 ; no warnings about program word too large

radix dec

;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code or data protect,
; no failsafe clock monitor, two-speed start-up disabled
; no write protection, 4xPLL off, stack resets on,
; low BOR threshold, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF & _CPD_OFF & _FCMEN_OFF & _IESO_OFF
__CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF

; pin assignments
#define F_LED LATC,0 ; flashing LED on RC0
constant nF_LED=0 ; (port bit 0)

;***** CONSTANTS
constant FlashMS=500 ; LED flash toggle time in milliseconds
; (max 524 ms)
constant IncCCPR=FlashMS*1000/8 ; Amount to incr CCPRx by to generate
; FlashMS delay (assuming 8 us/tick)

;***** RESET VECTOR *****************************************************


RES_VECT CODE 0x0000 ; processor reset vector
pagesel start
goto start

;***** MAIN PROGRAM *****************************************************


MAIN CODE

;***** Initialisation
start

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 29
© Gooligum Electronics 2015 www.gooligum.com.au

; configure ports
banksel LATC ; start with all output pins low
clrf LATC ; (LED off)
movlw ~(1<<nF_LED) ; configure LED pin (only) as an output
banksel TRISC
movwf TRISC

; configure oscillator
movlw b'00111010' ; configure internal oscillator:
; -0111--- 500 kHz (IRCF = 0111)
; ------1- select internal clock (SCS = 1x)
banksel OSCCON ; -> 8 us / instruction cycle
movwf OSCCON

; initialise timers
banksel TMR1L ; clear TMR1
clrf TMR1L
clrf TMR1H
; configure timers
movlw b'00000001' ; configure Timer1:
; 00------ instruction clock (TMR1CS = 00)
; --00---- no prescaler (T1CKPS = 00)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 8 us
movwf T1CON

; initialise CCP modules


banksel CCPR3L ; load initial compare time to CCPR3
movlw LOW IncCCPR ; to match after 500 ms
movwf CCPR3L
movlw HIGH IncCCPR
movwf CCPR3H
movlw b'00001010' ; configure CCP3:
; ----1010 compare mode, interrupt only (CCP3M = 1010)
movwf CCP3CON ; -> (only) set CCP3IF on match
banksel PIE3
bsf PIE3,CCP3IE ; enable CCP3 interrupt

; enable interrupts
bsf INTCON,PEIE ; enable peripheral
bsf INTCON,GIE ; and global interrupts

;***** Main loop


main_loop
; (do nothing)

; repeat forever
goto main_loop

;***** INTERRUPT SERVICE ROUTINE


ISR CODE 0x0004

; *** Service CCP3 interrupt


;
; Triggered when TMR1 matches CCPR3
; (every 500 ms)
;
; Flashes LED at 1 Hz by toggling on each interrupt

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 30
© Gooligum Electronics 2015 www.gooligum.com.au

;
; (only CCP3 interrupts are enabled)
;
banksel PIR3 ; clear interrupt flag
bcf PIR3,CCP3IF

; add offset to CCPR3 for next match


banksel CCPR3H
movlw HIGH IncCCPR
addwf CCPR3H,f
movlw LOW IncCCPR
addwfc CCPR3L,f

; toggle LED
banksel LATC ; toggle flashing LED
movlw 1<<nF_LED
xorwf LATC,f

isr_end ; *** Exit interrupt


retfie

END

Example 6: Using CCPRx as a period register for Timer1


If you’re not using the analog-to-digital converter (ADC), the CCP module’s “special event trigger” mode,
‘1011’, provides a simpler way generate periodic interrupts with precise, arbitrary timing.
This “special event trigger” mode is like the “interrupt only” mode used in the last example, in that it sets the
CCPxIF flag, and triggers an interrupt (if CCP interrupts are enabled), when TMR1 matches CCPRx.
However, in addition to triggering an interrupt, special event trigger mode also resets (clears) TMR1 and
starts an analog-to-digital conversion if ADC is enabled.
This means that, assuming that ADC is not enabled, the special event trigger mode has the useful property of
automatically clearing TMR1, as soon as TMR1 reaches the value specified in CCPRx.
Thus, CCPRx acts as a 16-bit period register for Timer1.
This is similar to the Timer2 period register, PR2, described in lesson 16. An important difference,
however, is that PR2 holds Timer2’s maximum count (equal to the period minus one), while, in special
event trigger mode, CCPRx holds Timer1’s period.
For example, if you wanted Timer2 to reset every 100 counts, you would load the value 99 into PR2. But if
you wanted Timer1 to reset every 1000 counts, in special event trigger mode, you would load the value 1000
into one of the CCPRx registers.

To illustrate this, we can re-implement the previous example, using special event trigger mode.
Most of the code is exactly the same, so won’t be repeated here.
We will continue to use the CCP3 interrupt, triggered every 500 ms, to toggle RC0.
And we will still define the toggle period as a constant:
constant FlashMS=500 ; LED flash toggle time in milliseconds
; (max 524 ms)
Timer1 will increment every 8 µs, as before.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 31
© Gooligum Electronics 2015 www.gooligum.com.au

The period between interrupts is the same as the Timer1 period: we want Timer1 to be reset every 500 ms.
So, the number of times that TMR1 should increment, before being cleared, is given by:
Timer1 period = 500 ms × 1000 µs/ms ÷ 8 µs/tick = 62,500 ticks
Again, we can define this expression as a constant:
constant T1Period=FlashMS*1000/8 ; number of Timer1 counts to generate
; FlashMS delay (assuming 8 us/tick)

This value, being the Timer1 period, is the value we need to load into CCPR3.

Initialising the CCP module, selecting mode ‘1011’ this time, then becomes:
; initialise CCP modules
banksel CCPR3L ; load compare value to CCPR3
movlw LOW T1Period ; to match TMR1 after 500 ms
movwf CCPR3L
movlw HIGH T1Period
movwf CCPR3H
movlw b'00001011' ; configure CCP3:
; ----1011 compare mode, special trigger (CCP3M = 1011)
movwf CCP3CON ; -> reset TMR1 on match
banksel PIE3
bsf PIE3,CCP3IE ; enable CCP3 interrupt

The rest of the initialisation, and the main loop, is the same as in the last example.

However, now that TMR1 is being automatically cleared whenever it matches CCPR3, we no longer have
to adjust CCPR3 in the interrupt service routine.
The CCP interrupt handler becomes simply:
; *** Service CCP3 interrupt
;
; Triggered when TMR1 matches CCPR3
; (every 500 ms)
;
; Flashes LED at 1 Hz by toggling on each interrupt
;
; (only CCP3 interrupts are enabled)
;
banksel PIR3 ; clear interrupt flag
bcf PIR3,CCP3IF

; toggle LED
banksel LATC ; toggle flashing LED
movlw 1<<nF_LED
xorwf LATC,f

And that’s all.


Other than the change in the constant definitions (‘T1Period’ instead of ‘IncCCPR’, but otherwise the same
expression), selecting CCP mode ‘1011’ instead of ‘1010’, and removing the “add offset to CCPR3” section
from the interrupt handler, the program is exactly the same as in the last example.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 32
© Gooligum Electronics 2015 www.gooligum.com.au

Example 7: Periodic analog-to-digital conversions


We saw in lesson 13 that timer interrupts can be used to periodically sample an analog input. The example
in that lesson used a Timer0 interrupt to perform 7-segment LED display multiplexing, and to initiate each
analog-to-digital conversion. The result was then processed by an ADC interrupt handler.
This approach makes sense when you already have a timer interrupt running for some other purpose (such as
display multiplexing) at some rate that is also appropriate for sampling your analog inputs.
However, it won’t always be appropriate to tie analog-to-digital conversions to a timer interrupt in this way.

An alternative, and convenient, way to periodically sample an analog input is to use one of the CCP modules
in “special event trigger” mode. As we saw in the last example, special event trigger mode resets Timer1
and starts an analog-to-digital conversion if ADC is enabled, whenever TMR1 matches CCPRx.
This means that, if the ADC module is enabled, the special event trigger mode has the useful property of
automatically triggering periodic AD conversions, with the period specified by the 16-bit value in CCPRx.

To demonstrate how to do this, we’ll use the 2-digit light meter circuit from lesson 14 , as shown below.

To implement it using the Gooligum training board, place shunts:


 across every position (all six of them) of jumper block JP4, connecting segments A-D, F and G to
pins RA0-1 and RC1-4
 in position 1 (‘RA/RB4’) of JP5, connecting segment E to pin RA4
 across pins 2 and 3 (‘RC5’) of JP6, connecting digit 1 to the transistor controlled by RC5
 in jumpers JP8 and JP9, connecting pins RC5 and RA5 to their respective transistors
 in position 1 (‘AN2’) of JP25, connecting photocell PH2 to AN2.
All other shunts should be removed.
If you are using Microchip’s Low Pin Count Demo Board, you will need to supply your own display
modules, resistors, transistors and photocell, and connect them to the PIC via the 14-pin header on that
board.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 33
© Gooligum Electronics 2015 www.gooligum.com.au

Having only two digits instead of three reduces the program complexity a little, making it easier to see how
the CCP module’s special event trigger mode is used to drive the ADC module.
We can adapt the code from the ADC interrupt example in lesson 13, with a Timer0 interrupt used to
multiplex the display.
The Timer0 interrupt handler runs every 2 ms, and displays the current contents of two variables, ‘dig1’ and
‘dig2’. Note that that is the only thing the Timer0 handler is responsible for – unlike in the ADC interrupt
example in lesson 13, it does not trigger any analog-to-digital conversions. Its only job is display the current
contents of those two digit variables.
Instead, we configure one of the CCP modules (we’ll use CCP4), through its special event trigger mode, to
automatically trigger the analog-to-digital conversions:
; configure CCP modules
banksel CCPR4L ; load ADC sample period (in us) to CCPR4
movlw LOW ADCPeriod
movwf CCPR4L
movlw HIGH ADCPeriod
movwf CCPR4H
movlw b'00001011' ; configure CCP4:
; ----1011 compare mode, special trigger (CCP4M = 1011)
movwf CCP4CON ; -> reset TMR1 & initiate ADC each period

Note that we’re loading CCPR4 with the sample period, which, for better maintainability, has been defined
as a constant:
constant ADCPeriod=10000 ; ADC sample period in microseconds
; (max 65535)

This assumes that Timer1 has been configured to increment every 1 µs, which we can do by clocking the
processor at 1 MHz:
movlw b'01011010' ; configure internal oscillator:
; -1011--- 1 MHz (IRCF = 1011)
; ------1- select internal clock (SCS = 1x)
banksel OSCCON ; -> Tosc = 1 us,
movwf OSCCON ; 4 us / instruction cycle

and then configuring Timer1 to use the processor clock:


movlw b'01000001' ; configure Timer1:
; 01------ processor clock (TMR1CS = 01)
; --00---- no prescaler (T1CKPS = 00)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 1 us
movwf T1CON

With TMR1 being incremented every 1 µs, the maximum sample period will be 65.5 ms.

Since we are only displaying two digits of the ADC result, it is easiest if we configure the ADC with the
result left-justified, with the upper 8 bits in ADRESH:
movlw b'00000000' ; configure ADC:
; 0------- MSB of result in ADRESH<7> (ADFM = 0)
; -000---- Tad = 2*Tosc (ADCS = 000)
; = 2 us with Fosc = 1 MHz
; -----0-- Vref- is Vss (ADNREF = 0)
; ------00 Vref+ is Vdd (ADPREF = 00)
banksel ADCON1
movwf ADCON1

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 34
© Gooligum Electronics 2015 www.gooligum.com.au

We also need to enable the ADC and Timer0 interrupts:


banksel PIE1 ; enable ADC interrupt
bsf PIE1,ADIE
movlw 1<<GIE|1<<PEIE|1<<T0IE ; enable Timer0, peripheral
movwf INTCON ; and global interrupts

Note again that we do not have to enable the CCP or Timer1 interrupts.
The analog-to-digital conversions are triggered automatically when TMR1, after counting for the sample
period (10 ms in this example), matches CCPR4. TMR1 is then automatically reset and the count repeats.
No CCP or Timer1 interrupts are involved.
It all “just happens” automatically, behind the scenes.

When the result of the analog-to-digital conversion is ready, the ADC interrupt will be triggered. The ADC
interrupt handler then has to extract the digits from the result, for the Timer0 interrupt handler to display:
adc_int ; *** Service ADC interrupt
;
; Conversion is initiated by CCP special event trigger,
; every ADCPeriod microseconds
;
bcf PIR1,ADIF ; clear interrupt flag

; copy ADC result to display variables


; (to be displayed by Timer0 handler)
banksel ADRESH ; get digit 1
swapf ADRESH,w ; from high nybble of ADRESH
andlw 0x0F
banksel dig1
movwf dig1
banksel ADRESH
movf ADRESH,w ; get digit 2
andlw 0x0F ; from low nybble of ADRESH
banksel dig2
movwf dig2

goto isr_end

With the ADC interrupt handler extracting the ADC result, and the Timer0 interrupt handler displaying it,
there is nothing for the main loop to do:
main_loop
; do nothing
goto main_loop

Complete program
Here is how the program, based on the ADC interrupt example from lesson 13, comes together:
;************************************************************************
; *
; Description: Lesson 17, example 7 *
; *
; Demonstrates use of CCP compare mode special event trigger *
; to schedule periodic ADC measurements *

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 35
© Gooligum Electronics 2015 www.gooligum.com.au

; *
; Displays ADC output in hexadecimal on 7-segment LED displays *
; *
; Regularly samples analog input, using CCP special event trigger, *
; displaying result as 2 x hex digits on multiplexed 7-seg displays *
; *
;************************************************************************
; *
; Pin assignments: *
; Pin assignments: *
; AN2 = voltage to be measured (e.g. pot or LDR) *
; RA0-1,RA4,RC1-4 = 7-segment display bus (common cathode) *
; RC5 = digit 1 ("tens") enable (active high) *
; RA5 = digit 2 (ones) enable *
; *
;************************************************************************

#include "p16f1824.inc"

errorlevel -302 ; no warnings about registers not in bank 0


errorlevel -303 ; no warnings about program word too large

radix dec

;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code or data protect,
; no failsafe clock monitor, two-speed start-up disabled
; no write protection, 4xPLL off, stack resets on,
; low BOR threshold, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF & _CPD_OFF & _FCMEN_OFF & _IESO_OFF
__CONFIG _CONFIG2, _WRT_OFF & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LVP_OFF

; pin assignments
#define DIG1_EN LATC,5 ; digit 1 ("tens") digit enable
#define DIG2_EN LATA,5 ; digit 2 (ones) digit enable

;***** CONSTANTS
constant ADCPeriod=10000 ; ADC sample period in microseconds
; (max 65535)

;***** VARIABLE DEFINITIONS


UDATA_SHR
temp res 1 ; used by set7seg routine (temp digit store)

UDATA
mpx_cnt res 1 ; multiplex counter
; current ADC result (in hex):
dig1 res 1 ; digit 1 ("tens" - most significant)
dig2 res 1 ; digit 2 ("ones" - least significant)

;***** RESET VECTOR *****************************************************


RES_VECT CODE 0x0000 ; processor reset vector
pagesel start
goto start

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 36
© Gooligum Electronics 2015 www.gooligum.com.au

;***** MAIN PROGRAM *****************************************************


MAIN CODE

;***** Initialisation
start
; configure ports
banksel TRISA ; configure PORTA and PORTC as all outputs
clrf TRISC
movlw 1<<2 ; except RA2/AN2
movwf TRISA
banksel ANSELA ; select analog mode for RA2
movwf ANSELA ; -> RA2/AN2 is an analog input

; configure oscillator
movlw b'01011010' ; configure internal oscillator:
; -1011--- 1 MHz (IRCF = 1011)
; ------1- select internal clock (SCS = 1x)
banksel OSCCON ; -> Tosc = 1 us,
movwf OSCCON ; 4 us / instruction cycle

; configure timers
movlw b'11000000' ; configure Timer0:
; --0----- timer mode (TMR0CS = 0)
; ----0--- prescaler assigned to Timer0 (PSA = 0)
; -----000 prescale = 2 (PS = 000)
banksel OPTION_REG ; -> increment TMR0 every 8 us
movwf OPTION_REG ; -> TMR0 overflows every 2.048 ms

movlw b'01000001' ; configure Timer1:


; 01------ processor clock (TMR1CS = 01)
; --00---- no prescaler (T1CKPS = 00)
; -------1 enable Timer1 (TMR1ON = 1)
banksel T1CON ; -> increment TMR1 every 1 us
movwf T1CON

; configure ADC
movlw b'00000000' ; configure ADC:
; 0------- MSB of result in ADRESH<7> (ADFM = 0)
; -000---- Tad = 2*Tosc (ADCS = 000)
; = 2 us with Fosc = 1 MHz
; -----0-- Vref- is Vss (ADNREF = 0)
; ------00 Vref+ is Vdd (ADPREF = 00)
banksel ADCON1
movwf ADCON1
movlw b'00001001'
; -00010-- select channel AN2 (CHS = 00010)
; -------1 turn ADC on (ADON = 1)
movwf ADCON0
banksel PIE1 ; enable ADC interrupt
bsf PIE1,ADIE

; configure CCP modules


banksel CCPR4L ; load ADC sample period (in us) to CCPR4
movlw LOW ADCPeriod
movwf CCPR4L
movlw HIGH ADCPeriod
movwf CCPR4H
movlw b'00001011' ; configure CCP4:
; ----1011 compare mode, special trigger (CCP4M = 1011)
movwf CCP4CON ; -> reset TMR1 & initiate ADC each period

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 37
© Gooligum Electronics 2015 www.gooligum.com.au

; initialise variables
banksel dig1 ; clear display variables
clrf dig1 ; to ensure no out-of-range values
clrf dig2
clrf mpx_cnt ; mpx_cnt = 0 -> display digit 1 first

; enable interrupts
movlw 1<<GIE|1<<PEIE|1<<TMR0IE ; enable Timer0, peripheral
movwf INTCON ; and global interrupts

;***** Main loop


main_loop
; do nothing
goto main_loop

;***** INTERRUPT SERVICE ROUTINE ****************************************


ISR CODE 0x0004

pagesel $ ; select current page for gotos

; *** Identify interrupt source


btfsc INTCON,TMR0IF ; Timer0
goto t0_int
banksel PIR1 ; ADC
btfsc PIR1,ADIF
goto adc_int
goto isr_end ; none of the above, so exit

t0_int ; *** Service Timer0 interrupt


;
; TMR0 overflows every 2.048 ms
;
; Displays current ADC result (in hex) on 7-segment displays
;
bcf INTCON,TMR0IF ; clear interrupt flag

; Display current ADC result (in hex) on 3 x 7-segment displays


; mpx_cnt determines current digit to display
;
banksel mpx_cnt
incf mpx_cnt,f ; increment mpx_cnt for next digit
movf mpx_cnt,w ; and copy to W
; determine current mpx_cnt by successive subtraction
addlw -1
btfsc STATUS,Z ; if current mpx_cnt = 0
goto dsp_dig1 ; display digit 1
clrf mpx_cnt ; else mpx_cnt = 1, so reset to 0
goto dsp_dig2 ; and display digit 2

dsp_dig1
; display digit 1
banksel dig1 ; get digit 1
movf dig1,w
pagesel set7seg ; then output it
call set7seg
banksel LATA ; enable digit 1 display
bsf DIG1_EN

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 38
© Gooligum Electronics 2015 www.gooligum.com.au

pagesel dsp_end
goto dsp_end

dsp_dig2
; display digit 2
banksel dig2 ; get digit 2
movf dig2,w
pagesel set7seg ; then output it
call set7seg
banksel LATA ; enable digit 2 display
bsf DIG2_EN

dsp_end
pagesel isr_end ; end of digit display routine
goto isr_end ; -> exit interrupt handler

adc_int ; *** Service ADC interrupt


;
; Conversion is initiated by CCP special event trigger,
; every ADCPeriod microseconds
;
bcf PIR1,ADIF ; clear interrupt flag

; copy ADC result to display variables


; (to be displayed by Timer0 handler)
banksel ADRESH ; get digit 1
swapf ADRESH,w ; from high nybble of ADRESH
andlw 0x0F
banksel dig1
movwf dig1
banksel ADRESH
movf ADRESH,w ; get digit 2
andlw 0x0F ; from low nybble of ADRESH
banksel dig2
movwf dig2

goto isr_end

isr_end ; *** Exit interrupt


retfie

;***** LOOKUP TABLES ****************************************************


TABLES CODE

; pattern table for 7 segment display on port A


; RA4 = E, RA1:0 = FG
get7sA brw
retlw b'010010' ; 0
retlw b'000000' ; 1
retlw b'010001' ; 2
retlw b'000001' ; 3
retlw b'000011' ; 4
retlw b'000011' ; 5
retlw b'010011' ; 6
retlw b'000000' ; 7
retlw b'010011' ; 8
retlw b'000011' ; 9

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 39
© Gooligum Electronics 2015 www.gooligum.com.au

retlw b'010011' ; A
retlw b'010011' ; b
retlw b'010010' ; C
retlw b'010001' ; d
retlw b'010011' ; E
retlw b'010011' ; F

; pattern table for 7 segment display on port C


; RC4:1 = CDBA
get7sC brw
retlw b'011110' ; 0
retlw b'010100' ; 1
retlw b'001110' ; 2
retlw b'011110' ; 3
retlw b'010100' ; 4
retlw b'011010' ; 5
retlw b'011010' ; 6
retlw b'010110' ; 7
retlw b'011110' ; 8
retlw b'011110' ; 9
retlw b'010110' ; A
retlw b'011000' ; b
retlw b'001010' ; C
retlw b'011100' ; d
retlw b'001010' ; E
retlw b'000010' ; F

; Display digit passed in W on 7-segment display


set7seg
; disable displays
banksel LATA ; clear all digit enable lines
clrf LATA ; on PORTA
clrf LATC ; and PORTC

; output digit pattern


movwf temp ; save digit
call get7sA ; lookup pattern for port A
movwf LATA ; then output it
movf temp,w ; get digit
call get7sC ; then repeat for port C
movwf LATC
return

END

Conclusion
This has been a long lesson, but as we’ve seen, the CCP modules’ capture and compare modes are useful in a
number of situations where accurate timing is required, from measuring short input signal periods and pulse
widths, to driving precisely-timed output changes, and in generating regular interrupts and analog-to-digital
conversions with an arbitrary period.
You may not need to use these capabilities every day, but when you do, you’ll be glad to have the capture
and compare modes available.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 40
© Gooligum Electronics 2015 www.gooligum.com.au

However, the CCP module’s pulse-width modulation (PWM) modes, are arguably more commonly used,
providing (in conjunction with filtering) a form of analog output, especially useful for applications such as
light dimming and motor control, as we’ll see in the next lesson.

Enhanced Mid-Range PIC Assembler, Lesson 17: CCP, part 1 – Capture and Compare Page 41

You might also like