Interrupts in AVR
What are Interrupts?
One of the most fundamental and useful principles of modern embedded processors are interrupts. An interrupt is a way for an external (or, sometimes, internal) event to pause the current processor's activity, so that it can complete a brief task before resuming execution where it left off.
Let's say we are at home, writing an excellent tutorial on how a principle of modern embedded processors works. We are very interested in this topic, so we are devoting all our concentration to our keyboard. However, half way though, the phone rings. Despite not being by the phone waiting for a call, we are able to stop what we are doing, take the call, and go back where we left off once we have hung up.
Consider you’re at home watching an excellent movie(HD print). You are very much interested in watching the end of the movie and suddenly the doorbell rings. Now you have to pause your movie, get up from your chair, open the door and then resume the movie form where you left it.
This is how a microprocessor’s interrupt works. We can tell the processor to look for some specific specific external event (like data reception complete or pin changing its state like going from high to low) to become true. While processor checks for these events we can parallely do some other job. When these events occur, we stop the current task, handle the interrupt, and resume back where we left off. This gives us a great deal of flexibility. Rather that constantly checking the FLAG bit via code we can trust interrupts to do the job of checking.
What we are doing is called asynchronous processing - that is, we are processing the interrupt events outside the regular "execution thread" of the main program. The point to be noted here is that we aren’t doing parallel programing (- running two or more codes simultaneously) but we are just pausing the current program and resuming to it after handing the interrupt.
We can link a specific interrupt source to a specific handler routine, called an Interrupt Service Routine, or ISR for short.
Interrupts available in AVR
There are two main sources of interrupts:
- Hardware Interrupts : which occur in response to a changing external event such as a pin going low, or a timer reaching a preset value
- Software Interrupts : which occur in response to a command issued in software
In 8-bit AVRs the software interrupts are not available, which are basically used for generating user defined Exceptions and handling them as and when they occur.
Each microcontroller has a set of interrupt sources available.
Some of the interrupts available in ATmega16 are as follows:
- External Interrupt
- Timer Interrupt
- USART Receive and Transmit Interrupt
- EEPROM Ready Interrupt
- ADC conversion complete
How do we handle Interrupts?
The method of handling interrupts differs in different languages.
For an Interrupt to fire ISR three things must be true
- The AVR's global Interrupts Enable bit must be set to one in the microcontroller control register SREG. This allows the AVR's core to process interrupts via ISRs when set, and prevents them from running when set to zero. It is like a global ON/OFF switch for the interrupts. By default this bit is zero.
- The individual interrupt source's enable bit must be set. Each interrupt source has a separate interrupt enable bit in the related peripherals control registers, which turns on the ISR for that interrupt. This must also be set, so that when the interrupt event occurs the processor runs the associated ISR.
- The condition of the interrupt must be me - for example when the adc conversion is complete then only it will fire ADC conversion interrupt.
When all three conditions are met, the AVR will fire our ISR each time the interrupt event occurs.
The C code for writing the ISR is as follows
pseudo C code:
// ISR code to execute here
How do we enable an interrupt?
If you simply add an ISR to your existing program, you will find that it appears to do nothing when you try to fire it. This is because while we have defined an ISR, we haven't enabled the interrupt source.
Firstly, we need to set the I bit in the SREG register. This is the Global Interrupt Enable bit, without which the AVR will simply ignore any and all interrupts.
In C, we can use predefined library functions. In the case of AVR-GCC we just use the sei() and cli() macro equivalents defined in <avr/interrupt.h>:
pseudo C code:
sei(); // Enable Global Interrupts
cli(); // Disable Global Interrupts
Now, we need to enable a specific interrupt source, to satisfy the second condition for firing an ISR. The way to do this varies greatly between interrupt sources, but always involves setting a specific flag in one of the peripheral registers. Let's set an interrupt on the USART Receive Complete (USART RX) interrupt. According to the datasheet, we want to set the RXCIE bit in the UCSRB register:
pseudo C code:
UCSRB |= (1 << RXCIE);
After sei(); and setting RXCIE the ISR will code will run when the data receive is complete.
Some Important Points
There are a few things to keep in mind when using interrupts in your program:
- Sometimes it may happen than lot of interrupts are fired within a short period of time. It may happen that your ISR code isn’t complete and next interrupt is fired. The interrupt code and the main code don't run parallely. It may happen that the compiler doesn’t escape from the ISR code and infinite loop of interrupts occur due to which your main code will run slow or may halt.
When interrupt is fired the compiler tries to execute the ISR code quickly. Normally when you change the value of a variable the compiler quickly stores its value into temporary memory and then later on stores it into permanent memory. The latter process take time. When you change a variable’s value in ISR the value is stored in temporary memory and when the ISR code ends it does not have the time to copy it to permanent memory and thus the value is lost. If the variable is declared to be volatile, then the new value is directly copied to the permanent memory.
pseudo C code:
#include <avr/interrupt.h>int MyValue;
SetupInterrupts(); // Function for initializationwhile (MyValue == 0); // Wait for interrupt
Since the Variable here is not volatile the value of “MyValue” does not change when the interrupt is fired and thus the code runs into an infinite loop
If the variable had been defined as volatile then this error will not occur.
- Each interrupt source has exactly one flag to indicate that the event occurred. If two or more of the same interrupt event occurs while you are in the middle of processing an ISR, you will only receive one ISR call for the event when the current ISR terminates, and the remaining interrupt events will be lost.
Now we have learnt what are interrupts in general. In the next tutorial we will see how to implement specific interrupts like ADC Conversion Complete Interrupt, External Interrupts, Timer Interrupts etc.