|
|
View previous topic :: View next topic |
Author |
Message |
kel
Joined: 17 Oct 2005 Posts: 68 Location: Brisbane
|
Pic26f84a Timer_0 interrupt running slowly. |
Posted: Mon Jan 23, 2006 1:34 am |
|
|
I'm trying to build a simple alarm clock.I have used pic16f84a timer_0 interrupt to create a 35 us interrupt. In my code i incremented the first counter and check if it's greater than 10,000. if it's i increment the millisecond counter and check if it's greater than 100. if it's i increment the sec counter and so on.
I'm assumming that msec x firts=1,000,000 which is equivalent to 1 second.
But my the seconder is to slow.The following is the code. Can anyone help...
Code: | #include <16F84.h>
#device *=16
//#device adc=10
#FUSES NOWDT //No Watch Dog Timer
#FUSES HS //High speed Osc (> 4mhz)
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
//#FUSES NODEBUG //No Debug mode for ICD
//#FUSES BROWNOUT //Reset when brownout detected
//#FUSES LVP //Low Voltage Programming on B3(PIC16) or B5(PIC18)
//#FUSES NOCPD //No EE protection
//#FUSES NOWRT //Program memory not write protected
#use delay(clock=20000000)
//#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=9)
#include <LCD_TEST.C>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void blink_rat(char ch);
void print_int(int num, int len);
struct time {
unsigned int hr;
unsigned int min;
unsigned int sec;
unsigned int msec;
};
struct time *ptr;
//volatile char flag=0;
int16 count;
//int msec;
int cnt;
#int_RTCC
RTCC_isr()
{
set_timer0(81); // 20 mhz clock, no prescaler, set timer 0 to overflow in 35us
// 256-(.000035/(4/20000000))
count+=35;
if(count>=10000) {count-=10000; (ptr->msec)++;} //10000
if((ptr->msec)>=100) {(ptr->msec)-=100; (ptr->sec)++;}
if((ptr->sec)==60) {(ptr->sec)=0; (ptr->min)++;}
if((ptr->min)==60) { (ptr->min)=0; (ptr->hr)++;}
if((ptr->hr)==24) { (ptr->hr)=0;}
}
struct time my_time={17,14,45,0}; //initializing the structure
void main()
{
ptr=&my_time;
setup_counters (RTCC_INTERNAL, RTCC_DIV_1);
enable_interrupts(INT_TIMER0);
enable_interrupts(GLOBAL);
while(1)
{
}
} |
|
|
|
Guest
|
|
Posted: Mon Jan 23, 2006 3:49 am |
|
|
The key is to understand that the interrupt 'handler', is not called the instant an interrupt occurs.
When the interrupt occurs, a flag' is set in the hardware. On the next instruction execution, if the interrupt is not disabled, the code will call the global interrupt handler. This then executes about twenty instructions to save the registers, and then tests which interrupt flag is set, and calls the handler for this. So, when you arrive in the 'handler', perhaps 30 to 35 instruction times have allready occurred since the actual interrupt 'event'.
Now in your handler, you set the timer to '81'. The timer is counting in 'instructions', so the actual time to the bext interrupt,will then be 175 instruction times from this point. The total time between interrupts will be about 175+35 instruction times.
Realistically, it is better in terms of accuracy, to let the timer free run (don't try to set it 'onwards' in the interrupt. There has been code published in the past to show how to handle the arithmetic to give accurate timings, with an 'odd' interrupt timing. Alternatively, set the timer 'on' by the required amount (add your constant to the current value), but remember then that a couple of instruction times will execute between the 'read', and the 'write'. Using a timer with hardware reset (Timer2, with the CCP), is another good way of getting accuracy.
Best Wishes |
|
|
kel
Joined: 17 Oct 2005 Posts: 68 Location: Brisbane
|
Interrupt Accuracy... |
Posted: Tue Jan 24, 2006 2:58 am |
|
|
Guest, would you mind posting the link for code published in the past to show how to handle the arithmetic to give accurate timings, with an 'odd' interrupt timing?
I will appreciate... |
|
|
Humberto
Joined: 08 Sep 2003 Posts: 1215 Location: Buenos Aires, La Reina del Plata
|
|
|
Ttelmah Guest
|
|
Posted: Tue Jan 24, 2006 6:49 am |
|
|
I was thinking of the integer method of handling the clock, rather than adjusting the timer.
So, if you change the interrupt prescaler to /2, to give an interrupt every 512 instruction cycles (9756.625 interrupts per second), which reduces how much time your processor will be in the interrupt, you can then use:
Code: |
struct time {
int16 mSec=0;
int8 secs=0;
int8 mins=0;
int8 hrs=0;
} clock;
#int_RTCC
RTCC_isr()
{
static int16 tick=0;
tick+=256;
if (tick>2500) {
tick-=2500;
//Here every mSec
//As a 'general' comment, don't use pointer in interrupts, they
//take a lot more work from the processor.
if (++clock.msec>=1000) {
clock.msec=0;
if (++clock.secs>=60) {
clock.secs=0;
if (++clock.mins>=60) {
clock.mins=0;
clock.hours++;
}
}
}
}
}
|
Now this uses a 'trick', to give accurate timings. Basically, the 'msec' part will be executed every 2500/256 times the code is called (one call in 9.765625 times). This keeps the work in each interrupt, down to just adding 256, and checking if the total is above 2500. Only if it does exceed 2500, does the 'mSec' part get executed, which subtracts 2500 from the running 'tick' total, and increments the mSec counter. Only if this gets to 1000, does it then execute the 'second' code, and so on.
In the worst case (on the hours), all the routines will be executed, but this only happens very rarely. The values are accessed as direct variables, rather than using pointer arithmetic (this is _much_ faster inside an interrupt).
The mSec counter, will actually move fractionally in terms of real accuracy, but the total accuracy, should be 'spot on'.
This approach of using integer arithmetic like this, is an 'old' technique, but was first published here, by (I think) Neutone, in a number of posts about making an accurate RTC.
Best Wishes |
|
|
kel
Joined: 17 Oct 2005 Posts: 68 Location: Brisbane
|
Trouble with formula |
Posted: Tue Jan 24, 2006 8:07 pm |
|
|
Thanks alot guys for your help. I appreciate it very much.
Ttelmah, mate i still have a problem with how you change prescaler /2 and arrive at the 512 instructions cycles. I try to fit it into the formula to in order to figure out how it would result to 256 us delay.Please ,if you don't mind, explain a little bit further. I will appreciate coz i seem to like your approach.
did you mean i change:
Code: | setup_counters (RTCC_INTERNAL, RTCC_DIV_1);
to:
setup_counters (RTCC_INTERNAL, RTCC_DIV_2); |
?
And did you use the following formula to help with the calculations or something else?
Code: | 256-(.000256/(4/20000000)) | did you as above?
coz it gives me negative results.
Why did you choose 2500 and 1000 in your work out?
did you divide [(1/512)]*1000000 to get 9756.625?
Code: | [(1/512)/2]*1000000=976.5625!!! | what of higher prescalers i.e. 4,8,16,....256. will i use the same method i.e.
[(1/256*prescaler)/prescaler]*1000000?
thanks
cheers |
|
|
kel
Joined: 17 Oct 2005 Posts: 68 Location: Brisbane
|
Now nothing works!!! |
Posted: Tue Jan 24, 2006 10:02 pm |
|
|
I change it to the code given and nothing works now!!!
here is the code...
Code: | [code#include <16F84.h>
#device *=16
//#device adc=10
#FUSES NOWDT //No Watch Dog Timer
#FUSES HS //High speed Osc (> 4mhz)
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
//#FUSES NODEBUG //No Debug mode for ICD
//#FUSES BROWNOUT //Reset when brownout detected
//#FUSES LVP //Low Voltage Programming on B3(PIC16) or B5(PIC18)
//#FUSES NOCPD //No EE protection
//#FUSES NOWRT //Program memory not write protected
#use delay(clock=20000000)
//#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=9)
#include <LCD_TEST.C>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct time {
int16 mSec;
int8 secs;
int8 mins;
int8 hrs;
}clock={0,45,50,12};
void blink_rat(char ch);
void print_int(int num, int len);
void display_time(struct time clock);
//volatile char flag=0;int16 count;int msec;
int cnt;
//struct time *ptr;
#int_RTCC
RTCC_isr()
{
static int16 tick=0;
//set_timer0(256);
tick+=256; // set_timer0(206); 20 mhz clock, no prescaler, set timer 0 to overflow in 35us
// 256-(.001024/(4*256/20000000))
if(tick>2500){
tick-=2500;
if(++clock.mSec>=1000){
clock.mSec-=1000;
if(++clock.secs>=60){
clock.secs=0;
if(++clock.mins>=60){
clock.mins=0;
if(++clock.hrs>=24){
clock.hrs=0;
}
}
}
}
}
}
void main()
{
//unsigned int tim_12_hr;
//struct time
// clock;
lcd_init();
//setup_adc_ports(AN0);
//setup_adc(ADC_CLOCK_DIV_64);
//setup_psp(PSP_DISABLED);
// setup_spi(FALSE);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_2);
//setup_counters (RTCC_INTERNAL, RTCC_DIV_2);
// setup_timer_1(T1_DISABLED);
//setup_timer_2(T2_DIV_BY_1,255,16);
// setup_comparator(NC_NC_NC_NC);
// setup_vref(FALSE);
enable_interrupts(INT_TIMER0);
//enable_interrupts(INT_AD);
//enable_interrupts(INT_TIMER2);
enable_interrupts(GLOBAL);
blink_rat(5); //lcd_putc('\f');
lcd_gotoxy(0,2);
lcd_putc(" LCD Ready...");
delay_ms(1000);
delay_ms(1000);
lcd_putc('\f'); //lcd_writebcd()printf("testing Lcd");
lcd_gotoxy(0,2);
//print_int(msec,2);delay_ms(1000);delay_ms(1000);delay_ms(1000);delay_ms(1000);
lcd_putc(" Designer:S.Kel");
while(1)
{
display_time(&clock);
}
}
void blink_rat(char ch)
{
set_tris_a(0);
for(cnt=0; cnt<ch; cnt++)
{
output_low(PIN_A4);
delay_ms(100);
output_high(PIN_A4);
delay_ms(100);
}
}
void display_time(struct time clock)
{
static int tim_12_hr;
if(clock.hrs>=13) tim_12_hr=clock.hrs-12;
else tim_12_hr=clock.hrs;
lcd_gotoxy(4, 1); //lcd_putc("LCD Ready...");
printf(LCD_PUTC,"%02d:%02d:%02d\n",tim_12_hr,clock.mins,clock.secs);
lcd_gotoxy(12, 1);
if(clock.hrs>11) lcd_putc("PM");
else lcd_putc("AM");
} | [/code]
It prints a the wrong message on the lcd screen i.e
instead of:
the timer does not update itself.it seems the interrupt never occurs...
need your help
cheers |
|
|
Ttelmah Guest
|
|
Posted: Wed Jan 25, 2006 4:12 am |
|
|
There are a couple of things that 'leap out', which are going to be driving the code up the wall!. You now have a _global_ variable called 'clock', but are still trying to hand an address to 'display_time' in the code. You don't even have to hand the variable to the subroutine at all. 'clock' is accessible from anywhere in the code. As a general 'comment', I'd suggest you actually disable interrupts, make a copy of 'clock', then enable interrupts again, and display from the copy. The problem is that otherwise if you are reading the seconds for example, and printing this, what happens if the variable changes between the display showing the top byte and the second byte?. Generally, I'd also avoid using 'local' names, that match global variables. Though the compiler should handle this, it encourages confusion. This is causing problems in your code, since inside the display_time routine, you have told it there is a local variable called 'clock', but have sent the address of clock (not the contents) to the code, then access it internally, as if you have sent the actual variable....
So, rename clock, with something like 'GLOB_clock', which then makes it plain that this is a global variable. Then, do 'display_time', as:
Code: |
void display_time()
{
static int tim_12_hr;
struct time local_clock;
//memcpy, defaults to disabling interrupts
memcpy(&local_clock,&GLOB_clock,sizeof(struct time));
if(local_clock.hrs>=13) tim_12_hr=local_clock.hrs-12;
else tim_12_hr=local_clock.hrs;
lcd_gotoxy(4, 1); //lcd_putc("LCD Ready...");
printf(LCD_PUTC,"%02d:%02d:%02d\n",tim_12_hr,local_clock.mins,local_clock.secs);
lcd_gotoxy(12, 1);
if(local_clock.hrs>11) lcd_putc("PM");
else lcd_putc("AM");
}
|
This way the clock cannot change during the printout (since you are accessing the 'local' copy).
On the timings, the RTCC, counts in instruction times with a /1 prescaler, so with a /2 prescaler, counts 512 instructions.
The integer 'trick', can be made to work with any values at all really. It is a matter of taking the actual time intervals involved, and multiplying 'up', till you get to an integer, then working out the division 'down'. So (for instance), if you wanted a 1mSec apparent 'tick', from a clock at 256 instruction times, on a 20MHz clock, the RTCC would timeout at:
20000000/(4*256) = 19531.25 times per second. In one mSec, this gives 19.53125 counts. Now the trick is to advance the counter by this amount on each interrupt. Since floating point arithmetic is slow, and relatively inaccurate, you look for a 'binary' multiple of this number, which gives an integer. *32, gives 625. So in this case, if you added 32 to the counter on every call, and when the total is greater than 625, subtract this, and increment the next counter, the result will be an increment of this counter every 625/32 calls, which is the required 19.53125 division. :-)
Now this increment will actually happen on every 20 calls, with then an 'extra' increment being added sometimes on the 19th call, to give the right result, but with a slight 'jitter' in the time.
Yu can use any binary multiple 'above' this, provided the numbers are going to fit into the int16 counter.
The problem here (which is why I suggested switching to /2 on the prescaler), is just how often the interrupt will be called. The global interrupt handler, typically has an 'overhead', of about 60 instructions The interrupt code itself, may well be another 40 instructions, meaning that the processor would be inside the interrupt handler something like 100/256 of the time. Going to 512 instructions between interrupts, brings this down to 100/512.
For this, the required division, is 9.765625, and the possible binary values, are *64 or greater. So you could add 64, and check for/subtract 625, or 128/1250, or 256/2500. I just chose the latter, for no particular reason at all!... The factor of '1000', was because you appeared to want a 1mSec tick.
As a further comment, why fiddle around testing for AM/PM?. Why not just have the counter count to '12', instead of 24, and then set a 'PM flag', in another bit added to the clock structure?.
Best Wishes |
|
|
kel
Joined: 17 Oct 2005 Posts: 68 Location: Brisbane
|
Thank you thank you |
Posted: Wed Jan 25, 2006 10:16 pm |
|
|
Thanks alot.you guys are great....
Ttelmah, mate your explanation has help me dearly.This forum is better than sitting in a class room..
God bless
|
|
|
kel
Joined: 17 Oct 2005 Posts: 68 Location: Brisbane
|
why fiddle around testing for AM/PM? |
Posted: Wed Jan 25, 2006 10:18 pm |
|
|
I'm trying to add one or two bottons for the user to set the times and to change bet]ween 12/24 hrs system...
thanks |
|
|
|
|
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
|