Choosing Between Polling and Interrupts: When and Why?"

MrChips

Joined Oct 2, 2009
34,829
UART communications is a simple example where you can make the informed decision between using polling and interrupts.

If you need to transmit a string of characters and there is nothing else timely that the processor has to do, you can use polling to determine when the UART is ready to accept another character to be transmitted.

On the other hand, if the processor has to perform any process while at the same time receive characters, then it is critical that no character is missed. Here, you would use interrupts in order to receive all characters and store them away in a receiver buffer. You only need to determine that an "end of text" character has been received in order to notify the main process that the full message has been received.
 

nsaspook

Joined Aug 27, 2009
16,330
UART communications is a simple example where you can make the informed decision between using polling and interrupts.

If you need to transmit a string of characters and there is nothing else timely that the processor has to do, you can use polling to determine when the UART is ready to accept another character to be transmitted.

On the other hand, if the processor has to perform any process while at the same time receive characters, then it is critical that no character is missed. Here, you would use interrupts in order to receive all characters and store them away in a receiver buffer. You only need to determine that an "end of text" character has been received in order to notify the main process that the full message has been received.
That's correct in many cases but not all. There are times when a slightly more abstract system of polling and interrupts are used because it simplifies the processing logic by simulation of processing concurrency to have fine-grained polling while sharing the processing resource. A 8-bit controller with good speed, fast interrupts and modern buffered I/O modules can be leveraged to build a more abstract processing environment than just polling and/or interrupts.

In my odd-ball 9N1 example (that makes simple UART interrupt processing complex) I made a simple multi-tasker (using a regular 500us timer interrupt ISR) for the non-blocking UART task (other program timing tasks also run during this time splice). The time interval between UART task time slices to poll for flags and process RX/TX data is shorter than the receiver overflow time for a single transmission (with two FIFO's to buffer characters we have even more time) at the FM80's 9600 Baud rate. Each 500us timer interrupt gives polling processor time to insure the critical task of making sure no characters are missed while also giving time to the main process and other interrupts for their processing.

So here we have the UART communications time splice in yellow (active UART processing when high, primary task when low) and the received data to the UART on the purple trace.
1692374208078.png
detail
1692374253634.png
1692383171406.png
The UART polling processing time of the task 'tick' is 6.3us with the rest for software timing functions.

During the transmission time of one character (0x02) we have several executions of the UART polling code to check for flags, read and store the received data in a buffer for later processing in the main time task interval.
Note the odd-ball problem here: The scope serial decoding can't handle 9-bit serial data length so it's 'faked' using parity to decode some of the data.
 
Last edited:

John P

Joined Oct 14, 2008
2,063
Sometimes I've written code where I keep the number of interrupts to just one, and that one is a repetitive timer. All it does is set a flag which I always call "tick". This interrupt will be very fast in operation, which is useful in a PIC environment, where all interrupts use the same vector and then you have to test for them, but not if you only have a single one. Then the main program consists of an endless loop with a test for "tick". If found, it is cleared and possibly divided down for some purposes (i.e. every tenth "tick" or whatever). Incoming or outgoing UART characters can be detected, and any other tasks, like checking for keypad input, can be done in turn. Obviously, if a serial port is in use, the polling rate has to be fast enough to catch every incoming character. It works as long as the worst "jackpot condition", where everything wants service at once, doesn't take too long. This can be alleviated somewhat by setting the division rates up to avoid coincidences. I often end up with a switch-case construction to decide when to do each task, if not on every "tick".

The point of this was that servicing a PIC interrupt used to be fairly expensive in terms of time, with a number of registers needing to be stored away and then replaced afterward, and the interrupts were asynchronous and might coincide. Now I'm using the "enhanced midrange" PICs, which automatically save and restore several key registers, and I feel as though that changes the cost of interrupts considerably, so now I'm tending to use more interrupts directly and do less polling. But either way can work reliably.
 

nsaspook

Joined Aug 27, 2009
16,330
Sometimes I've written code where I keep the number of interrupts to just one, and that one is a repetitive timer. All it does is set a flag which I always call "tick". This interrupt will be very fast in operation, which is useful in a PIC environment, where all interrupts use the same vector and then you have to test for them, but not if you only have a single one. Then the main program consists of an endless loop with a test for "tick". If found, it is cleared and possibly divided down for some purposes (i.e. every tenth "tick" or whatever). Incoming or outgoing UART characters can be detected, and any other tasks, like checking for keypad input, can be done in turn. Obviously, if a serial port is in use, the polling rate has to be fast enough to catch every incoming character. It works as long as the worst "jackpot condition", where everything wants service at once, doesn't take too long. This can be alleviated somewhat by setting the division rates up to avoid coincidences. I often end up with a switch-case construction to decide when to do each task, if not on every "tick".

The point of this was that servicing a PIC interrupt used to be fairly expensive in terms of time, with a number of registers needing to be stored away and then replaced afterward, and the interrupts were asynchronous and might coincide. Now I'm using the "enhanced midrange" PICs, which automatically save and restore several key registers, and I feel as though that changes the cost of interrupts considerably, so now I'm tending to use more interrupts directly and do less polling. But either way can work reliably.
+1.

The newer PIC18 (like the Q84) with vectored interrupts (a separate ISR vector for each device interrupt) makes extensive interrupt processing on a 8-bit controller even less expensive.
https://microchipdeveloper.com/8bit:18finterrupts
https://microchipdeveloper.com/8bit:vectored-interrupts
 

joeyd999

Joined Jun 6, 2011
6,322
Sometimes I've written code where I keep the number of interrupts to just one, and that one is a repetitive timer. All it does is set a flag which I always call "tick". This interrupt will be very fast in operation, which is useful in a PIC environment, where all interrupts use the same vector and then you have to test for them, but not if you only have a single one. Then the main program consists of an endless loop with a test for "tick". If found, it is cleared and possibly divided down for some purposes (i.e. every tenth "tick" or whatever). Incoming or outgoing UART characters can be detected, and any other tasks, like checking for keypad input, can be done in turn. Obviously, if a serial port is in use, the polling rate has to be fast enough to catch every incoming character. It works as long as the worst "jackpot condition", where everything wants service at once, doesn't take too long. This can be alleviated somewhat by setting the division rates up to avoid coincidences. I often end up with a switch-case construction to decide when to do each task, if not on every "tick".

The point of this was that servicing a PIC interrupt used to be fairly expensive in terms of time, with a number of registers needing to be stored away and then replaced afterward, and the interrupts were asynchronous and might coincide. Now I'm using the "enhanced midrange" PICs, which automatically save and restore several key registers, and I feel as though that changes the cost of interrupts considerably, so now I'm tending to use more interrupts directly and do less polling. But either way can work reliably.
Even better: a set of ticks, each one half as frequent as the previous. This is the foundation of the framework I use for all my applications.

C:
void __interrupt(irq(IRQ_TMR0), base(IVT_Base)) TMR0_ISR(void)
{
    TMR0IF = 0;                //clear interrupt flag
    _tmrchg |= _timer+1^(_timer);       //accumlate changed bits
    _timer++;                           //increment volatile timer
}
 

John P

Joined Oct 14, 2008
2,063
Well, it depends where you feel like putting the computation. My instinct is to keep the interrupt itself as simple as possible, and then test for it in main(). Like this:
Code:
while (1)
{
  if (!tick)
    continue;
                      // Maybe set an external pin high here, to check operation on a scope
  tick = 0;        // Using the continue means there's no need for any indents beyond the while(1)
                // Maybe count times tick was set, and do things at certain counts
  ....           // Do all kinds of wonderful stuff
               // Clear the external pin, if we set it high
}
 

joeyd999

Joined Jun 6, 2011
6,322
Well, it depends where you feel like putting the computation. My instinct is to keep the interrupt itself as simple as possible, and then test for it in main(). Like this:
Code:
while (1)
{
  if (!tick)
    continue;
                      // Maybe set an external pin high here, to check operation on a scope
  tick = 0;        // Using the continue means there's no need for any indents beyond the while(1)
                // Maybe count times tick was set, and do things at certain counts
  ....           // Do all kinds of wonderful stuff
               // Clear the external pin, if we set it high
}
The problem is if your ticks are faster than the main program loop time. Eventually, you'll miss ticks.
 

John P

Joined Oct 14, 2008
2,063
The problem is if your ticks are faster than the main program loop time. Eventually, you'll miss ticks.
Oh sure, that's what I meant by "It works as long as the worst "jackpot condition", where everything wants service at once, doesn't take too long." And that's why if I've got any doubts about it, I insert a visible flag in the form of an external pin that indicates the time taken to service the various operations, as nsaspook showed. Or there are computer simulation techniques that let you do the same thing, but I've never got around to learning them.
 
Top