03 August 2010

Tutorial 10-a: Something Completely Different (Interrupts)

I realized half way through the last tutorial that we need to take an aside for now.  One of the unfortunate aspects in microcontrollers, especially for those with no prior experience, is that you run into chicken-egg dilemmas all the time.  How can you teach timers without interrupts?  How can you teach interrupts without something to cause interrupts, like timers?  It takes some patience, but with some experience (and lots of examples!) things start to settle into place.  In the mean time, I apologize for the occasional jump in topic.  For that matter, we're not entirely done with previous topics either, and will revisit some of the more advanced concepts of GPIO, Clocks, and so forth in the future.

So what exactly is an interrupt?  Hopefully you're familiar enough with C programming (if not, I highly recommend spending some time with a good book; I suggest the C Primer Plus (5th Edition)) to have a good handle on function calls.  Microcontrollers use function calls as we've seen before, just as your personal computer does.  But microcontrollers also interface with the outside world, and sometimes you need to perform a specific task based on what happens outside the circuit.  One option would be to continuously poll an input (or sequentially poll a set of inputs) to see if anything needs to happen and call the appropriate function accordingly.  Unfortunately, this ties up the processor so that it can't do anything useful in the mean time.  Perhaps worse, the processor keeps chugging along, using up power doing nothing but waiting to see if it's got a job to do.  The better option is to have the outside signal interrupt the processor (from its current task or its nap) to get a task done and then allow the processor to go back to what it was doing.  This ability is implemented in the aptly named interrupt service routine (ISR).

The Interrupt Handling Procedure
In order to control multiple sources at once, the MSP430 needs to have some definite organization; who gets priority over the other peripherals?  How do we keep track of where we were before?  To do this, the MSP430 is designed with a clean and efficient procedure to handle interrupts.

  1. If a peripheral's interrupt is enabled, the appropriate signal sets the interrupt flag in one of the peripheral's registers.
  2. If the processor is allowing interrupts, the presence of a peripheral interrupt flag sets a general flag to the processor.
  3. The processor finishes any instruction it's currently doing.
  4. The processor saves it place by copying the address where it's currently executing instructions (the value in the Program Counter (PC) register) to the memory stack (this is called "pushing onto the stack").
  5. The processor saves its current status by pushing the Status Register (SR) onto the stack.
  6. The interrupt that currently has the highest priority is selected if there are multiple flags set.  Check your device's datasheet for a list that shows the priority of the possible interrupts.
  7. The interrupt flag is cleared, unless the peripheral has more than one type of interrupt.  In that case, you have to clear the flags manually.
  8. The SR is cleared, which disables any further interrupts during the ISR handling and wakes up the processor from any low power mode (LPM).  Note that only "maskable" interrupts are disabled by the SR clear; this means "non-maskable" interrupts, such as a reset, are still possible during an ISR.
  9. The address of the interrupt vector is copied to the PC, directing the processor to start executing code from that address on.
From the start of an interrupt signal to the start of ISR execution can take anywhere from 6-12 clock cycles of the processor, depending on exactly what's going on when the interrupt is flagged.  (This latency isn't much, but must be handled differently for time-critical event handling.)  If the chip is operating at 1 MHz, that's anywhere from 6-12 μs for the processor to respond to an event.

When the ISR finishes, the processor executes the return from interrupt sequence, which consists of just two steps:
  1. Move the original SR value from the stack to the SR (this is called "popping" from the stack).
  2. Pop the original PC value from the stack to the PC.
This sequence takes another 5 cycles to complete, after which the processor returns to what it was doing.  Note that if the MSP430 was in an LPM before the interrupt, it returns to the same LPM after the ISR handling with no further instruction from the code necessary.  You only need to give a different instruction if you don't want the MSP430 to reenter the LPM.  We'll see how to do that soon, when we discuss the low power modes.

Coding Interrupts
Writing an ISR is very similar to writing encapsulated functions in C, but requires some special syntax that you may not have seen before.  Every ISR has this structure:
#pragma vector = <VECTOR_NAME>
__interrupt void <ISR_NAME> (void) {
    ISR code here
}

The keyword #pragma is used in C to signify a special operation peculiar to a compiler.  (If you were to compile your MSP430 code with another compiler, such as gcc, it would simply ignore this line, leading to potentially unexpected errors.)  In this case, it tells the MSP430 compilers that the following code is to be referenced for the particular interrupt named.  VECTOR_NAME is essential, and must follow the conventions for the particular peripheral interrupt being written used in your device's header file; the address of the start of the ISR in the code memory is recorded in the address defined by the header as VECTOR_NAME.  Take careful note that the #pragma line does not have a semi-colon at the end.

__interrupt signifies the start of the ISR.  You can name the ISR anything you'd like, but conventionally coders use something descriptive, such as P1_ISR for a Port1 interrupt, or TA0_ISR for a Timer_A interrupt.

Beyond that, everything remains the same as we're used to seeing.  There's no special code needed in C to start the return sequence, but in assembler you need to manage this using the reti instrunction.

Unused Interrupts
What happens if an interrupt is called and you don't have an ISR written for it?  The results are unpredictable, depending on what value is inside the interrupt vector.  The safest method to protect from this is to write an ISR for every interrupt, doing nothing, or perhaps entering an infinite loop, for interrupts not used.  (The infinite loop method makes debugging clearer; it's obvious where you get stuck that way!)  This convention doesn't take much code space in the MSP430, but can make your simpler projects a little more complicated to the outside viewer.  Another way to handle this problem is to be certain that unused interrupts are disabled so that they can't ever flag the processor.  Not controlling interrupts can cause serious problems, however, so be sure to try to use good coding practices whatever you do.

This part of the tutorial on interrupts is long enough, so the following post will give a couple of examples to get you started using what we've learned here.

2 comments:

Anonymous said...

Appreciate this blog very much! Keep it up, if possible!

Anonymous said...

Appreciate this blog very much!