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:
- timer1 proudly prints "***** Timer1 Overflow ****" to the screen.
- timer0 increments a global variable called lights and sends it out to PORTA for a nice light show
- timer0 also tracks a value called timer0_ticks and at the correct time prints the number of seconds that have elapsed to the screen
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.