In my last post, I made the decision that the next post would be about interrupts. Well, here goes nothing…
Interrupts allow a program to be temporarily stopped while another section of code (an interrupt handler) is executed instead. When the interrupt handler finishes up, the program continues where it left off. You can turn interrupts on and off, usually (always?) with a single assembly instruction.
So I’ve described what an interrupt is, but what’s the point? Where are they used? Peripherals built into the microcontroller use interrupts to tell the program that something happened. Examples: there might be an interrupt to tell the program that the serial port just successfully finished sending out a character. Or there might be a timer set up to cause an interrupt to occur every millisecond (which you could use to cause something to occur after a specified number of milliseconds). As you could imagine, this can be extremely useful. Often, you hear about polling I/O versus interrupt-driven I/O. With polling, your program sits in a loop waiting for something to occur, wasting lots of cycles that could be used elsewhere. With an interrupt-driven architecture, your program can be doing other things, and when it’s ready it will receive an interrupt.
Interrupts are a tricky concept because you have to be extremely careful when you’re coding a program that might be interrupted. Let’s take, for example, the following C statement:
blah = blah + 5;
Assume that blah is a variable somewhere. Obviously, that line of code will take whatever is stored in blah, add 5 to it, and store the new value into blah. That one statement does not directly translate into a single assembly language statement — at least in common microcontroller architectures. In general it will translate into three instructions:
1) Load whatever is stored at the memory address of blah into a register
2) Add 5 to the register’s value
3) Store the contents of the register to the memory address of blah
OK–so what’s the big deal? The deal is that since the single line of C translates into 3 assembly instructions, it’s not an atomic operation. An interrupt could occur in between the first and second instruction, or between the second and third instruction. You’re not guaranteed that nothing else will occur while that line of code is executing. If you’re expecting an interrupt to come in and modify the value stored in blah, you may end up with unexpected results. Let’s say that your interrupt routine consists of one line of code:
blah = 0;
If the interrupt fires in between two of the instructions belonging to the line that adds 5 to blah, something weird might happen. Example:
1) Load whatever is stored at the memory address of blah into a register.
2) INTERRUPT! blah = 0 now.
3) Add 5 to the register’s value
4) Store the contents of the register to the memory address of blah
Do you see what happened? The interrupt was supposed to clear blah, but it didn’t actually end up getting cleared. The first instruction read the value of blah into a register, and then the interrupt cleared blah. But that didn’t change the register’s contents, so 5 was added to blah‘s old contents still residing in the register and then the register was re-stored into blah. It’s as if the interrupt never occurred. Ideally, after this code runs, blah should contain 0 (or maybe 5, if the 0 immediately has 5 added to it). Instead, in this particular case, it contains the old value of blah + 5.
This example scenario above is very similar to a real-world bug that I have personally seen in an actual product. The end result was that it caused a speedometer to occasionally show a speed twice as large (and extremely rarely, 3 times as large) as the actual speed.
This kind of subtle behavior is what makes programming with interrupts difficult to grasp when you’re just getting started. It can cause all kinds of crazy stuff to happen that is very difficult to debug.
So how would you solve this problem in a real program? Let’s say you really did want to make sure that blah was cleared by the interrupt. One way to do this is to temporarily disable the interrupt from occurring while you’re modifying blah. Usually the easiest way to do this is to disable all interrupts, do the operation, and then enable all interrupts again:
__disable_irq(); blah = blah + 5; __enable_irq();
Pretend that __disable_irq() and __enable_irq() are macros that end up resolving to assembly statements for enabling or disabling interrupts. This will guarantee that when blah is cleared, it will not happen in the middle of adding 5 to it. If the interrupt is supposed to happen while interrupts are disabled, it will occur as soon as interrupts are enabled again.
Think about what I said there — the interrupt may not occur exactly when it’s supposed to. If this is a time-sensitive interrupt, it could be bad to delay it from happening. So if you do this, you should minimize the amount of code you have wrapped in a disable/enable interrupts combination, so if an interrupt does get held off, it doesn’t get held off very long.
I think that’s enough about interrupts for today. This should be a decent introduction to interrupts and why they have the potential to cause all kinds of problems. But they are really useful, and it’s vital to understand them if you’re going to be writing software for a microcontroller. Remember how I talked about an interrupt that could occur every millisecond? I’m going to go into how to do that kind of thing in my next post. We’ll be moving back into peripherals built into microcontrollers: in this case, timers.