![](templates/subSilver/images/CCSLogo.jpg) |
![CCS C Software and Maintenance Offers](templates/subSilver/images/forumAd6.jpg) |
View previous topic :: View next topic |
Author |
Message |
nurquhar
Joined: 05 Aug 2006 Posts: 149 Location: Redditch, UK
|
PIC12F1822 - using timer2 period register correctly ? |
Posted: Wed Jul 20, 2011 5:28 am |
|
|
CCS PCM C Compiler, Version 4.122
I have been experimenting with using timer2 and the period register to do some PWM LED dimming.
I have tried the code below which switches PR2 between two fixed values inside the interrupt routine to give an LED ON/OFF time of 1:7. My LED is on with the output pin low.
If I setup the timer with setup_timer_2 (T2_DIV_BY_64, 1, 1) then it works as expected, ie the LED is on for 1 tick (8us) and off for 7 ticks(56us). 8us = 0.125us * 64 * 1
However if I use a slower interrupt rate using setup_timer_2 (T2_DIV_BY_16, 1, 5) then the LED on times becomes very long, although the off time looks correct. See the relavant scope traces. Note I am using a postscaler of 5 in this case so what should be happening is that the timer should count upto one 5 times before generating an interrupt after 10us. 10us = 0.125us * 16 * 5
So why does my LED on time jump to 520us when using setup_timer_2 (T2_DIV_BY_16, 1, 5) It would be nice to understand this as my code is going to get more complex when I start to do 3 PWM LED's (RGB) with a 1024 bit resolution using t2 and a table of PR2 values.
Code: |
#include <12F1822.h>
#device adc=8
//////// Program memory: 2048x14 Data RAM: 112 Stack: 16
//////// I/O: 12 Analog Pins: 8
//////// Data EEPROM: 256
//////// C Scratch area: 20 ID Location: 8000
//////// Fuses: LP,XT,HS,RC,INTRC_IO,ECL,ECM,ECH,NOWDT,WDT_SW,WDT_NOSL,WDT
//////// Fuses: PUT,NOPUT,NOMCLR,MCLR,PROTECT,NOPROTECT,CPD,NOCPD,NOBROWNOUT
//////// Fuses: BROWNOUT_SW,BROWNOUT_NOSL,BROWNOUT,CLKOUT,NOCLKOUT,NOIESO
//////// Fuses: IESO,NOFCMEN,FCMEN,WRT,WRT_EECON400,WRT_EECON200,NOWRT
//////// Fuses: PLL_SW,PLL,NOSTVREN,STVREN,BORV25,BORV19,DEBUG,NODEBUG,NOLVP
//////// Fuses: LVP
////////
#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES NOCPD //No EE protection
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOMCLR //Master Clear pin enabled
//#FUSES MCLR //Master Clear pin enabled
#FUSES PUT //Power Up Timer
#FUSES BROWNOUT //Reset when brownout detected
#FUSES NOIESO //Internal External Switch Over mode disabled
//#FUSES IESO //Internal External Switch Over mode enabled
#FUSES NOFCMEN //Fail-safe clock monitor enabled
//#FUSES NODEBUG //No Debug mode for ICD
#FUSES DEBUG //Debug mode for ICD
//#FUSES WDT_NOSL
#FUSES NOWRT //Program Memory Write Protected
//#FUSES PLL // PLL Enabled
#FUSES PLL_SW // PLL under software control, disabled
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES BORV25 //Brownout reset at 2.5V
#FUSES NOCLKOUT //Output clock on OSC2
#use delay (clock=32000000)
#priority timer2
// Uart TX = C4 (RXD Yellow), RX = C5 (TXD Orange)
#use rs232(UART1, baud=0,parity=N,bits=8,stop=1, errors)
#byte APFCON = getenv("SFR:APFCON")
#bit RXDTSEL = getenv("bit:RXDTSEL")
#bit TXCKSEL = getenv("bit:TXCKSEL")
#byte PR2 = getenv("SFR:PR2")
#byte PORTA = getenv("SFR:PORTA")
#byte LATA = getenv("SFR:LATA")
#bit TMR2IF = getenv("bit:TMR2IF")
// I/O Functions
#define LED_R PIN_A0
#define LED_G PIN_A1
#define LED_B PIN_A2
#define HETR PIN_A3
#define UART_TX PIN_A4
#define UART_RX PIN_A5
#include <stdlib.h>
int8 t2i = 0;
#int_timer2
void timer2_isr()
{
if((++t2i) & 1) {
PR2 = 0x00 ; // 1 Tick
output_low(LED_B) ;
} else {
PR2 = 0x06 ; // 7 Ticks
output_high(LED_B) ;
}
}
void main()
{
// Switch to internal 32Mhz Osc, ie 4xPLL x 8Mhz = 32Mhz
setup_oscillator(OSC_8MHZ | OSC_NORMAL | OSC_PLL_ON);
// Turn stuff off
setup_adc_ports(NO_ANALOGS|VSS_VDD);
setup_adc(ADC_OFF);
setup_dac(DAC_OFF);
setup_spi(SPI_SS_DISABLED);
setup_timer_0(T0_internal);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_ccp1(CCP_OFF);
setup_comparator(NC_NC);
// Set pullups - RA5(RX), RA3(Hall Effect Trans)
port_a_pullups(0x24) ;
// Default LED's off
output_high(LED_R) ;
output_high(LED_G) ;
output_high(LED_B) ;
// One Bit interrupt time, 2us x 5 = 10.0us
setup_timer_2 (T2_DIV_BY_16, 1, 5); // This line gives odd results !
// One Bit interrupt time, 8us x 1 = 8.0us
// setup_timer_2 (T2_DIV_BY_64, 1, 1); // This line works ok.
// Enable the ints
enable_interrupts(INT_TIMER2) ;
enable_interrupts(GLOBAL) ;
while(1) ;
}
|
USING 8us "setup_timer_2 (T2_DIV_BY_64, 1, 1);"
USING 10us "setup_timer_2 (T2_DIV_BY_16, 1, 5);"
![](http://www.nusoft.co.uk/postimg/pict2test-10us.jpg) |
|
![](templates/subSilver/images/spacer.gif) |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Jul 20, 2011 2:05 pm |
|
|
Consider how long it takes to execute your #int_timer2 isr, including
the overhead of saving and restoring registers which is done by CCS
in their interrupt handler. It's at least 40 instruction cycles, which
takes 5 usec with a 32MHz oscillator.
If you use T2_DIV_BY_16, that gives (32 MHz /4) / 16 = 500 KHz as the
Timer2 clock. This gives a Timer2 clock period of only 2 usec.
So if you're trying to cause a PR2 match interrupt every 2 usec, it's just
not going to work, because it takes at least 5 usec just to get in and out
of the isr.
Look at the .LST file. Look at the code which starts at address 0004
and ends with a RETFIE. The first half is the isr entry handler and the
2nd half handles the exit. Plus, you need to look at the #int_timer2
code. Most of these instructions are 1 instruction cycle long, but any
BTFSS, GOTO, RETFIE, etc., will take 2 instruction cyles if they do the
jump. This all adds up.
More info in this thread:
http://www.ccsinfo.com/forum/viewtopic.php?t=43399 |
|
![](templates/subSilver/images/spacer.gif) |
nurquhar
Joined: 05 Aug 2006 Posts: 149 Location: Redditch, UK
|
|
Posted: Thu Jul 21, 2011 1:44 am |
|
|
Dear PCM
I would think you are correct, the interrupt handler probably will be about 5us and I have had an I/O pin to toggle on/off at the start and end of isr() to check this on the scope. As a side issue is there a way of "inserting" an output_low() instruction into the very start of the interrupt handler before all the stacking to get a more accurate picture ?
However in the case where my interrupt is being fired every 8us it does seem to return correctly before the next interrupt and gives the anticipated wave form on the scope. This is my case when using : Quote: | setup_timer_2 (T2_DIV_BY_64, 1, 1); |
What I think you might have missed is when I am interrupting every 10us, which is 2us more time than the 8us is when it is all going wrong ! Remember I am using a 5x postscale along with the T2_DIV_BY_16, ie when I use : Quote: | setup_timer_2 (T2_DIV_BY_16, 1, 5); |
So although the PR2 match is as you say every 2us, the interrupt will only be triggered after 5 matches have been counted, ie 2us x 5 = 10us.
And this is what I find so puzzling, when the interrupt is being fired at the faster rate of 8us its all ok, when I use the slower 10us rate then it goes wrong. Remember the only thing I am changing is the parameters to the setup_timer2, not any of the isr() code.
One thought that occurred to me, and I have no evidence for this, is when using the DIV_BY_64 the T2 counter is being run with a much wider, well 4x wider, clock pulse than with the erroneous DIV_BY_16 x 5 mode. Thus I wondered if somehow when using the faster DIV_BY_16 the PR2 match pulse is being missed by the PIC and thus does not reset it back to Zero after the 1 bit tick and so the timer has to run to the roll over each time. Thus giving me a the very long LED on time I have seen on the scope. Is this possible ? ![Question](images/smiles/icon_question.gif) |
|
![](templates/subSilver/images/spacer.gif) |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Fri Jul 22, 2011 3:16 pm |
|
|
I have come up with an explanation of what's happening.
In your example that fails, the Timer2 clock is 500 KHz and it has a
period of 2us. This is the causing the problem. That's why it works
with the lower frequency Timer2 clock that has an 8 us period.
Example of failure mode:
Let's say PR2 is set = 6, and that Timer2 is counting up, and it goes
to 4, then 5, and 6. Then 2 us after it hits 6, a hardware interrupt is
generated and Timer2 is reset = 0. It then starts counting up again,
incrementing every 2 us.
After the hardware interrupt, it takes about 3.2 us to get inside the
#int_timer2 routine. (This is the time required to execute about 23
instruction cycles used by the entry portion of the CCS interrupt handler).
But all during that 3.2 us time, Timer2 is counting up. When it gets inside
the isr, it will already have counted up to 1.
Then, your isr code executes and sets PR2 = 0. You were expecting this
to cause an immediate match interrupt, but that's not going to happen
because Timer2 is already past 0. So it has to count all the way up to 255
before it rolls over to 0 and then you get your interrupt. That's why the
LED output pin is low for about 500 us. (approx. 250 clocks x 2us = 500 us).
Now, you have the postscaler set = 5. So the first match interrupt takes
500 us or so. The remainder all execute in one Timer2 clock cycle, like
you would expect. Then you get your hardware interrupt, where you set
the LED pin back to a high level in the isr.
I used a 16F1823 to test this, because it's in the same PIC family as
the 12F1822, and it has more pins available for diagnostics.
I added the code shown in bold below. I outputted the lower 6 bits of
of Timer2 to Port C. (Port C is only 6 bits wide on the 16F1823).
Then I connected a logic analyzer to those pins, and to pin A2, which you
use for your LED output. I triggered on Pin A2 going low, and used a
10 MHz sample clock. I setup the Timer2 pins as a "group", so it would
display on one line of the analyzer as a Hex value. Then I could easily
see what the Timer2 value is during execution of the program and I
figure out what was happening.
Quote: |
#byte PortC = getenv("SFR:PortC")
#byte Timer2 = getenv("SFR:TMR2")
#int_timer2
void timer2_isr()
{
output_high(PIN_A0);
PortC = Timer2;
t2i++;
.
.
.
}
void main()
{
.
.
.
while(1)
{
PortC = Timer2;
}
} |
|
|
![](templates/subSilver/images/spacer.gif) |
nurquhar
Joined: 05 Aug 2006 Posts: 149 Location: Redditch, UK
|
|
Posted: Sat Jul 23, 2011 12:11 am |
|
|
Dear PCM
Wow, what an analysis
Yes I see what the problem is now. The latency of the interrupt means when I am trying to set PR2 back a very low value, like 0 or 1, then timer2 has already run past the PR2 and so has to run to the roll over before being caught the second time round. Hence I get the wrong interval.
Well done I guess I will have to stick with my DIV_64 mode. I had been hoping to get 1024 PWM LED control to run at 100hz for a really good no flick result. I now have my RGB LED 1024 bit PWM isr() running, but at 61hz (DIV_64 x 2). It just about ran at 122hz with DIV_64 x 1 but the isr()'s were back to back.
Ty
Neil |
|
![](templates/subSilver/images/spacer.gif) |
|
|
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
|