The AVR family of 8-bit microcontrollers from
Atmel are quickly growing in popularity for electronics and robotics hobbiests. Their major selling point for me, as opposed to the PIC microcontrollers from
Microchip, are their being designed to be developed using higher languages such as C. This allows them to be developed using the open-source GNU tools (gcc). This works out especially well for me working in Linux.
This tutorial series is for working with
avr-libc, the C library for AVRs using the GNU tools. This first tutorial will simply get you familiar with a basic C program for an AVR. Like so many other introductory tutorials, we'll simply be turning an LED connected to the microcontroller on and off.
Download the project file:
tutorial_1.tar.gz
This Tutorial Introduces...
- Basic include files for using the avr-libc with gcc.
- Using a pin on the AVR as a digital output.
- Clearing and setting a bit (ouput pin) using C bitwise operators.
- Compiling and AVR project using a simple Makefile.
Prerequisites
- Have a working installation of the GNU tools for AVR. See: Installing the GNU tools for AVRs.
- Have an AVR programmer (hardware) and a working installation of AVR programming software (such as avrdude). See: Setting up the AvrUsb500 and avrdude.
- Have a basic, fundamental, understanding of the C language. You should understand the basics of header files, pointers, control structures, and compiling/linking. I reccomend the book: The C Programming Language 2nd Ed.
- A basic knowledge of electronics concepts, however, I'll also explain the electronics when describing the cirtcuits.
- Make sure you have a copy of the ATMega8 datasheet (or the datasheet for whatever AVR you're using).
- Make sure you either have downloaded the avr-libc reference or have the online manual bookmarked.
Circuit
The circuit is very simple. PC0 is being used as a digital output pin. We connect an LED through a 1k resistor to PC0 and VCC. We can connect the LED directly to the IO pin beacuase ohms law tells us that with VCC at 5V, when the PC0 pin is low, we'll be sinking 5mA of current, which is well within range of the IO pin maximum curent rating (see "Electrical Characteristics" in the
datasheet).
The RESET pin is an "active-low" reset, which means that it resets the MPU when it is digital low (tied to ground). Therefore, we hardwire the RESET pin to VCC through a resistor to limit current.
The 0.1uF capacitor should be mounted (or inserted in your breadboard) very close to the VCC pin to filter noise on the line and prevent the chip from resetting or behaving eratically. It's common practice to place a capacitor like this on each VCC input of ICs in a digital system.
It is assumed that you are using an ATMega8 as it comes from the factory. In this condition, the chip is programmed to use it's own internal oscillator at 1MHz. Therefore, there is not a crystal oscillator or other external clock source in this circuit-- it's not needed!

Click to Enlarge

Click to Enlarge
Source Code
The source code for this simple project is contained in
main.c. We're going to step through this really slowly for this first tutorial.
#define F_CPU 1000000UL /* 1 MHz Internal Oscillator */
#include <avr/io.h>
#include <util/delay.h>
F_CPU defines the clock frequency in Hertz and is common in programs using the avr-libc library. In this case it is used by the delay routines to determine how to calculate time delays. Often, this call will be surrounded by
#ifndef and
#endif so that it can alternately be defined in the Makefile (more on that later).
The first include file
<avr/io.h> is part of avr-libc and will be used in pretty much any AVR project you work on. io.h will determine the CPU you're using (which is why you specify the part when compiling) and in turn include the appropriate IO definition header for the chip we're using. In this case, we'll be using the ATMega8 and so, the
iom8.h file will be included. This file is located in
avr/include/avr relative to where you installed avr-libc. In my case, it's at
/usr/local/avr/avr/include/avr/iom8.h. If you look at this file, you'll see it simply defines the constants for all your pins, ports, special registers, etc. For the most part, the label that is used to define a port, pin, or register in the datasheet is what is defined by this header. So if the datasheet says PORTC, we now have PORTC defined for us.
The
<util/delay.h> library contains some routines for short delays and requires the F_CPU definition as discussed above. The function we'll be using, is
_delay_ms(). The
delay.h file ALSO includes
<inttypes.h>, which according to the
avr-libc reference, "
includes the exact-width integer definitions from <stdint.h>, and extends them with additional facilities". We'll be using these types throughout our code.
/* function for long delay */
void delay_ms(uint16_t ms) {
while ( ms )
{
_delay_ms(1);
ms--;
}
}
This function,
delay_ms(), uses the
_delay_ms() function as mentioned above. The
avr-libc reference for
_delay_ms() states: "
The maximal possible delay is 262.14 ms / F_CPU in MHz". Since we're using the internal 1MHz oscillator, we're going to end up with a maximum delay of 262 mS using this function. However, I want a delay of 1 whole second for my LED toggling program. So, in this function we will call the
_delay_ms() function for 1 mS in a loop. The function's ms parameter is of type
uint16_t which is an exact 16-bit data type. This function does add a few instructions making the delay not exact mS delays, however, it's close enough for many purposes. For exact timing, timers should be used which is beyond the scope of this tutorial.
int main (void)
{
/* PC0 is digital output */
DDRC = _BV (PC0);
/* loop forever */
while (1)
{
/* clear PC0 on PORTC (digital high) and delay for 1 Second */
PORTC &= ~_BV(PC0);
delay_ms(1000);
/* set PC0 on PORTC (digital low) and delay for 1 Second */
PORTC |= _BV(PC0);
delay_ms(1000);
}
}
Our main routine has two parts to it. First, we setup/initialize our AVR, and then we enter an infinate loop where our program runs.
DDRC is the Data Direction Register for PORTC (see "I/O Ports" in the
datasheet). This register determines whether the pin is a digital output or a digital input, where a 1 represents an output and a 0 represents an input. Initially, the
DDRC register is all 0 (0x00 or 00000000b). The statement
DDRC = _BV (PC0); sets the PC0 bit. _BV() is a compiler macro defined as
#define _BV( bit ) ( 1<<(bit) ) in
<avr/sfr_defs.h> which was included already indirectly through
<avr/io.h>. It stands for
Bit
Value where you pass it a bit and it gives you the byte value with that bit set.
PC0 was defined in
iom8.h as 0. So the compiler will actually place
DDRC = 0x01; in place of
DDRC = _BV (PC0);. So now we've set
PC0 as a digital output.
If you are already familiar with bitwise operators in C and how to clear and set bits, you should skip ahead to
Building the Project.
In each iteration of the infinate loop (
while (1)) we are using standard C methods for setting and clearing the
PC0 bit followed by 1000 mS delay between each set and each clear. Let's take a closer look at each of those. The first, is to
clear the bit using
PORTC &= ~_BV(PC0); which turns off the LED (remember that the LED is connected to VCC, so a logical 1 output results in little to no voltage across the LED). Remember that our
_BV macro returns the bit set in byte form, in this case, 0x01 or 00000001b. So, the statement
PORTC &= ~_BV(PC0); is actually
PORTC &= ~0x01;. The bitwise operator
~ will
"not" the value first, which results in 11111110b. So now we basically have
PORTC &= 0xFE;. With this, the bit in position 0, PC0, will ALWAYS be cleared after this statement without effecting the other bits in the byte. So regardless of the value currently in PORTC, only the bit we're clearing is changed. Take a look:
10101010 0xAA
& 11111110 0xFE
-------- ----
10101010 0xAA Bit 0 is still clear, other bits uneffected.
01010101 0x55
& 11111110 0xFE
-------- ----
01010100 0x54 Bit 0 is cleared, other bits uneffected.
Now, in this demonstration, bit 0 (
PC0) is the ONLY bit we're working with, however, you can see how clearing a bit works. After delaying for 1 second, we then
set the bit using
PORTC |= _BV(PC0);. This works very similarily to the previous explanation of clearing a bit. The equivelent statement is
PORTC |= 0x01; meaning we're "
or-ing" PORTC with 00000001b.
10101010 0xAA
| 00000001 0x01
-------- ----
10101011 0xAB Bit 0 is set, other bits uneffected.
01010101 0x55
| 00000001 0x01
-------- ----
01010101 0x55 Bit 0 is still set, other bits uneffected.
Building the Project
The code is compiled using gcc, usually installed with an avr- prefix resulting in avr-gcc (See:
Installing the GNU tools for AVRs). However, it's much easier to use a Makefile to build the project. The simple makefile that I used for this project (and use for most of my projects) is looks like this:
# AVR-GCC Makefile
PROJECT=tutorial_1
SOURCES=main.c
CC=avr-gcc
OBJCOPY=avr-objcopy
MMCU=atmega8
CFLAGS=-mmcu=$(MMCU) -Wall
$(PROJECT).hex: $(PROJECT).out
$(OBJCOPY) -j .text -O ihex $(PROJECT).out $(PROJECT).hex
$(PROJECT).out: $(SOURCES)
$(CC) $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)
program: $(PROJECT).hex
avrdude -p m8 -c avrusb500 -e -U flash:w:$(PROJECT).hex
clean:
rm -f $(PROJECT).out
rm -f $(PROJECT).hex
I then simply execute the command
make to build the project, which results in the following commands being executed:
avr-gcc -mmcu=atmega8 -Wall -I./ -o tutorial_1.out main.c
avr-objcopy -j .text -O ihex tutorial_1.out tutorial_1.hex
Two first command,
avr-gcc, generates
tutorial_1.out which is the binary compiled project in
elf format. The second command,
avr-objcopy, converts the elf file into
tutorial_1.hex which is the Intel HEX format used to download into the AVR microcontroller.
This makefile includes a program section so that I can program my AVR using the command:
make program. However, this uses avrdude on a AvrUsb500 programmer. You will have to follow the procedure for programming the chip based on whatever programmer and software you are using.
There are many, many, all-purpose Makefiles out there on the internet that you can download and use. This one is very simple and minimal-- but suites my purposes more often then not.
Download the project file:
tutorial_1.tar.gz