06-21-2009 08:44 PM
The AVR microcontrollers from Atmel are extremely popular among hobbyists. They are a low cost general purpose micro-controller, with an excellent open-source, fully-featured C compiler. There are several dozen available AVR chips, all with a common core, but some small differences. For this tutorial, we will focus on the ATMEGA168, which is often found in the Arduino project, and is one of the best supported chips in the hobbyist arena.
Microcontrollers in General
A microcontroller is sort of like a small computer, but also sort of isn't just a small computer. So what sets a microcontroller apart from a regular desktop computer?
- Low Cost. Some AVR chips are less than $2, even the largest chips are under 10 bucks.
- Low Power. An AVR consumes only a few milliamps at 3.3 or 5V.
- Hardware Interrupts allow you to monitor the state of a pin, and in real-time, find out when it changes. Useful for many things on a robot, such as bump switches.
- Hardware Timers/Counters and PWM allow serious motor control and feedback through encoders.
- Digital I/O allow interaction with the world of electronics.
- Analog to Digital Conversion allow interaction with dozens of sensors which output analog voltages between 0 and 5V.
- Serial protocols such as I2C, SPI, and USART allow AVRs to talk with other high-level chips, and PCs.
- Non-SMD, most processors today have an enormous number of pins, and have very complex pin configurations that require surface-mount soldering, which is quite difficult. While all AVR chips are also offered in an SMD casing, most of the small to mid-size ones are also available in good, old-fashioned Dual-Inline-Pin cases, meaning you can use them with a breadboard or perf board.
Like computers, microcontrollers typically have several types of memory:
- FLASH ROM is non-volatile memory (doesn't change when the power goes out) where you put your code. A typical AVR has between 2k and 128k of FLASH memory. Changing this memory requires either an In-System Programmer, or special commands that must be run inside a bootloader (more on this later). The larger your program is, the more FLASH you will consume to store it.
- EEPROM is non-volatile memory, that is byte-addressed, and easily changed at run-time. A typical AVR has between 512 bytes and 8k of EEPROM.
- RAM is volatile memory that stores your variables and data at run time. A typical AVR has between 512 bytes and 16k of RAM.
First off, we need some way to compile the code we write into a form that will run on the AVR microcontroller, and a way to install the code onto the AVR, since it lacks things like a keyboard, monitor, or floppy drive. The AVR-GCC compiler is a special version of GCC, the GNU C Compiler, targeted at the AVR architecture. It includes an assembler specifically for the AVR, so that we can compile C and assembly code into the instructions the AVR undertands. A second piece of software, called AVRDUDE is used to actually download the code into the chip. Installation is quite simple:
- Under Windows, you can install WinAVR, which already incorporates AVRDUDE, AVR-GCC, and a code editing program called Programmer's Notepad.
- Under Ubuntu, type sudo apt-get install gcc-avr avrdude avr-libc. If it's not already installed, you'll also need make.
However, AVRDUDE needs some way to communicate with the chip. The most common way is an In-System Programmer (ISP) that communicates with your computer and can write your program into the FLASH memory of the AVR. I really like the Pololu ISP, for it's low cost and ease of use. In the olden days before re-writable memory was cheap, you literally loaded the code in just once, burning it into the memory. Today, we often still call the act of loading a program into the chip burning.
An alternative to using an ISP is to have a bootloader on your AVR. A bootloader is simply a piece of code that runs when the AVR is reset. It takes data from a serial connection and writes the program into it's own FLASH memory. A bootloader has several downsides:
- The bootloader takes up precious space in the FLASH memory of your AVR (typically ~2-3 kilobytes worth).
- You'll probably have to put a USB->serial converter on your board.
- You'll have to either press the reset button to program your chip, or deal with your AVR resetting each time the USB is plugged in.
- You need an ISP to burn a bootloader onto the AVR...
Wiring it Up
Our basic connections for the AVR are quite simple: we need a power source, a system clock, and an ISP connection. There are a number of good AVR boards out there, which are designed to house all the necessary equipment while making the AVR breadboard compatible.
The power source is quite simple: we need a 5V regulator. If you have a battery or wall-wart with voltage > 7V, you can just use a 7805 (with the appropriate capacitors and stuff). The AVR chip has multiple voltage and ground pins we need to connect. You should also try to place an 0.1uF decoupling capacitor as close as possible to each VCC pin. You'll notice we also connected the AREF pin to 5V, which is quite typical, this sets the reference voltage for the Analog-to-Digital Converter.
While the AVR chips have an internal 8MHz, we often want to use an external clock source so we can have a higher clock speed. When you buy a new AVR from the factory, it will have it's fuse bytes programmed such that it is using the internal 8MHz, divided by 8. <discuss fuse bytes?>. In the schematic below, we have labeled the "optional external XTAL", which is simply a full-swing crystal oscillator, the crystal requires two capacitors to connect it to ground.
The 6-pin ISP header is standard equipment on all AVR boards these days. You may also see a 10-pin version which is the older, and now less popular version.
A reset switch is quite useful. The reset pin is internally pulled high, the switch should be between reset and ground.
The AVR Architecture
There are two main branches of AVR chips: the ATTINY and ATMEGA branches. Chips are named such that the name conveys the family and how much FLASH the chip has. For this tutorial we will focus on the ATMEGA168 (you'll see why shortly), this chip has 16k of FLASH ROM, denoted by the 16 in the part number, and is code-compatible with the ATMEGA88 and ATMEGA328. The compatibility is denoted by the last number, the ATMEGA88 is an 8k version, and the ATMEGA328 is a 32k version. RAM and EEPROM size typically scale with the FLASH size. There are a couple very commonly used families out there:
- ATMEGA88, ATMEGA168, ATMEGA328 - available as 28-DIP devices, 24 I/O lines.
- ATMEGA164P, ATMEGA324P, ATMEGA644P - available as 40-DIP devices, have dual serial ports, 32 I/O lines.
AVR is a memory-mapped architecture. That means when we want to interact with specialty hardware, such as our analog pins or timers, we have to read and write to special memory locations (known as registers), rather than having special instructions for each piece of hardware. This keeps the instruction set small, and also means it is fairly easy to interact with any of the AVR hardware.
With most of our hardware, we interact in a fairly consistent way. There are typically a few configuration registers to set up the hardware the way we want it to work, a register to put data into the device, and a register to get data back from the device. We'll look at the implementation of the digital I/O ports to start our study of this.
External pins on the AVR are group together into ports of 8 pins. Our device has a port called port B, composed of the 8 digital I/O pins, PB0 through PB7. We have 3 registers related to the port, in which pin PB0 would correspond to bit 0, or the least-signifigant bit each register:
- DDRB is the data-direction register, which defines whether each pin is an input (0) or an output (1). If we set DDRB = 0x0F, our upper four pins (PB4-PB7) are set as inputs, while our lower four pins (PB0-PB3) are outputs.
- PORTB is the data register, if our pins are set as outputs, we can write a value to the pins. Writing a 1 to any of the bits will set an output high, 0 sets it low. An interesting thing to note, is that if we have our pin setup as an input, and write our data register high for that pin, an internal pull-up resistor will be activated.
- PINB is the read data register, it is read only, and returns the actual values of the pins.
This three-register scheme is true of all external ports on all AVR devices. <avr/io.h>, which is part of avr-libc, includes the definitions for your register names. At reset, all of these registers are set to 0, so all pins are inputs with pull-up disabled.
The analog to digital converter is similar to the digital pins in setup: there is a register for setting the clock speed of the converter, a register to set which pin to read and do a conversion from, and a register for reading back the result.
Setting up a timer/counter is also very similar. Each of the AVR's timers have several related registers: registers for setting up the clock speed and interrupt style of the timer, registers for setting the output width of the comparision channels, and a register where the actual count is stored.
So where do you find information about all these registers? Atmel publishes massive datasheets for each AVR it produces. These datasheets list every register in the AVR, and what the individual bits mean. They discuss, in depth, the setup of the chip. There are also a number of application notes that discuss in depth a particular use of the processor. Unfortunately, the language is often designed for embedded engineers, however google + forums can help with that.
Your First Program
Using the AVR is quite simple. We'll make a program that will blink an LED on the ATMEGA168, when connected between PB0 and ground. For now, we'll assume we are using the internal 1MHz oscillator, which is the default. First, we need to create a source file in C, we'll save the following as example.c:
Next we have to compile and upload. Download the makefile I've attached, you may have to edit the port or programmer names. Once you have the power on to your AVR, and have attached the ISP, run:
// example.c -- blinks light on PB0 of an AVR.
#define F_CPU 1000000UL
// Make PB0 an output
DDRB = 0x01;
// loop forever
// led on, wait 1/2 sec
PORTB = 0x01;
// led off, wait 1/2 sec
PORTB = 0x00;
Your LED should start blinking after the download is complete. If you know make, a quick look through the makefile, and the AVRDUDE manual, will get you going in no time.
The Arduino Approach
Many people out there use the Arduino. An Arduino is just a slightly special AVR, in fact it is just an ATMEGA168 programmed with a bootloader. The Arduino IDE makes developing with the AVR slightly simpler as you won't have to worry about makefiles and such, or ISP devices. The Arduino software installs very easily and you won't have to learn much about the underlying AVR architecture to do lots of simpler things.
That said, understanding the underlying architecture, and being able to use it effectively, allow you to do a lot more than you can with just the Arduino libraries. For instance, the Arduino libraries include an interrupt library, but it only extends to the 2-3 dedicated external interrupts. There is no library for using the newer pin interrupts -- you have to understand the AVR architecture, and how to manipulate registers, to be able to use this powerful feature. The Arduino libraries can do a lot, but real Arduino power-users will understand the other features of the AVR as well.
Note also, that you can use an ISP with the Arduino environment. In fact, I use the Arduino environment on most projects simply because it is easier to share them with others, but I don't use bootloaders on hardly any of my chips.
The Seattle Robotics group has some excellent example code for a workshop they run using the ATMEGA16
Slides and code from a series of workshops I ran during summer 2009 are available on my site. Although our focus was on the Arduino, we typically went deeper into the AVR architecture while discussing the Arduino.
Replies to Tutorial: AVR Basics
Re: AVR Basics
Hey, I was wondering If this would be a good buy ( the price to me seems excelent :P ). It is an Atmel ATmega328 with a preloaded Arduino Bootloader, which to me seems a good buy. It is about $9.50 . Because it's pre-loaded with the bootloader, you could use Arduino code, which makes it a big 'yes' from me. Any opinion on the matter?