Microcontroller - A Beginners Guide - Introduction to Interrupts - Using the Timer/Counter
as an Example
Since our brains are still warm on the subject of timers and counters, we will investigate
how interrupts work using the counter as an example. Interrupts really enhance the
use of microcontrollers ina big way. Interrupts make your programs react to the
hardware of the microcontrollers, which may be a reaction from the circuit/evironment
outside of the microcontoller.
Basically, an interrupt is exactly defined by it's title... it interrupts the normal
program flow to do something else (other code block that you program). Imagine your
program is doing its normal blinking LEDs or something and you want it to react
to, say, a PIR (Passive Infra Red) sensor (connected to an interrupt pin), and goes
to a special code block to, say, make a beep from a buzzer connected to another
pin. If you tell the microcontroller to do this, it will stop the program in its
tracks and go to the code that belongs to that interrupt (make a beep). After the
interrupt code is executed, the program continues exactly where it left off. In
some cases, interrupts would be an alternative to polling something, which requires
program cycles. For example, say your program is wanting to only beep when a person
passes the PIR sensor. Your program could keep testing that pin to see if it has
a high reading over and over within the never ending loop (while(1)). Instead, remove
all of the polling program code to test the pn for a high reading and let the interrupt
automatically go to the beep code when the micocontroller senses the high reading.
So, what types of interrupts are available for the Arduino Microcontroller? Interrupts
can be established for events such as a counter's number, a pin changing state (from
low to high or high to low), serial communication receiving of information, or the
Analog to Digital having established a conversion.
Here is a full list of interrupt vectors that you can use. Other tutorials
will make use of many of these interrupt vectors.
We are going to use the timer/counter as an example. We will inform the timer/counter
of a number that the TCNT1 (the couter) will need to match. The number to match
will go into a register called OCR1A (Output Compare Register). The "1" represents
the specific counter we are using which is the 16-bit version. We have two OCRs,
the A and the B. We will use A for this example. However, do we know if the counter
will reset to zero when th match is made? We definitely want the TCNT1 to go back
to zero so the count will start over and we get another match at our intended number,
but this isn't done in code like we did last time (remember the TCNT1 = 0;). This
time, we will want to turn on another switch in the TCCR1B control register called
WGM12 (waveform Generation Mode for timer 1). The #2 in that switch just represents
which WGM it is, since there are a few. We will also be using the CS10 and CS11
again to set the prescaling to 64 as in the
intro to timers video.
Then the timer/counter will need to know that we intend to use the interrupt feature.
This is done through the TIMSK (Timer/Counter Interrupt Mask Register). We only
need to turn on one switch in this register: the OCIE1A (Output Compare A Match
Interrupt Enable) switch. Once the number put into the OCR1A is matched by the counter,
the program will be interrupted to toggle the LED. For an interrupt to happen, we
will need to enable the global interrupts "sei()", then we will need to enable the
interrupt for the timer/counter, and finally the interrupt service routine (ISR)
will need to be created. The interrupt service routine is just like the functions
that were made in the button game example. The
interrupt service routine is just a code block outside of the main routine and begins
with ISR with the vector within parenthesis "(vector)", ollowed by the block of
code within brackets"{Code}". For the timer/counter (16-bit version), and the fact
that we are using the A version of the OCR1, the vector name is: TIMER1_COMPA_vect.
So the routine would be: ISR(TIMER1_COMPA_vect) { code to execute }.
We used a number in the intro
to timers video that represented one second: 15625, so we will use this
one for the OCR1A. But, the number should consider indexing from 0, so we will need
to use the number 15624 instead.
Let's put all of this information together in a program:
#include <avr/io.h>
#include <avr/interrupt.h>
int main(void)
{
sei();
DDRB |= 1<<PINB0;
TCCR1B |= 1<<CS10 | 1<<CS11 | 1<<WGM12;
TIMSK |= 1<<OCIE1A; //If using atmega324, this regester is TIMSK1
OCR1A = 15624;
while(1)
{
}
}
ISR(TIMER1_COMPA_vect)
{
PORTB ^= 1<<PINB0;
}
Here is the pseudocode version:
- Include some basic information to properly configure our microcontroller.
- Include the interrupt library that has all the interrupt functions we need.
- Start the main routine (do the indented stuff)
- Enable the global interrups
- Since we are using an LED, enable that pin to be output
- Turn on the timer and use 64 prescaling (skip 64 clock ticks)
- Enable the output compare register and set a number to represent one second
- Loop Forever
- No code needed in the forever loop this time
- End main routine
- Start Interrupt routine (for the correct vector)
- Toggle the pin to which the LED is connected
- End Interrupt routine (go back to where the program left off)