Some notes about interrupt service routines. Included are some questions and answers to hopefully add clarity.
When an interrupt service routine (ISR) is called, interrupts are automatically disabled. Interrupts are automatically enabled when returning from an ISR. It is not necessary to explicitly enable nor disable interrupts inside of an ISR. The processor does these two things for you.
Data that is shared with an ISR must be declared volatile...
[b]volatile[/b] unsigned long PulseCounts;
Data that is accessed by a single ISR and is not accessed outside the ISR does not need to be declared volatile.
When in doubt, declare data volatile. It's better to have correct reliable code than fast code.
Data that cannot be accessed atomically and is shared with an ISR needs to be protected by disabling interrupts during the access. Access means reading or writing. Only byte-sized data can be accessed atomically. Anything larger than a byte needs to be protected.
Accessing shared data involves saving the interrupt flag, disabling interrupts, performing the access, then restoring the interrupt flag...
unsigned long CalledFromLoopToGetPulseCounts( void )
{
unsigned long c;
{
uint8_t SaveSREG = SREG; // save interrupt flag
cli(); // disable interrupts
c = PulseCounts; // access the shared data
SREG = SaveSREG; // restore the interrupt flag
}
return( c );
}
Q: Isn't retrieving the value of PulseCounts an indivisible call?
A: No. Anything larger than a byte cannot be accessed atomically. (That's not entirely true. There are a few 16 operations that are atomic. But, determining what C-code maps to atomic 16 machine instructions can be tricky and time-consuming.)
Q: It would matter if he were retrieving the value of PulseCounts, modifying it, and then writing it back, but in this case he is simply retrieving the value.
A: It is not possible for an 8 bit processor to perform any operation atomically on a 32 bit value. Just reading the value requires four machine instructions; an interrupt can occur between any of them.
The vast majority of interrupt service routines written for the Arduino are not reentrant. The process goes something like this: An enternal event occurs (like a pin changes state) that triggers the interrupt. The processor stops what it's doing, globally disables interrupts, and calls the ISR. The ISR handles the interrupt and returns. The processor enables interrupts and resumes what it was doing before the interrupt occurred.
A reentrant ISR runs with interrupts enabled. The process goes something like this: An enternal event occurs (like a pin changes state) that triggers the interrupt. The processor stops what it's doing, globally disables interrupts, and calls the ISR. The ISR enables interrupts, handles the interrupt, and returns. The processor resumes what it was doing before the interrupt occurred.
Because the ISR enabled interrupts, any interrupt that occurs while the ISR is running interrupts the current ISR. It is possible for a reentrant ISR to be interrupted and called multiple times. As you might suspect, this kind of ISR is typically very complicated. If not carefully written, there is a long list of problems that can occur.
An examle routine used to access data shared with an ISR...
volatile unsigned long SomethingShared;
void ProcessSomethingShared( void )
{
{
cli(); // disable interrupts
// do something interesting with SomethingShared
sei(); // enable interrupts
}
}
Interrupts are disabled to safely access SomethingShared. Instead of saving and restoring the interrupt flag, interrupts are explicitly enabled with sei. ProcessSomethingShared is only called from loop so everything works as we expect it to work.
Months pass. Some new features need to be added. We decide ProcessSomethingShared should be called during the interrupt and at a certain point in loop. After some testing it's determined that the Sketch works correctly. A few weeks pass and the Arduino locks-up. A few more weeks pass and the Arduino locks-up again. We have a problem and it's going to be extremely difficult to debug.
Fortunately, a keen eye notices the sei call. Because interrupts are explicitly enabled in ProcessSomethingShared, the interrupt service routine has accidently become reentrant. On the rare occassion when a pair of interrupts occurs in quick succession, the ISR is reentered and goes haywire.
So long as sei can never be executed in the context of an interrupt it is safe to use. Saving the interrupt flag, disabling interrupts, then restoring the interrupt flag is always safe to use; the context is irrelevant. When in doubt, use the save-disable-restore method. It's better to have correct reliable code than a very nasty bug.