What Should An Isr Do?

In most cases the answer is, as little as possible.  An ISR should do what must be done immediately, but should not do anything that is better left for later, to be done in the background code.  Here's an analogy:  You're carving an ice sculpture for your whist club when the doorbell rings (interrupt!).  You answer the door (start of ISR) and receive the 2000 piece jigsaw puzzle you've ordered.  Now, do you sit down and complete the puzzle at this moment?  Or do you put the puzzle in a safe place and get back to the sculpture before it melts?  Prudence says that you put the puzzle aside and get back to the other work at hand, then come back to the puzzle when you have nothing more pressing to do.  The same applies to an ISR.  Do what you must in the ISR, and save off any data or state information so that your background code can do the rest of the work at a more appropriate time.

This becomes even more critical when multiple interrupts are enabled, as will usually be the case in an embedded system.  Having ISRs that take a long time to run (because they do too much inside the ISR) may cause delayed response to other interrupts that need fast handling.

WHO RANG?

In some cases only one event can trigger a given interrupt, so the ISR for that interrupt knows exactly the event that has triggered it.  However, in many cases multiple events will all vector to a single ISR.  For example, with the STM32 as many as 4 timer events can vector to a single timer ISR (we will see this in the chapter on timers).  In these cases the first task of the ISR is to determine which event has caused the interrupt.  It will do this by checking the appropriate flags for all the possible interrupting events until it finds the one that has caused the interrupt.  Then it will handle that interrupting event in the normal fashion.

INTERRUPT-DRIVEN LED PROGRAM

One form of interrupt that all microcontrollers can respond to is a simple digital change of state on a specified pin (usually called an "external interrupt" pin).  For example, if the pin is held high by a pullup resistor, and then is brought low by some signal or action, the high-to-low transition on the pin can trigger an interrupt.  We can modify our button-controlled LED program to detect the button transition via interrupt and change the LED accordingly.  This will give us a simple introduction to interrupts, and also show why reading buttons with external interrupts is not really a good idea.

Whether you are using an AVR or an STM32, the first step is to look on the chip datasheet to see which pin or pins supports external interrupts.  For the STK-500 with the ATmega8515, there are three such pins, PD2, PD3 and PE0, so we will choose to connect the 8 buttons of the STK-500 to PORTD, and use either button 2 or button 3 - let's choose button 2 (PD2) which is external interrupt INT0.  For the STM32VLDiscovery board, we only have one button, wired to PA0.  It turns out that the STM32 device is very flexible and almost any GPIO can be used as an external interrupt, so our button on PA0 will be fine.

EXTERNAL INTERRUPT TYPE AND POLARITY

There are a few different ways that an external interrupt can be configured, depending on what the particular microcontroller allows.  In the first place, external interrupts can be edge-triggered or level-triggered.  An edge-triggered interrupt generates an interrupt request only on an edge - that is, when the interrupt line goes from one state to the opposite state (1->0 or 0->1).  A level-triggered interrupt generates an interrupt request whenever the interrupt line is in the active state, so for example a low-level-trigger interrupt will generate requests whenever the line is low, and (this is important), will continue to generate requests until the line is brought high.  If the software or the hardware is incorrectly designed, a single level-triggered interrupt could result in a furious cycle of interrupt..end interrupt..re-enter interrupt..end interrupt and so on, thousands or millions of times per second.

The second aspect of an external interrupt is the interrupt polarity.  For an edge-triggered interrupt, the polarity could be rising edge triggered (0->1) or falling edge triggered (1->0).  For a level-triggered interrupt, the polarity could be low triggered (whenever the input is 0) or high level triggered (whenever the input is 1).  Again, these are possible configuration options, but not every external interrupt input on every microcontroller will support all of these options.  It is even possible to have an external interrupt input that triggers on any logic change - that is, it will generate an interrupt request on a high-low transition (1->0), or on a low-high transition (0->1).  This is just an edge-triggered interrupt that triggers automatically on both possible edges rather than just on one specified edge.

Note as well that the distinction between edge-triggered and level-triggered interrupts is a fundamental distinction for all types of interrupts, not just external interrupts.

CONFIGURING INTERRUPTS

In the general case, configuring a particular interrupt requires taking a good look at the chip data sheet.  For external interrupts on fixed pins things are easier than the more configurable interrupts on parts like the STM32.  The AVR INT0 interrupt only requires us to select the interrupt polarity and enable the interrupt.  AVR INT0 can be configured for rising-edge, falling-edge, both-edge, or low-level triggering.  Remembering that STK-500 buttons are active low, we will choose falling-edge so as to be able to catch the initial button push (otherwise we would have to wait until the button was released before an interrupt was generated).  Looking at our AVR datasheet we see that we need to set bits ISC01/ISC00 in register configuration MCUCR to 1/0 for negative-edge trigger.  Now we need to enable the INT0 interrupt, so we need to set bit INT0 in interrupt enable register GICR.  At this point, if global interrupts are enabled, the CPU will respond to any negative-edge input on INT0.  What's more, if there was a previous edge that set the internal INT0 flag, that previously set flag would now generate an interrupt.  This is almost always a bad thing, generating an interrupt off a previously set flag, so we need to do one other thing before enabling the interrupt.  We need to write a '1' to bit INTF0 in interrupt flag register GIFR.  Writing a '1' clears any pending INT0 interrupt, which is what we want to do before enabling the interrupt.  So our full AVR configuration is as follows:

MCUCR = 0b10 << ISC00;  // negative edge trigger
GIFR = 1 << INTF0;            // clear any pending interrupt
GICR = 1 << INT0;               // enable INT0