Вы находитесь на странице: 1из 11

BASICS OF INTERRUPTS AND APPLICATIONS

(Make your own STOPWATCH)


Interrupts are basically events that require immediate attention (high priority) by the
microcontroller. When an interrupt event occurs the microcontroller pause its current task
and attend to the interrupt by executing an Interrupt Service Routine (ISR) at the end of
the ISR the microcontroller returns to the task it had pause and continue its normal
operations.
Interrupt can be called either by a hardware or software. You will learn about the various
types in the coming pages
You might have also heard of the word polling. Polling is the continuous checking of
microcontroller on the status of its I/O pins.
These are the two ways by which devices receive services from microcontroller.
Both of these are methods of controlling the
I/O pins of the microcontroller. In the figure
aside loop is the POLLING part and pressing
button and turning the LED on/off is the
INTERRUPT.
In other words we can say that in polling work
is done based on when microcontroller
requires something where as in interrupt a
work is done when the requirement is from
user side. USING ONLY POLLING IS NOT
AN
EFFICIENT
USE
OF
MICROCONTROLLER.
Consider a Computer, it has a mouse and keyboard. How does is work, by polling or
interrupt? Whenever you move the mouse it gets updated on the screen on the screen. So it
must be interrupt correct!!... But the answer is NO, it is polling. Your display refreshes at a
rate of 50-60 Hz, so it is more than sufficient to update the mouse pointer position when the
screen is refreshed. The microprocessor of your computer reads the value from the mouse
only at those instants alone. If it would have been using interrupt, the interrupt would be
called at a very faster rate (as even when mouse is kept stationary some changes in the
readings are observed) which would slow down other processes. Coming to keyboard it
works using interrupts. Every time you press a key it does a job rather than checking
continuously whether a key is pressed or not. This is why when you press ALT+CTRL+DEL
the task manger opens up regardless of what processing is going on. This interrupt has the
highest priority.
Another example would be switching on/off of lighting systems. You would have seen in
offices/movies that based on whether people are there in a particular location or not the
lightings turn on/off automatically. This is by polling. The presence of human is continuously
checked. Where as in most places we use switches to turn lightings on/off manually. This
would be interrupts.
Now that you would be clear about what an interrupt is lets move on to learn how to use it in
our microcontroller ATMEGA8
First how does it perform the interrupt?
Upon activation of interrupts the microcontroller goes through the following stages:
1. It finishes the current instruction and saves the address of next instruction on the stack.
2. It jumps to a fixed memory location called interrupt vector table and it redirects the mc
(microcontroller) to the address of ISR( Interrupt Service Routine).
3. It executes it till the last instruction of ISR, which is RETI (RETurn from Interrupt).
1

4. After RETI is executed, it returns to place it was interrupted and then starts performing
instructions from the address stored on the stack.
This is the interrupt vector table.

Here we would be primarily discussing about TIMER interrupts and EXTERNAL interrupts.

FIRST of all the interrupt needs to be enabled. Interrupts must be enabled by mc in order to
respond to the interrupts. The 8th bit (D7) of the SREG (Status REGister) is responsible for
enabling or disabling the interrupts globally.

Steps to enable interrupt;


1. D7 bit of SREG must be set high. This is done by calling a inbuilt function SEI() (SEt
Interrupt). This is the master switch
2. Now each Interrupt has its own enable switch i.e. the required interrupt must be enabled by
setting corresponding bits in various registers to 1. We will see this in coming pages.

TIMSK Timer/counter Interrupt MaSK register.

Timer 0 - BIT 0
Timer 1 - BIT 2, BIT 3, BIT 4, BIT 5
Timer 2 - BIT 6, BIT 7
TOIEx : Timer x Overflow Interrupt Enable
When this bit along with D7 of SREG is set to one this interrupt is enabled.
The TOVx (Timer OVerflow flag) in TIFR (Timer Interrupt Flag Register) is set to 1 when the
timer rolls over. When this flag is set the mc is directed to the corresponding Interrupt Vector
(Refer the Interrupt Vector Table).
OCIEx : Timer x Output Compare Interrupt Enable
When this bit along with D7 of SREG is set to one this interrupt is enabled.
As Timer 1 is a 16-bit Timer it has OCIE1A and OCIE1B where as Timer 2 is a 8-bit one, so
it has only OCIE2. Timer 0 doesnt support this feature.
Using Overflow interrupt give us the only option of interrupt being called only at roll over
time. But this interrupt give us the option of calling the interrupts at any time before the roll
over.
The count of the timer is available in TCNTx (Timer/CouNTer register). For this the timer is
run in CTC mode (Clear Timer on Compare match). The TCNTx value is compare with the
value given by user to OCRx (Output Compare Register). When the match occurs a
corresponding flag in TIFR is set and interrupt is called.
TICIE1: Timer1 Input Capture Interrupt Enable
When this bit along with D7 of SREG is set to one this interrupt is enabled.
We are not discussing about this here.

External Interrupts:
ATMEGA8 has 2 pins for this purpose PD2 (INT 0) and PD3 (INT 1). Upon activation of
these the mc is interrupted in whatever it is doing and jumps to perform the ISR.
Before these are used these must be enabled.

GICR General Input Control Register

We require only BIT 6 and BIT 7.


INTx: external INTerrupt request x enable
When this bit along with D7 of SREG is set to one this interrupt is enabled.
The Interrupt Sense Control bits (ICSx0 and ICSx1) in MCUCR (MCU general Control
Register) define when the interrupt has to be called. Again this has a register like TIFR
called GIFR (General Interrupt Flag Register) in which INTFx (external INTerrupt Flag) is set
high when the condition set by ICSxx bits have been satisfied.
3

MCUCR MCU general Control Register;

We require BIT 0, BIT 1, BIT 2, and BIT 3 only.


This is for the interrupt due to INT0 (PD 2).

It is same for INT1 with ISC01 being replaced by ISC11 and ISC00 by ISC10.
Case 1: (ISCx1 == 0) && (ISCx0 == 0)
The interrupt here would be triggered whenever INTx receives a logic low. This is
called as low level triggered interrupt.
Case 2: (ISCx1 == 0) && (ISCx0 == 1)
The interrupt here would be triggered whenever there is a change from logic high to
low and vice versa in INTx. This is called as edge triggered interrupt.
Case 3: (ISCx1 == 1) && (ISCx0 == 0)
The interrupt here would be triggered whenever there is a change from logic high to
low in INTx. This is known as negative edge triggered interrupt.
Case 4: (ISCx1 == 1) && (ISCx0 == 1)
The interrupt here would be triggered whenever there is a change from logic low to
high in INTx. This would be positive edge triggered interrupt.
NOTE: There is no case to trigger an interrupt when there is a logic high.
Now you may have a doubt popped up in your mind. There are so many Interrupts
present in the ATMEGA8. What will happen if two or more interrupts are called
simultaneously???
You would have seen the interrupt vector table at the beginning. It has the address
location for each interrupt. Lower the address the higher the priority. So, if two interrupts are
called at the same time the one with higher priority takes place.
What will happen if the mc is executing an interrupt and another interrupt is called???
When an interrupt is called the D7 bit of SREG is set to 0, causing all other interrupts to be
disabled, and no other interrupt occurs (WHY???). When the RETI instruction is executed
the D7 bit of SREG is set back to 1, enabling the interrupts. If you want an interrupt to be
triggered inside an interrupt you need the set the D7 bit of SREG as 1 using sei(); inside
that interrupt. But be careful with this as this can lead to infinite looping case which
can even lead to crashing of your mc.
4

For e.g. A low level triggered hardware interrupt is under progress and you have
enabled interrupts inside that. As it is low level triggered the same interrupt will be called
again by itself and this would go on leading to stack overflow and finally unpredictable
consequences.

Now lets move on to build our own stopwatch using ATMEGA8.


Hardware Required:
1. ATMEGA 8 Development Board with USB cable
2. Switch or IR/Photodiode Proximity Sensor
3. Jumpers
NOTE: If u are using a switch would need to use pull up or pull down resistor
Concepts Used:
1. General I/O
2. Timers and Timer Interrupts
3. External Hardware Interrupts
4. Serial Communication ( USART ) To display time
Refer Tutorials for Timers and Serial Communication using ATMEGA8 before proceeding.

To build a stopwatch we first need a clock which can measure minutes, seconds and
milliseconds.
The ATMEGA8 used has an external clock with frequency 16MHz. We need to select a prescalar for the clock to suit our purpose. Pre-scalar selected is 64 which would give us a
frequency of 16MHz / 64 = 250KHz. This implies one count takes 4s i.e.0.004ms.

We need a least count of 1ms, so it would take 250 counts (250 x 0.004ms = 1ms).
Therefore each time the clock counts 250 the Timer Interrupt should be called to
increment a variable named millisecond by 1 and then roll back the counter to 0 to
repeat the process back again.
Hence we would be using the CTC mode of Timer1 with corresponding Output
Compare Interrupt Enabled. The following would the statuses to various registers.
For Pre-scalar 64: CS10 of TCCR1B = 1
CS11 of TCCR1B = 1
CS12 of TCCR1B = 0
For CTC mode: WGM10 of TCCR1A = 0
WGM11 of TCCR1A = 0
WGM12 of TCCR1B = 1
WGM13 of TCCR1B = 0
For roll over at 250: OCR1A = 250
For Timer Output Compare Interrupt Enable: OCIE1A of TIMSK = 1
Function header for Interrupt: ISR(TIMER1_COMPA_vect)

Lets start writing the code:


This is the function to initialize the Timer1
void Init_timer()
{
TCCR1B = (1<<WGM12)|(1<<CS11)|(1<<CS10);
OCR1A = 250;
}

This is the function to initialize the Timer Interrupt


void Init_interrupt()
{
TIMSK |= (1<<OCIE1A);
}
Interrupt service routine for Timer Interrupt
clock_millisecond, clock_second, clock_minute are global variables to count the time.
ISR(TIMER1_COMPA_vect)
{
clock_millisecond++;
if(clock_millisecond==1000)
{
clock_second++;
clock_millisecond=0;
if(clock_second==60)
{
clock_minute++;
clock_second=0;
}
}
}
Now that we have a clock lets add start and stop buttons using Ext. Hardware Interrupt in
order to make the stopwatch. The switch here is a proximity sensor. Each odd tap on the
sensor would be start and even ones will the stop. The output of sensor is given to INT0
which is PD2 on ATMEGA8 and the trigger would be rising edge.
Enabling External Hardware Interrupt: INT0 of GICR = 1
For Rising Edge Triggered: ISC00 of MCUCR = 1
ISC01 of MCUCR = 1
Function header for Interrupt: ISR(INT0_vect)
Conti the code
Setting up the Interrupt (Along with previous one):
void Init_interrupt()
{
GICR |= (1<<INT0);
MCUCR |= (1<<ISC00)|(1<<ISC01);

//PD2 Enable External Hardware Interrupt


//Rising edge triggered

TIMSK |= (1<<OCIE1A);
}
6

Interrupt Service Routine for Ext. Interrupt:


ISR(INT0_vect)
{
if(flag == 0)
{ clock_millisecond = 0;
clock_second = 0;
clock_minute = 0;
flag = 1;
init();
}
else if(flag == 1)
{ ms = clock_millisecond;
s = clock_second;
m = clock_minute;
display();
flag = 0 ;

// Starting the stopwatch

// Stopping the stopwatch

}
}
The final time stored in the variables m, s, ms.

We are now ready to measure time. But wait. Where do we display the time? We will take
the help to Serial Communication to display the timings on laptop using a terminal software
called RealTerm on laptop. You can even do it using LCD display.

The code for this goes like this:

Initializing USART:
void USARTInit(uint16_t ubrr_value)
{
UBRRL = ubrr_value;
//Setting Baud rate = 19200
UBRRH = (ubrr_value>>8);
UCSRC = (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1)

UCSRB = (1<<RXEN)|(1<<TXEN);

// Async. Mode, No Parity, 1 Stop Bit,


Char Size 8

//Enable The receiver and transmitter

Sending a data to laptop:


void USARTWriteChar(char data)
{
while(!(UCSRA & (1<<UDRE)))
{
//Do nothing
}
UDR=data;
}

//Wait until the transmitter is ready

//Now write the data to USART buffer

Sending time to laptop:


void display()
{
convert_digits();
for(i=(c-1) ; i>=0 ; i--)
USARTWriteChar(m1[i]);
USARTWriteChar(':');
for(i=(b-1) ; i>=0 ; i--)
USARTWriteChar(s1[i]);
USARTWriteChar(':');
for(i=(a-1) ; i>=0 ; i--)
USARTWriteChar(ms1[i]);
USARTWriteChar(' ');
}

Now, we can display the time but the time variables are in integer format (2 bytes) but we
can send only 1 byte at a time via USART. So, we have to split the integer to digits.

To initialize the array variables which store the digits of time as 48 (ASCII value for 0) to
avoid junk values:
void init()
{
for(int k=0; k<5; k++)
{
ms1[k] = 48;
m1[k] = 48;
s1[k] = 48;
}
}

Converting the numbers to digits: ( a,b,c are the counter variables for no. of digits)
void convert_digits()
{ a = 0;
while(ms != 0)
{ ms1[a] = ms%10 + 48;
ms = (ms/10);
a++ ;
}
b = 0;
while(s != 0)
{ s1[b] = s%10 + 48;
s = (s/10);
b++ ;
}
8

c = 0;
while(m != 0)
{ m1[c] = m%10 + 48;
m = (m/10);
c++;
}
if(a == 0)
a = 1;
if(b == 0)
b = 1;
if(c == 0)
c = 1;
}
Now that all modules are complete lets have a look at the complete code.
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// For using interrupts functions


// For delay function
//Global variable for the clock system

int clock_millisecond=0;
int clock_second=0;
int clock_minute=0;
int flag = 0;

void Init_timer()
{
TCCR1B=(1<<WGM12)|(1<<CS11)|(1<<CS10);
OCR1A=250;
}
void Init_interrupt()
{ GICR |= (1<<INT0);
MCUCR |= (1<<ISC00)|(1<<ISC01);
TIMSK|=(1<<OCIE1A);

//PD2 Enable External Hardware Interrupt


//Rising edge triggered

//Enable the Output Compare A interrupt

}
void USARTInit(uint16_t ubrr_value)
{
UBRRL = ubrr_value;
//Set Baud rate
UBRRH = (ubrr_value>>8);
UCSRC=(1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1); //Async. Mode, No Parity, 1 Stop Bit, Char Size 8
UCSRB=(1<<RXEN)|(1<<TXEN);

//Enable The receiver and transmitter

void USARTWriteChar(char data)


{
while(!(UCSRA & (1<<UDRE)))
{
//Do nothing
}
UDR=data;
}

//Wait until the transmitter is ready

//Now write the data to USART buffer

ISR(TIMER1_COMPA_vect)
{
clock_millisecond++;
if(clock_millisecond==1000)
{
clock_second++;
clock_millisecond=0;
if(clock_second==60)
{
clock_minute++;
clock_second=0;
}
}
}
int ms,s,m;
// Storing final time
int ms1[5],s1[5],m1[5]; // Storing time as digits
int a,b,c;
// Counter for no. of digits;
int i;

void init()
{
for(int k=0; k<5; k++)
{ ms1[k] = 48;
m1[k] = 48;
s1[k] = 48;
}
}
void convert_digits()
{ a = 0;
while(ms != 0)
{ ms1[a] = ms%10 + 48;
ms = (ms/10);
a++ ;
}
b = 0;
while(s != 0)
{ s1[b] = s%10 + 48;
s = (s/10);
b++ ;
}
c = 0;
while(m != 0)
{ m1[c] = m%10 + 48;
m = (m/10);
c++ ;
}
if(a == 0)
a = 1;
if(b == 0)
b = 1;
if(c == 0)
c = 1;
}

10

void display()
{
convert_digits();
for(i=(c-1) ; i>=0 ; i--)
USARTWriteChar(m1[i]);
USARTWriteChar(':');
for(i=(b-1) ; i>=0 ; i--)
USARTWriteChar(s1[i]);
USARTWriteChar(':');
for(i=(a-1) ; i>=0 ; i--)
USARTWriteChar(ms1[i]);
USARTWriteChar(' ');
}
ISR(INT0_vect) // Ext. Hardware Interrupt 0
{
if(flag == 0)
{ clock_millisecond = 0;
clock_second = 0;
clock_minute = 0;
flag = 1;
init();
}
else if(flag == 1)
{ ms = clock_millisecond;
s = clock_second;
m = clock_minute;
display();
flag = 0 ;
}
}

void main()
{
DDRD = 0b00000000;
DDRB = 0b11111111;

// PORTD as input
// PORTD as output

Init_timer();
Init_interrupt();
USARTInit(51);

// Initializing the timer


// Initializing the timer
// Initializing USART ,UBRR = 51

_delay_ms(2000);
PORTB = 0xff;

// Waiting for 2 seconds to make sure everything is initialized


// To indicate that the stopwatch can be started to use

sei();

//Enable interrupts globally

while(1)
{
}
}

THANK YOU

11

Вам также может понравиться