The last time I talked about interrupts, I kind of described what interrupts are. I never really got into how to use them, though. In order to use an interrupt, you write an interrupt handler — a piece of code that the microcontroller jumps to when an interrupt occurs. How that interrupt handler is set up depends on which architecture you’re programming for. In any case, when writing it in a language like C, it’s basically a special function that may need some extra code at the beginning and/or end.

The trick with an interrupt handler is that when it’s done running, it needs to leave the processor in exactly the same state it was in before the interrupt occurred. Recall that a single C instruction may break down into multiple assembly instructions that will likely involve modifying values in the microcontroller’s registers. Let’s say we’re incrementing a variable stored in memory. It will turn into three raw instructions. Let’s assume the compiler decides to use register 2 to modify this variable:

  1. Load the variable from RAM into register 2.
  2. Add 1 to the value stored in register 2.
  3. Save register 2 to the variable in RAM.

Let’s do a concrete example using this process. Let’s say that the variable stored in memory contains the value 200. Without worrying about interrupts, here’s what happens:

  1. Load the variable from RAM (it contains the value 200) into register 2. Now register 2 contains “200”.
  2. Increment register 2. Now register 2 contains “201”.
  3. Save register 2 back to RAM. Now the variable in RAM contains “201”.

That’s all fine and dandy. Now let’s say an interrupt occurs between steps 2 and 3, and it doesn’t properly restore the state of the CPU:

  1. Load the variable from RAM. Now register 2 contains “200”.
  2. Increment register 2. Now register 2 contains “201”.
  3. INTERRUPT! The interrupt handler runs, and it did some stuff that used register 2. It didn’t save the original value of register 2, so now register 2 contains whatever the interrupt left it at — let’s assume it’s 1234.
  4. Save register 2 back to RAM. Now the variable in RAM contains “1234”.

In my first interrupt article, I had a very similar example, but you need to understand why this example is different. In the first article’s example, the main program was busy modifying a variable in memory the exact same way this one was modifying a variable in memory. However, the interrupt handler was also writing to that same variable in memory. Because of the possibility of the interrupt handler changing the variable while the main program was also busy changing it, I had to protect against that possibility by temporarily disabling interrupts whenever I was modifying the variable in the main program.

In this example, however, the interrupt routine didn’t care about the variable in memory. It was doing some arbitrary operation — anything. Whatever the ultimate goal of the interrupt routine, it had to change register 2 to get it done. Unfortunately, it didn’t restore register 2 to the value it originally had. After the interrupt routine, the main program went along happily, totally unaware that the register’s value had changed. In a real-world situation, this kind of a bug would likely screw up several different registers, unless the interrupt routine was very, very simple and didn’t need to use many registers to get its work done.

So could we protect the code by disabling interrupts here, just like in the last scenario? I guess so, but it wouldn’t make any sense to do it that way. In order to protect the code from this kind of a problem, you would need to have interrupts disabled during the entire program! Otherwise, any time you enabled interrupts, you would be at risk of your registers being totally corrupted. Needless to say, disabling interrupts during your entire program would not be a viable solution — what’s the point of having interrupts if they’re disabled the entire time?

So what’s the solution to this kind of a problem?

You have to make sure your interrupt handlers play nicely. The first thing an interrupt handler should do is save the values stored in any registers it knows it’s going to be using. Where does it save them? Generally, it will store them onto the stack. Likewise, the last thing an interrupt handler should do is restore any registers it saved when it first began. Also, it may have to execute a special instruction for returning from interrupts as its last instruction.

So rather than guarding against the interrupt everywhere else, you attack it at the source — the interrupt handler has to be nice enough to play along with the rest of your program.

It turns out that some microcontrollers are actually cool enough to save the registers for you. The Freescale 68HC11 is an example of a microcontroller that pushes all of its registers onto the stack before it jumps to the interrupt handler. That’s nice, but the 68HC11 doesn’t have many registers. On a more complex CPU, automatically saving all the registers just isn’t an option.

Some compilers will do all of this for you if you specify that a function is an interrupt handler. You might do this by adding __interrupt__ to its definition:

__interrupt__ void timer_intHandler(void);

It all depends on the compiler and the CPU architecture. You might even have to manually write the interrupt handler’s prologue and epilogue yourself with assembly.

I’m personally a big fan of the way the ARM Cortex-M3 works with interrupt handlers. Before I can get into it, though, I need to talk about ARM functions.

The Procedure Call Standard for the ARM Architecture states that any time you call a function, the first four registers (R0 through R3) are used to pass arguments to the function, and the function can also use them as scratch registers. So any time you call an ARM function, if something important is in R0 through R3, you need to save it before calling the function, because you’re not guaranteed that it will still be there when it finishes up (in fact, if the function returns something, the return value is stored in R0). You are guaranteed, however, that the other registers will still be intact after the function finishes up. Thus, if a function modifies pretty much any register other than R0-R3, it needs to save the value of it so it can restore it to its original state when finished. ARM C compilers automatically generate code that adheres to this procedure call standard. Sounds a lot like what an interrupt handler has to do, right?

The Cortex-M3 takes advantage of this fact. Before it jumps to an interrupt handler, it saves R0, R1, R2, and R3. Then it jumps to the interrupt handler. The C compiler follows the procedure call standard and makes sure it preserves the other registers it uses by generating code at the beginning of the function to push their values onto the stack (and matching code at the end of the function to pop the values off of the stack and back into the registers). Then, when the interrupt handler is finished, the processor restores R3, R2, R1, and R0. Since it works this way, a Cortex-M3 interrupt handler is nothing more than a normal C function! No special assembly or extra attribute needs to be added to the function. It just works out of the box.

As I said, though, on other architectures that don’t take advantage of rules like this, you will probably need to specify to the compiler that a function is an interrupt handler, and it will take care of all the saving registers mumbo jumbo for you.

There is one more thing I want to talk about. How do you tell the CPU what interrupt handler is for what interrupt? Let’s say your CPU has several interrupts — your timer has an interrupt, there’s an Ethernet controller interrupt, a USB interrupt, and several others. How does the microcontroller know that an interrupt handler belongs with a particular interrupt?

This is handled with what is called a vector table. A vector table is just an list of addresses to jump to. The first one might be the reset vector, which is where the microcontroller should jump to when it first starts. The next one could be for the timer, the next for the Ethernet, and so on. The microcontroller’s data sheet will specify which position in the list is for each interrupt. In high-level C terminology, you could say that a vector table is an array of function pointers pointing to the interrupt handlers.

So you create this vector table and put it in a place where the microcontroller expects it to be (often at the beginning of the program’s code), and then the microcontroller will know where to jump whenever an interrupt occurs. Your IDE may help you set up a vector table, and if it doesn’t, there will be sample code somewhere that will show you how to do it.

That’s enough for today. I’ve hopefully gone into more depth about what an interrupt handler is and why it has to be special (except on the Cortex-M3 and possibly others). I hope I didn’t go too crazy when talking about the Cortex-M3 (it’s a really nice architecture, I couldn’t resist!). I’m not sure exactly what my next article will be about, but I’m thinking I may start talking about some of these other crazy peripherals built into a microcontroller such as SPI.

Trackback

4 comments

  1. hi, good articles as allways, write some more!! 🙂

    I have one queation, maybe you can help.

    Im writing a program on stm32 that collects data strings from usart and then writes it to sd card.

    i am able to get one char from usart1 and send it to usart2.
    i have function that writes single block to sd card.
    what im trying to do is to write data to array of 82 chars and write it to sd card, but i cant make array inside usart handler. Maybe its because im new to programing microcontrolers and dont know how interrupts work or maybe because im new to c language. Maybe you can tell me how to collect data to array of 82 bytes inside that handler?
    Heres my code:

    http://pastebin.com/PczcGUG7

  2. Hey there,

    Well, first of all, the USART interrupt can mean many things (more than just a character has been received, depending on how you have it set up). So you might want to read the USART interrupt status register(s) to make sure it’s really a character being received that caused the interrupt.

    As for filling up an array, what I would do is make a static array outside of all the functions, and a counter to keep track of how far you are in the buffer. Something like:

    #define BUF_SIZE 82
    static unsigned char buffer[BUF_SIZE];
    static int bufferPosition = 0;

    As the USART interrupt comes in, save the character into the array at the current position (bufferPosition) and then increment bufferPosition. That’s all the interrupt would have to do (aside from bound checking to make sure you don’t overflow the buffer). Meanwhile, in your main loop (the while loop) keep checking to see if bufferPosition has reached BUF_SIZE. Once it has, then you can pass it off to your SD card write function. Note I made a compiler define called BUF_SIZE. Rather than hardcoding in 82 multiple times, it’s easier to put the size (82) once, and then refer to it with the define elsewhere. It makes it easier to change the code in the future, and I’ve personally experienced it in the real world 🙂

    Also, you will want to be careful to make sure you don’t fill the buffer past its size, which could corrupt some other variable stored next to it (possibly the counter). So you might want to do checks in your interrupt routine to make sure the counter doesn’t get to 82 or higher to prevent from overflowing the buffer. Anyway that’s just a few tips to get started! Hope it helps.

  3. Hello. Great work Doug! How can I contact you?

  4. Thanks Yiannis!

    If you have a question about microcontrollers I would prefer it stayed on the blog comments here so the knowledge can be shared with everyone. Otherwise, my contact info can be found by clicking the “Contact” tab at the top of this website.

Add your comment now