CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Determining square-wave frequency input into pic
Goto page 1, 2, 3, 4  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
totalnoob



Joined: 22 Oct 2006
Posts: 24

View user's profile Send private message

Determining square-wave frequency input into pic
PostPosted: Fri Feb 23, 2007 4:06 pm     Reply with quote

Ok, I'm trying to read a square-wave frequency into a PIC 16F877A and determine what the frequency is. The square wave is a standard 0-5V wave with 50% duty cycle, the only variable is the frequency. I need to be able to see what the frequency is and check it against a set frequency to determine what to do next in my program. I know that I can use the CCP module in capture mode to read in a frequency but I'm really unfamiliar with CCS and PIC programming, if anyone has done this sort of thing before and thinks they can help I would really appreciate it.
Ttelmah
Guest







PostPosted: Sat Feb 24, 2007 8:08 am     Reply with quote

Look at EX_FREQ.C

Best Wishes
Ttelmah
Guest







PostPosted: Sat Feb 24, 2007 8:09 am     Reply with quote

Make that EX_FREQC.C... Smile
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Sun Feb 25, 2007 5:36 pm     Reply with quote

Here is a demo program that shows how to use the CCP to measure
the frequency of rectangular waveform. I used a PicDem2-Plus board
with a 16F877 and a 4 MHz crystal to test this program. It was tested
with compiler vs. 3.249. Any board that is similar to PicDem2-Plus
can be used. You need a trimpot connected to pin RA0, which is
standard on the PicDem2-Plus

To run this program, you need to jumper pins C1 and C2 together.
This connects the output of the CCP2 to the input of CCP1.

This program has two sections. One section uses CCP2 to generate a
test signal, which can be varied in frequency from 244 Hz to 2016 Hz
by turning the trimpot. The 2nd section uses CCP1 to measure the
period of this signal. Then this period is converted into a frequency
and the result is displayed on a terminal window every 1/2 second.

For example, if I start the program and turn the trimpot from one
side to the other, this is the output that I see. It doesn't show all
the intermediate values because I didn't turn it slowly enough to
do that.
Quote:

244 Hz
244 Hz
244 Hz
277 Hz
355 Hz
355 Hz
416 Hz
534 Hz
672 Hz
856 Hz
2016 Hz
2016 Hz


The methods shown in this post could be used for a basic
tachometer program. There are many other aspects that
I haven't put in this post such as the CCP1 prescaler and
extending the 16-bit Timer to 24-bits to allow more range,
and then filtering the results to reduce 'bobble' in the
displayed output. My purpose is to post a basic program
which will get you started. You should read the data sheet
and add any other desired features.

Here is the demo program:
Code:

#include <16F877.H>
#fuses XT, NOWDT, NOPROTECT, BROWNOUT, PUT, NOLVP
#use delay(clock=4000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)

// This global variable holds the time interval
// between two consecutive rising edges of the
// input signal.   
int16 isr_ccp_delta;

// When a rising edge occurs on the input signal,
// the CCP1 will 'capture' the value of Timer1
// at that moment.  Shortly after that, a CCP1
// interrupt is generated and the following isr
// is called.   In the isr, we read the 'captured'
// value of Timer1.  We then subtract from it the
// Timer1 value that we 'captured' in the previous
// interrupt.  The result is the time interval
// between two rising edges of the input signal.
// This time interval is then converted to a frequency
// value by code in main(). 
#int_ccp1
void ccp1_isr(void)
{
int16 current_ccp;
static int16 old_ccp = 0;

// Read the 16-bit hardware CCP1 register
current_ccp = CCP_1;  // From 16F877.H file

// Calculate the time interval between the
// previous rising edge of the input waveform
// and the current rising edge.  Put the result
// in a global variable, which can be read by
// code in main().
isr_ccp_delta = current_ccp - old_ccp;

// Save the current ccp value for the next pass.
old_ccp = current_ccp;
}


//=======================
void main()
{
int8 duty;
int8 pr2_value;
int16 current_ccp_delta;
int16 frequency;


// Setup the ADC so we can read a value from 0 to 255
// as we turn a trimpot which is connected to pin A0.
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_DIV_8);  // Divisor for 4 MHz crystal
set_adc_channel(0); 
delay_us(20);

// The CCP2 will be used in PWM mode to output a test
// signal that will be connected to the CCP1 pin.
// The test signal can be varied from 244 Hz to 2016 Hz
// by turning the trimpot on pin A0.
setup_ccp2(CCP_PWM);

// Setup Timer1 and CCP1 for Capture mode so that
// we can measure the input signal's frequency.
// The input signal comes from the CCP2 pin, which
// is connected to the CCP1 pin with a wire.
set_timer1(0);           
setup_timer_1(T1_INTERNAL | T1_DIV_BY_1); 
setup_ccp1(CCP_CAPTURE_RE);   

// Clear the CCP1 interrupt flag before we enable
// CCP1 interrupts, so that we don't get an unwanted
// immediate interrupt (which might happen).
clear_interrupt(INT_CCP1);
enable_interrupts(INT_CCP1);
enable_interrupts(GLOBAL);


while(1)
  {

   // Read the A/D value from the trimpot.
   // This is a value from 0 to 255.  We limit
   // it to a minimum of 30 for the purposes
   // of this test program.  This will limit the
   // test signal to 2016 Hz maximum frequency.
   pr2_value = read_adc();
   if(pr2_value < 30)
      pr2_value = 30;

   // Configure CCP2 to put out a rectangular
   // waveform with approximately a 50% duty
   // cycle.  The setting of the trimpot controls
   // the frequency of this waveform.
   setup_timer_2(T2_DIV_BY_16, pr2_value, 1);
   duty = pr2_value / 2;
   set_pwm2_duty(duty);

   
   // Now calculate the frequency. 

   // Get a local copy of the latest ccp delta from the isr.
   // We have to disable interrupts when we read a global
   // isr variable that is larger than a single byte.
   disable_interrupts(GLOBAL);
   current_ccp_delta = isr_ccp_delta;
   enable_interrupts(GLOBAL);

   // To calculate the frequency of the input signal,
   // we take the number of clocks that occurred
   // between two consecutive edges of the input signal,
   // and divide that value into the number of Timer1
   // clocks per second.   Since we're using a 4 MHz
   // crystal, the Timer1 clock is 1 MHz (Timer1 runs
   // at the instruction cycle rate, which is 1/4 of the
   // crystal frequency).  For example, suppose the
   // the input waveform has a frequency of 244 Hz.
   // 244 Hz has a period of about 4098 usec.
   // Timer1 is clocked at 1 MHz, so between two
   // consecutive rising edges of the input signal,
   // it will count up by 4098 clocks.  To find the
   // frequency, we divide 4098 into the number of
   // clocks that occur in 1 second, which is 1000000.
   // This gives 1000000 / 4098 = 244 Hz.
   
   frequency = (int16)(1000000L / current_ccp_delta);

   // Display the calculated frequency.
   printf("%lu Hz\n\r", frequency);

   delay_ms(500);
  }

}
totalnoob



Joined: 22 Oct 2006
Posts: 24

View user's profile Send private message

PostPosted: Mon Feb 26, 2007 3:02 pm     Reply with quote

Thanks a lot for all the help, I appreciate it. I'll give it a try and see what I can get working.
Micahel
Guest







Disabling interrupt when reading a global ISR variable
PostPosted: Wed Mar 28, 2007 3:47 pm     Reply with quote

Hi,

I was reading this thread and saw your comments on the following codes, Please explain why do you need to disable global interrupt when you try to copy a global variable that is larger than a byte.



// Get a local copy of the latest ccp delta from the isr.
// We have to disable interrupts when we read a global
// isr variable that is larger than a single byte.
disable_interrupts(GLOBAL);
current_ccp_delta = isr_ccp_delta;
enable_interrupts(GLOBAL);


Regards, MY
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Wed Mar 28, 2007 4:29 pm     Reply with quote

The reason is because it takes more than one instruction to copy a
16-bit variable. An interrupt can occur between instructions. So the
isr code could change the 16-bit variable in mid-copy. The first byte
that you copy would be from the previous value of the 16-bit variable,
and the 2nd byte would be from the new value that occurred during
the latest interrupt.

Here are the 4 lines of ASM code that are required to copy a 16-bit
variable. This code is in main():
Code:

016B:  MOVF   isr_ccp_delta+1,W     // Copy MSB
016C:  MOVWF  current_ccp_delta+1
016D:  MOVF   isr_ccp_delta,W       // Copy LSB
016E:  MOVWF  current_ccp_delta


For example, suppose you have an isr that increments a 16-bit value
every time you get an interrupt. Here are a few values, to show how
it increments:
Quote:

0x00FE
0x00FF
0x0100
0x0101

Suppose you're copying the isr variable in code in main(), and it happens
to be 0x00FF. Suppose that you didn't disable interrupts. Your code in
main() could copy the MSB (0x00) and then you get an interrupt. During
the interrupt the 16-bit variable goes from 0x00FF to 0x0100. When
the interrupt code finishes, and resumes executing the code in main(),
the remaining instructions will execute, and the LSB will be copied. Only
now, because the count has rolled over to 0x0100, the LSB is 0x00.
So the value that you copied is 0x0000, when it should be 0x00FF.
It's been corrupted.

This can be prevented by disabling interrupts temporarily while you copy
the 16-bit isr value. This also applies to any variable that is updated in
an interrupt service routine, and that's larger than a byte, such as an
int32 or a float.
michael
Guest







Disbaleing interrupt when reading a global ISR varibale
PostPosted: Wed Mar 28, 2007 5:46 pm     Reply with quote

Thanks for the explanation. Regards, MY
acidice333



Joined: 14 Oct 2006
Posts: 33

View user's profile Send private message

PostPosted: Sun Apr 08, 2007 3:13 pm     Reply with quote

Good demo.
How can I clear it rather than hold it when the signal is removed?
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Sun Apr 08, 2007 4:19 pm     Reply with quote

To do that, you need to do these things:

1. Declare a global variable to indicate that a CCP interrupt occured.

2. Inside the CCP isr, set the global variable = TRUE.

3. At start of main(), set it = FALSE.

4. Then, down at bottom of the main while() loop, change the code
that displays the frequency so that it checks this global flag.
If the flag is TRUE, then display the frequency. If not, then display
a message indicating there's no signal present. Also, if the flag
is TRUE, then clear it.

Here's the modified code:
Code:

if(gc_capture_flag == TRUE)
  {
   frequency = (int16)(1000000L / current_ccp_delta);
   
   // Display the calculated frequency.
   printf("%lu Hz    \n\r", frequency);
   gc_capture_flag = FALSE;
  }
else
  {
   printf("No signal\n\r", frequency);
  }

I added a few spaces after the "Hz" so that if this was being displayed
on an LCD, it would be sure to overwrite the "No signal" text if that was
present.
acidice333



Joined: 14 Oct 2006
Posts: 33

View user's profile Send private message

PostPosted: Mon Apr 23, 2007 7:05 pm     Reply with quote

What is needed to change for this to work on a 48mhz clock? (PLL 20mhz)
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Apr 24, 2007 2:56 pm     Reply with quote

I assume you have a board with a PIC such as the 18F4550 that you
want to run at 48 MHz, with a 20 MHz crystal. Your fuses statement
should look like this:
Quote:

#include <18F4550.H>
#fuses HSPLL, PLL5, CPUDIV1, NOWDT, PUT, BROWNOUT, NOLVP
#use delay(clock=48000000)


The minimum number of changes required to make the tachometer demo
program work at 48 MHz are:

1. Change the ADC divisor to 64:
Quote:
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_DIV_64); // Divisor for 48 MHz oscillator
set_adc_channel(0);
delay_us(20);


2. Change the frequency calculation equation, to account for the
oscillator running 12 times faster than in the original example.
Change the numerator from 1 million to 12 million.
Quote:
frequency = (int16)(12000000L / current_ccp_delta);


Because of the higher 48 MHz oscillator frequency, the minimum PWM
frequency is much higher than the original example. So when you
turn the trimpot, the frequency range will be from 2929 Hz minimum,
to a maximum of 24193 Hz. But the program still works OK and you
will see the correct frequency displayed in the terminal window.
acidice333



Joined: 14 Oct 2006
Posts: 33

View user's profile Send private message

PostPosted: Tue Apr 24, 2007 6:22 pm     Reply with quote

Thanks I thought I did something wrong but its all correct.

I'm having a wierd probem- with it operating at 4mhz it seems fine and at 48mhz the reading spikes and is never stable. I will try running it at 20mhz tommorow.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Apr 24, 2007 6:26 pm     Reply with quote

What's your PIC and your compiler version ?
acidice333



Joined: 14 Oct 2006
Posts: 33

View user's profile Send private message

PostPosted: Tue Apr 24, 2007 6:57 pm     Reply with quote

18f2550, and v4.009

4mhz crystal, with it set to 4mhz works fine.
4mhz crystal, with it set to 24mhz doesn't work (spikes)
20mhz crystal, with it set to 48mhz doesn't work (spikes)

Im measuring a frequency from a 4n25, ranging from 25hz to 200hz
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2, 3, 4  Next
Page 1 of 4

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group