|
|
View previous topic :: View next topic |
Author |
Message |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Feb 25, 2007 5:36 pm |
|
|
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
|
|
Posted: Mon Feb 26, 2007 3:02 pm |
|
|
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 |
Posted: Wed Mar 28, 2007 3:47 pm |
|
|
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
|
|
Posted: Wed Mar 28, 2007 4:29 pm |
|
|
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 |
Posted: Wed Mar 28, 2007 5:46 pm |
|
|
Thanks for the explanation. Regards, MY |
|
|
acidice333
Joined: 14 Oct 2006 Posts: 33
|
|
Posted: Sun Apr 08, 2007 3:13 pm |
|
|
Good demo.
How can I clear it rather than hold it when the signal is removed? |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun Apr 08, 2007 4:19 pm |
|
|
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
|
|
Posted: Mon Apr 23, 2007 7:05 pm |
|
|
What is needed to change for this to work on a 48mhz clock? (PLL 20mhz) |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Apr 24, 2007 2:56 pm |
|
|
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
|
|
Posted: Tue Apr 24, 2007 6:22 pm |
|
|
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
|
|
Posted: Tue Apr 24, 2007 6:26 pm |
|
|
What's your PIC and your compiler version ? |
|
|
acidice333
Joined: 14 Oct 2006 Posts: 33
|
|
Posted: Tue Apr 24, 2007 6:57 pm |
|
|
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 |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Apr 24, 2007 7:57 pm |
|
|
You want it to work at very low frequencies. That's different from just
making the demo work. It requires adjustments to the pre-scalers.
I'll work on it tomorrow. |
|
|
acidice333
Joined: 14 Oct 2006 Posts: 33
|
|
Posted: Tue Apr 24, 2007 8:02 pm |
|
|
Yes. I was thinking it might be a prescalar problem, looking forward to your post tommorow (: Would be a bonus if it worked with 1hz (: |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Sun May 20, 2007 2:06 pm |
|
|
Here's the answer to your question:
To make the tachometer example work with a 1 Hz input signal while the
PIC is running at 48 MHz, you need to extend Timer1 to 24 bits (currently
it's only 16 bits wide).
The reason for this is because at 48 MHz, the Timer1 clock is 12 MHz
(1/4 of the oscillator frequency). A 1 Hz input signal has a period of
one second. During that one second, the Timer1 will count up to 12
million. But Timer1 is only 16-bits wide, so it can only hold 65535.
By extending it to 24-bits, it can count up to 16777215 and so it's now
big enough to hold the count of 12,000,000 which occurs between the
positive edges of a 1 Hz input signal.
You could increase the pre-scaler on Timer1 to 8 (the maximum value
available), and this would reduce the timer clock to 1.5 MHz. But the
count during one second would still be 1.5 million, and that's way more
than the 65535 that the Timer1 hardware can count up to. So even
with a Timer1 prescaler, you still need to extend the Timer to 24-bits.
This thread has an alternate #int_ccp1 routine, which uses a Timer1
value which is extended to 24 bits.
http://www.ccsinfo.com/forum/viewtopic.php?t=906
Other changes:
In the frequency calculation in main(), you need to change the
numerator to 12000000L. That's the frequency of the Timer1 clock
(12 MHz). You also need to increase size of the global variable that
holds the current CCP to be an 'int32' (it was 16 bits). This will hold
the 24-bit extended Timer1 value. Also the 'current_ccp_delta' will
have to be change to an 'int32'.
The revised display code would look like this.
Code: |
disable_interrupts(GLOBAL);
current_ccp_delta = g32_ccp_delta;;
enable_interrupts(GLOBAL);
if(gc_capture_flag == TRUE)
{
frequency = (int16)((12000000L + (current_ccp_delta >> 1)) /
current_ccp_delta);
printf("%lu Hz, delta = %lx \n\r", frequency, current_ccp_delta);
gc_capture_flag = FALSE;
}
else
{
printf("No signal\n\r");
} |
|
|
|
|
|
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
|