Write a program

Creating an AVR program involves two steps: write the code and compile to an uploadable hex file.

As usual, you write your code in your favourite text editor. I find it quite fortunate to be able to write in the C language, which I will roughly outline here.

Simple code example

/* file count_to_3_simple.c for the ATMEGA8 */
#include <inttypes.h>
#include <avr/io.h>

/* This program is too simple to produce nice output */
int main(void)
{
uint8_t x; // an 8 bit unsigned integer

DDRD = 0x03; // use the lowest 2 pins of port D for output (PD0, PD1)
x = 0;

for (;;){
PORTD = x;
x++;
if (x > 3) x = 1;
}

return 0;
}

Token explanations

You will find that the constants are named exactly like the ports and flags described in the datasheets:

DDRD is the direction register for port D. The number 3 in binary notation is 00000011. By setting DDRD to 3, the lowest two pins of port D (PD0 and PD1) are used for output; i.e. you can set both of PD0 and PD1 to either high (5V) or low (0V). Alike DDRD you will find other direction registers in the datasheet: DDRA, DDRB, DDRC, depending on your chip. You can set DDRD to 0xff to use all its pins for output.

PORTD is the output register for port D. Any bits of this register that are set to 1 make the corresponding pins go high (5V). All zero bits pull the corresponding pin down to 0V. Again, there are similar ports to be found in the datasheet: PORTA, PORTB, PORTC...

`for(;;){ .. }' just loops forever. You can use any kinds of loops known in the C programming language.

What it does

This program first sets the lowest two pins of Port D to output mode and then enters an infinite loop. The loop keeps counting from 1 to 3, each time displaying the number in binary format using the LEDs you might have connected to pins PD0 and PD1. But the problem with this program is that the chip is running at MHz speeds, and counting to 3 does definitely take it less than a thousandth of a second. Your LEDs will appear to be on constantly.

If you want to connect eight LEDs, you could make an 8-bit wide binary count, which is pretty neat to look at for a limited time.

For any (accurate) timing needs, use...

Timer/counter interrupts

Here is a slightly larger program counting to 3 in human readable form, using the Timer/Counter 1B. It assumes that a 4 MHz Quartz is connected and timing the chip. For illustration, the 4 MHz are first divided by 256 using the chip's prescaler. Thus (4 million / 256 =) 15625 timer counter ticks will pass, pretty precisely, for each second.

/* file count_to_3_interrupt.c for the ATMEGA8 */

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define LEDTIME 15625
// with 4MHz and a prescaling of 256: 15625 = 1 second
// This will only work with a 16bit timer. An 8bit timer will only
// ever count to 255, so you'd have to do some more counting.

volatile uint8_t state;

SIGNAL (SIG_OUTPUT_COMPARE1B)
{
state ++;
if (state > 3) state = 1;

PORTD = state;

OCR1B += LEDTIME;
}

int main(void){

TCCR1B = _BV (CS12); // 4 MHz clock div 256 makes 15625 Hz
TCNT1 = 0;

OCR1B = LEDTIME; // first timer 1B compare value ...

DDRD = 0x03;
state = 1;

TIMSK = _BV (OCIE1B); // ... for the compare interrupt

sei (); // enable interrupts

for (;;); // endless loop

return 0;
}

Token explanations

volatile simply means that the code should be compiled in such a way that the variable is never assumed to keep the same value. I do this because the variables are used both in the main program and in the interrupt function.

The SIGNAL makro defines an interrupt function. Which interrupt name corresponds to which function can be looked up in the avr-libc manual in section Modules -> Interrupts and Signals. How to get to the avr-libc manual is described in the last section of this guide: What else you want to know

SIG_OUTPUT_COMPARE1B is the interrupt that is fired each time the counter of timer 1B becomes the exact same value that was put in OCR1B.

OCR1B is the port where you put the next counter value at which to call interrupt SIG_OUTPUT_COMPARE1B.

TCCR1B is set to CS12 to switch the prescaler for counter 1B to a division of 256, according to the datasheet.

_BV is a makro, where BV means Bit Vector. It is used to make bit-shifting readable. Instead of _BV(x), you can write (1 << x). Using the makro makes your code more readable, though.

CS12 corresponds to a name in the ATMEGA8 datasheet and is a simple preset value for the prescaler for timer/counter 1B.

TCNT1 is the actual counter value for timer/counter 1. You can set it to whichever value you want, and the counter will continue counting from that value on.

TIMSK = _BV (OCIE1B) enables the timer SIG_OUTPUT_COMPARE1B, which makes sense when looking up OCIE1B in the datasheet.

sei() is necessary to enable any interrupts in the first place.

Again the infinite `for' loop: as soon as the timer interrupt is set, my main program just idles around. Each time the counter value hits the compare value, code execution is taken away to the interrupt's code, afterwards returning to whatever was running before (my idling main program).

What it does

This program also counts to three, but it waits precisely one second at every number. There is a lot of complicated interrupt jargon around a tiny piece of count-to-three code. However, as soon as you study those datasheets, the strangeness of all the letters and numbers gradually decreases. It ends up being a very simple program.

[Next section]
login 2010-08-14 02:17