|
|
View previous topic :: View next topic |
Author |
Message |
dbotkin
Joined: 08 Sep 2003 Posts: 197 Location: Omaha NE USA
|
Long ints and interrupts |
Posted: Fri Nov 03, 2006 1:35 pm |
|
|
I'm using the TIMER0 interrupt to increment and decrement a couple of unsigned long int variables. I use those variables in if() statements elsewhere in the program. Once in a while it looks like the ISR is getting called and changing the variable in the middle of a comparison or test, and things are taking an entirely wrong branch. here's an example:
Code: |
// seconds, since_id and bcn_time are unsigned long ints
// seconds & since_id get incremented and decremented
// once per second by TIMER0 ISR.
if((seconds < 1) || (bcn_time && (since_id >= bcn_time))) {
if(!seconds) {
do_this();
} else {
do_that();
}
}
|
We know bcn_time is always 0, so when seconds reaches 0 we should execute do_this(). However, every once in a while we execute do_that() for no apparent reason. When that happens, the debugging printf() I put in tells me seconds is at 255, and since_id is always the same value (341).
I think what's happening is the ISR decrements seconds from 0x0100 to 0x00FF in the middle of the first if() statement. The main() function tests the low byte of seconds, and it's zero. ISR occurs, decrementing seconds to 0x0100. Now main() tests the high byte of seconds and finds it zero also. Now as far as main() is concerned, seconds is zero - but it's actually 255. Then the second if() sees it's not zero, so we execute do_that().
So now that I know (or think I know) why Im seeing the incorrect behavior, how do I prevent it? I suspect this is something blindingly obvious to a decent programmer, but it's not jumping out at me right now. I suppose I could disable interrupts any time I'm using one of those long ints, but that's way too messy to be right. What am I missing? |
|
|
newguy
Joined: 24 Jun 2004 Posts: 1907
|
|
Posted: Fri Nov 03, 2006 2:08 pm |
|
|
Not messy at all. Just put a disable_interrupts(INT_TIMER0); just before the if() and an enable_interrupts(INT_TIMER0) just after. I'm pretty sure that each of these commands boils down to a single instruction, so they're fast. It only looks messy.
I can't think of a better way around this that is "cleaner" without adding a buttload of code for checking this condition. Perhaps one of the gurus can help. |
|
|
C Turner
Joined: 10 Nov 2003 Posts: 40 Location: Utah
|
|
Posted: Fri Nov 03, 2006 4:19 pm |
|
|
When checking a variable that could be changed by an ISR whilst using it, I always make a temporary copy of that variable and then use that temporary copy in the if statements, as in:
temp = isr_timer
...
if(temp [condition])
etc.
While this works for a SINGLE byte, there's still the chance that, while working with multi-byte variables (16 bit, 32 bit, floats, etc.) that, while it's being copied to "temp" that an ISR will occur, changing part of "isr_timer" (in our example) in the middle of the copy.
There are two easy ways around this:
- Stop interrupts, copy the variable, and restart them. The interrupts will be less-disrupted by turning them off during the copy than they would be if you did so during the conditional statements. If, however, you need precise timing AND you are preloading the timer in your ISR to obtain a specific rate (as you would with timer0 or timer1) then this could upset the delicate timing.
The other method is to:
do {
temp1 = isr_timer;
temp2 = isr_timer;
} while (temp1 != temp2);
When you are done, temp1 or temp2 will be guaranteed to have a "good" copy of "isr_timer" and one of these "copies" would be used for the comparison. (Of course, this could be done with just a single temporary variable.)
The advantage to this is that it doesn't require stopping the ISRs at all - but it *does* require a tiny bit of extra memory and processor time.
CT
Last edited by C Turner on Wed Nov 08, 2006 6:32 pm; edited 1 time in total |
|
|
kam
Joined: 15 Jul 2005 Posts: 59
|
|
Posted: Fri Nov 03, 2006 7:52 pm |
|
|
First of all, congrats on finding this error! You must have gone mad when this "glitch" happens...Holy crap!
Everyone is right.
The issue comes down to the need of a mutex/sychronization for those variabes.
We can do the
Code: |
do {
temp1 = isr_timer;
temp2 = isr_timer;
} while (temp1 != temp2);
|
But we would need to have a "temp" variable for each logic use of that variable
Code: |
if(seconds!=0 && seconds!=0 && seconds!=0); // we could fail on the last seconds
|
So before each "logic" block, wait for the mutex to go low, flip is high, do what you want, release the Mutex. In the Int, do the same, but instead of waiting for the mutex to go low, just don't touch the variables but continue.
Now this will give us 100% accurate usage of the variables.
What we loose if that sometimes there's a small pause in the "logic" mutex waiting for the Int to finish. Sometimes the variables don't get updated in the Int. But the values will always be true. This is just like the disable ints, but you can use the Ints still.
There could be a total failure () if a routine within also needs sychronization, it'll wait forever.
Yes this add's code, but hey....
~Kam (^8* |
|
|
|
|
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
|