|
|
View previous topic :: View next topic |
Author |
Message |
pat
Joined: 07 Sep 2003 Posts: 40 Location: Adelaide, Australia
|
Timer0 loosing time |
Posted: Thu Jan 29, 2004 6:44 am |
|
|
I am trying to count seconds using timer0, but my counter seems to be loosing time! This is on a 16F876 running a 4MHz crystal. CCS V3.168.
I have borrowed Jon Fick's timer0 interrupt service routine as follows
Code: | #int_rtcc
void timer_0_isr (void) {
/*
** Gets here every 15.872ms = (256 - 8) * 64us
** "timer_0_count" is incremented every time.
** When it gets to 63 it has been 0.999936 seconds
*/
if (timer_0_count < 63) {
timer_0_count++;
}
else {
/* Count seconds */
tenth_sec_a++;
/* start count over again */
timer_0_count = 0;
}
/* reset timer for 248 counts, rather wrap at 255 */
set_timer0 (8);
}
|
The prescaler is set to 64 and is an int32 that I use in the main loop to print to an LCD once every 300ms or so.
After 4 minutes (240 seconds), the LCD shows a count of 236 seconds.
My crystal is OK becuase delays and RS232 are working fine.
Could my counter be corrupted as I'm printf'ing it? I actually print it using a function call, so a local copy is being made of the var, hence it should not be corrputed/overwritten if another interrupt occurs while I'm printing it to the LCD.
Jon's spreadsheet says that 0.23 seconds will be gained per hour due to the fractional error accumulation.
Any suggestions would be appreciated.
Patrick
PS thanks Jon, also borrowed your key debounce routine, works very well) |
|
|
neil
Joined: 08 Sep 2003 Posts: 128
|
Int32? |
Posted: Thu Jan 29, 2004 7:28 am |
|
|
What are you using an int32 for? If it is tenths of a second, it only counts from 0-9. An 8-bit int is more than enough. I doubt this could cause the problem you are seeing, but working with multibyte words greatly increases the processor overhead. If you don't need your 'tenth_sec_a' variable to count up to 4,294,967,296 then change to an int.
Printing to an LCD every 300ms is a sensible time interval to use, but if you are printing 10ths of a second you will only capture the counts which coincide with a display refresh. Is this 10ths variable something like a stopwatch, where it is displayed after the timer is stopped?
Do you have an oscilloscope or even better a frequency counter to check the xtal frequency? If your RS232 is slow (<9600 or so) it will not be particularly sensitive to frequency variation.
Just some ideas,
Neil. |
|
|
Ttelmah Guest
|
Re: Timer0 loosing time |
Posted: Thu Jan 29, 2004 7:46 am |
|
|
pat wrote: | I am trying to count seconds using timer0, but my counter seems to be loosing time! This is on a 16F876 running a 4MHz crystal. CCS V3.168.
I have borrowed Jon Fick's timer0 interrupt service routine as follows
Code: | #int_rtcc
void timer_0_isr (void) {
/*
** Gets here every 15.872ms = (256 - 8) * 64us
** "timer_0_count" is incremented every time.
** When it gets to 63 it has been 0.999936 seconds
*/
if (timer_0_count < 63) {
timer_0_count++;
}
else {
/* Count seconds */
tenth_sec_a++;
/* start count over again */
timer_0_count = 0;
}
/* reset timer for 248 counts, rather wrap at 255 */
set_timer0 (8);
}
|
The prescaler is set to 64 and is an int32 that I use in the main loop to print to an LCD once every 300ms or so.
After 4 minutes (240 seconds), the LCD shows a count of 236 seconds.
My crystal is OK becuase delays and RS232 are working fine.
Could my counter be corrupted as I'm printf'ing it? I actually print it using a function call, so a local copy is being made of the var, hence it should not be corrputed/overwritten if another interrupt occurs while I'm printing it to the LCD.
Jon's spreadsheet says that 0.23 seconds will be gained per hour due to the fractional error accumulation.
Any suggestions would be appreciated.
Patrick
PS thanks Jon, also borrowed your key debounce routine, works very well) |
Your problem is probably with setting timer_0 to a value.
Remember that it takes _time_ to get to this point in the code. The time taken, will also vary if there has been a delay in handling the interrupt (because of things like serial interrupts). This is why routines that use this type of behaviour, will nearly allways give timing errors. You will see in the archives, that I have allways advocated 'advancing' the timer, rather than using this type of behaviour, so you add a value to the timer, which reduces the problem (there is still a slight problem because of the asynchronous times when the timer is read, and written).
So in order of accuracy, you have:
1) Don't fiddle with the timer, but instead use a counter that can handle the odd times itself. There have been some nice code examples published in the past doing this.
2) Add an amount to the timer, rather than setting it to a value. Again examples have been given in the past.
3) Setting to a value.
Your approach is the least accurate route...
There have been a couple of 'RTC' code examples published here, that give accurate timing. The typical method is to add a value to the counter, and then check against a larger 'limit', to give a counter that corrects for the timer interrupt being at an odd interval. This works much better. :-)
Best Wishes |
|
|
neil
Joined: 08 Sep 2003 Posts: 128
|
That got me thinking |
Posted: Thu Jan 29, 2004 8:16 am |
|
|
Hi Ttelmah,
do you suggest adding a value to the rtcc because the interrupt overhead can have a variable (latency, is that the word?) time to get to the rtcc handler, depending on what other events occurred?
I use the presetting approach, but don't do any if() testing on the timer count. I haven't done anything time critical yet, but as you know, I have had lots of 'timing issues' in the past!
If you add a constant to timer0, is there still not the danger that it can increment during the add. Is the add operation 2 cycles long? eg.
Code: | movlw CONST;
addwf tmr0; | Can you explain more why presetting is not the best thing to do?
Regards,
Neil. |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Thu Jan 29, 2004 9:35 am |
|
|
When the interupt flag is set sometimes the code that is being run is in a spot where global interupts are briefly disabled. That will introduce jitter in the amount of time it takes to jump to the interupt function. Setting the timer to a fixed value will insure that every jitter adds up to become long term error. Adding a fixed value to the timer might remove this jitter error if the process of adding a fixed value to the timer has a fixed execution time. This sort of thing is very difficult to calculate. You have to solve for the value to add experimentaly and accuracy is limited by the resolution of your measurments. The drawback of using the RTC code posted in this forum in the past is the jitter in individual readings. On the up side there is none of the long term drift that you have observed when adding a fixed value and it is all based on easily calculated values. |
|
|
Ttelmah Guest
|
Re: That got me thinking |
Posted: Thu Jan 29, 2004 9:53 am |
|
|
neil wrote: | Hi Ttelmah,
do you suggest adding a value to the rtcc because the interrupt overhead can have a variable (latency, is that the word?) time to get to the rtcc handler, depending on what other events occurred?
I use the presetting approach, but don't do any if() testing on the timer count. I haven't done anything time critical yet, but as you know, I have had lots of 'timing issues' in the past!
If you add a constant to timer0, is there still not the danger that it can increment during the add. Is the add operation 2 cycles long? eg.
Code: | movlw CONST;
addwf tmr0; | Can you explain more why presetting is not the best thing to do?
Regards,
Neil. |
Because as you say of latency. Remember if you have (say) a serial ISR, and the timer interrupt occurs a moment after the serial one, the global interrupt handler will take typically perhaps 30 instruction times to send the code into the ISR, and then recover the registers. If the ISR itself takes another forty instructions, you have the processor effectively with it's interrupts disabled for seventy instruction times. Inside the timer ISR, there is then another perhaps 18 instructions before you even reach the ISR, and then the duration of the ISR itself, before you reach your 'set' code. Even if the ISR is called immediately, the overhead of saving the registers, and running through the ISR code, is many uSec. So at the point where you increment, the counter may (or may not) have allready incremented. If you then set it to a value, assuming it has not incremented, your clock will now be slow by the amount that the counter had advanced.
Unless you can _guarantee_ that the clock 'tick' won't advance in the time taken to reach the part of the handler where the clock is set, changing the number to a fixed value, will result in errors.
You can just increment the counter by a fixed number (since this is an 8bit counter), but even here, if the 'increment' event, occurs on the instruction edged between reading the register, and writing it back, the event will be lost....
Hence the much better approach, is to make the 'second' counter, use a count value that adjusts for the 'odd' timer interval. So in your case, if you let the timer 'free run' (don't advance it to 8), the timer interrupt will trigger every 64*256uSec. This then gives 61.03515625 interrupts/second. The 'key' is that if you increment a 32bit counter, by the number of uSec/interrupt, and then check for this reaching one million, and when it does, subtract the million from it, the clock will give the correct division ratio. So:
Code: |
#int_rtcc
void timer_0_isr (void) {
/*
** Gets here every 16.384mSec = 256 * 64us */
/* Now increment the count by the number of uSec/interrupt */
if ((timer_0_count+=16384l)>1000000l)
timer_0_count-=1000000l;
tenth_sec_a++;
}
}
|
Since the average result of 1000000/16384, is 61.03515625, the 'tenth_sec_a' counter will increment by one every second, and the clock should keep the correct time. The counter will be one 61.035 'late' on some seconds, and slightly early on others, but the overall result, is the required ratio.
Best Wishes |
|
|
neil
Joined: 08 Sep 2003 Posts: 128
|
Sorted |
Posted: Thu Jan 29, 2004 10:02 am |
|
|
Thanks Ttelmah & Neutone. I've got it now. |
|
|
pat
Joined: 07 Sep 2003 Posts: 40 Location: Adelaide, Australia
|
|
Posted: Thu Jan 29, 2004 8:44 pm |
|
|
Thanks all for your help. The first order problem that I discovered since my original post is in this code here
Code: | if (timer_0_count < 63) {
timer_0_count++;
}
|
this is a typical gotcha - becuase I reset timer_0_count to zero and compare it to <63 it is actually counting 64 interrupts, not 63. So I have changed the constant to 62. I originally thought this had solved the problem, and while it is vastly "better", I still have some drift, but only observable over a much longer time period, eg 15 minutes or so.
I can't spot any more coding erros, but I really like the look of Ttelmah's suggestion which I will try over this weekend.
Thanks again |
|
|
pat
Joined: 07 Sep 2003 Posts: 40 Location: Adelaide, Australia
|
It works brilliantly - very cool! |
Posted: Fri Jan 30, 2004 1:52 am |
|
|
Ttelmah you're a genius!! I just coded it up, and its been running for 20 minutes with zero drift!! Its so clever and simple too.
I've actually dropped off a zero from your one million becuase I wanted to count 10ths of seconds.
Now I just need to display the time in hours mins and sec and I'm done!
Thanks again |
|
|
Ttelmah Guest
|
Re: It works brilliantly - very cool! |
Posted: Fri Jan 30, 2004 3:03 am |
|
|
pat wrote: | Ttelmah you're a genius!! I just coded it up, and its been running for 20 minutes with zero drift!! Its so clever and simple too.
I've actually dropped off a zero from your one million becuase I wanted to count 10ths of seconds.
Now I just need to display the time in hours mins and sec and I'm done!
Thanks again |
Good.
I was slightly puzzled in the original, since the variable name referred to 'tenths of a second', yet you counted to 64. Hence my assumption that you were going to 1 second. The lovely thing is that this process of addition, test and subtraction, allows any factor to be dealt with very easily, without too much work for the processor. :-)
Best Wishes |
|
|
pat
Joined: 07 Sep 2003 Posts: 40 Location: Adelaide, Australia
|
|
Posted: Fri Jan 30, 2004 2:48 pm |
|
|
13 hours, 21 minutes and it still hasn't drifted. I won't post again |
|
|
|
|
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
|