Polling or Interrupt based method
If the event of interest is:
- Asynchronous
- Urgent
- Infrequent
then an interrupt based handler would make sense.
If the event of interest is:
- Synchronous (i.e. you know when to expect it within a small window)
- Not Urgent (i.e. a slow polling interval has no ill effects)
- Frequent (i.e. majority of your polling cycles create a 'hit')
then polling might be a better fit.
Other considerations include whether you are writing a device driver for an OS or just writing bare metal code with no thread support. In bare metal situations the CPU is often just looping when it isn't busy so it might as well be polling something.
When deciding upon polling or interrupt you have to fully understand the nature of the event you are trying to follow and your response to it.
Interrupts require no processing when nothing is happening, but require all your attention when something is happening. If the event is external and has noisy edges or fast pulses then this can cause major headaches with interrupts, you have to be careful around the setup of interrupts.
In this example the interrupt routine is responding to a laser beam having become clear and is setting itself up for an event where it becomes blocked:
BEAM_INTR_EN = TRUE; /*re-enable the beam interrupts*/
/*Set the beam interrupt for the next clear to blocked event*/
BEAM_INTR_EDGE = CLEAR_TO_BLOCKED;
BEAM_INTR_FLAG = FALSE; /*Clear the interrupt*/
There are 2 weak points of this code: 1) If the laser beam has become blocked again before the interrupt flag is cleared (BEAM_INTR_FLAG = FALSE;). The interrupt will have been missed and the code will be out of sync with the laser beam state.
2) When setting up interrupts in either in the background routine or for a higher priority than the priority this code is on, care must be taken when enabling the interrupt. If the interrupt flag was already set (incorrectly) prior to it being enabled, the interrupt routine would be called incorrectly as soon as it was enabled and maybe for the wrong edge.
The easiest way to fix 1) is to double check after you set up the interrupt, if it has occurred then force an interrupt. To fix 2) move the enabling of the interrupts to after the the double check:
/*Set the beam interrupt for the next clear to blocked event*/
BEAM_INTR_EDGE = CLEAR_TO_BLOCKED;
BEAM_INTR_FLAG = FALSE; /*Clear the interrupt*/
/*Double check beam state to see if it has already gone blocked*/
if (BEAM_STATE == BEAM_BLOCKED)
{
BEAM_INTR_FLAG = TRUE; /*Force the interrupt to re-enter the ISR after exiting*/
}
BEAM_INTR_EN = TRUE; /*re-enable the beam interrupts*/
The forcing of the interrupt makes the system work with the same state machine, just forcing it round manually to cover the blind spot.
Basically:
Set the edge to detect the next interrupt event
Clear the interrupt flag
if (the event has already occurred)
{
Set the interrupt flag to force the interrupt
}
Enable the interrupt
If the time of the response to an event is has to be consistent (e.g. 1ms +/-10us after after the input line goes high, transmit the event signal) then interrupts are usually best.
If the time of the response to an event is has to be within a certain time (e.g. within 1ms of the input line going high, transmit the event signal), then an interrupt would be best.
The problem with interrupts is you have to start thinking about threading and that two pieces of code can access the same data at the same time.
Interrupts are also good for allow processors to go into low power modes (sleep/idle etc.) whilst waiting for something to happen.
Having said all that polling can give very tight time responses to events if there is only one thing for the processor to do, often interrupt hardware takes several cycles to respond to an event whilst a tight polling loop will do.
If the event is none timing critical and potentially noisy (e.g. someone pressing a switch) then polling allows simple filtering without missing the long term transitions. A common mistake is to poll multiple times when setting things up:
void fnInitialiseSystem(void)
{
if (MODE_INPUT == MODE_A) /*First polling of the MODE_INPUT*/
{
PR2 = PR2_MODE_A;
}
else
{
PR2 = PR2_MODE_B;
}
OpenTimer2( TIMER_INT_ON &
T2_PS_1_1 &
T2_POST_1_8 );
if (MODE_INPUT == MODE_A) /*Second polling of the MODE_INPUT*/
{
CurrentMode = MODE_A;
PROBE_INT_EDGE = CLEAR_TO_BLOCKED;
}
else
{
CurrentMode = MODE_B;
PROBE_INT_EDGE = BLOCKED_TO_CLEAR;
}
}
In the above example MODE_INPUT is an external switch, if the two times MODE_INPUT is polled differ then the behaviour is unexpected. When reading these kinds of signals it is best to use filtering to decide upon the long term state of the input, and perform actions on the filtered version.
For example with switch de-bouncing just check a switch regularly (every 1ms?) and if a number of them (say 16) are different (switch closed) from the filtered version (switch open) then update the result and perform the action required. Be careful with signal aliasing, an oscillating signal may look stable!
An example of use of polling and interrupts is, again, for the use of a input which doesn't change often but is noisy when it does. Yet again a switch is a good example of this: the code can set up an interrupt to check for a change in the switch state, when an interrupt occurs then the switch can be regularly polled until the switch state is "stable" (either changed state or back to what it was). This gives the advantage of low processing overhead when nothing is happening, and noise filtering when something is happening.
Polling should be avoided where possible, as it typically eats a lot of CPU cycles unnecessarily (unless either (a) you are only going to poll for a short time or (b) you can afford to sleep for a reasonable time in your polling loop). Wasting CPU cycles is bad not only from a performance perspective, but it also drives up power consumption, which may well be an issue for battery-powered embedded applications.