PIC18F47J13 Capture Mode?

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
I have never used capture mode before. I thought this was a way to capture the time difference between two events. But the datasheet says:

Capture Mode
In Capture mode, the CCPR4H:CCPR4L register pair
captures the 16-bit value of the TMR1 or TMR3 register
when an event occurs on the CCP4 pin, RB4. An event
is defined as one of the following:
• Every falling edge
• Every rising edge
• Every 4th rising edge
• Every 16th rising edge

Am I taking this too literally? Is what they are really saying here is the timer registers are updated on the next event with the time from the previous event? Or am I completely off base on the use of capture mode?

Do I need to enable the timer? Is here anything else on the timer I need to configure?
 

JohnInTX

Joined Jun 26, 2012
4,787
All that CAPTURE does is copy the current value of the (hopefully running timer) to the capture register when the first specified event occurs. When the next event occurs, the timer value is again copied, overwriting the previous captured value. To measure the time between 2 events (edges for example) you:
Configure and start the timer.
Enable the capture interrupt
On the first interrupt, copy the capture register to temp storage.
On the next interrupt, subtract the first value (in temp) from the new value in the capture registers to get the number of timer ticks between the events.
Consider what to do if the timer overflows.

You can poll the capture flag (CCPxIF) if you aren’t using interrupts.

The primary reason for using capture is to eliminate the problem of the timer running on past the event, adding counts while you're getting around to detecting the event and reading and resetting the timer.

Have fun!
 
Last edited:

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
All that CAPTURE does is copy the current value of the (hopefully running timer) to the capture register when the first specified event occurs. When the next event occurs, the timer value is again copied, overwriting the previous captured value. To measure the time between 2 events (edges for example) you:
Configure and start the timer.
Enable the capture interrupt
On the first interrupt, copy the capture register to temp storage.
On the next interrupt, subtract the first value (in temp) from the new value in the capture registers to get the number of timer ticks between the events.
Consider what to do if the timer overflows.

You can poll the capture flag (CCPxIF) if you aren’t using interrupts.

The primary reason for using capture is to eliminate the problem of the timer running on past the event, adding counts while you're getting around to detecting the event and reading and resetting the timer.

Have fun!

Ahh that all makes sense now. Do I need to reset the timer? Or just let it overflow and deal with the difference with absolute value. ?
 

JohnInTX

Joined Jun 26, 2012
4,787
Ahh that all makes sense now. Do I need to reset the timer? Or just let it overflow and deal with the difference with absolute value. ?
Depends on the application but you can usually just let it run and do absolute value of the difference in counts as long as the time between events is guaranteed to be less than the total period time of the timer.

You can use the timer overflow interrupt flag to know that the timer has overflowed between captures, too.
 

JohnInTX

Joined Jun 26, 2012
4,787
You should get the most accurate result by letting the timer run.
That's the reason you'd want to go to all of the trouble of calculating differences - the unknown overhead of manipulating the timer.

FWIW, you can extend the time base indefinitely by using also counting counter overflows and using them in the difference calculation:
Let
d be the difference in counts between events and
n be the number of counter overflows between events and
Ktotal be the number of counts between events.

d = capture2 - capture1

if d is positive
Ktotal = d + (n * 2^16)
if d is negative
Ktotal = abs(d) + ( (n-1) * 2^16) )

That might be overdoing it but it takes care of the case where the time between events can be greater than the total timer period while maintaining precision in the timing.
 

jpanhalt

Joined Jan 18, 2008
11,087
Then it seems all you need to do is time each edge (either rising or falling) and do a little math. Considering most motors I have dealt with, say 1000 rpm, you may want to use the 32kHz internal oscillator. 1000 rpm = 16.7 Hz = 60 ms = 1920 counts. As others have said, leave TMR1 running. That's why you use capture.
 

JohnInTX

Joined Jun 26, 2012
4,787
I will likely use the standard CCP. ECCP requires the program of a programmable pin. I would rather not get into that right now.
I would just let the timer free-run. Set it up to capture and interrupt on every rising (or falling) edge. Configure the timer so that the time between edges will fit into the 16 bits of the timer. On each interrupt, compute the number of timer tiks since the previous edge using the abs(difference) method. Guard against very slow RPM by counting timer overflows (another interrupt) between events.

EDIT: @jpanhalt beat me to it.

Good luck!
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
I am so close. But I am not getting the time expected in my code. The yellow trace in the screen shot is the input to my CCP from the sensor. As you can see, the sensor trips every 74.8ms. The cyan line represents what is going on in my interrupt (code included below). Every other interrupt toggles the output of RB4. You can see that both traces correspond exactly. So I know there is a software interrupt occurring for every sensor trip when the timer is captured.

I chose Timer1 for CCP5 and chose the FOSC option (not the FOSC/4) \. The chip is using the internal OSC. I would not expect timing to be exact but I would think it should be close.

But I am not seeing the same time in my software. Perhaps it is my math or my understanding of how things actually work.

My FSOC=8MHZ so that would be a period of 0.000000125.

Here are some sample readings.

t1 = 20007
t2 =32243

Which would give me 12236 ticks per revolution.

If I do the math correctly that is 12236 * 0.000000125 = 0.0015295 or 1.5ms.

Where am I going wrong?

I am using the debugger and a breakpoint to obtain this data. I will try and hook up an LCD panel or terminal connection to see if live data changes anything.



upload_2018-11-1_17-11-19.png

Code:
#include <xc.h>

unsigned char timeCount = 0;
unsigned long t1;
unsigned long t2;

#define ABS(N) ((N<0)?(-N):(N))

void main(void) {
   
   
  OSCCONbits.IRCF0 = 1;
   OSCCONbits.IRCF1 = 1;   
   OSCCONbits.IRCF2 = 1;  
   
   
  T1CONbits.TMR1CS = 1;  // Timer1 clock source is the system clock (FOSC)
  T1CONbits.T1CKPS = 0;  // 1:1 Prescale value
  T1CONbits.RD16 = 0;  // 8 bit clock
   
   
  TRISBbits.TRISB5 = 1;  // RB5 is an output
  TRISBbits.TRISB4 = 0;  // RB4 is an input
   
  //  Capture mode: every rising edge
  CCP5CONbits.CCP5M0 = 0;   
  CCP5CONbits.CCP5M1 = 0;
  CCP5CONbits.CCP5M2 = 1;
  CCP5CONbits.CCP5M3 = 0;
   
  CCPTMRS1bits.C5TSEL0 = 0;  // Select Timer 1 for CCP5
   
  PIE4bits.CCP5IE = 1;  // Enable interrupts on CCP7
  INTCONbits.GIE = 1;   
  INTCONbits.PEIE = 1;   
   
   
  T1CONbits.TMR1ON = 1;  // Timer 1 on
   
   
   
   
  while(1);
  return;
}




__interrupt(high_priority) void interrupts_highPriority(void)
{
   
   
  if (PIR4bits.CCP5IF == 1)
  {
  PIR4bits.CCP5IF = 0;   
  LATBbits.LATB4 = 1;
   
   
  if (timeCount == 0)
  {
  t1 = (unsigned long)(CCPR5H<<8) | CCPR5L;
  timeCount = 1;
  }
  else
  {
  LATBbits.LATB4 = 0;
  t2 = (unsigned long)(CCPR5H<<8) | CCPR5L;
  timeCount = 0;
  long timePerRev = (long)(ABS((t2 - t1)));
  double t = 0.000000125 * timePerRev;
  int x;
  x = timePerRev + 1;
   
  }   
   
   
  }  
   
   
}
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
I am getting some odd results. Why would the results keep changing so much? Is it a result of the overflow?


T1=1170l
T2=42790l
Ticks per rotation=23916

T1=18998l
T2=60626l
Ticks per rotation=23908

T1=36774l
T2=12722l
Ticks per rotation=41484

T1=54502l
T2=30502l
Ticks per rotation=41536

T1=6590l
T2=48178l
Ticks per rotation=23948
 

jpanhalt

Joined Jan 18, 2008
11,087
The values I get, in order, for T2-T1 are:
41620
41628
41484
41536
41588

You also will notice that T2 for one set becomes T1 for the next reading (assuming your data show capture of every falling or rising edge and don't have any starts and stops in the process). Thus, T2(set1) to T1(set2) = 41744 and T2(set2) to T1(set3) = 41684 and so forth.

How constant is the "wheel" you are measuring?
 

jpanhalt

Joined Jan 18, 2008
11,087
@spinnaker
I know you do not do Assembly, but it can be added inline in C.

Here is a snippet from my files for an enhanced mid-range chip (12F1840) that has the subwfb instruction. The 18F's also have a subfwb (effectively the reverse), which should allow you to preserve T2 more easily. I tested a few of your T1,T2 pairs and got the same results as in post #15).
Code:
;SUBWFB EXAMPLE  T2-T1
     #include <p12F1840.inc>
     cblock 0x20
     T1H
     T1L
     T2H
     T2L
     T3H
     T3L
     endc
     radix dec
Start                    ;T2-T1
     movlw     low(36774)
     movwf     T1L
     movlw     high(36774)
     movwf     T1H
     movlw     low(12722)
     movwf     T2L
     movlw     high(12722)
     movwf     T2H
;OPTION 1
;Result in T2, T1 is preserved 
     movf      T1L,w     ;
     subwf     T2L,f     ;
     movf      T1H,w     ;
     subwfb    T2H,f     ;
     nop
;OPTION 2
;Save in W
;Both T1 and T2 preserved, result in T3
     movf      T1L,w     ;
     subwf     T2L,w     ;
     movwf     T3L       ;
     movf      T1H,w     ;
     subwfb    T2H,w     ;
     movwf     T3H       ; 
     nop
     END
John
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
The values I get, in order, for T2-T1 are:
41620
41628
41484
41536
41588

You also will notice that T2 for one set becomes T1 for the next reading (assuming your data show capture of every falling or rising edge and don't have any starts and stops in the process). Thus, T2(set1) to T1(set2) = 41744 and T2(set2) to T1(set3) = 41684 and so forth.

How constant is the "wheel" you are measuring?

With my code?


I did find issues with my code. It seems there is something strange with the way the compiler handles the shift. Here is the updated code. I added some debugging too.

Code:
#include <xc.h>
#include <stdio.h>
#include "uart.h"

unsigned char timeCount = 0;
volatile unsigned  t1;
volatile unsigned  t2;
volatile unsigned  ticksPerRev;
volatile unsigned char interruptTriggered = 0;


#define ABS(N) ((N<0)?(-N):(N))

void displayData()
{
   
  char buffer[100];
  unsigned tmpT1 = t1;
  unsigned tmpT2 = t2;
  unsigned tmpTicksPerRev = ticksPerRev;
   
  sprintf(buffer,"\r\n\r\nT1=%d\r\n",tmpT1);
  uart_putString(buffer);
  sprintf(buffer,"T2=%d\r\n",tmpT2);
  uart_putString(buffer);   
   
  sprintf(buffer,"Ticks per rotation=d\r\n",tmpTicksPerRev);
  uart_putString(buffer);   
   
   
   
  double timerPerRev = ticksPerRev * 0.000000125;
  sprintf(buffer,"\r\n\r\Time per rotation=%d\r\n\r\n",timerPerRev);
  //uart_putString(buffer);
   
   
   
   
   
   
}

void main(void) {
   
  char c;
   
  OSCCONbits.IRCF0 = 1;
   OSCCONbits.IRCF1 = 1;   
   OSCCONbits.IRCF2 = 1;   
   
  T1CONbits.TMR1CS = 1;  // Timer1 clock source is the system clock (FOSC)
  T1CONbits.T1CKPS = 0;  // 1:1 Prescale value
  T1CONbits.RD16 = 0;  //  Enables register read/write of Timer1 in one 16-bit operation
   
   
  TRISBbits.TRISB5 = 1;  // RB5 is an output
  TRISBbits.TRISB4 = 0;  // RB4 is an input
   
  //  Capture mode: every rising edge
  CCP5CONbits.CCP5M0 = 0;   
  CCP5CONbits.CCP5M1 = 0;
  CCP5CONbits.CCP5M2 = 1;
  CCP5CONbits.CCP5M3 = 0;
   
  CCPTMRS1bits.C5TSEL0 = 0;  // Select Timer 1 for CCP5
   
  PIE4bits.CCP5IE = 1;  // Enable interrupts on CCP7
  INTCONbits.GIE = 1;   
  INTCONbits.PEIE = 1;   
   
   
  T1CONbits.TMR1ON = 1;  // Timer 1 on
   
   
   
  uart_open();   
   
  while(1)
  {   
   
  uart_putStringRom("\r\n\r\nTEST Capture MENU\r\n\r\n");
  uart_putStringRom("1. Display Data\r\n\r\n");   
   
   
  while(1)
  {
  if(interruptTriggered == 1)
  {
  interruptTriggered = 0;
  displayData();
  }
   
  }
   
   
   
  }
   
  return;
}




__interrupt(high_priority) void interrupts_highPriority(void)
{
   
   
  if (PIR4bits.CCP5IF == 1)
  {
  PIR4bits.CCP5IF = 0;   
  LATBbits.LATB4 = 1;
   
   
  if (timeCount == 0)
  {
  t1 = CCPR5H;
  t1 = (t1<< 8) | CCPR5L;  
  timeCount = 1;
   
  }
  else
  {
  LATBbits.LATB4 = 0;
  t2 = CCPR5H;
  t2 = (t2<< 8) | CCPR5L;   
   
  unsigned long tmp = t2;
  if (t1 > t2)
  {
  tmp = tmp + 0xFFFF;   
  }   
   
  ticksPerRev = tmp - t1;

  timeCount = 0;
  interruptTriggered = 1;
   
  }   
   
  }  
   
   
}
 

Thread Starter

spinnaker

Joined Oct 29, 2009
7,830
That's the reason you'd want to go to all of the trouble of calculating differences - the unknown overhead of manipulating the timer.

FWIW, you can extend the time base indefinitely by using also counting counter overflows and using them in the difference calculation:
Let
d be the difference in counts between events and
n be the number of counter overflows between events and
Ktotal be the number of counts between events.

d = capture2 - capture1

if d is positive
Ktotal = d + (n * 2^16)
if d is negative
Ktotal = abs(d) + ( (n-1) * 2^16) )

That might be overdoing it but it takes care of the case where the time between events can be greater than the total timer period while maintaining precision in the timing.

Maybe this is my problem? I need to implement this? But how do I know the timer overflowed?
 

jpanhalt

Joined Jan 18, 2008
11,087
With my code?
No, with my handheld calculator, and later with my code. I can't do C.

As for knowing whether the timer overflowed, Timer 1 IF will tell you. But, as my code shows, you don't need to know that so long as it never overflows more than once per sample pair.
 
Top