Processor Interrupts with Arduino
Interrupts!
Interrupts, what are they? They are people that intermittently prevent you from doing your current work. Haha, well maybe... but we really want to know is what are they in the context of embedded electronics and microprocessors.
So, what really is an interrupt in that context? Well, there is a method by which a processor can execute its normal program while continuously monitoring for some kind of event, or interrupt. This event can be triggered by some sort of sensor, or input like a button, or even internally triggered by a timer counting to a particular number.
We see the event, then what?
So what happens when the event, or interrupt happens? The processor takes immediate notice, saves its execution state, runs a small chunk of code often called the interrupt handler or interrupt service routine, and then returns back to whatever it was doing before. How does it know what code to execute? This is set up in the program. The programmer defines where the processor should start executing code if a particular interrupt occurs. In Arduino, we use a function calledattachInterrupt() to do this. This function takes three parameters. The first is the number of the interrupt, which tells the microprocessor which pin to monitor. The second parameter of this function is the location of code we want to execute if this interrupt is triggered. And the third, tells it what type of trigger to look for, a logic high, a logic low or a transition between the two. Let's look at a simple coding example to make more sense of this:
/* Simple Interrupt example by: Jordan McConnell SparkFun Electronics created on 10/29/11 license: Beerware- feel free to use this code and maintain attribution. If we ever meet and you are overcome with gratitude, feel free to express your feelings via beverage. */ int ledPin = 13; // LED is attached to digital pin 13 int x = 0; // variable to be updated by the interrupt void setup() { //enable interrupt 0 (pin 2) which is connected to a button //jump to the increment function on falling edge attachInterrupt(0, increment, FALLING); Serial.begin(9600); //turn on serial communication } void loop() { digitalWrite(ledPin, LOW); delay(3000); //pretend to be doing something useful Serial.println(x, DEC); //print x to serial monitor } // Interrupt service routine for interrupt 0 void increment() { x++; digitalWrite(ledPin, HIGH); }
Before running this code, make sure to connect a button to pin 2 and the other side of the button to ground. The main loop of this program turns off the LED every 3 seconds. Meanwhile, this program watches digital pin 2 (which corresponds to interrupt 0) for a falling edge. In other words, it looks for a voltage change going from logic high (5V) to logic low (0V), or ground, which happens when the button is pressed. When this happens the function increment is called. The code within this function is executed, variable x is incremented, and the LED is turned on. Then the program returns to where ever it was in the main loop. If you play around with it you'll notice that the LED stays on for seemingly random amounts of time but never longer than 3 seconds. How long the LED stays on depends on where you interrupted the code in the main loop. For example, if the interrupt was triggered right in the exact middle of the delay function, the LED would remain lit for about 1.5 seconds after you hit the button.
What are the advantages of this method?
At this point you might wonder, why use an interrupt at all? Why not just occasionally use a digitalRead on pin 2 to check its status? Won't that do the same thing? The answer depends on the situation. If you only cared what the status of the pin was at a certain point in your code or time frame, then a digitalRead will probably suffice. If you wanted to continuously monitor the pin, you could poll the pin frequently with digitalReads. However, you could easily miss data in between reads. This missed information could be vital in many real time systems. Not to mention, the more often you're polling for data, the more processor time that is being wasted doing that rather than executing useful code.
Take a car for instance...
Let's take the system that monitors and controls the anti-lock braking of a car as a critical timing example. If a sensor detects the car is about to lose traction, you really don't care about what part of program is currently being executed, because something needs to be done about this situation immediately to assure the car retains traction and hopefully avoids a car wreck or worse. If you were just polling the sensor in this situation, the sensor may be polled too late and the event could be missed entirely. The beauty of interrupts is that they can prompt execution immediately, when it's necessary.
A quick check can solve the matter
One common problem with interrupts is they often can trigger multiple times for a single event. If we run the code above, you'll notice that even if you press the button just once, x will increment many times. To explore why this happens, we have to take a look at the signal itself. If we took an oscilloscope to monitor the voltage of the pin at the moment we pressed the button, it would look something like this:
While the main transition of the pin is from high to low, during the process, there are several spikes which can cause multiple interrupts. There are several ways to remedy this. Often you can fix it with hardware by adding an appropriate RC filter to smooth the transition or you can fix it in software by temporarily ignoring further interrupts for a small time frame after the first interrupt is triggered. Going back to our old example, lets add in a fix that allows the variable x to only be incremented once each button press instead of multiple times.
/* Simple Interrupt example 2 by: Jordan McConnell SparkFun Electronics created on 10/29/11 license: Beerware- feel free to use this code and maintain attribution. If we ever meet and you are overcome with gratitude, feel free to express your feelings via beverage. */ int ledPin = 13; // LED is attached to digital pin 13 int x = 0; // variable to be updated by the interrupt //variables to keep track of the timing of recent interrupts unsigned long button_time = 0; unsigned long last_button_time = 0; void setup() { //enable interrupt 0 which uses pin 2 //jump to the increment function on falling edge attachInterrupt(0, increment, FALLING); Serial.begin(9600); //turn on serial communication } void loop() { digitalWrite(ledPin, LOW); delay(3000); //pretend to be doing something useful Serial.println(x, DEC); //print x to serial monitor } // Interrupt service routine for interrupt 0 void increment() { button_time = millis(); //check to see if increment() was called in the last 250 milliseconds if (button_time - last_button_time > 250) { x++; digitalWrite(ledPin, HIGH); last_button_time = button_time; } }
This fix works because each time the interrupt handler is executed, it compares the current time retrieved by the functionmillis() with the time the handler was last called. If its within a certain defined window of time, in this case a fourth of a second, the processor immediately goes back to what it was doing. If not, it executes the code within the if statement updating the variable x, turning on the LED and updating the last_button_time variable so the function has a new value to compare to when its triggered in the future.
Straight to the point
Be aware though that it is important to keep the interrupt handler as short as possible because it prevents the processor from running its normal code, prevents other interrupts of lower priority, and could be called quite often depending what input you have connected. It's best to just update status variables and then leave, and let your normal program do the rest of the necessary work. Also, on that note, the function delay() does not work within interrupt handlers and should not be used.
Special cases
Its possible to reassign interrupts using the attachInterrupt() function again, but its also possible to remove them by calling the function detachInterrupt(). If there is a section of code that is time sensitive, and it's important that an interrupt is not called during that time frame, you can temporarily disable interrupts with the function noInterrupts() and then turn them back on again afterward with the function interrupts(). This definitely comes in handy on occasion.
Thanks for reading
Well, we have learned a bit about the basics of interrupts and hopefully have realized how useful they can be. Consider integrating interrupts into your next project and always feel free to share them with us! You may just get a front page post out of it.
Comments
Post a Comment