When using global-variables shared between your main() function and an interrupt (or two threads?), there are a lot of potential disasters one might not expect...
----------
I'm no expert, here... but I've learnt a few things along the way, which could be handy for others. There's probably an entire course in most Computer-Science degrees on the matter, but maybe this is a decent starting-point for those who haven't run into it...
----------
First: Use "volatile" correctly...
Here's a pretty great writeup on the matter, so I won't go into it: http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword
Read that, because some of the following relies on your having-done...
---------
Now for the fun stuff... That page doesn't describe several other common pitfalls...
E.G. say you have:
while( volatile_int != 0 )
{
do something with volatile_int
}
Will it reread volatile_int *each* time it's referenced within that while-loop, or will that entire iteration of the while loop use that value of volatile_int? Or worse, maybe:
if( volatile_int > 0 )
{
do something with volatile_int
}
else if ( volatile_int < 0 )
{
do something else with volatile_int
}
else // volatile_int == 0
{
do something else, again, with volatile_int
}
In cases like these, it's too confusing (for me) to rely on whatever the standard might claim in some vague reference-manual somewhere hard to find... Nevermind what would happen when an interrupt occurs between testing the first "if" and the next "else if"...
So best to use a separate *local* variable... e.g.
int temp_int;
while( (temp_int = volatile_int) !=0 )
{
do something with temp_int
}
or
int temp_int = volatile_int;
if( temp_int > 0 )
{
do something with temp_int
}
else if ( temp_int < 0 )
{
do something else with temp_int
}
else // temp_int == 0
{
do something else, again, with temp_int
}
And, when needing to write back to volatile_int...?
Best to write to a temp variable and *only once* write that temp-variable back to the global.
Even still, there's some difficulty. If your interrupt *and* main both write to the same variable you can get into some trouble, depending on the implementation... This is where mutual-exclusion comes into play, and I thankfully haven't had to mess with that much.
---------
(For most of my needs, an interrupt writes a variable, main reads it back, and *if* main needs to write it, it's usually just as an indicator that the value has been processed... more on that in a second).
---------
And, finally, it may not be *as* common on a 32-bit device, but definitely a common problem with 8-bitters... Consider a global volatile int64_t being written in an interrupt and used in main():
temp_int64 = volatile_int64;
On a 32-bit system, that will most-likely require *two* instructions, each writing 32-bits of temp_int, one 32-bit word at a time. If the interrupt occurs *between* those writes, temp_int64 will contain a mess. Here's a 16-bit example on an 8-bit processor:
volatile_int16 = 0x00ff;
main:
temp_int16 = volatile_int16;
// -> temp_int16[low byte] = 0xff;
// <INTERRUPT>
interrupt:
volatile_int16 = 0xff00;
// <return to main>
main:
temp_int16 = volatile_int16; //(continued)
// -> temp_int16[high byte] = 0xff;
//now, in main, temp_int16 == 0xffff
Bad news!
This one's a real problem, because most people don't see it just looking at the code. *And* it happens *very* rarely that the interrupt will occur at *exactly* that moment.
But if you're not careful the consequences could be horrendous
(sudden-acceleration of cars? pace-makers firing when not needed? etc. etc. etc.)
So, then, it's somewhat common to change it in main:
<disable interrupts>;
temp_int16 = volatile_int16;
<reenable interrupts>
This makes the two-instruction assignment "atomic", it cannot be interrupted.
Of course, this assumes you already had interrupts enabled and want them reenabled after your assignment to temp_int16... but what if you don't *know* whether interrupts are enabled but want to make sure your assignment to temp_int16 remains atomic? It gets a bit more complicated... so don't just go throwing around cli() (clear interrupts, on AVRs) and sei() (set interrupts) willy-nilly.
There are probably more-common and better ways of handling this...
E.G. on AVRs there's "ATOMIC_BLOCK()"
Check out http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html for some more examples.
----------
Personally, ATOMIC_BLOCK is a bit too complex for my normal needs, ("WTF is this argument?!") so I've written "CLI_SAFE" and "SEI_RESTORE" for most architectures I've worked with... here's the AVR example:
#define CLI_SAFE(uint8_name) (uint8_name) = (SREG); cli()
#define SEI_RESTORE(uint8_name) (SREG) = (uint8_name)
Now I'd rewrite the temp_int16 assignment (in main) as:uint8_t oldInterruptState;
CLI_SAFE(oldInterruptState);
temp_int16 = volatile_int16;
SEI_RESTORE(oldInterruptState);
One handy side-effect of how it's written... you don't need the explicit declaration of oldInterruptState:
uint8_t CLI_SAFE(oldInterruptState);
temp_int16 = volatile_int16;
SEI_RESTORE(oldInterruptState);
----------
So, earlier I said that for most of my needs, an interrupt writes a variable, main reads it back, and *if* main needs to write it, it's usually just as an indicator that the value has been processed, so it won't be processed again.
E.G.
volatile int16_t receivedByte = -1;
void interrupt_handler(void)
{
//Unfortunately, this'll overwrite the last value
// if not processed quickly-enough by main()!
receivedByte = <read UART Rx-Register>;
}
int main(void)
{
while(1)
{
uint8_t CLI_SAFE(oldState);
int16_t tempRx = receivedByte;
receivedByte = -1;
SEI_RESTORE(oldState)
if( tempRx >= 0 )
{
//display the hex-value of the byte received:
printf( "Received 0x%2x", tempRx);
}
}
}
Note how the read *and* modification of receivedByte occur within the same "atomic block" in main.Anything more complicated than that is too much for this write-up... Just be aware of these pitfalls, and don't assume that I actually know what I'm talking about, and *don't* use my code/explanations for designing self-driving cars or anything else that could harm a person, fergodsakes.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.