Main Menu


Electrons :: Articles :: AVR :: AVR-GCC Programming Guide
AVR-GCC Programming Guide
Description: Learn how to code for these nifty microcontrollers using the excellent AVR-GCC.

  • 1. AVR-GCC Programming Guide
AVR-GCC Programming Guide
Copyright (C) 2003,2004, Psychogenic Inc. All Rights Reserved.

When you've decided that an AVR microcontroller is right for a particular task, it's time to design the system--both hardware and software. Here we will cover the basics of installing and using Free software, such as AVR-GCC, to build programs written in assembly, C and C++. The focus in this tutorial is on the setup and use Linux development workstations, but just about everything should apply to other Unix platforms. Much of it should also apply to the Win32 builds of avr-gcc.


Required Components

In order to have a complete AVR development platform you need the following components:


If you have an RPM based distribution and would like to save some compile time, locate and install these RPMs:


They have worked quite well for us on RedHat 9 systems. RPM versions indicated are current as of this writting, be sure to check for fresher RPMs when you download.

You might also wish to try the CDK4AVR Cross Development Kit for Atmels AVR, which provides a unified set of RPMs for AVR development including binutils, gcc, avr-libc, gdb, simulavr, avrdude and more. We've yet to try these but the sites goal is "to provide a full featured tool chain distribution of all the free resources, because most of the stuff is scattered on the Internet and only compileable by wizards or professionals". We will try to describe a simple process for anyone to install the tools from source but the site does simplify things if you are using an RPM based distro.

Manual installation involves retrieving the source for the above requirements and compiling/installing them. It may be a good idea to select a directory under which you will install all the AVR related software, perhaps under your home directory if you are the only person who will use the programs or don't have rootly powers on the development system. In the following, we will assume you've opted to install everything under /usr/local/AVR/ (customize the instructions accordingly).


Begin by installing the BinUtils, compiled for our AVR target, by running

$ ./configure --prefix=/usr/local/AVR --target=avr
$ make
# make install

Assuming your install directory was empty beforehand, it will now contain

$ ls /usr/local/AVR/
avr  bin  info  lib  man  share
Have a look around... you will see that /usr/local/AVR/bin/ contains a number of executables to create and manipulate AVR object files. Run the AVR assembler (avr-as) with the --help switch to see a list of supported AVR MCUs:
$ /usr/local/AVR/bin/avr-as --help
Usage: /usr/local/AVR/bin/avr-as [option...] [asmfile...]

Known MCU names:
  avr1 avr2 avr3 avr4 avr5 at90s1200 attiny10 attiny11 attiny12 attiny15
  attiny28 at90s2313 at90s2323 at90s2333 at90s2343 attiny22 attiny26
  at90s4433 at90s4414 at90s4434 at90s8515 at90s8535 at90c8534 at86rf401
  atmega603 atmega103 at43usb320 at43usb355 at76c711 atmega8 atmega83
  atmega85 atmega8515 atmega8535 atmega16 atmega161 atmega162 atmega163
  atmega169 atmega32 atmega323 atmega64 atmega128 at94k

To access the man pages for these programs, you will either need to enter the full path to the file manually

$ man /usr/local/AVR/man/man1/avr-as.1
or simply edit /etc/man.config and add /usr/local/AVR/man to the list of MANPATHs:
MANPATH /usr/local/AVR/man

You can then run manpath or man avr-as to verify that your changes have taken effect.

This is also a good time to add /usr/local/AVR/bin to your PATH.


AVR support is now included with GCC, so all you need is the latest release from a GCC site mirror. When you've unpacked the source, cd into its directory and run configure to specify the avr target and destination directory:

$ ./configure --prefix=/usr/local/AVR \
              --target=avr --enable-languages="c,c++" \

Then, make the compiler and install it:

$ make
# make install

In the above, we enabled both C and C++ compilation. If you are certain you will never be writting C++ for the AVR, you can change it to --enable-languages=c.


The final essential piece, is the AVR LibC library. If you want the latest source, or are having difficulty locating a fresh release tar ball, you can fetch the files from CVS:

$ export CVS_RSH="ssh"
$ cvs -z3 -d:ext:anoncvs@savannah.nongnu.org:/cvsroot/avr-libc co avr-libc

To build these, you may have to go through some particular hoops. Specifically, as they're probably won't be any Makefiles or configure script with the CVS sources, you'll need to use autoconf/automake. The AVR LibC folks have prepare a few scripts to make things easier, but you may still have to go through some annoyances such as the usual over-reliance on specific autoconf/automake versions. Do configure and build the package, run:

$ cd avr-libc
$ ./doconf --prefix=/usr/local/AVR
$ ./domake prefix=/usr/local/AVR
# ./domake prefix=/usr/local/AVR install

GDB and SimulAVR

SimulAVR may be used in conjunction with avr-gdb to simulate and debug AVR programs. Installation and use of these programs is detailed on the AVR-GDB page.

Building Programs

Here we go over the basic steps involved in creating, compiling and linking AVR projects using the tool chain we've just installed.

Writing Assembly

We won't be providing a course on the AVR instruction set or assembly programming here, a quick web search should reveal numerous pages and tutorials, such as Gerhard Schmidt's AVR-Assembler-Tutorial site. An excellent source of information is Atmel's own AVR Assembler User Guide.

One assembly specific detail worth mentioning is that, although the AVRs possess 32 general purpose registers, there are certain restrictions--namely that certain operations (five constant arithmetic and logic instructions between a constant and a register and the load immediate, LDI, instruction) may only be used with the second half of the registers (16 through 31).

It important to note that the tutorial listed above, along with most other information and asm code for the AVR you are likely to find on the web, are often unsuitable for use with avr-gcc and the avr assembler. Though the instruction set is uniform throughout the AVR family, the specific assembly directives used (for instance to include header files and defined constants) in the source available on the web may vary from those used by the binutils AVR assembler, avr-as. To use this source code with avr-as, you'll need to make a number of changes such as:

  • Adding an architecture definition, such as ".arch atmega8"
  • Changing ".def a=b" and ".equ a=b" lines to "#define a b"
  • Replacing any .include directives by equivalent #includes
  • And likely more changes we are unaware of...

Even with thes modifications, there's a good chance a random AVR asm file you've snatched off the internet still won't compile--either as it's attempting to .include one of the Atmel .inc definition files or because it is using some other Atmel DOS assembler feature like .org to organize the vector table.

So you have two choices. You can stick with avr-as and avr-gcc, using the already installed toolset and writing avr-gcc-compatible assembly code or using C inline asm() and renouncing the benefits of all the DOS AVR software out there. You may also choose to install one of the programs which compiles code written for Atmel's AVR DOS assembler, such as the GPLed "Tom's GNU/Linux AVR Assembler". This option is detailed below in the Assembly with tavrasm section.

Assembly with AVR-AS

You can get to know the avr-as assembler by consulting its info page:

$ info avr-as

and the complete AVR instruction set is described in many only tutorials and in the AVR Assembler User Guide. But a good, hands-on, way to get to know the GCC assembly, if you are familiar with C, is to write a simple C program and have GCC convert it to a .s assembly file, using

$ avr-gcc -Os -mmcu=atmega8 -I/usr/local/AVR/avr/include/ -S test.c

One problem with this approach is that you'll be missing out on all the helpful constants defined in the avr/io.h file and raw register names, so you will wind up with stuff like "out 56-0x20,r24" instead of the more meaningful "out PORTB,mydefinedregistername" and will see many of the details (e.g. data segment initialisation) which are normally hidden by avr-gcc.

Here is a short assembly program you can use to test compilation and linking (save this as test.S):

#define mp r16
#define outerwait r17
#define innerwait r18
.global main
/* main function sets port B for output
** and loops to alternate
** all on/all off
        ldi     mp,0xFF
        out     DDRB,mp

        ldi     mp,0x00
        out     PORTB,mp
        rcall  waitabit

        ldi     mp,0xFF
        out     PORTB,mp
        rcall  waitabit

        rjmp    loop     /* loop forever */
/* waitabit function, busy wait called to
** leave portB state stable for a few clock
** cycles.
        ldi outerwait,0x77
        ldi innerwait,0xFF
        subi innerwait,0x01
        brne waitinnerloop
        subi outerwait,0x01
        brne waitouterloop

You can see that this code uses a good deal of C-like syntax, with /* comments */, #includes and #defines. The included file is the same as that used in C projects and contains a number of I/O related definitions, such a for PORTB.

Attempting to feed this source code to avr-as directly will fail, partly because the includes and defines will be ignored. The file should instead be compiled by avr-gcc so it can take care of the preprocessing and other tasks.

While compiling, you will need to specify the architecture you are creating your program for (which of the AVR family this program will run on). To do so, use the -mmcu command line argument to avr-gcc:

$ /usr/local/AVR/bin/avr-gcc -Os -mmcu=atmega8 -Wa,-gstabs -I/usr/local/AVR/avr/include/ -c test.S

Here, we've selected to compile for the ATMega8 platform (see the avr-gcc documentation for the list of supported microcontrollers).

As this is a simple program, entirely contained in the single test.S file, we could combine compilation and linking by omitting the -c switch and adding a -o outputname:

$ /usr/local/AVR/bin/avr-gcc -Os -mmcu=atmega8 -Wa,-gstabs -I/usr/local/AVR/avr/include/ -o test.elf test.S

The resulting ELF file, test.elf, is now ready to be converted to a hex format suitable for use by a programmer (to upload the program to the uC).

Even when writing assembly, avr-gcc hides many of the tiresome details such as vector table management and initialisation. A commented disassembly of the program, obtained using:

$ /usr/local/AVR/bin/avr-objdump -S test.elf > test.disasm

will reveal all. The most interesting section in test.disasm is the vector table:

Disassembly of section .text:

00000000 <__vectors>:
   0:   0c c0           rjmp    .+24            ; 0x1a
   2:   12 c0           rjmp    .+36            ; 0x28
   4:   11 c0           rjmp    .+34            ; 0x28
   6:   10 c0           rjmp    .+32            ; 0x28

which is generated by avr-gcc, right at the beginning of the file (RESET interrupt at address 0 and 12 others, for the ATmega8, with default avr-gcc behavior)

Atmel Assembly with tavrasm

If you are interested in using asm source code written for Atmel's DOS Assembler, or if you simply want to do some experimenting while learning from online AVR assembly tutorials, you will need to use an assembler other than avr-as. There are many of these available on the web, a number of which have Linux support.

One assembler we've used successfully is tavrasm: Tom's GNU/Linux AVR Assembler. The program deals with a superset of Atmel AVR assembler, can save programs to numerous formats (Intel Hex, Motorola S-record, etc.) and is Free software (note the capital F: the code is released under the GPL). Building the program is a simple matter of

$ cd tavrasm.XXX
$ gmake

and copying the resulting tavrasm executable somewhere in your PATH. Once you have the compiler installed, you will still need to install the Atmel definition files. Much of the AVR software available assumes you are working on an MS32 workstation, using Atmel's own assembler or compiler. As such, lots of the software contains references to standard Atmel definition files, e.g. 1200def.inc, 8515def.inc and others. These files offer a layer of abstration, providing uniform symbolic names for things like I/O registers (whose addresses change from chip to chip).

You can retreive the files from the AVR section of the Atmel site. Check for the latest AVR Family Assembler in the Tools and Software section. If you download and unzip the AVR Family Assembler without Setup, you will see that it contains a number of .INC files in the APPNOTES/ directory. In the zip file we downloaded all the definition files had UPPERCASE.INC names, even though it is customary to use lowercase names in the source files. This is a MSWindows shortcomming, as it is a (case-)insensitive clod and will treate FILE.INC and file.inc as the same file. To remedy the situation, you can run this on the command line from within the APPNOTES dir:

$ for i in *.INC; do cp $i `echo $i | sed -e 's/\(.*\)/\L\1/g'`; done

Move the .inc files to a permanent location, for instance /usr/local/AVR/include/asm/ and make sure you add an -I /usr/local/AVR/include/asm/ argument to add the location to the search path.

Writing C

Writing programs in C is easy thanks to GCC and the AVR LibC library, which provides a subset of the standard C library for use with AVRs. The library also include a "start-up" module to setup the environment before execution. The start-up module initialises the default interrupt handlers, watchdog and data and .bss segments, then jumps to main().

A Simple Program

Here is the absolute simplest C program you can write for the AVR (or pretty much any platform):

    /* Empty program, does nothing */
    void main (void)

This program does exactly nothing. But it will compile and you can run it on the microcontrollers. The most noteworthy thing about this program is that it doesn't return anything. Even if you call the AVR LibC version of the stdlib.h exit function the program never actually returns or exits, as there is no environment to return or exit to. Explicitly calling exit() or allowing an implicit call to default _exit function both get compiled to the following assembly:

    rjmp    .-2

which just means "jump back to this instruction"... i.e. loop forever doing nothing. The only way to get out of such a loop is through an interrupt or reset.

Building the Simple Program

The empty program is very useful in getting to know the AVR LibC's internal operations. Save the program to a file called empty.c and customize our AVR project Makefile, Makefile.tpl, such that it contains:

# Name of our project
# (use a single word, e.g. 'myproject')

# Source files
# C source:
# (list all files to compile, e.g. 'a.c b.c c.c'):

and save it as "Makefile" in the same directory. We will use one of the useful Makefile targets, debug, to have a look at some of the AVR LibC magic that goes on behind the scenes. Be warned that we will be looking at a few assembly instructions, but it's all quite simple and amply described so don't worry :-). Now type:

$ make debug

to build the program (by default, for the AT90S8515 AVR uC), empty.out as well as a number of other files.

AVR LibC Default Setup Behavior

Open the empty.s file (created with make debug earlier) in your favorite editor--it contains the original C program interspersed with the compiled program assembly and additional comments.

The very first part of the program in this file is the vector table. This is where the microcontroller creates a table to match every interrupt with a corresponding instruction address to jump to:

empty.out:     file format elf32-avr

Disassembly of section .text:

00000000 <__vectors>:
   0:   0c c0           rjmp    .+24            ; 0x1a
   2:   26 c0           rjmp    .+76            ; 0x50
   4:   25 c0           rjmp    .+74            ; 0x50

The very first entry, at program memory address 0, is the RESET vector (activated when power is applied or the reset pin is brought low). This table entry instructs the microcontroller to jump 24 steps past the next address (which is 2), so all the way to location 26 (0x1a in hexadecimal). If you scroll down to the line marked 1a:, you will see that this is the first instruction following the vector table itself.

None of the other interrupts have been set in our very simple program, thus they all follow the default behavior as dictated by AVR-LibC. They all jump to the instruction at address 0x50, which is:

00000050 <__bad_interrupt>:
  50:   d7 cf           rjmp    .-82            ; 0x0

__bad_interrupt, which in turn just jumps back to 0x0--the RESET table entry. Therefore, should any interrupt be triggered, it will reset the microcontroller and restart execution from the beginning (though this should not happen as we haven't enabled anything in our program).

Starting at address 0x1a, the program does the default setup for the data and bss segment and then finally calls our main() function:

: /* Empty program, does nothing */ void main (void) { 52: cf e5 ldi r28, 0x5F ; 95 54: d2 e0 ldi r29, 0x02 ; 2 56: de bf out 0x3e, r29 ; 62 58: cd bf out 0x3d, r28 ; 61 return; } 5a: 00 c0 rjmp .+0 ; 0x5c 0000005c <_exit>: 5c: ff cf rjmp .-2 ; 0x5c

Though our main() function only contained a return statement, AVR-LibC has added some default behavior. In the lines marked 52: and 54:, the program is simply loading some values (which are microcontroller dependant, and selected according to the MCU set in the Makefile) into registers 28 and 29. At addresses 56 and 58, the values stored in the registers are copied to locations 0x3e and 0x3d. What the program is actually doing in these four instructions is setting up the stack pointer, by storring correct values in the low and high bytes of the 16 bit SP. This means that, as a C programmer, you can pretty much ignore the stack as AVR-LibC handles it all, and just be happy that its there :)

The single instruction we included in our program, return, jumps execution into the _exit infinite loop as discussed previously.

AVR LibC Specific Programming Notes

Here are a few AVR LibC-specific notes you should know when coding using the library.

Library Functions

In addition to the AVR specific functions (stuff like watchdog timer setup), a number of the standard C library functions are supported by AVR LibC: string functions like strcpy, strlen, etc. math functions like sin, cos, sqrt and pow, I/O functions like printf and scanf and more. You must remember though that the entire standard library isn't available--certain functions have yet to be implemented (perhaps you can help?) while others may not be possible or even make sense on the platform. Check the AVR LibC docs before you code.

ROM Strings

If you are using many constant strings in your application, for instance while using an LCD display or speaking to a serial port through the UART, you'll want to save RAM by storing the constant strings (and leaving them) in the comparatively vast ROM. You can do this thanks to the AVR LibC Program Space String Utilities. By adding an


to your C source file, you gain access to functions which allow access to data stored in the device's program space (the flash memory ROM). With the program space string utilities, you can declare and manipulate PGM_P variables: pointers to strings in program space. In addition to low level pgm_read_* functions, they provide a number of _P equivalent string manipulation functions, such as

  • void * memcpy_P (void *, PGM_VOID_P, size_t)
  • char * strcat_P (char *, PGM_P)
  • int strcmp_P (const char *, PGM_P)
  • char * strcpy_P (char *, PGM_P)
  • size_t strlen_P (PGM_P)
  • and more...
Function Register Access

Lots of the microcontroller programmer's work involves dealing with I/O and other function registers, often on a bit-by-bit basis. AVR-LibC provides two methods by which this may be accomplished. You may use specific IO instructions on IO address space, e.g.

    outb(PORTB, 0xFF);

Or you can choose to use the symbolic address directly:

    PORTB = 0xFF;

The former method may be preferable if you plan to compile the program using other AVR platform C compilers. The latter has the advantage of clarity. For instance, here are two equivalent instructions, taken from the AVR LibC documentation:

    outb(DDRD, inb(DDRD) & ~LCDBITS);
Interrupt API

The vector table, described above, is set to point interrupt routines with predetermined names. The library provides default interrupt routines, which get used unless overridden.

If you wish to provide an interrupt routine for a particular signal you must, in addition to any required AVR setup, create the function using one of the SIGNAL() or INTERRUPT() macros, along with the appropriate signal names. There are a number of preset signal names, such as

  • SIG_ADC (ADC conversion done)
  • SIG_INTERRUPT0..7 (external interrupts 0 to 7)
  • SIG_OVERFLOW0..3 (timer/counter overflow)
  • SIG_UART0_DATA, SIG_UART0_RECV, SIG_UART0_TRANS (UART empty/receive/transmit interrupts)
  • etc.

To create a interrupt routine, select a macro-signal combination and

        /* Your overflow handler routine */

For the same overflow signal, you could have written

        /* Your overflow handler routine */

The difference between the two is the state of the global interrupts as your enter the function. In the case of INTERRUPT(), global interrupts are initally enabled whereas for SIGNAL() they are disabled (your routine can not be itself interrupted).

Things to remember when using interrupts:

  • The list of signal names is constant but exactly which interrupts will be available to you depends on the specific AVR microcontroller flavor you select. Check the datasheet.
  • Perform your peripheral setup before you enable interrupts. A number of steps may be required, depending on the specific peripheral. For instance, here is the setup for a 16 bit timer:
      /* ... */
      /* enable the timer/counter1 overflow interrupt in the T/C int mask register */
      /* no compare/pwm/capture mode */
      TCCR1A = 0;
      /* setup this timer's prescaler */
      /* Setup the timer starting value */

    A number of constants and function registers are used, the MY... values are user defined while the others are set through the io.h header. The important thing here is to notice that it is often important to perform a good deal of initial setup before a peripheral and its interrupts may be used.

  • When you are ready to receive interrupts, enable them by using:
      /* ... */
      /* enable global interrupts */

Writing C++

You can write programs for the AVR platform in C++, if you included c++ in the enabled-languages during configuration of avr-gcc. Just about everything in the Writing C AVR programs section applies, so read that first.

The major drawbacks of using C++ are:

  • C++ calling convention side-effects
  • No libstdc++ support.

C++ calling convention side-effects

Certain C++ features will automatically generate implied code if required, which can waste valuable program memory space and processor time. For instance, if at some point in the program a function is passed a C++ object by value:

    void myfunction(MyCppClass object);

You will wind up with a default copy constructor being generated and called to create the temporary copy of object used in myfunction(). Be careful if this isn't what you want: equivalent behavior should be attainable by passing a reference to a constant MyCppClass object, while avoiding the code and execution overhead.

Missing libstdc++ and other C++ features

None of the C++ standard templates, classes or functions are available. In addition, operators new and delete have yet to be implemented.

C++ exception support is also lacking. You'll probably need to make sure to use the -fno-exceptions compiler option to turn off the exceptions in the C++ front-end.

What does work? Even though lots of the C++ goodies you are accustomed to working with aren't available, it can be worthwhile to program the AVR in C++. Constructors and destructors are functional and just the organizational advantages of using classes and object oriented programming may make C++ a great choice.

Building and Installing Programs

You can build programs in much the same way as usual using gcc. The main difference in compiling is that you will use avr-gcc and avr-g++ rather than the regular gcc and g++.

However, when creating programs for the AVR platform, the final linked program produced by avr-gcc--in ELF format--isn't suitable for installation on the 8 bit microcontrollers. The format required depends on which hardware and software programmer combination you use and will normally be some form of ascii encoded hex (ihex, srec and others). Many programmers, such as avrdude, accept Intel hex format.

A very simple way to manage your AVR projects is to use our AVR Makefile template. Detailed instructions on customizing the Makefile, which allows you to manage C/C++ and assembly source files, for your project are available on the AVR Makefile Template details page.

You can perform all the steps manually and we will describe a short, fictitious, example here. It is a three source file project to be compiled for the AT90S8515 microcontroller. The files are:

  • myclass.cpp - a C++ source file
  • functions.c - a C source file
  • lowlevel.S - an assembly file

We start by compiling each of the files using an appropriate command and then link them all into the myproject program.

$ avr-g++ -mmcu=at90s8515 -I. -Os -g -Wall -fno-exceptions -c myclass.cpp
$ avr-gcc -mmcu=at90s8515 -I. -Os -g -Wall -c functions.c
$ avr-gcc -mmcu=at90s8515 -I. -Wa,-gstabs -Wall -x assembler-with-cpp -c lowlevel.S 
$ avr-gcc -o myproject.elf  myclass.o functions.o lowlevel.o

Note that the -mmcu argument is used to let AVR-GCC know the specific AVR microcontroller for which we are compiling. The -g argument is given to the compiler and -gstabs option is passed to the assembler in order to allow tracing through the program with avr-gdb.

The linked file, myproject.elf, may be used to simulate and debug the program. To actually install it on an 8515 microcontroller, we need some to generate a file in a suitable format and some hardware to upload it. To create a file that we can upload using the avrdude programmer, we use avr-objcopy to extract the .text segment and store in the correct format (Intel hex, ihex, in our example) as follows:

$ avr-objcopy -j .text -O ihex myproject.elf myproject.hex

The myproject.hex file produced may now be uploaded to the chip's flash program memory.

This entire process is greatly simplified by using the AVR Project Makefile Template. All the steps described above, including actually uploading the hex file to the chip using avrdude, would be performed by issuing this command:

$ make writeflash

So have a look at our AVR Makefile tutorial or just download the Makefile template directly and have a look at the comments within.

You should now be more than ready to create, build and install your own AVR projects! Happy hacking :)

Level: Article
Additional Article Data
Level: Article

The comments are owned by the poster. We aren't responsible for their content.

Jump to section

All contents are Copyright (C) 2004-2005 Psychogenic Inc -- All rights reserved