Last time we looked at Arduino code, we examined how registers worked in the ATmega328P. We continue peeking under the hood to see how to set up external interrupts using direct register writes.
An interrupt is anything that can stop the main execution thread of a program and cause the processor to perform some action before returning to that main thread. In the case of external interrupts, something like a button push can be configured to cause this brief pause to allow some other piece of code to run.
In the ATmega328P, program memory is divided up into three main sections:
When you give power to (or reset) your ATmega328P, the very first instruction that's loaded is at program memory address 0x0000. This just happens to be the RESET vector. Usually, the only instruction there is a jump command to another place in memory (usually 0 x 0034 --- the start of your program space). Your program begins executing sequentially from there. Part of the setup code might be telling the processor that you are open to an external interrupt when Port D, Pin 2 (also known as interrupt source "INT0") experiences a falling edge (logic HIGH to logic LOW).
Note that you generally need three things to happen to have the interrupt trigger:
sei()
function). Note that global interrupts are enabled by default in Arduino.Once all three of these conditions are met, the interrupt will trigger. Execution on your main program will stop, any state variables or numbers the processor was working on are saved to memory, and execution jumps to the IVT. Which entry in the table it moves to is based on which interrupt triggered. For example, a falling edge on Port D, Pin 2 is the INT0 interrupt, so execution will jump to address 0 x 0002 (see the Reset and Interrupt Vectors table in the ATmega328P datasheet).
Often, only a single instruction will reside at the interrupt address. This is normally a jump command to somewhere else in program memory, which is where the ISR resides. The memory location of the ISR is known as an "Interrupt Vector." So, our processor will jump to the ISR, perform whatever instructions it finds there, and then jump back to the main program to pick up where it left off (restoring any saved variables it might have previously stored).
External interrupts are great for responding to things like button pushes in a timely fashion or waking from a sleep state when a sensor has data to be read.
How else have you used interrupts? Why do you find them so important in microcontroller development?
Thanks Shawn. To me, the main value of this series is for education. It helps to bridge the gap between the hobbyist community and the skills we need to teach students for industry. We need to show how microcontrollers work beyond the Arduino library so that students can go into industry using a wide variety of microcontroller families. Love the work you are doing!
BTW it would also be nice to show how to use the pin change interrupts in addition to the 2 external interrupts. With a bit of code you can effectively make an interrupt on every pin using the pin change interrupts. I've use this Arduino library for that in the past https://github.com/GreyGnome/EnableInterrupt I only mention it because with student projects they regularly like using more than just 2 pins as interrupts.
Thank you! I am hoping to help the people who want to move from the hobby level with Arduino to more product-level development. I've heard from several people who have worked with early startups; they always say "get rid of the Arduino and program the microcontroller directly."
I'll keep the request for pin change interrupts in mind. As I'm covering interrupts, I'm really trying to just show one to get people started. Time permitting, I would like to cover more, but I know there are lots of interrupts available and many ways to use each one.
Very cool video! Why do you not have to de-bounce the button? Does the interrupt code not get called multiple times, due to the multiple flanks, at each button press?
The interrupt totally gets called multiple times due to contact bounce. However, I was purposely leaving it out in this video with the hopes that I might create a "debouncing" video in the future explaining the various ways one might debounce a button :)
This comment has me concerned "any state variables or numbers the processor was working on are saved to memory". Unless the Arduino has something under the hood that saves this information, the program is going to jump directly to the interrupt vector and then your ISR. Any saving of data must be preformed in your code, Even the status register must be preserved if your ISR alters it.
With the basic ISR() definition, avr-gcc automatically handles storing the SREG for you. If you're writing in assembly or use the ISR_NAKED parameter, then yes, you'll likely need to save some register values (source: https://www.microchip.com/webdoc/AVRLibcReferenceManual/group__avr__interrupts.html).
A few reminders for home gamers:
Writing directly to registers is a valid way to speed up your code, but at the cost of code portability (which is one of the benefits of using the specialized Arduino functions). i.e. your code is locked into the specific processor (family, or often specific model). It would require more porting effort to use the same code on a different platform even within the Arduino compatible range of processors. I'm not saying not to use direct register writing, but just be aware of the trade off of speed vs. portability.
Because the AVR processors can't handle an interrupt within an interrupt, the available functions that will work in an ISR is reduced. Any function that uses the timers won't work within an ISR. This includes anything that accesses the the UART (no debugging output to the serial console), and any delay functions. Probably other I/O functions. This was (and probably still is) a newby trap on the Arduino Forums back several years ago when I was active there.
Also note that the internal clock timer (used by delay, millis, and micros) doesn't increment during ISR handling on the AVR. So this is further reason to keep your ISRs as short as possible.
When I have a project connected to an external RTC (either Chronodot style or GPS) that has a PPS (Pulse Per Second) line on it, I often attach the PPS to an interrupt. I use the set a flag method and then any operation that I want to take place at any integer multiple of a second will get reliably fired every second without relying on the non-precise timer within the AVR.
All good points, so thank you for these! I would like to dig into it further, but I believe you can set up nested interrupts on an AVR. I do see how that could lead to complications, though, and why you probably shouldn't. :)
Generally, from what I remember with out MCU types, the best way to handle a case where you might have multiple interrupts is to MUX the IRQ and then have it check what type of interrupt it is via port or code/data. This way you just build a LUT of data instead of over complicate the matter.
I haven't tried this myself, but the people on the Arduino forums who know much more than I do about the AVR interrupt systems advise that the interrupt system only has a single interrupt buffer.
For example, lets say you have 3 types of interrupts configured called A, B, and C. If Interrupt A is being serviced and interrupt B is triggered, once interrupt A is finished being serviced, interrupt B will be serviced. But, if while interrupt A is being serviced and interrupt B and then interrupt C are triggered, only one of the two will be serviced once the ISR for A exits. I'm not clear on if the most recent trigger will be remembered, or if only the first trigger will be remembered. (So in my case above I'm not sure if B or C will be serviced, but I've been lead to understand that only one of them will be serviced.)
Using Reddit instead of the datasheet is a bad idea. There is not really an "interrupt buffer"; there are, however, event triggered interrupt flags for each peripheral which will pend until they are handled. So assuming interrupts A, B, and C are event triggered interrupts they will all be remembered and handled. Another type of interrupt is level interrupts, which will only trigger if interrupts are enabled at the time the interrupt signal is present; so a level interrupt will not be remembered and will not trigger if another interrupt is in progress.
As I said, I didn't try it out myself so I admit that I don't fully understand them... "Buffer" is probably the wrong word for a single bit flag. But the effect can be similar to a buffer if something else toggles the flag and it maintains that state until the process looks at the value.
That said, thank you for your explanation.
Also, the forums that I were referring to wasn't Reddit... I was referring to the "official" Arduino forums on arduino.cc. Granted, all the people posting there are community volunteers. But, the people that were talking about interrupts with authority have all a history in the forums of knowing what they are talking about as they assist those of us who don't know as much. Specifically this forum: https://forum.arduino.cc/index.php?board=4.0