# Can CCP do this on 16F876A? :(

Discussion in 'Microcontrollers' started by bjox1, Mar 7, 2008.

1. ### Nigel GoodwinSuper ModeratorMost Helpful Member

Joined:
Nov 17, 2003
Messages:
39,214
Likes:
640
Location:
Derbyshire, UK
I understood you before, but as you will be using different frequencies the absolute time of the high pulse doesn't provide you enough information, you need to know the time of the low pulse as well, so you can calculate the lag in degrees. Knowing the frequency would do the same thing, and by measuring both high and low, you are in effect measuring the frequency.

2. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Guys I need ur valuable guidance in this case. I was wondering if it is at all possible to compare the XOR output to the constant PIC clock??

What I am trying to do is input the XOR waveform into PIC and run 4MHz clock with it. Thus a 4MHz clock will be constant irrespective of the input waveform. If I compare both waveforms and get the time difference [pulse width], I might achieve the task.

For instance, if the two signals are of 10kHz freq i.e. 277.7 nsec will represent 1 deg of difference. That is, the XOR pulse width representing 1 deg [ON time of pulse], which in freq is 3.6 MHz. Now if I compare it to the constant running 4MHz clock, I can get the time difference which is the phase.

For the other case if the signals are now at 1kHz freq, i.e. 2.77 us will represent the smallest 1 deg difference. So the XOR pulse width representing 1 deg in freq will be 360 kHz. Again comparing it to the same constant 4MHz PIC clock can give us the difference.

I honestly have no idea and I may be wrong in assuming all this. The advantage will be that the comparison will always be with a 4MHz crystal, independent of the signal frequency.

I will greatly appreciate if you could help me implementing this solution [if you think it's at all possible to achieve].

I will appreciate any suggestions on this.

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK

Thanks!

Joined:
Jan 12, 1997
Messages:
-
Likes:
0

5. ### Nigel GoodwinSuper ModeratorMost Helpful Member

Joined:
Nov 17, 2003
Messages:
39,214
Likes:
640
Location:
Derbyshire, UK

Check the spec on the CCP module, you certainly can't get 277nS resolution with a 4MHz clock.

6. ### Super_voipNew Member

Joined:
Jul 2, 2007
Messages:
340
Likes:
1
Location:
Brisbane, Australia
Sounds like a job for a PLL (Phase Locked Loop) using the reference signal the PLL will output a signal proportional to the phase error. PLL are used in things like instrument tuners, indicating whether a played note is higher or lower than the reference.

7. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Nigel,
Mate if I use a 20MHz XT?? Is it possible to do it in this way?? if so then how can I implement in a code?

I will really really appreciate that mate.

Thanks again for ur time.

PS:
Nigel,

Could you explain this once again ?or probably how I can write it in code.

Cheers

Hi,
PLL outputs a current signal. I am not sure how to input it to my PIC for finding a phase.

Last edited: Mar 10, 2008
8. ### Nigel GoodwinSuper ModeratorMost Helpful Member

Joined:
Nov 17, 2003
Messages:
39,214
Likes:
640
Location:
Derbyshire, UK
Running the PIC at 20MHz, the CCP module should have 200n resolution, use that to measure the width of the pulse from an XOR gate.

9. ### mike50New Member

Joined:
Jun 29, 2007
Messages:
103
Likes:
2
Location:
Rochester, Minnesota
The 16F876 has two CCP modules. Why not use one module to Capture the leading edge of one of the signals and the other module to Capture the leading edge of the other. Then you don't need any external XOR function; you can compute the frequency and the phase directly with the 200 ns resolution.

Mike

• Like x 1
10. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Mike,

Mate it sounds perfect but I haven't got that much experience in writing this sort of code. I have done a few LED blinking codes and can attempt this CCP if I get a little bit of help.

Mike, Sorry for being so slow but could you check my following steps:

1) Configure CCP [capture] for the reference signal.
2)Configure CCP for the delay signal.

But what do I have to write in my code to get the phase value which will be independent of the signal frequency??

I would appreciate any help on this.

Last edited: Mar 11, 2008
11. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Anyone up for the kind help??

Ps:

Heyy guys,

I found this nice document[attached with this post] for measuring the width of a signal [in my case, XOR output]. I will be grateful if you could help me in writing the steps described in the document.

Many many thanks guys.

<edit>

#### Attached Files:

• ###### CCP.jpg
File size:
44.9 KB
Views:
120
Last edited: Mar 12, 2008
12. ### mike50New Member

Joined:
Jun 29, 2007
Messages:
103
Likes:
2
Location:
Rochester, Minnesota
1. Run the PIC at 20 Mhz.
2. You need to configure Timer1 to count instruction cycles (200 ns).
3. You need to setup CCP1 to capture the leading edge of signal 1.
4. You need to setup CCP2 to capture the leading edge of signal 2.
5. Each time a capture occurs save the captured time from CCP1 and/or CCP2 (whichever caused the interrupt)
6. Each time you get an interrupt for a capture of CCP1, subtract the previous value that you captured from CCP1. This will give you the time between leading edges of the signal (in units of 200 ns)
7. 5,000,000 divided by this time will give the frequency in Hz.
8. You could do the same with the times from CCP2 to verify that the frequencies are the same.
9. Subtracting the most recent CCP2 time from the most recent CCP1 time will give you the time between the edges of the two signals. Take this time interval and multiply it by 360 and then divide by the period (the time you measured in step 5 above) and you will have the phase angle in degrees.

This technique will work for signals down to about 77 Hz. Below that, Timer1 will overflow between pulses and you will get incorrect values. You don't ever have to stop Timer1, just let it run free. It is okay for Timer1 to overflow as long as it doesn't overflow more than once between captures on either timer....and that won't happen for frequencies above 77 Hz.

Mike

13. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Mike,

I have no words to thank you for being so down-to-earth and for being so helpful.

Just before I try this, is there any example code anywhere similar to this??
I mean it would really help me in learning [I really don’t know how to use an interrupt to capture the time].

As u have suggested that there will be two capture times for signal1 i.e. CCP1a for the first rising edge and CCP1b for the second rising edge. And then CCP2 will be used to get the time for the first rising edge on signal2.

My question is, do I need to use two different Timers for this??

i.e. -Timer1 [T1] will be clear at the first rising edge of signal1 and it will count till the next rising edge arrives. This gives the value T1 [or period of the signal1].

- Now as soon as we move to Pin2 for CCP2, the second counter [T2] will start counting till the next rising edge on signal2.

Lastly - T2 / T1 will be my phase value in time.

Please please suggest if I am on the right track??

Cheers Mike.

14. ### mike50New Member

Joined:
Jun 29, 2007
Messages:
103
Likes:
2
Location:
Rochester, Minnesota
You wouldn't have to use an interrupt. Your main application could just stay in a loop watching the CCP1IF bit in PIR1 and the CCP2IF bit in PIR2. When CCP1IF changes to 1, then read out the CCPR1H and CCPR1L to get the captured value. Then turn off the CCP1IF bit in PIR1. Likewise, when CCP2IF changes to 1, then read out the CCPR1H and CCPR1L to get that captured value. Don't forget to turn off the CCP2IF bit in PIR2.
Well, you can't use two timers. Both CCP1 and CCP2 capture modes use Timer1. But that works fine for you.
Timer1 just runs all the time. Whatever value it has when you capture the rising edge of signal1 is the start time. Later when you capture signal1's rising edge again you will get some other value. The difference between these is the time for one cycle. It doesn't matter where Timer1 starts nor whether it overflows (as long as the total time is less than 65,536 * 200ns - which is why 77 Hz is the lowest frequency you could capture).
You don't "move" to CCP2, both CCP modules are capturing all the time. At any time, the most recent CCP1 and CCP2 captured values will represent the time between the leading edges of the two signals.
Imagine that you keep the last two times captured from CCP1 in SIG1x and SIG1y (with SIG1y being the most recent) and that you keep the last time captured from CCP2 in SIG2. Then right after capturing CCP2 in SIG2 you can compute

frequency = 5,000,000 / (SIG1y - SIG1x)
phase = 360 * (SIG2 - SIG1y) / (SIG1y - SIG1x)

Of course, to work with numbers this big you will need to use 32-bit variables.

Mike

15. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Mike,

Mate you don't realise how you have helped. Thanks again pal.

After reading the datasheet [Timer1 section] I am a little bit confused on the following things:

-I found out that the max Timer1 value is FFFFh. But I don't know if I need to set the Timer1 as a timer or as a counter??

- Also, do I need an additional external XT for this??
- what will be the prescaler value to run it free.
- Do I need an interrupt service routine in my code??

Could you please check my settings below:
For PIC-16F876A
T1CON
b'00000001'

Am I correct? Please enlighten me Mike.

Thanks again Mike.

Last edited: Mar 13, 2008
16. ### mike50New Member

Joined:
Jun 29, 2007
Messages:
103
Likes:
2
Location:
Rochester, Minnesota
You need to set Timer1 as a timer. The T1CON value you came up with is correct. This will cause Timer1 to increment on every instruction cycle. If you are running the PIC at 20 MHz, then Timer1 will increment every 200 ns.

You don't need an external crystal specifically for Timer1 because you are running it in Internal Clock mode. You do need a 20 MHz crystal to connect to the OSC1 and OSC2 pins to clock the chip. (and don't forget that you need appropriate capacitors too) - see Figure 14-1 and Table 14-2 in section 14.2 of the PIC16F87XA datasheet.

The prescaler is set to 1:1 by the T1CON register value you selected. That is what you want, as you want Timer1 to increment on every instruction clock.

As I said in my previous note, you don't need an interrupt service routine. You can just "poll" the CCP1IF and CCP2IF bits in a mainline loop. When you see either of those bits come on, you copy out the capture value from the corresponding registers. Whether you use interrupts or not, you need to turn the CCP1IF and CCP2IF bits off to wait for the next capture.

The arithmetic to compute the difference between two capture values needs to be done modulo 2^16. Which means you want to do that using 16-bit unsigned integers. That is required to make sure you get the right results if a Timer1 overflow occurs between the two captures.

Imagine that you capture the value F120h from CCP1 and then later capture 0240h from CCP1. Notice that a Timer1 overflow occurred between those two captures. You will compute the time between these two events as:

time = 0240h - F120h

doing that in 16-bit variables will give you 1120h which is in fact the number of 200ns clock ticks between Timer1 values of F120h and 0240h. If you did this arithmetic in 32-bit variables you would get the result FFFF1120h which wouldn't be what you want. As long as no more than one Timer1 overflow occurs between captures you will get the right answer.

Of course when you do further calculations such as:

frequency = 5000000 / time

you want to use 32-bit variables because 5000000 doesn't fit in a 16-bit variable. It is just when you are subtracting two captured time values that 16-bit arithmetic is required. Or alternatively you could just do:

time = ( SIG1y - SIG1x ) & FFFFh

this works with 32-bit variables too. BTW, you haven't said what language you will be writing the program in. It is easier to talk about this knowing the language you will be using.

Mike

Last edited: Mar 13, 2008
17. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Hey Mike,

I have no words to thank you again. You are just like an angel sent.

Mate, I am using ASM language. I spent last night figuring out how to do a 16 bit subtraction in assembly. I found a few help links on the internet to do that.I have done that successfully.

I am all puzzled about the division now.

Mike I will be grateful if you could double check the following steps:

- Setup CCP1CON for the rising edge. [Set input pins as well]
- Check for CCP1IF flag to set.
- Save the capture time T1H and T1L in Memory reg.[This is CCP1a]
- NOW setup CCP1CON for the falling edge.
- Check again for CCP1IF flag to set.
- Save the capture values again. [This is CCP1b]

- Do 16 bit subtraction and save the result in 16 bit memory reg.

Mike, I am all stuck after this . Please advise on this:

- Now at this time Timer1 will still be running so setup CCP2CON for the rising edge on CCP2 pin.
- Save CCP2 capture value.
- Thus do CCP2 - CCP1b.

Am I on the right track mike??

I really appreciate all ur suggestions.

Thanks again.

Last edited: Mar 14, 2008
18. ### mike50New Member

Joined:
Jun 29, 2007
Messages:
103
Likes:
2
Location:
Rochester, Minnesota
You might take a look at http://www.piclist.com/techref/microchip/math/basic.htm You will find lots of code for doing arithmetic. For divide I would use a 32 by 16 bit divide such as http://www.piclist.com/techref/microchip/math/div/32by16ph.htm And for the multiply try http://www.piclist.com/techref/microchip/math/mul/16x16umalin.htm which does a 16-bit by 16-bit multiply with a 32-bit result.
You do not want to change CCP1CON to look for the falling edge. Once you set it up to look for leading edges, leave it alone. The time you want is the time between two successive leading edges.

You want something like this:
Code (text):
- Configure the pins - disable analog inputs and set TRIS regs
- Setup CCP1CON for the rising edge.
- Setup CCP2CON for the rising edge.
- Set CCP1IH to 0
- Set CCP2IH to 0

- outer loop

- Inner loop 1
- If CCP1IH = 0 then goto Inner loop 1

- Copy CCPR1H to CCP1aH
- Copy CCPR1L to CCP1aL
- Set CCP1IH = 0

- Inner loop 2
- If CCP1IH = 0 then goto Inner loop 2

- Set CCP2IH = 0  ; make sure to capture CCP2 after second CCP1 capture
- Copy CCPR1H to CCP1bH
- Copy CCPR1L to CCP1bL
- Set CCP1IH = 0

- Inner loop 3
- If CCP2IH = 0 then goto Inner loop 3

- Copy CCPR2H to CCP2aH
- Copy CCPR2L to CCP2aL
- Set CCP2IH = 0

- time = CCP1b - CCP1a  (use 16-bit subtract)
- frequency = 5000000 / time  (use 32 by 16 divide - since 5000000 won't fit in 16 bits)
- phase = 360 * ( CCP2a - CCP1b ) / time  (use 16 bit subtract, then 16-bit by 16-bit multiply, then 32-bit by 16-bit divide)
- output frequency and phase as necessary
- goto outer loop

As I showed above, do all the setup at the start.

19. ### bjox1New Member

Joined:
Mar 7, 2008
Messages:
38
Likes:
0
Location:
UK
Mike,

I am really indebted to you for all ur help and guidance!

I am just going to try the code with all ur suggestions.

Mike, Just to get some handy experience with the capture module, I have written my code which measures the width of a TTL signal. It does the following things:

{My setup-: PIC 16F877A [I am using this PIC], with 4MHz XT and the CCP1 input is RC2 pin}

-Set CCP1 pin.
-Setup Timer1.
-Setup CCP1CON for the rising edge.
-As soon as CCP1IF flag is set, Save the value to H2 and L2 reg [these values are smaller than the next capture values]
-clear CCP1IF and setup CCP1CON for falling edge.
- do the same, get H1 and L1 values.
- Subtract L2 from L1, Propagate borrow and sub H2 from H1.
-Light LEDs [8 on each port] on PORTB and PORTD.

But somehow it doesn't show me anything on the output. I have already checked the SUBTRACT routine and it works fine.

Thanks again Mike. I really appreciate it.

Code (text):
list p=16f877a

include "p16f877a.inc"

__config _LVP_OFF & _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BODEN_OFF & _DEBUG_OFF

cblock  0x020           ;start of general purpose registers
H1
H2
L1
L2
AnsH
AnsL
endc
; Start at the reset vector
org     0x000
goto    Start

Start   org     0x010
clrf    PORTB
clrf    PORTD
clrf    PORTC
clrf    H1
clrf    H2
clrf    L1
clrf    L2
clrf    AnsH
clrf    AnsL
clrf    TMR1H
clrf    TMR1L
;   clrf    CCP1CON
clrf    CCPR1H
clrf    CCPR1L
bsf     STATUS,RP0      ;bank 1
bcf     STATUS,RP1
movlw   b'00000100'
movwf   TRISC
movlw   b'00000000'
movwf   TRISB           ;portb [7-0] outputs
movlw   b'00000000'
movwf   TRISD           ;portd output
bcf     STATUS,RP0      ;bank0
;   bcf     PIR1,CCP1IF
;**********************************************

Main:       movlw   b'00000001'      ;timer 1 using to capture, prescaler 1:1
movwf   T1CON
;bsf    T1CON,TMR1ON

movlw   b'00000101'
Wait:
btfss   PIR1,CCP1IF
goto    Wait
movf    CCPR1H,H2       ;save the value in H2 and L2 {lower value}
movf    CCPR1L,L2
bcf PIR1,CCP1IF
;
movlw   b'00000100'           ;config CCP1 to faling
movwf   CCP1CON
Wait1:
btfss   PIR1,CCP1IF
goto    Wait1

movf    CCPR1H,H1       ;save now the value in H1,L1
movf    CCPR1L,L1
bcf     PIR1,CCP1IF     ;clr flag CCP1

;SUB lower value H2,L2 from current value H1,L1

SUB:
movf    L2,W
subwf   L1,W
movwf   AnsL
movwf   PORTB

btfss  STATUS, C
goto   BORROW
goto   SUB_1
BORROW:
decf   H1, F

SUB_1:  movf    H2,W
subwf   H1,W
movwf   AnsH
movwf   PORTD
;   goto    Main

;************************************************

end

Last edited: Mar 14, 2008
20. ### PommieWell-Known MemberMost Helpful Member

Joined:
Mar 18, 2005
Messages:
10,014
Likes:
317
Location:
Brisbane Australia
ONLINE
You cannot do move file to file. MPASM is really lax in this area and should give an error but unfortunalely doesn't.

These instructions won't work,
Code (text):

movf    CCPR1H,H1       ;save now the value in H1,L1
movf    CCPR1L,L1

You have to do,
Code (text):

movf    CCPR1H,W        ;save now the value in H1,L1
movwf   H1
movf    CCPR1L,W
movwf   H1

Mike.

21. ### mike50New Member

Joined:
Jun 29, 2007
Messages:
103
Likes:
2
Location:
Rochester, Minnesota
You really need to pay attention to the errors and warnings you get when you assemble (build) the program. The first three you get are okay...they are just warning you that you are accessing memory outside of bank 0...but you have done the banking correctly.

The next four errors you got "Argument out of range" are real errors in your program. These really should have been "errors" not "warnings".

You have misinterpreted what the MOVF instruction does. At the first of these errors you coded:

Code (text):
movf    CCPR1H,H2       ;save the value in H2 and L2 {lower value}
thinking that this will move data from CCPR1H to H2. But these PICs are not that clever. They can only move data to and from the W register. So you need two instructions at each of these four places. At this first place you would code:

Code (text):
movf    CCPR1H,w                ; copy the value into the W reg
movwf   H2                      ; and then into H2

Alternatively you could code:
Code (text):
movfw   CCPR1H                  ; copy the value into the W reg
movwf   H2                      ; and then into H2

You need similar code at the other three similar warnings.

You should always reset CCP1IH after setting CCP1CON. See section 8.1.3 of the datasheet.

What do you suppose the chip will do when your program reaches your last instruction? That "end" directive isn't actually an instruction, it just tells the assembler that there is no more code. The CPU will go on executing instructions - whatever it finds there. Most likely it will end up running your whole program over again - but it is hard to say. If you just want it to end there, then you should code something like:

Code (text):
loop    goto loop
It is really useful to learn to use the MPLAB SIM debugger. That lets you step through your code and even simulate inputs on the pins. You could set up a simulation of your whole frequency/phase prloblem and step through the code to see that it is working.

Mike