Polling and interrupts

Thread Starter

hunterage2000

Joined May 2, 2010
487
I have been reading the advantages of using interrupts over polling. A couple of questions:

1. Is the number of interrupts used in a program limited to use of specific pins?
2. Seen as interrupts have many advantages over polling, are interrupts solely used in applications or it a mix of both?
 

OBW0549

Joined Mar 2, 2015
3,566
I have been reading the advantages of using interrupts over polling. A couple of questions:

1. Is the number of interrupts used in a program limited to use of specific pins?
That depends on the particular microcontroller chip. I think in most cases the answer is "yes" although there are micros, such as Microchip's dsPIC33EPxxx series, that feature reconfigurable pin assignments via a crossbar switch.

2. Seen as interrupts have many advantages over polling, are interrupts solely used in applications or it a mix of both?
I imagine that in most cases it's a mix.
 

dannyf

Joined Sep 13, 2015
2,197
1. Is the number of interrupts used in a program limited to use of specific pins?
generally unrelated. Many interrupts are triggered within the mcu, no pins involved at all.

2. Seen as interrupts have many advantages over polling,
It depends and in many cases that statement isn't true.

are interrupts solely used in applications or it a mix of both?
Not sure what you meant by that.
 

Papabravo

Joined Feb 24, 2006
21,159
Take the standard UART. It is quite common to use interrupts on the receiver because you can't predict when the characters will arrive, or how short the inter-character arrival time will be. On the transmitter side it may be preferable to poll the the transmitter data register empty bit and just send the characters at the synchronous limit. Using an interrupt on the transmitter side is a bit tricky and one of the first of many epic fails a first time programmer will make. the results can be surprising and hilarious.
 

nsaspook

Joined Aug 27, 2009
13,086
Interrupts on a typical controller can also be internal from specific modules that run independently from main CPU execution and software driven (traps) in some architectures.

Example:
Instead of directly polling the done flag for a ADC conversion value the program can be designed to use the ADC interrupt flag to transfer the result to a memory location while the main program execution does other things. One of the problems with interrupts vs polling is the synchronization of processes. How does the main program know there is new data? This problem can be solved with memory buffers, the main process could 'poll' a data buffer to see if one or more values are stored inside from the ADC interrupt routine. While there are advantages to interrupts it can be a minefield where you have to plan each step carefully in advance.
 

shteii01

Joined Feb 19, 2010
4,644
Ok. Back to basics. Like others said, there are actually two TYPES of interrupts. If you have a decent textbook, you would have known this, but you, obviously, don't have one.

There are HARDWARE interrupts, they use pins.
There are SOFTWARE interrupts, they use flag bits that are built into registers of the uC, meaning that you can not physically access them from outside, you can not hook a wire to them like you can to a pin.

Now. The number of software and hardware interrupts is limited. If I remember right, Intel 8051 uC has 4 hardware interrupts and 4 software interrupts. Why? Because interrupts is a feature, it uses resources and there is a limit to how many resources a chip has, so the designers of the chip, Intel in this case, decided that they will have 4 hardware interrupts and 4 software interrupts. Just from hardware interrupt view, 8051 has something like 40 or 44 pins. You use some for Vcc and GND, so now you removed 2 pins from the pool of resources, you use 2 for external oscillator, remove 2 more pins from the pool of resources, other things will use other pins, so the pool of available pins to be used for hardware interrupts keeps on shrinking. And that brings us to the interrupts vs polling.

Like any good textbook will point out, if you have relatively simple system, that does not have a lot of sudden changes or have just a set of tasks that run very regularly, then polling is actually better/simpler system to have/to implement. The other aspect is availability of hardware, if all your pins are occupied, then you have no choice, you can not use hardware interrupts and you are forced to use polling instead, but that also depends on your planning, if you planned to use hardware interrupts and set those pins aside, then found workarounds for other things, then you use interrupts.
 

MaxHeadRoom

Joined Jul 18, 2013
28,619
To add all the Microchip modules have their own settable interrupt, and the type 18F and on have hi & lo priority interrupt capability.
Max.
 

shteii01

Joined Feb 19, 2010
4,644
To add all the Microchip modules have their own settable interrupt, and the type 18F and on have hi & lo priority interrupt capability.
Max.
Right. That is additional aspect of interrupts, they have priority. If the uC receives two interrupts, it will execute the high-er priority interrupt first, always. And that is the other part of the design process, you the designer, need to be careful which interrupt is used for which signal. If you have some life and death situation, you want that signal to go to high priority interrupt vs low priority one.

I guess maybe temperature might be good example. Let say you are taking daily temperature reading, say every 1 minute, you know, one of those environmental studies that some scientists then use a supercomputer to crunch all the data a 100 years later. Do you really need interrupt for this? You can use it, but do you actually need it? No, you don't need it, polling will do this just fine. On the other hand, if you have oven for baking bread or steel tempering process or a chemical reaction that is temperature specific, here your polling might miss the critical event, here high sensitivity and/or quick reaction to change is important, interrupt will provide you with the ability to act on whatever sudden change takes place.
 

Picbuster

Joined Dec 2, 2013
1,047
To poll or not to poll is embedded in your design and based on that you select the mpu to use.
example: measure temperature changes of a huge mass (100Kg++) once in 5 or 10 minutes. no int needed this will free one.
but an alarm or other signals whom require direct action thus high priority.
At the other hand handling many interrupts could put you in trouble when processor is to slow and or too little memory for stack.
It's balancing and assuming worst case situations.
(electronic engineers life is like a toddler shirt short and full of sh......)
 

John P

Joined Oct 14, 2008
2,025
Take the standard UART. It is quite common to use interrupts on the receiver because you can't predict when the characters will arrive, or how short the inter-character arrival time will be...
Ah, but there is one important thing you can predict: the characters can't arrive at an interval of less than one character time. If you poll faster than that, you'll never have any problems.

Personally I try to write code which uses one interrupt only, and that one is a counter/timer, which on a mid-range PIC usually ends up being TMR2. I'll interrupt the program fast enough to catch incoming data on the UART as I explained above, and then any other inputs are polled and any other time intervals are generated by dividing down the basic timer. The way I feel is that I want to have an exact measure of time, and if I allowed events like characters coming in on the UART to happen at random, there would be a chance that the basic clock could be delayed. If you say "some things are too important to be delayed" then I have to ask what happens if there's already an interrupt being serviced when that "high priority" event occurs? Obviously it gets delayed then, and maybe it gets delayed some more if there are multiple interrupts pending. What makes it worse in my mind is that it's difficult to know for sure how long an interrupt will be delayed before service occurs.

And beyond that, typically when an interrupt occurs it gets processed completely, and control returns to the main() routine, and then another interrupt gets a chance to be serviced. It costs you time for every instance of entry or exit from an interrupt, whereas in my timed program, you pay for those entries and exits at a constant fixed rate, and all the tasks are performed in a known order. If some things have to be done at an exact time, I'll put them first, so that they get done as soon as the interrupt begins. Other things (scan the keypad, for instance) won't be affected by timing jitter. Yes, you will point out that with my approach I absolutely must get everything done in the timer interrupt before the next interrupt occurs, and get some work done in main() in between. I'm always aware of that! And in fact it's a check I often make, to set and clear a port pin at the start and end of the interrupt, and I'll watch it on a scope. Look for the "jackpot" when the interrupt has to perform all the most time-consuming jobs in one cycle!

I'm not consistent about performing tasks directly in the interrupt versus setting a flag and letting main() do them when it finds the flag set. It depends on whether I'm comfortable with the amount of work that takes place in the interrupt, versus accepting that the timing of the delayed tasks will be somewhat random, maybe even waiting for multiple interrupts if main() is doing something slow.

There is a way that software is an art and not a science, where human personality affects the finished product. Maybe a real connoisseur could look over people's code and identify the artist by their personal style.
 

cmartinez

Joined Jan 17, 2007
8,220
What makes it worse in my mind is that it's difficult to know for sure how long an interrupt will be delayed before service occurs.
That's why I seldom use interrupts at all (the love of my life is the 8051 architecture, btw) I normally prefer polling, since it's perfectly predictable. And use interrupts only when the application is simple to code (so that it can be easy to read and interpret later on), and not time-critical, or if the process being controlled is quite slow compared to the chip's cycle.
 

nsaspook

Joined Aug 27, 2009
13,086
I'll jump back in to show a C example of PIC18 interrupt based programming driving a simple HD44780 LCD display. The old C18 xlcd lib used bit-banging bus signals and delays in the main program execution flow to sequence commands to the chip for display. This has the effect of blocking other main tasks for a fairly long period of time.

With this code one small splice of time in the main (1) triggers a much longer sequence of interrupt driven (2) I/O sequences giving quick panel updates while other processes are executed.


With slow command delay set on the first item from the ring-buffer.

Detail of one char being sent to LCD. DLED_4 (1) from main and DLED_5 (2) pulses from the state machine for debug



Its more complex but if you have several I/O streams to manage the advantages grow and it's really only complicated the first time you try it. After a while it becomes natural to think of programming projects this way if the system has the resources to handle the extra cpu time and memory requirements.

You need a simple circular buffer for commands and data to be sent to LCD. (and other I/O sources) This allows you to access the LCD asynchronously, you dump the data into the buffer then move on to other tasks. The buffer has extra bits for each buf variable so you can insert control data to the state machine.

fragment of the ring-buffer C header
C:
#define RBUF_SIZE 32

typedef struct ringBufS_t {
uint16_t buf[RBUF_SIZE];
int8_t head;
int8_t tail;
int8_t count;
} ringBufS_t;

void ringBufS_init(ringBufS_t *_this);
int8_t ringBufS_empty(ringBufS_t *_this);
int8_t ringBufS_full(ringBufS_t *_this);
uint16_t ringBufS_get(ringBufS_t *_this);
void ringBufS_put(ringBufS_t *_this, const uint16_t c);
void ringBufS_flush(ringBufS_t *_this, const int8_t clearBuffer);
Next is a timer based ISR state machine to handle the bit-banging and delays. Using the timer allows the main application to keep executing during periods it would be otherwise be waiting. Learning how to manage memory queues with a simple FSM should be a fundamental step in learning embedded programming without an operating system .

Standard method of sending LCD commands with xlcd
C:
while (BusyXLCD());
WriteCmdXLCD(0xc); // blink, cursor off
while (BusyXLCD());
WriteCmdXLCD(0x1); // clear screen
Testing code for the ISR based method. S_WriteCmdXLCD is a new buffer based function and putsXLCD was modified to the S_WriteDataXLCD routine.
C:
#include <p18cxxx.h>
#include "xlcd.h"

/********************************************************************
*       Function Name:  putsXLCD
*       Return Value:   void
*       Parameters:     buffer: pointer to string
*       Description:    This routine writes a string of bytes to the
*                       Hitachi HD44780 LCD controller. The user
*                       must check to see if the LCD controller is
*                       busy before calling this routine. The data
*                       is written to the character generator RAM or
*                       the display data RAM depending on what the
*                       previous SetxxRamAddr routine was called.
********************************************************************/
void putsXLCD(char *buffer)
{
    while (*buffer) // Write data to LCD up to null
    {
        S_WriteDataXLCD(*buffer); // Write character to LCD buffer
        buffer++; // Increment buffer
    }
    return;
}

void putrsXLCD(const rom char *buffer)
{
    while (*buffer) // Write data to LCD up to null
    {
        S_WriteDataXLCD(*buffer); // Write character to LCD buffer
        buffer++; // Increment buffer
    }
    return;
}

...
#include <p18cxxx.h>
#include "xlcd.h"

void S_WriteDataXLCD(char data)
{
    while (ringBufS_full(L.tx1b)); // normally there is plenty of space
    ringBufS_put(L.tx1b, data);
    return;
}
...

/* Loop forever */
while (TRUE) {
ClrWdt(); // reset the WDT timer
if (ringBufS_empty(L.rx1b)) {
if (!z++) {
voltfp(L.adc_val[adc_buf.map.index], f1); // convert to string
sprintf(bootstr2, "S %sV,R %uC %d ", f1, L.adc_raw[adc_buf.map.index], adc_buf.map.index); // display  info
bootstr2[20] = NULL0; // limit the string to 20 chars
DLED_4 = HIGH;
S_WriteCmdXLCD(0b10000000 | LL2); // SetDDRamAddr to line #2 to the buffer
putsXLCD(bootstr2); // sends chars to the buffer
DLED_4 = LOW;
}
} else {
DLED_2 = HIGH;
adc_buf.buf = ringBufS_get(L.rx1b); // get the analog voltages
ADC_Update(adc_buf.buf & ADC_MASK, adc_buf.map.index);
// do something
ringBufS_put(spi_link.tx1b, adc_buf.map.index); // send control data to SPI devices (DAC)
DLED_2 = LOW;
}

}
ISR timer3 FSM, reading and writing ring-buffers
C:
// state machine variables
static union adc_buf_type adc_buf;
static union lcd_buf_type lcd_buf;
static int8_t data, slow = 0;
....
// ADC interrupts
if (PIE1bits.ADIE && PIR1bits.ADIF) { // ADC conversion complete flag
V.adc_count++; // just keep count
PIR1bits.ADIF = LOW;
adc_buf.buf = ADRES;
adc_buf.map.index = L.adc_chan; // add channel data to the 16 bit variable
ringBufS_put(L.rx1b, adc_buf.buf);
if (!(L.adc_chan & ADC_CHAN_MASK)) {
DLED_3 = HIGH; // pulse high on ADC channel zero
} else {
DLED_3 = LOW;
}
L.adc_chan++; // next ADC channel
adc_trigger = FALSE; // reset the skip flag
}

// timer 3 interrupts
/*
* several khz state machine sequencer timer
*/
if (PIE3bits.TMR4IE && PIR3bits.TMR4IF) {
PIR3bits.TMR4IF = LOW;
PR4 = TIMER4_NORM;
V.pwm4int_count++;

/*
* scan ADC channels
*/
if (!ADCON0bits.GO) {
ADCON0bits.CHS = L.adc_chan & ADC_CHAN_MASK; // set the current channel
if (adc_trigger++) // trigger the conversion on the next timer int so the channel mux can settle
ADCON0bits.GO = HIGH; // and begin A/D conv, will set adc int flag when done.
}

/*
* send SPI data
*/
if (!ringBufS_empty(spi_link.tx1b)) { // SPI send
SSP1BUF = ringBufS_get(spi_link.tx1b); // transfer the 8 bit data buffer
}

if (!ringBufS_empty(L.tx1b) || lcd_buf.map.state) { // LCD send, 4bit , upper nibble
switch (lcd_buf.map.state) {
case 0:
DLED_5 = HIGH;
lcd_buf.buf = ringBufS_get(L.tx1b);
data = lcd_buf.buf;
lcd_buf.map.state = 0;
lcd_buf.map.skip = 0;
TRIS_DATA_PORT &= 0x0f;
DATA_PORT &= 0x0f;
DATA_PORT |= data & 0xf0;
if (lcd_buf.map.cmd) {
RS_PIN = 0; // Set control signals for command
} else {
RS_PIN = 1; // Set control bits for data
}
RW_PIN = 0;
lcd_buf.map.state++;
break;
case 1:
DLED_5 = !DLED_5;
E_PIN = 1; // Clock nibble into LCD
lcd_buf.map.state++;
break;
case 2:
DLED_5 = !DLED_5;
E_PIN = 0;
DATA_PORT &= 0x0f;
DATA_PORT |= ((data << 4)&0xf0);
lcd_buf.map.state++;
break;
case 3:
DLED_5 = !DLED_5;
E_PIN = 1; // Clock nibble into LCD
lcd_buf.map.state++;
break;
case 4:
if (!lcd_buf.map.skip) { // don't repeat if we're in slow time
DLED_5 = !DLED_5;
E_PIN = 0;
TRIS_DATA_PORT |= 0xf0;
}
/* fall-through to default people */
default:
if (lcd_buf.map.slow) { // stay in this state until the slow flag clears below
lcd_buf.map.skip = 1;
if (slow++ >= LCD_SLOW) { // for home and clear commands >3ms delay
lcd_buf.map.slow = 0;
slow = 0;
}
} else {
lcd_buf.map.state = 0;
DLED_5 = LOW;
}
break;
}
if (!lcd_buf.map.slow)
PR4 = TIMER4_FAST; // pump the LCD faster than normal
} else {
lcd_buf.map.state = 0;
}
}
If you program in C learning how to use unions and structures to access memory blocks with bitmaps in a human readable way makes it easier to debug code. Here the 8 bit data and state machine control bits are embedded into one 16 bit variable.

C:
// LCD structs

typedef struct D_data { // bitmap
uint16_t buf : 8; // data/cmd
uint16_t slow : 1; // long delay for clear and home
uint16_t cmd : 1; // LCD control bit
uint16_t state : 3; // state machine sequence
uint16_t skip : 1; // slow cmd repeating flag
} D_data;

/* used to hold 16-bit LCD buffer and control bits*/
union lcd_buf_type {
uint16_t buf;
struct D_data map;
};

// ADC structs

typedef struct A_data {
uint16_t dummy8 :8; // dummy space for adc data
uint16_t dummy4 :4; // C18 limits bitmaps to 8
uint16_t config : 1; // adc control bit
uint16_t index : 3; //adc channel select
} A_data;

/* used to hold 16-bit adc buffer, index and control bits*/
union adc_buf_type {
uint16_t buf;
struct A_data map;
};
http://microchip.wikidot.com/harmony:state-machine-model
 
Last edited:
Top