AVR Advanced Timer Interrupts

Thumbnail image of Jason Bauer
Jason Bauer
December 19, 2015 (Last Updated: ) | Reading Time: 6 minutes

Now that you have timer interrupts working, let's get to work making them more useful.

STK500 Setup

For this example, make sure that you have your PORTA jumpered to LEDs, as was discussed in our Port Output guide. You will also want to have the second serial port called RS232 SPARE connected to your computer, and a terminal program listening to that port. This example uses 19,200 baud.

Volatile Global Variables

If you are going to modify a global variable in your ISR or you are going to modify it in your main loop and then use it in your ISR, you need to declare that variable as volatile, to tell the compiler to not use the optimized value and assume that it hasn't changed.

// these values are changed in an ISR, so they must be declared as volatile
volatile uint8_t timer0_ticks;

Main Loop

If you want to process code in the foreground, while letting timers do other processes in the background, then put your main code in your main loop. For instance, if you want to respond to SW0 connected to PORTB, you might have the following code in your main loop:

while(true) {
    // check to see if the user has pressed SW0 down
    if(bit_is_clear(PINB,PB0)) {
        // if they have, yell at them
            printf("***** Stop poking me! *****\n");
        // wait for button to be let go, software debounce
        // this loop can be interupted by the timers
        while(bit_is_clear(PINB,PB0)) { }
        // when they finally let go, thank them for their generosity
        printf("***** That's better. *****\n");
    }
}

Let's look at this block of code really close. if (bit_is_clear (PINB,PB0)) checks to see if the pushbutton connected to PB0, which is SW0 has been pressed. If it has not been presssed, code continues on. However, if it has been pressed, then the entire if block gets executed. Inside the if block, we print Stop poking me! at the user, and then enter a while loop that does nothing { } and watches the same bit. As long as the user holds SW0 down, we will never leave that empty { } loop. That's ok, because the timer can still interrupt us to do whatever it has to do, even though we are effectively looping forever. When the user finally let's go of the button, our second while loops ends and we tell the user "That's better." After that, we jump back to the top of our never ending main loop.

Timer Interrupt Routines

The timer interrupt routines are going to be busy every once in a while doing randome tasks. In this program, we have them doing the following:

Timers, Tracking Seconds, and printf

This is a large example of code that uses timers to keep track of how many seconds have elapased, and every second prints the current number of seconds to the USART usingprintf. It also responds to button inputs on SW0 and SW1 in the main loop.

// ********************************************************************************
// Includes
// ********************************************************************************
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <stdbool.h>
// ********************************************************************************
// Macros and Defines
// ********************************************************************************
#define BAUD 19200
#define MYUBRR F_CPU/16/BAUD-1
// ********************************************************************************
// Function Prototypes
// ********************************************************************************
void usart_init(uint16_t ubrr);
char usart_getchar( void );
void usart_putchar( char data );
void usart_pstr (char *s);
unsigned char usart_kbhit(void);
int usart_putchar_printf(char var, FILE *stream)
 
// ********************************************************************************
// Global Variables
// ********************************************************************************
static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar_printf, NULL, _FDEV_SETUP_WRITE);
// these values are changed in an ISR, so they must be declared as volatile
volatile uint8_t timer0_ticks;
volatile uint16_t seconds;
volatile uint8_t lights;
// ********************************************************************************
// Interrupt Routines
// ********************************************************************************
// timer1 overflow
ISR(TIMER1_OVF_vect) {
    printf("***** Timer1 Overflow *****\n");
}
// timer0 overflow
ISR(TIMER0_OVF_vect) {
    // let's make the PORTA lights do something every timer tick
    lights++;
    PORTA=~lights;
    // 61 ticks = 2 seconds @ 8.0MHz
    timer0_ticks++;
    if(timer0_ticks==30){
        // fake it, notice the printf is printing seconds+1
        printf("Its been %u seconds.\n",seconds+1);
    }

    if(timer0_ticks==61){
        timer0_ticks = 0;
        // till you make it, now we increment seconds twice
        seconds++;
        seconds++;
        printf("Its been %u seconds.\n",seconds);
    }
}
 
// ********************************************************************************
// Main
// ********************************************************************************
int main( void ) {
    // Configure PORTA as output
    DDRA = 0xFF;
    // Configure PORTB as input
    DDRB = 0x00;
    // make sure it is high impedance and will not source
    PORTB = 0;
    // setup our stdio stream
    stdout = &mystdout;
    // fire up the usart
    usart_init ( MYUBRR );
    printf("Ready to rock!\n");
    // enable timer overflow interrupt for both Timer0 and Timer1
    TIMSK=(1<<TOIE0) | (1<<TOIE1);
    // set timer0 counter initial value to 0
    TCNT0=0x00;
    // start timer0 with /1024 prescaler
    TCCR0 = (1<<CS02) | (1<<CS00);
    // let's turn on 16 bit timer1 also
    TCCR1B |= (1 << CS10) | (1 << CS12);

    // enable interrupts
    sei();
    while(true) {
        // check to see if the user has pressed SW0 down
        if(bit_is_clear(PINB,PB0)) {
            // if they have, yell at them
            printf("***** Stop poking me! *****\n");
            // wait for button to be let go, software debounce
            // this loop can be interupted by the timers
            while(bit_is_clear(PINB,PB0)) { }
            // when they finally let go, thank them for their generosity
            printf("***** That's better. *****\n");
        }
        // check if the user has pressed SW1 down
        if(bit_is_clear(PINB,PB1)) {
            // clear seconds and the timer0_ticks
            seconds=0;
            timer0_ticks;
        }
    }
}
 
// ********************************************************************************
// usart Related
// ********************************************************************************
void usart_init( uint16_t ubrr) {
    // Set baud rate
    UBRRH = (uint8_t)(ubrr>>8);
    UBRRL = (uint8_t)ubrr;
    // Enable receiver and transmitter
    UCSRB = (1<<RXEN)|(1<<TXEN);
    // Set frame format: 8data, 1stop bit
    UCSRC = (1<<URSEL)|(3<<UCSZ0);
}
void usart_putchar(char data) {
    // Wait for empty transmit buffer
    while ( !(UCSRA & (_BV(UDRE))) );
    // Start transmission
    UDR = data;
}
char usart_getchar(void) {
    // Wait for incoming data
    while ( !(UCSRA & (_BV(RXC))) );
    // Return the data
    return UDR;
}
unsigned char usart_kbhit(void) {
    //return nonzero if char waiting polled version
    unsigned char b;
    b=0;
    if(UCSRA & (1<<RXC)) b=1;
    return b;
}
void usart_pstr(char *s) {
    // loop through entire string
    while (*s) {
        usart_putchar(*s);
        s++;
    }
}
 
// this function is called by printf as a stream handler
int usart_putchar_printf(char var, FILE *stream) {
    // translate \n to \r for br@y++ terminal
    if (var == '\n') usart_putchar('\r');
    usart_putchar(var);
    return 0;
}

You can download the complete source code here. This program compiles to 2,300 bytes (thanks to printf). It generates the following output if left alone:

Ready to rock!
Its been 1 seconds.
Its been 2 seconds.
Its been 3 seconds.
Its been 4 seconds.
Its been 5 seconds.
Its been 6 seconds.
Its been 7 seconds.
Its been 8 seconds.
***** Timer1 Overflow *****
Its been 9 seconds.
Its been 10 seconds.

And if you press and hold SW0 for a second or 2, then release it, you'll get this in your output:

Its been 11 seconds.
***** Stop poking me! *****
Its been 12 seconds.
Its been 13 seconds.
Its been 14 seconds.
***** That’s better. *****
Its been 15 seconds.

Meanwhile, the LEDs connected to PORTA are constantly lighting up in a binary counting pattern. Our next guide is not yet written... Head back to our index of AVR Guides here.

Comments

avatar

Charli - 2015-12-20 19:53:02

reply

Excellent post.

avatar

Thanks - 2015-12-20 22:13:01

reply

It'sreally a nice and helpful piece of information.

avatar

Great article - 2015-12-21 07:20:18

reply

Hiya very nice site!! Man .. Beautiful .. Wonderful .. I will bookmark your blog and take the feeds also? I'm happy to seek out numerous useful info here in the put up, we want work out more techniques in this regard, thanks for sharing.

avatar

Amaechi - 2016-02-24 09:32:22

reply

excellent presentation

avatar

Alvaro Luiz - 2016-05-22 20:42:40

reply

I liked this, very good, very well do. São Paulo - Brazil

Leave a reply

Thank you! Your comment has been successfully submitted. It will be approved as soon as possible.

More from Efundies