Tuesday, June 7, 2011

blinky lights with interrupts

In the previous post, I kind of glossed over the problem with using the built in delay function. I said that it would be better to use interrupts so I'll go more in depth about that.

The problem with using the built in delay is that CPU clock cycles are essentially wasted. In such a simple program like we had in the previous post, we are not worried about conserving clock cycles but you can imagine that in a very complex program you would want every clock cycle to do something. Once interrupts are set up and enabled, they will "interrupt" the program when a certain condition is met. In the case of delay, a timer counts clock cycles and once a certain number is reached, the program is interrupted and carries out a "service routine" specified by the programmer. Once the service routine is finished, the program returns to the line of code it was executing when the interrupt occurred.

To illustrate the difference between the two types of delays, I wrote a program that does the exact same thing as the program in the previous post but it uses interrupts rather than the built in delay. This program simply makes the built in LEDs turn on and off at a rate that is perceptible to the human eye:
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void)
{
    DDRB = 0xFF;        //PORT B (LEDs) output
    TCNT0 = 0;          //timer starts at zero
    OCR0 = 200;         //and counts up to 200
    TCCR0 = 0b00001101; //CTC mode, internal clk, 1024 prescaler

    sei();              //global interrupt enable
    TIMSK = (1<<OCIE0); //timer0 overflow interrupt enable
 
    while(1)
        asm("nop");     //stay here....
}

ISR (TIMER0_COMP_vect)
{
    PORTB ^= 0xFF;      //toggle PORTB (LEDs)
}



So you can load this program using the same method as I showed before and see that the LEDs simply turn on and off. All the connections should be exactly the same.

Some notes on the program: we first set up timer0 by setting TCCR0 to the binary 0001101. Of course we could express this in hex or even decimal but for me, the binary makes it easier to understand whats going on. Using the 8 bits of the TCCR0 register, you can specify whether you want "normal mode" (based on overflow) or "CTC mode" (based on compare match). You can also specify a prescaler value which is multiplies the number of clock cycles needed for a count. In other words, a large prescalar makes a delay longer. For our purposes, we'll set TCCR0 for CTC mode and have a prescaler of 1024. TCNT0 is the register which counts upwards with the clock cycles so we want this to start at zero. OCR0 is the value that TCNT0 will count up to before the interrupt occurs, so we will set this to 200. Note that since this is an 8 bit register, the largest value possible is 255. The sei() command sets the global interrupt bit and TIMSK enables the various different conditions that can cause an interrupt. The one we want is OCIE0 (timer0 overflow) which is actually bit _. Finally, the asm("nop") is not necessary for the program to work but it makes it easier to simulate the program in the AVR Simulator.

Speaking of the AVR Simulator or debugger in AVR Studio, lets take a look at how to make sure the program works without even loading it on the AVR. After you have entered all the code and hit F7 for build, go to Debug on the top menu and select Start Debugging.

First notice that a yellow arrow is pointing to the first executable line of your code. If you hit F11, you will see that the arrow moves to the next line. This is called stepping through the code. One subtle thing to notice is that F10 steps OVER lines of code while F11 steps INTO lines of code. This comes into play when you have subfunctions within your main() function. You can step INTO the subfunctions or step OVER them. Anyway, lets look at the I/O view on the right side of the screen.

The I/O view should pop up automatically but if not you can hit Alt+5 to bring it up. From this view you can see that status of all the registers in the AVR which can be extremely useful. For our program, click on the TIMER_COUNTER_0 and check out the registers we were talking about before (OCR0, TCCR0, TCNT0 and TIMSK). Now step through the program and once you get to the asm("nop"), notice how the arrow just stays here every time you hit F11. This is because you are in a while(1) loop, so this would normally just go on forever but we have interrupts now so once the compare match happens we will escape the loop and perform the service routine. If you hit F11 a bunch of times you will see that TCNT0 is increasing slowly. This is because of the prescaler. If there was no prescaler, each clock cycle would increase TCNT0. As you can see, it will take a lot of cycles to get TCNT0 to 200 and see the service routine in action, but eventually you can get there.

So thats about all I'm going to get into. Of course there is a lot more to this subject but this at least illustrates one example.

1 comment:

  1. If the main lights are blinking and very low in voltages then how would people travel. Because a blinking light can be very dangerous.

    ReplyDelete